java泛型和通配符
java泛型/通配符
泛型
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(表示Java 类,包括基本的类和我们自定义的类)
- K - Key(表示键,比如Map中的key)
- V - Value(表示值)
- N - Number(表示数值类型)
- ? - (表示不确定的java类型)
为什么会有泛型?
1.需要解决代码冗余,提高复用性
2.需要编译期的检查
编译期检查
ArrayList<Animal> arrayList = new ArrayList<>();
arrayList.add(dog);
arrayList.add(new Object());//编译器这里会报错
运行结果
java: 对于add(java.lang.Object), 找不到合适的方法
提高代码复用性
Dog
和 Cat
分别继承 Animal
/* void run(Dog t) {
System.out.println(t);
}
void run(Cat t) {
System.out.println(t);
}
void run(Animal t) {
System.out.println(t);
}*/
void run(T t) {
System.out.println(t);
}
T
可以传入任意类型,和Object
一样,但一般都会给泛型一个界限,有助于编译期发现问题
//如果T 指定为Animal类型 则
think.run(dog);
think.run(cat);
think.run(new Car());//编译期会报错
think.run(new ArrayList<>());//编译期会报错
泛型使用
示例
测试类
package com.huke;
import java.util.ArrayList;
/**
* Author:huke
* DATE 2023/3/29 10:00
* 泛型
*/
public class GenericsTest<T extends Animal,V extends Car> {
public static void main(String[] args) {
GenericsTest<Animal> genericsTest = new GenericsTest<>();
Dog dog = new Dog();
Cat cat = new Cat();
ArrayList<Animal> arrayList = new ArrayList<>();
arrayList.add(dog);
arrayList.add(cat);
Think<Animal, Car> think = new Think<>(arrayList);//指定泛型T,V的界限
think.run(dog);
think.run(cat);
think.run(new Car());//编译期会报错
think.run(new ArrayList<>());//编译期会报错
}
}
Think
类
package com.huke;
import java.util.ArrayList;
/**
* Author:huke
* DATE 2023/3/29 10:23
*/
public class Think<T, V> {
T t;
V v;
ArrayList<T> arrayList;
Think(ArrayList<T> arrayList) {
this.arrayList = arrayList;
}
void run() {
this.arrayList.forEach(a -> System.out.println(a.toString()));
}
/* void run(Dog t) {
System.out.println(t);
}
void run(Cat t) {
System.out.println(t);
}
void run(Animal t) {
System.out.println(t);
}*/
void run(T t) {
System.out.println(t);
}
}
泛型在创建对象时就必须确定,如果没有确定则会使用Object
public class WailCardTest<T> {
public static void main(String[] args) {
WailCardTest<Object> wailCardTest = new WailCardTest<>();
}
}
静态方法的泛型
//静态方法需要声明泛型 <T, V>
public static <T, V> T playBall(T t, V v) {
System.out.println("动物们玩球:t:" + t + ",v:" + v);
return t;
}
如果不指定无法通过编译期
// 报错:java: 无法从静态上下文中引用非静态 类型变量 T
public static T playBall(T t, V v) {
System.out.println("动物们玩球:t:" + t + ",v:" + v);
return t;
}
静态方法 泛型使用需要方法泛型定义
主要原因
1.Java中的静态方法属于类级别,在类加载时加载进内存,普通方法是在类实例化时才被加载进内存,因此类级别无法访问任何实例变量或方法。对于泛型而言对象不创建泛型无法确认.而静态方法不需要实例化既可以访问
2.当静态方法使用泛型时,Java编译器无法推断出泛型类型,因为在编译时它不知道类被实例化时会传入哪种类型。因此,需要在方法上定义泛型,以便告诉编译器需要使用哪种类型,以便将泛型类型替换为实际类型
通配符
解决了什么问题?
1.使用泛型时难以选择具体类型
2.不希望使用Object类型
3.希望进行编译期检查
通配符使用
通配符不能作为参数入参只能作为引用参数
package com.huke;
import java.util.ArrayList;
/**
* Author:huke
* DATE 2023/3/29 15:04
* 通配符
*/
public class WailCardTest<T> {
public static void main(String[] args) {
WailCardTest<Object> wailCardTest = new WailCardTest<>();
ArrayList<Cat> cats = new ArrayList<>();
cats.add(new Cat());
wailCardTest.play(cats);
ArrayList<Car> cars = new ArrayList<>();
cars.add(new Car());
wailCardTest.play(cars);
System.out.println("play方法执行完毕!");
ArrayList<Animal> animals = new ArrayList<>();
animals.add(new Cat());
wailCardTest.showBySuper(animals);
}
private void play(ArrayList<?> arrayList) {
arrayList.forEach(System.out::println);
}
private void showByExtends(ArrayList<? extends Animal> arrayList) {
arrayList.forEach(System.out::println);
//arrayList.add(new Cat()); //报错
}
private void showBySuper(ArrayList<? super Animal> arrayList) {
arrayList.forEach(System.out::println);
System.out.println("---------");
arrayList.add(new Cat()); //不报错
Object object = arrayList.get(0);
System.out.println("arrayList[0]:"+object);//但是取出来直接成为Object
arrayList.forEach(System.out::println);
}
}
输出
Cat
Car
play方法执行完毕!
Cat
---------
arrayList[0]:Cat
Cat
Cat
错误写法
void test(? t){//编译不通过
}
//可以改为
void test(ArrayList<?> arrayList) {
arrayList.forEach(System.out::println);
}
ArrayList<?> arrayList = new ArrayList<>();//这种写法
arrayList.add(new Cat());//编译不通过
//可以改为
ArrayList<? super Animal> arrayList = new ArrayList<>();
arrayList.add(new Cat());
pecs概念
pecs
全称是Producer Extends Consumer Super
使用extends确定上界的只能是生产者,只能往外生产东西,取出的就是上界类型。不能往里塞东西。
使用Super确定下界的只能做消费者,只能往里塞东西。取出的因为无法确定类型只能转成Object类型
- 用于灵活写入,主要目的是统一使用父类的容器,使得对象可以写入父类型的容器。或者用于比较,使得父类型的比较方法可以应用于子类对象。
- 用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象。
示例
//上限
private void showByExtends(ArrayList<? extends Animal> arrayList) {
arrayList.forEach(System.out::println);
//arrayList.add(new Cat()); //报错
}
//下限
private void showBySuper(ArrayList<? super Animal> arrayList) {
arrayList.forEach(System.out::println);
System.out.println("---------");
arrayList.add(new Cat()); //不报错
Object object = arrayList.get(0);
System.out.println("arrayList[0]:"+object);//但是取出来直接成为Object
arrayList.forEach(System.out::println);
}
泛型擦除
什么是泛型擦除?
泛型是个语法糖,只存在于编译器中。而不存在于虚拟机(JVM)中
编译阶段:编译器对带有泛型的java代码进行编译时,会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,供JVM接收并执行,此时泛型信息被擦除
示例
测试类
package com.huke;
/**
* Author:huke
* DATE 2023/3/29 17:34
*/
public class GenericsDeleteTest {
public static void main(String[] args) {
Phone<String > phone = new Phone<>();
phone.setT("绿色");
String color = phone.getT();
System.out.println(color);
}
}
Phone
package com.huke;
/**
* Author:huke
* DATE 2023/3/29 17:35
*/
public class Phone <T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
反编译字节码
public class com.huke.GenericsDeleteTest {
public com.huke.GenericsDeleteTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/huke/Phone
3: dup
4: invokespecial #3 // Method com/huke/Phone."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String 绿色
11: invokevirtual #5 // Method com/huke/Phone.setT:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #6 // Method com/huke/Phone.getT:()Ljava/lang/Object;
18: checkcast #7 // class java/lang/String
21: astore_2
22: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
25: aload_2
26: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: return
}
分析
11:开始将String类型转为Object
15:get后得到Object类型
18:检查类型时候可以转换,转为String
泛型和通配符的区别?
相同点
都可以接收未知参数,都可以指定类型界限
不同点
通配符
当设置泛型通配符上限的时候,只能读取不能写入
当设置泛型通配符下限的时候,可以写入,读取出来就是Object类型
泛型
可以对集合进行添加操作,因为调用泛型方法的时候,类型就已经确定了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix