什么是标量类型,为什么将未经检查的强制转换抑制为数组类型而不是标量类型风险更大?

Gee*_*eek 4 java generics effective-java

来自Effective Java Item 26 偏好通用类型

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

作者scalar type在这里是什么意思,他想在这里传达什么?什么是选项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];
}
Run Code Online (Sandbox Code Playgroud)

VS

// Appropriate suppression of unchecked warning
public E pop() {
  if (size == 0)
    throw new EmptyStackException();
  // push requires elements to be of type E, so cast is correct
   @SuppressWarnings("unchecked") E result = 
   (E) elements[--size];
   elements[size] = null; // Eliminate obsolete reference
   return result;
}                    
Run Code Online (Sandbox Code Playgroud)

lba*_*scs 5

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

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

另一个值得考虑的意见是由ruakh在评论中提到的:"我认为并不是说证据的复杂性,而是在发现错误时检测错误.我认为错误之间通常会有较少的"距离". -but-unchecked强制转换为(E)和后续隐式强制转换引发ClassCastException,而不是使用转换为(E [])而不是"

第三种观点(如果我理解正确的话,这是无可争辩的想在他的答案中指出的,无论如何这是我的新观点)是阵列演员是"冒险的"因为这个阵列不能在这之外使用类.(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;
    }
}
Run Code Online (Sandbox Code Playgroud)


irr*_*ble 5

理想情况下我们想写

E[] elements;

public Stack() 
{
    elements = new E[DEFAULT_INITIAL_CAPACITY];
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,Java犯了一个巨大的错误并且不允许这样做.所以我们需要解决方法.

这种解决方法

E[] elements;

public Stack() 
{
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
Run Code Online (Sandbox Code Playgroud)

在Java类型系统中理论上是错误的,因为Object[]它不是一个子类型E[].它碰巧在运行时在今天的 JVM上工作,但是,我们不应指望它永远工作.嗯,实际上,人们确实指望这一点,我认为它不会有任何改变的机会.所以没人关心.

(更正:实际上语言规范§5.5特别允许强制转换在运行时工作,因此每个规范的代码都没有错.然而,它太过于hackish,它不是"普通"类型系统的一部分,它的正确性是基于一些我们不想真正学习的妥协.)

从实际和理论上讲,第二种解决方法是正确的

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--];
}
Run Code Online (Sandbox Code Playgroud)

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