继承与组合(Java详解)

本文围绕Java开发展开,详细介绍了继承、父类成员访问、组合和多态等内容。阐述了继承的由来、概念、使用和方式,分析了父类成员访问规则,对比了组合和继承的优缺点,还讲解了多态的概念、使用、重写及转型等知识,为Java开发者提供了全面的参考。

目录

一.继承

1.继承的由来

2.继承的概念

3.继承的使用

4.继承方式

5.继承总结

二.父类成员访问

2.子类和父类成员变量和成员方法不同名时

3.成员方法小总结

4.super

1.super使用注意事项

2.子类构造方法

1.父类没有显示定义构造方法或显示定义无参构造方法,子类是可写可不写构造方法的

2.父类定义了构造方法而且带参数

3.再谈初始化

4.super和this,final

三.组合

1.组合的优缺点

2.继承的优缺点

四.多态

1.多态概念

2.多态的使用

3.多态体现案例

4.重写

1.对重写的理解

2.重写需要注意的事项

3.重写的实例

4.重写与重载的对比

5.向上转型和向下转型

1.向上转型

1.直接赋值法

2.方法传参法,方法返回值

​编辑

2.向下转型

3.转型的优缺点


一.继承

1.继承的由来

        继承,继承,在日常生活中我们还是很常见的:像家族产业的继承,文化传统的继承,某种信息的继承。我相信大多数学者都会有这样的感觉,这种继承似乎都是在传递某种东西,某种思想。在我们学习的Java中也会有继承这样的一个概念,它和我们传统的继承方式还是有那么一点不一样,到底有哪些区别呢?请参考以下代码:

class Student{
    public String name;
    public int age;
    public String gender;
    public String classRoom;
    public double score;

    public Student(String name, int age, String gender, String classRoom, double score) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.classRoom = classRoom;
        this.score = score;
    }
}

class Teacher{
    public String name;
    public int age;
    public String gender;
    public String SchoolWide;

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

        在看完上述代码后,相信各位学者也都发现了,这两个不同的类中都有两三个相同的属性。如果我们在做一个项目的时候,需要写上百个类,而这些类中都有一些相同的属性,方法,我们可不可以把这些相同的属性,方法写在一个类里面,然后供其他需要这些属性的类去调用这个类就行?答案是可以的,这也是我们Java中继承的由来。

2.继承的概念

        在面向对象中我们能对不同类中的一些相同的方法或者属性进行共性抽取,组合成一个新的类,这个新类可以供其他需要这个新类中属性的类调用,实现共性的抽取,代码的复用。

3.继承的使用

        1.一个Studnet类和一个Tearcher类中拥有相同的名字,年龄,性别,这个时候我们就可以通过继承的思想对两个类中相同的属性进行共性抽取组合成一个新的类,两个类对这个新类继承就可以达到对代码的复用效果。图解:

        上面的图解中,出现的People类就是对我们Student类和Teacher类进行共性抽取产生的新类,在Java中我们把这个新类称为父类(别称基类/超类),把Student类和Teacher类称为子类或派生类,通过子类来继承父类,就能实现代码的复用,同时也出现一个问题,我们通过什么方式去继承这个父类?下面我们通过代码演示来解开这一问题。

class People{
    public String name;
    public int age;
    public String gender;
    
    public void eat(){
        System.out.println("吃饭");
    }
    public void sleep(){
        System.out.println("睡觉");
    }
}
class Student extends People{
    public String classRoom;
    public double score;

   public void bark(){
       System.out.println("上学");
   }
}

class Teacher extends People{
    
    public String SchoolWide;

    public void mew(){
        System.out.println("上班");
    }
}

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

        Student student = new Student();
        student.name = "小明";
        student.age = 18;
        System.out.println("name:"+student.name+"age:"+student.age);
        student.eat();
        
        student.bark();//编译通过,正确运行
    }
}

        从上面的演示代码中我们可以清晰的看到,子类与父类构成联系是通过extends关键字实现的,我们此时能得出一个结论在Java中我们要实现继承必须通过extends关键字实现。不知道各位学者在学习的过程中会不会有这样一个疑问,有没有其他的关键字也能实现继承这样的操作,答案是没有的,想要继承就必须通过extends关键字来实现。但是有implements关键字实现接口这一操作(抽象和接口会学到)。

        我们继续观察上面的代码,在main方法中我们实例化Student对象,我们通过student对象名对名字,年龄,甚至是方法属性进行调用,此时编译不会报错,结果也会正确运行,为什么Student类中没有定义任何名字,年龄,方法等成员变量,我们还能够调用这些属性,方法?子类在继承父类的时候这些父类的成员变量和成员方法也会一并继承到我们的子类中,所以在我们子类中也是可以进行调用的。

        我们如何在调用方法,属性或者在类的结构上区分子类和父类?我们依然观察上面的代码,各位学者应该发现了,我的父类中有成员变量,成员方法,我的子类中也有成员变量,成员方法。大家是不是会想把我子类中的成员方法,变量写到父类中不就行了?首先我们要明白继承是为了把很多类中相同的属性或者方法集合在一起,实现代码的复用,而不是为某一个类提供服务!!为什么要在子类中写成员变量,成员方法了,是为了体现出子类与基类的不同(子类中必须新添加自己的特有成员),这样继承才会有意义。

4.继承方式

        在Java中也是有继承方式的,分为三种继承方式:单继承,多层继承,不同类继承同一个类,下面给出图解:

        有同学可能会问到Java不能支持多继承吗?首先Java是不支持多继承的,但是Java中为了弥补缺陷也是设计了这样一个关键字来解决不能实现多继承的问题!!大家可以先思考思考,后面我会讲到。

5.继承总结

        1.子类与父类实现继承需要通过extends关键字实现

        2.子类会将父类中成员变量,成员方法继承到子类中

        3.子类继承父类后,必须在子类中添加自己特有的成员,体现与基类的不同,使继承有意义

        4.继承的实现主要是为了共性的抽取,实现代码复用

二.父类成员访问

        子类继承父类后不仅可以调用父类的成员方法和成员变量还必须添加自己特有的子类成员方法或者变量目的就是为体现子类与基类的不同。当我们子类在访问父类的成员变量或者成员方法时出现了与子类同名的成员方法名和成员变量名,我们Java中又该如何区分呢?

1.子类和父类成员变量或成员方法名同名时

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

    public void methondA(){
        System.out.println("父类吃饭");
    }
    public void methondA(int a){
        System.out.println(a);
    }
    public void methondB(){
        System.out.println("睡觉");
    }
}
class Student1 extends Person{
    public String name;

    public void methondA(){
        System.out.println("子类我要去吃饭");
    }
    public void methondD(){
        //子类与父类变量同名时访问哪一个?
        name = "zhangsan";
        System.out.println(name);
        //为什么报错了
        //age = 18;
        //classR  = "sdfsd";
        //访问父类成员中的方法
        methondB();
        //猜猜这个地方访问的是父类还是子类中
        methondA();
        //methondC();//报错
        methondA(3);

    }
}

        根据以上代码我们不难发现,当有同名变量的时候,子类优先访问的是自己的变量,我们又会发现我们在访问age和classR时程序会报错,这一点也说明了,我们子类继承了父类中的成员变量或者方法时也是会受到修饰符的限制的。再者当我们子类成员方法和父类成员方同名的时候,子类在进行访问的时候子类会优先访问自己的成员方法,当子类成员方法没有此方法或变量的时候再去访问父类中的成员方法,如果父类中也没有该成员编译就会报错,运行不通过。像上面methondC()这个方法两个类都没有就会导致程序报错。我们继续观察代码会发现还有一个重载的methondA(int a)方法,子类访问父类或子类重载的成员时也是可以通过传递不同的实参来调用不同的方法的。

2.子类和父类成员变量和成员方法不同名时

class Person{
    public String name;
    public void methondA(){
        System.out.println("父类吃饭");
    }
    public void methondA(int a){
        System.out.println(a);
    }
    public void methondB(){
        System.out.println("睡觉");
    }
}
class Student2 extends Person{
    public int a;
    public int b;

    public void methondD(){
        System.out.println("咱们不同名");
    }
    public void methondE(){
        a = 100;
        b = 20;
        name = "lisi";
        System.out.println(a+b+name);
        methondA();
        methondB();
        methondD();
        //methondC();报错
    }
}

        当子类中的成员变量,成员方法和父类中的成员变量,方法不同名的时候,我们只需要考虑这个方法或变量在两个类中存不存在,有没有修饰符,限定符,干扰子类使用即可。

3.成员方法小总结

        1.当访问的成员变量,成员方法出现同名时,子类中有优先访问子类

        2.当访问的成员变量,成员方法不同名时子类中没有,就访问父类中继承下来的成员变量,方法,如果父类中也没有,程序自动报错

        3.当出现子类对象访问父类与子类同名方法时,如果父类和子类同名方法参数列表不同(重载),根据传递的参数来选择合适的方法,如果没有就报错

        4.子类继承了父类所有的成员变量和方法,同时也受到父类中成员限定符和修饰符的控制(能继承被static和private的变量但不能访问)

当学完父类与子类之间的访问,我们是否会有这样一个疑问,那我需要访问子类与父类同名的成员变量或方法我们该怎么办?

4.super

        衔接上一个问题,子类中访问父类中同名成员该怎么办?直接访问是无法做到的,所以在我们Java中提供了super关键字,由super关键字来访问父类中与子类同名成语变量。

class Person{
    public String name;
    public void methondA(){
        System.out.println("父类中的methondA()");
    }
    public void methondA(int a){
        System.out.println("父类传参:"+a);
    }
}
class Student3 extends Person{
    
    String name;
    int a;
    
    public void methondA(){
        System.out.println("子类的methondA()");
    }
    
    public void methondC(){
        a = 10;
        //这个时候我们访问依旧是子类的
        name = "xiaozhang";
        System.out.println("子类:"+name);
        super.name = "小明";
        System.out.println("父类:"+super.name);

        methondA();//访问的还是子类
        methondA(3);//通过传参访问到父类中的menthondA(int a)
        //想要访问我们父类中的成员方法还得借助super关键字
        super.methondA();//这个时候就是访问我们父类其中的成员变量

    }
}

        通过观察上面的代码和结果,我们会发现super关键字的确是能实现访问父类中同名的成员属性。

1.super使用注意事项

        1.只能在非静态方法中使用

        2.适合在子类方法中访问父类的成员属性

        5.子类构造方法和初始化

2.子类构造方法

        构造方法主要就是为了给对象成员初始化,为对象成员开辟内存空间。子类由于继承了父类成员属性,在构造方法上会不会有哪些变化?

class Base{
    public int year;
    public int month;
    public int day;

    public Base(){
        System.out.println("父类:空的构造方法");
    }
    public Base(int year){
        this.year = year;
        System.out.println("父类:定义了一个变量的构造方法");
    }
    public Base(int year,int month,int day){
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("父类构造方法");
    }
}
class A extends Base{

    public A(){
        System.out.println("子类:没有定义任何变量的构造方法");
    }
    public A(int year){
        super(year);
        System.out.println("子类:定义了一个变量的构造方法");
    }

    public A(int year,int month,int day){
        super(year,month,day);
        System.out.println("子类构造方法");
    }
}
public class Test3 {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A(222);
        A a3 = new A(2222,2,2);

    }
}

        从上面的结果来看,父类构造方法总是先子类构造方法执行,在代码中我们父类的构造方法是重载的,在我们子类中也是重载的与父类中的构造方法相对应,而且子类中的构造方法都是通过调用super关键字去实现的这一构造方法,为什么?子类和父类本来是两家人(两个类),但是子类是继承了父类就相当于成为了一家子人(抽象),一家子人在实例化对象的时候子类就需要给这一家子的每个人都初始化完整,所以子类在初始化的时候必须要对父类中有参构造方法的成员属性也初始化并且在子类中定义父类中有参的构造方法时super关键字必须放在第一行。

我们能不能用this去实现了?答案是不能的,而且this和super在构造方法中不能同时出现(super和this的比较中,给出了详细解释)。在子类中能不能不实现构造方法?这个问题得分情况讨论,比如:

1.父类没有显示定义构造方法或显示定义无参构造方法,子类是可写可不写构造方法的

        但是java中还是会隐式调用默认的构造方法(不是说就不调用了)

class Base{
    public int year;
    public int month;
    public int day;

    public Base(){
        System.out.println("父类:空的构造方法");
    }
 }
class B extends Base{
    public void methondA(){
        System.out.println("父类为空的构造方法,子类没有实现构造方法,编译通过");
    }
}
2.父类定义了构造方法而且带参数

        父类定义了构造方法而且带参数,这个时候子类就必须定义构造方法,并且必须在子类构造方法的第一行显示地用super调用超类的构造方法。(尤其是父类构造方法由重载的情况下,我们必须为super()提供合适的参数)

class Base{
    public int year;
    public int month;
    public int day;
    
    public Base(int year){
        this.year = year;
        System.out.println("父类:定义了一个变量的构造方法");
    }
    public Base(int year,int month,int day){
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("父类构造方法");
    }
}
class B extends Base{
    
    public B(int year){
        super(year);
        System.out.println("子类:带有一个的构造方法");
    }
    public B(int year,int month,int day){
        super(year,month,day);
        System.out.println("子类:带有三个成员变量的构造方法");
    }
}

        如果没有定义在子类中没有定义父类有参数构造方法,会出现什么事?报错,一定是报错。

3.再谈初始化

        在类和对象章节我们学的代码块还记得叭!!在代码块中我们静态代码块是最先执行的,后执行的是实例代码块,最后就是我们的普通代码块。这是没有继承的情况下我们代码块是按照这个情况执行的,那在我们又继承的情况下我们的执行顺序又会是怎样的呢?咱们拭目以待!!

class Person {
     public String name;
     public int age;
     public Person(String name, int age) {
            this.name = name;
            this.age = age;
        System.out.println("Person:构造方法执行");
     }
     
    {
        System.out.println("Person:实例代码块执行");
    }
    static {
        System.out.println("Person:静态代码块执行");
    }
}
class Student1 extends Person{
        public Student1(String name,int age) {
            super(name,age);
        System.out.println("Student:构造方法执行");
    }
    {
        System.out.println("Student:实例代码块执行");
    }

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

public class Test1 {
    public static void main(String[] args) {
        Student1 student1 = new Student1("张三",19);
        System.out.println("===========================");
        Student1 student2 = new Student1("gaobo",20);
    }
}

从代码演示的结果上我们能得出以下结论:

        1.父类的静态代码块和子类的静态代码块是最早执行的

        2.父类实例代码块和普通代码块依次执行,最后就是子类的实例代码块和普通代码块

        3.第二次实例化子类对象时,父类和子类的静态代码块都不执行了

4.super和this,final

        我们认识到了super和this都可以在成员方法中用来访问成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,他们之间又有什么区别呢?

相同点:

        1.都是关键字

        2.都只能在类的非静态方法中使用 ,用来访问非静态成员方法和字段

        3.在构造方法中调用时,必须是构造方法中的第一条语句,两者不能同时存在

不同点:

        1.this是当前对象的引用,什么是当前对象,当前对象就是正在调用实例方法的对象。super是用来引用子类从父类中继承下来的成员属性

        2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承而来的方法和属性

        3.在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现

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

super和this关键字知道了,但是final关键字是干什么的呢?其实final关键字跟static类似,可以用来修饰变量,成员方法以及类!

        1.final修饰变量或者字段的时候,表示常量(既不能修改)

        2.如果修饰类,表示此类不能被继承

        3.最后就是修饰方法,表示该方法不能被重写

三.组合

        什么是组合?我们都知道一台电脑有cpu,cpu散热器,主板,内存,硬盘等配件,把这些配件拼接在一起能组成一台主机,使电脑运行起来。这些配件拼接在一起的过程在Java中就叫组合,组合也是一个类啊(特殊的类重点)那组合是不是也对很多功能进行包装了?,跟继承又有什么关系了?

        继承是对一个类中共性的抽取,实现代码复用。继承支持扩展,子类通过继承父类来实现类中的很多功能,使这个类具有相对完整的功能体系!组合的确有包装的这个作用,但不能说是对功能包装,在Java中应该表示为是对某个类,多个类进行包装,就是把具有很多功能的某个类集合在一起成为自己的变量,在Java中这种变量称为类类型变量,这种变量的实现,我们能通过实例化组合的这个类,去调用这个变量中特有的功能,方法。组合就像上面的电脑壳一样,把很多配件组合在一起成为一个主机,供电脑运行。下面演示组合在Java中的实战

class Cpu{
    //电脑cpu
     public void cpu(){
        System.out.println("电脑cpu");
    }
}
class Radiator{
    public void radiator(){
        System.out.println("电脑散热器");
    }
}
class Motherboard{
    public void motherboard(){
        System.out.println("电脑主板");
    }
}
class Memory{
    public void memory(){
        System.out.println("电脑内存");
    }
}
class Disk{
    public void disk(){
        System.out.println("电脑硬盘");
    }
}

//电脑主机
class HostComputer{
    private Cpu cpu;
    private Radiator radiator;
    private Motherboard motherboard;
    private Memory memory;
    private Disk disk;


    public HostComputer(Cpu cpu, Radiator radiator, Motherboard motherboard, Memory memory, Disk disk) {
        this.cpu = cpu;
        this.radiator = radiator;
        this.motherboard = motherboard;
        this.memory = memory;
        this.disk = disk;
    }

    public Cpu getCpu() {
        return cpu;
    }

    public Radiator getRadiator() {
        return radiator;
    }

    public Motherboard getMotherboard() {
        return motherboard;
    }

    public Memory getMemory() {
        return memory;
    }

    public Disk getDisk() {
        return disk;
    }

    public void hostComputer(){
        System.out.println("是一台主机");
    }
}
class Legion extends HostComputer{
    //将主机给继承下来
    //这是一个联系牌主机

    public Legion(Cpu cpu, Radiator radiator, Motherboard motherboard, Memory memory, Disk disk) {
        super(cpu, radiator, motherboard, memory, disk);
    }

    public void Legion(){
        System.out.println("联想主机");
    }
}

public class Computer{
    //在学习的时候,你们也可以打开试试这个
    public static void main1(String[] args) {
        Legion legion = new Legion(new Cpu(),new Radiator(),new Motherboard(),new Memory()
                ,new Disk());
        legion.getCpu().cpu();
    }
    public static void main(String[] args) {
        HostComputer hostComputer =
                new HostComputer(new Cpu(),new Radiator(),new Motherboard(),new Memory()
                        ,new Disk());
        hostComputer.hostComputer();
        hostComputer.getCpu().cpu();
        hostComputer.getRadiator().radiator();
        hostComputer.getMemory().memory();
        hostComputer.getMotherboard().motherboard();
        hostComputer.getDisk().disk();
    }
}

1.组合的优缺点

        1.组合支持动态扩展,能够在运行的时候,依据具体对象选择不同类型的对象组合,组合的扩展性比继承要好

        2.组合代码的复用对对象内部的实现细节对外不可见,封装性比较好,属于黑盒式复用

        3.使用组合能降低类与类之间的耦合,程序的安全性提高

        4.最要命的缺点就是在创建整体类对象的时候,需要创建所有的局部类对象

2.继承的优缺点

        1.继承也支持扩展,可以通过父类来实现,也容易修改被复用的代码。

        2.继承不支持动态扩展不能像组合那样在运行时依据对象选择不同类型的对象组合,它在编译期就已经决定了父类

        3.继承主要依赖于父类,代码耦合度比较高,并且父类中的代码如果被修改,可能会导致子类也不得不修改,增加了维护难度

        4.在继承中代码的封装性也不怎么好,父类的实现细节容易暴露给子类,属于白盒式复用

四.多态

1.多态概念

        昨天晚上我买了两个煎饼,在买煎饼的时候,我发现了一个特别有趣的一个现象。我这两个煎饼都是在同一家买的,有趣的是两煎饼不是同一个人做的,第一位给我做煎饼的是个大叔,大叔在给我做煎饼的时候,它会把煎饼中间隔开一个口子,然后把拌好的鸡蛋清和黄顺着口子流入煎饼里面,让这个鸡蛋跟煎饼相融合,大叔说这样口感会好一点。第二个煎饼是一个阿姨给我做的,它在做煎饼的时候没有剪开口子,而是直接把搅拌好的鸡蛋,顺着煎饼倒在了煎饼表面,让鸡蛋跟煎饼相融合。还蛮好玩的哈!煎饼吃完了,多态也出来了!!!不知道大家发现没有,同样是做煎饼,大叔是这样做,阿姨是那样做。这个时候多态他就体现出来了,不同的对象需要完成同一个行为的时候,会有不同的表现形态,这就是多态。

2.多态的使用

        那多态在Java中又是如何运用的呢?咱们样式代码见文章

class Animal{
    String name;
    int age;
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void sleep(){
        System.out.println(name + "睡觉");
    }
}
class Cat extends Animal{

    public Cat(String name,int age){
        super(name,age);
    }
    public void sleep(){
        System.out.println(name + "睁着眼睡觉");
    }
}
public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("小鱼儿",19);
        cat.sleep();
    }
}

        此时咱们同学可能会有一些疑问,这不就是子类继承父类吗!而且在父类中你还重写了父类中的方法,这样有什么意义呢?我们同学可能会感觉白瞎忙活了一场咱们继续看下面代码

class Animal{
    String name;
    int age;
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void sleep(){
        System.out.println(name + "睡觉");
    }
}

class Fish extends Animal{

    public Fish(String name,int age){
        super(name,age);
    }
    public void sleep(){
        System.out.println(name + "睁着眼睡觉");
    }
}
public class TestAnimal {
    public static void sleep(Animal a){
        a.sleep();
    }
    public static void main(String[] args) {
        Fish fish = new Fish("小鱼儿",19);
        sleep(fish);        
    }
}

        小朋友!你是否会有很多问号???在子类中重写了父类中的方法就算了,还用子类实例化对象作为参数传递给父类对象,并且用父类对象调用的sleep()方法呈现出的结果还是子类里面的输出内容?不明白对不对?没事让我为你一一讲述其中的道道。

        为什么要重写方法了,子类有些时候对父类中的方法实现并不满意,但又不想修改父类中的方法,子类又想在父类方法的基础上扩展一些功能,那么这个时候方法重写孕育而生。至于为什么要用父类去引用调用重写的方法,咱们以代码演示为例,当调用者在编写sleep()这个方法的时候,参数类型为Animal(父类),此时方法内部并不知道a引用会指向哪个子类类型的实例。那么这个时候a所调用的方法就会出现很多不同的形态表现,这就是多态的一种体现。当然这样做也提升了代码的封装性和扩展性。最后为什么父类对象调用的方法输出的是子类方法的内容?这个地方其实在调用的过程中发生了一个动态绑定的操作!什么意思呢,在程序运行之前,我不知道我要调用谁,只有当程序运行起来的时候,通过参数的传递,父类对象此时才能够知道我们具体要调用哪个方法,并且在调用过程中父类调用的这个方法会绑定到子类重写的方法中,也就会出现上面的结果,这也叫动态绑定。多态的实现其实是有条件的,首先多态必须是在继承条件下实现,其次子类继承的父类中,子类必须重写父类中的方法,最后我们在调用的方法的时候必须通过父类的引用调用重写的方法!

咱们继续看代码

class Animal{
    String name;
    int age;
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
}
class Dog extends Animal{

    public Dog(String name,int age){
        super(name,age);
    }

    @Override
    public void sleep() {
        System.out.println(name + "闭着眼睡觉");
    }
}
public class TestAnimal {
    public static void sleep(Animal a){
        a.sleep();
    }
    public static void main(String[] args) {
        Dog dog = new Dog("小黑儿",12);
        dog.sleep();
    }
}

        这个代码在运行的时候,编译都不能通过,在程序中给我们提示“方法不会覆盖或实现超类的方法”什么意思?这其实也是我们使用多态的一个缺陷,我们一旦使用了多态,成员变量就必须用父类的成员方法也只能用父类里面有的(只能调用父类里面定义过,子类里重写过的成员方法),在调用过程中会优先用子类里面重写父类的成员方法,如果子类没有重写父类的方法就向上找父类自己的成员方法。但是不能用子类特有的成员方法。在使用的时候一定要注意!!!!

3.多态体现案例

class Animal{
    String name;
    int age;
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void sleep(){
        System.out.println(name + "睡觉");
    }
}

class Fish extends Animal{

    public Fish(String name,int age){
        super(name,age);
    }
    public void sleep(){
        System.out.println(name + "睁着眼睡觉");
    }
}
class Dog extends Animal{

    public Dog(String name,int age){
        super(name,age);
    }

    @Override
    public void sleep() {
        System.out.println(name + "闭着眼睡觉");
    }
}
public class TestAnimal {
    public static void sleep(Animal a){
        a.sleep();
    }
    public static void main(String[] args) {
        Fish fish = new Fish("小鱼儿",19);
        sleep(fish);
        Dog dog = new Dog("小黑儿",12);
        dog.sleep();
    }
}

4.重写

1.对重写的理解

        在多态的实现下,重写是必不可免的,作者个人对重写的理解为子类可以根据需要对继承父类中的方法进行重新改写(外壳不变,核心重写),这种重新改写不仅可以保留父类的属性也能加入子类特有的属性,更像是一种对方法的扩充。

官方解释:在Java和其他一些高级面向对象的编程语言中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。 如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。

2.重写需要注意的事项

重写注意事项(参考)

        1.发生方法重写的两个方法返回值、方法名、参数列表必须完全一致(子类重写父类的方法)

        2.子类抛出的异常不能超过父类相应方法的异常,子类访问的级别也不能低于父类访问的级别,当然子类重写的方法的返回值类型不能大于父类方法的返回值类型(返回值类型要么相同,要么构成父子类型)

        3.在多态的实现中,一定要在子类中重写父类中的方法,并且名称和参数都要保持一致。

3.重写的实例

class Move{
    String name;
    public Move(String name){
        this.name = name;
    }
    public void move(){
        System.out.println(name + "正在游泳");
    }
}
class Cat extends Move{

    public Cat(String name){
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + "正在抓老鼠");
    }
}
public class TestMove {
    public static void main(String[] args) {
        Move m = new Cat("小猫喵");
        m.move();
    }
}

4.重写与重载的对比

        方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现

5.向上转型和向下转型

1.向上转型

        向上转型实际就是创建一个子类对象,将其当成父类对象使用。

        向上转型发生的前提是在有继承之后,并且向上转型一般有三种用法。

        假设有一个Animal类,类中有两个变量(name,age),和一个sleep()方法;

class Animal{
    String name;
    int age;
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void sleep(){
        System.out.println(name + "睡觉");
    }
}
1.直接赋值法
class Pig extends Animal{
    public Pig(String name,int age){
        super(name,age);
    }
    @Override
    public void sleep() {
        System.out.println(name + "正在闭着眼睡觉");
    }
    public void test(){
        System.out.println(name+"test");    
    }
}
public class Test4 {
    public static void main(String[] args) {
        //直接赋值法
        Animal pig = new Pig("小猪",1);
        pig.sleep();
        //pig.test();
    }
}

再调用test();方法之后会出现报错

        当向上转型之后,父类引用变量可以访问子类中属于父类的属性和方法,但是不能访问子类独有的属性和方法。例子中由于子类重写了父类的sleep()方法,所以调用的sleep()方法是子类的sleep()方法,输出结果为:“小猪正在闭着眼睡觉”,而调用子类的test()方法则会报错。

2.方法传参法,方法返回值
class Cattle extends Animal{
    public Cattle(String name,int age){
        super(name,age);
    }
    @Override
    public void sleep() {
        System.out.println(name + "在水里躺着");
    }
}
public class Test4 {
    //2.方法传参的使用:形参为父类性引用,可以接受任意类型变量
    public static void TestSleep(Animal animal){
        animal.sleep();
    }

    //3.作返回值:返回任意子类对象
    public static Animal buyAnimal(String var){
        if("牛" == var){
            return new Cattle("小水牛",6);
        }else if("猪" == var){
            return new Pig("小猪",1);
        }else{
            return null;
        }
    }
    public static void main(String[] args) {
        Cattle cattle = new Cattle("小水牛",6);
        //方法传参法
        TestSleep(cattle);
        //方法返回值
        Animal animal = buyAnimal("牛");
        animal.sleep();
    }
}
2.向下转型

        向下转型的前提是,这个对象原本就是子类对象通过向上转型得到的时候才能够成功转型!可能会有一个疑问?既然向上转型了,为什么还要向下转型,那不就转回去了嘛,那有啥作用?在我们向上转型中有一个缺点,父类引用变量只能够访问子类中属于父类中的属性和方法,对于子类特有的属性和方法是访问不了的。想要访问子类中特有的方法怎么办?这个时候就能用到我们的向下转型,他原本是什么对象,就让他变回原来的对象。当然在我们的向下转型中是需要用到强制类型转换的。

        向下转型也用三种使用方法,这个地方我们拿直接赋值法举例

public static void main(String[] args) {
    Pig pig = new Pig("小猪",1);

    System.out.println("向上转型:");
    //向上转型
    Animal animal = pig;
    animal.sleep();
    //报错
    //animal.test();
    //向下转型
    System.out.println("向下转型:");
    if(animal instanceof Pig){
        pig = (Pig) animal;
        pig.test();
    }
}

instanceof关键字是Java的二元运算符,用于测试对象是否是某个类或接口的实例,常用于对象类型强制转换。

3.转型的优缺点

        1.首先是说到了向上转型,介绍了它会出现的三种场景,知道了在向上转型之后无法调用子类特有的方法

        2.但是在向下转型后,我们解决了这个问题,通过对父类对象进行一个强转,就可以调用到子类当中的方法,不过可以看到这种做法不太安全,若是一开始父类对象接受了一个子类的引用,但是在强转的时候转化为了另外一个子类,就会造成类型转换的问题,于是后面对方法进行了修正,在前面加上了instanceof关键字进行一个判断,只有父类接收到了这个子类的引用,才可以强转为这个子类的对象(一般不建议使用向下转型)

对红字部分代码演示:像这种情况就会报错

public static void main(String[] args) {
    Pig pig = new Pig("zhu",1);
    Cattle cattle = new Cattle("niu",2);

    Animal animal = pig;
    animal = cattle;
    pig = (Pig) animal;

兄弟们,这一章节又又又完了,错误的地方,不完善的地方,欢迎大家指出。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值