Java泛型

Java泛型

1 为什么使用泛型程序设计

泛型程序设计(generic programming)意味者编写多种不同类型的对象重用。

举例:

收集String和File对象无需编写不同的类,只需要一个ArrayList就可以收集。

1.1类型参数的好处

在Java中增加泛型类之前,泛型程序设计是用继承实现的。

ArrayList类只维护一个Object应用的数组:

public class ArrayList //before generic classes
{
    private Object[] elementData;
    ...
    public Object get(int i) {...}    
    public void add(Object o) {...}
}

缺点:

1.首先取值需要强制转换

ArrayList files = new Arraylist();
String filename = (String) files.get(0);

2.无错误检查,可以想数组列表中添加任何类的值

files.add(new File("..."));

编译和运行都不会出错,但若将get的即通过强制类型转换为String类型,会出错

泛型提供的解决方案 :类型参数 (指示元素的类型)

ArrayList<String> files = new ArrayList();

优点:

1.可读性好

2.编译器充分利用

String filename = files.get(0);//返回类型为String, 而不是Object
files.add(new File("..."));//error 无法通过编译

2 泛型类

泛型类(generc class)就是有一个或多个类型变量的类。

举例:

public class Pair <T>{
    private T first;
    private T second;

    public T getFirst() { return first; } // 返回类型为T的变量
    public T getSecond() { return second; }

    public void setFirst(T first) { this.first = first; } // 局部类型变量的类型为T
    public void setSecond(T second) { this.second = second; }

    public Pair(T first, T second) { this.first = first; this.second = second; }
}

类型变量T 用<>括起来放在类命的后面,泛型类可以有多个类型变量。

public class Pair<T, U> {...}

注:

类型变量一般使用大写字母,而且简短。Java中E表示集合的元素类型,K和V分别表示键和值的类型。T(U和S)表示“任意类型”。

可以用具体的类型替换类型变量来实例化(instantiate)泛型类型。

举例:

Pair<String>

3 泛型方法

类型变量放在修饰符之后,返回值之前。

泛型方法既可以在普通方法中定义,也可以在泛型类中定义。

举例:

class ArrayAlg {
    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }
}

调用泛型方法,可以将具体类型放在尖括号中,放在方法名前面。

举例:

String middle = ArrayAlg.<String>getMiddle("1","2","3");

大部分下情况下可以省略,编译器会自动推断。

不可省略的情况举例:

double middle = ArrayAlg.getMiddle(3.14, 1, 2);

编译器将参数自动装箱为1个Double对象和2个Integer对象,然后寻找这些类的共同超类型。事实上找到了2个超类型Number和Comparable接口。编译工具会隐晦的提示:

Static member 'ArrayAlg.getMiddle(java.lang.Number & java.lang.Comparable<? extends java.lang.Number & java.lang.Comparable<?>>...)' accessed via instance reference 

4类型变量的限定

有时候, 类或方法需要对类型变量需要加以约束。

举例:

public class ArrayAlg<T>{
    public static <T> T min(T[] a) {
        if (a == null || a.length == 0) return null;
        T smallest = a[0];
        for (int i = 0; i < a.length; i++) {
            if (smallest.compareTo(a[i]) > 0) smallest = a[i];
        }
    }
}

这段代码有一定的问题,min方法中,smallest的类型为T,意味它可以是任何一个类的对象,但不是每一个类都有一个compareTo方法。

如何改正:只能限制T为实现了Comparable接口(包含一个compareTo的方法)的类。

举例:

public static <T extends Comparable> T min(T[] a)...
// 为什么Comparable是接口却用extends ?
// 仅仅因为它更加接近子类型的概念

一个类型变量或通配符可以有多个限定

举例:

ArrayAlg<T extends Comparable & Serializable, E extends Collection>

5 泛型代码

1类型擦除

无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(raw type)。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量都会被擦除(erased),并且替换为限定类型(或者,对于无限定的变量则替换为Object)。

原始类型用第一个限定来替换类型变量,或者没有给限定就替换为Object。

举例:

Pair<T> //原始类型为Pair
Interval<T extends Comparable & Serializable> //第一个限定类型为Comparable

2转换泛型表达式

编写一个泛型方法调用时,如果擦除了返回类型,编译器会插入强制类型转换。

举例:

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

编译器擦除类型后返回类型是Object,调用getFirst会自动插入转换到Employee的强制类型转换。

即:1.对原始方法Pair.getFirst的调用 2.将返回的Object类型强制转换为Employee类型

3转换为泛型方法

类型擦除也会出现在泛型方法

举例:

public static <T extends Comparable> T min(T[] a);
// 泛型擦除后
public static Comparable min(Comparable[] a);

6限制性与局限性

1不能使用基本类型实例化类型参数

举例:

不能使用Pair 只有Pair

原因在于类型擦除,擦除之后Pair类含有Object类型的字段,而Object类型不能存储double值。

2运行时类型查询只适用于原始类型

虚拟机对象中总有一个特定类型的非泛型类型。因此,所有类型查询只产生原始类型。

举例:

if (a instanceof Pair<String>) //error
if (a instanceof Pair<T>) //error
Pair<String> p = (Pair<String>) a; // warning--can only test that a is a Pair

为提醒这一风险,如果试图查询一个对象是否属于某个泛型类型,会得到一个错误或警告。

同样道理,由于泛型擦除会有以下结果出现:

Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) //true

3不能创建参数化类型的数组

举例:

var table = new Pair<String>[10]; // error

**原因:**一般来说数组会记住自己的元素类型,如果试图存储其他类型的元素,会抛出ArrayStore-Exception异常,而泛型擦除后 var table = new Pair[10];会使这种机制无效。

Object[] arr = table;
arr[0] = new Pair<Employee>(); //编译通过, 但是会导致类型错误

5不能实例化类型变量

不能再类似new T(…)的表达式中使用类型变量

举例:

public Pair() {first = new T(); second = new T();} //error

类型擦除将T变成Object

6不能构造泛型数组

举例:

public static <T extends Comparable> T[] minmax(T... a) {
    T[] mm = new T[2]; // error
}

类型擦除会使得构造Comparable[2]数组

String[] names = ArrayAlg.minmax("Tom", "Dick");
// 在Comparable[]引用强制转换为String[]时候,会出现ClassCastException

7泛型类的静态上下文中类型变量无效

不能在静态字段或方法中引用类型变量。

举例:

public class Singlenton<T> {
    private static T singleInstance; //error
    private static T getSingleInstance() {//error
    	if(singleInstance == null)
            return singleInstance;
    }    
}

8不能抛出或捕获泛型类的实例

不能抛出或捕获泛型类的的对象。事实上泛型类扩展Throwable都是不合法的。

举例:

public class Problem<T> extends Exception {...} // error

public static <T extends Throwable> void doWork(Class<T> t) {
    try {
        // do work
    } catch (T e) { // error
        
    }
}

7泛型类型的继承规则

无论S与T有什么关系,通常Pair与Pair都没有关系。

举例:

Manager[] topHonchos = ...;
Pair<Employee> result = ArrayAlg.minmax(topHonchos); //error

minmax返回Pair ,而不是Pair,这样的赋值不合法。

这一点与数组不同

Employee[] employeeBuddies = new Manager[]{ceo, cfo}; // ture

将一个参数化类型转换为原始类型的时。也可能产生类型错误。

举例:

Pair rawBuddies = new Pair<Manager>;
rawBuddies.serFirst(new File("...")); // error

最后,泛型类也可以扩展或实现其他泛型类。

**例如:**ArrayList类实现List接口

8通配符类型

1通配符的概念

在通配符类型中允许类型参数发生变化。

举例:

Pair< ? extends Employee>

public static void printBuddies(Pair<Employee> p) {
    Employee first = p.getFirst();
    Employee second = p.getSecond();
    System.out.println(first.getName() + " and " + secong.getName() + " are buddies.");
}

这个方法的局限性就是不能将Pair 传给这个方法。但是利用通配符就可以解决。

Pair是Pair< ? extends Employee>的子类型

Pair< ? extends Employee>
? extends Employee getFirst()
void setFirst(? extends Employee)

以上代码不可能调用setFirst方法,编译器只需要知道Employee的某个子类型,但不知道具体是什么类型,导致直接拒绝传递任何特定的类型。毕竟?不能匹配。

使用getFirst不存在这个问题,将getFirst的返回值赋予一个Employee引用完全合法

2通配符的超类型限定

举例:

Pair< ? super Manager>
? super Manager getFirst()
void setFirst(? super Manager)    

编译器无法知道setFirst方法的具体类型,所以不能接受参数类型为Employee 或Object的方法调用,只能传递Manager类型的对象,或某个子类型的对象。而调用getFirst,不能保证返回对象的类型,只能把它赋值给一个Object。

Pair是Pair< ? super Manager>的子类型

某些情况下使用通配符可以帮程序员去除对调用参数的不必要限制

举例:

LocalDate extends ChronLocalDate
ChronLocalDate extends Comparable<ChronLocalDate>
// 所以 LocalDate 实现的是Comparable<ChronLocalDate> 而不是Comparable<LocalDate>
// 这种情况下可以利用超类型解决
public static <T extends Comparable<? super T>> T min(T[] a)...

3无限定通配符

举例:

Pair<?>
? getFirst();
void setFirst(?)

getFirst的返回值只能赋给一个Object。setFirst方法不能被调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值