0%

Java 泛型(一):基本概念和原理

1. 怎样理解泛型

  • 字面意思就是广泛的类型
  • 类、接口和方法的代码可以应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一起,同一套代码可以用于多种数据类型
  • 好处是不仅可以复用代码降低耦合,而且可以提高代码的可读性安全性

2. 怎样理解泛型类

  • 类名后面多了一个 <T>
  • T 表示类型参数泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入。
  • 类型参数可以有多个,多个类型之间以逗号分隔。比如:Pair<String, Integer> pair = new Pair<String, Integer>("老隋", 100);
  • <String, Integer> 既出现在了声明变量时,也出现在了 new 后面,比较繁琐。从 Java 7 开始,支持省略 new 后面的类型参数。比如:Pair<String, Integer> pair = new Pair<>("老隋", 100);

3. 泛型的基本原理

  • Java 有 Java 编译器Java 虚拟机,编译器将 Java 源代码转换.class 文件,虚拟机加载并运行 .class 文件。
  • 对于泛型类,Java 编译器会将泛型代码转换为普通的非泛型代码,将类型 T 擦除,替换为 Object,插入必要的强制类型转换
  • Java 虚拟机在实际运行的时候,它是不知道泛型这回事的,只知道普通的类及代码。

4. Java 为什么要用类型擦除的方式设计泛型呢

  • 泛型是 Java 5 以后才支持的。
  • 这么设计是为了兼容性而不得已的一个选择。

5. 泛型的好处是什么

  • 更好的安全性。语言和程序设计的一个重要目标是将 bug 尽量消灭在摇篮里,能消灭在写代码的时候,就不必等到代码写完程序运行的时候。只使用 Object,代码写错的时候,开发环境和编译器不能帮我们发现问题、不会有错误提示。使用泛型,开发环境和编译器就可以提示可能的类型错误,这称之为类型安全,为程序多设置一道安全防护网。
  • 更好的可读性。使用泛型,还可以省去繁琐的重复的强制类型转换代码,再加上明确的类型信息,代码可读性也更好。

6. 怎样理解容器类

  • 泛型类最常见的用途是作为容器类
  • 容器类,即容纳并管理多项数据的类。
  • Java 泛型的引入主要也是为了更好地支持 Java 容器。

7. 【笔试题】实现一个类似 ArrayList 的动态数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//具体的类型还可以是一个泛型类,比如:DynamicArray<Pair<Integer, String>> arr = new DynamicArray<>();
public class DynamicArray<E> {
private static final int DEFAULT_CAPACITY = 10;
private int size;
private Object[] elementData;

public DynamicArray() {
this.elementData = new Object[DEFAULT_CAPACITY];
}

private void ensureCapacity(int minCapacity) {
int oldCapacity = elementData.length;
if(oldCapacity >= minCapacity) {
return;
}
int newCapacity = oldCapacity * 2;
if(newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}

public void add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e;
}

public E get(int index) {
return (E) elementData[index];
}

pubilc int size() {
return size;
}

public E set(int index, E element) {
E oldValue = get(index);
elementData[index] = element;
return oldValue;
}

}

DynamicArray<Double> arr = new DynamicArray<Double>();
Random rnd = new Random();
int size = 1 + rnd.nextInt(100);
for(int i = 0; i < size; i++) {
arr.add(Math.random());
}
Double d = arr.get(rnd.nextInt(size));

8. 怎样理解泛型方法

  • 类型参数为 T,放在返回值前面

  • 类型参数可以有多个,以逗号分隔。比如:

    1
    2
    3
    4
    public static <U, V> Pair<U, V> makePair(U first, V second) {
    Pair<U, V> pair = new Pair<> (first, second);
    return pair;
    }
  • 一个方法是不是泛型的,与它所在的类是不是泛型没有关系

9. 怎样理解泛型接口

  • 接口也可以是泛型的,实现接口时,应该指定具体的类型

  • Comparable 接口Comparator 接口 都是泛型的。代码如下:

    1
    2
    3
    public interface Comparable<T> {
    public int compareTo(T o);
    }
    1
    2
    3
    4
    public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);
    }

10. 怎样理解类型参数的限定

  • 类型参数的上界限定通过 extends 关键字表示。

  • Java 支持限定这个参数的一个上界,这个上界可以是某个具体的类或者某个具体的接口,也可以是其他的类型参数

    • 上界为某个具体的类:指定边界后,类型擦除时就不会转换为 Object 了,而是会转换为它的边界类型。

    • 上界为某个接口

      1
      public static <T extends Comparable<T>> T max(T[] arr) {}
    • 上界为其他类型参数

      1
      2
      3
      4
      5
      public <T extends E> void addAll(DynamicArray<T> c) {
      for(int i = 0; i < c.size; i++) {
      add(c.get(i));
      }
      }
  • 需要注意的是,虽然 IntegerNumber 的子类,但 DynamicArray<Integer> 并不是 DynamicArray<Number> 的子类,所以 DynamicArray<Integer> 的对象也就不能赋值给 DynamicArray<Number>,即容器是容器,类型是类型

-------------------- 本文结束感谢您的阅读 --------------------