Java 类和对象——继承、多态

一、继承

有些类在设计上是有关联的,比如我们定义了一个动物类:

class Animal {
    public String name;
    public int age;
    public String sex;
    
    public void eat() {
        System.out.println("动物在eat");
    }
}

在定义一个猫类:

class Cat {
    public String name;
    public int age;
    public String sex;

    public void eat() {
        System.out.println("猫在eat");
    }
    
    public void catSound() {
        System.out.println("猫发出的声音是喵喵");
    }
}

    我们发现:这两个类有很多的部分是重叠的。那么,能不能降低重叠的部分,把Animal类中的直接拿过来在Cat类中使用呢?面向对象思想中提出了继承的概念。

1、继承的概念

    继承:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。简单来说,继承是is--a的关系:猫是一个动物。

2、定义继承

Java中要表示类的继承关系,需要extends(扩展)关键字。Cat如何继承Animal:

    类只有两种访问修饰限定符:public和什么都不加的默认权限访问。一个Java文件中只能只能有一个public类。由于main方法所在的类有一个public,所以其他的类都是默认的访问修饰限定符。这里的Cat是子类(或者派生类),Animal是父类(或基类、超类)。中间的extends表示扩展、继承。

那么上面的两个类就可以写为:

class Animal {
    public String name;
    public int age;
    public String sex;

    public void eat() {
        System.out.println("动物在eat");
    }
}

class Cat extends Animal {

    //Cat类继承了Animal类的属性和方法。

    public void catSound() {
        System.out.println("猫发出的声音是喵喵");
    }
}

现在我们以Base类(基类)和Derived类(派生类)来举例。

class Base {
    public int data1 = 10;
    public int data2 = 20;

    public void baseMethod() {
        System.out.println("父类方法");
    }
}

class Derived extends Base {
    public int data3 = 30;
    public int data4 = 40;

    public void derivedMethod() {
        System.out.println("子类方法");
    }

}

访问子类和父类的属性或方法:

发现:实例化子类对象,红色的框中出现子类继承过来父类的一些属性。 

3、super关键字

    现在在子类中写一个derivedBaseSameNameAttribute()方法表示出现子类和父类中变量名同名的情况,在Base类中加一个data5=51;Derived类中加一个data5=50,然后在这个方法中访问。

打印的是子类中的data5。要访问父类的data5,需要加super关键字。super表示父类对象的引用。

    super和this关键字的使用差不多。super和this一样有三种使用方法:super.data,表示调用父类的属性、super.func()表示调用父类的方法、super()表示调用父类中的构造方法。这里的super.data5就表示调用父类的属性。

如果是成员方法名相同:在Base类和Derived类中加一个derivedBaseSameNameMethod()方法。

    打印的是子类的derivedBaseSameNameMethod()方法。在main方法中要打印父类的同名方法,加一个super是不行的(同名属性也不行)。要么在子类中写一个方法来间接调用、要么将父类的derivedBaseSameNameMethod()方法和子类的方法形成重构、要么实例化父类对象访问。

这里的参数没有意义,只是为了调用父类的构造方法。但是最好不要这样写没有意义的参数。

总结:

(1)在子类中,如果想要明确来访问父类中的成员时,需要super关键字就可以了。父类和子类中不重名的时候也可以使用this。

(2)重名的时候,编译器采用就近原则。先去子类中找,找不到就去父类(不能从父类到子类)。还找不到就报错。

(3)通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法构成重载,根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;如果父类和子类同名方法相同(方法名、参数列表等)则只能访问到子类的,父类的会被覆盖掉(构成重写),无法通过子类对象直接访问到。

(4)super关键字只能在非静态方法中使用。在子类方法中访问父类的成员。

super和this比较,相同点:

(1)都是Java中的关键字,只能在类的非静态方法中使用。

(2)用在构造方法中,都要放在相对的第一行。也就是说它们不能同时存在。

不同点:

(1)this是当前对象的引用,super是子类对象从父类中继承下来的成员的引用。也就是说,this一般用来访问子类的方法和属性,super用来访问父类的方法和属性。

(2)this是非静态成员方法的一个隐藏参数,super不是隐藏的参数。

(3)构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。

4、子类构造方法

    在没有继承的时候,对类中的成员变量进行初始化,有就地初始化、默认初始化,代码块初始化等、最重要的是用构造方法进行初始化。那么一个类被继承后,有没有什么变化呢?

在父类写出它的带参数的构造方法。

给子类也加一个构造方法。

    我们知道,子类是继承了父类的。所以子类在进行构造的时候,应该要给父类也进行构造。所以子类在构造的时候,要先帮助父类进行构造,在调用子类的构造方法。使用super()就可以了。

带参数的构造方法:

不带参数的构造方法:

 总结:

(1)若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法。

(2)如果父类构造方法是带有参数的,此时编译器不会再给子类生成默认的构造方法,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。

(3)super()和this()相似,都要放在构造方法中的第一行。也就是说,super()和this()不能同时使用。

5、protected关键字

    当父类中的属性被private修饰的时候,只能在父类中使用。子类虽然是继承了父类,但是访问不到这个属性。

只需要将这个private改为protected(受保护的)权限就可以了。

    我们知道,Java中权限有四种:private、默认、protected和public。private表示只能在当前类中使用。默认表示只能在同一个包中使用。protected是继承权限,范围比默认的要大,可以在不同的包中访问到。public是公共权限,在哪里都能访问。

    类要尽量做到封装,隐藏内部实现细节,只留下必要的信息给类的调用者。因此,要使用比较严格的访问权限。能提供private的就不要提供protected权限。

6、继承方式

Java支持多种继承方式:

    Java不支持一个类继承多个类的继承方式。但是可以使用接口来实现。在继承关系中,一般不要超过三层的继承关系。如果一个类不想被继承,就可以使用final关键字来修饰。final可以用来修饰变量或字段,表示常量(不能被修改);修饰类,表示这个类是一个密封类,不能被继承;修饰方法,表示这个方法不能被重写。

7、组合

    继承是一种表达类之间关系的方式,组合也是一种表达类之间关系的方式。组合只是将一个类的实例作为另一个类的字段。

有三个类:学生类、老师类和学校类。要把他们组合起来,就是学校里面有学生,学校里面有老师。

这样创建之后是不能直接访问的,要对学生类和老师类进行实例化后才能访问。

    其他的就和前面的语法没有什么区别。我们写课后作业的时候的测试类就是一个组合。继承是is--a的关系:猫是一个动物,而组合是has--a的关系:动物有猫这个物种。

二、多态

    多态具体一点就是要去完成某个行为,不同的对象去完成,会产生不同的状态。比如考试,学霸考试得到高分,学渣考试得到混合双打,这是一种多态。

1、多态实现的条件

(1)必须要在继承的体系下。

(2)子类对父类中的方法进行重写。

(3)通过父类引用去调用子类重写的方法。

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。下面对这些条件分别做介绍。

2、向上转型和向下转型

    向上转型是实现多态的一部分条件,它在继承的体系下。多态中上指的是父类,下指的是子类。向上转型就是子类转型成父类。或者:父类引用,引用子类对象。只要记住父类引用,引用子类对象。这是从小的范围像大的范围转换。

class Base {
    public int data1;
    private int data2;
    
    public void BaseMethod() {
        System.out.println("父类方法");
    }
    
}

class FirstDerived extends Base {
    public int data3;
    private int data4;

    public void derivedMethod() {
        System.out.println("子类方法");
    }

}

public class test_04_03_04 {//本人的Java文件名

    public static void main(String[] args) {
        
        Base base = new FirstDerived();
        base.BaseMethod();
        
    }
}

    父类引用了子类对象,但是不能调用子类方法。这个derivedMethod()方法在父类中是没有的。向上转型有三种用法:直接赋值、作为参数传参、作为返回值。

直接赋值:

作为参数传参:在main方法中新写一个方法,用来传参数。

 作为返回值:写一个方法,返回值是Base类型。

这三种虽然用处不一样,但本质上还是父类引用,引用子类对象。

向上转型能够让代码实现更灵活,缺点是不能调用子类特有的方法。

向下转型是子类引用,引用父类方法。但是这意味着范围的缩小,所以它不安全。

图示:

3、重写方法

    重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。

定义一个Animal类,Cat类和Dog类继承了Animal类。

class Animal {
    public String name;
    public int age;
    
    public void eat() {
        System.out.println(this.name + " 吃食物");
    }
}

class Cat extends Animal {
    
    public void catchingMice() {
        System.out.println(this.name + " 抓老鼠");
    }
}

class Dog extends Animal {
    
    public void housekeeping() {
        System.out.println(this.name + " 看家门");
    }
}

public class test_04_03_05 {

    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
    }
}

    我们知道,猫是可以吃猫粮的,狗是可以吃狗粮的。所以,对父类中的eat()方法能不能改一下呢?可以改,但是不能直接在父类方法中改,并不是所有的动物都是吃猫粮的。为了代码的直观,删去了Cat类中的catchingMice()方法和Dog类中的housekeeping()方法。

    这个时候,子类和父类的eat方法中:返回值相同、方法名相同、方法参数列表相同。这就构成了方法的重写:子类的eat()方法对父类的eat()方法进行了扩展或覆盖。我们在用向上转型去调用子类的eat()方法。 

发现这里的结果是调用了子类的eat()方法。为了让重写显着更清楚,可以加上@Override。

这样,不仅可以显示更清楚,而且如果变量名等写错,就可以提示出来。 

    在编译的时候,eat()调用应该是父类的方法,结果运行的时候,调用的是子类的方法。这种情况是在运行的时候发生的,叫运行时绑定(动态绑定)。

    动态绑定,也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用哪个类的方法。

重写要注意的几个点:

(1)子类在重写父类的方法时,一般必须与父类方法原型一致:修饰符 返回值类型 方法名(参数列表) 要完全一致。

(2)返回值可以不同,但是要形成父子关系,叫协变类型。

(3)子类的重写方法访问权限不能比父类低。如果父类时protected,子类就不能是 默认或private;如果父类是public,子类只能是public。

(4)父类静态的,private修饰的,final修饰的方法都不能重写。

(5)和重载的区别

方法的重载是一个类的多态的体现,方法的重写是子类和父类多态的体现。

    现在,在主类中写一个方法,传进去不同的对象, 调用父类的eat()方法,显示因为传进去猫这个对象,打印吃猫粮,传进去狗这个对象,打印吃狗粮。

class Animal {
    public String name;
    public int age;

    public void eat() {
        System.out.println(this.name + " 吃食物");
    }
}

class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println(this.name + " 吃猫粮");
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println(this.name + " 吃狗粮");
    }
}

public class test_04_03_05 {

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

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name = "mimi";
        Dog dog = new Dog();
        dog.name = "大黄";
        function(cat);
        function(dog);
    }
}

4、使用多态的优缺点

优点:

(1)提高了代码的可读性,避免了使用大量的if-else。

(2)可扩展能力更强。

比如,打印不同的图形,这样,使用多态直接扩展就可以了。

class Shape{

    public void draw() {
        System.out.println("打印图形");
    }
}

class Cycle extends Shape {

    @Override
    public void draw() {
        System.out.println("打印⚪");
    }
}

class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("打印♦");
    }
}

class Flower extends Shape {

    @Override
    public void draw() {
        System.out.println("打印♣");
    }
}

public class test_04_04_04 {

    public static void drawShapes() {
        Shape[] shapes = {new Cycle(), new Rect(), new Flower()};
        for (Shape shape : shapes) {
            shape.draw();
        }
    }

    public static void main(String[] args) {
        drawShapes();
    }
}

缺点:代码的运行效率低,而且,不能使用子类的方法。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值