多态的应用

1.(1)多态形式的数组
    声明数组时,元素类型是父类的类型,存储的元素对象是子类的对象

//需求1:我们有多个图形的对象,有矩形,圆形,三角形,需要统一管理它们,并且按照面积排序。

public class TestUse {
    public static void main(String[] args) {
        //声明图形数组
        Graphic[] arr = new Graphic[3];//数组的元素类型声明为父类类型
        arr[0] = new Rectangle(3,2); //左边的arr[0]是Graphic类型,右边的对象是Rectangle对象,是多态引用
        arr[1] = new Circle(1.1);
        arr[2] = new Triangle(3,4,5);

        //遍历输出三个图形的信息
        for (int i=0; i<arr.length; i++){
//            System.out.println(arr[i].getLength());//错误,arr[i]此时编译时类型是Graphic,无法访问子类Rectangle的getLength()
            System.out.println("第" + (i+1) +"个图形的面积是:" + arr[i].area());
        }

        //冒泡排序,从小到大
        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()){
                    //交换arr[j]与arr[j+1]的对象
                    Graphic temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }

        //排序后显示结果
        System.out.println("排序后:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println("第" + (i+1) +"个图形的面积是:" + arr[i].area());
        }

    }
}
//声明所有图形的公共父类,来体现所有图形的共同特征,比如:求面积功能
class Graphic{
    double area(){
        return 0.0;
    }
}

class Rectangle extends Graphic{
    private double length;
    private double width;

    public Rectangle() {
    }

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = 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;
    }

    //重写方法快捷键,Ctrl + O
    @Override  //可以去掉,它是一个注解,表示下面的方法是一个重写的方法
    public double area() {
        return length * width;
    }
}

class Circle extends Graphic{
    private double radius;

    public Circle() {
    }

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }
    //重写方法快捷键,Ctrl + O
    @Override
    double area() {
        return Math.PI * radius * radius;
    }
}

class Triangle extends Graphic{
    private double a;
    private double b;
    private double c;

    public Triangle() {
    }

    public Triangle(double a, double b, double c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public double getA() {
        return a;
    }

    public void setA(double a) {
        this.a = a;
    }

    public double getB() {
        return b;
    }

    public void setB(double b) {
        this.b = b;
    }

    public double getC() {
        return c;
    }

    public void setC(double c) {
        this.c = c;
    }
    //重写方法快捷键,Ctrl + O

    @Override
    double area() {
        //三角形的三条边是有要求的
        if(a+b>c && a+c>b && b+c>a && a>0 && b>0 && c>0){//任意两边之后大于第三边,才是合格的三角形,才需要求面积
            //求面积,海伦公式,根据3条边求面积
            double p = (a+b+c)/2;
            return Math.sqrt(p*(p-a)*(p-b)*(p-c));
        }
        return 0.0;
    }
}

(2)多态形式的参数
    声明形参时,形参的类型是父类的类型,接收的实参对象是子类的对象

//需求2:我们有不同的动物对象,我们测试类提供一个方法,可以观察不同动物对象的eat行为
 
public class TestUse2 {
    public static void main(String[] args) {
        Dog a1 = new Dog();
        lookAnimalEat(a1);//方法调用时,实参a1给形参animal赋值,相当于  Animal animal = a1; 左边是父类类想,右边是子类对象

        Cat a2 = new Cat();
        lookAnimalEat(a2);
    }

    public static void lookAnimalEat(Animal animal){
        animal.eat();
    }

    //方法的重载
/*    public static void lookAnimalEat(Dog animal){
        animal.eat();
    }
    public static void lookAnimalEat(Cat animal){
        animal.eat();
    }*/
}

class Animal{
    public void eat(){
        System.out.println("亚米亚米亚米");
    }
}

class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("啃骨头");
    }
}
class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("吃鱼");
    }
}

(3)多态形式的返回值
    声明方法时,返回值类型是父类的类型,实际返回的对象是子类的对象

//需求3:我们有不同种类的汽车,根据用户的需求不同,可以生产不同的汽车
 
public class TestUse3 {
    public static void main(String[] args) {
        Car car1 = Factory.createCar(15);//左边car1是父类类型,实际返回的是QQ的子类对象
        car1.drive();

        Car car2 = Factory.createCar(50);//左边car2是父类类型,实际返回的是BMW的子类对象
        car2.drive();
    }
}
class Factory{
    public static Car createCar(double money){
        if(money < 20){
            return new QQ();
        }else{
            return new BMW();
        }
    }
}

class Car{
    public void drive(){
        System.out.println("滴滴滴...");
    }
}
class BMW extends Car{
    @Override
    public void drive() {
        System.out.println("坐在宝马车里就是开心");
    }
}
class QQ extends Car{
    @Override
    public void drive() {
        System.out.println("坐在QQ车里面有点不开心");
    }
}

(4)多态形式的成员变量/局部变量
    声明变量时,变量的类型是父类的类型,实际赋值是子类对象

//需求4:表示某个人的信息,包含这个人的名字,还有它伴侣的对象,并且创建对象,测试
 
public class TestUse4 {
    public static void main(String[] args) {
        Human human = new Human();
        human.name = "Jane";

        Orangutan orangutan = new Orangutan();
        orangutan.name = "金刚";

        human.fere = orangutan;//fere声明的类型是Creature,赋值的是Orangutan子类的对象
        System.out.println("human的名字是" + human.name + ",他伴侣的名字:" + human.fere.name);

        Human human2 = new Human();
        human2.name = "董奕辰";

        Human girl = new Human();
        girl.name = "如花";

        human2.fere = girl;//fere声明的类型是Creature,赋值的是Human子类的对象
        System.out.println("human2的名字是" + human2.name + ",他伴侣的名字:" + human2.fere.name);
    }
}

//Creature:生物
class Creature{
    String name;
}
class Human extends Creature{
    Creature fere;
}
//Orangutan:猩猩
class Orangutan extends Creature{

}

2.数据类型转换,是关于引用数据类型,而且是关于父子类之间。
(1)向上转型:子类的类型变为父类的类型
        当我们把子类的对象/变量赋值给父类的变量时,就会发生向上转型。
        一旦向上转型,就只能调用父类中声明的成员了。就失去了调用子类扩展的成员的能力 。
(2)向下转型:父类的类型变为子类的类型
        当我们需要把父类的对象/变量赋值给子类的变量时,就需要进行向下转型。

 特别说明:无论是向上转型还是向下转型,并没有改变对象的真正的类型,只是在编译期间,呈现出不同的类型状态而已。所以很多人也把引用数据类型的转换称为“造型”。
   “造型”,陈文彬同学今天穿了女人的衣服,看起来是一个女的,但是本质上还是一个爷们。

如果向下转型不恰当,会发生   ClassCastException 类型转换异常。说明向下转型有风险。

如何避免ClassCastException 类型转换异常?
Java中给我们提供了一个关键字instanceof,用于判断 变量中对象的真正类型,避免类型转换异常问题。

instanceof关键字,又有点像运算符,用于判断某个变量的类型问题。
语法格式:
  变量/对象  instanceof 引用数据类型
结果:true/false

什么情况返回true?
    当变量/对象的运行时类型 <= instanceof后面的引用数据类型,才会返回true。

public class TestClassCast {
    public static void main(String[] args) {
        Ren r1 = new NvRen();//多态引用,向上转型
        r1.eat();
//        r1.shopping();//失去了调用shopping()的能力,因为编译时,r1是Ren类型,编译器只看到Ren类型。

        NvRen n1 = (NvRen) r1;//向下转型,其实就是引用数据类型的强制类型转换
        n1.shopping();

        Ren r2 = new NanRen();//多态引用,向上转型
        r2.eat();

      //  NvRen n2 = (NvRen) r2;//编译器检测到r2的类型是Ren类型,Ren是NvRen的父类,允许你向下转型
                             //运行时报错了 ClassCastException 类型转换异常
      //  n2.shopping();

        System.out.println("---------------------------");
        showRen(new NvRen()); // 实参new NvRen(),是一个匿名对象,它负责给形参(Ren ren)赋值用。
        showRen(new NanRen());
        showRen(new ChineseNvRen());
    }

    public static void showRen(Ren ren){
        ren.eat();

        //想要看ren的其他行为
//        ren.shopping();
        if(ren instanceof NvRen) {
            NvRen n = (NvRen) ren;
            n.shopping();
        }else if(ren instanceof NanRen){
            NanRen n = (NanRen) ren;
            n.smoke();
        }
    }
}

class Ren{
    public void eat(){
        System.out.println("吃~~~");
    }
}
class NvRen extends Ren{
    @Override
    public void eat() {
        System.out.println("细嚼慢咽的吃~~~");
    }

    public void shopping(){
        System.out.println("买买买~~~");
    }
}
class ChineseNvRen extends NvRen{

}

class NanRen extends Ren{
    @Override
    public void eat() {
        System.out.println("大快朵颐的吃~~~");
    }

    public void smoke(){
        System.out.println("吞云吐雾~~~");
    }
}

理解继承关系?
继承是表示类与类之间的关系。父类中的所有的成员变量和成员方法都会继承到子类中。

想法1:父类中的所有的成员变量和成员方法在子类中重复一遍?
答:不是
  在方法区中,需要加载每一个类的信息,父类中声明的成员变量和成员方法不需要在子类的类信息中再重复一遍。

想法2:既然不重复,那么子类的对象是怎么调用父类的成员变量和成员方法?
答:当子类继承父类后,就表示这个子类拥有父类的这个事物的特征,
    那么子类的对象就可以调用我父类声明的成员变量和成员方法,只要权限修饰符允许。
       子类的类名也可以调用父类的静态的成员变量和成员方法只要权限修饰符允许。
       就算因为权限修饰符不能调用,子类事物也是具有该特征的。

想法3:子类创建的对象,修改了父类的成员变量,和父类的对象是否有关系?
答: 如果是静态变量,是有关系的,因为静态变量,每一个类只存一份,无论是通过父类修改还是子类修改,修改的都是同一份。
     如果是实例变量,是无关,因为每一个对象都是独立存储的。

public class TestInherited {
    public static void main(String[] args) {
        Son son = new Son();
        son.b = 2;
        System.out.println(son.b);//2
        son.methodB();//父类的非静态方法

        Son.a = 2;
        System.out.println(Son.a);//2
        Son.methodA();//父类的静态方法

        Father f = new Father();
        System.out.println(f.b);//0
        System.out.println(Father.a);//2
    }
}

class Father{
    static int a;//这里权限修饰符缺省,为了子类能够直接调用
    int b;
    static void methodA(){
        System.out.println("父类的静态方法");
    }
    void methodB() {
        System.out.println("父类的非静态方法");
    }
}
class Son extends Father{

}

3.多态引用时,成员变量的访问是什么原则?
(1)成员变量是没有“重写”一说,即没有“覆盖”一说。
成员变量会存在“并存”的情况。
也就是说Sub子类继承了父类的成员变量,又增加了自己的成员变量,此时相当于Sub子类的对象中有两个a实例变量。

(2)成员变量的访问原则:只看变量/对象的编译时类型,不看运行时类型。
也就是说,变量的编译时类型是什么,就从哪个类找它声明的成员变量。

public class TestField {
    public static void main(String[] args) {
        Base b = new Base();//创建了父类的对象,此时没有多态引用
        System.out.println(b.a);//1  b变量无论是编译时还是运行时都是Base类型。
                                    //b.a此时表示访问的是Base类中声明的a

        Sub s = new Sub();//创建了子类的对象,此时没有多态引用
        System.out.println(s.a);//2     s变量无论是编译时还是运行时都是Sub类型。
                                    //s.a此时表示访问的是Sub类中声明的a

        System.out.println(((Base)s).a);//1  ((Base)s) 向上转型,把s向上转型为Base类型,它只是编译时的一个造型,本质上仍然是Sub
                                        //这样做的目的是,为了让编译器知道,我们要访问的是Base中声明的a

        Base b2 = new Sub();//创建了子类的对象,此时是多态引用
        System.out.println(b2.a);//1  b2变量编译时类型是Base,运行时类型是Sub类型
                                //b2.a此时表示访问的是Base类中的a
        System.out.println(((Sub)b2).a);//2  ((Sub)b2)向下转型,把b2向下转型为Sub类型,为了让编译器知道,我们要访问的是Sub中声明的a

        System.out.println("----------------------------------");
        Base b3 = new Base();
        b3.c = 1;

        Base b4 = new Sub();
        System.out.println(b4.c);//0      c是实例变量,每一个对象都是独立的,b3对象修改b3.c为1,和b4对象的c无关

        Base b5 = new Sub();
        b5.c = 3;                           //b5.c,此时b5的编译时类型是Base,它修改的是Base类中声明的c
        System.out.println(((Sub)b5).c);//0  ((Sub)b5)向下转型,它访问的是Sub中声明的c
    }
}

class Base{
    int a = 1;//为了简化代码,直接初始化
    int c;
}
class Sub extends Base{
    int a = 2;
    int c;
}

4.多态引用的形式下,关于静态的变量,静态的方法的访问原则:
都只看编译时类型。

 当子类继承父类后,通过"子类对象.方法"或“子类名.方法"的形式调用方法时,
会先在子类中找是否有这样的方法,如果找到了,就执行子类的,
如果没有,就去父类中找是否有这样的方法,如果有的话,就OK,执行父类的,
如果所有父类(包括直接父类和父类的父类)都没有,就报错。

那么既然方法是代表事物的特征,那么如果父类的方法功能实现,不适用子类,怎么办?
答:子类可以选择重写。

方法的重写(Override)

当子类继承了父类之后,发现父类的某个方法实现不适用子类,那么子类可以选择对齐进行重写。

方法的重写有要求:
(1)方法名不能变,即重写方法与被重写方法的方法名必须一致,否则就不叫重写了。
(2)形参列表不能变,即重写方法与被重写方法的形参列表必须一致
(3)返回值类型,
    基本数据类型和void,必须一致。即重写方法与被重写方法的返回值类型一致。
    引用数据类型,可以是<=的关系。即重写方法的返回值类型 可以是被重写方法的返回值类型的子类或者一样。

    //此处关注返回值类型
    父类被重写方法:public void method(){}
    子类重写方法:public void method(){}

     //此处关注返回值类型
    父类被重写方法:public int method(){..}
    子类重写方法:public int method(){..}

     //此处关注返回值类型
    父类被重写方法:public Father method(){..}
    子类重写方法:public Father method(){..}

    //此处关注返回值类型
    父类被重写方法:public Father method(){..}
    子类重写方法:public Son method(){..}

    //此处关注返回值类型
    父类被重写方法:public Son method(){..}
    子类重写方法:public Father method(){..}  错误
(4)权限修饰符:重写方法的权限修饰符 >= 被重写方法的权限修饰符

    //此处关注权限修饰符
    父类被重写方法:public int method(){..}
    子类重写方法:public int method(){..}

    //此处关注权限修饰符
    父类被重写方法:int method(){..}
    子类重写方法:int method(){..}

        //此处关注权限修饰符
    父类被重写方法:protected int method(){..}
    子类重写方法:protected int method(){..}

    //此处关注权限修饰符
    父类被重写方法:int method(){..}
    子类重写方法:public int method(){..}

    //此处关注权限修饰符
    父类被重写方法:protected int method(){..}
    子类重写方法:public int method(){..}

    //此处关注权限修饰符
    父类被重写方法:public int method(){..}
    子类重写方法:int method(){..}     错误

 (5)throws异常列表的要求,等讲异常的时候再说。
 (6)特殊的几种情况,方法是不能被重写
    private的方法不能被重写。因为private的方法,子类不可见,所以无法重写。
    static的方法不能被重写。因为static的方法是在编译期间就确定的,根据哪个类型的类名/对象调用就是哪个类型的静态方法,没有重写一说。

public class TestOverride {
    public static void main(String[] args) {
        Rectangle r = new Rectangle();
        r.setLength(5);
        r.setWidth(2);
        System.out.println(r.area());
    }
}

//Graphic图形
class Graphic{
    //求面积
    double area(){
        return 0.0;//(1)这里为什么要有return 0.0; 的语句
                    // 因为area()方法有返回值,必须保证有return语句。
                    //又因为这里没有说明是具体哪种图形,无法给出统一的公式来求面积
                //(2)能不能不写double area()方法呢?
                //因为所有图形确实有求面积这个特征,那么作为所有图形的抽象描述的类,就应该声明这个方法,代表这类事物的特征。
                //类的定义:一类具有相同特性的事物的抽象描述。
                //而且从另一个角度来说,这是为我们后面的多态做准备。
    }
}
//子类继承了父类,子类也会继承父类的area()方法的事物特征,但是return 0.0;不符合矩形的实现
class Rectangle extends Graphic{
    private double length;
    private double width;

    public Rectangle() {
    }

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = 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;
    }

    //重写父类的area()方法
    public double area(){
        return length * width;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值