Java泛型
2023-09-11 14:22:32 时间
1. 了解
未使用泛型的集合→集合可以装任意类型的数据,但是接收数据的时候很麻烦,因为你不知道你会收到什么类型的数据,时刻需要准备强转。
使用了泛型的集合→编译器会检测插入的数据类型,集合只能装指定类型的数据,其优势:
- 编译期间检测类型(类型安全)
- 减少强制转换
泛型概念:
- JDK1.5新特性,提供编译时类型安全检测机制
- 本质是参数化类型,即所操作的数据类型被指定为一个参数
2. 类与接口
2.1 类
定义格式:
/*
常用的标识:
T - Type(Java类) V - Value(值)
E - Element(在集合中使用) N - Number(数值类型)
K - Key(Key键) ? - 不确定的java类型
*/
class 类名 <泛型标识1,泛型标识2,...>{
private 泛型标识 变量名;
...
}
泛型类定义:
package generics;
/**
* 泛型类
* @author 364.99
* @version 1.0
* @param <T> 泛型标识
* T 创建对象的时候指定具体的数据类型
*/
public class Demo1<T> {
//T的类型由外部类使用此类对象时来指定
private T item;
public Demo1() {
}
public Demo1(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
@Override
public String toString() {
return "Demo1{" +
"item=" + item +
'}';
}
}
泛型类使用格式:
类名<数据类型> 对象名 = new 类名<数据类型>();//JDK1.7之后,后一个数据类型可省略
泛型类的使用:
package generics;
public class Test1 {
public static void main(String[] args) {
Demo1<String> demo1 = new Demo1<>();
demo1.setItem("张三");
System.out.println(demo1);
}
}
注意:
- 泛型类在使用时,没有指定数据类型,按照
Object
类型接收数据 - 泛型的数据类型只支持
类类型
(基本类型使用其对应的包装类实现) - 同一泛型类,根据不同的数据类型创建的对象都是同一个Class对象(.java→.class→只会生成一个Class类对象,详情见反射)
子类
- 子类与父类都是泛型→子类与父类泛型类型要一致
class 子类<T> extends 父类<T>
//子类的泛型标识可以有多个,但必须有一个与父类一致
class 子类<T,E,...> extends 父类<T>
- 子类不是泛型类→父类要明确泛型的数据类型
class 子类 extends 父类<具体数据类型>
2.2 接口
定义格式:
interface 接口名 <泛型标识1,泛型标识2,...>{
泛型标识 方法名();
...
}
泛型接口
package generics;
/**
* 泛型接口
* @param <T>
*/
public interface Demo2 <T>{
T print();
}
泛型接口的实现类
- 实现类是泛型类,实现类和接口的泛型类型要一致
//必须保证实现类的泛型标识包含泛型类的泛型标识
public class Test2<T> implements Demo2<T>{
@Override
public T print() {
return null;
}
}
- 实现类不是泛型类,接口要明确数据类型
//不标明→默认Object
public class Test2 implements Demo2{
@Override
public Object print() {
return null;
}
}
//标明
public class Test2 implements Demo2<String>{
@Override
public String print() {
return null;
}
}
2.3 方法
定义格式:
修饰符 <泛型标识1,泛型标识2,...> 返回值类型 方法名(参数列表){
方法体
}
注意:
- 只有声明了
<泛型标识>
的方法才是泛型方法 - 只有声明了
<T>
的方法才可以在方法中使用泛型类型T
/**
* 单个泛型类型的泛型方法
* @param list
* @param <E>
* @return
*/
public static <E> E getLast(LinkedList<E> list){
return list.getLast();
}
/**
* 多个泛型类型的泛型方法
* @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());
}
使用:
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
System.out.println(Demo1.getLast(list));
Demo1.printType("AB",'c',4);
注意:
- 泛型方法的泛型类型是独立于泛型类的泛型类型
- 泛型类的成员方法(getter,setter)不能定义为
static
(泛型类需要在创建对象时指定类型,而静态方法是通过类名直接调用的,此时成员方法类型未被指定,会报错)
3. 类型通配符
为什么要是用类型通配符?
上案例:
定义一个泛型类
public class Box<T> {
private T item;
public Box() {
}
public Box(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
重载showItem方法依旧报错
遇到类似情况就需要引入类型通配符了
什么是类型通配符:
- 类型通配符一般是用
?
代替具体的类型实参 - 类型通配符是类型实参,而不是类型形参
类型通配符的上限
要求该泛型的类型,只能是实参类型,或实参类型的子类类型
格式:
类/接口<? extends 实参类型>
类型通配符的下限
要求该泛型的类型,只能是实参类型,或实参类型的父类类型
类/接口<? super 实参类型>
4. 类型擦除
类型擦除:泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
System.out.println(list1.getClass().getSimpleName());
System.out.println(list2.getClass().getSimpleName());
}
}
无限制类型擦除
Box<Integer> box = new Box<>();
//利用反射,获取Box类的字节码文件的Class类对象
Class clazz = box.getClass();
//获取box所有成员变量
Field[] fields = clazz.getDeclaredFields();
//打印所有成员变量的名称和类型
for (Field field:fields) {
System.out.println(field.getName() + ":" + field.getType().getSimpleName());
}
有限制类型擦除
public class Box<T extends Number> {
Box<Integer> box = new Box<>();
//利用反射,获取Box类的字节码文件的Class类对象
Class clazz = box.getClass();
//获取box所有成员变量
Field[] fields = clazz.getDeclaredFields();
//打印所有成员变量的名称和类型
for (Field field:fields) {
System.out.println(field.getName() + ":" + field.getType().getSimpleName());
}
下限是转换成Object
擦除方法中类型定义的参数
System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName());
桥接方法
5. 泛型数组
泛型数组的创建:
- 可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象
泛型会在编译前类型擦除,而数组会在整个编译运行阶段持有其初始类型,这两个起了冲突
List<String>[] lists = new ArrayList[10];//注意,后面这块儿不要使用泛型
List<String> strList = new ArrayList<>();
strList.add("张三");
strList.add("李四");
lists[0] = strList;
System.out.println(Arrays.toString(lists));
- 可以通过
java.lang.reflect.Array
的newInstance(Class<T>,int)
创建T[]数组
public static void main(String[] args) {
List<String>[] lists = (List<String>[]) Array.newInstance(ArrayList.class,10);
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
lists[0] = list;
System.out.println(Arrays.toString(lists));
}
6. 泛型和反射
反射常用的泛型类:
- Class
/*
快捷键
类名.class.var+回车
*/
Class<Test> testClass = Test.class;
- Constructor
/*
快捷键
testClass.getConstructor().var
*/
Constructor<Test> constructor = testClass.getConstructor();
//利用反射创建对象
Test test = constructor.newInstance();
对比不使用泛型创建对象
Class testClass1 = Test.class;
Constructor constructor1 = testClass1.getConstructor();
//只能创建Object对象,想要获取Test对象,需要强转
Object o = constructor1.newInstance();
相关文章
- Java 对象和类
- [转]JAVA泛型通配符T,E,K,V区别,T以及Class<T>,Class<?>的区别
- 2014年9月14号阿里巴巴校招笔试题 一道题的JAVA实现
- java在Linux执行命令Java在Windows执行命令
- Java 让Map value自增
- 【JAVA问题解决方案】01.EasyExcel导出数据超过Excel单表上限解决方案
- maven项目的java和resources等文件夹不在Java Resources的文件夹里,并且缺少Deployment...
- Java程序员如何在竞争中保持优势
- java 反射和泛型-反射来获取泛型信息
- java为什么要用类型擦除实现泛型?--c++,java,c# 的泛型是如何实现的
- Error:Java 8 language support, as requested by 'android.enableD8.desugaring= true'
- java泛型中<?>和<T>有什么区别?
- Java学习-050-AES256 之 java.security.InvalidKeyException: Illegal key size or default parameters 解决方法
- Java_并发工具包 java.util.concurrent 用户指南(转)
- Java_java动态编译整个项目,解决jar包找不到问题
- Java泛型与类型擦除
- Java泛型解析(01):认识泛型
- java.lang.NumberFormatException
- Java Collections.sort 排序
- 2022 年 25 大 Java 8 面试问题和答案 - 从基础到有经验
- Java泛型
- Java hutool/java 常用方法