Day 05多态

文章详细介绍了Java中的`instanceof`关键字用于判断对象类型,以及类型转换的概念,包括向上转型和向下转型。此外,文章探讨了多态性在运行时的行为,并通过代码示例展示了如何在抽象类和继承的上下文中使用多态。同时,文章还包含了几个练习题,涉及几何形状类的比较和披萨类的创建,以及抽象类和具体类的实例化与方法调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多态

instanceof 和类型转换

  • instanceof引用类型比较,两种对象的类型是否一样,返回true /false。判断一个对象是什么类型
public class InstanceofTest {
    public static void main(String[] args) {
        Object object = new Student();
        System.out.println(object instanceof Student); //true
        System.out.println(object instanceof Person);//true
        System.out.println(object instanceof Object);//true

        System.out.println(object instanceof String); //false

        System.out.println("===========================");
        Person person = new Student();

        System.out.println(person instanceof Student);
        System.out.println(person instanceof Person);
        System.out.println(person instanceof Object);
    }
}
  • 类型转换
    • 父类引用引用指向子类对象
    • 把子类转换为父类,向上转型,会丢失自己原来的一些方法
    • 父类转换为子类,向下转型,强制转型,才能调用子类方法
    • 方便方法的调用(转型)

向上转型:

父类创建了一个子类对象(父类 名称1=new 子类)此时,调不了子类的特有属性或者方法,可以调用父子类共有的方法

向下转型:

子类对象强制转换(子类 名称2=(子类)名称1)强制转换,此时可以调用子类的特有属性或方法

public class Test {
    public static void main(String[] args) {

        Person person1 = new Student();
        person1.eat();
        person1.sleep();

        System.out.println("=============================");
        //TODO 向上转型,丢失子类特有的方法
        //不能调用 子类所特有的方法、属性, 编译的时候, person1是 Person
        person1.name = "leilei";
        person1.eat();
        //TODO 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类的类型,导致编译时,只能调用父类中声明的属性和方法。
        /*person1.height = 180; //无法调用
        person1.goSchool();//无法调用
         */

        System.out.println("=============================");
        //TODO 如何调用子类所特有的属性和方法?
        //TODO 使用强制类型转换,也称为向下转型
        Student stu = (Student) person1;
        stu.height = 180;

        //使用强转,可能出现问题 ClassCastException
        /*Women women = (Women) person1;
        women.goShopping();*/

        if (person1 instanceof Women) {
            Women women = (Women) person1;
            women.goShopping();
            System.out.println("**************Women*************");
        }

        if (person1 instanceof Student) {
            Student stu1 = (Student) person1;
            stu1.goSchool();
            System.out.println("**************Student*************");
        }

        if (person1 instanceof Person) {
            System.out.println("**************Person*************");
        }

        if (person1 instanceof Object) {
            System.out.println("**************Object*************");
        }
    }
}

练习题

定义三个类,父类GeometricObject代表几何形状,子类Circle代表圆形,MyRectangle代表矩形。

定义一个测试类GeometricTest,编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术), 编写displayGeometricObject方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。

在这里插入图片描述

public class GeometricObject {
    private String color;
    private double weight;

    public GeometricObject(String color, double weight) {
        this.color = color;
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
    public double findArea(){
        return 0;
    }
}
public class Circle extends GeometricObject{
    public static final double PI = 3.14;
    private double radius;

    public Circle(String color, double weight, double radius) {
        super(color, weight);
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public double findArea() {
        return PI * radius * radius;
    }
}
public class MyRectangle extends GeometricObject{
    private double width;
    private double height;

    public MyRectangle(String color, double weight, double width, double height) {
        super(color, weight);
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public double findArea() {
        return width * height;
    }
}

public class GeometricTest {
    public static void main(String[] args) {
        GeometricTest geometricTest = new GeometricTest();

        GeometricObject circle = new Circle("red",4,5);
        GeometricObject myrectangle = new MyRectangle("yellow",6,8,9);

        System.out.println(geometricTest.equalsArea(circle,myrectangle));
        System.out.println(geometricTest.displayGeometricObject(circle));
        System.out.println(geometricTest.displayGeometricObject(myrectangle));

    }
    public boolean equalsArea(GeometricObject g1,GeometricObject g2){
        return g1.findArea() == g2.findArea();
    }

    public double displayGeometricObject(GeometricObject geometricObject){
        return geometricObject.findArea();
    }
}

在这里插入图片描述

在这里插入图片描述

public class Piza {
    private String name;
    private double price;
    private int size;

    public Piza(String name, double price, int size) {
        this.name = name;
        this.price = price;
        this.size = size;
    }

    public void display() {
        System.out.println("披萨:" + name);
        System.out.println("价格:" + price + "元");
        System.out.println("尺寸:" + size + "寸");
    }
}

public class Bacon extends Piza{

    public Bacon(String name, double price, int size) {
        super(name, price, size);
    }
}
public class Seafood extends Piza{

    public Seafood(String name, double price, int size) {
        super(name, price, size);
    }
}

public class Factory {

    public static void product() {
        Scanner input = new Scanner(System.in);
        System.out.println("输入你要点的披萨(海鲜披萨或培根披萨):");

        String food1 = input.nextLine();
        System.out.println("输入你需要的披萨尺寸(6,12,20):");
        int size = input.nextInt();

        if (food1.equals("海鲜披萨")) {

            Bacon bacon = new Bacon(food1, size * 12, size);
            bacon.display();

        } else if (food1.equals("培根披萨")) {
            Seafood seafood = new Seafood(food1, size * 12, size);
            seafood.display();
        } else {
            System.out.println("输入不正确,请联系工作人员!");
        }
    }
}
public class Order {
    public static void main(String[] args) {
        Factory factory = new Factory();
        factory.product();
    }
}

在这里插入图片描述

面试题

多态是运行时行为,还是编译时行为?

class Animal {
    protected void eat() {
        System.out.println("Animal eat food");
    }
}

class Cat extends Animal {
    @Override
    protected void eat() {
        System.out.println("Cat eat food");
    }
}

class Dog extends Animal {
    @Override
    protected void eat() {
        System.out.println("Dog eat food");
    }
}

class Sheep extends Animal {
    @Override
    protected void eat() {
        System.out.println("Sheep eat food");
    }
}

class Test {
    public static Animal getInstance(int key) {
        switch (key) {
            case 0:
                return new Cat();
            case 1:
                return new Dog();
            default:
                return new Sheep();
        }
    }


    public static void main(String[] args) {
        int key = new Random().nextInt(3);
        Animal animal = getInstance(key);
        animal.eat();
    }
}

在运行完之后,可以看到运行结果并不是调用了Animal中 eat()方法,而是调用了判断之后,返回对象类型中的

eat()方法。这时就体现了多态是运行时的行为!

因为程序只能在程序运行的时候才能决定调用哪个对象的方法

object类使用

  1. Object是所有Java类的根父类
  2. 如果在类中的声明没有使用extends关键字指明父类,默认父类为java.lang.Object
  3. Object
方法类型描述
public Object()构造构造
public boolean equals()普通对象比较
public int hashcode()普通获取hashcode
public String toString()普通对象打印的时候使用

面试题 ==运算符与equals方法

==运算符:

  1. 可以使用在基本数据类型变量和引用数据类型变量中
  2. 如果比较的是基本数据类型,比较的是两个变量的字面值是否相等
  3. 如果比较的是引用数据类型,比较的是两个对象的地址是否相同,即两个引用是否指向同一个对象

equals()方法的使用:

  1. 只适用于引用数据类型
  2. Object类中定义的 equals() 和 == 的作用是相同,比较的是两个对象的地址值是否相同,即两个引用是否指向同一个对象。
  3. 像String、Date、File、包装类等都重写了Object类中定义的 equals()方法。比较两个对象的“实体内容”是否相同。

显然, 当equals为true时,==不一定为true。

public class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class ObjectTest {
    public static void main(String[] args) {
        int i = 100;
        int j = 100;
        double d = 10.0;
        System.out.println(i == j);
        System.out.println(i == d);


        char c = 10;
        System.out.println(i == c);


        char c1 = 'A';
        char c2 = 65;
        System.out.println(c1 == c2);



        String str1 = new String("BAT");
        String str2 = new String("BAT");
        System.out.println(str1 == str2);
        System.out.println(str1.equals(str2));


        Person aa = new Person("aa", 18);
        Person bb = new Person("aa", 19);

        System.out.println(aa == bb);
        System.out.println(aa.equals(bb));
    }
}

toString()

像String、Data、File、包装类等都重写了Object类中的toString方法。

代码块 执行顺序(重点!!)

  1. 代码块的作用:用来初始化类、对象的
  2. 代码块如果有修饰的 话,只能使用static

静态代码块:

  1. 内部可以有输出语句
  2. 随着类的加载而执行,而且只执行一次
  3. 作用:初始化类的信息
  4. 如果一个类中,定义了多个静态代码块,则按照声明的先后顺序执行
  5. 静态代码块的执行,优先于非静态代码块的执行
  6. 静态代码块内只能调用静态的属性、方法,不能调用非静态的结构。

非静态代码块:

  1. 内部可以有输出语句
  2. 随着对象的创建而执行
  3. 每创建一个对象,就执行一次非静态代码块
  4. 作用:可以在创建对象时,对对象的属性进行初始化
  5. 如果一个类中,定义了多个非静态代码块,则按照声明的先后顺序执行
  6. 非静态代码块可以调用静态的属性,方法 或者 非静态的属性、方法。

静态区代码加载类时以弃被初始化,最早执行且只执行一次

先静态代码块(里面有静态方法就执行)

public class Test {

    {
        System.out.println("匿名代码块");
    }

    static {
        System.out.println("静态代码块");
    }

    public Test() {
        System.out.println("构造方法");
    }

    public static void main(String[] args) {
        Test test = new Test();
        System.out.println("===========");

        Test test1 = new Test();
    }
}

在这里插入图片描述

由父类到子类,静态先行

public class Root {//父亲
        static {
            System.out.println("Root 的静态初始化块");
        }

        {
            System.out.println("Root 的普通初始化块");
        }

        public Root() {
            System.out.println("Root 的 无参构造器");
        }
    }

    class Mid extends Root {//儿子

        static {
            System.out.println("Mid 的静态初始化块");
        }

        {
            System.out.println("Mid 的普通初始化块");
        }

        public Mid() {
            System.out.println("Mid 的 无参构造器");
        }

        public Mid(String msg) {
            this();
            System.out.println("Mid 的 有参构造器,参数为" + msg);
        }
    }

    class Leaf extends Mid {//孙子
        static {
            System.out.println("Leaf 的静态初始化块");
        }

        {
            System.out.println("Leaf 的普通初始化块");
        }

        public Leaf() {
            super("herb");
            System.out.println("Leaf 的 无参构造器");
        }
    }

    class LeafTest {
        public static void main(String[] args) {
            new Leaf();
        }
    }

在这里插入图片描述

抽象类

为什么需要抽象类?

类继承的主要作用在于可以扩充已有类的功能,但是对于之前的继承操作会发现,子类可以由自己的选择任意来决定是否需要覆写某一个方法,这个时候父类无法对子类做出强制性约定,这种情况下往往不会采用类的继承,而是必须要继承抽象类。

抽象类的主要作用在于对子类中的覆写方法进行约定,在抽象类里面可以定义一些抽象方法实现前面说的约定。

抽象方法指的是使用 abstract 关键字定义的并且没有提供方法体的方法。而抽象方法所在的类必须为抽象类,抽象类必须使用 abstract 关键字 进行 定义

abstract class Message {
    private String type;

    public abstract String getInfo();

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

public class AbstractTest {
    public static void main(String[] args) {
        Message message = new Message();
        System.out.println(message.getInfo());
    }
}

上面的代码会编译失败

为什么?

抽象类是不能直接new 的

当一个抽象类定义完成之后,如果想要去使用抽象类。必须按照以下的规则:

  • 抽象类必须提供子类,子类使用 extends 继承一个抽象类
  • 抽象类的子类一定要 重写 抽象类的全部抽象方法
  • 抽象类的对象实例化可以利用 对象多态性通过子类向上转型的方式完成
abstract class Message {
    private String type;

    public abstract String getInfo();

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}
class DataBaseMessage extends Message {

    @Override
    public String getInfo() {
        return "连接";
    }
}

public class AbstractTest {
    public static void main(String[] args) {
        Message message = new DataBaseMessage();
        System.out.println(message.getInfo());
    }
}

相关说明

  1. 在定义抽象类的时候决不能使用 final 关键字来定义,因为抽象类必须有子类,而 final 定义的类 断子绝孙
  2. 抽象类是作为一个普通类的加强版,只是追加了抽象方法。
  3. 抽象类中可以没有抽象方法,但是即便没有抽象方法,也无法使用关键字new 直接实例化抽象类对象
  4. 抽象类中可以提供 static 方法,并且该方法不受到抽象类对象的 局限
abstract class Message {
    private String type;

    public abstract String getInfo();

    public static Message getInstance() {
        return new DataBaseMessage();
    }
}
class DataBaseMessage extends Message {

    public DataBaseMessage() {
        System.out.println("1111");
    }

    @Override
    public String getInfo() {
        return "连接";
    }
}

public class AbstractTest {
    public static void main(String[] args) {
        Message message = new DataBaseMessage();
        System.out.println(message.getInfo());
    }
}

总结

abstract 修饰类:抽象类

抽象类不能实例化

抽象类中一定有构造方法,便于子类实例化时调用

abstract 修饰方法:抽象方法

抽象方法,只有方法的声明,没有方法体

包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法

abstract 注意点:

  1. abstract 不能修饰变量、代码块、构造器
  2. abstract 不能用来修饰私有方法,静态方法,final的方法、final的类
  3. 但是抽象类中可以有static方法

练习

编写一个 Employee 类,声明为抽象类,

包含如下三个属性:name,id,salary。

提供必要的构造器和抽象方法:work()。

对于 Manager 类来说,他既是员工,还具有奖金(bonus)的属性。

请使用继承的思想,设计 CommonEmployee 类和 Manager 类,

要求类中提供必要的方法进行属性访问。

abstract class Employee {
    private String name;
    private int id;
    private double salary;

    public Employee() {
    }

    public Employee(String name, int id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }
    public abstract void work();

}
public class Manager extends Employee{
    private double bonus;

    public Manager(double bonus) {
        this.bonus = bonus;
    }

    public Manager(String name, int id, double salary, double bonus) {
        super(name, id, salary);
        this.bonus = bonus;
    }

    @Override
    public void work() {
        System.out.println("管理员工");
    }
}
public class CommonEmployee extends Employee{

    @Override
    public void work() {
        System.out.println("生产产品");
    }
}

public class EmployeeTest {
    public static void main(String[] args) {
        Employee prestige = new Manager("prestige", 1, 2000, 1000);
        prestige.work();

        CommonEmployee commonEmployee = new CommonEmployee();
        commonEmployee.work();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7n4LSDD-1673527096163)(C:\Users\bkpp\AppData\Roaming\Typora\typora-user-images\image-20230108230038080.png)]

抽象类中有无参构造函数,有参构造函数,抽象方法,一定要重写抽象类的全部抽象方法,而有参无参函数可以根据用户的需求选择重写。

抽象类不能直接实例化,需经过子类向上转型 (父类是抽象类) :

子类 对象1 = new 子类();--------->父类 对象1 = new 子类()

lary);
this.bonus = bonus;
}

@Override
public void work() {
    System.out.println("管理员工");
}

}


public class CommonEmployee extends Employee{

@Override
public void work() {
    System.out.println("生产产品");
}

}


public class EmployeeTest {
public static void main(String[] args) {
Employee prestige = new Manager(“prestige”, 1, 2000, 1000);
prestige.work();

    CommonEmployee commonEmployee = new CommonEmployee();
    commonEmployee.work();
}

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/0663b076cc864b1aa814445bd0cb56fc.png#pic_center)


抽象类中有无参构造函数,有参构造函数,抽象方法,一定要重写抽象类的全部抽象方法,而有参无参函数可以根据用户的需求选择重写。

抽象类不能直接实例化,需经过子类向上转型 (父类是抽象类) :

子类  对象1 = new 子类();--------->父类  对象1 = new 子类()

3. 几何图形(满分50分) 版本1:满分 10 分 设计抽象类 GeometricObject 及其子类 Triangle 和Circle。 GeometricObject 类设计要求如下: ■ 一个名为 color 的Color类型的私有数据域,表示对象的颜色 ■ 一个名为 filled 的Boolean类型的私有数据域,表示对象是否 ■ 一个名为 dateCreated 的Date 类型的私有数据域,表示对象的 ■ 一个无参构造方法。 ■ 一个能创建特定 color 和filled 的有参构造方法。 ■ 相关数据域的访问器和修改器。 ■ 两个个名为 draw 和erase的抽象方法。 ■ 一个名为 getArea的抽象方法。 ■ 一个名为 getPerimeter的抽象方法。 ■ 重写 toString 方法。 Triangle 类设计要求如下: ■ 三个名为 side1、side2和 side3 的double 类型的私有数据域表 们的默认值是 1.0。要求三个数据域保留 2 位小数。 ■ 一个无参的构造方法创建默认三角形。 ■ 一个能创建带指定 side1、side2和 side3 的有参构造方法。 ■ 所有三个数据域的访问器和修改器方法。 ■ 父类抽象方法的实现。 ■ 重写 toString 方法。 Circle 类设计要求如下: ■ 一个名为 radius 的double 类型的私有数据域,表示圆的半径,数据域保留2 位小数。 ■ 一个名为 PI 的静态常量,其值为 3.14 ■ 一个无参的构造方法创建默认三角形。 ■ 一个能创建带指定 radius 的有参构造方法。 ■ radius 数据域的访问器和修改器方法。 ■ 父类抽象方法的实现。 ■ 重写 toString 方法。 测试类 TestGeometricObject1 设计要求如下: ■ 一个能随机生成 Circle 类和Triangle 类对象的静态方法 GeometricObject[] RandomCreateGeometricObject() ■ 以随机生成的数组为参数,输出数组中每个对象的基本信息、周长和面积。 ■ 类中其它方法的测试 版本2:满分 20 分 将上面的抽象类GeometricObject 改为接口,接口只保留其中四个抽象方法,声明类 Circle、Triangle 实现该接口,类的基本要求如上,同时为每个类增加一个将当前对象序列化 到指定文件的方法 writeToFile(File f)。 测试类 TestGeometricObject2 设计要求如下: ■ 一个能随机生成 Circle 类和Triangle 类对象的静态方法,该方法将随机生成的象序列 化到指定的文件 GeometricObjects.dat 中,序列化成功返回真,否则返回假。 Boolean RandomCreateGeometricObject() ■ 将GeometricObjects.dat 文件中对象全部读出,存储到 GeometricObject 对象数组中, 然后以此数组为参数,输出数组中每个对象的基本信息、周长和面积。 ■ 类中其它方法的测试。 新增一个类Rectangle ,也实现接口 GeometricObject ,同时修改测试类 TestGeometricObject2 ,体会开-闭原则。 版本3:满分 20 分 在第2 步的基础上设计实现一个具有 GUI 界面的几何图形绘制系统系统,要求实现根 据选择的几何图形类型来绘制和删除相应的图形,其中相关参数应通过界面输入,并可计算 图形的周长和面积。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值