- ①. 泛型的概述
- ②. 泛型 - 类class
- ③. 泛型 - 接口
- ④. 泛型 - 通配符 ?
- ⑤. 泛型 - 方法
- ⑥. 泛型上下边界
-
①. 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
-
②. 把元素的类型设计成一个参数,这个类型参数叫做泛型
-
③. 程序的运行结果会以崩溃结束
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生
List arrayList = Lists.newArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
}
- ④. 泛型的特点:泛型只在编译阶段有效。看下面的代码:
泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型
List②. 泛型 - 类classstringArrayList = Lists.newArrayList(); List integerArrayList = Lists.newArrayList(); Class classStringArrayList = stringArrayList.getClass(); Class classIntegerArrayList = integerArrayList.getClass(); if (classStringArrayList.equals(classIntegerArrayList)) { // 下面这里会输出 System.out.println("泛型测试,类型相同"); }
- ①. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic{ //key这个成员变量的类型为T,T的类型由外部指定 private T key; public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定 return key; } }
class Notepad{ // 此处指定了两个泛型类型 private K key ; // 此变量的类型由外部决定 private V value ; // 此变量的类型由外部决定 public K getKey(){ return this.key ; } public V getValue(){ return this.value ; } public void setKey(K key){ this.key = key ; } public void setValue(V value){ this.value = value ; } }
-
②. 泛型类的构造器如下:public GenericClass(){}
而下面是错误的:public GenericClass(){} -
③. 泛型的指定中不能使用基本数据类型,可以使用包装类替换
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型 //传入的实参类型需与泛型的类型参数类型相同,即为Integer. GenericgenericInteger = new Generic (123456); //传入的实参类型需与泛型的类型参数类型相同,即为String. Generic genericString = new Generic ("key_vlaue");
- ④. 定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型
public class Generic③. 泛型 - 接口{ public static void main(String[] args) { Generic generic = new Generic("111111"); Generic generic1 = new Generic(4444); Generic generic2 = new Generic(55.55); Generic generic3 = new Generic(false); System.out.println("泛型测试,key is:"+generic.getKey()); System.out.println("泛型测试,key is:"+generic1.getKey()); System.out.println("泛型测试,key is:"+generic2.getKey()); System.out.println("泛型测试,key is:"+generic3.getKey()); } //key这个成员变量的类型为T,T的类型由外部指定 private T key; public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定 return key; } }
- ①. 泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子
//定义一个泛型接口 public interface Generator{ public T next(); }
- ②. 当实现泛型接口的类,未传入泛型实参时:
class FruitGeneratorimplements Generator { @Override public T next() { return null; } }
- ③. 当实现泛型接口的类,传入泛型实参时:
public class FruitGenerator implements Generator④. 泛型 - 通配符 ?{ private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
- ①. 我们知道Ingeter是Number的一个子类,同时在特性章节中我们也验证过Generic
与Generic 实际上是相同的一种基本类型。那么问题来了,在使用Generic 作为形参的方法中,能否使用Generic 的实例传入呢?在逻辑上类似于Generic 和Generic 是否可以看成具有父子关系的泛型类型呢
GenericgInteger = new Generic (123); Generic gNumber = new Generic (456); // 这里直接会报错 gInteger.showKeyValue1(gInteger); public void showKeyValue1(Generic obj){ System.out.println("泛型测试,key is:"+obj); }
- ②. 回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic
类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic 和Generic 父类的引用类型。由此类型通配符应运而生
public void showKeyValue1(Generic> obj){
System.out.println("泛型测试,key is:"+obj);
}
- ③. 类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参 ! 此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
-
①. 泛型方法定义
-
②. 区别泛型方法、普通方法
public class GenericTest {
//这个类是个泛型类,在上面已经介绍过
public class Generic{
private T key;
public Generic(T key) {
this.key = key;
}
//我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
//所以在这个方法中才可以继续使用 T 这个泛型。
public T getKey(){
return key;
}
}
public T showKeyName(Generic container){
System.out.println("container key :" + container.getKey());
//当然这个例子举的不太合适,只是为了说明泛型方法的特性。
T test = container.getKey();
return test;
}
//这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic这个泛型类做形参而已。
public void showKeyValue1(Generic obj){
}
//这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
//同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
public void showKeyValue2(Generic> obj){
}
}
- ③. 当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下
public class GenericFruit {
class Fruit{
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
class Person{
@Override
public String toString() {
return "Person";
}
}
class GenerateTest{
public void show_1(T t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public void show_3(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest generateTest = new GenerateTest();
//apple是Fruit的子类,所以这里可以
generateTest.show_1(apple);
//编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
//generateTest.show_1(person);
//使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
- ④. 泛型方法与可变参数
publicvoid printMsg( T... args){ for(T t : args){ Log.d("泛型测试","t is " + t); } } printMsg("111",222,"aaaa","2323.4",55.55);
- ⑤. 如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法
public class StaticGenerator⑥. 泛型上下边界{ public static void show(T t){ } }
- ①. 在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类
public void showKeyValue1(Generic extends Number> obj){
}
Generic generic1 = new Generic("11111");
Generic generic2 = new Generic(2222);
Generic generic3 = new Generic(2.4f);
Generic generic4 = new Generic(2.56);
//这一行代码编译器会提示错误,因为String类型并不是Number类型的子类
//showKeyValue1(generic1);
showKeyValue1(generic2);
showKeyValue1(generic3);
showKeyValue1(generic4);
- ②. 如果我们把泛型类的定义也改一下:
public class Generic{ private T key; public Generic(T key) { this.key = key; } public T getKey(){ return key; } } //这一行代码也会报错,因为String不是Number的子类 Generic generic1 = new Generic ("11111");
- ③. 再来一个泛型方法的例子:
//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界,即在泛型声明的时候添加 //public T showKeyName(Generic container),编译器会报错:"Unexpected bound" public T showKeyName(Generic container){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test; }
- ④. 泛型的上下边界添加,必须与泛型的声明在一起
extends Number> (无穷小, Number]:只允许泛型为Number及Number子类的引用调用
super Number> [Number , 无穷大):只允许泛型为Number及Number父类的引用调用
extends Comparable>:只允许泛型为实现Comparable接口的实现类的引用调用



