写在前面:
博主主页:戳一戳,欢迎大佬指点!
博主秋秋:QQ:1477649017 欢迎志同道合的朋友一起加油喔
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个小菜鸟嘿嘿
-----------------------------谢谢你这么帅气美丽还给我点赞!比个心-----------------------------
多态的概念:多态就是多种形态,意思就是对于同一件事情,如果去完成它的对象不同,最后产生的结果也是不一样的,有不同的状态。
还是拿猫猫和狗狗给大家举个例子,如图:
在java里面,对于多态的实现,必须满足一下几个条件:
1,必须是在继承的条件下,因为涉及到多个子类对象。
2,子类必须重写父类中的方法。
3,通过父类的引用调用子类重写的方法。(这里会发生一个向上转型)
介绍完条件后,那我们要怎么实现多态呢?步骤如下:
向上转型就是用父类的引用来接收子类的对象。
class Animal{};
class Dog extends Animal{};
//实例化对象的时候如下:
Animal animal = new Dog();
如这段代码,animal这个父类的引用接收的是子类Dog的对象,这里就是发生了向上转型。但是注意,animal到底是父类的引用,所以这个时候你通过animal能访问到的只是父类自己特有的成员以及方法,不能访问到子类的成员与方法的。
重写,也称覆写或者覆盖,指的是子类对父类中的方法进行重新定义,来实现自己特有的行为。
class Animal{
......//省略一些代码,会在后面完全展出
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class Dog extends Animal{
......
public void eat(){//重写父类的方法
System.out.println(getName() + "正在吃狗粮!");
}
}
如这段代码,子类中的eat方法就是对父类中的eat方法进行了重写,从而定义了自己特有的行为功能。
既然有了这么一个概念,重写的使用也是存在着很多的注意事项的…
注意:
1,子类在重写方法的时候,方法的方法名,参数列表,返回值都必须和父类的保持相同。其中,返回值我们是要求相同,但是,如果父类的返回值与子类的返回值也构成父子关系的话,这种情况也是允许的。(协变类型)
class Animal{
......//省略一些代码,会在后面完全展出
public Animal eat(){
System.out.println(name + "正在吃饭!");
return new Animal();
}
}
class Dog extends Animal{
......
@Override
public Dog eat(){//重写父类的方法
System.out.println(getName() + "正在吃狗粮!");
return new Dog();
}
}
2,父类中,被static,private,final(final修饰得到称为密封方法)修饰的方法,以及构造方法,都是不能被重写的。
3,子类方法的访问修饰符的权限必须大于等于父类方法的访问权限。(private < default < protected < public)
4,在重写的方法前,可以用 @Override注解来进行标识,这样就可以进行一些合法性的校验,例如把方法名写错,编译器就会报错提醒。
【扩展】重载与重写的对比分析
总的来说,重载只要求名字相同,然后参数列表必须不同,其余随意;重写除了返回值的特殊情况,都必须保持一样。
class Animal{
......//省略一些代码,会在后面完全展出
public Animal eat(){
System.out.println(name + "正在吃饭!");
return new Animal();
}
}
class Dog extends Animal{
......
@Override
public Dog eat(){//重写父类的方法
System.out.println(getName() + "正在吃狗粮!");
return new Dog();
}
}
public class TestDemo220516 {
public static void func(Animal animal){//用父类的引用接收子类的对象
animal.eat();//动态绑定
}
public static void main(String[] args) {
func(new Dog("狗狗",5));//参数是子类的对象
}
}
这个时候就完全的实现了多态,其中还有一点需要知道的是这个时候发生了动态绑定,或者说运行时绑定。因为我们上面说过,父类的引用不能调用子类的方法的,但是这里确确实实最后执行的是子类的重写的方法,那到底是为什么呢?
我们利用 javap -c 文件名 查看了编译时主函数类的字节码文件,如下:
我们可以看到,在编译的时候,func里面显示的还是调用的父类自己的eat方法呀,根本就没有调用子类的重写的方法,但是最后结果又是输出的子类方法的执行结果,难道之前的结论是错误的嘛,其实都没有错,想想之前说的动态绑定(运行时绑定),虽然编译的时候还是显示调用父类的方法,但是运行的时候肯定是绑定调用了子类的重写的方法的。
【动态绑定与静态绑定】
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。典型代表函数重写。
class Animal{
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class Dog extends Animal{
private String color;//子类特有属性
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat(){//重写父类的方法
System.out.println(getName() + "正在吃狗粮!");
}
}
class Cat extends Animal{
private String weight;//子类特有属性
public Cat(String name, int age) {
super(name, age);
}
public void eat(){//重写父类的方法
System.out.println(getName() + "正在吃猫粮!");
}
}
public class TestDemo220516 {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
func(new Dog("狗狗",5));
func(new Cat("猫猫",5));
}
}
//上面注意一个点,因为父类中的成员都是定义的private的,所以我们子类虽然继承了,对象空间中也确实存在这些成员,但是子类无法直接访问到,所以父类中需要提供公共的接口
程序运行截图:
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法(重写时的动态绑定是个例外),但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
class Animal{
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class Dog extends Animal{
private String color;//子类特有属性
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat(){//重写父类的方法
System.out.println(getName() + "正在吃狗粮!");
}
public void swim(){
System.out.println(getName() + "在游泳!");
}
}
public class TestDemo220516 {
public static void main(String[] args) {
Animal animal = new Dog("狗狗",5);
Dog dog = (Dog)(animal);//向下转型
dog.swim();//调用Dog类特有的swim方法
}
}
但是呢,虽然有向下转型的方法,我们一般最好也不要用,因为很容易出错。
public static void main(String[] args) {
Animal animal = new Cat("猫猫",5);
Dog dog = (Dog)(animal);
dog.swim();
}
编译没有报错,但是运行后会发现:
报出了这么一个异常,表示转换的两边两个类的类型不兼容,为什么,因为这个时候我们的animal引用的是Cat类的对象,然后你要将其强制转换成Dog类的对象,那肯定是行不通的,所以我们必须得改进为如下:
public static void main(String[] args) {
Animal animal = new Cat("猫猫",5);
if(animal instanceof Dog){
Dog dog = (Dog)(animal);
dog.swim();
}
}
这里引入了instanceof,用来判断前者是不是引用了后者的实例对象,是的话,结果为true,否则为false。这样就避免了上面的错误情况,只有当animal引用的也是Dog类的对象时,才能执行里面的向下转型的代码,也才能用来调用Dog类的方法。
其实到这里,我们可以发现,向上转型是向下转型的前提,并且只有当我们向上转型时引用的子类的对象类型与我们向下转型时想转型成为的类的类型一样时,才能正确的完成向下转型(或者说之前必须已经引用了这个类)。
优点:
1, 能够降低代码的 “圈复杂度”。
2,代码可扩展能力强。
缺点:
代码运行效率降低。
注意:应该避免在构造方法里面调用重写方法
class Animal{
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
eat();//构造方法里面调用重写的方法
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class Dog extends Animal{
private String color;//子类特有属性
public Dog(String name, int age,String color) {
super(name, age);
this.color = color;
}
@Override
public void eat(){//重写父类的方法
System.out.println(color);
System.out.println(getName() + "正在吃狗粮!");
}
public void swim(){
System.out.println(getName() + "在游泳!");
}
}
public class TestDemo220516 {
public static void main(String[] args) {
Dog dog = new Dog("狗狗",5,"黄色");
}
}
程序运行截图:
可以看到,我们new出一个dog对象后,虽然没有直接调用Dog类的eat方法,但是也执行到了,因为在父类的构造方法里面调用了eat,并且还执行的是子类的eat方法,也就是说构造方法里面也会存在动态绑定,这个时候我们在构造子类Dog对象的时候先去调用了父类Animal的构造方法,而里面正好调用了eat方法,所以动态绑定后就直接找到了子类的eat方法。
但是,这种在构造函数里面调用重写方法极其不推荐,因为很有可能会出问题,因为你在调用子类的重写函数的时候,都还在走父类的构造函数,子类的对象根本就还没构造出来,比如上面的color,我们输出为什么是null,因为在执行Dog的eat方法的时候子类的构造方法都还没执行,其特有属性color也都还没有初始化。
最后,今天的文章分享比较简单,就到这了,如果大家觉得还不错的话,还请帮忙点点赞咯,十分感谢!