背景

先来看一个例子:

  1. public static void main(String[] args) {
  2. ArrayList list = new ArrayList();
  3. list.add("java");
  4. list.add(100);
  5. list.add(true);
  6. for(int i = 0; i < list.size(); i++) {
  7. Object obj = list.get(i);
  8. String str = (String)obj;
  9. System.out.println(str);
  10. }
  11. }

以上程序在编译期间没有问题,而在运行出错,报java.lang.ClassCastException,提示说Integer类型不能转换为String类型。

image.png
添加泛型:

  1. public static void main(String[] args) {
  2. ArrayList<String> list = new ArrayList<>();
  3. list.add("java");
  4. list.add("python");
  5. list.add("golang");
  6. for(int i = 0; i < list.size(); i++) {
  7. String str = list.get(i);
  8. System.out.println(str);
  9. }
  10. }

声明集合中的类型为String类型,不会报错。

image.png

image.png
代码复用、类型安全

泛型类

image.png
定义一个泛型类的例子:

  1. public class Generic<T> {
  2. // key这个成员变量的类型为T,T的类型由外部类调用该类时指定
  3. private T key;
  4. // 泛型构造方法形参key的类型也为T,T的类型由外部指定
  5. public Generic(T key) {
  6. this.key = key;
  7. }
  8. public T getKey(){
  9. return key;
  10. }
  11. }

T:是由外部使用类使用该类对象时指定。

image.png
编写上面泛型类的测试方法:

  1. // 只编写了一个泛型类 Generic,就达到了代码复用
  2. Generic<String> gen_str = new Genertic<>("abc");
  3. String k1 = gen_str.getKey();
  4. System.out.println("k1: " + k1);
  5. Generic<Integer> gen_int = new Genertic<>(123);
  6. int k2 = gen_int.getKey();
  7. System.out.println("k2: " + k2);
  8. // 泛型类在创建对象时,没有指定类型,将按照Object类型来操作。
  9. Generic gen_obj = new Genertic(123);
  10. Object k3 = gen_obj.getKey();
  11. System.out.println("k3: " + k3);
  12. // 泛型类不支持基本数据类型,即<>中不能为int、long、float等
  13. // 同一个泛型类,如Generic,根据不同的数据类型创建的对象,本质上是同一类型。
  14. System.out.println(gen_str.getClass()); // 打印Generic的类路径
  15. System.out.println(gen_int.getClass()); // 打印Generic的类路径(和上一句一样)

注意点:
image.png

泛型类实际案例

需求:公司年会上有抽奖品的,也有抽现金的,编写一个抽奖器类完成功能

  1. // 抽奖器类
  2. public class ProGetter<T> {
  3. Random random = new Random();
  4. // 奖品,奖品(String)或奖金(int)
  5. private T product;
  6. // 奖品池
  7. List<T> list = new ArrayList<>();
  8. // 添加奖品到奖品池
  9. public void addProduct(T t) {
  10. list.add(t);
  11. }
  12. // 抽奖
  13. public T getProduct(){
  14. list.get(random.nextInt(list.size()));
  15. return product;
  16. }
  17. }
  18. // 测试类
  19. class TestProduct{
  20. // 创建抽奖器对象,指定数据类型
  21. ProGetter<String> str_pro = new ProGetter<>();
  22. String[] pros = {"iphone 13", "Huawei P50", "Lenovo Y900P", "Beats"};
  23. for(String s : pros) {
  24. str_pro.addProduct(s);
  25. }
  26. // 抽奖
  27. String pro = str_pro.getProduct();
  28. System.out.println("恭喜您,你抽中了: " + pro);
  29. //-------------------------------------------------
  30. ProGetter<Integer> int_pro = new ProGetter<>();
  31. int[] cash = {3000, 5000, 666, 888, 10000};
  32. for(int c : cash) {
  33. int_pro.addProduct(c);
  34. }
  35. // 抽奖
  36. int pro = int_pro.getProduct();
  37. System.out.println("恭喜您,你抽中了: " + pro);
  38. }

泛型类派生子类

image.png

泛型接口

image.png
image.png
看个具体例子:

  1. // 定义一个泛型接口
  2. public interface Generator<T> {
  3. T getKey();
  4. }
  5. // 实现了泛型接口的类,实现类不是泛型类(上面的情况一)
  6. public class Apple implements Generator<String> {
  7. @Override
  8. public String getKey() {
  9. return "hello";
  10. }
  11. }
  1. // 实现类也是泛型类(上面情况二)
  2. // 泛型类的类型还可以扩充,如<T, E, K>,但是必须得包含接口的泛型类型,否则编译报错
  3. public class Pair<T> implements Generator<T> {
  4. private T key;
  5. @Override
  6. public T getKey() {
  7. return key;
  8. }
  9. }

泛型方法

image.png
上面泛型类中定义的方法的返回值定义为T类型,在此声明,此方法不是泛型方法,如上面的代码块中定义的Pair泛型类中的getKey成员方法就不是泛型方法。
image.png
注意点:
image.png
例子:

  1. // 抽奖器类
  2. public class ProGetter<T> {
  3. Random random = new Random();
  4. // 奖品,奖品(String)或奖金(int)
  5. private T product;
  6. // 奖品池
  7. List<T> list = new ArrayList<>();
  8. // 添加奖品到奖品池
  9. public void addProduct(T t) {
  10. list.add(t);
  11. }
  12. // 抽奖,不是泛型方法
  13. public T getProduct(){
  14. list.get(random.nextInt(list.size()));
  15. return product;
  16. }
  17. /**
  18. 定义一个泛型方法
  19. <E> 是泛型标识,是具体类型,由调用方法的时候指定。
  20. */
  21. public <E> getProduct(ArrayList<E> list) {
  22. return list.get(random.nextInt(list.size()));
  23. }
  24. }
  25. // 测试类
  26. public class TestPro {
  27. // 创建抽奖器对象,指定数据类型
  28. ProGetter<Integer> pro = new ProGetter<>();
  29. // 调用泛型类的成员方法,方法的返回值类型必须跟泛型类定义的类型Integer一致
  30. int[] cash = {3000, 5000, 666, 888, 10000};
  31. for(int c : cash) {
  32. pro.addProduct(c);
  33. }
  34. Integer product = pro.getProduct();
  35. System.out.println(product + "\t" + product.getClass().getSimpleName());
  36. //*************************************************************************
  37. ArrayList<String> strList = new ArrayList<>();
  38. list.add("手机");
  39. list.add("服务器");
  40. list.add("显卡");
  41. // 调用泛型方法,类型通过调用方法时指定
  42. String prize = pro.getProduct(strList);
  43. System.out.println(prize + "\t" + prize.getClass().getSimpleName());
  44. }

泛型方法支持可变参数
image.png

  1. // 定义一个是可变参数的且是静态的泛型方法
  2. public static <E> void print(E... e) {
  3. for(int i = 0; i < e.length; i++) {
  4. System.out.print(i + "\t");
  5. }
  6. }
  7. // 测试一下:
  8. ProGetterprint(1, 2, 3, 4, 5);

image.png

  1. // 静态的泛型方法,采用多个泛型
  2. public static <T, E, K> void printType(T t, E e, K k) {
  3. System.out.println(t + "\t" + t.getClass().getSimpleName());
  4. System.out.println(e + "\t" + e.getClass().getSimpleName());
  5. System.out.println(k + "\t" + k.getClass().getSimpleName());
  6. }
  7. // 测试一下
  8. ProGetter.printType(100, "java", true);

类型通配符

image.png
例子:

  1. public class Box<E> {
  2. private E first;
  3. public E getFirst() {
  4. return first;
  5. }
  6. public void setFirst() {
  7. this.first = first;
  8. }
  9. }
  10. // 测试
  11. public class TestBox{
  12. public static void showBox(Box<Number> box) {
  13. Number first = box.getFirst();
  14. System.out.println(first);
  15. }
  16. public static void showBox(Box<Integer> box) {
  17. Number first = box.getFirst();
  18. System.out.println(first);
  19. }
  20. // 上面两个方法不叫重载,会报错,只能存在一个,使用泛型通配符解决
  21. // ? 表示可以传递任意类型
  22. public static void showBox(Box<?> box) {
  23. Object first = box.getFirst();
  24. System.out.println(first);
  25. }
  26. }

类型通配符的上限

image.png

  1. class Animal{
  2. }
  3. class Cat extends Animal{
  4. }
  5. class MiniCat extends Cat{
  6. }
  7. // 测试
  8. public class TestUp{
  9. public static void showAnimal(ArrayList<? extends Cat> list) {
  10. for(int i = 0; i < list.size(); i++) {
  11. Cat cat = list.get(i);
  12. System.out.println(cat);
  13. }
  14. list.add(new Cat()); // 报错,上线通配符不能添加元素
  15. list.add(new miniCats); // 同样报错
  16. }
  17. public static void main(String[] args){
  18. ArrayList<Animal> animals = new ArrayList<>();
  19. ArrayList<Cat> cats = new ArrayList<>();
  20. ArrayList<MiniCat> miniCats = new ArrayList<>();
  21. showAnimal(animals); //报错,上限只能是Cat
  22. showAnimal(cats);
  23. showAnimal(miniCats);
  24. }
  25. }

类型通配符的下限

image.png

  1. class Animal{
  2. }
  3. class Cat extends Animal{
  4. }
  5. class MiniCat extends Cat{
  6. }
  7. // 测试
  8. public class TestDown{
  9. public static void showAnimal(ArrayList<? extends Cat> list) {
  10. for(int i = 0; i < list.size(); i++) {
  11. Cat cat = list.get(i);
  12. System.out.println(cat);
  13. }
  14. list.add(new Cat()); // 下限通配符添加元素不会报错
  15. list.add(new Animal()); // 不会报错
  16. }
  17. public static void main(String[] args){
  18. ArrayList<Animal> animals = new ArrayList<>();
  19. ArrayList<Cat> cats = new ArrayList<>();
  20. ArrayList<MiniCat> miniCats = new ArrayList<>();
  21. showAnimal(animals);
  22. showAnimal(cats);
  23. showAnimal(miniCats); //报错,下限只能是Cat
  24. }
  25. }

jdk中下线通配符的使用

类型擦除

image.png

java中所有的泛型信息只存在于编译阶段,编译完成后,所有的泛型信息将被擦除掉

无限制类型擦除

image.png

泛型无限制类型擦除,编译结束后,把T类型全部替换为Object类型

可以通过反射来得到编译后T类型被擦除后得到的类型:

  1. // 通过反射,拿到Erasure的key的数据类型
  2. class TestErasure{
  3. psvm(){
  4. Erasure<Integer> erasure = new Erasure<>();
  5. // 利用反射,获取Erasure类的字节码文件的Class类对象
  6. Class<? extends Erasure> clz = erasure.getClass();
  7. // 获取所有成员变量
  8. Field[] fields = clz.getDeclaredFields();
  9. for(Field field : fields) {
  10. // 打印成员变量的名称和类型
  11. System.out.println(field.getName() + ":" + field.getType().getSimpleName());
  12. }
  13. }
  14. }
  15. 运行结果:
  16. // key:Object

有限制类型擦除

image.png

  1. // 通过反射,拿到Erasure的key的数据类型
  2. class TestErasure{
  3. psvm(){
  4. Erasure<Integer> erasure = new Erasure<>();
  5. // 利用反射,获取Erasure类的字节码文件的Class类对象
  6. Class<? extends Erasure> clz = erasure.getClass();
  7. // 获取所有成员变量
  8. Field[] fields = clz.getDeclaredFields();
  9. for(Field field : fields) {
  10. // 打印成员变量的名称和类型
  11. System.out.println(field.getName() + ":" + field.getType().getSimpleName());
  12. }
  13. }
  14. }
  15. 运行结果:
  16. // key:Number

擦除泛型方法中类型定义的参数

image.png

  1. public class Erasure<T extends Number> {
  2. ....;
  3. // 添加泛型方法
  4. public <T extends Number> T show(T t) {
  5. return t;
  6. }
  7. }
  8. class TestErasure{
  9. psvm(){
  10. Erasure<Integer> erasure = new Erasure<>();
  11. // 利用反射,获取Erasure类的字节码文件的Class类对象
  12. Class<? extends Erasure> clz = erasure.getClass();
  13. // 获取所有成员方法
  14. Methods[] methods = clz.getDeclaredMethods();
  15. for(Method method : methods) {
  16. // 打印成员方法的名称和返回值类型
  17. System.out.println(method.getName() + ":" + mathod.getReturnType().getSimpleName());
  18. }
  19. }
  20. }
  21. // 结果:
  22. getKey:Number
  23. show:List
  24. setKey:void

桥接方法

image.png

  1. // 编写泛型接口和实现类
  2. // 测试类
  3. psvm{
  4. // 利用反射,获取InfoImpl类的字节码文件的Class类对象
  5. Class<InfoImpl> infoClass = InfoImpl.class;
  6. // 获取所有成员方法
  7. Method[] methods = infoClass.getDeclaredMethods();
  8. //
  9. for(Method method : methods) {
  10. // 打印成员方法的名称和返回值类型
  11. System.out.println(method.getName() + ":" + mathod.getReturnType().getSimpleName());
  12. }
  13. }
  14. // 结果:
  15. info:Integer
  16. info:Object (多了个方法,编译器替我们去生成的)

泛型数组

image.png
真正开发中一般都是用泛型集合,很少使用泛型数组。

泛型和反射