-
Notifications
You must be signed in to change notification settings - Fork 124
Java 泛型
泛型,英文为Generics,其本质是参数化类型,也就是将具体的数据类型指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。 简单来说,泛型允许程序员在编写代码时不指定具体的数据类型,而是在使用时才指定类型。这使得同一段代码可以适用于多种数据类型,而不需要为每种类型都编写重复的代码
泛型的主要作用
- 类型安全性
泛型的核心作用是提供编译时类型安全检查,避免运行时的ClassCastException异常。
在没有泛型之前,集合类如List、Set等只能存储Object类型的元素,这就导致在取出元素时必须进行显式的类型转换,容易出错:
// 没有泛型的情况
List list = new ArrayList();
list.add("Hello");
list.add(100); // 可以添加任何类型
String str = (String)list.get(0); // 需要强制转换
Integer num = (Integer)list.get(1); // 运行时可能抛出ClassCastException
使用泛型后,可以在编译时就限制集合中元素的类型:
// 使用泛型
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
// stringList.add(100); // 编译错误,无法添加非String类型
String str = stringList.get(0); // 无需强制转换
通过这种方式,泛型将运行时可能出现的类型错误转移到了编译时,大大提高了程序的可靠性。
- 消除强制类型转换 泛型消除了源代码中的许多强制类型转换,使代码更加简洁易读。
非泛型代码示例:
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0); // 需要强制转换
泛型代码示例:
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // 无需强制转换
- 提高代码复用性 泛型使得可以编写通用的代码模板,适用于不同类型的对象,而不需要为每种类型都编写重复的代码。
例如,一个简单的泛型Box类可以存储任何类型的值:
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 使用示例
Box<Integer> intBox = new Box<>();
intBox.set(10);
Integer intValue = intBox.get();
Box<String> strBox = new Box<>();
strBox.set("Hello");
String strValue = strBox.get();
同样的Box类可以用于存储Integer、String或其他任何类型的对象,无需为每种类型创建单独的类。
- 性能优化 虽然泛型的主要优势不在于性能,但它确实可以带来一些性能上的好处:
避免了装箱(boxing)和拆箱(unboxing)操作:当使用泛型而非Object类型时,对于基本数据类型的包装类(如Integer、Double等),可以避免不必要的装箱和拆箱操作。 减少了类型检查的开销:由于类型在编译时就已经确定,运行时不需要额外的类型检查
- 泛型类 泛型类是在类定义时声明类型参数的类,允许在实例化类时指定具体的类型。
定义语法:
修饰符 class 类名<类型变量, 类型变量, ...> {
// 类体
}
示例:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用示例
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String content = stringBox.getContent();
- 泛型接口 泛型接口是在接口定义时声明类型参数的接口,允许在实现接口时指定具体的类型。
定义语法:
修饰符 interface 接口名<类型变量, 类型变量, ...> {
// 接口方法
}
示例:
java
public interface Processor<T> {
T process(T input);
}
// 实现泛型接口
public class StringProcessor implements Processor<String> {
@Override
public String process(String input) {
return input.toUpperCase();
}
}
- 泛型方法 泛型方法是在方法定义时声明类型参数的方法,允许在调用方法时指定或推断具体的类型。
定义语法:
修饰符 <类型变量, 类型变量, ...> 返回值类型 方法名(形参列表) {
// 方法体
}
示例:
java
public class Utils {
// 泛型方法
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// 使用示例
String[] strings = {"A", "B", "C"};
Utils.swap(strings, 0, 2); // 交换位置0和2的元素
需要注意的是,泛型方法与泛型类中使用泛型类型参数的普通方法是不同的。只有声明了类型参数的方法才是泛型方法
泛型擦除是Java编译器处理泛型的一种机制,它会在编译期间移除所有泛型类型信息,在运行时JVM并不知道泛型的具体类型。这是Java为了向后兼容而采取的设计决策。
擦除的具体表现
-
类型参数被替换:
-
无界类型参数(如)被替换为Object
-
有界类型参数(如)被替换为边界类型(Number)
-
类型检查:
-
类型检查只在编译时进行
-
运行时无法获取泛型的实际类型参数
擦除带来的影响
1.无法直接实例化泛型类型:
T obj = new T(); // 编译错误
2.无法创建泛型数组:
T[] array = new T[10]; // 编译错误
3.instanceof检查无效:
if (list instanceof List<String>) // 编译错误
4.静态成员共享:
class MyClass<T> {
static T value; // 错误,静态成员不能使用类型参数
}
5.桥接方法
桥接方法是Java编译器为了解决类型擦除与多态性冲突而自动生成的一种合成方法,主要用于泛型继承和接口实现场景。
- 泛型方法重写
class Parent<T> {
public void set(T t) {}
}
class Child extends Parent<String> {
@Override
public void set(String s) {} // 擦除后签名与父类不同
}
编译器会生成一个桥接方法:
public void set(Object o) {
set((String) o); // 调用实际的set(String)方法
}
- 协变返回类型
class Parent {
Object get() { return null; }
}
class Child extends Parent {
@Override
String get() { return ""; } // 返回类型不同
}
编译器生成桥接方法:
public Object get() {
return get(); // 调用String get()方法
}
关键字说明
- ? 通配符类型
- extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类
- super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object
换成白话是这个意思:
List<? extends T> 是说 这个list放的是T或者T的子类型的对象,但是不能确定具体是什么类型,所以可以get(),不能add()(可以add null值)
List<? super T> 是说这个list放的是至少是T类型的对象,所以我可以add T或者T的子类型,但是get得到的类型不确定,所以不能get
extends 示例
static class Food{}
static class Fruit extends Food{}
static class Apple extends Fruit{}
static class RedApple extends Apple{}
List<? extends Fruit> flist = new ArrayList<Apple>();
// complie error:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // only work for null
List<? extends Frut> 表示 “具有任何从Fruit继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List 赋值。
Fruit fruit = flist.get(0);
Apple apple = (Apple)flist.get(0);
由于,其中放置是从Fruit中继承的类型,所以可以安全地取出Fruit类型。
flist.contains(new Fruit());
flist.contains(new Apple());
在使用Collection中的contains 方法时,接受Object 参数类型,可以不涉及任何通配符,编译器也允许这么调用。
super 示例
List<? super Fruit> flist = new ArrayList<Fruit>();
flist.add(new Fruit());
flist.add(new Apple());
flist.add(new RedApple());
// compile error:
List<? super Fruit> flist = new ArrayList<Apple>();
List<? super Fruit> 表示“具有任何Fruit超类型的列表”,列表的类型至少是一个 Fruit 类型,因此可以安全的向其中添加Fruit 及其子类型。由于List<? super Fruit>中的类型可能是任何Fruit 的超类型,无法赋值为Fruit的子类型Apple的List.
// compile error:
Fruit item = flist.get(0);
因为,List<? super Fruit>中的类型可能是任何Fruit 的超类型,所以编译器无法确定get返回的对象类型是Fruit,还是Fruit的父类Food 或 Object.
- JMM与volatile关键字
- synchronized的实现原理
- synchronized等待与唤醒机制
- ReentrantLock的实现原理
- ReentrantLock等待与唤醒机制
- CAS、Unsafe类以及Automic并发包
- ThreadLocal的实现原理
- 线程池的实现原理
- Java线程中断机制
- 多线程与并发常见面试题
- Android基础知识汇总
- MVC、MVP与MVVM
- SparseArray实现原理
- ArrayMap的实现原理
- SharedPreferences
- Bitmap
- Activity的启动模式
- Fragment核心原理
- 组件化项目架构搭建
- 组件化WebView架构搭建
- 为什么 Activity.finish() 之后 10s 才 onDestroy ?
- Android系统启动流程
- InputManagerService
- WindowManagerService
- Choreographer详解
- SurfaceFlinger
- ViewRootImpl
- ActivityManagerService
- APP启动流程
- PMS安装与签名校验
- Dalvik与ART
- 内存优化策略
- UI界面及卡顿优化
- App启动优化
- ANR问题
- 包体积优化
- APK打包流程
- 电池电量优化
- Android屏幕适配
- 线上性能监控1--线上监控切入点
- 线上性能监控2--Matrix实现原理
- Glide实现原理
- OkHttp实现原理
- Retrofit实现原理
- RxJava实现原理
- RxJava中的线程切换与线程池
- LeakCanary实现原理
- ButterKnife实现原理
- ARouter实现原理
- Tinker实现原理
- 2. 两数相加
- 19.删除链表的倒数第 N 个结点
- 21. 合并两个有序链表
- 24. 两两交换链表中的节点
- 61. 旋转链表
- 86. 分隔链表
- 92. 反转链表 II
- 141. 环形链表
- 160. 相交链表
- 206. 反转链表
- 206 反转链表 扩展
- 234. 回文链表
- 237. 删除链表中的节点
- 445. 两数相加 II
- 面试题 02.02. 返回倒数第 k 个节点
- 面试题 02.08. 环路检测
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 35. 复杂链表的复制
- 1. 两数之和
- 11. 盛最多水的容器
- 53. 最大子序和
- 75. 颜色分类
- 124.验证回文串
- 167. 两数之和 II - 输入有序数组 -169. 多数元素
- 189.旋转数组
- 209. 长度最小的子数组
- 283.移动0
- 303.区域和检索 - 数组不可变
- 338. 比特位计数
- 448. 找到所有数组中消失的数字
- 643.有序数组的平方
- 977. 有序数组的平方