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");
}
}
Why does Y.class.getField("name")
return B.name
instead of A.name
?
Is there any answer in the Java specification?
Static methods in interfaces are never inherited.
Static classes are sealed and therefore cannot be inherited. They cannot inherit from any class except Object. Static classes cannot contain an instance constructor. However, they can contain a static constructor.
Static method can be inherited similar to normal methods, however unlike normal methods it is impossible to create "abstract" methods in order to force static method overriding.
Only members of a class that are declared protected or public are inherited by subclasses declared in a package other than the one in which the class is declared. Constructors, static initializers, and instance initializers are not members and therefore are not inherited.
The direct superinterface will be searched before superclass, see the doc of getField
:
The field to be reflected is determined by the algorithm that follows. Let C be the class represented by this object:
If C declares a public field with the name specified, that is the field to be reflected.
If no field was found in step 1 above, this algorithm is applied recursively to each direct superinterface of C. The direct superinterfaces are searched in the order they were declared.
If no field was found in steps 1 and 2 above, and C has a superclass S, then this algorithm is invoked recursively upon S. If C has no superclass, then a NoSuchFieldException is thrown.
The key point here is that you're using reflection and getting unexpected behavior.
Although you already have the reflection algorithm, I will add a visual example.
First of all, using normal access you will get:
class Y extends X implements B {
public void test() {
System.out.println(name);
}
}
Error:(29, 36) java: reference to name is ambiguous both variable name in com.test.A and variable name in com.test.B match
Now considering an example:
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"));
}
}
The output will be:
Y : public static final java.lang.String A.name
Changing signature to
class Y extends X implements A, B {}
Gives us output:
Y : public static final java.lang.String B.name
The direct superinterfaces are searched in the order they were declared.
Further observation shows that actually we can get both names like:
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));
}
}
}
Output:
A
B
And for both class Y extends X implements B { }
and class Y extends X implements B, A { }
:
B
A
The first name
resolved in runtime comes from first implemented interface (direct superinterface) because it has higher precedence, then comes the value from the second interface, then the value from the superclass.
When you calling Y.class.getField("name")
you're getting the first resolved name
which comes from first direct superinterface - A
.
This is an interesting question. Let's first simplify this a bit
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);
}
What do you think this will print?
It will be 2 1
Instance variables are inherited, but they are not overridable. This is called hiding, Parent’s x is hidden by Child’s x. Instance variables are inherited, but they are not overridable. This is called hiding, Parent’s x is hidden by Child’s x.
The second line though… Where are we pulling x from? We do have a Parent reference, but it’s pointing to a Child instance, right? It’s like Child has two different variables with the same name; one it declares, one it inherits. Does it?
Arrays.stream(Child.class.getFields())
.map(Field::getName)
.forEachOrdered(System.out::println); // x x
It’s like Child has two fully qualified variables and depending on the reference you can access one or the other.
Now to your example (a bit simplified):
interface First {
int x = 3;
}
interface Second {
int x = 4;
}
static class Impl implements First, Second {}
What do you think this will print?
Arrays.stream(Impl.class.getFields())
.map(Field::getName)
.forEachOrdered(System.out::println);
As in the previous example, it will print x twice, the meaning of this is explained above.
Knowing this, let's go to your question (again, a bit simplified):
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?");
}
As the way this is written right now, this will NOT throw the AssertionError
.
The idea here is that Field#equals
uses 3 things for equality: type
, name
and declaringClass
. The first two obviously match, so all we care about is the last one. And this is where the JLS comes at help which the other answer has already provided.
That is the reason btw, that if you change the order of interface declarations : class Impl implements Second, First
that code will throw the AssertionError. Because now, x will be resolved from Second
(in the order of the declaration... ), but we compare it with First
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With