Quiz Yourself: Default Methods (Advanced)

原文期刊
您可以从接口中的默认方法访问哪些字段?

题目如下:

interface Nameable {    
    default void setName(String name) {
        this.name = name;
    }    
    default String getName() {
        return this.name;
    }
}

class Employee implements Nameable {
    protected String name;   
}

class HR {
    public static void main(String[] args) {
        Employee e = new Employee();
        e.setName("John Doe");
        System.out.println(e.getName());
    }
}

结果是什么?选一个。

接口Nameable无法编译。
类Employee无法编译。
类HR无法编译。
输出John Doe。

此代码考察的是添加到Java 8中的default方法特性。默认方法也是实例方法,不过是在接口中定义并实现的。尽管与类中定义的常规实例方法相比,这些方法的继承方式有所不同,但是这个特性在Java中创建了一种多实现接口(Multiple implementation inheritance )的形式。最初,Java中没有包含多实现,因为这个特性在用C++编写的代码中引起了许多设计问题。

为了限制多实现所造成的问题,Java采取了两个步骤。第一个步骤,为特定和有限的目的而使用该特性时,给出一个简单的警告,特别是对库接口的扩展时(有趣的是,核心API本身实际上破坏了这个指导)。第二个步骤,接口可以定义方法,但不能定义变量(除了public static final的变量,这一直都是可以的)。此外,类中声明的字段在接口中不可见(请记住,类实现接口,但接口无法知道哪些类可能实现它们);因此,实例变量对default方法是不可见的!所以,避免了多实现的真正麻烦问题,特别是“菱形继承”问题。(具体什么问题,有待后续学习)

由于上述的设计选择,尽管default方法中确实有一个this引用(他们是实例方法),唯一可以通过this引用访问的对象是接口方法(abstract和default)和接口中声明的public static final字段。不可能直接引用任何常规实例状态。(没看懂这句呢?)(当然,abstract方法的实现方法可以这样做,但是这样的代码是在类中编写的,而不是在接口中编写的。)

在这个问题中,Nameable接口中没有一个字段叫做name。因此,试图在接口中使用两个带有this.name的default方法,会导致Nameable无法编译。从这里,你知道选项A是正确的。

让我们看一下在接口中放置变量的问题。假设您添加String name到Nameable接口如下:

interface Nameable {
    String name = "John Doe";
    …
}

默认情况下,接口中的所有字段都是final(因此,在声明期间,强制性转换的),并且它们是public和static的.

添加了String name后,getName方法中声明的Nameable接口将成功编译。如下:

default String getName() { 
    return this.name; // OK
}

但是,setName方法仍然无法编译,因为final变量名称不得重新分配(不能改变):

default void setName(String name) {
    this.name = name; // NOT OK
}

因此,即使添加了String name接口中的静态字段,如果不对default方法进行更多的变更,接口仍然会编译报错。

鉴于Nameable接口无法编译,还不完全清楚其他类是否可以编译。但是,通常情况下,如果您有这样的问题,即代码的某一部分肯定由于代码中的某个特定错误而无法编译,而给定代码的其他部分本身并没有错误,并且您需要选择一个描述无法编译的代码的确切答案,那么可以安全地假设原因是与公开错误相关的。实际上,在这种情况下,类Employee和HR在所有方面都是正确的,除了取决于错误的接口Nameable。但是当然,整个代码没有编译,所以没有输出结果。从这些观察,你知道选择B,C和D是不正确的。

实际上还有一些关于之前提出的静态字段String name的讨论。首先,如果可变的StringBuilder被使用,而不是使用String。但那会是引用对象中存储的内容改变,而不是引用本身(name)改变。另一个考虑是,如前所述,这是一个static字段;只有一个name值在实现了Nameable的所有类的所有实例之间共享。实例中使用getter和setter方法,可能会得到意外的效果。(所以,不允许修改?)

正确的答案是选项A。

2020年4月25日08:54:04

今天再回忆的时候,想到,会不会是接口和类(包括抽象类)在内存中的分布有关,所以导致了接口的this只能访问到接口的接口方法(abstract和default)和接口中声明的public static final字段?
比如,类在创建实例时,对于实现的接口和继承的类是不同的处理:

接口

接口只在jvm的内存方法区中存在,this当然也只能访问当前接口的接口方法和静态字段

参考文档深入理解Java对象的创建过程:类的初始化与实例化
创建实例时,初始化时,会将父类一起初始化,存在的空间是java堆,所以调用类的this时,可以访问到子类实例的属性和方法?感觉也不行呢?后面再看吧。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值