public
Created

demonstration of API incompatibility between wildcards vs. named type parameters

  • Download Gist
APICheck.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
/*
* 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
}
*/

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.