Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

re-inheritance static field from class and interface

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?

like image 482
speed9 Avatar asked Apr 24 '18 10:04

speed9


People also ask

Can I inherit static method from interfaces?

Static methods in interfaces are never inherited.

Can static field be 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.

Can static fields be inherited in Java?

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.

Do subclasses inherit static variables?

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.


3 Answers

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.

like image 120
xingbin Avatar answered Oct 21 '22 07:10

xingbin


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.

like image 41
J-Alex Avatar answered Oct 21 '22 06:10

J-Alex


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 1Instance 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.

like image 2
Eugene Avatar answered Oct 21 '22 06:10

Eugene