【奇淫巧技】Java 泛型 泛型的约束与局限性

不能用基本类型实例化类型参数

不能用类型参数代替基本类型:例如,没有Pair<double>,只有Pair<Double>,其原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储double值。这体现了Java语言中基本类型的独立状态。

运行时类型查询只适用于原始类型(raw type)

运行时:通常指在Classloader装载之后,JVM执行之时

类型查询:instanceof、getClass、强制类型转换

原始类型:即(raw type),泛型类型经编译器类型擦除后是Object或泛型参数的限定类型(例如Pair<T extends Comparable>,Comparable就是T的限定类型,转化后泛型的原始类型就是Comparable,所以Pair类不带泛型是Pair<Comparable>),即Pair类含有Comparable类型的域

JVM中没有泛型

if(a instanceof Pair<String>) //ERROR,仅测试了a是否是任意类型的一个Pair,会看到编译器ERROR警告


if(a instanceof Pair<T>) //ERROR


Pair<String> p = (Pair<String>) a;//WARNING,仅测试a是否是一个Pair


Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) 
 //会得到true,因为两次调用getClass都将返回Pair.class
 //加入Java开发交流君样:756584822一起吹水聊天

不能创建参数化类型的数组(泛型数组)

参数化类型的数组:指类型带有泛型参数的数组,也即泛型数组,如Pair<T>[] 、 T[]

不能实例化参数化类型的数组,例如:

Pair<String> table = new Pair<String>[10]; //ERROR

在这里我们假设可以实例化,那么经编译器类型擦除后,table的类型是Pair[],我们再让它协变为Object[]:

Object[] objArray = table;

而一般来说,数组会记住他的元素类型Pair,我们如果试图存储其他类型的元素,就会抛出异常(数组存储检查),例如:

objArray[0] = "Hello"; //ERROR--component type is Pair

但是,对于泛型类型Pair<String>,类型擦除会使这种不同类检查机制无效,这就是不能实例化泛型数组的原因!

objArray[0] = new Pair<Employee>();  
//如果泛型机制允许我们实例化数组,那么这一步就没理由出错了!
//而这违背了我们的初衷(限定类型)

数组存储只会检查擦除后的类型,又因为Java语言设计数组可以协变,所以可以通过编译
能够通过数组存储检查,不过仍会导致一个类型错误,故不允许创建参数化类型的数组
注意,声明类型为Pair<String>[]的变量是合法的,只是不能创建这些实例(我们应该直接用new Pair<String>[10]{......}来初始化这个变量)

泛型数组的间接实现:

通过泛型数组包装器,如ArrayList类,维护一个Object数组,然后通过进出口方法set、get来限定类型和强制转换数组类型,从而间接实现泛型数组,

例如:ArrayList: ArrayList<Pair<T>>、ArrayList<T>

不能实例化类型变量T

即不能使用new T(..) , new T[..] 或 T.class这样的表达式中的类型变量
例如: public Pair() { first = new T(); } //ERROR!类型擦除将T改变成Object,调用非本意的new Object()
不能使用new T(..)
但是,可通过反射调用Class.newInstance方法来构造泛型对象(要注意表达式T.class是非法的)

public static <T> Pair<T> makePair(Class<T> cl){
    try{ return new Pair<>(cl.newInstance() , cl.newInstance()); }
    catch(Exception ex) { return null; }
}
//加入Java开发交流君样:756584822一起吹水聊天
//这个方法可以按照下列方式调用:
Pair<String> p = Pair.makePair(String.class);

注意:Class类本身是泛型。String.class是一个Class<String>的实例,因此makePair方法能够推断出pair的类型
不能使用new T[..]

解决方案:使用泛型数组包装器,例如ArrayList
然而,当在设计一个泛型数组包装器时,例如方法minmax返回一个T[]数组,则泛型数组包装器无法施展,因为类型擦除,return (T [])new Object是没有意义的强转不了。此时只好利用反射,调用Array.newInstance

import java.lang.reflect.*;
...
public static <T extends Comparable> T[] minmax(T... a){
    T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType() , 2);
...
}

【API文档描述】public Class<?> getComponentType() 返回表示数组组件类型的 Class。如果此类不表示数组类,则此方法返回 null。
而ArrayList类中的toArray方法的实现就麻烦了


public Object[] toArray() 无参,返回Object[]数组即可 
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

【API文档描述】public static <T> T[] copyOf(T[] original,int newLength)
  复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,副本将包含 null。当且仅当指定长度大于原数组的长度时,这些索引存在。所得数组和原数组属于完全相同的类。
public <T> T[] toArray(T[] a) a - 要存储列表元素的T[]数组(如果它足够大)否则分配一个具有相同运行时类型的新数组,返回该T[]数组

@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //a.getClass()得运行时目的数组的运行时类型//加入Java开发交流君样:756584822一起吹水聊天
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

【API文档描述】
public static <T,U> T[] copyOf(U[] original,int newLength, Class<? extends T[]> newType)
复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,副本将包含 null。当且仅当指定长度大于原数组的长度时,这些索引存在。所得数组属于 newType 类。
泛型类的静态上下文中类型变量无效

泛型类不能在静态域或静态方法中引用类型变量

public class Singleton<T>{
    private static T singleInstance; //ERROR
    public static T getSingleInstance(){...} //ERROR
}

类型擦除后只剩下Singleton类,因为静态所以他只包含一个singleInstance域,如果能运行则以Singleton类为模板生成不同类型的域,因此产生了冲突

不能throws或catch泛型类的实例(有关异常)

泛型类继承Throwable类不合法,如public class Problem<T> extends Exception {...}//ERROR 不能通过编译
catch子句不能使用类型变量

public static <T extends Throwable> void doWork(Class<T> t){
    try{
            do work
        }catch (T e){ // ERROR
            Logger.global.info(...)
        }
}

不过,在异常规范中使用类型变量是允许的:

public static <T extends Throwable> void doWork(T t) throws T { //此时可以throws T
    try{//加入Java开发交流君样:756584822一起吹水聊天
            do work
        }catch (Throwable realCause){ //捕获到具体实例
            t.initCause(realCause); 
            throw t; //这时候抛具体实例,所以throw t 和 throws T 是可以的!
        }
}

此特性作用:可以利用泛型类、类型擦除、SuppressWarnings标注,来消除对已检查(checked)异常的检查,
unchecked和checked异常: Java语言规范将派生于Error类或RuntimeException的所有异常称为未检查(unchecked)异常,其他的是已检查(checked)异常

  • Java异常处理原则:必须为所有已检查(checked)异常提供一个处理器,即一对一个,多对多个
 @SuppressWarnings("unchecked") 
   //SuppressWarning标注很关键,使得编译器认为T是unchecked异常从而不强迫为每一个异常提供处理器
public static <T extends Throwable> void throwAs(Throwable e) throws
   T{  //因为泛型和类型擦除,可以传递任意checked异常,例如RuntimeException类异常

    throw (T) e;
}

假设该方法放在类Block中,如果调用 Block.<RuntimeException>throwAs(t); 编译器就会认为t是一个未检查的异常

public abstract class Block{
    public abstract void body() throws Exception;
    public Thread toThread(){
        return new Thread(){
                        public void run(){
                            try{
                                 body();
                            }catch(Throwable t){
                                 Block.<RuntimeException>throwAs(t);
                            }//加入Java开发交流君样:756584822一起吹水聊天
                        }
                    };
    }

    @SuppressWarnings("unchecked")
    public static <T extends Throwable> void throwAs(Throwable e) throws T{
    throw (T) e ;
    }
}

再写个测试类

public class Test{
    public static void main(String[] args){
        new Block(){
            public void body() throws Exception{
                //不存在ixenos文件将产生IOException,checked异常!
                Scanner in = new Scanner(new File("ixenos"));
                while(in.hasNext())
                    System.out.println(in.next());
            }//加入Java开发交流君样:756584822一起吹水聊天
        }.toThread().start();
    }
}
  • 启动线程后,throwAs方法将捕获线程run方法所有checked异常,“处理”成unchecked
    Exception(其实只是骗了编译器)后抛出;

有什么意义?正常情况下,因为run()方法声明为不抛出任何checked异常,所以必须捕获所有checked异常并“包装”到未检查的异常中;意义:而我们这样处理后,就不必去捕获所有并包装到unchecked异常中,我们只是抛出异常并“哄骗”了编译器而已
注意擦除后的冲突

Java泛型规范有个原则:“要想支持擦除的转换,就需要强行限制一个泛型类或类型变量T不能同时成为两个接口类型的子类,而这两个接口是统一接口的不同参数化”
注意:非泛型类可以同时实现同一接口,毕竟没有泛型,很好处理

class Calender implements Comparable<Calender>{...}

class GGCalender extends Calender implements Comparable<GGCalender>{...} //ERROR

在这里GGCalender类会同时实现Comparable<Calender> 和 Comparable<GGCalender>,这是同一接口的不同参数化

image

最新2020整理收集的一些高频面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法等详细讲解,也有详细的学习规划图,面试题整理等,需要获取这些内容的朋友请加Q君样:756584822

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容