目录
一、final关键字
final关键字用来修饰变量(常量)、类(密封类)、方法(表示该方法不能被重写)
二、方法的重写
方法重写的规则与注意事项:
1.方法名相同
2.方法的参数列表相同(个数、顺序、类型)
3.方法的返回值相同
4.被重写的方法返回值类型可以不同,但是必须是具有父子关系的
5.父类被final、static、private修饰的方法、构造方法都不能被重写
6.访问权限不能比父类中被重写的方法的访问权限更低
例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected
7.重写的方法可以使用@Override注解来显示指定,主要起合法性校验的作用。例如不小心将方法名字拼写错了(原本eat()方法写成aet()),那么此时编译器就会发现父类中没有aet()方法,就会编译报错,提示无法构成重写。
三、静态绑定
在编译的时候就确定调用哪个方法了
四、向上转型
向上转型就是创建一个子类对象,把它当作父类对象来使用。
发生向上转型的三种情况
注意事项:
1.向上转型一定是安全的。
2.优点:让代码实现更简单灵活。
3.弊端:无法调用子类的特有方法,如上图。
五、向下转型
向下转型就是还原子类对象,用强制类型转换的方式实现。
注意事项:
1.谁"new"的就还原成谁。
2.还原之前使用 对象名 instanceof 类名 进行判断,避免出错。
六、动态绑定
动态绑定的概念:编译的时候确实认为调用的是父类的方法,但是在运行的时候调用了子类的方法,这个机制就叫做动态绑定!
发生动态绑定的条件:
1.父类引用引用子类对象(向上转型)
2.通过父类引用调用重写的方法
class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(this.name+"正在吃!");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);//alt+回车
}
public void bark() {
System.out.println(this.name+"汪汪叫!");
}
@Override
public void eat() {
System.out.println(this.name+"正在吃狗粮!");
}
}
class Bird extends Animal {
public Bird(String name, int age) {
super(name, age);
}
public void qiqi() {
System.out.println(this.name+"吱吱叫!");
}
public void fly() {
System.out.println(this.name+"正在飞!");
}
@Override
public void eat() {
System.out.println(this.name+"正在吃鸟粮!");
}
}
public class Test {
public static void func(Animal animal1) {
animal1.eat(); //动态绑定
}
public static void main(String[] args) {
Dog dog = new Dog("小黄",10);
Bird bird = new Bird("小鸟",10);
func(dog); //向上转型
func(bird);
}
}
当animal引用的对象不一样调用eat方法表现的行为不一样此时就叫做多态!因为同一个引用调用同一个方法表现的行为不一样!
七、多态
概念:具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
通过同一个父类引用调用同一个被重写的方法,此时父类引用引用的子类对象不一样表现的形式也不一样,此时叫做多态!!!
格式
父类名 对象名 = new 子类名();
父类接口名 对象名 = new 子类名();
多态调用成员方法
编译看左:编译器在编译时根据等号左边的类来界定对象的类型。
运行看右:在运行访问成员方法的时候,根据等号右边实例化的对象来访问相应的成员方法。
多态调用成员属性
编译和运行都看左:编译器在编译时根据等号左边的类来界定对象的类型,在访问的时候,也是根据等号左边的类来访问相应的成员变量。
使用多态的好处
1.能够降低代码的“圈复杂度”,避免使用大量的if-else
示例代码:
class Shape {
public void draw() {
System.out.println("画图形!");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("画一个矩形!");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("画一个圆圈!");
}
}
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("画一个三角形!");
}
}
public class Test {
public static void drawMaps2() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Triangle triangle = new Triangle();
String[] shapes = {"1", "2", "2", "1", "3"};
for (String s : shapes) {
if(s.equals("1")) {
cycle.draw();
}else if(s.equals("2")) {
rect.draw();
}else if(s.equals("3")) {
triangle.draw();
}
}
}
public static void drawMaps() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Triangle triangle = new Triangle();
Shape[] shapes = {cycle, rect, rect, cycle, triangle};
for (Shape shape : shapes) {
shape.draw();
}
}
public static void main(String[] args) {
drawMaps2();
System.out.println("=====================");
drawMaps();
}
}
运行结果:
2.可扩展能力更强
避免在构造方法中调用重写的方法
执行结果:
1.构造D对象的同时,会调用B的构造方法。
2.B的构造方法中调用了func方法,此时会触发动态绑定,会调用到D中的func
3.此时D对象自身还没有构造完成,此时num处在未初始化的状态,值为0。
结论:“用尽量简单的方式使对象进入可工作状态”,尽量不要在构造器中调用方法(如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成),可能会出现一些隐藏的但是又极难发现的问题。