面向对象
面向对象思维
- 先整体,再局部
- 先抽象,再具体
- 能做什么,再怎么做
对象与内存分析
封装
封装性是面向对象思想的三大特征之一,封装就是隐藏实现细节,仅对外提供访问接口。实现细节部份包装、隐藏起来的方法。
封装有:属性的封装、方法的封装、类的封装、组件的封装、模块化封装、系统级封装…
封装的好处:模块化、信息隐藏、代码重用、插件化易于调试、具有安全性
封装的缺点:会影响执行效率
成员变量和局部变量的区别?
- 在类中的位置不同
成员变量:在类中定义
局部变量:在方法中定义或者方法的参数 - 在内存中的位置不同
成员变量:在堆内存(成员变量属于对象,对象进堆内存)
局部变量:在栈内存(局部变量属于方法,方法进栈内存) - 生命周期不同
成员变量:随着对象的创建而存在,随着对象的销毁而消失
局部变量:随着方法的调用而存在,随着方法的调用完毕而消失 - 初始化值不同
成员变量:有默认初始化值,引用类型默认为null
局部变量:没有默认初始化值,必须定义,赋值,然后才能使用 - 注意:
局部变量名称可以和成员变量名称一样,在方法中使用的时候,采用的是就近原则。
问题:成员字段为什么要私有化?
- 之所以设为private,就是不想让外部的代码直接调用。比如一个类中有10个成员变量,其中有9个是用于保存中间状态的,根本不需向外部输出,另外一个变量需要根据这个9个变量的值,才可以得出结果...还有私有成员函数,只供内部的其它成员函数调用...
值传递和引用传递?
在Java中只有按值传递,并没有所谓的按引用传递
// 基本数据类型的按值传递
public class Swap {
public static void main(String[] args) {
int x = 10;
int y = 20;
swap(x, y);
System.out.println("x(2) = " + x);
System.out.println("y(2) = " + y);
}
public static void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
System.out.println("x(1) = " + x);
System.out.println("y(1) = " + y);
}
}
/*输出
x(1) = 20
y(1) = 10
x(2) = 10
y(2) = 20
*/
// main函数调用swap函数来交换 x,y的值,然而调用函数之后发现main中x,y的值并未交换。包括在Java api中找不到一个可以交换两个变量的方法。这与Java语言的特性有关。通过一个图就可以知道上面程序的运行结果了。main函数中的x,y和swap函数中的x,y分别存放在不同的区域,在main中调用swap函数的时候,会将main中的x,y的值赋给swap中的x,y。当swap函数中对x,y交换时只是对swap帧中的x,y做交换,并不会改变main中的x,y。所以当函数返回时main中的x,y并不会改变
// 引用数据类型的按值传递,引用数据数据类型分为三种:①接口 ②类 ③数组
public static void main(String[] args) {
int []a={10,20};
System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=10,a[1]=20;
swap(a, 0, 1);
System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10;
}
public static void swap(int []a,int i,int j){
nt temp=a[i];
a[i]=a[j];
a[j]=temp;
System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10;
}
//输出
/*a[0]=10 a[1]=20
a[0]=20 a[1]=10
a[0]=20 a[1]=10
*/
// 原来引用类型的按值传递,传递的是对象的地址,由图可以看出在swap中仅仅是得到了数组的地址,并没有对数组的元素进行复制,在swap中对数组的操作是直接对main函数中数组的操作,因此swap函数返回后main函数中的a[0] ,a[1]的值发生交换
// 接口值传递
public interface IPresenter {
void codeMsg(String str);
}
public class A {
private IPresenter ip;
public A(IPresenter ip) {
this.ip = ip;
set();
}
public void set() {
ip.codeMsg("A class");
}
}
public class test implements IPresenter {
public static void main(String[] args) {
// TODO Auto-generated method stub
new A(new test());
}
@Override
public void codeMsg(String str) {
// TODO Auto-generated method stub
System.out.println(str);
}
}
对象一对一关系
Java中对象的对应关系有很多种,比如单向一对一,双向一对一,一对多,多对一,多对多等,其实现原理相同
其实可以理解为类的组合问题,把对象当做另一个的属性来操作,使得其产生对应关系(很多设计模式就是通过类的组合实现的)
class Hero {
private String name; //属性私有化 ,防止外面直接赋值和调用
private int age;
private Weapon weapon; //声明一个武器类的对象 (私有化)
//此处省略get,set方法和构造器
}
class Weapon {
private String name;
private int age;
private Hero hero; //声明一个对象
//此处省略get,set方法和构造器
}
public class One2One {
public static void main(String[] args) {
Hero hero = new Hero("刘备",35); //实例化一个对象,并且赋值
Weapon weapon = new Weapon("双股剑",3);
//建立关系
hero.setWeapon(weapon); //把武器类对象设置成英雄类中的一个属性
weapon.setHero(hero);
//用武器查找其主人
Hero h = weapon.getHero();
//用主人查找武器
Wepon w = hero.getWepon();
}
对象初始化
- 父类静态字段初始化
- 父类静态代码块、子类静态字段初始化 (接下来探究两者的顺序)
- 子类静态代码块
- 父类普通字段初始化
- 父类构造代码块
- 父类构造函数
- 子类普通字段初始化
- 子类构造代码块
- 子类构造函数
抽象类
抽象类的规则:
a、抽象类可以没有抽象方法,有抽象方法的类必须是抽象类
b、非抽象类继承抽象类必须实现所有抽象方法
c、抽象类可以继承抽象类,可以不实现父类抽象方法。
d、抽象类可以有方法实现和属性
e、抽象类不能被实例化
f、 抽象类不能声明为final
g、抽象类可以有构造方法
接口
// 接口的定义格式:
interface 接口名称{
全局常量 ;
抽象方法 ;
}
//示列:
interface IEat{
//public abstract void eat();
void eat(); //默认为public abstract void eat();
//public static final int NUM = 10;
int NUM = 10; }
interface ISleep extends IEat{
void sleep();
}
接口的使用规则:
(1)定义一个接口,使用interface关键字
(2)在一个接口中,只能定义常量、抽象方法,JDK 1.8后可以定义默认的实现方法
(3)接口可以继承多个接口:extends xxx,xxx
(4)一个具体类实现接口使用implements关键字
(5)一个类可以实现多个接口
(6)抽象类实现接口可以不实现接口的方法
(7)在接口中定义的方法没有声明 访问修饰符,默认为public
(8)接口不能有构造方法
(9)接口不能被实例化
Java 8新增
(1)增加了default方法和static方法,这两种方法完全可以有方法体
(2)default方法属于实例,static方法属于类(接口)
(3)接口中的静态方法不会被继承,接口中的静态变量会被继承
多态
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性
多态性我们大概可以分为两类:
(1)方法的重载与重写
(2)对象的多态性
用父类的引用指向子类对象(用大的类型去接受小的类型,向上转型、自动转换)
Chicken home = new HomeChicken();
class A{
void fun1(){}
void fun2(){}
}
class B extends A{
void fun1(String a){} //重载fun1
void fun2(){} //重写fun2
}
class C{
public static void main(String[] args){
A a = new B();
a.fun1(); //这里会调用A类的fun1方法,由于向上转型,B的fun1(String a) 会被丢弃
a.fun2(); //这里调用B的fun2方法,由于是new 的B对象,而B重写了fun2,所以会调用B的fun2
}
}
在编程时针对抽象类型的编写代码,称为面向抽象编程(或面向接口编程)父类通常都定义为抽象类、接口
a、方法的重载与重写就是方法的多态性表现
b、多个子类就是父类中的多种形态
c、父类引用可以指向子类对象,自动转换
d、子类对象指向父类引用需要强制转换(注意:类型不对会报异常,因为子类太多,不知是哪个子类)
e、在实际开发中尽量使用父类引用(更利于扩展)
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,
该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,
必定是使用子类中定义的这些方法(动态连接、动态调用)。
//接口多态
interface Car{
//要求 接口中有:汽车名称和售价
String getName();
int getPrice();
}
//宝马类
class BMW implements Car{
@Override
public String getName() {
// TODO Auto-generated method stub
//return null;
return "宝马";
}
@Override
public int getPrice() {
// TODO Auto-generated method stub
//return 0;
return 300000;
}
}
//奇瑞QQ
class CheryQQ implements Car{
@Override
public String getName() {
// TODO Auto-generated method stub
return "奇瑞QQ";
}
@Override
public int getPrice() {
// TODO Auto-generated method stub
return 40000;
}
}
//汽车出售店
class CarShop{
//收入
private int money=0;
//卖出一部汽车
public void sellCar(Car car){
System.out.println("车型:"+car.getName()+"价格:"+car.getPrice());
//增加卖出车售价的收入
money+=car.getPrice();
}
//售车总收入
public int getMoney(){
return money;
}
}
public class jieKouDemo {
public static void main(String[]args){
CarShop shop=new CarShop();
//卖出一辆宝马
shop.sellCar(new BMW());
//卖出一辆奇瑞QQ
shop.sellCar(new CheryQQ());
System.out.println("总收入:"+shop.getMoney());
}
}
内部类
内部类就是在一个类的内部定义的类。
成员内部类:内部类对象依赖外部类对象而存在,即在创建一个普通内部类对象时首先需要创建其外部类对象
内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段
成员内部类格式如下:
class Outer {
class Inner{}
}
Outer out = new Outer() ;// 产生外部类实例
Outer.Inner in = null; // 声明内部类对象
in = out.new Inner() ; // 实例化内部类对象
局部内部类:内部类可以作为一个类的成员外,还可以把类放在方法内定义(不常用,匿名内部类可以显示局部内部类的功能)。在局部内部类里面可以访问外部类对象的所有访问权限的字段,而外部类却不能访问局部内部类中定义的字段
注意:
a、局部内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
b、局部内部类对象不能使用该内部类所在方法的非final局部变量。
class Outer {
public void doSomething(){
class Inner{
public void seeOuter(){}
}
}
}
静态内部类
在一个类内部定义一个静态内部类:
静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它。静态嵌套类仅能访问外部类的静态成员和方法。
静态内部类中也无法访问外部类的非静态成员
class Outer{
static class Inner{}
}
class Test {
public static void main(String[] args){
Outer.Inner n = new Outer.Inner();
}
}
匿名内部类
匿名内部类就是没有名字的内部类。
匿名内部类的三种情况:
(1)继承式的匿名内部类
(2)接口式的匿名内部类
(3)参数式的匿名内部类
在使用匿名内部类时,要记住以下几个原则:
(1)不能有构造方法,只能有一个实例。
(2)不能定义任何静态成员、静态方法。
(3)不能是public,protected,private,static。
(4)一定是在new的后面,用其隐含实现一个接口或继承一个类。
(5)匿名内部类为局部的,所以局部内部类的所有限制都对其生效
局部内部类访问局部变量必须用final修饰,为什么?
当调用这个方法时,局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法被调用时会入栈,
方法结束后即弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,显然已无法使用了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也就可以继续使用了。
注意:在 JDK 1.8中取消了在局部内部类中使用的变量必须显示的使用final修饰,编译器默认会为这个变量加上final
内部类的作用
每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,
内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。
依赖外部类对象的:成员内部类,方法内部类,匿名内部类
静态内部类不依赖外部类的对象。所以,我们在项目中优先考虑选择静态内部类(不会产生内存泄露)