Java学习笔记day4——多态及相应练习

【多态知识点】

1. 多态能解决什么问题?

可以确定某个变量、元素等它的类型是属于某个系列的,但是不能确定它更具体的类型。

这个变量需要赋值的对象可能是这个系列的子类的任意一种对象。

那么就需要多态这种特性支持。

2. 什么是多态?


(1)多态引用的概念

父类类型 变量= 子类的对象;

如:

public class Pet{
    public void eat(){
        System.out.println("~~~");
    }
}
public class Cat extends Pet{
    @Override
    public void eat(){
        System.out.println("猫在吃鱼");
    }
}

public class Dog extends Pet{
    @Override
    public void eat(){
        System.out.println("狗在啃骨头");
    }
}
public class Person{
    private String name;
    private Pet pet;
    public void adopt(Pet pet){
        this.pet = pet;
    }
    public void feed(){
        pet.eat();
    }
}
//测试
public class Test{
    public static void main(String[] args){
        Person p = new Person();
        Pet dog = new Dog();
        p.adopt(dog);
        p.feed();
        //将会返回:狗在啃骨头,而不是默认的~~~
    }
}

(2)多态的现象

编译时类型和运行时类型不一致

编译时,变量为父类类型,它可以,也只能调用父类的方法

运行时,这个变量是子类类型,所以会执行子类重写的方法体。

如上面的程序,执行的是重写后的eat();而如果在Dog类中再写一个方法watchHouse(),在测试类中调用p.watchHouse()将会报错

==》对象的本质类型是子类的类型,只是编译器在编译时,把它当成父类类型来检查

(3)多态的优势和弊端

好处:实现动态绑定,代码编写更灵活,如上面程序中的Dog和Cat,执行不同子类重写方法

弊端:失去个性,只能调用父类声明的方法,子类扩展方法缺失

3. 应用场景

(1)多态数组

元素的类型声明为父类类型,元素实际存储的是子类对象

(2)多态参数

形参类型为父类类型,实参对象是子类对象

(3)多态返回值

方法返回值类型是父类类型,实际返回是子类对象

(4)变量类型是父类类型,变量值为子类对象

重点:重写的方法:编译时看父类,运行时看子类

练习1:图形

(1)父类Graphic图形

public double area()方法:返回0.0

public double perimeter()方法:返回0.0

public String getInfo()方法:返回图形面积和图形周长

(2)子类Circle圆继承Graphic图形

包含属性:radius,属性私有化

包含get/set方法

重写area()求面积方法

重写perimeter()求周长方法

重写getInfo()方法,返回圆的半径,面积和周长

(3)子类矩形Rectangle继承Graphic图形

包含属性:length、width,属性私有化

包含get/set方法

重写area()求面积方法

重写perimeter()求周长方法

重写getInfo()方法,返回长和宽,面积、周长信息

(4)在测试类的main方法中创建多个圆和矩形对象方法放到Graphic[]类型的数组中,并按照面积从小到大排序

代码如下:

Graphic类:

public class Graphic {
    public double area(){
        return 0.0;
    }
    public double perimeter(){
        return 0.0;
    }
    public String getInfo(){
        return "面积:"+area()+"周长:"+perimeter();
    }
}

Rectangle类:

public class Rectangle extends Graphic{
    private double length;
    private double width;
    public double getLength() {
        return length;
    }
    public void setLength(double length) {
        this.length = length;
    }
    public double getWidth() {
        return width;
    }
    public void setWidth(double width) {
        this.width = width;
    }
    @Override
    public double area() {
        return length*width;
    }
    @Override
    public double perimeter() {
        return (length+width)*2;
    }
    @Override
    public String getInfo() {
        return "长"+length+"宽"+width+super.getInfo();
    }
}

Circle类:

public class Circle extends Graphic {
    private double radius;
    public double getRadius() {
        return radius;
    }
    public void setRadius(double radius) {
        this.radius = radius;
    }
    @Override
    public double area() {
        return Math.PI*radius*radius;
    }
    @Override
    public double perimeter() {
        return Math.PI*radius*2;
    }
    @Override
    public String getInfo() {
        return "半径为:"+radius+super.getInfo();
    }
}

测试类:

public class GraphicTest {
    public static void main(String[] args) {
        Graphic[] arr = new Graphic[4];
        Rectangle r1 = new Rectangle();
        r1.setLength(5);
        r1.setWidth(2);
        arr[0] = r1;

        Rectangle r2 = new Rectangle();
        r2.setLength(4);
        r2.setWidth(3);
        arr[1] = r2;

        Circle c1 = new Circle();
        c1.setRadius(2);
        arr[2] = c1;

        Circle c2 = new Circle();
        c2.setRadius(1);
        arr[3] = c2;

        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i].getInfo());

        }
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(arr[j].area()>arr[j+1].area()){
                    Graphic tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i].getInfo());

        }

    }

}

4. 向上转型与向下转型

向上转型:

自动类型转换。

当把子类对象赋值给父类的变量时,在“编译时”会自动类型提升为父类的类型。

向下转账:

需要调用子类“扩展”的方法时,必须进行向下转型,通过强制类型转换完成,这样才能通过编译

对象的本质类型从头到尾都没有变,只是骗编译器的。

5. 什么情况下向上、向下转型才能成功

向上转型:只要满足对象是这个变量的类型或它的子类类型就可以

如: Person p1 = new Man();

向下转型:必须满足对象的运行时类型 <= ()中向下转的类型

如:Person p1 = new Man();

Man m = (Man)p1;

又如:Person p2 = new ChineseMan();

Man m2 = (Man)p2;

6. 如何避免ClassCastException?

加入instanceof判断

作用为:判断某个变量、对象的运行时类型是否小于等于转换类型

7. 成员变量和成员方法有无多态?

成员变量:没有多态概念,变量的寻找只看编译时类型,没有编译时类型和运行时类型不一致的说法。

成员方法:有多态的概念,编译时看父类,运行时看子类,如果子类重写了,一定执行子类重写的方法体

如:

public class Test{
    public static void main(String[] args){
        Son s = new Son();
        System.out.println(s.a);//输出2
        System.out.println(((Father)s).a);//输出1,
//把s向上转型为Father类,让编译器从Father中取a
        Father f = new Son();
        System.out.println(f.a);    //1
        //此时f的编译类型为father,让编译器从father中取a
        System.out.println(((son)f).a);    //2
        Son son = new Son();
        son.method();
        ((Father)son).method();    //方法重写了,只要运行类型为Son,那么一定执行重写方法
    }
}

class Father{
    int a = 1;
    public void method(){
        System.out.println("Father.method");
    }
}
class Son{
    int a = 2;
    @Override
    public void method(){
        System.out.println("Son.method");
    }
}

8. 虚方法

凡是可以被子类重写的方法,都叫做虚方法

子类不能重写的方法就不是虚方法

不能重写的方法有:private等

9. 虚方法的调用原则

(1)编译时:看父类

在父类中找到能够匹配的方法

(2)运行时:看子类

如果子类重写了这个虚方法,那么一定执行重写的方法

如果子类没有重写这个虚方法,那么还是执行父类方法

练习2:判断输出

public class Test {
    public static void main(String[] args) {
        MyClass my = new MySub();
        Father f = new Father();
        Son s = new Son();
        Daughter d = new Daughter();
        my.method(f);
        /*my变量的编译时类型是MyClass,编译时看MyClass;运行时看MySub
        (1)第一步,编译器去MyClass类中找匹配的方法
        public void method(Father f)
        public void method(Son s)
        实参f的编译时类型Father,和它最匹配的是public void method(Father f),找到了
        (2)第二步,运行时,MySub中找重写
           public void method(Father d){
            System.out.println("sub-father");
        }
        输出sub-father
         */
        my.method(s);
        /*my变量的编译时类型是MyClass,编译时看MyClass;运行时看MySub
        (1)第一步,编译器去MyClass类中找匹配的方法
        public void method(Father f)
        public void method(Son s)
        实参s的编译时类型Son,和它最匹配的是public void method(Son s),找到了
        (2)第二步,运行时,MySub中找重写
           没找到,输出son
         */
        my.method(d);
        /*my变量的编译时类型是MyClass,编译时看MyClass;运行时看MySub
        (1)第一步,编译器去MyClass类中找匹配的方法
        public void method(Father f)
        public void method(Son s)
        实参d的编译时类型Daughter,没有匹配的,找兼容的,public void method(Father f)
        (2)第二步,运行时,MySub中找重写
           public void method(Father d){
            System.out.println("sub-father");
        }
        输出sub-father
         */
    }
   static class MyClass{
        public void method(Father f){
            System.out.println("father");
        }//第一个方法
        public void method(Son s){
            System.out.println("son");
        }
        //重载第一个方法
    }
    static class MySub extends MyClass{
        public void method(Father d){
            System.out.println("sub-father");
        }//重写第一个方法
        public void method(Daughter d){
            System.out.println("daughter");
        }//重载
    }
    static class Father{}
    static class Son extends Father{}
    static class Daughter extends Father{}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值