泛型
基本概念
- Jdk 5引入
- 提供了编译时类型安全检测机制,该机制允许我们在编译时检测到非法的类型数据
- 作用:
- 对数据的类型进行检测
- 减少数据类型间的转换(主要体现在集合中)
- 本质:参数化类型,把某种数据类型当作一种参数
泛型类
-
泛型类的定义语法
class 类名 <泛型标识,泛型标识,...>{ 泛型标识 变量名; ... } //在类中把泛型标识当作一种数据类型即可
常用泛型标识:T、E、K、V
-
使用语法
类名<具体数据类型> 对象名 =new 类名<具体数据类型>(); jdk1.7后,第二个<>中的可以省略
-
注意:
-
泛型类,如果没有指定具体的数据类型,此时,操作类型是Object
-
泛型的类型参数只能是类类型,不能是基本数据类型(int、double、…)
-
泛型类型在逻辑上可以理解为多个不同的类型,但实际上都是相同类型
A<Integer> c1 = new A<>(); A<String> c2 = new A<>(); System.out.println(c1.getClass()==c2.getClass());//返回true
-
泛型类派生子类
-
子类也是泛型类,子类和父类的泛型标识要一致,即子类中要有一个和父类的泛型标识一样的泛型标识,因为在创建子类对象时,父类需要借助子类填充泛型标识
class A<T> extends B<T>
-
子类不是泛型类,父类要明确泛型类型
class A extends B<Integer>
泛型接口
-
定义语法
interface 接口名 <泛型标识,泛型标识,...>{ 泛型标识 方法名(); }
-
泛型接口的使用
- 实现类不是泛型类,接口要明确数据类型
- 实现类是泛型类,实现类的泛型标识要有与接口泛型标识一样的
(与泛型类派生子类类似)
泛型方法
-
语法:
修饰符 <T,E,...> 返回值类型 方法名{ }
-
public与返回值中间的非常重要,可以理解为把此方法声明为泛型方法
-
只用声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法不是泛型方法
-
在泛型类中,可以声明和类的泛型标识一致的泛型方法,但是泛型方法的泛型标识要独立于类的标识
public class ProductGet <T>{ public <T> T getProduct(ArrayList<T> list){ return list.get(new Random().nextInt(list.size())); } //假设创建对象时,传入的是String,但用方法的时候却不是String,此T非彼T }
-
泛型方法和泛型类的成员方法的区别:
- 泛型方法可以声明为static ,而成员方法不行
-
可变参数
public <E> 返回值类型 方法名(E... e){ }
-
使用:泛型标识的类型取决于你传入的参数,所以用泛型方法时,要把泛型类型放入参数列表,这样才好赋上泛型标识的类型
public <T,K> void test(T t,K k){ System.out.println(132); } start.test(123,321);//正确 //start.test<Integer,Intege>(123,321);错误写法
通配符
通配符一般是使用’?'代替具体的类型实参,所以,类型通配符是类型实参,而不是类型形参
public static void test(Ger<?> ger){
System.out.println(ger.getKey());
}
//此方法接收泛型类型为任意类型的Ger类对象
类型通配符上限
类/接口 <? extends 实参类型>
要求该泛型的类型只能是实参类型或者实参类型的子类类型
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
//test(animals);报错
test(cats);
test(miniCats);
}
public static void test(ArrayList<? extends Cat> list){//只能传Cat类或Cat类的子类
//list.add(new Cat());无法插入只能读取
for (Cat cat : list) {
System.out.println(cat);
}
}
(**在使用<? extends E>
的泛型集合中,对于元素的类型,编译器只知道元素是继承自E
,具体是E
的那个子类,这是无法知道的,所以向一个无法知道具体类型的泛型集合中插入元素是不能通过编译的。但是,由于知道元素是继承自E
,所以从这个泛型集合中取Fruit
类型的元素是可以的。**也就是说,我可以传一个new ArrayList(MiniCat),也可以传一个new ArrayList(Cat),这都是不确定的,只有调用时传参才知道,但集合中由于通配符的限制只能传特定的对象,所以向一个无法知道具体类型的泛型集合中插入元素是不能通过编译的)
类型通配符下限
类/接口<? super 实参类型>
要求该泛型的类型,只能是实参类型,或者是实参类型的父类类型
public static void main(String[] args) {
ArrayList<Animal> animals=new ArrayList<>();
ArrayList<Cat> cats=new ArrayList<>();
ArrayList<MiniCat> miniCats=new ArrayList<>();
show(animals);
show(cats);
//show(miniCats);
}
public static void show(ArrayList<? super Cat>list){
list.add(new Cat());
list.add(new MiniCat());
//list.add(new Animal());
for (Object o : list) {
System.out.println(o);
}
}
(在使用<? super E>的泛型集合中,元素类型是E的父类,但无法知道是那个具体的父类,因此读取时无法确定以某个具体的父类进行读取(只能以Object读取),可以插入Cat与Cat的子类,因为这个集合中的元素都是Apple的父类。(意思就是插入Cat与他的子类后,可以安全的转为Cat父类的引用)
在jdk中的体现:
public class TreeSet<E>...
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
能在TreeSet中传入泛型类型为实参类型或其父类的比较器
类型擦除
泛型是 Java 1.5 版本才引进的概念,在这之前是没有泛型的概念的,但显然,泛型代码能够很好地和之前版本的代码很好地兼容。这是因为,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
- 之前泛型类中的类型参数部分如果没有指定上限,如
<T>
则会被转译成普通的 Object 类型 - 如果指定了上限如
<T extends String>
则类型参数就被替换成类型上限。
缺陷:可以通过反射绕开泛型的限制,因为反射时已经完成类型擦除
泛型与数组
泛型数组的创建
-
可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象
ArrayList<String>[] ArrList=new ArrayList[5];//用带泛型的数组引用指向一个普通数组 ArrayList<String> strings = new ArrayList<>(); strings.add("123"); ArrList[0]=strings;
-
可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组
反射中的泛型
- Class
- Constructor
用反射创建对象时的差异
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
//不使用泛型还要强转
Class personClass1 = Person.class;
Constructor constructor1 = personClass1.getConstructor();
Object o = constructor1.newInstance();