目录
1、多态
在Java中,对于多态的核心表现主要有以下两点:
方法的多态性:
(1)方法的重载 — 静态绑定
同一个方法名称可以根据参数的类型或个数不同调用不同的方法体
(2)方法的覆写 — 动态绑定
同一个父类的方法,可能根据实例化子类的不同也有不同的实现。
1.1 向上转型:
父类引用 引用子类对象。
什么情况下会发生向上转型呢?即 父类引用 引用子类对象
class Person{
public void work(){}
}
class Student extends Person{
public void learn(){}
}
public class Main{
// 2、方法传参,父类引用子类对象
public static Person func(Person p) {
p.work();
Student s = new Student();
// 3、方法返回值
return s;
}
public static void main(String[] args){
// 1、子类对象赋值给 父类引用
Person p = new Student();
Student s = new Student();
// 2、方法传参
func(s);
}
}
父类引用 引用子类对象 ,常见在 如上三种情况。
问题就来了
继承中,子类与父类出现同名方法,
如何确定,该去调用 谁的方法呢?
1.1.1运行时绑定:(动态绑定)
当 父类引用去引用子类对象 ,并且父类引用调用父类在子类中被重写的方法时, 就会发生 运行时绑定,
其在编译过程中调用的还是父类中的方法,
但是在运行时,调用的却是子类重写的方法。
有了动态绑定,问题就迎刃而解了。
如上,我们便可以得出、多态发生的前提:
1、 向上转型 --- 父类引用子类对象
2、 继承重写 --- 子类父类拥有同名的覆盖方法
3、 动态绑定 --- 父类引用此时调用这个覆盖的方法
**多态 = 动态绑定 + 向上转型 **
1.2 向下转型:
子类引用 引用父类对象
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
/** private Animal eat(){}
* 1、被重写的方法不能被final进行修饰,否则不能进行修改
* */
protected Animal eat(){
System.out.println(this.name + "Animal:: eat()");
// 返回父类对象,子类重写返回子类对象则为协变
return new Animal(this.name);
}
/** 2、 static 静态方法继承
* static 所修饰的静态方法是不能被重写的
* */
public static void GetBreed(){
System.out.println("I am animal");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
protected Cat eat(){
System.out.println(this.name + "Bird:: eat()");
// 协变类型 ,返回子类对象
return new Cat(this.name);
}
public void drink(){
System.out.println("this.name" + "喝水");
}
}
class Dog extends Animal {
public Dog(String name){
super(name);
}
public Dog eat() {
System.out.println(this.name );
return new Dog(this.name);
}
public void play(){
System.out.println("this.name" + "play");
}
}
public class TestMain {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Cat cat1 = (Cat)new Animal("Cat");
//Cat
Animal animal1 = new Cat("Cat");
Cat cat1 = (Cat)animal1;
cat1.drink(); // Cat eat
// Dog
Animal animal2 = new Dog("Dog");
Cat cat2 = (Cat)animal2;
// cat2.drink();
// Dog 类中没有Cat中drink方法,盲目进行向下转型挥发生错误//
// Exception in thread "main"java.lang.ClassCastException:
// Animal cannot be cast to Cat at TestMain.main(TestMain.java:15)
}
}
向下转型可以对 子类特有的方法进行 访问
为了转型更加安全,需要预判看看 animal 本质上是不是一个 Brid 实例,
再来转换,所以此时便会引用 instanceof 关键字
Animal animal = new Cat("Cat");
if(animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.drink();
} else {
System.out.println("Error")
}
1.2.1 instanceof
预判一个引用是否某个类的实例。
同时也可以理解为: 判断一个引用之前是否引用了某个类的实例
如果是,则返回 true.
此时再进行向下转型 就比较安全了
1.3 多态的好处
a. 类调用者对类的使用成本降低
调用者只需要使用对象的具体方法即可
b. 降低代码圈复杂度,即减少条件分支和循环语句
c. 扩展性增强,调用者无线更改代码,只需要增加新类即可
1.4 在构造方法中 调用重写的方法 (注意)
创建两个类,B是父类,D是子类。
D中重写func方法,并且在B的构造方法中调用 func
class A {
int i = 10;
public A() {
print();
i = 20;
}
public void print() {
System.out.println("A: i = " + i);
}
}
class B extends A {
int i = 100;
public B() {
print();
i = 200;
}
@Override
public void print() {
System.out.println("B: i = " + i);
}
}
---------
new B()
打印:
B: i = 0
B: i = 100
分析:
1、new B() 先调用父类构造函数,进而执行 print() 函数
而 B对A print() 函数进行了重写,在执行print() 方法时,发生动态绑定,进而调用子类 print()方法
2、new B() 构造对象分为:(1)new分配内存 (2)构造函数构造初始化对象
动态绑定,调用子类 print() 方法,只分配内存,还未进行初始化(构造函数),此时 i 为 默认值 0;
3、执行完print()方法后,进而继续执行完父类构造函数,进而执行子类构造函数
4、子类构造函数,初始化 i = 100, print()打印
2、方法重写
1、返回值类型相同
2、方法名称相同
3、参数列表相同
4、继承中,子类重写父类方法
这种情况称之为: 覆写/重写/覆盖(Override)
1、被重写的方法不能用final进行修饰,否则不能进行重写
父类的final方法不能被子类所覆盖的,
即子类是不能存在和父类相同方法。
2、被重写的方法不能被private修饰
类的private方法会隐式地被为final修饰
注意:
父类的private方法,将会导致子类中不能直接继承到此方法,
因而可以存在与父类相同方法
注意
父类的private方法,将会导致子类中不能直接继承到此方法,
因而可以存在与父类相同方法
3、在子类中重写的方法的访问限定符的权限一定要大于等于父类中被重写方法的权限,
其遵循的规则: private<default<protected<public
4、被static所修饰的静态方法是不能被重写的
5、我们推荐在代码中进行重写方法时显式加上 @Override
6、重写方法时抛出异常,可以是父类方法抛出异常的全集,子集,空集。
7、重写的方法的返回值,可以缩小返回类型的权限范围,这个返回值之间的关系我们称之为 协变类型