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中没有泛型
eg:

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

不能创建参数化类型的数组(泛型数组)
参数化类型的数组:指类型带有泛型参数的数组,也即泛型数组,如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; }
} //这个方法可以按照下列方式调用:
Pair<String> p = Pair.makePair(String.class);
- 注意:Class类本身是泛型。String.class是一个Class<String>的实例,因此makePair方法能够推断出pair的类型
- 不能使用new T[..]
- 即不能(直接)创建泛型数组,参考我另一篇博文(http://www.cnblogs.com/ixenos/p/5648519.html),这里不再赘述
- 解决方案:使用泛型数组包装器,例如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()得运行时目的数组的运行时类型
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 类。
- 【API文档描述】
- public Object[] toArray() 无参,返回Object[]数组即可
- 而ArrayList类中的toArray方法的实现就麻烦了
泛型类的静态上下文中类型变量无效
- 泛型类不能在静态域或静态方法中引用类型变量
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{
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);
}
}
};
} @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());
}
}.toThread().start();
}
}
- 启动线程后,throwAs方法将捕获线程run方法所有checked异常,“处理”成unchecked Exception(其实只是骗了编译器)后抛出;
- 有什么意义?正常情况下,因为run()方法声明为不抛出任何checked异常,所以必须捕获所有checked异常并“包装”到未检查的异常中;意义:而我们这样处理后,就不必去捕获所有并包装到unchecked异常中,我们只是抛出异常并“哄骗”了编译器而已
- 启动线程后,throwAs方法将捕获线程run方法所有checked异常,“处理”成unchecked Exception(其实只是骗了编译器)后抛出;
- 此特性作用:可以利用泛型类、类型擦除、SuppressWarnings标注,来消除对已检查(checked)异常的检查,
注意擦除后的冲突
- Java泛型规范有个原则:“要想支持擦除的转换,就需要强行限制一个泛型类或类型变量T不能同时成为两个接口类型的子类,而这两个接口是统一接口的不同参数化”
- 注意:非泛型类可以同时实现同一接口,毕竟没有泛型,很好处理
class Calender implements Comparable<Calender>{...} class GGCalender extends Calender implements Comparable<GGCalender>{...} //ERROR- 在这里GGCalender类会同时实现Comparable<Calender> 和 Comparable<GGCalender>,这是同一接口的不同参数化
Java的泛型约束和限制的更多相关文章
- 【JAVA之泛型】
一.引例. 1.引例. 假设现在有一个ArrayList的容器,如果不使用泛型约束,则可以向容器中加入各种类型的对象,但是如果取出来的时候只是用一种类型的转换则肯定会抛出ClassCastExcept ...
- Java中泛型 类型擦除
转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...
- 【Java】泛型学习笔记
参考书籍 <Java核心技术:卷1> 泛型, 先睹为快 先通过一个简单的例子说明下Java中泛型的用法: 泛型的基本形式类似于模板, 通过一个类型参数T, 你可以"私人定制&qu ...
- Java“禁止”泛型数组
Java“禁止”泛型数组 原文:https://blog.csdn.net/yi_Afly/article/details/52058708 1. 泛型定义泛型编程是一种通过参数化的方式将数据处理与数 ...
- C++ Java C#泛型
泛型概述C#中的泛型C#泛型和java泛型的比较C#泛型和C++模板的比较C#泛型中的约束 泛型概述 Bruce Eckel :您能对泛型做一个快速的介绍么? Anders Hejlsberg : 泛 ...
- java基础-泛型举例详解
泛型 泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数.这种类型参数可以在类.接口.和方法的创建中,分别被称为泛型类.泛型接口.泛型方法. 一.认识泛型 在没 ...
- Kotlin星投影与泛型约束详解
星投影(star projection): 继续来学习Kotlin泛型相关的东东,星投影(star projection),这是个啥东东呢?下面先来说一下概念: 1.对于Star<out T&g ...
- Java基础 - 泛型详解
2022-03-24 09:55:06 @GhostFace 泛型 什么是泛型? 来自博客 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了&quo ...
- 06.C#泛型约束和高级泛型(三章3.3-3.4)
吃午饭前继上篇泛型再写一篇关于泛型的文章,虽然总是被博客园移出首页,文章不精确实是大问题啊,会再接再厉的.进入正题. 先来说下泛型约束.当我们在使用泛型的时候通常会希望使用泛型实参时,参数能具备某一些 ...
随机推荐
- BZOJ 4821 [Sdoi2017]相关分析 ——线段树
打开题面,看到许多$\sum$ woc,好神啊,SDOI好强啊 然后展开之后,woc,SDOI好弱啊,怎么T3出个线段树裸题啊. 最后写代码的时候,woc,SDOI怎么出个这么码农的题啊,怎么调啊. ...
- 刷题总结:排序机械臂(石室中学oj)(splay)
题目: 题目描述 为了把工厂中高低不等的物品按从低到高排好序,工程师发明了一种排序机械臂.它遵循一个简单的排序规则,第一次操作找到最低的物品位置 P1,并把从左起第 1 个至第 P1 个之间的物品反序 ...
- Eclipse + Jersey 发布RESTful WebService(一)了解Maven和Jersey,创建一个WS项目(成功!)
一.下文中需要的资源地址汇总 Maven Apache Maven网站 http://maven.apache.org/ Maven下载地址: http://maven.apache.org/down ...
- Java面试题之继承、组合、聚合有什么区别
继承:他是is-a的关系,指一个类继承另外一个类的功能 例如:public class A extends B { } 聚合:他是has-a 例如:public class A{ List<B& ...
- APUE 学习笔记(六) 进程控制
1. fork 创建新进程 fork创建的新进程称为子进程,fork函数调用一次,返回两次. 两次返回的唯一区别就是子进程的返回值是0,而父进程的返回值是新子进程的进程ID 在fork之后是父进程先执 ...
- Mysql之Handler_read%
纯属自己理解,如有误导概不负责O(∩_∩)O 加索引: mysql> flush status; Query OK, rows affected (0.00 sec) mysql> flu ...
- linux内核学习之四:进程切换简述【转】
转自:http://www.cnblogs.com/xiongyuanxiong/p/3531884.html 在讲述专业知识前,先讲讲我学习linux内核使用的入门书籍:<深入理解linux内 ...
- grep用法详解:grep与正则表达式【转】
转自:http://blog.csdn.net/hellochenlian/article/details/34088179 grep用法详解:grep与正则表达式 首先要记住的是: 正则表达式与通配 ...
- NGINX白名单功能,ngx_http_limit_conn_module和ngx_http_limit_req_module值设置多少才合适呀?
要根据不同的应用慢慢学习测试? 我现在设置的10左右,看看再说吧... #增加限制规则,如果不能正常访问,则需要调节这两个值 -- #增加ip白名单功能 geo $whiteiplist { defa ...
- PythonWeb开发教程(二),搭建第一个django项目
这篇写怎么创建django项目,以及把django项目运行起来. 1.创建django项目 a.使用命令创建,安装完django之后就有django-admin命令了,执行命令创建即可,命令如下: ...