JAVA学习(8)继承 ( 继承的注意事项、Java的执行顺序、继承方式、限定词protcted、final关键词、多态、动态绑定和静态绑定 )

接上次的博客:JAVA学习(7)(初始化、构造方法、封装、包、自定义包、常见的包、static修饰成员变量、static修饰成员方法、static成员变量初始化、代码块​)_di-Dora的博客-CSDN博客

目录

继承的注意事项:

易错练习

Java的执行顺序

继承方式

protcted

final关键词

多态——多态其实是一种思想!

动态绑定和静态绑定


子类:又称为派生类;

父类:又称为超类、基类。

子类继承父类。

继承的注意事项:

1、就近原则:子类和父类不存在同名变量名,因为子类会继承父类的实例变量。当子类和父类拥有同名的成员变量时,就近优先访问的是子类的变量。如果一定要访问父类的变量,一定要在访问时的成员变量前面加上 “ super. ”。

通过super访问的是继承自父类的,这是一个关键字,重要作用是:在子类方法中访问父类。

也就是说,它一定是用在子类中的。

super只能访问从父类继承过来的成员变量。这里super就是一个提高代码可读性的关键字,并不能说它代表父类对象的引用!!!

通过this可以访问全部(父类的+子类的),但是当父类和子类同名情况下,子类优先访问;

同理,同名方法(返回值、参数、方法名称都相同,即方法签名都相同的方法)也如此,此时,使用 super. func() 可以访问父类的成员方法。

相同的方法签名,实现了方法的重写。

super( )  调用父类的构造方法。

super和this不能在静态方法中使用,只能在实例方法中使用。这是因为静态方法属于类级别,而不是实例级别,没有this指向当前对象,也没有super指向父类对象。因此,在静态方法中使用this或super是没有意义的,并且会引起编译错误。只有在实例方法中才可以使用this和super,分别表示当前对象和父类对象。

2、当子类继承父类之后,一定要现在子类中先构造父类(一定要作为第一条语句),再构造子类自己。

也就是说,要先在子类当中调用父类的构造方法,直接通过super调用想要调用的构造方法。    

当你没有写任何构造方法时,编译器会默认给你提供一个无参数的构造方法 super( ) 。

在调用构造方法的时候,super() 和 this() 不可以同时存在,这是因为 super() 和 this() 都需要作为构造方法的第一条语句出现,用于调用父类构造方法或当前类其他构造方法,两者同时出现会导致构造方法执行顺序不明确。例如,如果同时调用了 super() 和 this(),就不知道应该先调用父类构造方法还是当前类构造方法,这样编译器就无法确定构造方法的执行顺序,会导致编译错误。因此,在一个构造方法中只能使用 super() 或 this(),而不能同时使用。

3、总的来说,super() 和 this() 的作用是不同的,二者可以用于不同的场景。

相同点:

  • 都是Java中的关键字
  • 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  • super() 和 this() 都是用来调用其他构造方法的;
  • super() 和 this() 都必须是在构造方法的第一行调用;
  • super() 和 this() 都只能在构造方法中使用。


不同点:

  • 在非静态成员方法中,this用来访问本来的方法和属性,是当前对象的引用(当前对象即调用实例方法的对象);super用来访问父类继承下来的方法和属性,相当于是子类对象中从父类继承下来部分成员的引用;
  • super() 用于调用父类的构造方法,this() 用于调用本类的其他构造方法;
  • super() 可以不显式调用,如果不显式调用则默认调用父类的无参构造方法,this() 必须显式调用,否则编译器并不会自动在构造方法的第一行插入一个无参的 this();
  • super() 和 this() 不能同时出现在同一个构造方法中。

易错练习

class Base{
  public Base(String s){
    System.out.print("B");
  }
}

public class Derived extends Base{
  public Derived (String s) {
    System.out.print("D");
  }
  public static void main(String[] args){
    new Derived("C");
  }
}

你觉得这个代码会输出什么?

编译出错啦! 在创建 Derived 的对象时,首先会调用父类 Base 的构造方法,由于子类没有显式地调用 super(String s),所以会调用父类默认的无参构造方法,但是父类 Base 中只有一个有参构造方法,因此程序会报错。

 这样就没有问题了。

这句话正确吗?

super关键字只代表当前对象内部的那一块父类型特征,不包含在子类对象中。

正确,super关键字用于访问父类中的成员变量、成员方法和构造方法,它代表的是父类对象。当子类继承父类时,父类中的所有成员变量和成员方法都会被子类继承,包括特征和行为。因此,使用super关键字可以访问到父类中的所有特征和行为。但是,super关键字并不会将父类中的成员变量和成员方法复制到子类中,而是在子类对象中保留了对父类对象的引用。

这句话正确吗?super关键字不仅可以指代子类的直接父类,还可以直接指代父类的父类。

不正确。错在“直接”二字。使用super关键字可以调用直接父类的成员变量、成员方法和构造方法,也可以通过super关键字再次“间接”调用父类的super,以此类推,直到达到Object类为止。所以,super关键字不仅可以指代子类的直接父类,也可以间接指代父类的父类。总之,我们使用super. 可以调用父类的父类的属性的原因是他的父类也有一个super,这样链式委托的。

Java的执行顺序

现在,让我们把所学内容综合一下,来看看你是否真的清楚Java中的执行顺序了:

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

    static {
        System.out.println("这是一个子类的静态代码块!");
    }

    {
        System.out.println("这是一个子类的实例代码块!");
    }

    public Dog() {
        super("Mr.wang");
        System.out.println("这是一个子类的构造方法!");
    }

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

再实例化一个对象呢?

    public static void main(String[] args) {
        Dog dog1=new Dog();
        System.out.println("========================");
        Dog dog2=new Dog();
    }

 总结下来就是:

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

父类实例代码块和父类构造代码块紧接着执行;

子类实例代码块和子类构造方法紧接着执行;

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

继承方式

Java中的继承方式主要有三种:单继承、多重继承和接口继承。

单继承
Java只支持单继承,即一个子类只能有一个父类。这种继承方式可以避免多重继承可能引起的歧义和冲突。单继承使得Java中的继承关系形成了一棵树形结构,从而易于理解和管理。

多层继承: 

不同类继承同一个类:

 多重继承

多重继承指一个子类可以有多个父类,这种继承方式在一些编程语言中被支持,如C++。多重继承具有一定的优势,可以在多个类之间共享代码,但也存在一些问题,如命名冲突、二义性等。Java中不支持多重继承。

接口继承
接口继承是Java中另一种重要的继承方式。接口继承指一个类可以实现多个接口,从而具有多个接口的特性。接口继承可以在不同的类中实现相同的接口,并且可以为不同的类提供相同的行为和特性。接口继承可以为Java中的多态性和抽象性提供支持,是Java中的重要特性之一。

protcted

protected 是 Java 中的一种访问修饰符,用于限制访问类的成员变量和方法的范围,即主要限定:类或者类中的成员能否在类外或者其他包中被访问。当使用 protected 修饰成员变量和方法时,这些成员变量和方法只能被本类、子类、同一个包下的类和不同包里的子类访问,不能被其他包下的其他类访问。

下面是一个示例代码,演示了 protected 在不同包里的子类访问中的使用:

package demo2;

public  class TestDemo1 {
    protected int a = 99;
}


package demo3;

import demo2.TestDemo1;

public class Test extends TestDemo1{

    public void func() {
        TestDemo1 testDemo1 = new TestDemo1();
        System.out.println(super.a);
    }

    public static void main(String[] args) {
        test.func();
    }

这里一定要用super去访问才是正确的,而super又是不可以在静态方法中出现的,所以要从main中独立出来。如下代码就是无法编译成功的:

package demo2;

public  class TestDemo1 {
    protected int a = 99;
}


package demo3;

import demo2.TestDemo1;

public class Test extends TestDemo1{

   public static void main(String[] args) {
        TestDemo1 testDemo1 = new TestDemo1();
        System.out.println(testDemo1.a);
    }

final关键词

在Java中,final关键字可以用来修饰类、方法和变量,具有不同的作用:

final修饰类:表示该类不能被继承,即该类为最终类(密封类,这个类不能被继承)。
final修饰方法:表示该方法不能被子类重写,即该方法为最终方法。
final修饰变量:表示该变量为常量,只能被赋值一次,一旦赋值后就不能被修改。

final int [] array1={1,2,3,4,5};
array1=new int[10];//不能修改array1这个变量中存储的值,不能让它指向新的对象!
array1[0]=99;//是可以的

具体来说,final变量有以下特点:

一旦被赋值后,其值就不能再次改变。
如果是实例变量,则必须在定义时或构造函数中进行初始化赋值,否则编译器会报错。
如果是类变量(即static变量),则必须在定义时或静态初始化块中进行初始化赋值。
常量名通常使用全大写字母,多个单词之间用下划线连接,例如:MAX_VALUE。


总的来说,使用final可以增加程序的可读性、可维护性和安全性。例如,在多线程环境下使用final可以避免竞态条件,而在设计API时使用final可以保护API的稳定性和一致性。

多态——多态其实是一种思想!

多态是一种面向对象编程的概念,指的是同一个方法调用可以有不同的行为。具体来说,它允许使用父类或接口类型的引用来调用子类对象的方法,实现了代码的灵活性和可扩展性。

多态主要分为两种类型:编译时多态和运行时多态。编译时多态也称为静态多态,是指在编译阶段就能确定方法的调用版本;而运行时多态也称为动态多态,是指在程序运行时根据实际类型来确定方法的调用版本。

多态的实现主要依靠继承和重写两种机制,子类可以重写父类的方法,当使用父类类型引用指向子类对象时,调用该方法会执行子类的方法实现。同时,接口也可以实现多态,实现接口的不同类可以提供自己的实现。

多态的好处是提高了代码的可维护性和可扩展性,使得代码更加灵活,同时可以使代码更容易被复用。

实现多态需要满足以下条件:

继承或实现:要使用多态,必须有继承或实现的关系;

方法重写:子类必须重写父类的方法,且方法名、参数列表和返回值类型必须与父类中被重写的方法完全一致,也就是说,子类中的方法与父类中的方法有相同的签名;

父类引用指向子类对象:要实现多态,需要使用父类类型的变量引用子类对象,这样可以在运行时自动确定需要调用的方法;

向上转型:

Dog dog1 = new Dog;
Animal animal = dog1 ;
//上面两行代码可以等效于下面这行代码

Animal animal = new Dog;

运行时绑定:在运行时根据对象的实际类型(而不是变量的类型)来确定需要调用的方法,这也就是所谓的动态绑定或者运行时绑定。

简单来说,如果父类Animal有一个eat的方法,子类Dog也有一个eat方法:

Animal animal = new Animal;
animal.eat();

调用的是父类自己的方法,这就是静态绑定;

Animal animal = new Dog;
animal.eat();

调用的是变成子类的方法,这就是动态绑定。

总的来说,多态的实现需要利用继承和方法重写来构建类层次结构,使用父类类型的引用指向子类对象,利用运行时绑定实现动态多态性。

动态绑定和静态绑定

什么是动态绑定?什么是静态绑定?

静态绑定和动态绑定是面向对象编程中的两个重要概念。

静态绑定是指在编译时确定方法或变量的调用或引用所对应的具体类或变量,也称为编译期绑定或早期绑定。在静态绑定中,方法或变量的选择是根据引用变量的声明类型进行的,而不是根据实际类型。

动态绑定是指在运行时根据对象的实际类型确定方法或变量的调用或引用所对应的具体类或变量,也称为运行时绑定或晚期绑定。在动态绑定中,方法或变量的选择是根据实际类型进行的,而不是根据引用变量的声明类型。

多态的实现就依赖于动态绑定,因为在多态中,方法的具体实现取决于实际的对象类型。因此,多态只有在运行时才能确定调用的具体方法,这就需要使用动态绑定机制。

下面用代码来进行一些详细的说明:

静态绑定指在编译时就已经确定了调用的方法,主要是针对静态方法、私有方法、final方法和构造方法。

例如下面的代码:

class A {
    public static void foo() {
        System.out.println("A's foo");
    }
}
class B extends A {
    public static void foo() {
        System.out.println("B's foo");
    }
}
public class Test {
    public static void main(String[] args) {
        A a = new B();
        a.foo(); // 静态绑定,输出 A's foo
    }
}

上面的代码中,尽管 a 引用的是 B 对象,但是 foo 是一个静态方法,编译时已经确定了调用的是 A 类中的 foo 方法,所以最终输出的是 A's foo。

而动态绑定指在运行时才根据对象的实际类型来确定调用的方法,主要是针对实例方法(包括重写的方法)。

例如下面的代码:

class A {
    public void foo() {
        System.out.println("A's foo");
    }
}
class B extends A {
    public void foo() {
        System.out.println("B's foo");
    }
}
public class Test {
    public static void main(String[] args) {
        A a = new B();
        a.foo(); // 动态绑定,输出 B's foo
    }
}

上面的代码中,a 引用的是 B 对象,且 foo 是一个实例方法,所以在运行时根据 a 的实际类型 B 确定调用的是 B 类中的 foo 方法,最终输出的是 B's foo。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值