范型与函数对象

  1. 1. 1. 范型
    1. 1.1. 1.1 使用Object表示范型
    2. 1.2. 1.2 包装类型
    3. 1.3. 1.3 使用接口表示范型
    4. 1.4. 1.5 利用Java范型特性
      1. 1.4.1. 1.5.1 简单范型类和接口
      2. 1.4.2. 1.5.2 自动拆箱/装箱
      3. 1.4.3. 1.5.4 带有限制的通配符
      4. 1.4.4. 1.5.6 类型界限
      5. 1.4.5. 1.5.7 类型擦除
      6. 1.4.6. 1.5.8 范型的限制
  2. 2. 2 函数对象
    1. 2.1. 2.1 简单实现

《数据结构与算法分析Java语言描述》第一章1.4与1.5节读书笔记

1. 范型

范型机制:如果对象除去基本类型外,其余的实现方法相同,可以用范型实现来描述。

1.1 使用Object表示范型

  Object类是所有类的超类,可以通过Object类实现范型类。

1
2
3
4
5
6
7
8
9
10
11
12
public class MemoryCell {
// 通过Object实现范型对象
private Object storedValue;
// 写:给存储的值赋值,通过Object写入范型对象
public void write(Object x) {
storedValue = x;
}
// 读:返回存储的值
public Object read() {
return storedValue;
}
}
  • 注意,如果需要一个特定的类型使用,则需要强制转为正确的类型。
  • 不能使用基本类型(8大类型),只有引用类型(类、接口类型、数组类型、枚举类型)与Object相容。(这里测试实际是可以的(Java8):自动拆装箱)

1.2 包装类型

  Java提供了8种基本类型,这8种类型不能与Objecct相容,所以Java提供了对应的包装类。


1.3 使用接口表示范型

  只有在使用Object类中已有的方法能够表示所执行的操作时,才能使用Object作为范型类工作。(与代码对象类型无关)。
  例如找出Object数组中的最大项,与类型无关,只能实现Comparable接口,重写compareTo方法。


1.5 利用Java范型特性

Java支持范型类与范型方法

1.5.1 简单范型类和接口

  在类上使用<范型>,表示一个范型类。如下是前面的MemoryCell类范型版代码。也可以实现范型接口。

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericMemoryCell<T> {
// 指定范型为T,new类的对象时候再指定类型,注意不能是int类的基本类型
private T storedValue;
// 读:返回存储的值
public T read() {
return storedValue;
}
// 写:给存储的值赋值
public void write(T x) {
storedValue = x;
}
}

1.5.2 自动拆箱/装箱

自动装箱:如果一个基本类型,例如int被传递到一个需要Integer对象的地方,编译器幕后插入对Integer构造方法调用。
自动拆箱:一个包装类型,例如Integer被放到需要int类型的地方,则编译器幕后调用一个intValue方法。
注意:需要指定范型的地方,仍是不可改变的。

1.5.4 带有限制的通配符

  为了解决范型的一些问题,Java使用 通配符:?来解决这些问题,如下方法表示范型必须是Shape类或者其子类。

1
2
3
// 该方法的集合参数的范型必须是Shape类或者Shape类的子类
public static double totalArea(Collection<? extends Shape> arr) {
}

1.5.6 类型界限

  例如,想实现如下代码,编译器不能证明在第行对compareTo的调用是合法的。只有在T是Comparable的情况下才能保证compareTo存在。可以使用类型界限解决。在<>内,指定参数必须具有的性质。
错误的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FindMaxTest {
// 返回T类型的对象
public static <T> T findMax(T[] arr) {
int maxIndex = 0;
for (int i = 1; i < arr.length; i++) {
// 此处报错,T类型的对象没有实现Comparable接口,不可直接比较
if (arr[i].compareTo(arr[maxIndex]) > 0) {
maxIndex = i;
}
}
return arr[maxIndex];
}
}

正确代码<T extends Comparable<? super T>>
区别:<T extends Comparable<? super T>>:T必须继承Comparable接口或者父类继承了Comparable接口
  <T extends Comparable<T>>:T必须继承Comparable接口
  T extends决定了传入对象的上限是T(只能是T或子类),<? super T>决定了下限是T(最少T实现了Comparable)

1
2
3
4
5
6
7
8
9
10
11
12
 public class FindMaxTest {
// 返回T类型的对象,这里使用如下范型,规定了T必须继承Comparable接口或者父类继承了Comparable接口
public static <T extends Comparable<? super T>> T findMax(T[] arr) {
int maxIndex = 0;
for (int i = 1; i < arr.length; i++) {
if (arr[i].compareTo(arr[maxIndex]) > 0) {
maxIndex = i;
}
}
return arr[maxIndex];
}
}

1.5.7 类型擦除

  Java中的范型是伪范型,只在源码中存在,只在源码中有效,有编译检查。一旦编译通过,Java的范型就会被擦除,成为原生类型,称为类型擦除。

1.5.8 范型的限制

由于类型擦除的原因,下面的每一个限制都必须遵守。

基本类型

 基本类型不能用作类型参数,如GenericMemoryCell<int>非法,必须使用包装类。

instanceof检测

 insatanceof检测和类型转换只对原始类型(此处的GenericMemoryCell)进行。编译通过后,类型会被擦除,如下:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
GenericMemoryCell<Integer> cell1 = new GenericMemoryCell<>();
cell1.write(4);
Object cell = cell1;
GenericMemoryCell<String> cell2 = (GenericMemoryCell<String>) cell;
// 此处报错,Integer不能转String,上面的不报错,因为上面的类型都是GenericMemoryCell类型
String s = cell2.read();
}

范型类型不能实例化

1
T obj = new T()   // 右边是非法的

不能创建范型数组

1
T[] arr = new T[10];    // 右边是非法的

不能实例化参数化类型数组

1
2
3
4
5
6
7
8
9
10
11
12
public static void ClassCastExceptionTest() {
// 此处后面部分指定范型报错,只能不指定范型类型
GenericMemoryCell<String>[] arr1 = new GenericMemoryCell[10];
GenericMemoryCell<Double> cell = new GenericMemoryCell<>();
cell.write(4.5);
Object[] arr2 = arr1;
// 此处本应该报错,为ArrayStoreException,因为上面的范型规定为String,结果存入了Double,
// 可是由于类型擦除后,数组类型皆为GenericMemoryCell[],加入数组中的类型也是GenericMemoryCell,不会报错。
arr2[0] = cell;
// 此处报错,Double不能转String,上面的不报错,因为上面的类型擦除后都是GenericMemoryCell类型。
String s = arr1[0].read();
}

2 函数对象

定义一个只有方法而没有数据的类,然后把这个类的对象传递给别的方法,该对象通常叫做函数对象

2.1 简单实现

  如下是一个传递Comparator类型的函数对象的实现

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
public class GetTheMaxNum {

public static void main(String[] args) {
String[] arr = {"ZEBRA", "alligator", "crocodile"};
System.out.println(findMax(arr, new CaseInsensitiveCompare()));
}

// 比较方法
public static <T>T findMax(T[] arr, Comparator<? super T> cmp) {
int maxIndex = 0;
for (int i = 1; i < arr.length; i++) {
// 因为比较器是升序,所以这里大于0的意思是arr[i]大于arr[index]
if (cmp.compare(arr[i], arr[maxIndex]) > 0) {
maxIndex = i;
}
}
return arr[maxIndex];
}

// 指定比较器,范型指定传入的类型是String
static class CaseInsensitiveCompare implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
}
}