泛型(Generic)
1. 泛型概述
- 泛型是一个未知的, 不确定的数据类型. 比如ArrayList 中的E, 就是一个未知的不确定的数据类型, 那么他就是一个泛型
- 泛型虽然是一个未知的, 不确定的数据类型, 但他不是一直未知, 一直不确定当我们使用这个类的时候, 会指定这个泛型的类型. 比如ArrayList , ArrayList
- 泛型可以省略, 如果泛型省略, 相当于泛型是Object. 如
ArrayLIst list = new ArrayList();
这样的话就可以放任何类型的数据. - 泛型也是语法糖, 下面会用例子介绍
package drafts.drafts10;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
//不适用泛型
ArrayList list = new ArrayList();
//list.add(100);编译时不报错, 运行时报错
list.add("hello");
list.add("world");
list.add("java");
for (Object obj : list) {
String str = (String) obj;
System.out.println(str.length());
}
//使用泛型
ArrayList<String> list2 = new ArrayList<>();
//list2.add(100);编译时期报错
list2.add("hello");
list2.add("world");
list2.add("java");
for (String str : list2) {
System.out.println(str.length());
}
}
}
前面曾提过, 集合中是可以存放任意对象的, 只要把对象储存集合后, 那么这是他们都会被提升成Object类型. 当我们在取出每一个对象, 并进行相应的操作, 必须==采用类型转换==.
看一段代码:
import java.util.Iterator;
public class GenericTest {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("捣乱黄");
coll.add("捣乱绿");
coll.add(5);//由于集合并没有限定,所以任何类型都可以给其中存放
Iterator it = coll.iterator();
while (it.hasNext()){
//需要打印每个字符串的长度, 就要把迭代出来的对象转成String类型
String str =(String) it.next();
System.out.println(str.length());
}
}
}
//3
//3
//Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
// at drafts.drafts3.GenericTest.main(GenericTest.java:16)
程序在运行时抛出了java.lang.ClassCastException
.类型转换异常
为什么会出现此异常呢?
由于集合中什么类型都可以储存, 导致取出时强制转换, 引发运行时ClassCastException
.
Collection虽然可以储存各种对象, 但实际上, 通常Collection只储存同一类型对象, 因此, 在JDK1.5
后, 新增了泛型(Generic)语法
使得在设计API
时可以指定类或方法支持泛型, 这样我们使用API
的时候也变得更为简洁, 并得到了==编译时运行检查==.
- 泛型 : 可以在类或方法中预支的使用未知的类型,
tips : 一般在创建对象时, 将未知的类型确定具体的类型, 当没有指定泛型时, 默认类型为Object类型.
2. 泛型的好处
- 将运行时期的
ClassCastException
, 转移到了编译时期变成编译失败 - 省去了向下转型的操作(只是操作层面省去了, 下详)
看下面一段代码: 体现了省去向下转型
package drafts.drafts4;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class GenericTest2 {
public static void main(String[] args) {
Collection <String> list = new ArrayList<>();
list.add("捣乱黄");
list.add("捣乱绿");
//list.add(666); 这时, 不是String类型的元素就会编译报错
Iterator<String> it = list.iterator();
while (it.hasNext()){
String str = it.next();
//当使用Iterator<String>控制元素类型后, 就不需要强转了,
//获取到的元素就是String类型
System.out.println(str.length());
}
}
}
tips: 泛型时数据类型的一部分, 我们将类名与泛型合并一起看做数据类型
泛型擦除: 泛型也是语法糖, 本质还是要做向下转型.
Java中的泛型都是伪泛型, 泛型只在源代码阶段有效, 一旦编译, 泛型就会消失, 俗称泛型参数
package drafts.drafts10;
import java.util.ArrayList;
/*
泛型擦除, 在用xjad反编译.class文件后发现
Java中的泛型都是伪泛型, 泛型只在源代码阶段有效, 一旦编译, 泛型就会消失, 俗称泛型擦除
*/
@SuppressWarnings("all")
public class GenericTest2 {
public static void main(String[] args) {
ArrayList<String> list2 = new ArrayList<>();
//list2.add(100);编译时期报错
list2.add("hello");
list2.add("world");
list2.add("java");
for (String str : list2) {
System.out.println(str.length());
}
//编译之后泛型就没了, 本质其实还是向下转型
}
}
3. 泛型的定义与使用
泛型, 是一种未知的不确定的数据类型. 用来灵活的将数据类型应用到不同的类, 方法, 接口中. 将数据类型作为参数进行传递.
如果定义类的时候, 在类名后面加上, 此时就表示定义了一个未知的, 不确定的数据类型T , 这个T就是一个泛型, T可以用任何字母代替, 但一般用T (type)
这个位置的数据类型T会在我们使用这个类的时候确定下来
### 3.1 含有泛型的类( 泛型类 )
定义格式 :
修饰符 class 类名 <代表泛型的变量> { }
例如, API中的ArrayList集合
class ArrayList<E>{
public boolean add (E e) {}
public E get (int index) {}
...
}
在类中定义的泛型在整个类中都可以使用
泛型在定义的时候不具体, 使用的时候才具体, 在使用的时候确定泛型的具体数据类型
在创建对象的时候确定泛型 :
例如, ArrayList<String> list = new ArrayList<String> ()
;
此时, 变量E的值就是String类型. 那么我们的类型就可以理解为 :
class ArrayList<String>{
public boolean add (String e) {}
public String get (int index) {}
...
}
3.2 含有泛型的方法
如果想要延后泛型类型的确认时间, 那么可以使用泛型方法. 如果将泛型定义在方法上, 那么该方法就是一个泛型方法
在方法上定义的泛型, 需要调用方法的时候才能够确定这个泛型是什么类型
定义格式 :
修饰符 <代表泛型的变量> 返回值类型 方法名 (参数列表) { ... }
例如 :
public class MyGenericMethod {
public <E> void show <E e> {
System.out.println(mvp.getClass());
}
public <E> E show2 (E e) {
return e;
}
}
调用方法时, 确定泛型的类型
public class GenericMethodDemo {
public static void main (String[] args){
//创建对象
MyGenericMethod mm = new MyGenericMethod();
mm.show("aaa"); //class java.lang.String
mm.show(1234); //class java.lang.Integer
mm.show(12.211); //class java.lang.Double
}
}
定义方法, 接收什么参数, 就返回什么结果
: 表示在方法上定义了一个不确定的数据类型E
后面两个E: 使用泛型当做参数和返回值的数据类型
在方法上定义的泛型, 可以在整个方法中使用
在方法上定义的泛型, 需要调用这个方法的时候才能确定这个泛型表示的是什么类型.
public <E> E getSame(E e) {
return e ;
}
小结 :
如果在类上面定义泛型, 那么类就是泛型类, 在类上面定义的泛型, 可以在整个类中使用, 类上面定义的泛型需要使用整个类的时候才能确定泛型的类型
如果在方法上面定义泛型, 那么这个方法就是泛型方法, 在方法上面定义的泛型, 可以在整个方法中使用, 方法上面定义的泛型需要使用该方法的时候才能确定泛型的类型
3.3 含有泛型的接口
如果在定义接口的时候在接口名后面加上尖括号, 那么这个接口就是一个泛型接口, 在接口中定义的泛型, 在整个接口中都可以使用.
定义格式 :
修饰符 interface 接口名<代表泛型的变量> {...}
例如 :
public interface MyInterface <T> {//表示定义了一个不确定的数据类型T
public abstract void add (E e);
public abstract void E getE();
}
使用格式 :
3.3.1 定义类的时候确定泛型的类型
例如
public class MyImp1 implements MyGenericInterface<String> {
@Override
public void add(String e) {
// 省略...
}
@Override
public String getE() {
return null;
}
}
此时, 泛型E 的值就是String类型
3.3.2 直到创建对象时, 才确定泛型的类型
例如
public class MyImp2<E> implements MyGenericInterface<E> {
@Override
public void add(E e) {
// 省略...
}
@Override
public E getE() {
return null;
}
}
确定泛型 :
/*
* 使用
*/
public class GenericInterface {
public static void main(String[] args) {
MyImp2<String> my = new MyImp2<String>();
my.add("aa");
}
}
泛型接口的使用:
3.4 泛型通配符
泛型之间是没有继承关系的, 比如ArrayList并不是ArrayList的父类
如果想要让泛型可以匹配任何类型的数据, 那么可以使用泛型通配符.
我们用?
表示泛型通配符.
==注意 :== 泛型通配符要使用在参数位置==被动匹配==, 不能主动使用.
3.4.1 泛型通配符的使用
import java.util.ArrayList; /* 泛型之间是没有继承关系的。比如ArrayList<Object>并不是ArrayList<String>的父类。 如果想要让泛型可以匹配任何类型的数据,那么可以使用泛型通配符。 ? 表示泛型通配符,可以匹配任何类型的泛型。 注意: 泛型通配符要使用在参数位置被动匹配, 不能主动使用。 */ public class Demo01Generic { public static void main(String[] args) { //创建集合,用来保存字符串 ArrayList<String> strList = new ArrayList<>(); //添加元素 strList.add("Hello"); strList.add("World"); strList.add("Java"); //调用printArrayList方法,遍历集合 printArrayList(strList); //创建集合,保存Integer ArrayList<Integer> intList = new ArrayList<>(); //调用printArrayList,遍历 printArrayList(intList); //创建集合,使用?当做泛型类型 //ArrayList<?> list = new ArrayList<>(); //list.add(); } /* 定义一个方法,用来遍历存储任何类型数据的集合。 参数: ArrayList<Object> */ public static void printArrayList(ArrayList<?> list) { //?表示泛型通配符,可以匹配任何类型的泛型。 //对参数list集合进行遍历 for (Object obj : list) { System.out.println(obj); } } }
3.4.2 泛型限定
如果想要对?泛型通配符的使用范围进行限制,那么可以使用泛型限定(上限,下限)
<? extends A>
:泛型类型要么是A类,要么是A类的子类, 孙子类...。 上限。 <? super A>
: 泛型类型要么是A类,要么是A类的父类, 爷爷类 直到Object类。 下限。
泛型主要用于代码的重构.
示例:
import java.util.ArrayList; /* 如果想要对?泛型通配符的使用范围进行限制,那么可以使用泛型限定(上限,下限) <? extends A>:泛型类型要么是A类,要么是A类的子类。 上限。 <? super A>: 泛型类型要么是A类,要么是A类的父类。 下限。 泛型主要用于代码的重构. */ public class Demo02Generic { public static void main(String[] args) { //创建集合 ArrayList<Student> stuList = new ArrayList<>(); //添加元素 stuList.add(new Student("jack", 20)); stuList.add(new Student("rose", 12)); stuList.add(new Student("tony", 24)); //调用printArrayList方法 printArrayList(stuList); //创建集合 ArrayList<Person> personList = new ArrayList<>(); printArrayList(personList); //创建集合 ArrayList<Object> objList = new ArrayList<>(); //printArrayList(objList); 要求泛型要么是Person,要么是Person的子类,不能是Person的父类 //method(stuList); 泛型类型要么是Person,要么是Person的父类,不能是Person的子类 method(personList); method(objList); } /* 定义方法,使用集合当做参数。 */ public static void method(ArrayList<? super Person> list) { //泛型类型要么是Person,要么是Person的父类 } /* 要求:定义方法,遍历保存Person或者Person子类对象的集合。 */ public static void printArrayList(ArrayList<? extends Person> list) {//泛型的类型要么是Person,要么是Person的子类 for (Person p : list) { System.out.println(p); } } }