继承与多态

1.继承

在现实生活中继承有继承财产,而在java代码中的继承就是继承代码。

现在看一个例子

package 继承;

public class Dog {
    public String name;
    public int age;

    public void eat(){
        System.out.println(this.name+"正在吃饭");
    }

}

package 继承;

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

    public void eat(){
        System.out.println(this.name+"正在吃饭");

    }
}

会发现出现了代码重复,虽然在不同类中

而我们如果把相同的代码放进一个单独的类中,在使用继承的方法,就完美的解决了这个问题。

1.1继承的概念

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

例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。

上述图示中,

Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的 子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

1.2继承的语法

在Java中如果要表示类之间的继承关系,需要借助extends关键字

父类

package 继承;

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

}

子类

package 继承;

public class Dog extends Animal {
    public void eat(){
        System.out.println(this.name+"正在吃饭");
    }

}
package 继承;

public class Cat extends Animal{
    public void eat(){
        System.out.println(this.name+"正在吃饭");

    }
}

测试类

package 继承;

public class Test {
    public static void main(String[] args) {
        Dog dog =new Dog();
        dog.name="旺财";
        dog.age=11;
        dog.eat();
        Cat cat =new Cat();
        cat.name="咪咪";
        cat.age=12;
        cat.eat();



    }
}

结果

注意:

1. 子类会将父类中的成员变量或者成员方法继承到子类中了

2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

1.3父类成员访问

在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?

1.3.1子类中访问父类的成员变量

1. 子类和父类不存在同名成员变量

public class Base {
int a;
int b;
}
public class Derived extends Base{
int c;
public void method(){
a = 10; // 访问从父类中继承下来的a
b = 20; // 访问从父类中继承下来的b
c = 30; // 访问子类自己的c
}
}

2. 子类和父类成员变量同名

public class Base {
int a;
int b;
int c;
}
/
public class Derived extends Base{
int a; // 与父类中成员a同名,且类型相同
char b; // 与父类中成员b同名,但类型不同
public void method(){
a = 100; // 访问父类继承的a,还是子类自己新增的a?
b = 101; // 访问父类继承的b,还是子类自己新增的b?
c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
}
}

在子类方法中 或者 通过子类对象访问成员时

1.如果访问的成员变量子类中有,优先访问自己的成员变量。

2.如果访问的成员变量子类中无,则访问父类继承下来的,

3.如果父类也没有定义,则编译报错。 如果访问的成员变量与父类中成员变量同名,则优先访问自己的

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。

1.3.2子类中访问父类的成员方法

1.成员方法名字不同

public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
}
public class Derived extends Base{
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodB(); // 访问子类自己的methodB()
methodA(); // 访问父类继承的methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
}
}

2.成员方法名相同的时候

public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
public void methodA(int a) {
System.out.println("Derived中的method(int)方法");
}
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
}
}

1,通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。

2,通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错

1.4super关键字

super关键字,该关键字主要作用:在子类方法中访问父 类的成员

public class Base {
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
int a; // 与父类中成员变量同名且类型相同
char b; // 与父类中成员变量同名但类型不同
// 与父类中methodA()构成重载
public void methodA(int a) {
System.out.println("Derived中的method()方法");
}
// 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
// 对于同名的成员变量,直接访问时,访问的都是子类的
a = 100; // 等价于: this.a = 100;
b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从基类继承下来的部分
super.a = 200;
super.b = 201;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
super.methodB(); // 访问基类的methodB()

【注意事项】

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

2. 在子类方法中,访问父类的成员变量和方法。

1.5子类构造方法

子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

public class Base {
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base{
public Derived(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}


结果打印:
Base()
Derived()

子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整

注意:

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

2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。

3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。

4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现

1.6 super与this

相同点

1. 都是Java中的关键字

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

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

不同点

1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成 员的引用

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

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

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

1.7实例代码块与静态代码块 和构造方法的(在继承环境下的运行顺序)

1.7.1回顾之前没有继承环境下的运行顺序

package 继承;

class Person {
   public String name;
   public int age;
   public Person (String name,int age){
       this.name=name;
       this.age=age;
       System.out.println("这个是构造方法的执行");
   }
    {
        System.out.println("这是实例代码块的执行");
    }
    static
    {
        System.out.println("这个是静态代码块的执行");
    }

}

public class Test {
    public static void main(String[] args) {
//        Dog dog =new Dog();
//        dog.name="旺财";
//        dog.age=11;
//        dog.eat();
//        Cat cat =new Cat();
//        cat.name="咪咪";
//        cat.age=12;
//        cat.eat();
        Person person=new Person("wo",55);
        System.out.println("----------------------");
        Person person1=new Person("fskd",5);
    }
}

执行结果

注意;

1. 静态代码块先执行,并且只执行一次,在类加载阶段执行

2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

1.7.2继承环境下的运行顺序

父类

package 继承;

public class Animal {
    public String name ;
    public int age;
    public Animal (String name,int age){
        this.name=name;
        this.age=age;
        System.out.println("父类  这个是构造方法的执行");
    }
    {
        System.out.println("父类   这是实例代码块的执行");
    }
    static
    {
        System.out.println("父类    这个是静态代码块的执行");
    }

}

子类

package 继承;

class Person extends Animal {
   public String name;
   public int age;
   public Person (String name,int age){
      super(name,age);
       System.out.println("子类  这个是构造方法的执行");
   }
    {
        System.out.println("子类   这是实例代码块的执行");
    }
    static
    {
        System.out.println("子类   这个是静态代码块的执行");
    }

}

public class Test {
    public static void main(String[] args) {
//        Dog dog =new Dog();
//        dog.name="旺财";
//        dog.age=11;
//        dog.eat();
//        Cat cat =new Cat();
//        cat.name="咪咪";
//        cat.age=12;
//        cat.eat();
        Person person=new Person("wo",55);
        System.out.println("----------------------");
        Person person1=new Person("fskd",5);
    }
}

执行结果

通过分析执行结果,得出以下结论:

1、父类静态代码块优先于子类静态代码块执行,且是最早执行

2、父类实例代码块和父类构造方法紧接着执行

3、子类的实例代码块和子类构造方法紧接着再执行

4、第二次实例化子类对象时,子类和父类的

1.8protected关键字

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {
private int a;
protected int b;
public int c;
int d;
}
// extend01包中
// 同一个包中的子类
public class D extends B{
public void method(){
// super.a = 10; // 编译报错,父类private成员在相同包子类中不可见
super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
super.c = 30; // 父类中public成员在相同包子类中可以直接访问
super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
// extend02包中
// 不同包中的子类
public class C extends B {
public void method(){
// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问



super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
// extend02包中
// 不同包中的类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
// System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
}

1.9final关键字

final关键可以用来修饰变量、成员方法以及类。

1.9.1修饰字段或变量,表示常量(即不可以被修改)

1.9.2修饰类(即类就不可以被继承)

我们平时用的字符串就是用Sreing修饰的,所以不能被改变

1.9.3修饰方法(既不能进行方法的重写)

1.10继承方式

1.单继承

2.多层继承

3.不同类继承一个类

4.多继承·(不支持)

2.多态

2.1多态实现的条件

1. 必须在继承体系下

2. 子类必须要对父类中方法进行重写

3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

父类

package 继承;

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

    public Animal (String name,int age){
        this.name=name;
        this.age=age;
       // System.out.println("父类  这个是构造方法的执行");
    }
//    {
//        System.out.println("父类   这是实例代码块的执行");
//    }
//    static
//    {
//        System.out.println("父类    这个是静态代码块的执行");
//    }

    public void eat(){
        System.out.println(this.name+"正在吃饭");
    }

}

Dog子类

package 继承;

//package 继承;

public class Dog extends Animal {
    public Dog (String name,int age){
        super(name,age);

    }
    public void eat(){
        System.out.println(this.name+"汪汪叫");
    }

}
//    int a;
//    public void eat() {
//        System.out.println(this.name + "正在吃饭");
//        this.a=77;
//        super.a=88;
//    }
//    //被static修饰的方法,是属于静态方法
//    //this 和super一样都是依赖于对象,但是static修饰后属于类
//        public static void add () {
//        //this.a;
//        //super.a;
//
//        }
//
//
//}

Cat子类

package 继承;

public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    public void eat() {
        System.out.println(this.name + "喵喵叫");
    }
}

//    public void eat(){
//        System.out.println(this.name+"正在吃饭");
//
//    }
//}

teat测试类

package 继承;

class Person extends Animal {
    public String name;
    public int age;

    public Person(String name, int age) {
        super(name, age);
        // System.out.println("子类  这个是构造方法的执行");
    }
//    {
//        System.out.println("子类   这是实例代码块的执行");
//    }
//    static
//    {
//        System.out.println("子类   这个是静态代码块的执行");
//    }
}

public class Test {
//    public static Animal fun(){
//        Cat cat=new Cat("s",5);
//        return cat;
//    }
//    public static void fun(Animal animal){
//
//    }

    public static void main(String[] args) {
        //先进行向上转型
        Animal animal=new Dog("旺财",5);
        animal.eat();
        Animal animal1=new Cat("喵喵",8);
        animal1.eat();
    }
}

执行结果

2.2方法的重写

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

方法重写的规则

1.子类在重写父类的方法时一般必须跟父类的方法一样即(方法名 参数列表 返回值类型与父类的方法一样)

2.被重写的方法返回值可以不一样,但一定要有父子关系

3.访问权限不能比被重写的父类的方法的访问权限还要低,例如父类中的访问权限为public时

子类中的访问权限就不能是protected

4.父类被static,private,声明和构造方法都不能被重写

5.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写.

重载于重写的区别

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

2.3向上转型与向下转型

2.3.1向上转型

三种方式

1.直接赋值

        2.方法传参

3.方法返回

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

2.3.2向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的 方法,此时:将父类引用再还原为子类对象即可,即向下转换

package 继承;

class Person extends Animal {
    public String name;
    public int age;

    public Person(String name, int age) {
        super(name, age);
        // System.out.println("子类  这个是构造方法的执行");
    }
//    {
//        System.out.println("子类   这是实例代码块的执行");
//    }
//    static
//    {
//        System.out.println("子类   这个是静态代码块的执行");
//    }
}

public class Test {
//    public static Animal fun(){
//        Cat cat=new Cat("s",5);
//        return cat;
//    }
//    public static void fun(Animal animal){
//
//    }

    public static void main(String[] args) {
        //先进行向上转型
        Animal animal=new Dog("旺财",5);
        animal.eat();
        Animal animal1=new Cat("喵喵",8);
        animal1.eat();
        //这是调用子类中特有的方法就会报错
       // animal.brea();

        //这时就可以使用向下转型
      Dog dog=(Dog)animal;
      dog.brea();

      //但是向下转型不能乱转  例如
       //无法正常还原,因为向上转型的时候时由子类Dog;
        // Cat cat=(Cat)animal;
        //向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入
        //了 instanceof ,如果该表达式为true,则可以安全转换。

    }

}

运行结果

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。

package 继承;

class Person extends Animal {
    public String name;
    public int age;

    public Person(String name, int age) {
        super(name, age);
        // System.out.println("子类  这个是构造方法的执行");
    }
//    {
//        System.out.println("子类   这是实例代码块的执行");
//    }
//    static
//    {
//        System.out.println("子类   这个是静态代码块的执行");
//    }
}

public class Test {
//    public static Animal fun(){
//        Cat cat=new Cat("s",5);
//        return cat;
//    }
//    public static void fun(Animal animal){
//
//    }

    public static void main(String[] args) {
        //先进行向上转型
        Animal animal=new Dog("旺财",5);
        animal.eat();
        Animal animal1=new Cat("喵喵",8);
        animal1.eat();
        //这是调用子类中特有的方法就会报错
       // animal.brea();

        //这时就可以使用向下转型
      //Dog dog=(Dog)animal;
      //dog.brea();

      //但是向下转型不能乱转  例如
       //无法正常还原,因为向上转型的时候时由子类Dog;
        // Cat cat=(Cat)animal;
        //向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入
        //了 instanceof ,如果该表达式为true,则可以安全转换。

        if(animal instanceof Dog){
            Dog dog=(Dog)animal;
            dog.brea();
        }
        if(animal instanceof Cat){
            Cat cat=(Cat)animal;
            cat.fun();
        }

    }

}

2.4避免在构造方法中调用方法的重写

一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func

class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果
D.func() 0

构造 D 对象的同时, 会调用 B 的构造方法.

B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func 此时 D 对象自身还没有构造,

此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.

所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

相关推荐

  1. 对象、抽象、封装、继承以及举例

    2024-06-08 19:08:01       11 阅读
  2. python继承

    2024-06-08 19:08:01       13 阅读
  3. 面向对象(方法)、私有化、继承

    2024-06-08 19:08:01       35 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-08 19:08:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-08 19:08:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-08 19:08:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-08 19:08:01       18 阅读

热门阅读

  1. 算法题 — 可可喜欢吃香蕉(二分查找法)

    2024-06-08 19:08:01       9 阅读
  2. PostgreSQL的内存结构

    2024-06-08 19:08:01       9 阅读
  3. git版本管理工具

    2024-06-08 19:08:01       15 阅读
  4. 动态语言的开源编译器汇总

    2024-06-08 19:08:01       9 阅读
  5. Oracle 收缩表高水位线

    2024-06-08 19:08:01       7 阅读
  6. Linux网络编程之select的理解

    2024-06-08 19:08:01       6 阅读
  7. MATLAB sort

    2024-06-08 19:08:01       6 阅读
  8. 2024-06-04 问AI: 介绍一下 Tensorflow 里面的 Keras

    2024-06-08 19:08:01       6 阅读
  9. spec文件是干嘛的?

    2024-06-08 19:08:01       5 阅读
  10. 11本AI人工智能相关电子书推荐(带下载地址)

    2024-06-08 19:08:01       11 阅读