一、泛型
JDK1.5
增加泛型支持在很大程度上都是为了让集合能够记住其元素的数据类型。在没有泛型之前,一旦把一个对象丢进java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。这样取出来使用时,往往需要强制转换,效率降低。
1.1 概念
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参在声明变量、创建对象、调用方法时动态地指定。
从java5开始,java引入了参数化类型(parameterized type)
,运行程序在创建集合时指定集合元素的类型。java的参数化类型也被称为泛型。
1.1 使用泛型的好处:
- 安全: 在编译的时候检查类型安全。
- 省心: 所有的强制转换都是自动和隐式的,提高代码的重用率。
1.2 泛型的基本用法
使用泛型,可以在编译时进行类型转换检查,而不使用泛型,在运行时才检查
使用泛型,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。
1.2泛型字母
1.3 java7泛型的菱形语法
在java7以前,泛型的格式:
List<String> strList = new ArrayList<String>();
在java7开始,可以使用下面的方式:
List<String> strList = new ArrayList<>();
java可以推断尖括号里应该是什么类型
1.4 构造器注意事项
当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来 的类名,而不要增加泛型声明。例如,为Apple<T>
类定义构造器,其构造器名依然是Apple
,而不是Apple<T>
, 调用该构造器时却可以使用 Apple<T>
,其中的T
为实际传入的类型参数。
正如泛型方法允许在方法签名中声明类型形参一样,Java也允许在构造器签名中声明类型形参。
class Foo {
public <T> Foo(T t) {
System.out.println(t)
}
}
二、内部原理及更深应用
泛型是提供给javac编译器
使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。编译器编译带类型说明的集合时会去除类型
信息。对于参数化的泛型类型,getClass()
方法的返回值和原始类型的完全一样。由于编译后会去掉泛型的类型信息,只要跳过编译器,就可以往某个泛型集合中加入其它类型的数据。注意,在元数据中会保留泛型信息,可通过反射获取这些泛型信息。
2.1 泛型有继承性吗
在使用泛型时,一定要明确不存在泛型类,不存在泛型的父子关系等等。例如。List<String>
对象不能被当做List<Object>
对象使用。List<String>
类并不是List<Object>
子类。
数组和泛型有所不同,假设Foo是Bar的一个子类型,那么Foo[] 依然是Bar[]的子类型,但是G<Foo>
不是G<bar>
的子类型。
2.2 泛型反射
明白泛型的原理后,2.1的问题就不攻自破。
例如,用反射得到集合,再调用其add()方法添加元素。
getClass()
获取的到的字节码是同一份。编译完成后就没有泛型信息了
可以通过反射拿到去到泛型的集合,然后添加元素,这样就跳过了泛型检查
(运行时已经没有泛型相关的信息了)
2.3ArrayList<E>
类定义和`ArrayList类引用中涉及如下术语
- 整个成为
ArrayList<E>
泛型类型 ArrayList<E>
中的E称为类型变量或类型参数- 整个
ArrayList<Integer>
称为参数的类型。 ArrayList<Integer>
中的Integer称为类型参数的实例或实际类型参数ArrayList<Integer>
中的<>
读typeofArrayList
称为原始类型
2.4 参数化类型与原始类型的兼容性
没有泛型数组。在创建数组实例时,数组的元素不能使用参数化类型。例如下面的语句就是错误的
vector<Integer> vectorList[] = new Vector<Integer>[10];
2.5 思考题
下面两种为兼容性都是可行的:
把一个参数化类型给原始类型;
把一个原始类型给参数类型;
编译器是通过一行一行的编译代码
注意
:编译器是一行一行的进行代码检查。
所以上面是不会报错的。
三、泛型的通配符扩展应用
可以向 printCollection(<Collection<Object>cols
) 中使用cols.add("string")
;原因是 字符串类型属于Object
但是,cols = new HashSet<Date>()
会报错。带通配符的集合仅表示是各种泛型对应集合的,但是并不能将元素加入,原因是集合的类型未知。
3.1 使用通配符?
带来的问题
3.2 通配符的扩展
通配符的上边界和下边界,也同样不能对该集合进行有关类型的操作,因为并不知道具体的子类和具体的父类。如,试图使用add操作,将会报错。
程序可以设置多个上限(至多一个父类上限、可以有多个接口上限),表明该类型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。例如:
//表明T类型必须是Number类或其子类。并实现java.io.Serializable接口。
public class Apple<T extends Number & java.io.Serializable> {
}
与类同时继承父类、实现接口类似的是,为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。
3.3 泛型方法与方法重载
因为泛型既允许设定通配符的上限,也允许设定通配符的下线,从而允许在一个类里面包含如下两个方法:
public class MyUtils {
public static <T> void copy(Collection<T> dest,Collection<? extends T> scr) {
……
}
public static <T> void copy(Collection<? super T> dest,Collection<T> scr) {
……
}
}
虽然定义的时候能够通过,但是在调用的时候,因为不能确定唯一,所以会出现编译错误。例如
List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
copy(ln,li);//编译错误,不知道选择哪一个。
四、泛型集合的应用
HashMap<String,Integer> maps = new HashMap<String, Integer>();
maps.put("zxx", 28);
maps.put("lhm", 35);
maps.put("flx", 33);
Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();
for(Map.Entry<String, Integer> entry : entrySet){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
不能对Map
进行直接的迭代,没有实现Iterable
接口
给编译器执行类型检查和类型推断
4.1 类型推断
类型推断,取最小公倍数
4.2 泛型中不允许使用基本类型
使用基本类型会报错。 泛型中类型只能是引用类型,不能是基本类型
其中T
不能被int
替换。只有引用类型才能做泛型的方法的实际参数
4.3 泛型方法
4.4 异常如何使用泛型
4.5 从泛型类派生子类
当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类、或从该父类派生子类,需要指出的是,当使用这些接口、父类是不能再包含类型形参。
//Apple类不能再跟类型形参。如果要使用泛型,则需要指定具体的实际参数
public class A extends Apple<T> {}
//指定了具体的参数,可以。
public class A extends Apple<String> {}
//或者泛型擦除
public class A extends Apple {}
五、自定义泛型方法的练习与类型推断总结
案例: 编写一个泛型方法,自动将Object类型转换为其他类型
private static <T> T autoConvert(Object obj){
return (T)obj;
}
案例二 定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
private static <T> void fillArray(T[] a,T obj){
for(int i=0;i<a.length;i++){
a[i] = obj;
}
}
(使用dest 和 src 做变量,非常清晰,在自己的命名中也可以借鉴)
5.1 类型参数的类型推断
六 自定义泛型类的应用
泛型方法, 需要在方法的返回值之前和所有修饰符之后设置类型,在类上定义就只在类后面设置类型
接口或者类上的泛型字母只能使用在普通方法上,不允许使用在静态方法上,以及常量中。
6.1 使用约束
- 泛型在使用时指定类型,不过只能是引用类型,不能是基本类型
- 泛型在声明时字母不能使用静态属性,静态方法上,(原因是泛型是在使用时确定,而静态属性和方法在编译时确定)。如果要在静态方法上使用泛型,则不能与使用与类或者接口后面的泛型。
- 接口泛型,不能使用在全局常量上,只能使用在方法中。
- 泛型方法,在修饰符的后面,返回值得前面。
七、 泛型的擦除
当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都被扔掉。比如把List<String>
类型转换成了List
7.1 继承
package com.bjsxt.gen03;
/**
* 父类为泛型类
* 1、属性
* 2、方法
*
* 要么同时擦除,要么子类大于等于父类的类型,
* 不能子类擦除,父类泛型
* 1、属性类型
* 父类中,随父类而定
* 子类中,随子类而定
* 2、方法重写:
* 随父类而定
* @param <T>
*/
public abstract class Father<T,T1> {
T name;
public abstract void test(T t);
}
/**
* 子类声明时指定具体类型
* 属性类型为具体类型
* 方法同理
*/
class Child1 extends Father<String,Integer>{
String t2;
@Override
public void test(String t) {
}
}
/**
* 子类为泛型类 ,类型在使用时确定
*/
class Child2<T1,T,T3> extends Father<T,T1>{
T1 t2;
@Override
public void test(T t) {
}
}
/**
* 子类为泛型类,父类不指定类型 ,泛型的擦除,使用Object替换
*/
class Child3<T1,T2> extends Father{
T1 name2;
@Override
public void test(Object t) {
// TODO Auto-generated method stub
}
}
/**
* 子类与父类同时擦除
*/
class Child4 extends Father{
String name;
@Override
public void test(Object t) {
}
}
/**
*错误:子类擦除,父类使用泛型
class Child5 extends Father<T,T1>{
String name;
@Override
public void test(T t) {
}
*/
7.2 接口
package com.bjsxt.gen03;
/**
* 泛型接口:与继承同理
* 重写方法随父类而定
*
*/
public interface Comparable<T> {
void compare(T t);
}
//声明子类指定具体类型
class Comp implements Comparable<Integer>{
@Override
public void compare(Integer t) {
// TODO Auto-generated method stub
}
}
//擦除
class Comp1 implements Comparable{
@Override
public void compare(Object t) {
// TODO Auto-generated method stub
}
}
//父类擦除,子类泛型
class Comp2<T> implements Comparable{
@Override
public void compare(Object t) {
// TODO Auto-generated method stub
}
}
//子类泛型>=父类泛型
class Comp3<T> implements Comparable<T>{
@Override
public void compare(T t) {
// TODO Auto-generated method stub
}
}
//父类泛型,子类擦除 错误
package com.bjsxt.gen03;
/**
*泛型的擦除
*1、继承|实现声明 不指定类型
*2、使用时 不指定类型
*统一Object 对待
*1、编译器警告 消除使用Object
*2、不完全等同于Object ,编译不会类型检查
*/
public class Student<T> {
private T javaScore;
private T oracleScore;
//泛型声明时不能使用 静态属性|静态方法上
//private static T1 test;
public T getJavaScore() {
return javaScore;
}
public void setJavaScore(T javaScore) {
this.javaScore = javaScore;
}
public T getOracleScore() {
return oracleScore;
}
public void setOracleScore(T oracleScore) {
this.oracleScore = oracleScore;
}
public static void main(String[] args) {
Student stu1 = new Student();
//消除警告 使用 Object
Student<Object> stu = new Student<Object>();
//stu.setJavaScore("af"); //以Object对待
/*
擦除,不会类型检查,所以test(stu) 类型通不过
而在test1(<?>) 泛型是不定的,所以是可以通过。
*/
test(stu1); //stu1 相当于Object 但是不完全等同Object
//擦除,不会类型检查
//test(stu);
test1(stu1);
test1(stu);
}
public static void test(Student<Integer> a){
}
public static void test1(Student<?> a){
}
}
八、其他
8.1 泛型没有继承
如果要使用类似多态的功能则需要借助通配符
通配符 ? Extends super
1. 可以用在声明类型及声明方法参数上, 但是不能声明在类上或则使用时。
2. ? 可以接受泛型的任意类型, 只能接受和输出,不能修改
3. ? Extends 泛型上限 <=
4. ? Super 泛型下限 >=
package com.bjsxt.gen04;
/**
* 通配符
* ?类型不定,使用时确定类型
* ?使用:声明类型|声明方法上,不能声明类或使用时
* ? extends : <= 上限 指定类型 子类或自身
* ? super :>=下限 指定类型 为自身或父类
* @author Administrator
*
*/
public class Student<T> {
T score;
public static void main(String[] args) {
Student<?> stu = new Student<String>();
test(new Student<Integer>());
test2(new Student<Apple>());
//test3(new Student<Apple>()); //泛型没有多态
//test4(new Student<Apple>()); //<
stu = new Student<Fruit>();;
//test4(stu); //使用时确定类型
test4(new Student<Object>());
test4(new Student<Fruit>());
}
public static void test(Student<?> stu){
}
public static void test3(Student<Fruit> stu){
}
// <=
public static void test2(Student<? extends Fruit> stu){
}
//>=
public static void test4(Student<? super Fruit> stu){
}
//泛型的嵌套
package com.bjsxt.gen04;
public class Bjsxt <T>{
T stu ;
public static void main(String[] args) {
//泛型的嵌套
Bjsxt<Student<String>> room =new Bjsxt<Student<String>>();
//从外到内拆分
room.stu = new Student<String>();
Student<String> stu = room.stu;
String score =stu.score;
System.out.println(score);
}
}
8.2 泛型数组
没有泛型数组,不能创建泛型数组
package com.bjsxt.gen04;
/**
* 没有泛型数组
* 声明可以使用,但是创建失败
*/
public class Array {
public static void main(String[] args) {
Integer[] arr = new Integer[4];
//Student<String>[] arr2 = new Student<String>[10];
Student<?>[] arr2 = new Student[10];
//只在声明时才能用
//在添加时使用Object 类型,所以元素都是可以添加进去的。
MyArrayList<String> strList =new MyArrayList<String>();
strList.add(0, "a");
String elem =strList.getElem(0);
System.out.println(elem);
}
}
class MyArrayList<E>{
//E[] cap =new E[10]; 没有泛型数组
//E[] cap =new Object[10]; 不可以的。 另外,类型不定,无法开辟空间
Object[] cap = new Object[10];
public void add(int idx,E e){
cap[idx] =e;
}
@SuppressWarnings("unchecked")
public E[] getAll(){
return (E[]) cap;
}
@SuppressWarnings("unchecked")
public E getElem(int idx){
return (E) cap[idx];
}
}
九、通过反射获得泛型的实际类型参数
查看反射相关内容。
参考
- 《java疯狂讲义》–李刚
- 高淇三百级视频
- java高新技术–张孝祥