zl程序教程

您现在的位置是:首页 >  其他

当前栏目

接口

2023-09-14 09:04:05 时间

思维导图

点击标题可下载xmind文件

代码练习

package interfaceTest;

// 接口定义使用关键字interface
// 访问权限修饰符只能是public或默认
// 接口可以多继承其他接口(多个接口间逗号隔开),不能继承类
// 接口可以被类多实现,见Test1Impl,类实现接口使用关键字implements,实现的多个接口间以逗号隔开
public interface Test extends Test1,Test2,Test3{
    // 接口成员:常量
    // 默认被public static final修饰,三个修饰符可以省略不写,系统默认补全
    // 常量名字需要符合标识符要求,即常量名称需要全大写,且词间以下划线隔开
    // 常量的初始化,必需在声明常量时进行。因为接口没有构造器和初始化块成员。
    public static final int MAX_NUM = 10;

    // 接口成员:抽象方法
    // 默认被public abstract修饰,两个修饰符可以省略不写,系统默认补全
    // 抽象方法不能定义方法体,只能定义方法签名
    // 接口抽象需要被普通类重写,否则普通类需要定义成抽象类,具体见TestImpl类定义
    public abstract void test();

    // 接口成员:默认方法
    // 默认被public修饰,public可以省略不写,系统默认补全。但是default不能省略
    // 接口默认方法需要定义方法体,否则编译报错。
    // 接口的实现类可以重写默认方法,也可以不重写默认方法。不重写的话,实现类继承接口该默认方法。
    // 接口的默认方法可以被实现类对象直接调用。
    public default void testDef(){
        System.out.println("接口的默认方法,被实现类重写");
    }

    public default void testDef2(){
        System.out.println("接口的默认方法,没被实现类重写");
    }

    // 接口成员:静态方法
    // 默认被public修饰,public可以省略不写,系统默认补全,但是static不能省略
    // 接口静态方法需要定义方法体,否则编译报错
    // 接口的静态方法不能被实现类继承,也不能被实现类重写
    // 接口的静态方法只能通过接口名调用
    // 需要注意的是,实现类中定义的同名方法,并不是重写接口的静态方法,两者无关系。
    public static void testStat(){
        System.out.println("接口静态方法");
    }

    // 接口成员:私有方法
    // 无简写规则。其中private不能简写。且static可选。被static修饰的属于静态私有方法,没有被static修饰属于默认私有方法
    // 接口的私有放啊需要定义方法体,否则编译报错
    // 接口的私有方法只能在接口内部使用,不能被实现类继承
    private void testPrivateDef(){
        System.out.println("接口私有(默认)方法");
    }

    private static void testPrivateStat(){
        System.out.println("接口私有(静态)方法");
    }

    // 接口成员:内部类,枚举类
    // 默认被public static修饰,其中public static可以省略不写。系统默认补全。
    public static class Inner{}
    public static enum Season{SPRING,SUMMER,FALL,WINTER;}

    // 接口不能定义初始化块
    //否则编译报错:Not allowed in interface
    //{}
    //static {}

    // 接口不支持定义构造器
    //public Test(){}
}

// 如果普通类不实现接口全部抽象放啊,则需要成抽象方法
// 否则编译报错Class 'TestImpl' must either be declared abstract or implement abstract method 'test()' in 'Test'
abstract class TestImpl implements Test{}

class Test1Impl implements Test,Test1,Test2,Test3{

    // 普通类必须重写实现接口的抽象方法
    @Override
    public void test() {
        System.out.println("被实现类重写的抽象方法");
    }

    // 普通类可以重写接口的默认方法,也可以不重写。该类对象可以直接调用接口默认方法。
    @Override
    public void testDef() {
        System.out.println("被实现类重写的接口默认方法");
    }


    public void testStat(){
        System.out.println("实现类自定义新方法,和实现接口的静态方法无关系");
    }
}

interface Test1{}

interface Test2{}

interface Test3{}

class Main{
    public static void main(String[] args) {
        Test1Impl test1 = new Test1Impl();
        test1.testDef();// 运行结果:被实现类重写的接口默认方法
        test1.testDef2();// 运行结果:接口的默认方法,没被实现类重写
        test1.testStat();// 运行结果:实现类自定义新方法,和实现接口的静态方法无关系
        Test.testStat();// 运行结果:接口静态方法

        // 接口使用:多态(向上转型),即接口引用变量可以指向接口实现类对象
        Test test = test1;

        // 接口使用:多态(强转),即接口实现类对象可以强转为接口类型
        Object obj = (Test) test1;

        // 接口不能使用:接口不能用于创建实例
        // 否则编译报错:'Test' is abstract; cannot be instantiated
        //Test test2 = new Test();
    }
}

思考题

0.接口中的成员变量,也叫常量,这里常量是指常量值不会改变吗?

如果接口成员常量的类型是基本数据类型,则对应的常量值不会改变。

如果接口成员常量的类型是引用类型中不可变类类型,则对应的常量值不会改变。

如果接口成员常量的类型是引用类型中可变类类型,则对应的常量值内容是可能改变的。

接口中成员变量默认被public static final修饰,

但是这里的final只是保证  变量名的引用不变,即变量名存储的地址值不变,但是无法变量的引用指向的值不变。

这里的值如果是基本类型数据,则由于基本类型数据本身一旦确定就无法改变。

这里的值如果是引用类型数据,我们需要直到引用类型数据分为两类:不可变类,可变类。


不可变类的概念一旦该类对象被创建,则对象的实例变量值不会再被改变。反之,就可变类概念。
所以不可变类类型的值,一旦确定就无法改变。

1.接口的成员是否都默认被public修饰?

不是。Jdk9之后,接口引入了一个新成员:私有方法。该成员被private修饰。

除了私有方法外,其他成员都都默认被public修饰:

常量: public static final

抽象方法: public abstract

默认方法: public default

静态方法: public static

内部类和枚举类: public static

接口没有构造器和初始化块成员。

2.为什么接口要引入默认方法,静态方法,私有方法?

我们知道在Jdk8之前接口没有默认方法,静态方法成员。

所以如果需求变更,则我们只能在接口中新增抽象方法,而实现接口的实现类必须重写接口定义的全部抽象方法,否则编译报错。

那么接口变更的话,就会造成其实现类的大量修改。这个不符合开发设计的原则。

Jdk8引入了默认方法和静态方法,用于满足拓展接口功能,而不会影响其实现类。因为接口默认方法,其实现类可以选择重写或不重写。而接口静态方法不能被实现类继承和重写,只能通过接口名调用。

那么静态方法的用处是什么呢?

接口静态方法一般用于定义接口级别的工具方法,这类方法用于良好的实现,不希望被重写,防止实现被篡改。

那么如果接口定义了多个默认方法,且多个默认方法中有很多重复代码,或者接口定义了多个静态方法,且多个静态方法中有很多重复代码,那么如何提取这部分重复代码呢?

Jdk9后,java给接口引入了私有方法,用于提取默认方法和静态方法中的重复代码,设计成私有的目的就是为了保证这部分重复代码逻辑只能在接口内调用,不需要也不能被外部调用。体现良好的封装性。

3.由于接口是可以多继承的,那么当多个父接口中定义了重复的抽象方法,默认方法,静态方法,子接口如何处理?

1.由于抽象方法没有具体实现,所以多个父接口中同名的抽象方法,继承给子接口后,子接口只会当成同一个抽象方法,不需要特殊处理。

2.由于默认方法有具体实现,所以子接口从多个父接口继承过来的同名默认方法会发生冲突,子接口必须重写该默认方法。

3.由于接口静态方法不会继承给子接口,而是属于对应父接口。所以无需特殊处理。 
package interfaceTest;

public interface Inter extends Inter1,Inter2{
    // Inter继承Inter1,Inter2,其中Inter1,Inter2定义了同名的抽象方法,默认方法,静态方法
    // 如果不做任何处理,则
    // 编译报错:interfaceTest.Inter inherits unrelated defaults for testDef() from types interfaceTest.Inter1 and interfaceTest.Inter2
    // 处理编译报错的方式
    // 1.无需关注多个父接口的同名抽象方法
    // 2.多个父接口存在同名的默认方法,则子接口必须重写该冲突的默认方法,否则编译报错
    // 3.无需关注读个父接口的同名静态方法,因为静态方法是属于对应接口的,不会继承给子接口
    @Override
    default void testDef() {
        System.out.println("重写多个父接口中冲突的同名默认方法");
    }
}

interface Inter1{
    void test();

    default void testDef(){
        System.out.println("Inter1 的默认方法");
    }

    static void testStat(){
        System.out.println("Inter1 的静态方法");
    }
}

interface Inter2{
    void test();

    default void testDef(){
        System.out.println("Inter2 的默认方法");
    }

    static void testStat(){
        System.out.println("Inter2 的静态方法");
    }
}

4.如果子接口和父接口中定义了相同的抽象方法,默认方法,静态方法,子接口是否会编译报错?

不会

1.子接口会将父接口的抽象方法和自己的同名抽象方法当成同一个方法。

2.子接口的默认方法实现会覆盖父接口的默认方法。

3.接口静态方法属于接口,子接口不会继承父接口的静态方法,所以不存在冲突。

另外实现子接口的实现类对象调用接口默认抽象方法时,调用的时子接口的默认方法。
package interfaceTest;

public interface Inter extends Inter1{
    void test();

    default void testDef(){
        System.out.println("子接口 的默认方法");
    }

    static void testStat(){
        System.out.println("子接口 的静态方法");
    }
}

interface Inter1{
    void test();

    default void testDef(){
        System.out.println("父接口 的默认方法");
    }

    static void testStat(){
        System.out.println("父接口 的静态方法");
    }
}

class Main1 implements Inter{
    @Override
    public void test() {
        System.out.println("实现类重写的接口抽象方法");
    }

    public static void main(String[] args) {
        Main1 main1 = new Main1();
        main1.test();// 实现类重写的接口抽象方法
        main1.testDef();// 子接口 的默认方法
        Inter.testStat();// 子接口 的静态方法
        Inter1.testStat();// 父接口 的静态方法
    }
}

5.由于类可以实现多个接口,那么当多个接口存在重复的抽象方法,默认方法,静态方法时,实现类该如何处理?

类实现多个接口,且多个接口存在同名的

1、抽象方法

由于抽象方法没有方法体,所以实现类会将多个接口的同名抽象方法当成一个来处理。

2、默认方法

由于默认方法有方法体,所以实现类从多个接口继承到多个同名不同方法体的方法后,会冲突,编译报错,需要强制实现类重写该默认方法。

3、静态方法

无需考虑,接口静态方法属于接口,和实现类无关。
package interfaceTest;

public interface Inter{
    void test();

    default void testDef(){
        System.out.println("接口1 的默认方法");
    }

    static void testStat(){
        System.out.println("接口1 的静态方法");
    }
}

interface Inter2{
    void test();

    default void testDef(){
        System.out.println("接口2 的默认方法");
    }

    static void testStat(){
        System.out.println("接口2 的静态方法");
    }
}

class Main1 implements Inter,Inter2{
    @Override
    public void test() {
        System.out.println("实现类重写的接口抽象方法");
    }

    // 如果不重写Inter,Inter2中的同名冲突的默认放啊,则编译报错
    // interfaceTest.Main1 inherits unrelated defaults for testDef() from types interfaceTest.Inter and interfaceTest.Inter2
    @Override
    public void testDef() {
        System.out.println("实现类重写的多个接口的同名冲突的默认方法");
    }

    public static void main(String[] args) {
        Main1 main1 = new Main1();
        main1.testDef();
    }
}

6.由于类可以同时继承一个父类,和实现多个接口,那么当父类和接口存在重复的抽象方法,默认方法,静态方法时,子类该如何处理?

先给出结论:

父类和父接口的抽象方法定义重复的话,子实现类会将这两个抽象方法当成一个处理,只会重写一次。

父类和父接口的静态方法定义重复的话,子类无需处理,父接口的静态方法属于接口,和实现类无关。父类的静态方法会继承给子类。

父类和父接口的默认方法定义重复的话,子类不会因为冲突而编译报错,但是子类对象调用该默认方法时:父类的方法执行优先级高级父接口的同名默认方法。

具体分析:

首先排除接口静态方法,因为接口静态方法属于接口本身,和实现类无关。所以接口静态方法和实现类的父类的同名静态方法不会冲突。

但是需要注意一点:父类的静态方法会继承给子类类本身。但是子类不能重写父类的静态方法,就算子类中定义一个和父类相同定义的静态方法,也是一个子类本身一个新方法,和父类同名静态方法无关。
package interfaceTest;

public interface Inter{
    public static void testStat(){
        System.out.println("父接口 的静态方法");
    }
}

class Father{
    public static void testStat(){
        System.out.println("父类 的静态方法(和父接口静态方法同名)");
    }
}

class Main2 extends Father implements Inter{
    public static void main(String[] args) {
        Main2 main2 = new Main2();
        main2.testStat();// 父类 的静态方法(和父接口静态方法同名)
        // 父类的静态方法会继承给子类类本身
        Main2.testStat();// 父类 的静态方法(和父接口静态方法同名)
        Father.testStat();// 父类 的静态方法(和父接口静态方法同名)
    }
}

 

其次默认方法,由于接口的默认方法被实现类继承或重写后就变成普通方法样式,如下
interface Inter{
    public default void test(){
        System.out.println("接口的默认方法");
    }
}

class Child implements Inter{
    @Override
    public void test(){
        System.out.println("实现类重写的默认方法");
    }   
}
所以构造冲突场景的话,父类也需要定义一个类似重写接口静态方法后的方法
package interfaceTest;

public interface Inter{
    default void testDef(){
        System.out.println("接口1 的默认方法");
    }
}

class Father{
    public void testDef(){
        System.out.println("父类 的方法(和父接口默认方法同名)");
    }
}

class Main2 extends Father implements Inter{
    public static void main(String[] args) {
        Main2 main2 = new Main2();
        main2.testDef();// 父类 的方法(和父接口默认方法同名)
    }
}
可以看出,父接口Inter和父类Father的同名方法testDef不会给子实现类造成冲突。

但是,子实现类调用该同名方法,最终执行的是父类的方法。所以可得:父类的方法执行优先级高级父接口的同名默认方法。
最后对于抽象方法,此时父类选择抽象类,因为只有包含抽象方法的类一定是抽象类
package interfaceTest;

public interface Inter{
    public abstract void test();
}

abstract class Father{
    public abstract void test();
}

class Main2 extends Father implements Inter{
    @Override
    public void test() {

    }
}
由于抽象方法没有方法体,所以子实现类会将父类和父接口中的抽象方法当成同一个抽象方法,只会重写一次。

7.接口不能继承其他类,那么接口类型变量是否可以赋值给Object类型的引用变量?

java中所有确定目标类型的对象都可以赋值给Object变量。因为Object是所有类的父类或超类。

需要注意的是:Lambda表达式不能直接赋值给Object类型变量,需要强转成目标类型后,才能赋值给Object变量。

8.接口为什么不能创建实例?

从概念上理解:接口是一种行为规范定义,并不是任何具体对象的抽象,所以接口实例化不出具体对象,也就不能实例化。

从语法上理解:接口没有构造器成员,所以不能创建实例。

9.接口中定义的常量为什么只能在声明时初始化?

我们需要知道,常量不会被系统默认初始化,需要程序员手动初始化。并且常量初始化只有三个时机:

1.声明常量时,初始化

2.在初始化块中初始化

3.在构造器中初始化

但是接口没有初始化块和构造器,所以只能在声明常量时初始化