如何在 java 中过滤泛型值数组并使其返回泛型数组



>我需要使用过滤泛型数组并返回过滤后的泛型数组的方法创建单例类(但不是 Object[]!!)我在网上找不到解决方案。这是我的代码。

public class SingletonGenericsLambda {
public static void main(String[] args) {
String[] initialArray = new String[]{"One", "Two", null, "Three", null, "Four", "Five"};
System.out.println("Initial Array = " + Arrays.toString(initialArray));
String[] newArray = ArrayFilter.getInstance().filter(initialArray, Objects::nonNull);//has an error
System.out.println("Result Array = " + Arrays.toString(newArray));
}
}
class ArrayFilter<T>{
private static volatile ArrayFilter arrayFilter;
private ArrayFilter(){ }
public static ArrayFilter getInstance(){
ArrayFilter localInstance = arrayFilter;
if (localInstance == null) {
synchronized (ArrayFilter.class) {
localInstance = arrayFilter;
if (localInstance == null) {
arrayFilter = localInstance = new ArrayFilter();
}
}
}
return localInstance;
}
public <T> T[] filter(T[] array, Predicate<T> lambda){
return (T[]) Arrays.stream(array).filter(lambda).toArray();// here i also tried: Arrays.stream(array).filter(lambda).map(array.getClass()::cast).toArray();
}
}

你想要的是不可能的。

数组和泛型相互憎恨,除非在非常非常有限的情况下,否则不能强迫它们彼此友好地合作;没有解决这个问题。

泛型不是"化"的。从本质上讲,泛型是编译器想象力的虚构。这些信息要么在编译时被完全剥离,要么保留,但就 JVM 而言,这是一个注释:JVM 不会修改它的行为方式,也不会以任何方式或形式关心泛型;它在那里,以便Javac可以看到它并相应地进行编译。但是,javac非常擅长泛型,了解它们是什么,如果泛型导致 java 得出结论认为你搞砸了,则会生成适当的警告和错误。

数组是完全颠倒的:JVM知道数组类型,并将确保不会发生任何违规行为;它们是统一的:给定任何实例,如果它是一个数组,你可以在运行时询问组件类型是什么(与ArrayList实例相反,在实例中,不可能弄清楚泛型参数是什么。该信息在运行时根本不存在)。无论如何,javac 很愚蠢,不会得到数组差异。您可以编写没有警告的代码,但会违反类型安全。这是微不足道的,甚至:

String[] x = new String[10];
Object[] y = x; // this is, somehow, legal java code. It should not have been.
y[0] = Integer.valueOf(5); // compiles

在运行时,第 3 行根本不会将任何内容写入该数组并抛出ArrayStoreException。与泛型的对比:

List<String> x = new ArrayList<String>();
List<Object> y = x; // this is a compiler error.
y.add(Integer.valueOf(5));

在第 2 行,你得到一个编译器错误,但是如果你使用技巧使第 2 行发生,那么第 3 行执行没有问题;值是设置的,没有异常发生。如果您因此跟进:

String z = x.get(0);

然后你确实会收到一个错误(因为 x 的第一个值不是字符串):抛出了一个ClassCastException,这很奇怪,因为该行上没有(可见的)强制转换。欺骗很容易,尽管无需向后弯腰,编译器至少会发出警告,通知您现在可以发生上述情况:

List /* raw */ list = x;
List<Object> y = list;
y.add(Integer.valueOf(5)); // now compiles fine
String z = x.get(0); // throws ClassCastEx.

结果是,给定一个Predicate<T> lambda,然后您无法在运行时弄清楚T实际上是什么,但是如果不知道T是什么,您将无法正确进行T[]

根本没有解决这个问题

但是,一些注意事项:

  1. 虽然Object[]String[]并不相同,你可以在运行时判断:向字符串数组添加非字符串会导致ArrayStoreException,你可以在数组上调用.getClass().getComponentType(),数组相应地返回String.classObject.class,尽管如此,即使从类型安全的角度来看这是完全错误的, Java将允许您将字符串数组转换为对象数组,甚至允许您将任何数组(带有警告)转换为T[],然后调用代码将解释为例如String[],即使不是。换句话说,解决困境的一种方法是闭上眼睛,希望这种类型的系统故障实际上无关紧要,大多数Java代码都这样做。在您的过滤方法上贴上@SuppressWarnings并继续。

  2. 更一般地说,解决方法是将数组(尤其是非原语数组)视为过时的构造: 它们作为低级"机器代码"存在,就像ArrayList等实用程序类使用的中间代码一样,并且永远不会在业务逻辑代码中使用。 例如,除非你真的在编写你正在构建应用程序的核心实用程序的东西, 表现得好像数组不存在一样。使用List<T>,而不是T[]这是最适合您的项目的正确操作过程。为什么还要首先开设ArrayFilter<T>课?

  3. 可以通过在构造函数或静态方法上包含Class<T>作为参数并存储此字段来尝试统一泛型。但是,请注意,有些类型Class可以表示泛型无法int.class,有些泛型可以表示Class不能表示的类型(List<String>)。因此,如果这样做,则无法将代码正确用于涉及基元或泛型的任何内容。你根本不能再使用ArrayFilter来过滤List<String>.class,或者至少,你不能没有一堆奇怪的编译器警告。

  4. 请注意,鉴于T[]无论如何都是假的,通常最好不要拐弯抹角,而只是使用Object[],尤其是在内部。例如,java.util.ArrayList的代码有一个Object[]作为后备数组,而不是一个T[]。这是一个好主意:毕竟,T[]是谎言,可能会让您感到困惑:通常,String[]不可能容纳非字符串;如果您尝试,VM 将引发异常,即使您非常努力地使用反思和欺骗。但是,T[] 可以容纳非 Ts。最好说清楚并使用Object[],完全消除这种虚假的承诺。

对于这个任务,我有严格的要求,说我需要传递泛型数组并在过滤器方法中获取它。结果是我能找到的唯一解决方案:

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class SingletonGenericsLambda {
public static void main(String[] args) {
String[] initialArray = new String[]{"One", "Two", null, "Three", null, "Four", "Five"};
System.out.println("Initial Array = " + Arrays.toString(initialArray));
String[] newArray = ArrayFilter.getInstance().filter(initialArray, Objects::nonNull);
System.out.println("Result Array = " + Arrays.toString(newArray));
}
}
class ArrayFilter<T> {
private static volatile ArrayFilter arrayFilter;
private ArrayFilter() {}
public static ArrayFilter getInstance() {
ArrayFilter localInstance = arrayFilter;
if (localInstance == null) {
synchronized (ArrayFilter.class) {
localInstance = arrayFilter;
if (localInstance == null) {
arrayFilter = localInstance = new ArrayFilter();
}
}
}
return localInstance;
}
public static <T> T[] toArray(List<T> list) {
@SuppressWarnings("unchecked")
T[] toR = (T[]) Array.newInstance(list.get(0).getClass(), list.size());
for (int i = 0; i < list.size(); i++) {
toR[i] = list.get(i);
}
return toR;
}
public <T> T[] filter(T[] array, Predicate<T> lambda) {
List<T> list = Arrays.stream(array).filter(lambda).collect(Collectors.toList());
return toArray(list);
}
}

据我了解,尝试找出通用真实类型并不是非常好的解决方案。此外,这仅在不是接口或抽象类时才有效。

我同意@rzwitserloot所说的,总结了工作流程。

我想只有当你将你正在处理的类的值传递给函数时,它才会发生。

public class SingletonGenericsLambda {
public static void main(String[] args) {
String[] stringArray = new String[]{"One", "Two", null, "Three", null, "Four", "Five"};
Integer[] integerArray = new Integer[]{1, 2, null, 4, null, 5, 6};
String[] newStringArray = ArrayFilter.getInstance()
.filter(stringArray, Objects::nonNull, String[]::new);
Integer[] newIntegerArray = ArrayFilter.getInstance()
.filter(integerArray, Objects::nonNull, Integer[]::new);
System.out.println("Result String Array = " + Arrays.toString(newStringArray));
System.out.println("Result Integer Array = " + Arrays.toString(newIntegerArray));
}
}
class ArrayFilter<T> {
private static volatile ArrayFilter arrayFilter;
private ArrayFilter() {
}
public static ArrayFilter getInstance() {
ArrayFilter localInstance = arrayFilter;
if (localInstance == null) {
synchronized (ArrayFilter.class) {
localInstance = arrayFilter;
if (localInstance == null) {
arrayFilter = localInstance = new ArrayFilter();
}
}
}
return localInstance;
}
public static <T> T[] filter(T[] array, Predicate<T> lambda, IntFunction<T[]> function) {
return Arrays.stream(array)
.filter(lambda)
.toArray(function);
}
}

数组对象在运行时知道它们的组件类型,所以如果你只有一个List<T>,你无法正确创建T[],因为List对象在运行时不知道它的组件类型。但是,在这里,您将传递一个T[],一个在运行时知道组件类型的数组对象。您可以提取传入数组的运行时组件类型,以创建相同组件类型的新数组对象。这正是 Java 库方法Arrays.copy()Arrays.copyOfRange()的方式,这些方法采用泛型数组并返回泛型数组工作。

尽管传入数组的运行时组件类型可能是T的子类型,但这并不重要 - 该组件类型保证能够保存结果数组的元素,因为结果数组的元素是传入数组元素的子集,并且该组件类型能够保存传入数组的所有元素。

所以这样的事情应该有效:

import java.lang.reflect.Array;
public static <T> T[] filter(T[] array, Predicate<T> lambda){
return Arrays.stream(array).filter(lambda).toArray(
n -> (T[]) Array.newInstance(array.getClass().getComponentType(), n)
);
}

最新更新