复习:
代码块执行顺序:static修饰的代码块执行始终在普通代码块之前
public classText1113 {
{
System.out.println("2222");
}
static {
System.out.println("1111");
}
public static void main(String[] args) {
Text1113 text1113 = new Text1113();
}
}
//
输出结果为
1111
2222
一个对象的初始化顺序:
1.默认初始化
2.就地初始化/代码块初始化(并列关系,谁在前面就先执行谁)
3.代码块
4.构造方法
创建子类实例时会先创建父类实例
多态
多态在Java语法中的体现:
package Shape;
public class Shape {
public void draw(){
}
}//
package Shape;
public class Cycle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}//
package Shape;
public class Flower extends Cycle{
@Override
public void draw() {
System.out.println("✿");
}
}//
package Shape;
public class Rect extends Cycle{
@Override
public void draw() {
System.out.println("□");
}
}//
package Shape;
public class Text {
public static void main(String[] args) {
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
drawShape(shape1);
drawShape(shape2);
drawShape(shape3);
}
private static void drawShape(Shape shape) {
shape.draw();
}
}//
1.向上转型(父亲的引用指向一个字类的实例,子类的引用转成了父亲的引用)
eg:“看那个动物(猫)好可爱”
a)直接赋值
b)方法传参 (也体现了is-a这样的语义)
c)方法返回(与方法传参本质上都是赋值)
父类的引用,只能访问到父类的属性和方法,访问不到子类特有的属性和方法
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
2.动态绑定
父类和子类之间都具有同名并且同参数的方法,这个时候通过父类引用调用该方法的时候,就是在运行时决定时调用父类的方法版本还是子类的方法版本
动态:运行时 静态:编译期
shape.draw();
new的是谁,调用的就是谁
Animal animal = new Cat();
animal.eat("鱼");
如果eat方法只在父类中存在,此时调用的eat就是父类的这个eat方法。(不涉及动态绑定)
如果eat方法只在子类中存在,此时调用的eat就会编译报错。(也不涉及动态绑定)
如果eat方法在父类和子类中都存在,并且参数也相同,此时调用eat就会涉及到“动态绑定”
在程序运行时,看animal究竟是指向一个父类的实例还是子类的实例,指向父类的实例就调用父类版本的eat,指向子类实例就调用子类的版本的eat
animal这个引用指向的是父类还是子类的实例,是运行时才能确定的
如果eat方法在父类和子类中都存在,并且参数不相同,此时调用eat,也不涉及“动态绑定”看起来像是重载,但是又不太一样,与前面两条类似
根据调用方法时传入的参数类型和个数,判断在父类中是否存在匹配的方法,如果不存在就编译报错(编译期确定的)
上述的动态绑定的规则,我们是站在编译器和JVM实现着的角度来看待
此操作在Java的语法层次上也有一个术语,“方法重写(Override)”
注:方法的访问限定符会影响到动态绑定,如:若子类中这个方法是private,这时候外部是看不到子类这个方法的,此时不涉及到动态绑定
3.方法重写
多态:一个引用能表现出多种不同的形态。结合上下文来看此引用具体指向的是哪种实例
- 多态是封装的更进一步,让类的使用者不需要关注具体对象的类型,也能正确使用
- 方便扩展,未来如果需要新增新的子类,对于类的使用者来说影响很小
- 消灭一些分支语句,降低程序的圈复杂度
@Override
public void draw() {
System.out.println("□");
若没有这个注释,其实代码也能编译运行,加了这个注释之后,编译器就能更好的执行检查和校验工作。
例如:父类中本来有一个eat方法,但写子类的人不知道有父类这个方法,也写了一个同参数的eat方法,编译器此时就可检查出来,加上@Override明确告诉编译器,目的就是进行重写,防止无意中写出这种方法重写的代码。
注意:Java中的所有类都是直接或者间接继承自Object(祖宗类),如果写了某个类,没写extends,默认继承自Object,toString其实就是Object的方法
重写跟重载相同点与不同点:
重载是同一个作用域中,方法名字相同,参数不同,(参数的个数/种类)
重写是父类和子类之间,存在同名方法,参数相同,此时通过父类引用调用该方法,就会触发重写,此时具体执行哪个版本的方法由动态绑定规则来决定
面试必考题:
解释什么叫多态(用自己话讲,最好结合自己写的代码,不能只会回答:“一个引用,对应到多种形态(不同类型的实例)”
根据实际使用场景,选择恰当的实例、方法等,从而适用于多种情况
4.了解:重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成或相同,特殊情况除外)
- 假设父类和子类方法的返回值完全互不相干,这种情况下是会编译出错的;
- 如果父类和子类方法的返回值类型具有父子关系,这种情况下,就可以
了解即可, 实际重写的时候类型一般都是相同的,一般进行重写的两个方法,功能都是类似的,所以返回值类型也是兼容的,若返回值类型完全不相干,就没啥意义,就不应该进行重写
5..多态优势
- 多态的本质(第一个好处):多态这种设计思想,本质上是“封装”的更进一步,封装的目的是为了让类的使用者不需要知道类的实现细节, 就能使用 ,但是使用者仍然需要知道这个类是啥类型,但在使用多态时,类的使用者不仅不需要知道类的实现细节,也不要知道这个类具体是啥类型,只要知道这个类有一个draw方法就可以,这个时候类的使用者知道的信息更少,使用成本就越低
- 第二个好处:方便扩展,未来如果需要新增一个形状,创建一个新的子类即可,并且让子类也去重写这个draw方法,类的调用者这里的代码不需要做出任何修改/或者只做很小的修改
- 第三个好处:减少一些分支语句(减少if/else switch/case)
向下转型
将父类引用转成子类引用,向下转型必须保证操作是合理的,否则可能会存在问题
Animal animal = new Bird();
//这个向下转型操作就是非法的,虽然编译能通过,但是运行时会存在问题‘’
Cat cat = (Cat)animal;
向下转型是存在限制的,不是随便就能转,相当于向上转型的逆过程,通过向上转型得到的父类的引用可以借助向下转型还原到原来的类型
向下转型的应用场景:
有些方法只是在子类中存在,但是父类中不存在,此时使用多态的方法就无法执行到对应的子类的方法,就必须把父类引用先转回到子类的引用,然后在调用对应的方法
保险措施:可以在向下转型之前,先做出判定,判定当前的父类的引用到底是不是指向该子类,如果不是就不进行向下转型(instanceof)