泛型
学习参考视频:B站黑马
本文结构:
一、什么是泛型
背景
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
泛型的概念
参数化类型
好处
- 编译时检查类型
- 减少了类型转换
MainClass.java
通过以下程序理解泛型的好处
import java.util.ArrayList;
public class MainClass {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("a");
list.add(1);
// 类型转换错误
// for (Object o : list) {
// String tmp = (String) o;
// System.out.println(tmp);
// }
ArrayList<String> ll = new ArrayList<>();
ll.add("Java");
ll.add("Python");
ll.add("C++");
// ll.add(1); // 编译错误
for (String s : ll) {
System.out.println(s);
}
}
}
二、泛型类、接口
2.1 泛型类的定义
泛型类的定义
class 类名 <泛型标识,泛型标识> {
private 泛型标识 变量名;
}
常见的泛型标识:T、E、K、V
使用
- 语法:类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
- java1.7之后,后面的<>内部可省略:类名<具体的数据类型> 对象名 = new 类名<>();
Generic.java
泛型的定义
public class Generic<T> {
private T key;
public Generic() {
}
public Generic(T key) {
this.key = key;
}
@Override
public String toString() {
return "Generic{" +
"key=" + key +
'}';
}
public void setKey(T key) {
this.key = key;
}
public T getKey() {
return key;
}
public static void main(String[] args) {
// 不指定类型时,默认Object型
Generic ge1 = new Generic();
Object key1 = ge1.getKey();
System.out.println("key1-->" + key1);
// 不能是基本数据类型
// new Generic<int>();
// 泛型类型实际上都是相同类型
Generic<String> strGe = new Generic<>("abc");
Generic<Integer> intGe = new Generic<>(1);
String strGeKey = strGe.getKey();
Integer intGeKey = intGe.getKey();
System.out.println("strKey-->" + strGeKey);
System.out.println("intGe-->" + intGeKey);
System.out.println(strGe.getClass() == intGe.getClass());
}
}
2.2 泛型类的使用
定义一个奖品抽奖的泛型类,getProduct方法获取随机奖品,addProduct向奖品池添加奖品。
import java.util.ArrayList;
import java.util.Random;
public class ProductGetter<T> {
Random r = new Random();
// 奖品
private T product;
// 奖品池
ArrayList<T> list = new ArrayList<>();
public ProductGetter() {
}
public ProductGetter(ArrayList<T> list) {
this.list = list;
}
// 抽奖
public T getProduct() {
int i = r.nextInt(list.size());
return list.get(i);
}
// 添加奖品
public void addProduct(T product) {
list.add(product);
}
public static void main(String[] args) {
// 1. str型
ProductGetter<String> strProductGetter = new ProductGetter<>();
// 加入奖池
String[] strProducts = new String[]{"手枪", "充电器", "弓箭"};
for (int i = 0; i < strProducts.length; i++) {
strProductGetter.addProduct(strProducts[i]);
}
// 抽奖
String product = strProductGetter.getProduct();
System.out.println("恭喜你抽中了" + product);
// 2. int
ProductGetter<Integer> intProductGetter = new ProductGetter<>();
// 加入奖池
int[] intProducts = new int[]{100000, 20000, 80000};
for (int i = 0; i < intProducts.length; i++) {
intProductGetter.addProduct(intProducts[i]);
}
// 抽奖
Integer intProduct = intProductGetter.getProduct();
System.out.println("恭喜你抽中了" + intProduct);
}
}
输出:
恭喜你抽中了手枪
恭喜你抽中了100000
2.3 泛型类的派生子类
规则
-
子类也是泛型类,子类和父类的泛型类型要一致。class ChildGeneric extends Generic
-
子类不是泛型类,父类要明确泛型的数据类型。class ChildGeneric extends Generic
Parent.java
泛型父类
public class Parent<T> {
private T value;
public Parent(T value) {
this.value = value;
}
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
FirstChild.java
子类也是泛型
public class FirstChild<T> extends Parent<T> {
public FirstChild(T value) {
super(value);
}
public static void main(String[] args) {
FirstChild<String> firstChild = new FirstChild<>("aaa");
String value = firstChild.getValue();
System.out.println("value-->" + value);
}
}
SecondChild.java
子类不是泛型,需指定父类的泛型类型
public class SecondChild extends Parent<Integer> {
public SecondChild(Integer value) {
super(value);
}
public static void main(String[] args) {
SecondChild secondChild = new SecondChild(111);
Integer value = secondChild.getValue();
System.out.println("value-->" + value);
}
}
2.4 泛型接口
规则
- 实现类不是泛型类,接口要明确数据类型
- 实现类是泛型类,实现类和接口的泛型类型要一致
Generator.java
/**
* 泛型接口
* @param <T>
*/
public interface Generator<T> {
T getKey();
}
Apple.java
public class Apple implements Generator<String> {
@Override
public String getKey() {
return "hello apple";
}
public static void main(String[] args) {
Apple apple = new Apple();
System.out.println(apple.getKey());
}
}
Pair.java
public class Pair<E, T> implements Generator<T> {
private T key;
private E value;
public Pair(E value, T key) {
this.key = key;
this.value = value;
}
@Override
public T getKey() {
return key;
}
public E getValue() {
return value;
}
public static void main(String[] args) {
Pair<String, Integer> pair = new Pair<>("爱马仕", 100);
Integer key = pair.getKey();
String value = pair.getValue();
System.out.println("key-->" + key + ", value-->" + value);
}
}
三、泛型方法
使用了泛型的类的成员方法不属于泛型方法。声明了的方法才是
语法
修饰符 <T, E, ...> 返回值类型 方法名(形参列表) {
}
可变参数在范型表示后面加…,参数为可遍历的对象。
public <E> void print(E... e){
for(e1: e) {
System.out.println(e1)
}
}
ProductGetter.java
import java.util.ArrayList;
import java.util.Random;
public class ProductGetter<T> {
Random r = new Random();
// 奖品
private T product;
// 奖品池
ArrayList<T> list = new ArrayList<>();
public ProductGetter() {
}
public ProductGetter(ArrayList<T> list) {
this.list = list;
}
// 抽奖
public T getProduct() {
int i = r.nextInt(list.size());
return list.get(i);
}
// 泛型方法。参数传递奖品池,然后从传入的奖品池随机获取奖品
/**
*
* @param list 奖品池
* @param <E> 奖品的类型
* @return
*/
public <E> E getProduct(ArrayList<E> list) {
int i = r.nextInt(list.size());
return list.get(i);
}
/**
* 定义了静态的泛型方法
* @param e
* @param t
* @param k
* @param <E>
* @param <T>
* @param <K>
*/
public static <E, T, K> void printType(E e, T t, K k) {
System.out.println(e + "--" + e.getClass().getSimpleName());
System.out.println(t + "--" + t.getClass().getSimpleName());
System.out.println(k + "--" + k.getClass().getSimpleName());
}
/**
* 可变参数的泛型方法
* @param e
* @param <E>
*/
public static <E> void print(E... e) {
for (E e1 : e) {
System.out.println(e1 + "--" + e1.getClass().getSimpleName());
}
}
// 添加奖品
public void addProduct(T product) {
list.add(product);
}
}
所以,泛型方法getProduct也可写为:
public <T> T getProduct(ArrayList<T> list) {
int i = r.nextInt(list.size());
return list.get(i);
}
MethodTest.java
对泛型方法进行测试
import java.util.ArrayList;
public class MethodTest {
public static void main(String[] args) {
ProductGetter<Integer> productGetter = new ProductGetter<>();
// 奖品池
ArrayList<String> list = new ArrayList<>();
list.add("华为电脑");
list.add("苹果电脑");
list.add("小米手机");
String product = productGetter.getProduct(list);
System.out.println(product + "\t" + product.getClass().getSimpleName());
System.out.println("<------>");
// Integer型
ArrayList<Integer> intList = new ArrayList<>();
intList.add(10000);
intList.add(20000);
intList.add(40000);
Integer intProduct = productGetter.getProduct(intList);
System.out.println(intProduct + "\t" + intProduct.getClass().getSimpleName());
System.out.println("<------>");
// 范型参数
productGetter.printType(111, "aaa", true);
System.out.println("<------>");
// 可变参数
productGetter.print(111, 222, 333);
}
}
输出:
苹果电脑 String
<------>
20000 Integer
<------>
111–Integer
aaa–String
true–Boolean
<------>
111–Integer
222–Integer
333–Integer
四、类型通配符
4.1 类型通配符
什么是类型通配符
- ?代替具体的类型实参
- 所以,类型通配符是类型实参,不是类型形参
Box.java
public class Box <E>{
private E first;
public void setFirst(E first) {
this.first = first;
}
public E getFirst() {
return first;
}
}
Test.java
测试
public class Test {
public static void main(String[] args) {
Box<Integer> box1 = new Box<>();
box1.setFirst(100);
showBox(box1);
Box<String> box2 = new Box<>();
box2.setFirst("aaa");
showBox(box2);
}
// public static <T> void showBox(Box<T> box) {
// T first = box.getFirst();
// System.out.println(first);
// }
public static void showBox(Box<?> box) {
Object first = box.getFirst();
System.out.println(first);
}
}
4.2 类型通配符的上限
要求该泛型的类型,必须是实参类型或者其子类
语法
类/接口<? extends 实参类型>
Test.java
public class Test {
public static void main(String[] args) {
Box<Integer> box1 = new Box<>();
box1.setFirst(100);
showBox(box1);
Box<Number> box2 = new Box<>();
box2.setFirst(200);
showBox(box2);
}
// public static <T> void showBox(Box<T> box) {
// T first = box.getFirst();
// System.out.println(first);
// }
public static void showBox(Box<? extends Number> box) {
Object first = box.getFirst();
System.out.println(first);
}
}
案例
三个类,继承关系:Animal<–Cat<–MiniCat
Test01.java
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
// showAnimal(animals); // 编译错误,因为只能接受Cat及其子类
showAnimal(cats);
showAnimal(miniCats);
}
public static void showAnimal(ArrayList<? extends Cat> list) {
// 不确定参数的类型,不能填充元素
// list.add(new Animal());
// list.add(new Cat());
// list.add(new MiniCat());
for (Cat cat : list) {
System.out.println(cat.getClass().getSimpleName());
}
}
}
4.3 类型通配符的下限
语法
要求该泛型的类型,只能是实参类型,或实参类型的父类类型。
类/接口 <? super 实参类型>
TestDown.java
import java.util.ArrayList;
import java.util.List;
public class TestDown {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
showAnimal(animals);
showAnimal(cats);
// showAnimal(miniCats); // 编译不通过
}
/**
* 类型通配符的下限,要求list只能是Cat或Cat的父类
* @param list
*/
public static void showAnimal(List<? super Cat> list) {
// 编译正确但是不保证正确
// list.add(new Cat());
// list.add(new MiniCat());
for (Object o : list) {
System.out.println(o);
}
}
}
案例
三个实体类
Animal.java
public class Animal {
public String name;
public String getName() {
return name;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
'}';
}
public Animal(String name) {
this.name = name;
}
}
Cat.java
public class Cat extends Animal{
public int age;
public int getAge() {
return age;
}
public Cat(String name, int age) {
super(name);
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
MiniCat.java
public class MiniCat extends Cat{
public int level;
public MiniCat(String name, int age, int level) {
super(name, age);
this.level = level;
}
public int getLevel() {
return level;
}
@Override
public String toString() {
return "MiniCat{" +
"name='" + name + '\'' +
", age=" + age +
", level=" + level +
'}';
}
}
Test02.java
import java.util.Comparator;
import java.util.TreeSet;
public class Test02 {
public static void main(String[] args) {
TreeSet<Cat> treeSet = new TreeSet<>(new Comparator2());
// TreeSet<Cat> treeSet = new TreeSet<>(new Comparator1());
// TreeSet<Cat> treeSet = new TreeSet<>(new Comparator3());
treeSet.add(new Cat("jerry", 20));
treeSet.add(new Cat("amy", 22));
treeSet.add(new Cat("frank", 35));
treeSet.add(new Cat("jim", 15));
//
for (Cat cat : treeSet) {
System.out.println(cat);
}
}
}
class Comparator1 implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.getName().compareTo(o2.getName());
}
}
/**
* 按年龄升序排列
*/
class Comparator2 implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
return o1.getAge() - o2.getAge();
}
}
class Comparator3 implements Comparator<MiniCat> {
@Override
public int compare(MiniCat o1, MiniCat o2) {
return o1.getLevel() - o2.getLevel();
}
}
TreeSet比较器的定义:
此时E为Cat,比较器的泛型必须是Animal或Cat。而比较器Comparator3的泛型类型为MiniCat,第二行注释的代码编译会出现错误。
public TreeSet(Comparator<? super E> comparator)
五、类型擦除
概念
泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除。
泛型信息只存在编译阶段!!!
5.1 无限制类型擦除
泛型类信息无条件擦除,为Object
Erasure.java
public class Erasure <T> {
private T key;
@Override
public String toString() {
return "Erasure{" +
"key=" + key +
'}';
}
public Erasure() {
}
public Erasure(T key) {
this.key = key;
}
public void setKey(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
Test09.java
public class Test09 {
public static void main(String[] args) {
// 泛型类型不同,最后会进行类型擦除
ArrayList<Integer> intList = new ArrayList<>();
ArrayList<String> strList = new ArrayList<>();
System.out.println(intList.getClass().getSimpleName());
System.out.println(strList.getClass().getSimpleName());
System.out.println(strList.getClass() == intList.getClass());
// 无限制的擦除为Object
Erasure<Integer> erasure = new Erasure<>();
Class<? extends Erasure> cls = erasure.getClass();
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field.getName() + "--" + field.getType().getSimpleName());
}
}
}
5.2 有限制的类型擦除
有限制的擦除,擦除为指定类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Muapi8YO-1690871344412)(/Users/xuhanqi/Library/Application Support/typora-user-images/image-20230729164820346.png)]
5.3 擦除方法中类型定义的参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sPchbcLX-1690871344413)(/Users/xuhanqi/Library/Application Support/typora-user-images/image-20230729165120324.png)]
Erasure.java
public class Erasure <T extends Number> {
private T key;
// 泛型方法,上限为Number
public <T extends Number> T getValue(T value) {
return value;
}
@Override
public String toString() {
return "Erasure{" +
"key=" + key +
'}';
}
public Erasure() {
}
public Erasure(T key) {
this.key = key;
}
public void setKey(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
Test10.java
public class Test10 {
public static void main(String[] args) {
Erasure<Integer> er1 = new Erasure<>();
Erasure<Float> er2 = new Erasure<>();
Class<? extends Erasure> cls = er1.getClass();
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
// 类型擦除为Number
System.out.println(method.getName() + "--" + method.getReturnType().getSimpleName());
}
}
}
输出:
toString–String
getValue–Number
getKey–Number
setKey–void
5.4 桥接方法
Info.java
/**
* 泛型接口
* @param <T>
*/
public interface Info <T> {
T info(T t);
}
InfoImpl.java
public class InfoImpl implements Info<Integer> {
@Override
public Integer info(Integer value) {
return value;
}
}
InfoTest.java
import java.lang.reflect.Method;
public class InfoTest {
public static void main(String[] args) {
Class<InfoImpl> cls = InfoImpl.class;
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName() + "--" + method.getReturnType().getSimpleName());
}
}
}
输出:
info–Integer
info–Object
六、泛型和数组
规则
- 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
- 可以通过java.lang.reflect.Array的newInstance(Class, int)创建T[]数组
推荐:ArrayList[] arr = new ArrayList();
Test10.java
public class Test10 {
public static void main(String[] args) {
// 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
// ArrayList<String>[] listArr = new ArrayList<>[5]; // 编译不通过
// 先声明引用,不推荐
ArrayList[] list = new ArrayList[5];
ArrayList<String>[] listArr = list;
ArrayList<Integer> intList = new ArrayList<>();
intList.add(100);
// 编译通过但是 ClassCastException异常
list[0] = intList;
String s = listArr[0].get(0);
System.out.println(s);
}
}
Test11.java
import java.util.ArrayList;
public class Test11 {
public static void main(String[] args) {
// 莫有问题
ArrayList<String>[] listArr = new ArrayList[5];
ArrayList<Integer> intList = new ArrayList<>();
intList.add(100);
// 编译不通过
listArr[0] = intList;
}
}
案例
Fruit.java
import java.lang.reflect.Array;
public class Fruit <T> {
// 不知道具体类型,无法new
// private T[] array = new T[3];
private T[] array;
public Fruit(Class<T> cls, int len) {
// 通过Array.newInstance创建泛型数组
array = (T[]) Array.newInstance(cls, len);
}
// 填充数组
public void put(T item, int index) {
array[index] = item;
}
// 获取数组元素
public T get(int index) {
return array[index];
}
// 获取数组
public T[] getArray() {
return array;
}
}
Test12.java
import java.util.Arrays;
public class Test12 {
public static void main(String[] args) {
Fruit<String> fruit = new Fruit(String.class, 3);
fruit.put("apple", 0);
fruit.put("lemon", 1);
fruit.put("peach", 2);
String[] arr = fruit.getArray();
System.out.println(Arrays.toString(arr));
}
}
七、 泛型和反射
使用泛型的反射会更加方便
/**
* 泛型和反射
*/
public class Test {
public static void main(String[] args) throws Exception{
// 指定泛型类型
Class<Person> cls = Person.class;
Constructor<Person> declaredConstructor = cls.getDeclaredConstructor();
Person p1 = declaredConstructor.newInstance();
// 不指定泛型类型
Class clz = Person.class;
Constructor con = clz.getDeclaredConstructor();
Object p2 = con.newInstance();
}
}