泛型--泛型的限制

本文深入探讨了Java泛型的限制与应用,包括具体化类型、不可具体化类型、堆污染、参数化类型限制等内容,详细解释了如何有效地利用Java泛型特性,避免常见错误。

不可具体化类型

一个具体化类型是其信息在运行时是完全可知的。这包括原始类型,非泛型类型,原类型,和非受限通配符范型调用。

 

不可具体化类型是其信息在编译时的类型擦除是被去掉了——没有定义成非受限的通配符范型调用。一个不可具体化类型在运行时其信息不完全可知。List<String>和List<Number>是不可具体化类型;JVM在运行时无法知道它们间的区别。如在“泛型的限制”一节中,有些情况下是不能使用不可具体化类型:在 instanceof 表达式中,在数组元素中。

 

堆污染

当参数化类型变量引用一个非参数化类型对象时,就会发生堆污染现象。这种情况发生在当程序执行一些会产生编译时的非受检警告(unchecked warning)时。

 

 

 

为了更有效的使用Java的泛型,你必须考虑到下面的这些限制:

n  不能使用原始类型来实例化泛型类型

n  不能创建类型参数的实例

n  不能声明类型为类型参数的静态字段

n  不能在参数化类型上使用映射(cast)和instanceof

n  不能创建参数化类型数组

n  不能创建,捕获,抛出参数化类型对象

n  不能重载形式类型参数擦除后得到相同的原类型的方法

 

不能使用原始类型来实例化泛型类型

考虑下面参数化类型:

class Pair<K, V> {
 
    private K key;
    private V value;
 
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
 
    // ...
}

 

当创建一个Pair对象时,你不能用原始类型替换类型参数K,V:

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

 

你只可以用非原始类型替换类型参数K和V:

Pair<Integer, Character> p = new Pair<>(8, 'a');

 

注意,Java编译器会自动装箱,8会变成Integer.valueOf(8),‘a’会变成Character(‘a’):

Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));

 

 

不能创建类型参数的实例

你不能创建类型参数的实例。例如,下面的代码会产生一个编译错误:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

 

变通方法是,你可以通过反射机制类型参数对象:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);

}

 

你可以用以下方式来调用append方法:

List<String> ls = new ArrayList<>();
append(ls, String.class);

 

 

不能声明类型为类型参数的静态字段

一个类的静态字段,可以被类的所有非静态对象共享的类的变量。因此,类型参数的静态字段是不允许的。考虑下面的代码:

public class MobileDevice<T> {
    private static T os;
 
    // ...
}

 

如果允许静态类型参数字段,下面的代码将产生混淆:

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

 

因为phone,pager,和pc共享静态字段os,那么os该是什么类型呢?它不能同时是Smartphone,Pager,和TabletPC。因此,你不能创建类型参数的静态字段。

 

不能在参数化类型上使用映射(cast)和instanceof

因为Java编译器会擦除所有泛型中的类型参数,在运行时,你不能区分正在使用的是泛型那个参数化类型:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

 

传递给rtti方法的参数化类型的集合是:

S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }

 

 

运行时不会跟踪类型参数,因此不能区分ArrayList<Integer>和ArrayList<String>间的不同。你最多能做的是使用上界通配符来验证list是一个ArrayList:

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

 

 

一般情形下,你不能映射一个参数化类型,除非是不受限的参数化。例如:

List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // compile-time error

 

 

然而,在一些情况下,编译器知道一个类型参数总是有效的,并运行映射。例如:

List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1;  // OK

 

 

不能创建参数化类型数组

你不能创建参数化类型数组。例如,下面的代码不能编译:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

 

 

下面的代码演示了如果将不同类型插入进数组会发生什么:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

 

 

如果你对泛型list做同样的事,将会产生问题:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

 

如果允许参数化列表数组,上面的代码将不能抛出希望的ArrayStoreException。

 

不能创建,捕获,抛出参数化类型对象

一个泛型类不能从Throwable类直接或间接扩展。例如,下面的类不会通过编译:

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error
 
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

A method cannot catch an instance

 

一个方法不能捕获类型参数的实例:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...
    }
}

 

 

你可以在throws子句中使用类型参数:

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // OK
        // ...
    }
}

 

 

不能重载 形式类型参数擦除后得到相同的原类型的方法

 

一个类不能有两个重载的方法,如果它们在类型擦除后得到的方法签名是一样的。

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

 

这两个重载共享同样的类文件(classfile)的表示,将产生编译错误。

### TypeScript 约束的使用 在 TypeScript 中,约束用于限制参数的类型范围,从而提高代码的安全性和灵活性。通过 `extends` 关键字,可以定义参数必须满足的条件。以下是关于如何使用 TypeScript 约束的具体说明: #### 基础概念 当定义一个函数或接口时,如果希望该能够操作某些特定类型的属性或方法,则需要为其添加约束。例如,在不加约束的情况下,编译器会认为可能表示任意类型,因此不允许访问任何具体的属性或方法[^4]。 #### 使用 `extends` 添加约束 可以通过 `extends` 关键字为设置约束条件。这意味着传入的类型必须继承自某个基类或者实现某个接口。下面是一个简单的例子: ```typescript function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); // 返回 number 类型,正常工作 // getProperty(x, "m"); // 错误:"m" 不属于 "a", "b", "c", "d" ``` 在这个例子中,`K` 被约束为 `keyof T` 的子集,这样就确保了传递给 `key` 参数的值一定是对象 `obj` 上存在的键[^3]。 #### 更复杂的场景——结合接口 除了基本的数据结构外,还可以利用接口来进一步增强的功能。比如创建一个带有约束的接口: ```typescript interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; } ``` 这里要求传入的对象至少拥有 `length` 属性。如果不满足此条件,就会报错[^5]。 #### 默认类型支持 有时为了简化调用者的工作量,也可以设定默认类型作为备用方案。即使提供了默认值,仍然可以根据实际需求覆盖这些预设选项。 ```typescript function createArray<T = string>(): T[] { return []; } const strArr = createArray(); // 推断成字符串数组 const numArr = createArray<number>(); // 明确指定为数字数组 ``` 以上展示了多种方式去运用和理解 TypeScript 下的约束机制。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值