Java 8默认方法的可读性

8

Java 8引入了默认方法的概念。考虑以下带有默认方法的接口:

public interface IDefaultMethod {

    public abstract void musImplementThisMethod();
    public default void mayOrMayNotImplementThisMethod() {
        System.out.println(" This method is optional for classes that implement this interface ");
    }

}

实现这个接口的类:

一个实现此接口的类:

    public class DefaultMethodImpl implements IDefaultMethod {

        @Override
        public void musImplementThisMethod() {
                System.out.println("This method must be implementd ");
        }


        @Override
        public void mayOrMayNotImplementThisMethod() {
            // TODO Auto-generated method stub
            IDefaultMethod.super.mayOrMayNotImplementThisMethod();
        }

}

我对mayOrMayNotImplementThisMethod中以下调用的可读性有疑问:

IDefaultMethod.super.mayOrMayNotImplementThisMethod();

我知道在上述调用中明确指定接口名称的原因是为了避免在类实现多个接口具有相同方法时产生混淆。但我不理解在这种情况下super关键字的含义。当我们说IDefaultMethod.super时,我们到底指的是什么?相较于IDefaultMethod.super.mayOrMayNotImplementThisMethod()IDefaultMethod.mayOrMayNotImplementThisMethod()会更易读吗?删除super关键字会使代码更易读,但代价是区分静态或非静态方法调用。

为什么这个被踩了?请添加评论。 - Chetan Kinger
命名“IDefaultMethod”是丑陋的老匈牙利标记法,在 Microsoft 成为规范后又被正确地淘汰了——除了在 C# 中的接口(由于某些难以理解的原因)。不太美观,应该避免在 Java 代码中使用。 - Per Lindberg
@PerLindberg 我非常清楚在接口名称前加上 I 应该避免的事实。在这个特定的例子中,接口的唯一目的是演示默认方法,因此显式地使用 I,因为在这个例子中接口只是一个接口,没有其他作用。 - Chetan Kinger
4个回答

7

我将尝试通过自己的推理来为讨论做出贡献。

使用类

首先,让我们看一下如何使用简单的Java类:

class Barney {
    void foo() { System.out.println("Barney says foo"); }
}

class Fred extends Barney {
    @Override void foo() { super.foo(); }
}

如果我们在Fred实例中调用foo方法,它会要求在其父类中寻找foo方法的实现并执行。

显然,这些其他方法都不起作用:

@Override void foo() { foo(); } //means this.foo() so infinite recursion
@Override void foo() { Barney.foo(); } //means a static method

我们可以进行第三种配置:

class Barney {

    void foo() { System.out.println("Barney says foo"); }

    class Fred extends Barney {
        @Override void foo() { Barney.this.foo(); }
    }

}

在这种情况下,如果我们在Fred的一个实例中调用foo,由于该实例将与其封闭实例建立联系,因此此调用将调用Barney的封闭实例中的foo方法。

例如:

new Barney().new Fred().foo();

因此,这里使用 Barney.this 来在内部/外部关系的实例之间进行导航。

使用接口

现在让我们尝试使用接口来重复相同的思想。

interface Barney {
    default void foo() { System.out.println("Barney says foo"); }
}


interface Fred extends Barney {
    @Override default void foo() { Barney.super.foo(); }
}

据我所知,这与类的情况完全相同,只是在这种情况下,由于接口可以继承多个接口,因此我们只需在此情况下使用接口名称限定“super”关键字即可。
意思是相同的,我们想要显式地调用明确指定的超级接口中的“foo”方法的“实现”。
与类一样,以下内容将不起作用:
@Override default void foo() { super.foo(); } //can't be sure of which interface
@Override default void foo() { this.foo(); } //infinite recursion
@Override default void foo() { Barney.foo(); } //static method
@Override default void foo() { Barney.this.foo(); } //not an inner class relation

因此,在这里逻辑上的选择是Interface.super.method()

这里的一个问题是,在使用接口时,是否可以像Interface.this.method这样使用。

实际上不行,因为接口代表静态上下文,因此在接口之间从来不存在类似于内部类的概念。因此,这是不可能的。

interface Barney {
    default void foo() { System.out.println("Barney says foo"); }

    interface Fred extends Barney {
            @Override default void foo() { Barney.this.foo(); }
    }
}

基本上,这是不可能的,因为上面的代码并不意味着在Barney实例的上下文中需要存在一个Fred实例。这只是一个静态内部接口,它的实例可以独立于任何父接口的实例存在。
所以,这就是为什么这不是一个好选择的原因。
所以,正如您所看到的,在所有这些之后,使用super有点说得通,或者至少我希望我已经解释得足够清楚了,传达了这个想法。

尽管它可能是最合乎逻辑的关键词,但这并不意味着它是最易读的选择。 - Chetan Kinger
@bot 嗯,那是一种观点。我无法反驳。只是出于好奇,你认为它应该怎么样呢? - Edwin Dalorzo
我认为可以省略super关键字。使用Interface.method而不是Interface.super.method更易读。这样做的代价是无法区分静态调用和非静态调用。但是,在Java中已经有许多这样的情况了。例如,考虑从同一类的实例方法调用类的静态成员方法。在看到签名之前,您不知道被调用的方法是否为静态方法。 - Chetan Kinger
好的,我可以同意在所有情况下(类、接口等)使用与关键字super不同的东西,但我永远不会同意在这种情况下做与我们在语言的其他部分所做的不同的事情。此外,我不确定你所说的“Java中已经有很多例子”是什么意思。我想不出一个例子。我知道你可以从实例调用静态方法,但相反的情况由于明显的原因是不可能的。当你提到它时,你心里有什么想法? - Edwin Dalorzo
当我从实例方法调用静态方法时,除非查看被调用方法的签名,否则您不会知道我正在调用静态方法。在Java语言中已经存在这种类型的歧义,因此如果默认方法的唯一目的是区分静态和非静态调用,那么摆脱super关键字也不会有害。 - Chetan Kinger

5
这只是默认方法的扩展,用于访问超类成员的通常方法(JLS 15.11)

T.super.Identifier形式是指与T相对应的词法封闭实例的名为Identifier的字段,但将该实例视为T的超类的实例。

基本上,当一个类有多个祖先时(无论是因为默认方法还是因为它在层次结构中具有多个超类),可能会存在关于所引用成员的歧义。 super关键字是内部类中Outer.this的类比;它意味着你想要获取“this”,但是我将看到那个超类内部的东西,而不是这个子类内部的成员。


抱歉,我仍然无法理解IDefaultMethod.super调用。虽然Outer.this意味着获取对应于内部this的Outer this,但是IDefaultMethod.super表示什么?获取IDefaultMethod接口的super吗?super的一般用法是指父类。当我说super.something时,我的意思是父类的something。阅读IDefaultMethod.super并不直观。 - Chetan Kinger
1
@bot 它的意思是“附加到IDefaultMethod的此方法的版本”,而不是“附加到AbstractDefaultMethodImpl的此方法的版本”。使用super关键字是必要的,以便将其与静态方法区分开来。 - chrylis -cautiouslyoptimistic-
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Chetan Kinger
1
我认为误解来自于您对super的定义。虽然可以宽泛地说T.super是指当前对象,但当前对象被视为当前类的超类T的实例。在这种情况下,T可以是一个接口。虽然它永远无法被实例化,但它确实具有默认方法的方法体,这些方法可以被T.super调用。因此,使用super作为关键字是正确的(也是明智的)。 - M.P. Korstanje
1
不对。如果有帮助的话,T.super 是一个逆波兰式表示法,也可以写成 super(Class<T> clazz)。它返回一个当前对象的实例,就好像它是 T 类的一个实例一样。你认为 T 总是父类,但这种看法太过严格。同时要注意:你得到的不是类型为 T 的对象,而是当前对象,就好像它是 T 类的一个实例一样。这虽然是个小细节,但却非常重要。 - M.P. Korstanje
显示剩余2条评论

1

Java 8接口也具有静态方法。

如果说, IDefaultMethod.mayOrMayNotImplementThisMethod();
那么这是调用静态方法的一种方式,因为它类似于访问类的静态成员变量。

对于默认方法,如果未使用'super',则可能会使用'this',但这没有意义,因为'this'属于我们进行方法调用的类。

我的观点是,您是正确的,因为它不提供良好的可读性,但似乎符合语言设计。


1

super是指你继承的一个类或接口。它的意思是你想调用一个方法,而忽略它已经被覆盖的事实。

如果你使用this,你会引用到这个类(或子类),从而导致无限递归。


同意。但是IDefaultMethod.super似乎不太对。对我来说,IDefaultMethod.mayOrMayNotImplementThisMethod更易读。 - Chetan Kinger
3
但是那种形式会与静态方法产生歧义。 - chrylis -cautiouslyoptimistic-
@chrylis 默认方法仅在接口中受支持。您不能在同一接口或类中具有相同名称的静态和非静态方法。那么歧义会在哪里出现呢? - Chetan Kinger
1
确切地说,这会给人一种调用静态方法的印象。静态方法无法调用静态范围之外的任何内容,而默认方法可以调用其他接口方法。这为我们提供了一个简单的抓手来理解可能被影响的内容。 - M.P. Korstanje

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接