简体   繁体   中英

Why can a Java generic class implement a method of a generic interface with Object parameter?

Here's the Java code:

public interface myInterface<T> {
    void doSomething(T yes);
}

private static class myInterfaceImpl<T> implements myInterface<T>{
    @Override
    public void doSomething(Object yes) {

    }
}

This thing does compile even though I think the class doesn't override the method from the interface. By type inference I would assume that the type parameter T is always equal to Object since the class implements the method from interface where it clearly states Object as a parameter. The version with (T yes) in overriding method also works, but that is obvious to me. Can you please tell me why is that the case with the example I presented?

Thank you.

This is where type erasure, the mechanism used by Java for backwards compatibility when introducing generics in JDK 1.5, makes things a little counter-intuitive.

Normally one would expect that the signature of an overriding method must match exactly. That is not quite the case. While matching a signature exactly will certainly override the method, the erasure of the signature will also override the method. The JLS, Section 8.4.8.1 , states:

An instance method m C declared in or inherited by class C, overrides from C another method m A declared in class A, iff all of the following are true:

--snip

The signature of m C is a subsignature (§8.4.2) of the signature of m A .

And section 8.4.2 states:

The signature of a method m 1 is a subsignature of the signature of a method m 2 if either:

  • m 2 has the same signature as m 1 , or

  • the signature of m 1 is the same as the erasure (§4.6) of the signature of m 2 .

Because the erasure of T is Object , it is allowed to override (here implement) the method with a method that takes Object . If you place an upper bound on T in the interface, then you can no longer override the method with Object ; it must be that upper bound, which is the erasure.

// Example of erasure with upper bound
interface myInterface<T extends Number> {
    void doSomething(T yes);
}

class myInterfaceImpl<T extends Number> implements myInterface<T>{
    @Override
    public void doSomething(Number yes) {

    }
}

Note that parameter contravariance is not allowed here. With an upper bound, you cannot override doSomething with a method taking a supertype of the parameter type, eg Object .

// A compiler error occurs here when the erasure is Number.
@Override
public void doSomething(Object yes) {

}

So you stumbled upon Covariance and Contravariance. Lets say you have:

IMyInterface<T> {
  void foo(T o);
}

and you implement

class A implements IMyInterface<Integer> {
  void foo(Integer o) { ... }
}

you could also do either write:

class A implements IMyInterface<Integer> {
  void foo(Number o) { ... }
}

class A implements IMyInterface<Integer> {
  void foo(Object o) { ... }
}

since the param o of type T is guaranteed to be Integer -> o is also guaranteed to be Number or Object -> because Integer extends Number extends Object

so you are weakening the type! This is what your code does.

I have probably found the answer :

During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.

Consider the following generic class that represents a node in a singly linked list:

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

Because the type parameter T is unbounded, the Java compiler replaces it with Object:

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

In the following example, the generic Node class uses a bounded type parameter:

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

The Java compiler replaces the bounded type parameter T with the first bound class, Comparable:

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}

Thanks @Oleksandr for that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM