Java P4-Interfaces & Inheritance

本文围绕Java的接口与继承展开。介绍了接口的定义、使用及演变,如接口可包含抽象、默认和静态方法,Java 8后支持方法具体实现。阐述了继承的概念、super关键字的使用、对象类型转换、多态等。还提及抽象类、匿名类、final修饰符及Java初始化顺序等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Interfaces in Java

Java接口为引用类型,包含:常量/方法签名/默认方法/静态方法等

Defining an Interface

接口由修饰符,关键字interface,接口名称,继承接口列表,以及接口主体组成

支持public/package-private修饰符(顶级)

一个接口可以扩展继承自任意数量的接口

接口内,可以包含抽象方法,默认方法和静态方法

抽象方法(Abstract Methods),仅包含方法的声明,不包含方法的具体实现

抽象方法参数括号后,直接接分号,没有方法体

接口中所有抽象/默认/静态方法都是隐式public ,因此可以省略public修饰符

接口中的抽象方法隐式具有public abstract修饰

接口中可以声明常量,常量值是隐式public static final, 因此可以省略

CC:接口中的方法/常量,均省略多余的修饰符

CC:接口命名:可以是名词或名词短语;形容能力的接口以对应的形容词(able后缀)命名;基于服务的接口声明服务类型后缀

定义一个类,通过implements关键词声明实现指定接口

实现类实现多接口时,使用逗号分隔接口列表

实现接口,就必须实现接口中所有的抽象方法

Undergraduate类实现了 Learnable/Playable接口 则必须实现2个接口中的全部抽象方法 与普通类一样可以声明自己的属性/构造函数等

@Override注解 声明此方法为一个重写的方法

本科生具有Learnable与Playable的能力 因此,可以基于本科生创建独有相应能力的对象 研究生没有Playable的能力 因此,无法创建相应能力的对象

Using an Interface as a Type

定义了一个接口,就是定义了一个可以引用的类型,像类一样,在任何需要的地方作为类型使用

抽象出相同的状态(属性),设计为实体类

抽象出相同的行为(方法),设计为接口

接口,以行为能力分类

组合一系列 行为(方法)

组合一系列 属性

将方法聚合为能力/服务

将属性聚合为实体类

Evolving Interfaces

试图添加新的play能力,但是原接口实现者可能并没有该能力

Java8以前,接口中仅允许定义抽象方法,无法声明方法的具体实现,增加了接口的维护成本 例如,Java8在集合的顶级接口Iterable中,添加支持Lambda表达式的forEach()迭代方法,而仅JDK(不算其他第三方库) 其子接口有数十个,涉及到的实现类有数百个,但是其实现代码却是相同的,直接添加抽象方法的代价过高 因此在Java8后,允许通过default关键词在接口中声明方法的具体实现

将方法声明为default方法 则支持在接口中直接实现该方法

无需修改任何Playable接口实现类

所有实现了Learnable接口类型的对象 均可直接调用该接口中实现的方法

Java8之前,通过定义工具类以及静态方法,提供辅助性操作 Java8之后,允许在接口中定义静态方法,从而更容易组织工具库以及创建代理类

在接口中声明static方法,通过接口名称 可直接调用 无需任何实现类

Java接口支持 公有常量 抽象方法,通过类实现使用 默认方法,通过实现类直接使用 私有方法,为默认方法提供支持 公有静态方法,通过接口直接使用 私有静态方法,为公有静态方法提供支持

支持多继承

不支持包级成员与私有常量

默认/私有方法,依然是单向调用

  1. 默认方法:默认方法可以被接口中的其他方法调用,包括其他默认方法和抽象方法。在接口的实现类中,可以选择重写默认方法或者直接继承它们。但默认方法不能被实现类或其他类直接调用。

  2. 私有方法:私有方法只能在接口内部被默认方法或其他私有方法调用,它们对于接口的实现类和其他类是不可见的。

  3. 静态方法:静态方法可以被接口中的其他静态方法调用,但它们不能被默认方法或私有方法调用。在接口的实现类中,可以直接通过接口名调用静态方法。

Inheritance

Subclass,从另一个类派生出的类,称为子类(派生类,扩展类等)

Superclass,派生子类的类,称为超类(基类)

习惯上称子类的直接超类为,父类

每个类能且仅能,直接继承自一个父类(单继承

没有显式声明继承时,每个类都隐式继承自Object

类A显示继承自类B,类B显示继承自类C,类C没有显式继承任何类,但类C依然隐式继承自Object类

因此,Java中所有类都是Object派生的子类

Inheritance,继承,是面向对象程序设计语言基本特征之一(封装/继承/多态)

继承意义在于:当需要创建一个新类,且当前已经存在一个包含需要代码的类时,可以从现有的类中派生出一个新类,从而重用现有类的成员,而无需重新编写/调试

Using the super Keyword

关键词this,指向当前对象的引用

关键词super,代表当前对象的超类(不仅仅是直接的父类,可追溯到Object类)。用以区分自己的/超类的

*可通过super调用超类public/protected成员

*可通过super调用超类构造函数

不能理解为是指向父类的引用,虽然实例化时会调用超类构造函数,但并不会创建超类对象。因此,不存在超类对象

解释一下:当你调用super()构造函数时,它会返回一个代表父类的对象,并在子类中使用这个对象来调用父类的构造函数,这个过程中不会创建新的超类对象,只是初始化子类对象中的父类部分,所以,super()调用的结果是调用父类构造函数初始化子类对象中的父类部分而不是创建新的超类对象

public interface Moveable {
    void move();
}
public class Animal implements Moveable {
    private String name;
    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public void move() {
        System.out.println(name+"moving");
    }
}

支持在子类中声明子类自有的新属性

支持在子类中声明子类自有的新方法

子类继承超类所有public/protected成员(变量/方法)

可以调用父类public成员方法,超类必然包括Object类,因此可以调用Object public/protected成员方法

在子类/超类的方法没有名称冲突时 可直接调用超类中的方法 无需声明super

System.out.println(getName() + "is flying")
等效
System.out.println(super.getName() + "is flying")

*子类必须满足超类特性。因此,逻辑上,能够构造初始化子类的前提,是必须能够构造初始化超类

*即使超类未显式提供有参构造函数,也会调用超类无参构造函数

    //两个参数的构造函数,但一个为父类属性,且父类提供了构造函数,因此可通过父类初始化封装一个属性,再自己初始化一个属性
    public Bird(String name,String color) {
        super(name);//在构造函数中,通过super调用超类构造函数语句必须置于构造函数第一行
        this.color = color;
    }
//父类没有显式提供有参构造函数时


// 父类
class Parent {
    private String name;

    // 父类没有显式提供有参构造函数

    // Getter 和 Setter 方法省略
}

// 子类
class Child extends Parent {
    private int age;

    // 子类的构造函数
    public Child(String name, int age) {
        // 在子类的构造函数中调用父类的无参构造函数
        super();
        this.age = age;
    }

    // Getter 和 Setter 方法省略
}

子类无法继承超类的private成员,但可以通过属性的getter/setter方法访问超类的属性

public class Bird extends Animal implements Flyable {
    private String color;

    //两个参数的构造函数,但一个为父类属性,且父类提供了构造函数,因此可通过父类初始化封装一个属性,再自己初始化一个属性
    public Bird(String name,String color) {
        super(name);//在构造函数中,通过super调用超类构造函数语句必须置于构造函数第一行
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    //Bird类,继承自Animal类,因此继承父类name属性(通过父类构造函数以及继承getter/setter方法实现)
    //继承父类Movable能力,同时具有独有color属性,具有独有Flyable能力
    @Override
    public void fly() {
        System.out.println(getName() + "is flying");
    }

    public static void main(String[] args) {
        Bird bird = new Bird("Raven","black");
        bird.move();
        bird.fly();
        System.out.println(bird.getName() + "/" + bird.getColor());
    }
}

支持在子类中声明一个与超类中方法签名相同的,新实例方法,从而overriding覆盖超类方法(方法的重写)

由于方法签名与父类中的方法签名相同,为避免歧义,使用@Override注解显式声明重写超类方法

    @Override
    public void fly() {
        System.out.println(getName() + "is flying");
    }

    public static void main(String[] args) {
        Bird bird = new Bird("Raven","black");
        bird.move();
        Animal animal = new Animal("Lion");
        animal.move();
    }

子类/父类对象均调用move()方法 但子类重写了父类的方法 因此实现已不同

Casting Objects

相关的不同类型间的转换,是多态的表现形式

Bird继承自Animal以及Object,因此Bird也是Animal类型,也是Object类型。但是反过来则无法确定

        Bird bird = new Bird("Raven","black");
        System.out.println(bird instanceof Bird);
        System.out.println(bird instanceof Animal);
        System.out.println(bird instanceof Object);

因此,在类的继承与接口的实现中,允许将一种类型转换为其继承/实现的另一种类型,称为上转型(隐式转换,implicit casting)

        Bird bird = new Bird("Raven","black");
        Animal animal = bird;
        Object obj = bird;

变量bird/animal/obj 声明的类型不同,但引用的 是同一个对象

        System.out.println(bird.getClass().getName());
        System.out.println(animal.getClass().getName());
        System.out.println(obj.getClass().getName());

说明 变量的类型为 其实际引用对象类型的超类时 可自动完成上转型 其实际类型仍为引用对象类型 而非变量声明的类型

相反,当试图将Animal或Object转换为Bird时,称为下转型(explicit casting,显式转换),需显式声明强制转换

Object obj = bird;
Bird bird2 = (Bird) obj;
bird2.move();

由于obj引用的对象为bird类型 因此可以类型转换且没有异常

隐式的上转型,子类一定具有超类的特性,因此编译器可自动实现类型转换

显式的下转型,超类未必一定是子类,因此需显式声明强制转换,且只有运行时才能知道错误

Bird,animal变量引用的是同一个类型对象,但变量类型不同

Animal变量无法调用bird中的fly()方法,声明的变量类型(Animal类型)限制约束了他的行为

即,此时animal只能表现出Animal类型的行为状态,即使他实际上引用的是一个Bird类型对象

Bird重写了父类的move()方法

即,声明的变量类型仅约束行为(限制方法的调用),不影响方法的实现

基于接口实现类创建对象

接口类型变量的实际类型为 引用对象的类型

接口无法实例化,因此,声明的接口类型变量,是其实际引用对象的类型,永远不能为其声明的接口类型,只能是接口其中一个实现类的的类型

注意:1、:通过接口类型的引用,可以访问到实现类中继承或重写了接口中定义的方法。因为 Moveable 接口可能包含了一些方法,而 Animal 类实现了这些方法,所以通过 Moveable 接口类型的引用可以调用 Animal 类中实现的这些方法。

2、:使用接口类型的引用无法直接访问实现类中未在接口中定义的方法。即使 Animal 类中有一些自己特有的方法,在使用接口类型的引用时也无法直接调用这些方法。如果需要调用实现类中特有的方法,需要将接口类型的引用强制转换为实现类的类型,然后才能调用这些特有方法。

animal变量无法调用其自身声明的方法,声明的变量类型(Movable类型)限制约束了他的行为 即,此时animal只能表现出Movable类型的能力,即使他实际上引用的是一个Animal类型对象

bird对象具有move() fly()的行为,但声明的变量类型(Flyable类型)限制约束了他的行为 即,此时bird只能表现出Flyable类型的能力,即使他实际上引用的是一个Bird类型对象

Bird具有move()与fly()的行为 但此时仅需表现他的Flyable能力

Polymorphism

通过继承的超类类型,引用不同子类类型对象实现多态

通过实现的接口类型,引用不同实现类类型对象实现多态

同一个对象,通过不同的约束,表现出不同的行为能力

即,Java通过声明不同的超类类型或实现的接口类型,实现多态

面向对象程序语言实现多态的意义: 可以只关注希望使用的能力/服务,无需关心该能力/服务的具体实现

即,面向接口编程/面向切面编程(AOP)/面向服务编程(SOP)。通过定义具有不同服务的接口,由容器或工具动态注入实现对象,而完全屏蔽具体实现类型

1、基于多态/转型的特性,重写的方法支持改变返回类型为,`小于等于`超类要求的类型

超类要求此方法必须返回 具有Movable能力的对象

则支持重写时改变类型为 任何实现了Movable接口类型的对象 或其子接口类型

2、重写超类方法,返回类型为基本数据类型的禁止改变

3、重写方法的访问范围,必须大于等于超类声明的范围

超类声明特性为 protected

类必须至少 具有超类的特性 因此禁止缩减范围 但是可以扩大范围

4、子类可直接调用超类中静态成员(public/protected)

5、支持在子类中声明一个与超类中方法签名相同的静态方法,从而hiding隐藏超类静态方法(静态方法的隐藏无需@Override注解修饰)

What You Can Do in a Subclass - Summary

1、支持在子类中声明子类自有的新属性

2、支持在子类中声明子类自有的新方法

3、子类继承超类所有public/protected成员(变量/方法)

4、支持在子类中声明一个与超类中方法签名相同的,新实例方法,从而overriding覆盖超类方法(方法的重写)

5、支持在子类中声明一个与超类中方法签名相同的,新的静态方法,从而hiding隐藏超类静态方法

6、创建子类对象,必然调用超类构造函数

7、支持编写子类构造函数时,通过关键字super显式调用超类构造函数

8、子类无法继承超类private成员,但可以通过属性的getter/setter方法访问超类的属性(如果提供)

Abstract Methods and Classes

1、抽象类,可以定义抽象没有实现的方法

2、抽象类无法被实例化,但是可以被继承使用(抽象类就是为了继承使用的模板)

3、抽象类中可以声明抽象的方法,也可以声明普通方法,普通方法可以被子类继承使用

4、继承了抽象类的子类,则必须实现抽象类中所有抽象方法,否则也必须声明为抽象类 抽象类可以实现接口,而无需实现接口中全部抽象方法,未实现的抽象方法可由子类实现

5、抽象类可以继承非抽象类的普通类

//定义抽象类,虽然不能被实例化,但是可以声明子类共有的属性,并提供构造函数拱子类封装使用

public abstract class Organism {
    private String name;
    public Organism(String name) {
        this.name = name;
    }

    //声明普通成员方法
    public void sleep() {
        System.out.println(name + "is sleeping");
    }

    //声明抽象方法
    public abstract void move();



    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Human extends Organism{
    public Human(String name) {
        super(name);
    }
    //子类继承抽象的父类则必须重写抽象的方法,调用父类构造函数封装父类属性,可直接调用继承自父类的方法
    @Override
    public void move() {
        System.out.println(getName() + "move as human.");
    }
}
public class Animal extends Organism{
    public Animal(String name) {
        super(name);
    }
    @Override
    public void move() {
        System.out.println(getName() + "move in many ways.");
    }
}

子类可直接调用父类中继承的方法 子类重写父类中抽象方法 从而完成不同的实现 可基于多态声明符合类型的变量

    public static void main(String[] args) {
        Human yu = new Human("yu");
        Organism sun = new Human("SUN");
        sun.move();
        sun.sleep();
        Organism lion = new Animal("Lion");
        lion.move();
        lion.sleep();
    }

无法创建抽象类的对象

Abstract Classes Compared to Interfaces

抽象类与接口相似,均不能被实例化,均可声明抽象/非抽象/公有/私有/静态等等成员

抽象类,关注于为子类提供支持,单继承限制子类扩展

接口,关注于能力服务,不耦合具体实现

使用抽象类场景

期望相关类共享公共代码及约束(包含抽象方法的模板)

期望相关类有具有各自特有的属性行为

使用接口场景

期望一些类能提供相同的能力但又彼此无关

期望使用指定的能力服务,而不关心其实现

Anonymous Classes

匿名类/匿名内部类,能够使代码更加简洁

匿名内部类,能够同时的一步到位的,声明和创建一个实现/继承的接口/抽象类的实现类和对象。

动态创建的实现类/子类没有名称,但可以像正常类一样使用

适合于仅声明使用一次的不会被复用的类

public class Student {
    private String name;

    //将学习能力作为类的一个拥有属性,从而每一个学生拥有自己的姓名以及自己的学习能力实现方式
    private Learnable learnable;
    public Student(String name) {
        this.name = name;
    }

//在main方法中创建匿名内部类

    public static void main(String[] args) {
        Student s1 = new Student("Yu");
        Learnable l1 = new Learnable() {
            @Override
            public void read() {
                System.out.println("我要按自己的方式阅读");
            }
        };
        s1.setLearnable(l1);
    }


    public String getName() {
        return name;
    }

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

    public Learnable getLearnable() {
        return learnable;
    }

    public void setLearnable(Learnable learnable) {
        this.learnable = learnable;
    }
}
//省略声明变量,一次性构建
    public static void main(String[] args) {
        Student s2 = new Student("Son");
        s2.setLearnable(new Learnable() {
            @Override
            public void read() {
                int number = 10;
                System.out.println("我要读" + number +"本书");
            }
        });
        System.out.println(s2.getLearnable());
        s2.getLearnable().read();
    }

同理 虽然无法创建抽象类的对象 但基于匿名内部类 可以直接创建抽象类子类的对象

即使普通类 也支持直接创建一个匿名内部类子类 及对象

public class Bicycle {
    private int gear;
    public Bicycle() {
        this.gear = gear;
    }

    public static void main(String[] args) {
        //基于匿名内部类直接创建Bicycle的子类及对象
        //实现子类的操作。此处重写了Bicycle父类中的方法
        Bicycle b = new Bicycle() {
            @Override
            public int getGear() {
                return super.getGear();
            }
        };
    }
    public int getGear() {
        return gear;
    }
    public void setGear(int gear) {
        this.gear = gear;
    }
}

Final

final修饰的方法,无法被子类重写

final修饰的类,无法被继承

Object as a Superclass

java.lang.Object类,位于类层次结构树的顶部

 每个类都直接或间接继承自Object类

每个类都继承Object类中的实例方法

public final Class getClass()

无法重写此方法

用于动态获取类型信息,方法返回Class类型对象,对象中具有获取有关类型信息的方法。例如,获取类型的全限定类名/超类/实现的接口/声明的方法/修饰的注解等等

Order of Java Initialization

public class A {
    public static String getS() {
        System.out.println("A:static method getS()");
        return S;
    }
    private static String S = create();
    private static String create() {
        System.out.println("A:static method create()");
        return "";
    }
    static {
        System.out.println("A:static block 2");
    }
    static {
        System.out.println("A:static block");
    }

    public static void main(String[] args) {
        A.getS();
    }
}

执行A.getS()方法时:

首先加载类至类加载器(仅加载,并非实例化)

只要类被加载,即执行static相关操作

按static变量或static代码块声明的先后顺序,初始化static变量或执行static代码块

当初始化static变量S时,执行create()方法

执行调用的static方法getS(),返回变量

第二次调用getS()方法,直接返回static变量的值/引用,而不会再次执行create()方法

Static变量是在类加载时完成的初始化,与是否调用getS()方法无关

即,加载类时,初始化static变量,执行static代码块 Static变量一旦赋值即置于堆空间的静态区共享使用

追加了构造函数

当执行A.getS()时 A当然未被实例化!

即,调用static方法加载类时,不会实例化该类 进一步说明了,static是类相关,与类的实例无关

即使,没有调用 static方法getA() 依然,初始化了static变量 后调用A构造函数初始化A

静态方法,不主动调用是不会执行的

当实例化A时 需先加载类,因此,会先执行static相关操作 后调用构造函数初始化A

只要类被加载,无论是由于被实例化,还是被调用static成员(变量/常量/方法),均按声明的先后顺序,初始化static成员或执行static代码块,后实例化类

实例化子类

预实例化子类B

1,加载父类A,执行A static相关

2,加载子类B,执行B static相关

3,调用子类B构造函数,必先调用父类A构造函数

4,调用子类B构造函数

类C中,包含类型A的属性变量 但构造C时,并没有加载类A 即,仅声明类型变量 不会加载该类型

public class A {
    public A() {
        System.out.println("A:constructor");
    }
    static {
        System.out.println("A:static block");
    }
    private static String S = create();
    private static String create() {
        System.out.println("A:static method create()");
        return "";
    }
}
public class C {
    private A a = new A();
    public C(A a) {
        this.a = a;
        System.out.println("C: Constructor");
    }
    static {
        System.out.println("C:static block");
    }
    public static void main(String[] args) {
        new C(new A());
    }
}

实例化一个类:

先,基于类加载的顺序(父类-子类),执行类static相关操作

再,执行类中属性初始化操作

最后,调用构造函数完成类的初始化 因此,构造函数中的初始化,可以覆盖属性的初始化

在进行new C(new A())操作时,我们要实例化C,所以先基于类C加载的顺序,执行static相关操作,输出C:static block,接着执行类C中属性初始化操作,初始化A,进行new(A)操作,跟刚刚类C的static操作一样输出A:static block,再进行类A中属性初始化,初始化S,输出A:static method create(),最后调用构造函数,输出A:constructor,此时我们类C中的属性初始化操作已经完成,最后需要调用构造函数,传入参数new A(),进行了一次A的构造,所以输出A:constructor,进行new C(new A()),最后输出C: Constructor

Summary

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值