demonstration of API incompatibility between wildcards vs. named type parameters
/* | |
* Test program to show that replacing wildcards by named type parameters, | |
* or vice versa, is not a safe change to a public API. | |
* If such a change were compatible, then the overrides below would succeed (sans "QQ"). | |
* | |
* In fact, JLS 8.4.2 (Method Signature) defines override equivalence as | |
* allowing renaming of type parameters, but not substituting type parameters | |
* for wildcards. Thus, the two kinds of type quantification are not equivalent. | |
* Think of it this way: A type parameter can be freely renamed, but only if it | |
* already has a name. | |
* | |
* JLS 8.4.8.3 (Requirements in Overriding and Hiding) defines the common-sense | |
* requirement that "near misses" are usually illegal: If two methods fail to | |
* be override-equivalent, then they must have distinct erasures, if they are | |
* to be present in the same class or interface. | |
* | |
* For non-overridable program elements such as constructors, the logical | |
* requirements are not so strict, but the fact remains that the 'Signature' | |
* attributes change (non-trivially) when wildcards are interchanged with | |
* named type parameters. Therefore, it is best to avoid such changes | |
* for any public API element. | |
* | |
* If the (private) body of the API element requires names for the | |
* wildcard types, two options are open to clean up the code. | |
* First, continue to use wildcards in the body, and use casts | |
* with @SuppressWarnings("unchecked") to force the various names | |
* to appear type-compatible. | |
* Second, refactor the body of the API element into a private | |
* subroutine, with explicit type parameters. | |
* | |
* (Corrections welcome.) | |
*/ | |
// To compile: $JAVA8_HOME/bin/javac -Xlint:all APICheck.java | |
// ASM disassembly shows the 'Signature' attributes for the test methods. | |
// Remove "QQ" below to elicit the given errors. | |
import java.util.Map; | |
class APICheck { | |
interface Super1 { | |
void test1(Map<? extends Number, ?> map); | |
// signature (Ljava/util/Map<+Ljava/lang/Number;*>;)V | |
// declaration: void test1(java.util.Map<? extends java.lang.Number, ?>) | |
} | |
interface Sub1 extends Super1 { | |
// error: name clash: <K,V>test1(Map<K,V>) in Sub1 | |
// and test1(Map<? extends Number,?>) in Super1 | |
// have the same erasure, yet neither overrides the other | |
<K extends Number, V> | |
void QQtest1(Map<K, V> map); | |
} | |
interface Super2 { | |
<K extends Number, V> | |
void test2(Map<K, V> map); | |
// signature <K:Ljava/lang/Number;V:Ljava/lang/Object;>(Ljava/util/Map<TK;TV;>;)V | |
// declaration: void test2<K extends java.lang.Number, V>(java.util.Map<K, V>) | |
} | |
interface Sub2 extends Super2 { | |
// error: name clash: test2(Map<?,? extends Number>) in Sub2 | |
// and <K,V>test2(Map<K,V>) in Super2 | |
// have the same erasure, yet neither overrides the other | |
void QQtest2(Map<?, ? extends Number> map); | |
} | |
} | |
/* | |
// class version 51.0 (51) | |
// access flags 0x600 | |
abstract interface APICheck$Super1 { | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Super1 APICheck Super1 | |
// access flags 0x401 | |
// signature (Ljava/util/Map<+Ljava/lang/Number;*>;)V | |
// declaration: void test1(java.util.Map<? extends java.lang.Number, ?>) | |
public abstract test1(Ljava/util/Map;)V | |
} | |
// class version 51.0 (51) | |
// access flags 0x600 | |
abstract interface APICheck$Sub1 implements APICheck$Super1 { | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Sub1 APICheck Sub1 | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Super1 APICheck Super1 | |
// access flags 0x401 | |
// signature <K:Ljava/lang/Number;V:Ljava/lang/Object;>(Ljava/util/Map<TK;TV;>;)V | |
// declaration: void QQtest1<K extends java.lang.Number, V>(java.util.Map<K, V>) | |
public abstract QQtest1(Ljava/util/Map;)V | |
} | |
// class version 51.0 (51) | |
// access flags 0x600 | |
abstract interface APICheck$Super2 { | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Super2 APICheck Super2 | |
// access flags 0x401 | |
// signature <K:Ljava/lang/Number;V:Ljava/lang/Object;>(Ljava/util/Map<TK;TV;>;)V | |
// declaration: void test2<K extends java.lang.Number, V>(java.util.Map<K, V>) | |
public abstract test2(Ljava/util/Map;)V | |
} | |
// class version 51.0 (51) | |
// access flags 0x600 | |
abstract interface APICheck$Sub2 implements APICheck$Super2 { | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Sub2 APICheck Sub2 | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Super2 APICheck Super2 | |
// access flags 0x401 | |
// signature (Ljava/util/Map<*+Ljava/lang/Number;>;)V | |
// declaration: void QQtest2(java.util.Map<?, ? extends java.lang.Number>) | |
public abstract QQtest2(Ljava/util/Map;)V | |
} | |
// class version 51.0 (51) | |
// access flags 0x20 | |
class APICheck { | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Sub2 APICheck Sub2 | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Super2 APICheck Super2 | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Sub1 APICheck Sub1 | |
// access flags 0x608 | |
static abstract INNERCLASS APICheck$Super1 APICheck Super1 | |
// access flags 0x0 | |
<init>()V | |
ALOAD 0 | |
INVOKESPECIAL java/lang/Object.<init> ()V | |
RETURN | |
MAXSTACK = 1 | |
MAXLOCALS = 1 | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment