Java泛型
1.泛型
1.1 泛型的背景
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
1.2 概念
Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是参数化类型,也就是说所操作的数据类型被指定为一个参数(Type Parameter),这种参数类型可以用在类、接口和方法的创建中。
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
1.3 为什么需要泛型
在Java中,大部分都是使用Object来代替任意类型的,Object是所有类的基类。但是这样就有一个向下强转的问题了,会显得程序不安全。
保证了类型的安全性:泛型约束了变量的类型,保证了类型的安全性。例如List和ArrayList。List集合只能加入int类型的变量,ArrayList可以Add任何常用类型,编译的时候不会提示错误。
避免了不必要的装箱、拆箱操作,提高程序的性能:泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。
举例说明:使用泛型之前,默认是Object类型。
object a = 1;//由于是object类型,会自动进行装箱操作。
int b=(int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。
1.4 优点
- 类型安全
- 泛型的首要目标是提高Java程序的类型安全,通过知道使用泛型的定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设。没有泛型,这些假设就只存在于系统开发人员的头脑中。
- 消除了强制类型的转换
- 这使得代码更加可读,并且减少了出错的机会。尽管减少强制类型转换可以提高使用泛型类的代码的累赞程度,但是声明泛型变量时却会带来相应的累赞程度。在简单的程序中使用一次泛型变量不会降低代码累赞程度。但是对于多次使用泛型变量的大型程序来说,则可以累积起来降低累赞程度。所以泛型消除了强制类型转换之后,会使得代码加清晰和筒洁。
- 更高的运行效率
- 在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
1.5 类型
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(表示Java 类,包括基本的类和我们自定义的类)
- K - Key(表示键,比如Map中的key)
- V - Value(表示值)
- N - Number(表示数值类型)
- ? - (表示不确定的java类型)
- S、U、V - 2nd、3rd、4th types
2.泛型类
2. 1 泛型类
泛型类就是把泛型定义在类中,当使用此类的时候哦,才把类型明确下来。使用了什么样的类型,那此类就代表着什么类型;这样就不用担心强转的问题,运行时出现转换异常的问题。
泛型类的定义语法:
class 类名称<泛型标识, 泛型标识, ...>{
private 泛型标识 变量名;
}
- 常用的泛型标识:T、E、K、V
- 泛型的使用语法:类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
- Java1.7以后,后面的<>中的具体的数据类型可以省略不写:类名<具体的数据类型> 对象名 = new 类名<>();
菱形语法
2.2 泛型类的注意事项
- 泛型类,如果没有指定具体的数据类型,此时,操作类型是Object
- 泛型的类型参数只能是类类型,不能是基本数据类型
- 泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型
package com.shao.demo1;
/**
* 泛型类的定义
* @param <T> 泛型标识---类型参数
* T 创建对象的时候指定具体的数据类型
*/
public class Generic<T> {
// T 是由外部使用类的时候来指定的
private T key;
public Generic() { // 无参构造
}
public Generic(T key) { // 有参构造
this.key = key;
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
@Override
public String toString() {
return "Generic{" +
"key=" + key +
'}';
}
}
定义好泛型类后,就在创建的时候指定类型。使用的时候,此类就会自动转换成想要使用的类型了
package com.shao.demo1;
public class Test1 {
public static void main(String[] args) {
// 泛型类在创建对象的时候,来指定操作的具体数据类型
Generic<String> stringGeneric = new Generic<>("abc");
String key = stringGeneric.getKey();
System.out.println(key); // abc
Generic<Integer> integerGeneric = new Generic<>(100);
Integer key1 = integerGeneric.getKey();
System.out.println(key1); // 100
// 泛型类在创建对象的时候,没有指定类型,将默认Object类型来操作
Generic generic = new Generic("Zhangsan");
Object key2 = generic.getKey();
System.out.println(key2);
// 泛型类,不支持基本数据类型
//Generic<int> generic1 = new Generic<int>(100);
// 同一泛型类,根据不同的数据类型创建的对象,本质上是同一类型
System.out.println(stringGeneric.getClass()); // class com.shao.demo1.Generic
System.out.println(integerGeneric.getClass()); // class com.shao.demo1.Generic
System.out.println(stringGeneric.getClass() == integerGeneric.getClass()); // true
}
}
2.3 泛型类案例:模拟抽奖器
创建实体类对象
package com.shao.demo2;
import java.util.ArrayList;
import java.util.Random;
/**
* 抽奖器
* @param <T>
*/
public class ProductGetter<T> {
Random random = new Random();// 生成随机数
// 奖品
private T product;
// 奖品集合
ArrayList<T> list = new ArrayList<>();
/**
* 添加奖品
* @param t 奖品
*/
public void addProduct(T t){
list.add(t);
}
/**
* 抽奖
* @return
*/
public T getProduct() {
product = list.get(random.nextInt(list.size())); // 不能超过奖品的最大数
return product;
}
}
测试~~
package com.shao.demo2;
public class Test {
public static void main(String[] args) {
// 创建抽奖器对象,指定数据类型
ProductGetter<String> stringProductGetter = new ProductGetter<>();
String[] strProduct = {"华为手机","苹果手机","小米手机","扫地机器人","咖啡机"};
// 往抽奖器中填充奖品
for (int i = 0; i < strProduct.length; i++) {
stringProductGetter.addProduct(strProduct[i]);
}
// 抽奖
String product1 = stringProductGetter.getProduct();
System.out.println("恭喜你,你抽中了:" + product1);
System.out.println("--------------------------");
// 创建抽奖器对象,指定数据类型
ProductGetter<Integer> integerProductGetter = new ProductGetter<>();
// 往抽奖器中填充奖品
int[] intProduct = {1000000,500000,30000,20000,10000,999};
for (int i = 0; i < intProduct.length; i++) {
integerProductGetter.addProduct(intProduct[i]);
}
// 抽奖
Integer product2 = integerProductGetter.getProduct();
System.out.println("恭喜你,你抽中了:" + product2);
}
}
2.4 从泛型类派生子类
- 子类也是泛型类,子类和父类的泛型类型要一致
class ChildGeneric<T> extends Generic<T>
//父类
public class Parent<E> {
private E value;
public E getValue() {
return value;
}
public void setValue(E value) {
this.value = value;
}
}
/**
* 泛型类派生子类,子类也是泛型类,那么子类的泛型标识要和父类一致。
* @param <T>
*/
public class ChildFirst<T> extends Parent<T> {
@Override
public T getValue() {
return super.getValue();
}
}
- 子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
/**
* 泛型类派生子类,如果子类不是泛型类,那么父类要明确数据类型
*/
public class ChildSecond extends Parent<Integer> {
@Override
public Integer getValue() {
return super.getValue();
}
@Override
public void setValue(Integer value) {
super.setValue(value);
}
}
测试~~
public class Test4 {
public static void main(String[] args) {
// 创建泛型类派生子类对象
ChildFirst<String> childFirst = new ChildFirst<>();
childFirst.setValue("abc");
String value = childFirst.getValue();
System.out.println(value);
// 创建子类不是泛型类对象
ChildSecond childSecond = new ChildSecond();
childSecond.setValue(100);
Integer value1 = childSecond.getValue();
System.out.println(value1);
}
}
2.5 泛型接口
泛型接口的定义语法:
interface 接口名称 <泛型标识,泛型标识,…> {
泛型标识 方法名();
}
2.6 泛型接口的使用
- 实现类也是泛型类,实现类和接口的泛型类型要一致
/**
* 泛型接口
* @param <T>
*/
public interface Generator<T> {
T getKey();
}
/**
* 泛型接口的实现类,是一个泛型类,
* 那么要保证实现接口的泛型类泛型标识包含泛型接口的泛型标识
* @param <T>
* @param <E>
*/
public class Pair<T,E> implements Generator<T> {
private T key;
private E value;
public Pair(T key, E value) {
this.key = key;
this.value = value;
}
@Override
public T getKey() {
return key;
}
public E getValue() {
return value;
}
}
- 实现类不是泛型类,接口要明确数据类型
/**
* 实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型。
*/
public class Apple implements Generator<String> {
@Override
public String getKey() {
return "hello generic";
}
}
测试~~
public class Test05 {
public static void main(String[] args) {
Apple applet = new Apple();
String key = applet.getKey();
System.out.println(key);
Pair<String, Integer> pair = new Pair<>("count",100);
String key1 = pair.getKey();
Integer value = pair.getValue();
System.out.println(key1+"="+value);
}
}
3.泛型方法
用法:泛型方法是在调用方法的时候指明泛型的具体类型。
语法:修饰符 <T,E, …> 返回值类型 方法名(形参列表) { 方法体… }
说明:
- public与返回值中间非常重要,可以理解为声明此方法为泛型方法。
- 只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
- < T >表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
- 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
3.1 泛型方法与可变参数
public <E> void print(E... e){
for (E e1 : e) {
System.out.println(e);
}
}
3.2 泛型方法总结
package com.shao.demo2;
import java.util.ArrayList;
import java.util.Random;
/**
* 抽奖器
* @param <T>
*/
public class ProductGetter<T> {
Random random = new Random();// 生成随机数
// 奖品
private T product;
// 奖品集合
ArrayList<T> list = new ArrayList<>();
/**
* 添加奖品
* @param t 奖品
*/
public void addProduct(T t){
list.add(t);
}
/**
* 抽奖
* @return
*/
public T getProduct() {
product = list.get(random.nextInt(list.size()));
return product;
}
/**
* 定义泛型方法
* @param list 参数
* @param <E> 泛型标识,具体类型由调用方法的时候决定
* @return
*/
public <E> E getProduct(ArrayList<E> list){
return list.get(random.nextInt(list.size()));
}
/**
* 静态的泛型方法,采用多个泛型类型
* @param t
* @param e
* @param k
* @param <T>
* @param <E>
* @param <K>
*/
public static <T,E,K> void printType(T t, E e, K k) {
System.out.println(t + "\t" + t.getClass().getSimpleName());
System.out.println(e + "\t" + e.getClass().getSimpleName());
System.out.println(k + "\t" + k.getClass().getSimpleName());
}
/**
* 泛型可变参数的定义
* @param e
* @param <E>
*/
public static <E> void print(E... e){
for (int i = 0; i < e.length; i++) {
System.out.println(e[i]);
}
}
}
测试~~
package com.shao.demo5;
import com.shao.demo2.ProductGetter;
import java.util.ArrayList;
public class Test6 {
public static void main(String[] args) {
// 创建抽奖器对象,指定数据类型
ProductGetter<Integer> productGetter = new ProductGetter<>();
int[] products = {500,300,200,100};
// 往抽奖器中填充奖品
for (int i = 0; i < products.length; i++) {
productGetter.addProduct(products[i]);
}
//泛型类的成员方法调用
Integer product = productGetter.getProduct();
System.out.println(product+ "\t" + product.getClass().getSimpleName());
System.out.println("------------------------------");
ArrayList<String> strList = new ArrayList<>();
strList.add("华为笔记本");
strList.add("华为手机");
strList.add("手机");
// 泛型方法的调用,类型是通过调用方法的时候来指定
String prdString = productGetter.getProduct(strList);
System.out.println(prdString+ "\t" + prdString.getClass().getSimpleName());
System.out.println("------------------------------");
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(10000);
arrayList.add(5000);
arrayList.add(3000);
Integer product2 = productGetter.getProduct(arrayList);
System.out.println(product2+ "\t" + product2.getClass().getSimpleName());
System.out.println("------------------------------");
// 调用多个泛型类型的静态泛型方法
ProductGetter.printType(100,"java",true);
ProductGetter.printType(false,false,true);
System.out.println("------------------------------");
productGetter.print("a","b","c");
productGetter.print(1,2,3,4,5,6);
}
}
4.类型通配符
4.1 什么是类型通配符
- 类型通配符一般是使用"?"代替具体的类型实参。
- 所以,类型通配符是类型实参,而不是类型形参。
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。因为add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。
4.2 类型通配符的上限
- 语法:
类/接口<? extends 实参类型>
;要求该泛型的类型,只能是实参类型,或实参类型的子类类型。
现在,我想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做???
List<? extends Number>
:List集合装载的元素只能是Number的子类或自身
public static void main(String[] args) {
//List集合装载的是Integer,可以调用该方法
List<Integer> integer = new ArrayList<>();
test(integer);
//List集合装载的是String,在编译时期就报错了
List<String> strings = new ArrayList<>();
test(strings);
}
public static void test(List<? extends Number> list) {
}
4.3 类型通配符的下限
- 语法:
类/接口<? super 实参类型>
;要求该泛型的类型,只能是实参类型,或实参类型的父类类型。 - 传递进来的只能是Type或Type的父类
<? super Type>
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
那它有什么用呢??我们来想一下,当我们想要创建一个TreeSet<String>
类型的变量的时候,并传入一个可以比较String大小的Comparator。
那么这个Comparator的选择就有很多了,它可以是Comparator<String>
,还可以是类型参数是String的父类,比如说Comparator<Objcet>
…
这样做,就非常灵活了。也就是说,只要它能够比较字符串大小,就行了
值得注意的是:无论是设定通配符上限还是下限,都是不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的!
- 带有子类限定的可以从泛型读取【也就是—>(? extend T)】-------->Producer Extends
- 带有超类限定的可以从泛型写入【也就是—>(? super T)】-------->Consumer Super
4.4 通配符和泛型方法
大多时候,我们都可以使用泛型方法来代替通配符的…
//使用通配符
public static void test(List<?> list) {
}
//使用泛型方法
public <T> void test2(List<T> t) {
}
上面这两个方法都是可以的…那么现在问题来了,我们使用通配符还是使用泛型方法呢??
原则:
- 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法
- 如果没有依赖关系的,就使用通配符,通配符会灵活一些.
参考链接:https://www.zhihu.com/question/272185241/answer/366129174
5.类型擦除
5.1 概念
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”
5.2 兼容性
JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。
当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。
值得注意的是:它保留的就类型参数的上限。
List<String> list = new ArrayList<>();
//类型被擦除了,保留的是类型的上限,String的上限就是Object
List list1 = list;
如果我把没有类型参数的集合赋值给带有类型参数的集合赋值,这又会怎么样??
List list = new ArrayList();
List<String> list2 = list;
它也不会报错,仅仅是提示“未经检查的转换”
5.3 泛型擦除分类
无限制类型擦除
有限制类型擦除
擦除方法中类型定义的参数
桥接方法
5.4 泛型的应用
当我们在编写程序的时候,会用到DAO数据访问层,我们每次都要写很多的DAO,这样会有点麻烦。
那么我们想要的效果是什么呢??只写一个抽象DAO,别的DAO只要继承该抽象DAO,就有对应的方法了。
要实现这样的效果,肯定是要用到泛型的。因为在抽象DAO中,是不可能知道哪一个DAO会继承它自己,所以是不知道其具体的类型的。而泛型就是在创建的时候才指定其具体的类型。
抽象Dao
public abstract class BaseDao<T> {
//模拟hibernate....
private Session session;
private Class clazz;
//哪个子类调的这个方法,得到的class就是子类处理的类型(非常重要)
public BaseDao(){
Class clazz = this.getClass(); //拿到的是子类
ParameterizedType pt = (ParameterizedType) clazz.getGenericSuperclass();
//BaseDao<Category>
clazz = (Class) pt.getActualTypeArguments()[0];
System.out.println(clazz);
}
public void add(T t){ // 新增
session.save(t);
}
public T find(String id){ // 查询
return (T) session.get(clazz, id);
}
public void update(T t){ // 更新
session.update(t);
}
public void delete(String id){ // 删除
T t = (T) session.get(clazz, id);
session.delete(t);
}
}
- 继承抽象DAO,该实现类就有对应的增删改查的方法了。
CategoryDao
public class CategoryDao extends BaseDao<Category> {}
BookDao
public class BookDao extends BaseDao<Book> {}
6.泛型与数组
泛型数组的创建
- 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
ArrayList<String>[] listArr = new ArrayList<5>(); //会报错
会报错
ArrayList[] list = new ArrayList[5];
ArrayList<String>[] listArr = list;
或者
ArrayList<String>[] listArr = new ArrayList[5];
不会报错
- 可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组
public class Fruit<T> {
private T[] array;
public Fruit(Class<T> clz, int length){
//通过Array.newInstance创建泛型数组
array = (T[])Array.newInstance(clz, length);
}
}
7.泛型和反射
- 反射常用的泛型类 :
Class<T>、Constructor<T>
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 泛型与反射
*/
public class Test11 {
public static void main(String[] args) throws Exception {
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
}
}