案例引入:
想象你开了一家快递站,但客户可能寄任何东西(衣服、零食、玻璃杯)
传统:什么都往盒子里面装。。。
但是有了泛型,也就是相当于给盒子贴类型标签,分类型去装,例如:
快递盒<衣服>
→ 只能放衣服快递盒<玻璃杯>
→ 只能放玻璃杯
小案例:
// 泛型学习
package com.logindemo;
import java.sql.SQLOutput;
// 定义一个泛型类:盒子(可以装任何类型的东西)
class Box<T>{
private T content;
public void put(T item) {
this.content = item;
}
public T get() {
return content;
}
}
public class GenericDemo1 {
public static void main(String[] args) {
// 创建一个装String的盒子
Box<String> stringBox = new Box<String>();
stringBox.put("Hello");
// stringBox.put(123); 这行会编译报错,因为只能放String
Box<Integer> integerBox = new Box<Integer>();
integerBox.put(456);
// 取出内容时不需要强制类型转换
String string = stringBox.get();
Integer integer = integerBox.get();
System.out.println("字符串盒子: " + string);
System.out.println("数字盒子: " + integer);
}
}
运行结果:
泛型概念:
泛型,可以用于类,接口,集合的定义中,在实体化对象时将泛型的部分变为具体的数据类型:
//这里以集合为例
//底层源码public class ArrayList<E>,E为泛型,在实体化对象时,Dog替代了E
ArrayList<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog("tom"));
dogs.add(new Dog("happt"));
//加入别的类则会报错
//dogs.add(new Cat("cat"));
(1)编译时就拦住错误类型,即往箱子里放猫 dogs.add(new Cat())
时,就会立刻被红线警告,不用等运行
(2)
-
不用泛型:从箱子里拿出东西时,要先判断是不是狗(
Object -> Dog
强制转换) -
用泛型:直接确定是狗,拿出来就能撸狗(
Dog dog = dogs.get(0)
)
泛型的好处:
- 适用于多种数据类型执行相同的代码
- 跳过了向下转型,不需要强制类型转换
泛型的细节:
1. 泛型具体化时只能是引用类型,而不能是基本数据类型。
例如是Integer,而不能是int:
//1.泛型中实现的只能是引用型数据类型 ArrayList<Integer> integers = new ArrayList<>(); //ArrayList<int> ints = new ArrayList<int>();
2. 若没有给泛型具体化,系统会默认其为Object类型。
3. 泛型具体化后,其输入的类型可以是本类型,也可以是本类型的子类。
4. 泛型不能用于修饰类的静态成员。
因为泛型类在类的实体化时进行具体化,而静态成员则是在类加载时就已启动,此时泛型还不能确定具体类型。
5. 泛型数组不能初始化。
因为在确定具体类型之前,每个元素的大小是不确定的,也就不能提前分配空间。
自定义泛型类:
在类名后加上<E,J,T,...>
(不限个数),然后用于修饰类的方法/属性/返回值,在类实体化时赋予其相应的实际类型
小案例:
package com.logindemo;
// 键值对
import java.util.HashMap;
import java.util.Map;
// 定义一个泛型类,可以存储任意类型的键值对
class GenericStorage<K, V> { // K代表键的类型,V代表值的类型
private final Map<K, V> storage = new HashMap<>();
/**
* 存储键值对
*
* @param key 键
* @param value 值
*/
public void put(K key, V value) {
storage.put(key, value);
}
/**
* 根据键获取值
*
* @param key 键
* @return 对应的值
*/
public V get(K key) {
return storage.get(key);
}
/**
* 显示所有存储的内容
*/
public void displayAll() {
System.out.println("存储内容:");
for (Map.Entry<K, V> entry : storage.entrySet()) {
System.out.println(entry.getKey() + " => " + entry.getValue());
}
}
}
public class GenericDemo2 {
public static void main(String[] args) {
// 创建一个存储<String, Integer>类型的GenericStorage实例
GenericStorage<String, Integer> ageStorage = new GenericStorage<>();
// 添加数据
ageStorage.put("张三", 25);
ageStorage.put("李四", 30);
// ageStorage.put(123, "测试"); // 这行会编译错误,因为类型不匹配
// 获取数据
System.out.println("李四的年龄是:" + ageStorage.get("李四"));
// 显示所有数据
ageStorage.displayAll();
// 创建一个存储<Integer, String>类型的GenericStorage实例
GenericStorage<Integer, String> productStorage = new GenericStorage<>();
// 添加数据
productStorage.put(1001, "手机");
productStorage.put(1002, "笔记本电脑");
// 获取数据
System.out.println("产品1002是:" + productStorage.get(1002));
// 显示所有数据
productStorage.displayAll();
}
}
运行结果:
自定义泛型接口:
泛型接口即是在其被其他接口继承/被类实现时。一旦实体化,其内部的方法,属性都会自动变为具有实体化属性的形式。方法参数/返回值类型自动同步替换。
例如 : 泛型实体化后,这些方法里的 M/S
都会变成具体的 String/String
小案例:
MoneyBox.java-----定义一个泛型存钱罐接口
package com.logindemo;
/**
* 泛型存钱罐接口
* @param <M> 钱币类型(可以是人民币、美元等)
* @param <S> 存钱罐形状(圆形、方形等)
*/
interface MoneyBox<M, S> {
// 抽象方法:存钱(必须由具体存钱罐实现)
void save(M money);
// 默认方法:展示存钱罐形状(可以直接用,也能被重写)
default void showShape(S shape) {
System.out.println("默认存钱罐形状:" + shape);
}
}
RMBRoundBox.java-----实现具体存钱罐(泛型实体化)
package com.logindemo;
/**
* 人民币圆形存钱罐(把泛型M和S具体化为String和String)
*/
class RMBRoundBox implements MoneyBox<String, String> { // 这里把 M 换成 String ; 把 S 换成 String
@Override
public void save(String money) { // 抽象方法:必须实现,且参数类型会变成具体类型
System.out.println("存入人民币:" + money);
}
// 可选择重写默认方法
@Override
public void showShape(String shape) { //默认方法 :会自动变成具体类型
System.out.println("[高级定制] 存钱罐形状:" + shape);
}
}
DollarSquareBox.java
package com.logindemo;
/**
* 美元方形存钱罐(把泛型M和S具体化为Integer和String)
*/
class DollarSquareBox implements MoneyBox<Integer, String> {
@Override
public void save(Integer money) {
System.out.println("存入美元:" + money + "刀");
}
// 不重写showShape(),直接使用接口的默认实现
}
Test.java------测试代码
package com.logindemo;
public class Test {
public static void main(String[] args) {
// 人民币圆形存钱罐
RMBRoundBox rmbBox = new RMBRoundBox();
rmbBox.save("100元"); // 输出:存入人民币:100元
rmbBox.showShape("圆形"); // 输出:[高级定制] 存钱罐形状:圆形
// 美元方形存钱罐
DollarSquareBox dollarBox = new DollarSquareBox();
dollarBox.save(50); // 输出:存入美元:50刀
dollarBox.showShape("方形"); // 输出:默认存钱罐形状:方形
}
}
运行结果:
泛型方法:
1.普通类中的泛型方法:
Printer.java
// 定义一个万能打印机(普通类)
class Printer {
// 泛型方法:可以打印任何类型
public <T> void print(T thing) {
System.out.println("正在打印:" + thing);
}
}
Test.java
public class Test {
public static void main(String[] args) {
Printer printer = new Printer();
// 不需要手动指定类型,编译器自动判断
printer.print("字符串"); // 自动识别 T = String
printer.print(100); // 自动识别 T = Integer
printer.print(3.14); // 自动识别 T = Double
}
}
运行结果:
普通方法:只能打印String
泛型方法:能打印任何类型
2.泛型类中的泛型方法
class C<M> { // 带M的类
// 方法自带独立的<M,T>(和类的M无关!!!!)
public <M, T> void f2(M m, T t) {
System.out.println(m.getClass()); // 由调用时的参数决定
System.out.println(t.getClass());
}
}
// 使用:
C<ArrayList> c = new C<>(); // 类的M=ArrayList
c.f2(1, 1.1f); // 方法的M=Integer, T=Float
运行结果:
class java.lang.Integer
class java.lang.Float
方法的<M>
和类的<M>
是两个独立插槽,互不干扰
泛型的继承:
具体说是其中的多态性质,泛型是不具有多态性质的,所以==两边的泛型实体化类型要相同,不存在什么父类子类
//错误示例
D<Object> objectD = new D<String>();
通配符:
(1)List<?>-------
允许任何类型的实体化
List<?> c = new ArrayList<任何类型>();
通配符 | 能赋值给它的集合(只能实体化的) | 能添加的元素 | 能读取的类型 |
---|---|---|---|
<? extends AA> | AA /BB /CC 的集合 | 不能添加任何元素 | 视为AA |
<? super AA> | AA /Object 的集合 | AA 及其子类(BB /CC ) | 视为Object |
(2)List<? extends AA>-------
只能取不能存
//规则: AA是爷爷,BB是爸爸,CC是儿子
class AA{}
class BB extends AA{}
class CC extends BB{}
List<? extends AA> list1 = new ArrayList<AA>(); // ✅
list1 = new ArrayList<BB>(); // ✅
list1 = new ArrayList<CC>(); // ✅
// 尝试添加元素:
// list1.add(new AA()); // ❌ 编译错误!
// list1.add(new BB()); // ❌ 编译错误!
// list1.add(new CC()); // ❌ 编译错误!
// 允许读取:
AA a = list1.get(0); // ✅ 只能视为AA
(3)List<? super AA>------
能存AA及其子类,取只能Object
List<? super AA> list2 = new ArrayList<AA>(); // ✅
list2 = new ArrayList<Object>(); // ✅
// 允许添加:
list2.add(new AA()); // ✅
list2.add(new BB()); // ✅ (因为BB是AA的子类)
list2.add(new CC()); // ✅ (因为CC是AA的子类的子类)
// 不允许添加:
// list2.add(new Object()); // ❌ 编译错误!
// 读取时:
Object obj = list2.get(0); // ✅ 只能视为Object
// AA a = list2.get(0); // ❌ 编译错误!
为什么这样设计?
-
extends
安全性:-
如果允许
list1.add(new BB())
,但list1
可能是ArrayList<CC>
,会导致类型污染。
-
-
super
灵活性:-
允许存入子类,因为子类一定能向上转型为
AA
或Object
。
-