唠一唠接口

接口可以new?

众所周知,接口是一种完全抽象化的设计,不能够实例化,即我们不能创建接口类型的对象。也就是说我们是不可以new出一个接口对象的,但是仔细想想看,我们是不是经常使用new一个接口的操作呢?

new出一个接口

其实之前刚刚讲过了lambda表达式,在那儿的时候我就想多说一句的,不过我还是忍住了,到这个地方再说。

public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird() {
            @Override
            public void fly() {
                System.out.println("Bird can fly!");
            }
        };
//        Bird bird = ()->{
//            System.out.println("Bird can fly");
//        };
        bird.fly();
    }
}

interface Bird {
    void fly();
}

注释中我使用的是lambda表达式的写法,上面的是一个匿名内部类。停!上面的是一个匿名内部类?我们是不是搞错了,上面我们new了一个接口?这编译器竟然还不报错,是吃干饭的嘛?

上面的代码一切都运行正常,不过这是不是就可以说明接口其实是可以进行实例化的呢?

既然他是可以new出来的,那我们就在这个类中打印一下他的类型看一下。看看是不是Bird类型,如果是的话就可以证明其实接口也是可以被new的。

System.out.println(this.getClass());

输出的结果是class com.sher.learnInterface.Test$1

Test$1这是个什么类,Test类是我们创建的,但是这个类我们根本不知道。这到底是怎么回事呢?

其实接口可以new完全是一个假象。接口是完全抽象化的设计,是不可能实例化的。不过那如果理解上面的代码呢?其实程序中的创建方式就是匿名内部类来实现的。上面的那个类Test$1就是由系统创建的一个类,这个类实现了Bird接口,并且实现了fly()方法。不怪刚学Java的时候,Java不建议在自己起的变量名中使用$,因为这个符号广泛的被Java所使用。

上面的那个匿名内部类的实现如下

static final class Test$1 implement Bird{
    public void fly(){
        System.out.println("Bird can fly");
    }
}
Bird bird = new Test$1();

由此可见,认为接口可以new真的是天大的笑话。

Java中没有多继承?

Java来源于C++。可以多继承是C++的一个重大的特点之一,不过Java认为C++中的多继承把继承结构搞得一团糟,比如继承两个类都有相同的方法和属性的时候。所以索性Java直接禁止类的多继承,只允许单继承。但是Java中多了接口,类可以实现多个接口

所以说,当别人问你,Java中有没有多继承的时候,我们应该回答没有??

上面只是说Java禁止类的多继承,也没说禁止其他的多继承啊。但是Java中除了类,还有什么玩意可以继承的吗?还真的有,那就是接口,接口也是可以继承的。是的,你没有听错,接口也是可以继承的。

下面我们来重新的了解一下接口到底是个什么玩意。

  • 接口中所有的方法都必须是public , abstract类型
  • 接口中的所有的属性都必须是public, static, final类型。
  • 接口中的所有的内部类(包括枚举)都必须是public, static类型。

上面是Java当中接口的一些硬性的要求,第三点或许有点儿陌生,不过不看第三点其实问题也不大。

然后下面我要该了解的就是接口继承的规则了,其实这也就是方法重写的规则。如果接口中的mSub方法,重写了父接口的mSuper接口,那么需要满足一下的条件。

  • mSub的签名是mSuper的子签名
  • mSub的返回类型是mSuper返回类型的可替换类型
  • mSub不能抛出比mSuper更多的受检异常

上面的条件其实我们早就知道了。(方法的签名就是指函数的参数的类型)

对于类的基础中的方法重写还要有几个条件,不过这些条件接口都已经默认的满足了。

  • mSub和mSuper都是实例方法。(接口中的方法都是实例方法,不可能是static)
  • mSub的访问权限不能低于mSuper。(接口中的方法全都是public的)
  • mSub继承了父类的mSuper方法。(都是public的,当然是可以继承的)

经过了上面的探讨,我们可以知道,Java中的接口是完全可以继承的,而且是可以进行多继承的。

不过既然是可以多继承的,那么我们就要来处理一下C++中的那个问题,如果有相同的方法或者属性,怎么办?

相同的属性名

interface A{
    int id = 1;
}

interface B{
    int id = 2;
}

interface C extends A, B{
    int id1 = A.id;
    int id2 = B.id;
}

public class Test2 implements A, B {
    public static void main(String[] args) {
//        System.out.println(id); 、、 Reference to 'id' is ambiguous, both A.id and B.id match 
        System.out.println(A.id);
        System.out.println(B.id);
    }
}

从上面可以看到,如果是实现了多个接口的话,同时接口中有相同的属性,我们就不可以使用简单的方式来访问这个变量,我们必须在使用的时候在前面加上类名一防止产生没有必要的歧义。

相同的方法签名

加入方法名相同,但是方法的签名相同,那么这个两个方法是完全可以分辨的。所以这种函数没有什么歧义。比如func(int)func(String)是完全没有歧义的。有歧义的就是方法签名相同的函数。

interface A {
    void func();
}

interface B {
    void func();
}

interface C extends A, B {

}

public class Test2 implements C {

    public static void main(String[] args) {
        A a = new Test2();
        a.func();

        B b = new Test2();
        b.func();
    }

    @Override
    public void func() {

    }
}

可以看到上面有相同的返回值是没有关系的,反正两个接口中都是没有实现的,所以类中实现一个就行了。使用其中的任何一个接口都可以访问到这个函数,但是两个函数是一样的。(相同名的函数,你还想实现两次,那怎么调用?)

不过如果方法的返回值不一样呢?

interface A {
    int func();
}

interface B {
    String func();
}

interface C extends A, B { // Error
}

public class Test2 implements C { // Error
}

虽然这个上面没有什么报错提示,但是我的idea上报错都满天飞了,看来返回值不相同是没有办法多继承的了。

不过真的如此吗?

interface A {
    Collection<String> func();
}

interface B {
    List<String> func();
}

interface C extends A, B {
}

public class Test2 implements C {

    public static void main(String[] args) {
        A a = new Test2();
        a.func();

        B b = new Test2();
        b.func();
    }

    @Override
    public List<String> func() {
        return null;
    }
}

可以看到,虽然方法的返回值是不一样的,但是由于List<>Collection<>的子类,所以说多继承的时候会将范围扩大,返回值变成了List<String>,这样就可以多继承啦。(List<String>同时为List<String>Collection<String>的子签名)。

由此可见,如果方法的签名相同,而且返回值还没有相同的子签名,那么这个两个接口将无法进行多继承。

接口中可以拥有非final的属性吗?

经过上面的了解,我们也知道了接口当中的所有的属性都必须要是public static final 的。原因是在于Java当中的接口是一种完全抽象的结构。是用来给多个类实现的。如果存在非final的变量,其中的一个实现修改了变量,其他的所有的实现中的该变量都将被修改,这是非常不合理的。所以说Java的中接口是对修改封闭的。禁止你修改的。

但是真的是对修改封闭了吗?

final是对值的修改封闭的。但是Java中有非常多的引用的类型。我修改引用里面的内容但是却不修改引用这完全符合final。

interface AA{
    CA ca = new CA();
}
public class Test3 implements AA{
    public static void main(String[] args) {
        System.out.println(ca.id); // 10
        ca.id = 110;
        System.out.println(ca.id); // 110
    }
}

class CA{
    public static int id = 10;
}

可见,通过这种方式,我们修改了接口中的值,(其实并没有修改,接口中保存的只是引用)就好像有了一个非final的属性。

我写这个例子并不是为了说,这样写就可以是接口拥有非final的属性,恰恰相反,我是为了防止这种情况的发生,在接口中我们尽量不要放置这种引用类型的变量。(idea才提醒我使用ca.id是个危险的行为)

总结

Java中的接口可谓的是一个非常强大的结构。算了,我也没有什么可以总结的地方了,一句话,接口牛逼就是了!!


一枚小菜鸡