把子类对象赋值给父类对象的引用
多态体现为父类引用变量可以指向子类对象。
public class Test1 {
public static void main(String[] args) {
Animal animal=new Animal("动物!!");
Dog dog=new Dog("gougou");
animal=dog; //animal这个引用指向了dog引用所指的对象
//把子类对象赋值给父类对象的引用
animal=new Dog("gougou");
}
}
上面两种方法都可以
向上转型发生的时机:
1、直接赋值
2、方法的传参
3、方法的返回值
Animal animal=new Dog("gougou");
//animal父类的引用指向dog子类对象
public class Test {
public static void func1(Animal animal1) {
}
//第一种方式
Animal animal=new Dog("gougou");
func1(animal);//Animal animal1=animal -> animal=new Dog;
//第二种方式
Dog dog=new Dog("gougou");
func1(dog);
}
以上两种均可以
第一种方式:
public class Test {
public static void func1(Animal animal1) {
}
public static Animal func2(){
//第一种方式
Dog dog=new Dog("gougou");
return dog;
//return new Dog("gougou"); //这是简化的
}
}
返回对象是Animal直接return dog是一样的,也是把子类对象赋值给父类对象的引用。
第二种方式:
public class Test {
public static Dog func3() {
Dog dog=new Dog("gougou");
return dog;
}
public static void main(String[] args) {
Animal animal1=func3();
}
}
返回的对象是Dog,需要把子类对象赋值给父类对象的引用。
满足的条件:
1、发生向上转型 -> 父类引用需要引用子类的对象。
2、通过父类引用来调用子类和父类同名重写方法。
上面的条件中提到了重写,那我们就先来看看什么是重写。
重写也称为覆写/覆盖(Override)
先看这张图,针对eat()方法
上面animal1确找不到eatDog()方法,其实道理很简短,前面的类型就是Animal的类型,再加上是子类Dog继承的父类Animal,所以变量animal1只能去Animal父类里面的方法。也得出一个结论:通过父类引用 调用方法或者成员变量的时候,只能调用父类自己特有的方法或者成员
我们现在改一下都把方法改成一样的会出现什么情况??
运行成功了,
这就引出了重写
重写:
1、方法名相同
2、方法的返回值相同
3、方法的参数列表相同
1、重写和重载完全不一样。(下期预告,区分javaSe里面容易混淆重写和重载的区别,等等)
2、
static修饰的静态方法不能重写
;3、final也不能被重写,final是密封方法:当前方法不能被重写。
4、重写中子类的方法的访问权限一定要大于等于父类的访问权限。
private<包(什么都不加)
5、 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).
竞然调用的都是Animal的类为什么最后调用了Dog的类尼?
我们再回到动态绑定:
public static void main(String[] args) {
Animal animal=new Animal("动物");
animal.eat();
Animal animal1=new Dog("gougou");
animal1.eat();//向上转型
/*
*多态
*运行时绑定
*父类引用 引用子类对象。同时,通过父类引用调用同名的覆盖方法,此时就会发生运行时绑定
*/
}
满足以上条件的时候,==在编译过程中,调用的是父类的方法,但是运行的时候,实际上调用的是子类的方法。==这就叫做动态规划!!!
从反编译这个图亦可以看出动态绑定
上面说了向上转型,向下转型顾名思义就是把向上转型概念反过来
完成了向上转型之后为什么又会报错尼?在编译器上提示的是他们是两种不同的类型,其实可以用包含关系来判断,狗是一种动物,但是动物不一定是狗,所以Animal的范围要广一点,强制类型转换成Dog就不会报错了。
为什么强制类型转了还是报错尼??,没有发生向上转型,导致类本身引用的对象还是不一样
所以结论:前提,那就是要发生了向上转型才能发生向下转型,只有发生了向上转型才能发生向下转型
但是向下转型有时候不靠谱:
animal本质上引用的是Dog对象,是不能转换成Bird对象的,会显示类型转换异常,为了避免这种情况会用到instanceof。
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了 。
new 一个Dog对象,又从Dog子类里面又传进父类Animal构造方法里,如果eat()方法在前,直接调用下面的eat()方法,没有执行this.name=name,所以为null,右边构造方法里的eat()方法放在下面是执行了this.name=name之后调用的,所以可以打印出来。这就是构造方法里面的一个坑。
有了上面的向上转型, 动态绑定, 方法重写之后, 我们就可以使用 多态(polypeptide) 的形式来设计程序了
class Shape {
public void drew(){
}
}
class Cycle extends Shape {
//调用同名的覆盖方法
@Override
public void drew() {
System.out.println("画一个○");
}
}
class Rect extends Shape {
//调用同名的覆盖方法
@Override
public void drew() {
System.out.println("画一个方片♦");
}
}
public class TestDome {
public static void drewMap(Shape shape) {
shape.drew();
}
public static void main(String[] args) {
//向上转型,方法传参
Cycle cycle=new Cycle();
drewMap(cycle);
Rect rect=new Rect();
drewMap(rect);
}
}
*注意:在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。*
在这个代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的 。
多态是一种思想,通过父类引用调用同名的覆盖方法可以表现出不同的形式,这就是多态,前提是父类引用对象重写了父类draw()方法;
多态顾名思义, 就是 “一个引用, 能表现出多种不同形态” 。
或者也可以这样说:当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现
(和 shape 对应的实例相关), 这种行为就称为 多态 。
一、类调用者对类的使用成本进一步降低
1、多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可 。
二、能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
三、可扩展能力更强
父类 Shape 中的 draw 方法好像并没有什么实际工作 ,绘制图形都是由Shape 的各种子类的 draw 方法来完成的 ,像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class)
abstract class Shape {
abstract public void draw();
}
包含抽象方法的类叫做抽象类。方法和类都由abstract修饰的。
注意事项:
1、抽象类当中可以定义数据成员和成员方法
2、抽象类不能直接实例化.
尽然不能实例化,那他意义在哪?
3、存在的意义就是为了被继承。
4.如果一个类继承了抽象类,那么这个类必须重写所有的抽象方法。抽象方法也不能用final修饰,final和abstract不能共存。
5、抽象方法不能被private修饰。本身存在的意义就是继承。
6、一个抽象类A可以被另外一个抽象类B继承,但是如果有另外普通类c继承了这个抽象类B,那么普通类c里面所有的抽象方法都被重写。
对于接口,里面的组成只有抽象方法
和全局常量
,所以很多时候为了书写简单,可以不用写public abstract 或者public static final。并且,接口中的访问权限只有一种:public
,即:定义接口方法和全局常量的时候就算没有写上public,那么最终的访问权限也是public,注意不是default。以下两种写法是完全等价的:
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合。接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现
1、接口是用interface来修饰
2、接口里面的方法只能是抽象方法。且,接口当中的方法默认是public abstract的。
3、在JDK1.8开始,接口当中的方法,可以是普通方法,但前提是:这个方法是有default修饰的。(默认方法)
4、接口同样不可以进行实例化。
5、类和接口的关系是implements(实现),同样该类需要实现我们的接口当中所有的抽象方法.
6、一个类可以使用implements实现n个接口,每个接口之间使用逗号分割开就可以了。
为什么可以实现多个接口尼?因为可以多继承。
7、接口和接口之间的关系,使用extends关键来维护。意为扩展。扩展该接口其他接口的功能。
interface A {
void func1();
}
interface B {
void func2();
}
interface C extends A,B {
void func3();
}
8、接口也是可以发生向上转型和多态的。他只需要关注,这个类的功能。