基本概念
在 Java 中,泛型是一种强类型约束机制,可以在编译期间检查类型安全性,并且可以提高代码的复用性和可读性。
为什么要设计泛型?
举个集合的例子:
这段代码是有泛型,并且会限定集合中类型是String:
Arraylist<String> list = new ArrayList<String>();
list.add("string类型");
String str = list.get(0);
假如没有泛型,以使用 Object 数组来设计 Arraylist
类。:
class Arraylist {
private Object[] objs;
private int i = 0;
public void add(Object obj) {
objs[i++] = obj;
}
public Object get(int i) {
return objs[i];
}
}
存取数据:
Arraylist list = new Arraylist();
list.add("string类型");
list.add(1111);
String str = (String)list.get(0);
可以看到使用了Object定义集合类型后,集合可以存储所有继承Object类的数据。这样的话从集合取出数据的话就需要进行强制转换,也是很麻烦很容易出错。
所以泛型优点就是:使用类型参数解决了元素的不确定性
再比如:
有个试想你需要一个简单的容器类,比如要存放一个苹果的篮子
class Fruit{}
class Apple extends Fruit{}
class Bucket{
private Apple apple;
public void set(Apple apple){
this.apple = apple;
}
public Apple get(){
return this.apple;
}
}
水果多了,需要不同容器:
class Fruit{}
class Apple extends Fruit{}
class Banana extends Fruit{}
class Orange extends Fruit{}
class BucketApple{
private Apple apple;
public void set(Apple apple){
this.apple = apple;
}
public Apple get(){
return this.apple;
}
}
class BucketBanana{
private Banana banana;
public void set(Banana banana){
this.banana = banana;
}
public Banana get(){
return this.banana;
}
}
class BucketOrange{
private Orange orange;
public void set(Orange orange){
this.orange = orange;
}
public Orange get(){
return this.orange;
}
}
大量重复,要写一个Object类型的Bucket就可以存放任何类型了
class Bucket{
private Object object;
public void set(Object object){
this.object = object;
}
public Object get(){
return this.object;
}
}
但是问题是这种容器的类型丢失了,你不得不在输出的地方加入类型转换:
Bucket appleBucket = new Bucket();
bucket.set(new Apple());
Apple apple = (Apple)bucket.get();
泛型使用
泛型类
格式:
修饰符 class 方法名<类型>
示例:
public class GenericsMutilClass<K,V> { //此处可以是任意的标识符号,T 是 type 的简称
private K param1; // 此变量的类型由外部决定
private V param2; // 此变量的类型由外部决定
public void getParams(){
System.out.println(param1);
System.out.println(param2);
}
public void setParam1(K param1) {
this.param1 = param1;
}
public void setParam2(V param2) {
this.param2 = param2;
}
}
class Test{
public static void main(String[] args) {
GenericsMutilClass<String,Integer> genericsMutilClass = new GenericsMutilClass<>();
genericsMutilClass.setParam1("zs");
genericsMutilClass.setParam2(20);
genericsMutilClass.getParams();
}
}
泛型方法
格式:
修饰符 <T,E,…> 返回值类型 方法名(形参列表){
....
}
示例:
/**
<T> T可以传入任何类型的list
关于参数T的说明:
第一个T表示<T>是一个泛型
第二个T表示方法返回的是T类型的数据
第三个T表示集传入的数据是T类型
*/
public class GenericsMethod {
public <T> T genericsMethod(T t){
return t;
}
}
class GenericsMethodTest{
public static void main(String[] args) {
GenericsMethod genericsMethod = new GenericsMethod();
Integer integer = genericsMethod.genericsMethod(111);
System.out.println(integer);
}
}
/**
多参数
*/
public class GenericMethod {
//3.带可变参数的泛型方法
public <A> void argsMethod(A ... args){
for (A arg : args) {
System.out.println(arg);
}
}
}
泛型接口
格式:
修饰符 interface 接口名<类型>{
.....
}
示例:
public interface GenericsInterface<T> {
T genericsInterface ();
void genericsInterface(T t);
}
class GenericsInterfaceClass implements GenericsInterface<String>{ //实现的时候指定类型
@Override
public String genericsInterface() {
return null;
}
@Override
public void genericsInterface(String s) {
}
}
泛型上下限
格式:
- 上界通配符(
? extends T
):表示参数化类型的可能是T或T的某个子类型。它限制了未知类型的上限。上界通配符是为了安全地读取T类型数据而设计的。- 下界通配符(
? super T
):表示参数化类型是T或T的某个父类型。下界通配符让咱们可以安全地写入T和T的子类型的对象。
示例:
public class GenericsInterExtent {
}
class GenericsInterExtent1 extends GenericsInterExtent{
}
class GenericsInterExtentTest{
public <T extends GenericsInterExtent> T genericsInterExtent(){
T t = null;
GenericsInterExtent1 genericsInterExtent1 = new GenericsInterExtent1();
t = (T)genericsInterExtent1;
return t;
}
}
class GenericsInterSupperTest{
public <T> void genericsIntersuper(GenericsInterExtent1 t, List<? super GenericsInterExtent1> list){
list.add(t);
}
}
泛型擦除
在 Java 的泛型机制中,有两个重要的概念:类型擦除和通配符。
如在List<Object>,List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型的附加信息对JVM是看不到的。Java编译器会在编译期间尽可能的发现出错的地方,但是无法在运行时刻出现类型转换和类型转换异常的情况。
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());//true
}
}
泛型在编译时会将泛型类型擦除,将泛型类型替换成 Object 类型。这是为了向后兼容,避免对原有的 Java 代码造成影响。
如,对于下面的代码:
List<Integer> intList = new ArrayList<>();
intList.add(123);
int value = intList.get(0);
在编译时,Java 编译器会将泛型类型 List<Integer>
替换成 List
,将 get 方法的返回值类型 Integer 替换成 Object,生成的字节码与下面的代码等价:
List intList = new ArrayList();
intList.add(Integer.valueOf(123));
int value = (Integer) intList.get(0);
Java 泛型只在编译时起作用,运行时并不会保留泛型类型信息。
为什会有泛型擦除呢?
是指在Java中,编译器在生成字节码时会擦除泛型类型信息。这个设计是为了兼容Java旧版本的字节码,因为泛型是在Java 5引入的,早期的Java版本并不支持泛型。擦除泛型类型信息可以使得新引入的泛型代码可以与旧版本的Java代码兼容运行。
具体来说,泛型擦除带来了以下几点原因和影响:
- 兼容性:通过擦除泛型信息,可以使得使用泛型编写的新代码可以在旧版本的Java虚拟机上运行,因为旧版本的JVM不支持泛型。
- 类型安全:尽管在运行时泛型类型信息被擦除了,但在编译时编译器会进行类型检查,从而保证类型安全性。这种方式通过静态类型检查确保泛型代码在编译时没有类型错误。
- 性能:泛型擦除可以减少生成的字节码的大小,从而提高性能和减少内存占用。这是因为泛型信息只在编译期间起作用,在运行时不需要保留。
总结来说,泛型擦除是为了在引入泛型后保持与旧版本Java代码的兼容性,并通过编译时的类型检查确保类型安全,尽管它在运行时会导致泛型类型信息丢失的情况。
带来的问题:
不能实例化泛型类型的数组:
T[] array = new T[10]; // 编译错误
这是因为在运行时,JVM需要知道数组的确切类型,而由于类型擦除,这个信息是不可知的。
不能实例化泛型类的类型参数:
public class GenericClass<T> {
T obj = new T(); // 编译错误
}
同样是因为在运行时,T
的具体类型是未知的。
不能创建具体类型的泛型数组:
List<Integer>[] arrayOfLists = new List<Integer>[10]; // 编译错误
这违反了Java的类型安全原则,因为泛型类型在运行时会被擦除,导致数组的实际类型只能是List[]
。
泛型类不能扩展Throwable
:
public class GenericException<T> extends Exception { } // 编译错误
这是因为异常处理是在运行时进行的,需要知道异常的确切类型。
泛型实际应用
数据类型转换:
public class DataConverter<T> {
public <U> U convert(T data, Class<U> targetClass) throws Exception {
// 实现数据转换逻辑
// U u = targetClass.getDeclaredConstructor().newInstance()
U u = conver(data,targetClass);
return u;
}
}
class tt{
public static void main(String[] args) throws Exception {
// 数据;类型转换
DataConverter<String> converter = new DataConverter<>();
Integer convertedData = converter.convert("123", Integer.class);
}
}
再比如:
/**
* 将List<T> 转为List<K> ,注意:此方法会舍弃掉非公有字段
*
* @param t1 List<T>
* @param k1 转换后对象
* @param <T> 原始类型
* @param <K> 转换后类型
* @return 转换后对象
* @throws IllegalArgumentException 转换异常
*/
public static <T, K> List<K> listObjToListObj(List<T> t1, Class<K> k1) {
try {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(t1);
JavaType javaType = getCollectionType(mapper, k1);
return mapper.readValue(json, javaType);
} catch (IOException e) {
throw new IllegalArgumentException("listObjToListObj对象转换异常" + k1, e);
}
}