泛型,通配符

拼搏现实的明天。 2022-04-15 00:53 460阅读 0赞

泛型

泛型的意义:

1.可以对类型进行自动检查。并不是替换,而是在编译期间进行检查
2.自动对类型进行转换。

泛型到底是怎么编译的?

类型的擦除机制===》向上擦除===》Object类
在编译器编译期间,把泛型全部擦除为Object类型

如我们可以写一个泛型栈如下:
其中T 只是一个类型占位符 表示GenericStack是一个泛型类

  1. class GenericStack<T> {
  2. private T[] elem;
  3. private int top;
  4. public GenericStack() {
  5. this.elem = (T[]) new Object[10];
  6. this.top = 0;
  7. }
  8. public void push(T val) {
  9. this.elem[this.top++] = val;
  10. }
  11. public void pop1() {
  12. this.top--;
  13. }
  14. public T getTop() {
  15. return this.elem[this.top - 1];
  16. }
  17. }

接下来写main函数调用该栈

  1. public class GenericStackDemo {
  2. public static void main(String[] args) {
  3. GenericStack<Integer> genericStack = new GenericStack<Integer>();
  4. genericStack.push(10);
  5. genericStack.push(12);
  6. int top1 = genericStack.getTop();
  7. GenericStack<String> genericStack1 = new GenericStack<String>();
  8. genericStack1.push("a");
  9. genericStack1.push("b");
  10. String top2 = genericStack1.getTop();
  11. System.out.println(top1);//12
  12. System.out.println(top2);//b
  13. }
  14. }

在调用栈中的getTop()方法时,泛型栈就会自动识别栈顶元素的类型,不用再加强转符号

泛型中存在以下几点需要注意

  • 不能new泛型类型的数组 this.elem = new T[]; Error
  • 不能new泛型类型的对象 T obj = new T();
  • 不能new泛型类型的对象数组
  • 不能用简单类型作为泛型的参数
  • GenericStack genericStack = new GenericStack(); 一定记得在类名后加</泛型类型的参数>,否则就是Object
  • 在static方法中,不能使用泛型类型的参数,因为:static方法不依赖对象

内存泄漏问题

如果用刚才的泛型栈,如果栈中存放的是对象类型的数据,在调用出栈方法时,会造成内存泄漏问题

  1. public void pop1() {
  2. this.top--;
  3. }

从代码可以看出,此种方法出栈只是将栈顶指针下移,而对象还是存在的,JAVA中的GC回收器只会自动回收没有没有被引用的对象,可做如下修改

  1. public void pop2() {
  2. this.elem[top-1] = null;
  3. --this.top;
  4. }

这样出栈之后刚才的对象就不在被引用,就会被回收

我们可以用用jmap命令查看内存泄漏,
jps ===>查看java进程号
jmap -histo:live 进程号 > d:\log.txt(查看对象个数要存放的文件夹)
start e:

将main函数修改如下,存放Animal对象类型的成员

  1. public static void main(String[] args) throws InterruptedException {
  2. GenericStack<Animal> genericStack = new GenericStack<Animal>();
  3. genericStack.push(new Animal());
  4. genericStack.push(new Animal());
  5. genericStack.push(new Animal());
  6. genericStack.pop();
  7. System.gc();
  8. Thread.sleep(100000);
  9. }

用pop1()方法出栈,仍存在三个Animal类的对象
用pop2()方法出栈,存在两个Animal类的对象,证明有一个对象被GC回收器自动回收

泛型的上界 T extends Comparable

  1. class GenericAlg<T extends Comparable<T>> {
  2. public T findMaxVal(T[] array) {
  3. T max = array[0];
  4. for (int i = 1; i < array.length; i++) {
  5. if (array[i].compareTo(max) > 0) {
  6. max = array[i];
  7. }
  8. }
  9. return max;
  10. }
  11. }
  12. public class GenericDemo {
  13. public static void main1(String[] args) {
  14. Integer[] array = {
  15. 1, 2, 3, 4, 5, 6, 7};
  16. Double[] array1 = {
  17. 1.0, 2.2, 6.3, 5.2};
  18. GenericAlg<Integer> genericAlg = new GenericAlg<Integer>();
  19. System.out.println(genericAlg.findMaxVal(array));//7
  20. GenericAlg<Double> genericAlg1 = new GenericAlg<Double>();
  21. System.out.println(genericAlg1.findMaxVal(array1));//6.3
  22. }
  23. }

如上代码是求数组中的最大值,在GenericAlg类中加上,因为T是向上擦除直到Object,因为我们要用到compareTo方法,所以我们规定一个上界为Comparable接口,就可以调用此方法,进行比较

但我们会发现,用此方法进行比较,每种类型的数组都需要新建一个对象去调用类中的findMaxVal方法,所以我们可以将该方法写成静态方法,如下

注意写静态方法时,泛型的上界写在静态方法修饰符后

  1. class GenericAlg2 {
  2. public static <T extends Comparable<T>> T findMaxVal(T[] array) {
  3. T max = array[0];
  4. for (int i = 1; i < array.length; i++) {
  5. if (array[i].compareTo(max) > 0) {
  6. max = array[i];
  7. }
  8. }
  9. return max;
  10. }
  11. }
  12. public class GenericDemo {
  13. public static void main(String[] args) {
  14. Integer[] array = {
  15. 1, 2, 3, 4, 5, 6, 7};
  16. Double[] array1 = {
  17. 1.0, 2.2, 6.3, 5.2};
  18. //T会通过实参的类型推演出泛型类型
  19. System.out.println(GenericAlg2.findMaxVal(array));//7
  20. System.out.println(GenericAlg2.findMaxVal(array1));//6.3
  21. }
  22. }

这样就可以写任意类型的数组,用类调用求最大值的方法

通配符 “?”

通配符”?” 的擦除机制,会擦除到Object类

写一个函数,打印集合ArrayList中的全部元素

  1. class GenericAlg3 {
  2. /*如果写成ArrayList<T> List,会擦除到Object类
  3. 而ArrayList<Object>与ArrayList<Integer>不构成继承关系,所以在编译时无法识别
  4. 因此要写成ArrayList<?> List,用通配符识别,则可以转换成任意类型
  5. */
  6. public static void printList(ArrayList<?> List) {
  7. for (Object obj : List) {
  8. System.out.println(obj + " ");
  9. }
  10. }
  11. }
  12. public class GenericDemo1 {
  13. public static void main(String[] args) {
  14. ArrayList<Integer> arrayList = new ArrayList<>();
  15. arrayList.add(10);
  16. arrayList.add(12);
  17. arrayList1.add(14);
  18. arrayList1.add(16);
  19. GenericAlg3.printList(arrayList);
  20. }
  21. }

通配符的下界 <? super T>

找到是不是有T的基类实现了Comparable接口
主要用来读取

例如下面的代码
在代码中,Student继承了Person类,而Person实现了Comparable接口

在GenericAlg4类中的静态方法findMaxVal1方法中通配符规定了下界,所以编译时会自动寻找是否存在基类实现了Comparable接口,Person类作为基类实现了Comparable接口,所以其派生类就不用再实现Comparable接口

  1. class Person implements Comparable<Person>{
  2. private String name;
  3. public Person(String name) {
  4. this.name = name;
  5. }
  6. @Override
  7. public String toString() {
  8. return "Person{" +
  9. "name='" + name + '\'' +
  10. '}';
  11. }
  12. @Override
  13. public int compareTo(Person o) {
  14. return name.compareTo(o.name);
  15. }
  16. }
  17. class Student extends Person {
  18. private int age;
  19. public Student(String name,int age) {
  20. super(name);
  21. this.age = age;
  22. }
  23. @Override
  24. public String toString() {
  25. return "Student{" +
  26. "age=" + age +
  27. '}';
  28. }
  29. }
  30. class GenericAlg4 {
  31. public static <T extends Comparable<? super T>> T findMaxVal1(ArrayList<T> list) {
  32. T max = list.get(0);
  33. for (int i = 0; i < list.size(); i++) {
  34. if(max.compareTo(list.get(i)) < 0) {
  35. max = list.get(i);
  36. }
  37. }
  38. return max;
  39. }
  40. public class GenericDemo2 {
  41. public static void main(String[] args) {
  42. ArrayList<Person> arrayList1 = new ArrayList<Person>();
  43. arrayList1.add(new Person("Allen"));
  44. arrayList1.add(new Person("DQ"));
  45. System.out.println(GenericAlg4.findMaxVal1(arrayList1));
  46. ArrayList<Student> arrayList2 = new ArrayList<Student>();
  47. arrayList2.add(new Student("Allen",20));
  48. arrayList2.add(new Student("DQ",15));
  49. System.out.println(GenericAlg4.findMaxVal1(arrayList2));
  50. }
  51. }

通配符的上界 ArrayList<? extends T>

上界主要用来写入 一般用作库的开发
我们仍然用在解释下界时所使用的代码,将其中的findMaxVal方法修改如下:

  1. public static <T extends Comparable<T>> T findMaxVa2(ArrayList<? extends T> list) {
  2. T max = list.get(0);
  3. for (int i = 0; i < list.size(); i++) {
  4. if(max.compareTo(list.get(i)) < 0) {
  5. max = list.get(i);
  6. }
  7. }
  8. return max;
  9. }

发表评论

表情:
评论列表 (有 0 条评论,460人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java通配符

    Java泛型是一种强类型约束机制,可以在编译时检查类型安全,防止类型转换错误。通配符是Java泛型的一个重要特性,可以帮助我们更灵活地使用泛型。 什么是泛型通配符? 泛

    相关 通配符

    > 在看Java核心技术卷I中泛型通配符相关的知识时产生了很多疑问,通过了解后简单的做下笔记方便回顾。 > 本文重点记录了一下这几个问题: > > 为什么要用泛型通

    相关 Java 通配符例子

    请看下面的代码,其中会发生错误的代码已经注释掉,并且写明了错误类型 总体来说,泛型通配符就是为了支持多态时父子类,接口扩展类之间的相互转换而生 packa