我是靠谱客的博主 俏皮盼望,最近开发中收集的这篇文章主要介绍Java SE基础(十一)面向对象2 类的继承与多态概述类的继承类的多态尾言,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Java SE基础(十一)面向对象2

  • 概述
  • 类的继承
    • 好处与弊端
    • Java中继承的特点
    • Java继承中成员变量与成员方法的特点
    • 方法重写
  • 类的多态
    • 好处与弊端
    • 向上转型与向下转型
    • 判断是否为同一类
    • 对象中的成员访问特点
  • 尾言

概述

之前有写过类的封装。

类的封装

今天再写一篇类的继承与多态,类的三大特性就记录完毕了。。。

类的继承

先举个栗子。。。

public class Student  {
    public void study() {
        System.out.println("学生要学习");
        }
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class Teacher {
    public void teach(){
        System.out.println("老师要教学");
        }
        
    private String name;
    private int age;

    public Teacher() {
    }

    public Teacher(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class Run {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        teacher.sleep();
        teacher.eat();
        teacher.teach();
        System.out.println("--*************--");

        Student student = new Student();
        student.sleep();
        student.eat();
        student.study();
    }
}

显然出现了非常多的重复代码。复制粘贴的方式表面上可行,但代码的复用性与可维护性都很差。。。为此,当多个类中存在相同的属性和行为时,需要将共同的属性和行为单独抽取到一个类,多个类便无需重复照搬代码来定义属性及行为,只需要继承父类即可获得很多特性,这种关系就是继承。

public class 类A extends 类B{
    
}

使用到关键字extends
其中,类A叫子类(派生类),类B叫父类(基类,超类)。

对上述代码进行抽取。

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void eat(){
        System.out.println("人要吃饭");
    }

    public void sleep(){
        System.out.println("人要睡觉");
    }

}

子类中只需要:

public class Student extends Person {
    public void study() {
        System.out.println("学生要学习");
    }
}
public class Teacher extends Person{
    public void teach(){
        System.out.println("老师要教学");
    }
}

瞬间清爽了很多。

好处与弊端

好处:
提高了代码的复用性
提高 了代码的可维护性
让类与类直接产生关系,是多态的前提。

弊端:
类与类之间之间产生关系,就会让类与类的耦合度提高。

开发中追求的是高内聚(类自己独立完成某些事情的能力)、低耦合(类与类之间的关系)。

Java中继承的特点

先看一段代码。

public class Grandfather {
    public Grandfather() {
        System.out.println("这是爷爷");
    }
    public void GrandFatherSay(){
        System.out.println("我是爷爷");
    }
    public void Say(){
        System.out.println("爷爷说:");
    }
}
public class Father extends Grandfather {
    public Father() {
        System.out.println("这是爸爸");
    }
    public void FatherSay(){
        System.out.println("我是爸爸");
    }
    public void Say(){
        System.out.println("爸爸说:");
    }
}
public class Son extends Father{
    public Son() {
        System.out.println("这是儿子");
    }

    public void SonSay(){
        System.out.println("我是儿子");
    }

    public void Say(){
        System.out.println("儿子说:");
    }
}
public class Run {
    public static void main(String[] args) {
        Grandfather grandfather = new Grandfather();
        Father father = new Father();
        Son son = new Son();

        System.out.println("--*********--");

        grandfather.GrandFatherSay();
        father.FatherSay();
        son.SonSay();

        System.out.println("--*********--");

        grandfather.Say();
        father.Say();
        son.Say();
    }
}

运行后:

这是爷爷
这是爷爷
这是爸爸
这是爷爷
这是爸爸
这是儿子
--*********--
我是爷爷
我是爸爸
我是儿子
--*********--
爷爷说:
爸爸说:
儿子说:

Process finished with exit code 0

这段代码可以说明很多问题。

首先,通过继承,类的构造方法中的所有方法显然都被继承下来了。子类中可以写与父类不同的成员方法。当然也可以写同名但是功能不同的方法(方法的重写,实现多态的前提)。

Java不像C++那样可以多继承,Java的类与类之间只能单继承(但是可以通过接口实现多继承的功能)。Java的类与类之间可以多层继承。。。

Java继承中成员变量与成员方法的特点

先看代码。

public class Father {
    public int age ;

    public Father(){
        this.age = 40;
    }

    public Father(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void show(){
        System.out.println("Father~age = " + age);
    }

}
public class Son extends Father {
    public int age = 10;

    public Son(){
        super();//无参访问可以什么都不写,第一行会默认有super();
    }

    public Son(int age){
        super(age);
    }

    public void show(){
        int age = 20;
        System.out.println("Son~age = " + age);//不修饰时优先就近访问局部变量/形参
        System.out.println("this.age = " + this.age);//有this修饰时访问本类的属性
        System.out.println("super.age = " + super.age);//使用super可以访问父类的属性
        super.show();//子类中可以使用super来调用父类的方法
    }

//    public void Age(int age){
//        super(age);
//    } //必须在构造函数中才能使用super调用父类的构造方法

}
public class Run {
    public static void main(String[] args) {
        Son son1 = new Son();

        son1.show();
        //如果子类中没有该方法,就去父类中找相同名称的方法

        son1.setAge(50);
        System.out.println("son1.getAge() = " + son1.getAge());

        Son son2 = new Son(60);

        System.out.println("son2.getAge() = " + son2.getAge());

    }
}

执行后:

Son~age = 20
this.age = 10
super.age = 40
Father~age = 40
son1.getAge() = 50
son2.getAge() = 60

Process finished with exit code 0

从中可以看出,Java中变量遵循就近原则,未指明时优先使用局部变量,使用this后指向当前对象的变量,使用super可以指向父类的变量。

通过super.父类的成员变量名 可以访问父类的成员变量,使用 super(有参或无参) 可以调用父类的构造方法(调用父类构造方法时,super()必须在子类构造方法的第一行,构造方法不写内容时默认调用的也是这个),使用 super.成员方法名 可以调用父类的普通方法。

子类的构造方法中必须第一句是super(有参或无参),目的是子类对象访问父类数据前,确保父类的数据已经初始化完毕。如果父类没有写构造方法或漏写,会导致后续子类调用比较繁琐,最好是使用idea自动生成有参、无参构造方法及所有参数的get与set方法。
在这里插入图片描述
子类的构造方法只能选父类的。。。这样可以避免出错。

同理,继承中成员方法也遵循就近原则。子类没有就去父类查找,找不到则编译不过(报错)。

方法重写

重写和重载有本质区别!!!

重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

方法重写是父类与子类有一模一样的方法,外壳相同(参数列表、返回值、名称等),功能略有不同。

在这里插入图片描述
子类重写父类方法时,访问权限不能更低。。。
Public 访问权限最高,公共的访问权限
Protected 受保护的访问。
默认的访问权限 (不写修饰符时)
Private 访问权限最低,只能本类访问。

可以使用@Override来确认重写时方法名称是否正确。

在这里插入图片描述
子类中重写时方法名写错会报错。。。

类的多态

多态是指同一事物(对象)在不同时刻表现出不同的状态。

类的多态实现起来需要父类与子类之间有继承关系,要有方法的重载,还需要父类引用指向子类对象。

举个栗子:

public class Animal {
    public void eat(){
        System.out.println("动物需要吃饭");
    }
}
public class Cat extends Animal {
    @Override
    public void eat(){
        System.out.println("猫会吃鱼");
    }

    public void play(){
        System.out.println("猫会玩毛线球");
    }
}
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗会吃肉");
    }
}
public class Run {
    public static void main(String[] args) {

        //向上转型任何时候都没有问题
        Animal animal1 = new Cat();
        Animal animal2 = new Dog();

        System.out.println("animal1 = " + animal1);
        System.out.println("animal2 = " + animal2);

        animal1.eat();
        animal2.eat();
        //animal1.play();//报错。。。向上转型时不能使用子类的特有方法

        Cat cat1 = (Cat)animal1;
        Dog dog1 = (Dog)animal2;
        System.out.println("cat1 = " + cat1);
        System.out.println("dog1 = " + dog1);//指向相同地址,说明内存中的数据没变
        //Dog dog2 = (Cat)animal1;//这种情况下会报错。。。向下转型只能转回原来的子类
        cat1.play();//此时可以使用Cat类特有的方法

    }
}

运行后:

animal1 = Demo6in2021_3_29.Cat@1b6d3586
animal2 = Demo6in2021_3_29.Dog@4554617c
猫会吃鱼
狗会吃肉
cat1 = Demo6in2021_3_29.Cat@1b6d3586
dog1 = Demo6in2021_3_29.Dog@4554617c
猫会玩毛线球

Process finished with exit code 0

在这里插入图片描述
向下转型时对象类型不对会报错。

这段代码能说明很多问题。

向上转型向下转型时对象指向的堆内存地址是相同的!!!说明在底层数据量是相同的。。。

好处与弊端

好处:
提高了程序的扩展性。同时,开发时也可以少记很多方法名称(这点与方法重载很像)。

弊端:
父类只能使用自己已有的方法(这样已经实现了多态),不再能够使用子类的特有方法。但是这个问题可以通过向下转型来解决。

向上转型与向下转型

使用 父类 对象名 = new 子类的构造方法;的格式即可使用向上转型,用于实现类的多态。

需要使用类的特有方法时,使用 子类 对象名 = (子类) 对象名; 的格式即可向下转型。

这一点很像基本数据类型中的强制类型转换。

当向下转换的时候,类与原对象的类不同时,会报错且编译不过。。。为了防止将对象作为形参传入方法造成报错的这种情况,需要在向下转型前先判断是否为同一类。

判断是否为同一类

public class Animal {
    public void eat(){
        System.out.println("动物要吃饭");
    }
}
public class Cat extends Animal {
    public void play(){
        System.out.println("猫会玩毛线球");
    }
    @Override
    public void eat(){
        System.out.println("猫要吃鱼");
    }
}
public class Dog extends Animal {
    public void swim(){
        System.out.println("狗会游泳");
    }

    @Override
    public void eat(){
        System.out.println("狗要吃肉");
    }
}

public class Run {
    public static void main(String[] args) {
        Animal animal1 = new Cat();
        Animal animal2 = new Dog();
        animal1.eat();
        animal2.eat();
        System.out.println("--*****************--");
        actor(animal1);
        actor(animal2);

    }

    public static void actor(Animal animal) {
        animal.eat();
        if (animal instanceof Cat) {//利用instanceof判断是否为同一个类,防止出错
            ((Cat) animal).play();
        }

        if (animal instanceof Dog) {//利用instanceof判断是否为同一个类,防止出错
            ((Dog) animal).swim();
        }
    }

}

运行后:

猫要吃鱼
狗要吃肉
--*****************--
猫要吃鱼
猫会玩毛线球
狗要吃肉
狗会游泳

Process finished with exit code 0

利用instanceof判断是否为同一个类,防止出错。

对象中的成员访问特点

先看段代码:

public class Person {
    public int age = 30;

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person() {
        //this.age = 30;
    }

    public Person(int age) {
        this.age = age;
    }

    public void eat(){
        System.out.println("人要吃饭");
    }


}
public class Student extends Person {
    public int age = 20;
    public Student() {
        //this.age = 20;
        super();
    }

    public Student(int age) {
        super(age);
    }

    public void eat(){
        System.out.println("学生爱吃饭");
    }


    public int getAge() {
        return this.age;
    }


    public void setAge(int age) {
        this.age = age;
    }

}
public class Run {
    public static void main(String[] args) {
        Person person1 = new Student();
        Student student1 = new Student();
        System.out.println("student1 = " + student1);
        System.out.println("person1 = " + person1);
        System.out.println("System.identityHashCode(person1.age) = " + System.identityHashCode(person1.age));
        System.out.println("System.identityHashCode(person1.getAge()) = " + System.identityHashCode(person1.getAge()));


        System.out.println("person1.getAge() = " + person1.getAge());//=20?????如果子类不重写get函数,对象年龄也是30
        System.out.println("student1.getAge() = " + student1.getAge());//如果子类不重写get函数,学生年龄也是30
        System.out.println("person1.age = " + person1.age);//=30?????
        System.out.println("student1.age = " + student1.age);//=20
        System.out.println("--**************--");

        Student student2 = (Student)person1;
        System.out.println("student2 = " + student2);
        System.out.println("student2.getAge() = " + student2.getAge());

        person1.eat();
        student1.eat();
    }
}

运行后:

student1 = Demo7in2021_3_29.Student@1b6d3586
person1 = Demo7in2021_3_29.Student@4554617c
System.identityHashCode(person1.age) = 1956725890
System.identityHashCode(person1.getAge()) = 356573597
person1.getAge() = 20
student1.getAge() = 20
person1.age = 30
student1.age = 20
--**************--
student2 = Demo7in2021_3_29.Student@4554617c
student2.getAge() = 20
学生爱吃饭
学生爱吃饭

Process finished with exit code 0

笔者很惊喜地发现,

person1.getAge() = 20,
person1.age = 30

他们居然不一样!!!

利用System.identityHashCode()看到,person1.ageperson1.getAge()指向了不同的堆内存地址。这是因为子类中重写了父类的getAge方法,导致了异常。。。

将子类中的

public int getAge() {
    return this.age;
}

屏蔽掉以后运行:

student1 = Demo7in2021_3_29.Student@1b6d3586
person1 = Demo7in2021_3_29.Student@4554617c
System.identityHashCode(person1.age) = 1956725890
System.identityHashCode(person1.getAge()) = 1956725890
person1.getAge() = 30
student1.getAge() = 30
person1.age = 30
student1.age = 20
--**************--
student2 = Demo7in2021_3_29.Student@4554617c
student2.getAge() = 30
学生爱吃饭
学生爱吃饭

Process finished with exit code 0

这时利用System.identityHashCode()看到,person1.ageperson1.getAge()指向了相同的堆内存地址,表明此时输出的30即为person1对象的age数值,而20为student1的age数值。

从中可以看出,

成员变量:编译看左边,运行看左边(按左边的类型来赋初值及访问变量值)。

成员方法:编译看左边,运行看右边(按右边的类型来调用类的成员方法)。

尾言

类的三大特性就暂时告一段落了。。。

最后

以上就是俏皮盼望为你收集整理的Java SE基础(十一)面向对象2 类的继承与多态概述类的继承类的多态尾言的全部内容,希望文章能够帮你解决Java SE基础(十一)面向对象2 类的继承与多态概述类的继承类的多态尾言所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(36)

评论列表共有 0 条评论

立即
投稿
返回
顶部