java遗珠之泛型类型擦除(代码片段)

吴冬冬 吴冬冬     2022-12-01     284

关键词:

擦除规则

泛型的作用之前已经介绍过了只是用于编译之前更为严格的类型检查,其他的一些特性也都是编译之前的,在编译之后泛型是会被擦除掉的。

类型擦除所做的事情如下:

  1. 如果是无界限的则会把类型参数替换成Object,如果是有界限的则会把类型参数替换为界限类型。
  2. 插入一些类型转换来保证类型安全
  3. 为了保证从泛型类型继承的多态性会增加一些桥接方法。

泛型类型的擦除

无边界类型擦除

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; 
    // ...

按照规则第一条T是无界的,会用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; 
    // ...

有边界类型擦除

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; 
    // ...

有界限的会替换成边界类型

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; 
    // ...

泛型方法的类型擦除

无边界类型擦除

public static <T> int count(T[] anArray, T elem) 
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;

和泛型类型同理,擦除如下:

public static <T> int count(T[] anArray, T elem) 
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;

有边界类型擦除

class Shape  /* ... */ 
class Circle extends Shape  /* ... */ 
class Rectangle extends Shape  /* ... */ 
public class Util 
    public static <T extends Shape> void draw(T shape)  /* ... */ 

类型擦除桥接方法

想想下面那个类的擦除

public class ANode<T> 

    public T data;

    public ANode(T data)  this.data = data; 

    public void setData(T data) 
        System.out.println("Node.setData");
        this.data = data;
    

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

会被擦除成

public class ANode 

    private Object data;

    public ANode(Object data)  this.data = data; 

    public void setData(Object data) 
        System.out.println("Node.setData");
        this.data = data;
    

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

然后再看它的子类

public class MyNode extends ANode<Integer> 
    public MyNode(Integer data) 
        super(data);
    
    
    public void setData(Integer data) 
        System.out.println("MyNode.setData");
        super.setData(data);
    

擦除之后

public class MyNode extends ANode 
    public MyNode(Integer data) 
        super(data);
    
    
    public void setData(Integer data) 
        System.out.println("MyNode.setData");
        super.setData(data);
    

这就有意思了我们在main方法里调用如下:

    public static void main(String[] args) 
        ANode n = new MyNode(5);
        n.setData("Hello");
        Integer x = (Integer) n.getData();    // Causes a ClassCastException to be thrown.
    

如果只看擦除前的代码,setData("Hello")调用的应该是子类的方法,参数不应该是String才对。

但是代码肯定是经过擦除的,从这个角度来说

  1. setData("Hello")调用的参数其实Object,是父类的方法,因为子类的参数是Integer。
  2. 然后getData()返回的是Object类型,但指向的还是String类型,强转依然会报错。

嗯,到目前为止只是我们期望的,那么一运行,蒙了,直接在setData("Hello")就报错了。

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at com.sweetop.studycore.generic.MyNode.main(MyNode.java:18)

这是因为为了保证泛型类型擦除后依然具有多态性,编译器在编译的时候自动增加了一个桥接方法,因此子类擦除后应该是这样的

public class MyNode extends ANode 
    public MyNode(Integer data) 
        super(data);
    
    
    public void setData(Object data) 
        setData((Integer) data);
    
    
    public void setData(Integer data) 
        System.out.println("MyNode.setData");
        super.setData(data);
    

如果是这样的话,那么子类就重写了父类的方法,调用的其实还是子类的setData(Object data).

因此就看到了上面那个错误。

之后碰到继承泛型类型的时候,一定要注意这种问题。最好在使用之前先做下类型判断

 public static void main(String[] args) 
        ANode n = new MyNode(5);
        Object a = "1";
        if (a instanceof Integer) 
            n.setData("1");
        
        Integer x = (Integer) n.getData();    // Causes a ClassCastException to be thrown.

java遗珠之泛型不可靠类型(代码片段)

不可靠类型可靠类型是在运行时包含所有完整信息的类型,包括原始类型,非泛型类型,原生类型和无边界通配符的调用。不可靠类型是编译时类型擦除移除了一些信息,比如不是无边界通配符的其他情况。一个... 查看详情

java遗珠之泛型七大限制(代码片段)

不能使用原始类型实例化泛型类型参数化类型如下:publicclassOrderedPair<K,V>implementsPair<K,V>privateKkey;privateVvalue;publicOrderedPair(Kkey,Vvalue)this.key=key;this.value=value;publicKgetKey()ret 查看详情

java遗珠之泛型的作用(代码片段)

泛型总共有三个作用编译时进行更强大的类型检查编译时错误比运行时错误更好发现和处理消除类型转换Listlist=newArrayList();list.add("hello");Strings=(String)list.get(0);使用泛型之后去掉转换List<String>list=newArrayList<Str... 查看详情

java遗珠之泛型类型推断(代码片段)

类型推断类型推断是java编译器的一种能力,通过查看方法调用和相应的声明来决定什么样的类型参数或者参数是更为合理的调用。推断算法先确定参数的类型,分配结果或者返回的类型,最终推断算法查找适合所有... 查看详情

java遗珠之泛型多边界(代码片段)

泛型的类型参数可以有多个边界<TextendsB1&B2&B3>当其中一个边界是class的时候需要写在前面。classA/*...*/interfaceB/*...*/interfaceC/*...*/publicclassD<TextendsA&B&C>/*...*/写在后面的话会有编译错误classD<TextendsB& 查看详情

java遗珠之泛型继承(代码片段)

当给定两个具体的类型A和B,MyClass<A>和MyClass<B>没有任何关系,不管A和B是什么关系。你可以通过扩展泛型类或者实现泛型接口的interfacePayloadList<E,P>extendsList<E>voidsetPayload(intindex,Pval);//...以下参数化的Payloa... 查看详情

java遗珠之泛型七大限制(代码片段)

不能使用原始类型实例化泛型类型参数化类型如下:publicclassOrderedPair<K,V>implementsPair<K,V>privateKkey;privateVvalue;publicOrderedPair(Kkey,Vvalue)this.key=key;this.value=value;publicKgetKey()returnkey;publicVgetValue()returnvalue;publicvoidsetKey(Kke... 查看详情

java遗珠之泛型通配符(代码片段)

通配符我们在之前说过明确指定了泛型类型的参数之后,会把参数限制的很严格,通配符的作用就是放宽这种限制,有上限有界通配符<?extendsA>,下限有界通配符<?extendsB>,无界通配符<?>。虽然通配符可以使... 查看详情

java遗珠之泛型通配符(代码片段)

通配符我们在之前说过明确指定了泛型类型的参数之后,会把参数限制的很严格,通配符的作用就是放宽这种限制,有上限有界通配符<?extendsA>,下限有界通配符<?extendsB>,无界通配符<?>。虽然通配符可以使... 查看详情

java遗珠之@safevarargs(代码片段)

当使用泛型作为可变参数时,因为可变参数是数组类型,在编译之后泛型会被擦出掉,那么传入参数就成了unchecked的警告:如下例子publicclassVarargsWaringprivatestaticList<String>useVarargs(List<String>...args)returnargs.len... 查看详情

java语法糖之泛型与类型擦除(代码片段)

1泛型与类型擦除泛型,JDK1.5新特性,本质是参数化类型(ParametersizedType)的应用,即所操作的数据类型被指定为一个参数。这种参数类型可用在:类接口方法的创建中,分别称为:泛型类泛型接口泛型方法在Java还... 查看详情

java之泛型擦除

泛型擦除  在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定泛型参数,则该类型参数被称作一个原始... 查看详情

java遗珠之lambda与方法重载(代码片段)

当lambda的目标类型不是很明确时,就需要根据一些特征来判断lambda的目标类型,比较常见的就是在方法重载的时候。与接口中的方法名无关,即使不同名,仍会同时匹配到两个接口publicclassOverloadLambdainterfaceRunablevo... 查看详情

java遗珠之多异常(代码片段)

catch可以同时捕获多个异常,示例如下:publicvoidwriteList()//TheFileWriterconstructorthrowsIOException,whichmustbecaught.PrintWriterout=null;trySystem.out.println("Enteredtrystatement");out&# 查看详情

java遗珠之多异常(代码片段)

catch可以同时捕获多个异常,示例如下:publicvoidwriteList()//TheFileWriterconstructorthrowsIOException,whichmustbecaught.PrintWriterout=null;trySystem.out.println("Enteredtrystatement");out&# 查看详情

java遗珠之接口方法(代码片段)

接口中的默认方法和抽象方法像示例方法一样可以被继承,当类的超类或者接口提供多个相同签名的默认方式时,java编译器就会用继承规则来解决命名冲突,这些规则遵循两个原则。实例方法优先于接口默认方法看... 查看详情

java遗珠之异常种类(代码片段)

异常总共分三种已检查异常,比如java.io.FileNotFoundException错误,java.io.IOError运行时异常,NullPointerException错误和运行时异常又统称为未检查异常已检查异常必须被处理或者throws,未检查异常不需要。但要注意的是未... 查看详情

java遗珠之变强了的comparator(代码片段)

一个更为强大的Comparator,可以说再也不用写侵入式的Comparable接口,而且连Comparator的lambda都可以不写了。我先来看需要排序的类:publicclassPersonpublicenumSexMALE,FEMALEprivateStringname;privateLocalDatebirthday;privateSexgend 查看详情