Java核心技术卷I:基础知识(原书第8版):12.6 约束与局限性

铁文整理

12.6 约束与局限性

    在下面几节中,将阐述使用Java泛型时需要考虑的一些限制。大多数限制都是由类型擦除引起的。

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

    不能用类型参数代替基本类型。因此,没有Pair<double>,只有Pair<Double>。当然,其原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储doubIe值。

    这的确令人烦恼。但是,这样做与Java语言中基本类型的独立状态相一致。这并不是一个致命的缺陷——只有8种基本类型,当包装器类型不能接受替换时,可以使用独立的类和方法处理它们。

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

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

        if (a instanceof Pair<String>) // same as a instanceof Pair

    实际上仅汉测试a是否是任意类型的一个Pair。下面的测试同样为真

        if (a instanceof Pair<T>) // T is ignored

    或强制类型转换

        Pair<String> p = (Pair<String>) a; // WAftNINC-can only test that a is a Pair

    要记住这一风险,无论何时使用instanceof或涉及泛型类型的强制类型转换表达式都会看到一个编译器警告。

    同样的道理,getClass方法总是返回原始类型。例如,

        Pair<String> strigPair = ...;

        Pair<Employee> employeePair = ...;

        if (strigPair.getClass() == employeePair.getClass()) // they are equal

    其比较的结果是true,这是因为两次调用getClass都将返回Pair.class

12.6.3 不能抛出也不能捕获泛型类实例

    不能抛出也不能捕获泛型类的对象。事实上,泛型类扩展Throwable都不合法。例如,下面的定义将不会通过编译:

public class Problem<T> extends Exception { ... } // ERROR--can't extend Throwable

    不能在catch子句中使用类型变量,例如,下面的方法将不能通过编译:

    public static <T extends Throwable> void doWork(C1ass<T> t) {

        try {

            doSomeWork();

        } catch (T e) // ERROR-—can't catch type variable

        {

            Logger.global.info(...);

        }

    }

    但是,在异常声明中可以使用类型变量。下面这个方法是合法的:

    public static <T extends Throwable> void doWork(T t) throws T // OK

    {

        try {

            doSomeWork();

        } catch (Throwable realCause) {

            t.initCause(realCause);

            throw t;

        }

    }

12.6.4 参数化类型的数组不合法

    不能声明参数化类型的数组,如:

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

    这样做有什么问题呢?擦除之后,table的类型是Pair[],可以将其转换为Object[]

        Object[] objarray = table;

    数组能够记住它的元素类型,如果试图存入一个错误类型的元素,就会抛出一个ArrayStoreException异常:

        objarray[0] = "Hello"; // ERROR-—component type is Pair

    但是,对于泛型而言,擦除将会降低这一机制的效率。赋值

        objarray[0] = new Pair<Employee>();

    可以通过数组存储的检测,但仍然会导致类型错误。因此,禁止使用参数化类型的数组。

    提示:如果需要收集参数化类型的对象,最好直接使用ArrayList: ArrayList<Pari<String>>,这样既安全又有效。

12.6.5 不能实例化类型变量

    不能使用像new T(...)new T[...]T.class这样的表达式中的类型变量。例如,下面的Pair<T>构造器就是非法的:

    public Pair() {

        first = new T();

        second = new T();

    } // ERROR

    类型擦除将T改变成Object,而且,本意肯定不希望调用new Object()。但是,可以通过反射调用Class.newInstance方法来构造泛型对象。遗憾的是,细节有点复杂。不能调用:

        first= T.class.newInstance(); // ERROR

    表达式T.class是不合法的。必须像下面这样设计API以便可以支配Class对象:

    public static <T> Pair<T> makePair(Class<T> cl) {

        try {

            return new Pair<T>(cl.newInstance(), cl.newInstance());

        } catch (Exnption ex) {

            return null;

        }

    }

    这个方法可以按照下列方式调用:

        Pair<String> p = Pair.makePair(String.class);

    注意,Class类本身是泛型。例如,String.class是一个Class<String>的实例(事实上,它是惟一的实例)。因此,makePair方法能够推断出pair的类型。

    不能构造一个泛型数组:

    public static <T extends Comparable> T[] minmax(T[] a) {

        T[] mm = new T[2]; // ERROR

    }

    类型擦除会让这个方法永远构造Object[2]数组。

    如果数组仅仅作为一个类的私有实例域,就可以将这个数组声明为Object[],并且在获取元素时进行类型转换。例如,ArrayList类可以这样实现:

public class ArrayList<E> {

    private Object[] elements;

 

    @SuppressWarnings("unchecked")

    public E get(int n) {

        return (E) elements[n];

    }

 

    public void set(int n, E e) {

        elements[n] = e;

    }// no cast need

}

    实际的实现没有这么清晰:

public class ArrayList<E> {

    private E[] elements;

 

    public ArrayList() {

        elements = (E[]) new Object[10];

    }

}

    这里,强制类型转换E[]是一个假象,而类型擦除使其无法察觉。

    由于minmax方法返回T[]数组,使得这一技术无法施展,如果掩盖这个类型会有运行时错误结果,假设执行代码:

    public static <T extends Comparable> T[] minmax(T[] a) {

        Object[] mm = new Object[2];

        return (T[]) mm;// compiles with warning

    }

    调用String[] ss = minmax("Tom", "Dick", "Mary");编译时不会有任何警告。当Object[]引用赋给String[]变量时,将会发生ClassCastException异常。

    在这种情况下,可以利用反射,调用Array.newInstance

    public static <T extends Comparable> T[] minmax(T[] a) {

        T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);

    }

    ArrayList类的toArray方法就没有这么幸运。它需要生成一个T[]数组,但没有成分类型。因此,有下面两种不同的形式:

    Object[] toArray()

    T[] toArray(T[] result)

    第二个方法接收一个数组参数。如果数组足够大,就使用这个数组。否则,用result的成分类型构造一个足够大的新数组。

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

    不能在静态域或方法中引用类型变量。例如,下列高招将无法施展:

public class Singleton<T> {

    public static T getSingleInstance() // ERROR

    {

    if (singleInstance == null) constructNewInstanceOfT():

    return singleInstance;

    }

 

    private static T singleInstance; // ERROR

}

    如果这个程序能够运行,就可以声明一个Singleton<Random>共享随机数生成器,声明一个Singleton<JFileChooser>共享文件选择器对话框。但是,这个程序无法工作。类型擦除之后,只剩下Singleton的类,它只包含一个singleInstance域,因此,禁止使用带有类型变量的静态域和方法。

12.6.7 注意擦除后的冲突

    当泛型类型被擦除时,无法创建引发冲突的条件。下面是一个示例。假定像下面这样将eqals方法添加到Pair类中:

public class Pair<T> {

    public boolean equals(T value) {

        return first.equals(value)&& second.equals(value);

    }

}

    考虑一个Pari<String>。从概念上讲,它有两个equals方法:

    boolean equals(String) // deftned in Pair<T>

    boolean equals(Object) // Inherited from Object

    但是,直觉把我们引入歧途。方法擦除后,boolean equals(T)就是boolean equals(Object)。与Object.equals方法发生冲突。

    当然,补救的办法是重新命名引发错误的方法。

    泛型规范说明还提到另外一个原则:“要想支持擦除的转换,就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。”例如,下述代码是非法的:

class Calendar implements Comparable<Calendar> { ... }

class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> { ... } // ERROR

    GregorianCalendar会实现Comparable<Calendar>Comparable<GregorianCalendar>,这是同一接口的不同参数化。

    这一限制与类型擦除的关系并不十分明确。毕竟,下列非泛型版本是合法的。

class Calendar implements Comparable { ... }

class GregorianCalendar extends Calendar implements Comparable { ... } // ERROR

    其原因非常微妙,有可能与合成的桥方法产生冲突。实现了Comparable<X>的类可以获得一个桥方法:

    public int compareTo(Object other) {

        return compareTo((X) other);

    }

    对于不同类型的X不能有两个这样的方法。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值