Java 什么是标量类型?为什么取消对数组类型的未检查强制转换比对标量类型的强制转换风险更大?

Java 什么是标量类型?为什么取消对数组类型的未检查强制转换比对标量类型的强制转换风险更大?,java,generics,effective-java,Java,Generics,Effective Java,来自有效Java项目26支持泛型类型 在所有其他条件相同的情况下,抑制未经检查的风险更大 强制转换为数组类型而不是标量类型,这将建议 第二个解决方案。但在比Stack更现实的泛型类中,您可以 可能会在代码中的多个点读取数组, 因此,选择第二种解决方案需要进行多次强制转换,而不是 而不是单个转换为E[],这就是为什么第一个解决方案使用得更多 通常[Naftalin07,6.7] 作者在这里所说的标量类型是什么意思?他在这里想表达什么?选项1被认为比选项2更危险的是什么 守则: // The ele

来自有效Java项目26支持泛型类型

在所有其他条件相同的情况下,抑制未经检查的风险更大 强制转换为数组类型而不是标量类型,这将建议 第二个解决方案。但在比Stack更现实的泛型类中,您可以 可能会在代码中的多个点读取数组, 因此,选择第二种解决方案需要进行多次强制转换,而不是 而不是单个转换为E[],这就是为什么第一个解决方案使用得更多 通常[Naftalin07,6.7]

作者在这里所说的标量类型是什么意思?他在这里想表达什么?选项1被认为比选项2更危险的是什么

守则:

// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime
// type of the array won't be E[]; it will always be Object[]!
@SuppressWarnings("unchecked") 
public Stack() {
  elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
VS


本例中的标量类型表示非数组类型。

本例中的标量类型是单个值,而不是由多个值组成的数组,如数学向量。E[]是数组,E是标量

我最初的想法是Joshua Bloch认为在数组的情况下抑制未经检查的强制转换警告的风险更大,因为证明代码的类型安全性不会出错更复杂

ruakh在评论中提到了另一个值得考虑的观点:“我本以为这与其说是证明的复杂性,不如说是在出现错误时检测错误。我认为错误但未经检查的转换到(E)之间的“距离”通常会更小以及引发ClassCastException的后续隐式强制转换,而不是使用对(E[])的强制转换“

第三种观点(如果我理解正确,这是Unretulable想在他的回答中指出的,无论如何,这是我的新观点)是数组类型是“有风险的”,因为这个数组不能在这个类之外使用
(E[])
是未经检查的强制转换:由于类型擦除,运行时无法真正检查此(不正确)强制转换的正确性。我们使用了一个肮脏的伎俩,但如果某个方法将此数组返回为E[],并将其分配给客户端类中的E[],它在运行时仍会失败,并出现ClassCastException:

public class Test {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        String[] array = stack.getArray(); // ClassCastException at runtime here!
    }
}

class Stack<E> {
    E[] elements;

    public Stack() {
        elements = (E[]) new Object[10];
    }

    // oh no, our dirty-tricky array escapes!
    E[] getArray() {
        return elements;
    }
}
公共类测试{
公共静态void main(字符串[]args){
堆栈=新堆栈();
String[]array=stack.getArray();//这里是运行时的ClassCastException!
}
}
类堆栈{
E[]要素;
公共堆栈(){
元素=(E[])新对象[10];
}
//哦,不,我们肮脏狡猾的阵法逃跑了!
E[]getArray(){
返回元素;
}
}

理想情况下,我们希望编写

E[] elements;

public Stack() 
{
    elements = new E[DEFAULT_INITIAL_CAPACITY];
}
不幸的是,Java犯了一个巨大的错误,不允许这样做。所以我们需要变通办法

这个变通办法

E[] elements;

public Stack() 
{
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

在Java类型系统中理论上是错误的,因为
对象[]
不是
E[]
的子类型。它恰好在运行时在今天的JVM上工作,但是,我们不应该指望它永远工作。嗯,事实上,人们确实相信这一点,而且我看不出有任何改变的可能。所以没人在乎

(更正:事实上,语言规范§5.5特别允许cast在运行时工作,因此代码在规范中没有错误。然而,它太粗糙了,它不是“正常”类型系统的一部分,它的正确性是基于一些我们并不真正想学的妥协。)

第二种解决方法在实践和理论上都是正确的

Object[] elements;

public Stack() 
{
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
}

public push(E e)
{
    ...
    elements[size++] = e;
}

public E pop()
{
    ...
    E result = (E)element[size--];
}

从Object到E的转换是正确的,因为程序逻辑确保它必须是
E

,那么呢?一般来说,这是一个非向量值的数学术语。我还提出了第二个问题。在阅读Eff中的第26项之后。JAVA(p127)@ruakh是的,在这5分钟内,我已经进行了伪编辑,你可以撤销。谢谢你消除了对标量类型的怀疑?你能回答我的第二个问题吗?@ruakh:你可能想用一个新的理论来检查我的更新答案:)@ruakh是的,这个问题是针对数组的。困惑来自这样一个事实,OP提出了两个无关的问题(第一:什么是标量?——实际上这本来是唯一的问题)。但我会重新表述我的答案:)@ruakh我复制了你的理论。也可以自由编辑答案,我想你有必要的权利:)1。那个“巨大的错误”是不可避免的,因为类型擦除。2.就我对高效Java的理解而言,作者确信第一种解决方法是正确的,因为“所讨论的数组(元素)存储在私有字段中,并且从未返回给客户端或传递给任何其他方法。”擦除是一个巨大的错误。有些人仍然希望有一天java能够摆脱擦除(这将打破第一个解决方法)。有些人不在乎,像第一个解决方法那样编写代码,这使得消除擦除的成本越来越高。我根据对您答案的理解更新了我的答案。“它恰好在运行时在今天的JVM上工作,但是,我们不应该指望它永远工作。”它根据Java语言当前的指定方式工作。如果语言在未来发生变化,那么所有的赌注都将落空。而且,擦除也不是一个“错误”。根据定义,“泛型”的类或方法应该对所有类型(在给定的范围内)同等工作,而不关心这些特定类型是什么。需要关心类型是什么,这表明泛型使用不当。
Object[] elements;

public Stack() 
{
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
}

public push(E e)
{
    ...
    elements[size++] = e;
}

public E pop()
{
    ...
    E result = (E)element[size--];
}