JDK1.5
增加泛型支持在很大程度上都是为了让集合能够记住其元素的数据类型。在没有泛型之前,一旦把一个对象丢进java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。这样取出来使用时,往往需要强制转换,效率降低。
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参在声明变量、创建对象、调用方法时动态地指定。
从java5开始,java引入了参数化类型(parameterized type)
,运行程序在创建集合时指定集合元素的类型。java的参数化类型也被称为泛型。
使用泛型,可以在编译时进行类型转换检查,而不使用泛型,在运行时才检查
使用泛型,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。
在java7以前,泛型的格式:
List strList = new ArrayList();
在java7开始,可以使用下面的方式:
List strList = new ArrayList<>();
java可以推断尖括号里应该是什么类型
当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来 的类名,而不要增加泛型声明。例如,为Apple
类定义构造器,其构造器名依然是Apple
,而不是Apple
, 调用该构造器时却可以使用 Apple
,其中的T
为实际传入的类型参数。
正如泛型方法允许在方法签名中声明类型形参一样,Java也允许在构造器签名中声明类型形参。
class Foo {
public <T> Foo(T t) {
System.out.println(t)
}
}
##二、内部原理及更深应用
泛型是提供给javac编译器
使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。编译器编译带类型说明的集合时会去除类型
信息。对于参数化的泛型类型,getClass()
方法的返回值和原始类型的完全一样。由于编译后会去掉泛型的类型信息,只要跳过编译器,就可以往某个泛型集合中加入其它类型的数据。注意,在元数据中会保留泛型信息,可通过反射获取这些泛型信息。
在使用泛型时,一定要明确不存在泛型类,不存在泛型的父子关系等等。例如。List
对象不能被当做List
对象使用。List
类并不是List
子类。
数组和泛型有所不同,假设Foo是Bar的一个子类型,那么Foo[] 依然是Bar[]的子类型,但是G
不是G
的子类型。
明白泛型的原理后,2.1的问题就不攻自破。
例如,用反射得到集合,再调用其add()方法添加元素。
getClass()
获取的到的字节码是同一份。编译完成后就没有泛型信息了
可以通过反射拿到去到泛型的集合,然后添加元素,这样就跳过了泛型检查
ArrayList
类定义和`ArrayList类引用中涉及如下术语ArrayList
泛型类型ArrayList
中的E称为类型变量或类型参数ArrayList
称为参数的类型。ArrayList
中的Integer称为类型参数的实例或实际类型参数ArrayList
中的<>
读typeofArrayList
称为原始类型没有泛型数组。在创建数组实例时,数组的元素不能使用参数化类型。例如下面的语句就是错误的
vector<Integer> vectorList[] = new Vector<Integer>[10];
下面两种为兼容性都是可行的:
把一个参数化类型给原始类型;
把一个原始类型给参数类型;
编译器是通过一行一行的编译代码
注意
:编译器是一行一行的进行代码检查。
所以上面是不会报错的。
可以向 printCollection(
) 中使用cols.add("string")
;原因是 字符串类型属于Object
但是,cols = new HashSet
会报错。带通配符的集合仅表示是各种泛型对应集合的,但是并不能将元素加入,原因是集合的类型未知。
?
带来的问题通配符的上边界和下边界,也同样不能对该集合进行有关类型的操作,因为并不知道具体的子类和具体的父类。如,试图使用add操作,将会报错。
程序可以设置多个上限(至多一个父类上限、可以有多个接口上限),表明该类型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。例如:
//表明T类型必须是Number类或其子类。并实现java.io.Serializable接口。
public class Apple<T extends Number & java.io.Serializable> {
}
与类同时继承父类、实现接口类似的是,为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。
因为泛型既允许设定通配符的上限,也允许设定通配符的下线,从而允许在一个类里面包含如下两个方法:
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
接口
给编译器执行类型检查和类型推断
类型推断,取最小公倍数
使用基本类型会报错。 泛型中类型只能是引用类型,不能是基本类型
其中T
不能被int
替换。只有引用类型才能做泛型的方法的实际参数
当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类、或从该父类派生子类,需要指出的是,当使用这些接口、父类是不能再包含类型形参。
//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 做变量,非常清晰,在自己的命名中也可以借鉴)
泛型方法, 需要在方法的返回值之前和所有修饰符之后设置类型,在类上定义就只在类后面设置类型
接口或者类上的泛型字母只能使用在普通方法上,不允许使用在静态方法上,以及常量中。
当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都被扔掉。比如把List
类型转换成了List
package com.bjsxt.gen03;
/**
* 父类为泛型类
* 1、属性
* 2、方法
*
* 要么同时擦除,要么子类大于等于父类的类型,
* 不能子类擦除,父类泛型
* 1、属性类型
* 父类中,随父类而定
* 子类中,随子类而定
* 2、方法重写:
* 随父类而定
* @param
*/
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{
String name;
@Override
public void test(T t) {
}
*/
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){
}
}
如果要使用类似多态的功能则需要借助通配符
通配符 ? 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()); //泛型没有多态
//test4(new Student()); //<
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);
}
}
没有泛型数组,不能创建泛型数组
package com.bjsxt.gen04;
/**
* 没有泛型数组
* 声明可以使用,但是创建失败
*/
public class Array {
public static void main(String[] args) {
Integer[] arr = new Integer[4];
//Student[] arr2 = new Student[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];
}
}
查看反射相关内容。