【问题标题】:re-inheritance static field from class and interface从类和接口重新继承静态字段
【发布时间】:2018-10-04 13:11:36
【问题描述】:
interface A {
    public static String name = "A";
}
interface B {
    public static String name = "B";
}
class X implements A { }
class Y extends X implements B { }
public void test_getField() {
    try {
        assertEquals(B.class.getField("name"), Y.class.getField("name"));
    } catch (NoSuchFieldException e) {
        fail("Got exception");
    }
}

为什么Y.class.getField("name") 返回B.name 而不是A.name? Java规范中有答案吗?

【问题讨论】:

  • 实际上,现在我正在写我的答案,我注意到您使用了反射,因为使用 Y.name 编译任何东西都会失败,因为它是模棱两可的。这是一个很好的捕获,但它不能正确使用(没有反射)。
  • 顺便说一句,您可以在这里接受答案...meta.stackexchange.com/questions/5234/…

标签: java inheritance reflection interface superclass


【解决方案1】:

直接超接口会在超类之前搜索,见getFielddoc

要反射的字段由后面的算法决定。 设 C 为该对象所代表的类:

如果 C 声明一个具有指定名称的公共字段,那就是 要反映的字段。

如果在上述步骤 1 中未找到任何字段,则此 算法递归地应用于C 的每个直接超接口。 直接超接口按它们的顺序搜索 声明。

如果在上面的步骤 1 和 2 中没有找到任何字段,并且 C 有一个 超类 S,则此算法在 S 上递归调用。如果 C 没有超类,则抛出 NoSuchFieldException。

【讨论】:

【解决方案2】:

这里的关键点是您正在使用反射并获得意外行为。

虽然你已经有了反射算法,但我将添加一个视觉示例。

首先,使用普通访问你会得到:

class Y extends X implements B {
    public void test() {
        System.out.println(name);
    }
}

错误:(29, 36) java: 对名称的引用不明确两个变量 com.test.A 中的名称和 com.test.B 中的变量名匹配

现在考虑一个例子:

class Y extends X implements A, B {}

public class Main {
    public static void main(String[] args) throws NoSuchFieldException {
        Y y = new Y();
        System.out.println("Y : " + y.getClass().getField("name"));
    }
}

输出将是:

Y : public static final java.lang.String A.name

将签名更改为

class Y extends X implements A, B {}

给我们输出:

Y : public static final java.lang.String B.name

直接超级接口按它们的顺序搜索 声明。

进一步观察表明,实际上我们可以得到这两个名称:

class Y extends X implements A, B { }

public class Main {

    public static void main(String[] args) throws IllegalAccessException {
        Y y = new Y();
        Field[] fields = Y.class.getFields();
        for (Field field : fields) {
            System.out.println(field.get(y));
        }
    }
}

输出:

A
B

对于class Y extends X implements B { }class Y extends X implements B, A { }

B
A

在运行时解析的第一个name来自第一个实现的接口(直接超接口),因为它具有更高的优先级,然后来自第二个接口的值,然后来自超类的值。

当您调用 Y.class.getField("name") 时,您将获得第一个解析的 name,它来自第一个直接超级接口 - A

【讨论】:

  • @J-Alex 你认为这会打印什么? Arrays.stream(Y.class.getFields()) .map(Field::getName) .forEachOrdered(System.out::println);
  • @Eugene 似乎我们有 2 个名称相同但值不同的字段,但是当通过反射获取字段时,我们从第一个超级接口获取“第一个”字段。我会调整答案。
  • @J-Alex 这就是重点,你能自己在一个文件中声明两个同名的字段吗?
  • @Eugene 不,你不能。但它们不在同一个文件中。它们是通过在运行时从不同位置反射来解决的。
  • @Eugene 根据您的观察添加了更多信息。
【解决方案3】:

这是一个有趣的问题。让我们先简化一下

static class Parent {
    public int x = 1;
}

static class Child extends Parent {
    public int x = 2;
}

public static void main(String[] args) {
    Child c = new Child();
    Parent p = c;

    System.out.println(c.x);
    System.out.println(p.x);

}

你认为这会打印什么?

它将是2 1Instance 变量被继承,但它们不可覆盖。这称为隐藏,Parent 的 x 被 Child 的 x 隐藏。实例变量是继承的,但它们是不可覆盖的。这叫做隐藏,Parent的x被Child的x隐藏了。

但是第二行……我们从哪里拉 x ?我们确实有一个 Parent 引用,但它指向一个 Child 实例,对吧?就像 Child 有两个同名的不同变量;一个它声明,一个它继承。可以?

      Arrays.stream(Child.class.getFields())
            .map(Field::getName)
            .forEachOrdered(System.out::println); // x x

就像 Child 有两个完全限定的变量,根据引用,您可以访问一个或另一个。

现在来看你的例子(有点简化):

interface First {
    int x = 3;
}

interface Second {
    int x = 4;
}

static class Impl implements First, Second {}

你认为这会打印什么?

Arrays.stream(Impl.class.getFields())
            .map(Field::getName)
            .forEachOrdered(System.out::println);

和前面的例子一样,它会打印两次x,这个含义上面已经解释过了。


知道了这一点,让我们来回答你的问题(再次,有点简化):

interface First {
    int x = 1;
}

interface Second {
    int x = 2;
}

class Impl implements First, Second {

}

Field left = First.class.getField("x");
// you might think that this will fail, since Impl has two variables x... 
Field right = Impl.class.getField("x");

if (!left.equals(right)) {
        throw new AssertionError("Aren't they equal?");
}

按照现在的编写方式,这不会抛出AssertionError

这里的想法是Field#equals 使用 3 个东西来表示相等:typenamedeclaringClass。前两个显然匹配,所以我们只关心最后一个。这就是 JLS 提供帮助的地方which the other answer has already provided

这就是顺便说一句的原因,如果您更改接口声明的顺序:class Impl implements Second, First 该代码将抛出 AssertionError。因为现在,x 将从Second 解析(按照声明的顺序...),但是我们将它与First 进行比较。

【讨论】:

  • 这很有帮助:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
  • 2013-02-25
  • 2011-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-10
相关资源
最近更新 更多