目录 start
目录 end
|2018-04-19| 码云 | CSDN | OSChina
参考博客: Java总结篇系列:Java泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
参考博客: Java深度历险(五)——Java泛型
泛型程序设计划分为三个熟练级别 基本级别就是仅仅使用泛型类,典型的是像ArrayList这样的集合--不必考虑他们的工作方式和原因,大多数人会停留在这个级别.直到出现了什么问题. 当把不同的泛型类混合在一起的时候,或是对类型参数一无所知的遗留代码进行对接时,可能会看到含糊不清的错误消息.如果这样的话,就需要系统的进行学习Java泛型来系统地解决问题.
泛型类可以看作普通类的工厂 -- Java核心技术卷 2004(1.5)
例如该行定义 : public abstract class RoomCache<P extends PlayerBO, M extends MemberBO, V extends VideoDataBO<M>, R extends RoomBO<M, V>> extends AbstractCache<PlatformRoomId, R> {}
E
集合的元素类型K V
表示表的关键字和值的类型T U S
等就表示任意类型不同于C的泛型,C是将模板类组合出来的生成一个新的类,Java则是进行类型擦除,然后再类型强转
例如 public static <T extends Comparable> T min (T[] list)
public static Comparable min(Comparable[] list)
例如该方法签名 public static <T extends Comparable & Serializable> T getMax(T[]list)
public class Pair <T extends Comparable>{}
在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多只有一个类,如果用一个类作为限定,他必须是限定列表中的第一个
以下代码示例:涉及的类Pair在上述的代码中已经定义, Human和Student是继承关系 并且因为看的 Java核心技术卷 比较老 jdk是1.5的所以没有用7的菱形语法简化泛型 7可以省去右边的类型:
Pair<Double> pair = new Pair<>();
Pair<double>
只有Pair<Double>
Pair<T>
和Pair<String>
是等价的,因为类型擦除Pair<String> pair1
Pair<Date> pair2
pair1.getClass()和pair2.getClass()是等价的都是返回Pair.classpublic class Problem<T> extends Exception{}
public static <T extends Throwable> void doWork(){try{}catch(T t){}}
public static <T extends Throwable> void doWork() throws T{.. catch(){throw t;}}
Pair<String>[] list = new Pair<String>[10];
ArrayList<Pair<String>>
,安全又高效 Object[] array = list;
array[0] = "hi";// 编译错误
array[0] = new Pair<Date>(); //通过数组存储的检测,但实际上类型错误了,所以禁止使用参数化类型的数组
new T(){}
public Pair(){
first = new T();
second = new T();
}
first = T.class.newInstance() //非法 T.class是不合法的
//要实例化一个Pair<T>的对象就要如下:
public static <T> Pair<T> initPair(Class<T> c){
try{
return new Pair<T>(c.newInstance(), c.newInstance());
}catch (Exception e){
return null;
}
}
// 如下调用
Pair<String> pair = Pair.initPair(String.class);
// 因为Class本身是泛型, String.class其实是Class<String>的实例
// 也不能实例化为一个数组 new T[5]
private static T first; // 错误
public static T getFirst(){ // 错误
return first;
}
public class Pair<T>{
public boolean equals (T value){
return ..
}
}
泛型规范说明
class Calendar implements Comparable<Calendar>{}
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{} // 错误
class Calendar implements Comparable{}
class GregorianCalendar extends Calendar implements Comparable{}
例如 父子类: Human Student 那么 Pair
Pair 是继承(inherit)关系么,答案是否定的!!
Pair<Human> humans = new Pair<Human>(man, woman);
Pair<Student> classmates = humans;// illegal, but suppose it wasn't
classmates.setSecond(junior) // 如果上面合法,那么这里是肯定可以执行的, 因为泛型类型变成了Student
//那么就有了问题了,原有的人类类型限制的对象中,出现了小学生
//所以不允许这样的类型变量约束的类进行多态
// 但是数组可以这样写是因为数组会有自己的检查保护
Human[] humans = {man, woman};
Student[] students = humans;
students[0] = junior ;// 虚拟机将抛出 ArrayStoreException 异常
永远可以将参数化类型转换为一个原始类型, Pair
是原始类型Pair的一个子类型,转换成原始类型也会产生错误
相关测试类
Pair<Human> humans = new Pair<Human>(man, woman);
Pair other = humans;
other.setFirst(new String("wtf"))// 只是会有一个编译时的警告(类型未检查),但实际上都看得出这明显是错误的
// 那么在后续代码中继续当做Human对象进行引用,必然就会有ClassCastException
// 所以这样的写法尽量避免,这里的设计 就失去了泛型程序设计提供的附加安全性.(挖的坑)
泛型类可以扩展或实现其他的泛型类,就这一点而言,和普通类没有什么区别
通配符上限 顾名思义,就是限定为该类及其子类, 例如:
Pair<? extends Human>
表示任何Pair泛型类型并且他的类型变量要为Human的子类
例如编写一个方法
public static void printMessage(Pair<Human> human){}
正如上面所说, Pair类型的变量是不能放入这个方法的,因为泛型变量是没有继承关系, 这时候就可以使用这个通配符:
public static void printMessage(Pair<? extends Human>)
可以get不能set
Pair<Human> humans = new Pair<Human>(man, woman);
Pair<? extends Human> classmates = humans;// 编译通过
classmates.setSecond(junior) // 编译错误,泛型约束起作用了
// 分析其泛型类实现可以理解为:
? extends Human getFirst()
void setFirst(? extends Human)
// 这样的话是不可能调用setFirst方法, 对于编译器来说,只是知道入参是Human的子类,但是类型并不明确,所以不能正常调用
// 使用get方法就不会有问题, 泛型起作用了.将get返回值赋值给Human的引用也是完全合法的,这就是引入该统通配符的关键之处
通配符下限 顾名思义就是限定为父类, 通配符限定和类型变量限定十分相似, 但是可以指定一个超类型限定(supertype bound)
? super Student
这个通配符就限定为Student的所有超类型(super关键字已经十分准确的描述了这种关系)带有超类型限定的通配符的行为和前者相反,可以为方法提供参数,但不能使用返回值即 可以 set 但是不能get
// Pair<? super Student> 例如这种定义
void setFirst(? super Student)
? super Student getFirst()
// 编译器不知道setFirst方法的确切类型,但是可以用任意Student对象(或子类型) 调用他, 而不能使用Human对象调用.
// 然而,如果调用getFirst,泛型没有起作用,只能将返回值用Object接收
总结: 类定义上的泛型变量:
子类型限定: <? extends Human> 是限定了不能set,但是保证了get
超类型限定: <? super Student> 限定了不能正确get,但是保证了set.
示例1:public static <T extends Comparable<T>> T min(T[] list);
Comparable<Calendar>
Comparable<Calendar>
, 而不是Comparable
public static <T extends Comparable<? super T>> T min(T[] list)
就是安全的示例2: public static <T extends ExcelTransform> List<T> importExcel(Class<T> target)
<T extends ExcelTransform> boolean
这样写编译没报错, 那么就是说, 就是一个泛型的定义, 后面进行引用, 省的重复写public static <T> List<T> importExcel(Class<T> target)
示例3: Spring4.x 添加的泛型依赖注入 , 使用的JPA就是依赖该技术 spring学习笔记(14)——泛型依赖注入
对于应用程序员, 可能很快的学会掩盖这些声明, 想当然地认为库程序员做的都是正确的, 如果是一名库程序员, 一定要习惯于通配符
否则还要用户在代码中随意地添加强制类型转换直至可以通过编译.
TODO 对其使用场景 尚有疑问,以后再解
// 例如 Pair<?>
? getFirst() // 方法的返回值只能赋值给一个Object
void setFirst(?) // 方法不能被调用,甚至不能用Object调用.
// Pair<?> 和 Pair 本质的不同在于: 可以用任意Object对象调用原始的Pair类的setObject(set方法,因为类型擦除 入参是Object, 简称setObject)方法
TODO 学习和理解使用场景
public static void swap (Pair<?> p){
? temp = p.getFirst(); // 错误, 不允许将?作为类型
p.setFirst(p.getSecond());
p.setSecond(temp);
}
public static <T> void swapHelper(Pair<T> p){
T temp = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(temp);
}
public static void swap(Pair<?> p){swapHelper(p);}
但是下面这个通配符类型出现在计算结果中间的示例
public static void maxMinBonus(Student[] students, Pair<? super Student> result){
minMaxBonus(students, result);
swapHelper(result);
}
// 在这里,通配符捕获机制是不可避免的, 但是这种捕获只有在许多限制情况下才是合法的.
// 对于编译器而言, 必须能够确信通配符表达的是单个, 确定的类型.
官方Java7的Class文档 | 现在Class类是泛型的, 例如String.class实际上是Class
类的对象(事实上是唯一的对象)
类型参数十分有用, 这是因为他允许Class方法的返回类型更加具有针对性.下面Class 的方法就使用了类型参数
T newInstance()
T cast(Object obj)
T[] getEnumConstants()
Class<? super T> getSuperclass()
Constructor<T> getConstructor(Class... paramterTypes)
Constructor<T> getDeclaredConstructor(Class... paramterTypes)
TODO 还要继续看书
// 传入一个Class对象, 得到Class对应类型的实例
public <T> T get(Class<T> target, String name);
// 加上约束
public <T extends Runable> T get(Class<T> target, String name);