1.对象的多态性:
class 动物
{}
class 猫 extends 动物
{}
class 狗 extends 动物
{}
猫 x = new 猫();
动物 x = new 猫(); //一个对象,两种形态
猫这类事物即具备了猫的形态,又具备着动物的形态。
这就是对象的多态性。
简单说:就是一个对象对应着不同类型。
2.多态在代码中的体现:
父类或者接口的引用指向其子类的对象。
3.多态的好处:
提高了代码的扩展性,前期定义的代码可以使用后期的内容。(接口也符合这种意义)
4.多态的弊端:用向下转型解决
前期定义的内容不能使用(调用)后期子类的特有功能。
5.多态的前提:
1.必须有关系,继承,实现。
2.要有覆盖。
6.向上转型:
作用:
1.提高扩展性
2.限制对子类特有功能的访问。
Animal a = new Cat(); //实际上进行了自动类型提升,猫对象提升为动物类型,特有功能无法访问,只能按照动物类型进行操作。
7.向下转型:转回来。
作用:为了使用子类的特有功能。
Cat c = (Cat)a; //向下转型是为了使用子类的特有功能。
c.catMouse();
8.关于转型的注意:
对于转型,自始自终都是子类对象在做着类型的变化。
例子:
//误区:假设Animal类可以创建对象。
// Animal a = new Animal();
// Animal a = new Dog();
// Cat c = (Cat)a; //类型转换异常ClasscastException,动物对象不能转换成子类对象。狗对象也不能转换成猫对象(不存在继承关系)
9.多态例子:
abstract class Animal
{
abstract void eat();
}
class Dog extends Animal
{
void eat()
{
System.out.println("啃骨头");
}
void lookHome()
{
System.out.println("看家");
}
}
class Cat extends Animal
{
void eat()
{
System.out.println("吃鱼");
}
void catchMouse()
{
System.out.println("抓老鼠");
}
}
class DuoTaiDemo
{
public static void main(String[] args)
{
// Cat c = new Cat();
// Dog d = new Dog();
// c.eat(); //扩展性不强
Animal a = new Cat(); //实际上进行了自动类型提升,猫对象提升为动物类型,特有功能无法访问,只能按照动物类型进行操作。
//作用就是限制对特有功能的访问。
//专业称为:向上转型。
a.eat();
//如果还想用具体动物猫的特有功能,可以对该对象进制向下转换。
Cat c = (Cat)a; //向下转型是为了使用子类的特有功能。
c.catMouse();
//注意:对于转型,自始自终都是子类对象在做着类型的变化。
//误区:假设Animal类可以创建对象。
// Animal a = new Animal();
// Animal a = new Dog();
// Cat c = (Cat)a; //类型转换异常ClasscastException,动物对象不能转换成子类对象。狗对象也不能转换成猫对象(不存在继承关系)
// method(c);
// method(d);
}
public static void method(Animal a) //多态:Animal a = new Cat(); 。避免有不同的对象需要不同的函数
{
a.eat();
// a.catchMouse(); //多态的弊端:前期定义的内容不能使用(调用)后期子类的特有功能。
}
}
理解转型举例:
class 毕姥爷
{
void 讲课()
{
System.out.println("管理");
}
void 钓鱼()
{
System.out.println("钓鱼");
}
}
class 毕老师 extends 毕姥爷
{
void 讲课()
{
System.out.println("Java");
}
void 看电影()
{
System.out.println("看电影");
}
}
class DuoTaiDemo
{
public static void main(String[] args)
{
// 毕老师 x = new 毕老师();
// x.讲课(); //Java
// x.看电影();
毕姥爷 x = new 毕老师();
x.讲课(); //Java,向上转型,变成父类型
x.看电影(); //错误,无法访问子类的特有功能
x.钓鱼(); //钓鱼,继承了毕姥爷的功能,毕老师没有覆盖而已,可以执行。
毕老师 y = 毕老师(x);
y.看电影(); //看电影,向下转型,变成本类型
y.钓鱼(); //钓鱼,继承了毕姥爷的功能,毕老师没有覆盖而已,可以执行。
}
}
10.向下转型中的类型判断:instanceof, 用于判断对象的具体类型,只能用于引用数据类型判断。
作用:通常在向下转型前用于健壮性的判断。
public static void method(Animal a) //多态:Animal a = new Cat(); 。避免有不同的对象需要不同的函数
{
a.eat();
// a.catchMouse(); //多态的弊端:前期定义的内容不能使用(调用)后期子类的特有功能。
// Cat c = (Cat)a;
// c.catchMouse(); //如果传了一只狗,就错了。要进行类型判断。(子类的子孙类也可以接收的)
//类型判断
if(a instanceof Cat) //instanceof:用于判断对象的具体类型(类或者接口)。只能用于引用数据类型判断。
//作用:通常在向下转型前用于健壮性的判断。
//a instanceof Animal ,始终真
{
Cat c = (Cat)a;
c.catchMouse();
}
//挨个判断一般不会出现,太多。
}
11.多态在 继承当中成员的一些变化:向上转型,子类型被隐藏,子类的特有方法不能使用。
Animal a = new Cat();
1.成员变量:
编译时:参考引用型变量所属的类中是否有调用的成员变量,有,编译通过。没有,编译失败。
运行时:参考引用型变量所属的类中是否有调用的成员变量,并运行该所属类中的成员变量。
简单说:编译和运行都参考左边。
面试才会出现 父子类成员变量同名。
覆盖只发生在函数中。
2.成员函数:(非静态)
编译时:参考引用型变量所属的类中是否有调用的函数,有,编译通过。没有,编译失败。
运行时:参考的是对象所属的类中是否有调用的函数,
简单说:编译看左边,运行看右边。
必须通过对象调用,依赖的是对象,看对象是谁。
运行时 有个动态绑定的过程:
3.静态函数:
编译时:参考引用型变量所属的类中是否有调用的静态方法。
运行时:参考引用型变量所属的类中是否有调用的静态方法。
简单说:编译和运行都看左边。
其实对于静态方法,是不需要对象的,直接用类名调用。
//Fu f = new Zi(); //对象多余
//f.staticMethod();
Fu. staticMethod();
Zi. staticMethod();
静态函数在方法区中的静态区,不依赖对象。new的对象用不上。
直接依赖引用型变量所在的类。可以说静态函数不存在多态性。
12.内部类:将一个类定义在另一个类里,对里面的那个类称为内部类(内置类,嵌套类)
1)特点:
①内部类可以直接访问外部类中的成员,包括私有成员。
②而外部类要访问内部类中的成员必须要建立内部类的对象。
③可以被成员修饰符修饰:
直接访问外部类中的内部类中的成员:不多见,内部类一般私有化。
Outer.Inner in = new Outer().new Inner();
如果内部类是静态的,相当于一个外部类。
Outer.Inner in = new Outer.Inner();
如果内部类是静态的,成员是静态的。(如果内部类中有静态成员,该内部类也必须是静态的)
Outer.Inner.function();
2)作用:访问方便
一般用于类的设计。
分析事物时,发现该事物描述中还有事物,而且这个事物还在访问被描述事物的内容。
这时就把还有的事物定义成内部类来描述。
心脏是内部类,身体是外部类。
3)细节:
为什么内部类能直接访问外部内的成员?
因为内部类持有了外部类的引用。外部名.this
①外部类和内部类的成员不同名的时候:
外部名.this,this省略,默认添加。
②同名的时候,需要手动指明。
1.默认先找方法中的局部成员。
2.如果要找内部类的成员,通过this. 或者内部类.this. 来访问。
3.如果要访问外部类的成员,通过外部类.this.来访问。
4)局部内部类:
内部类可以放在局部位置上。
注意:从内部类中访问局部变量 ,需要被声明成final类型。
原因:内部类在局部位置上只能访问局部中被final修饰的局部变量。因为局部变量的生命周期结束后,就消失了,这时内部类就不能访问了。所以必须加final。
5)匿名内部类:内部类的简写格式。就是一个匿名子类对象。
1.格式:new 父类or接口(){ 子类内容 } (一个有点胖的对象)
(匿名对象:是对象的简写格式。)
2.前提:内部类必须继承或者实现一个外部类或者接口。
abstract class Demo
{
abstract void show();
}
class Outer
{
int num = 4;
/*
class Inner extends Demo
{
void show()
{
System.out.println("有继承的内部类");
}
}
*/
public void method()
{
//new Inner().show();
//匿名内部类
new Demo() //Demo类子类的对象,在对象中把抽象方法实现。就是一个匿名子类对象。简化封装。有点胖的对象。
{
void show()
{
System.out.println("匿名内部类");
}
void haha()
{
}
}.show();
}
}
//如果需要调用多个方法,可以给匿名内部类取个名字:
/*
Demo d = new Demo()
{
void show()
{
System.out.println("匿名内部类 + 外部类的num:" + num);
}
};
d.show1();
d.show2();
*/
class InnerClassDemo
{
public static void main(String[] args)
{
new Outer().method();
}
}
3.匿名内部类使用场景:
当函数参数是接口类型时,而且接口中的方法不超过三个。否者对象太胖了。
可以用匿名内部类作为实际参数进行传递。
class InnerClassDemo
{
public static void main(String[] args)
{
method(new Inter() //直接用匿名内部类作为实际参数进行传递。就是一个子类的匿名对象。
{
public void show1(){};
public void show2(){};
public void show3(){};
})
}
public static void method(Inter in) //发生向上转型,子类对象特有的show3()无法调用。
{
in.show1();
in.show2();
}
}
4.内部类的细节:
①当内部类在主类中时:
主函数不能访问非静态的内部类成员,因为不能存在this,所以不能通过外部类.来调用。
在主类的其他方法中可以访问非静态的内部类,可以用this.new Inner();,其实this.表示外部类,默认添加,可以省略。
②匿名内部类是子类的对象,可以直接访问子类的成员。
如果给它取了名字,就相当于进行了向上转型,只能调用接口或者父类中存在的成员,子类中的特有成员被隐藏。
13.对象的初始化过程:
构造函数先进栈,最后出栈。
中间的初始化顺序:父类构造函数,显示初始化,构造代码块。
Person p = new Person();
1.JVM读取指定路径下的person.class文件,并加载进内存,并会先加载Person的父类(如果有直接父类的情况下)。
2.在堆内存中开辟空间,分配内存地址
3.并在对象空间中,对对象中的属性进行默认初始化。
4.调用对应的构造函数进行初始化。
5.在构造函数中,第一行先调用父类中构造函数进行初始化。
6.父类初始化完毕后,再对子类的属性进行显示初始化。
7.在执行 构造代码块 进行初始化。
8.然后继续完成子类构造函数的特定初始化。
9.初始化完毕后,将地址值赋值给引用变量。
图解: