Java 泛型和数组类型,不是你想的那样 (比如来自泛型类型的数组)。

18

我想指定一个泛型类必须是一个数组,或者更好的是一个基本类型的数组。目前为止,这是我得到的代码:

interface Foo<T> {
  void process( T data );
}

public class Moo implements Foo<int[]> {
  void process( int[] data ) {
     // do stuff here
  }
}

这是完全有效的Java代码,因为原始数组扩展了Object。请注意,这个问题与我一直在找到的所有其他Java数组通用问题完全不同。在那种情况下,人们想要创建一个由通用类型组成的数组。

问题在于类型T可以是任何扩展Object的东西。我想做的事情是:

<T extends ONLY ARRAYS>

或者
<T extends ONLY PRIMITIVE ARRAYS>.

这是可能的吗?

编辑:最终目标是在传递数组类型时添加编译时检查。现在可以传递任何旧对象,它将编译得很好。错误只有在运行时抛出类转换异常时才会被发现。事实上,这就是Java中泛型的全部意义,即添加更强的编译时类型检查。


2
为什么你需要这样做? - Jeffrey
我认为这应该是可能的。我的直觉告诉我,原始类数组的超类型除了Object之外没有其他选项。(Class类上有一个isArray()方法) - esej
1
一个单一的接口用于处理具有不同原始类型数据的图像。另一种选择是为每种类型使用不同的接口,这是更糟糕的补救措施。例如,Foo_S32,Foo_U8,Foo_F32等。 - lessthanoptimal
既然在Java中无法对任何数组类型进行子类型化,那你到底想要什么? - Ted Hopp
@PeterAbeles 但是你仍然无法在没有反射或强制转换为特定类型的情况下读取数组元素。此外,原始类型包括 char,它似乎不适合。而 boolean 每个元素占用一个字节,没有等效的碎屑或半字节类型。 - Tom Hawtin - tackline
4个回答

11
你不能这样做。没有类可以扩展数组,因此永远不会有一种类型满足泛型参数 T extends Object[]。(除了 Object[] 本身,但那时你就不会使用泛型了。)
你可以尝试像这样做:
public interface Foo<T extends Number> {
    public void process(T[] data);
}

但是,使用装箱可能会遇到性能问题。


4
特定类型的数组,如String[]可以赋值给Object[],但这对于基本类型的数组无济于事。 - Steven Schlansker
1
@StevenSchlansker 我举的例子无论如何都是无效的,因为数组不能用作类型参数限定。 - Jeffrey
你说得对,另一种方法会降低性能。经过进一步搜索,我找不到任何特殊的语言例外,可以允许我想要的约束类型。选择这个作为解决方案,因为你是第一个给出正确答案的人。 - lessthanoptimal

9
在Java中,类型参数只能通过子类型关系进行约束,并且所有数组的唯一公共超类型为ObjectClonableSerializable。最接近的方法是将其约束为Object[],它是具有非基本组件类型的所有数组的超类型,或者可能是Number[],它是Integer[]Long[]等的超类型。

即使Java支持这样的约束,你如何使用该数组做任何有用的事情呢?你不能读取单个元素,因为你无法声明一个变量来保存结果,也不能写入单个元素,因为你无法编写可分配给数组元素的表达式。

话虽如此,我会将类型变量绑定到组件类型而不是数组类型:

interface Foo<T extends Whatever> {
    void process(T[] data );
}

因为你可以参考 T 来了解 T[],但是知道 T extends Object[] 并不直接允许你引用组件类型。
编辑:Jeffrey 正确指出数组类型不能用于类型界限,即 <T extends Whatever[]> 无法编译,因此你一定要遵循我的建议声明 <T extends Whatever> 并使用 T[] 引用数组类型。

想要添加约束的目的并不是为了编写操纵数组的通用类,而是为了使API更易于使用。目前没有编译时检查,错误只能在运行时捕获。 - lessthanoptimal

1
不行。对于基本类型的数组,没有“有趣”的接口或任何超类型,只存在Object。
public class SOTEST {    
    public static void main(String[] args) throws Exception {
        int[] arr = new int[] {};
        Class c = arr.getClass();
        for(Class clazz:c.getInterfaces()) {
            System.out.println(clazz.getName());
        }

        System.out.println(c.getSuperclass().toString());
    }
}

-3

X的数组没有类型层次结构。Integer[]不是Number[]的子类。

要获得您想要的结果,请使用数组组件类型作为您的类型参数T,并将参数和返回类型声明为T的数组。


Integer[]Number[] 的子类型。例如,Number[] numbers = new Integer[] {1, 2, 3}; 可以编译通过... - meriton
这真是太疯狂了,也是 Java 类型系统中的一个漏洞。 Integer[] ints = new Integer[] { 1, 2, 3 }; Number[] numbers = ints; numbers[1] = new Float(4); for(int x: ints) { System.out.println(x); }可以编译通过,但在运行时抛出 ArrayStoreException 异常。 - PaulMurrayCbr
2
是的,数组的协变确实违反了Liskov替换原则(除非我们将ArrayStoreExceptions视为数组合同的一部分,就像JLS所做的那样)。但是,运行时检查确保Java在运行时保持类型安全。至于它是否“疯狂”,你肯定有权发表自己的意见,但我认为仅关注类型系统中的漏洞是一种相当受限制的观点。数组是协变的,并且在运行时进行检查。泛型必须在每个使用点上被作为协变,并且在运行时不进行检查(实际上会导致堆损坏)。哪种设计更好? - meriton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接