【Java SE】泛型详解
参考笔记:java 泛型 万字详解(通俗易懂)_java 泛型函数-CSDN博客
目录
1.泛型的引入
2.使用泛型举例
2.1 集合中使用泛型
2.2 比较器中使用泛型
3.什么是泛型
3.1 泛型的定义
3.2 泛型的作用
4.怎么使用泛型
4.1 泛型语法
4.2 泛型使用细节
4.3 泛型的使用
5.自定义泛型类
5.1 基本语法
5.2 使用细节
5.3 案例演示
6.自定义泛型接口
6.1 基本语法
6.2 使用细节
6.3 案例演示
7.自定义泛型方法
7.1 基本语法
7.2 使用细节
7.3 案例演示
8.泛型的补充内容
8.1 关于继承性
8.2 关于通配符(重要)
1.泛型的引入
在 Java 中声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过形参表示。在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入实参就可以了,如下图所示:
受以上启发, JDK5.0 设计了泛型的概念。泛型即为 "类型参数" ,这个类型参数可以声明在类、接口或方法,代表未知的某种通用类型
2.使用泛型举例
2.1 集合中使用泛型
集合类在设计阶段/声明阶段不能确定实际存的是什么类型的对象,所以在 JDK5.0 之前只能把元素类型设计为 Object ,JDK5.0 时 Java 引入了 "参数化类型 Parameterized type" 的概念,也就是泛型,允许我们在创建集合时指定集合元素的类型。比如:List<String>,这表明该 List 集合只能保存 String 类型的对象
集合中没有使用泛型时:
集合中使用泛型时:
案例演示
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class demo {
public static void main(String[] args) {
//1、集合中未使用泛型时
System.out.println("------------集合中未使用泛型时--------------");
List list1 = new ArrayList();
list1.add(1);//自动装箱int --> Integer
list1.add("小马");//String类型
//迭代器遍历
Iterator iter1 = list1.iterator();
while (iter1.hasNext()) {
Object o = iter1.next();
//需要强转
if (o instanceof Integer){
System.out.println((Integer) o);
}else if (o instanceof String){
System.out.println((String) o);
}
}
System.out.println("------------集合中使用泛型时--------------");
//2、集合中使用泛型时
List<String> list2 = new ArrayList();//只能添加String类型
list2.add("蔡徐坤");
list2.add("唱跳rap篮球");
//迭代器遍历
Iterator<String> iter2 = list2.iterator();
while (iter2.hasNext()) {
String s = iter2.next();//不需要强转,直接可以获取添加时的元素的数据类型
System.out.println(s);
}
}
}
运行结果:
2.2 比较器中使用泛型
java.lang.Comparable 接口和 java.util.Comparator 接口,是用于比较对象大小的接口。但是并不确定是什么类型的对象比较大小。在 JDK5.0 之前只能用 Object 类型表示,使用时既麻烦又不安全,因此 JDK5.0 之后给它们增加了泛型,如下所示:
案例演示:演示 Comparator 接口
未使用泛型前:
import java.util.Arrays;
import java.util.Comparator;
public class demo {
public static void main(String[] args) {
//创建比较器类
MyComparator comparator = new MyComparator();
Student s1 = new Student("小马", 99);
Student s2 = new Student("蔡徐坤", 70);
System.out.println(comparator.compare(s1, s2));
}
}
//比较器Comparator
class MyComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
//强制类型转换
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return s1.score >= s2.score? 1: -1;
}
}
//自定义类
class Student{
public String name;
public int score;
public Student(){}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
}
使用泛型后:
import java.util.Arrays;
import java.util.Comparator;
public class demo {
public static void main(String[] args) {
//创建比较器类
MyComparator comparator = new MyComparator();
Student s1 = new Student("小马", 99);
Student s2 = new Student("蔡徐坤", 70);
System.out.println(comparator.compare(s1, s2));
}
}
//比较器Comparator
class MyComparator implements Comparator<Student>{
@Override
public int compare(Student s1, Student s2) {
//不需要强制类型转换
return s1.score >= s2.score? 1: -1;
}
}
//自定义类
class Student{
public String name;
public int score;
public Student(){}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
}
3.什么是泛型
3.1 泛型的定义
① 泛型,又称参数化类型(Parameterized Type),是一种可以 "表示其他数据类型" 的数据类型。泛型是 JDK5.0 出现的新特性,解决数据类型的安全性问题,在类声明或实例化时只要指定好具体的类型即可
② Java泛型可以保证——如果程序在编译时没有发出警告,运行时就不会产生类型转换异常 ClassCastException ,同时使得代码更加简洁和健壮
3.2 泛型的作用
① 可以在类声明时通过一个标识来表示类中的某个属性的数据类型
② 可以表示类中某个方法的返回值的数据类型
③ 可以表示某个方法或者构造器的形参的数据类型
PS:作用效果可以理解为——将来会用指定的类型替换掉源代码中对应的"泛型"。只看文字的话可能有点抽象,下面会举个具体的例子
案例:使用 String 类型 "替换掉" Grape 类中给出的泛型 E ,代码如下:
/**
(1) 利用泛型创建对象时,就比如当前代码情况下,如果已经指定泛型<String>,
若传入非String类型,编译不通过
(2) 而如果仅对编译类型使用了泛型,构造器没有给出泛型,即写成下面这样子——
Grape<String> grape = new Grape(141);
这时候就会造成——运行期异常—— “ClasCastException”[类型转换异常]
*/
public class demo {
public static void main(String[] args) {
Grape<String> grape = new Grape<String>("grape");
System.out.println("temp = " + grape.getTemp());
System.out.println("temp's Class = " + grape.getTemp().getClass());
}
}
class Grape<E> {
/*
1. E可以表示temp变量的数据类型
该类型在定义Grape类对象时可以指定,即在编译期间确定E是什么类型
*/
private E temp;
/*
2. E也可以表示形参的数据类型,用法同上
*/
public Grape(E temp) {
this.temp = temp;
}
/*
3. E也可以表示函数的返回值的数据类型,用法同上
*/
public E getTemp() {
return temp;
}
}
运行结果:
在上面代码中,当我们创建葡萄类 Grape 对象给出 <String> 的泛型时,相当于以下效果:
class Grape<String> {
private String temp;
public Grape(String temp) {
this.temp = temp;
}
public String getTemp() {
return temp;
}
}
即将泛型 E 出现的所有地方都替换为了 String
4.怎么使用泛型
4.1 泛型语法
① interface 接口名<T> 或者 interface 接口名<K, V>
eg:ArrayList 类的定义:
public class ArrayList<E>{
....
}
② class 类名<E> 或者 class 类名<K, V>
eg:Map 接口的定义:
public interface Map<K,V>{
....
}
4.2 泛型使用细节
① 尖括号 <> 中可以填写任意字母作为泛型的标识符,一般均为大写,常用 T、K、V,分别表示 Type,Key、Value
② 字母本身不代表任何值,而代表类型,即程序员手动指定的数据类型
③ 在指定泛型时,必须要求最终确定的数据类型为引用类型,不可以是基本数据类型(基本数据类型用包装类替代即可)
④ 实际传入的类型可以是泛型指定类型的子类型
⑤ 若在定义类时使用了泛型,实例化该类时却什么都没有传入,默认使用 Object 类型
4.3 泛型的使用
从 JDK7 开始,泛型的编写形式发生了以下改变:
//JDK 7 之前的写法
List<Integer> list1 = new ArrayList<Integer>();
//JDK 7 之后更简洁的写法
List<Integer> list2 = new ArrayList<>();
即等号后边的泛型可以不用写了,称为 "菱形泛型" ,在实际开发中,菱形泛型的使用非常广泛,因此,推荐这种写法
案例演示
import java.util.ArrayList;
import java.util.Iterator;
public class demo {
public static void main(String[] args) {
//1.创建集合对象(使用泛型)
ArrayList<Integer> arrayList = new ArrayList<>();
//2.向集合中添加元素
arrayList.add(141);
arrayList.add(11);
arrayList.add(233);
arrayList.add(233);
//3.遍历集合
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer element = iterator.next();//不再需要进行类型转换
System.out.println(element);
}
}
}
5.自定义泛型类
在前文的举的 class Grape<E> 就是自定义泛型类的使用
5.1 基本语法
[修饰符] class 类名 <T,R,E ....>{//可以同时定义多个泛型
//body
}
5.2 使用细节
① 类的非静态成员可以使用泛型,比如属性、方法、构造器等
② 静态成员中不可以使用类的泛型
原因 :static 修饰的成员的初始化——在类加载时就会执行完毕,而泛型最终代表的数据类型是在创建对象时才确定的,所以,JVM 在对静态成员初始化时,还无法得知它们的实际类型,也就没法儿初始化了
③ 在自定义泛型类中,使用了泛型的数组只可以被定义,不可以被初始化
原因:JVM 无法确定数组的实际类型,也就没法在内存中开辟空间
④ 自定义泛型类中,泛型最后代表的数据类型是在创建对象时确定的
⑤ 如果在创建泛型类对象时没有给出指定类型,默认以 Object 替代
5.3 案例演示
import java.util.ArrayList;
public class demo {
public static void main(String[] args) {
ArrayList<Phone> phones = new ArrayList<>();
phones.add(new Phone<String, Integer>("Huawei", "mate50", 6000));
phones.add(new Phone<String, Integer>("Huawei", "mate40", 4000));
phones.add(new Phone<String, Integer>("Huawei", "mate30", 3500));
System.out.println("phones = " + phones);
Phone<String, Integer> apple = new Phone<>("Apple", "iphone 14", 6000);
/*
泛型方法中,泛型最终表示的实际的数据类型————传入的形参的类型。(引用类型)
*/
apple.charge(Integer.valueOf(100));
apple.charge(Long.valueOf(2333));
}
}
class Phone<T, E> {
//使用了泛型的成员变量
private T brand;
private T model;
private E price;
//使用了泛型的构造器
public Phone() {}
public Phone(T brand, T model, E price) {
this.brand = brand;
this.model = model;
this.price = price;
}
//使用了泛型的成员方法
public T getBrand() {
return brand;
}
public void setBrand(T brand) {
this.brand = brand;
}
public T getModel() {
return model;
}
public void setModel(T model) {
this.model = model;
}
public E getPrice() {
return price;
}
public void setPrice(E price) {
this.price = price;
}
//使用了泛型的成员方法的第二种形式(此处实际为'自定义泛型方法')
public<M> void charge(M m) {
//getClass()方法可以获取当前类的正名(包名 + 类名);
System.out.println("传入的引用类型 = " + m.getClass());
System.out.println("当前的" + getModel() + "手机已经充电了 " + m + " 分钟。");
}
@Override
public String toString() {
return "\nPhone{" +
"brand=" + brand +
", model=" + model +
", price=" + price +
'}';
}
}
运行结果:
如果我们在静态成员中使用泛型,IDEA会报错,如下图所示 :
6.自定义泛型接口
6.1 基本语法
[修饰符] interface 接口名 <T, R, E...> {//同样可以同时定义多个泛型
//body
}
6.2 使用细节
① 同自定义泛型类一个道理,自定义泛型接口的静态成员也不能使用泛型
② 自定义泛型接口中,泛型最终代表的数据类型是在继承该接口或者实现该接口时确定的
③ 若在使用时没有给出具体泛型,默认使用 Object 类型替代
6.3 案例演示
public class demo {
public static void main(String[] args) {
Ipad_Air ipad_air = new Ipad_Air();
ipad_air.charge("iPad Air 5", Long.valueOf(100));
Ipad_Pro ipad_pro = new Ipad_Pro();
ipad_pro.charge("iPad Pro 2022", Integer.valueOf(40));
}
}
//定义Usb接口
interface Usb<T, E> {
//在接口的(公有抽象)方法中使用泛型
public abstract void charge(T t, E e);
}
//演示1 : 继承接口时确定泛型
interface IPad extends Usb<String, Long> {}
class Ipad_Air implements IPad {
@Override
public void charge(String s, Long aLong) {
System.out.println("给" + s + "充电 " + aLong + " 分钟吧!");
}
}
//演示2 : 实现接口时确定泛型
class Ipad_Pro implements Usb<String, Integer> {
@Override
public void charge(String s, Integer integer) {
System.out.println(s + "设备" + "已充电 " + integer + " 分钟。");
}
}
7.自定义泛型方法
7.1 基本语法
[修饰符] <T, R...> 返回值类型 方法名(形参列表) {
//body
}
注:形参列表往往会使用定义好的泛型 <T,R...>
7.2 使用细节
① 自定义泛型方法,既可以定义在普通类中,也可以定义在泛型类
② 泛型最终代表的数据类型是在调用方法时确定的
③ 每次调用泛型方法,都可以指定不同的泛型类型
④ 注意区分自定义泛型方法和泛型在方法上的应用
7.3 案例演示
public class demo {
public static void main(String[] args) {
Watermelon<String,Integer> watermelon = new Watermelon();
watermelon.taste("蔡徐坤",23,'c');
}
}
//以下代码仅作为演示,无实际意义
class Watermelon <T, U> {
public<K> void taste(T t, U u, K k) {
System.out.println("T和U代表泛型在方法上的应用;而K则是自定义泛型方法的使用");
System.out.println("t的类型:"+t.getClass());
System.out.println("u的类型:"+u.getClass());
System.out.println("k的类型:"+k.getClass());
}
}
8.泛型的补充内容
8.1 关于继承性
如下图所示:
上面的代码在创建 ArrayList 对象时使用了泛型,但是没有采用 "菱形泛型" 的形式,而是在左边编译类型中给出了 <Object> 的泛型,在右边运行类型中给出了 <Interger> 。但 IDEA 会报错,直接编译不通过了。显示所需的类型和提供的类型不一致。这说明什么?
不会因为 Integer 类型是 Object 类型的子类就通过编译。即编译类型的泛型和运行类型的泛型必须统一,泛型本身不存在继承性
8.2 关于通配符(重要)
在看源码的时候经常会看到通配符这玩意,通配符是一个问号 ?。有以下三种使用场景:
① <?> : 单独使用,表示支持任意泛型类型
② <? extends A> :
(1)当 A 为类:表示支持 A 类、 A 类的子类
(2)当 A 为接口:表示支持 A 接口 、 A 的子接口、A 的实现类
③ <? super A> :
(1)当 A 为类:表示支持 A 类、 A 类的父类
(2)当 A 为接口:表示支持 A 接口 、 A 的父接口、超类 Object
案例演示:A为类
import java.util.ArrayList;
import java.util.List;
public class demo {
public static void main(String[] args) {
/**案例演示:A为类**/
//通配符使用情况一 : <?>
nutrition1(new ArrayList<Object>());
nutrition1(new ArrayList<Fruit>());
nutrition1(new ArrayList<Banana>());
nutrition1(new ArrayList<GreenBanana>());
/*
不报错,因为没有进行类型约束。
*/
System.out.println("=====================================s");
//通配符使用情况二 : <? extends A>
nutrition2(new ArrayList<Fruit>());
nutrition2(new ArrayList<Banana>());
nutrition2(new ArrayList<GreenBanana>());
System.out.println("=====================================s");
//如果编写 nutrition2(new ArrayList<Object>()),会报错,因为Object不属于"Fruit类及其子类"
//通配符使用情况三:<? super A>
nutrition3(new ArrayList<Object>());
nutrition3(new ArrayList<Fruit>());
//如果编写 nutrition3(new ArrayList<Banana>()) ,会报错,因为Banana类不属于"Fruit类及其父类"
}
//通配符使用情况一 : <?>
public static void nutrition1(List<?> fruit) {
System.out.println("水果营养丰富,富含维生素!");
}
//通配符使用情况二 : <? extends A>
public static void nutrition2(List<? extends Fruit> fruit) {
System.out.println("水果营养丰富,富含维生素!");
}
//通配符使用情况三 : <? super A>
public static void nutrition3(List<? super Fruit> fruit) {
System.out.println("水果营养丰富,富含维生素!");
}
}
class Fruit {
}
class Banana extends Fruit {
}
class GreenBanana extends Banana {
}
案例演示:A为接口
import java.util.ArrayList;
import java.util.List;
public class demo {
public static void main(String[] args) {
/**案例演示:A为接口**/
//通配符使用情况一 : <?>
nutrition1(new ArrayList<USB>());
nutrition1(new ArrayList<Phone>());
nutrition1(new ArrayList<SubPhone>());
nutrition1(new ArrayList<Student>());
/*
不报错,因为没有进行类型约束。
*/
System.out.println("=====================================s");
//通配符使用情况二 : <? extends A>,A = USB
nutrition2(new ArrayList<USB>());//A接口
nutrition2(new ArrayList<Phone>());//A的子接口Phone
nutrition2(new ArrayList<SubPhone>());//A的子接口SubPhone
nutrition2(new ArrayList<Student>());//A的实现类Student
System.out.println("=====================================s");
//通配符使用情况三:<? super A>
nutrition3(new ArrayList<USB>());//A接口
nutrition3(new ArrayList<Object>());//超类 Object
}
//通配符使用情况一 : <?>
public static void nutrition1(List<?> usb) {
System.out.println("nutrition1");
}
//通配符使用情况二 : <? extends A>
public static void nutrition2(List<? extends USB> usb) {
System.out.println("nutrition2");
}
//通配符使用情况三 : <? super A>
public static void nutrition3(List<? super USB> usb) {
System.out.println("nutrition3");
}
}
interface USB {}
interface Phone extends USB {}
interface SubPhone extends Phone {}
class Student implements USB{}