alex
alex

Reputation: 23

How to filter array of generic values in java and make it return generic array?

I need to create singleton class with method that filters generic array and returnes filtered array of generics(but not Object[]!!) I couldnt find the solution on the web. Here is my code.

 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();
  }
}

Upvotes: 0

Views: 1065

Answers (4)

newacct
newacct

Reputation: 122439

Array objects know their component type at runtime, so if you just had a List<T>, you could not create a T[] correctly because a List object does not know its component type at runtime. However, here, you are passed in a T[], an array object that knows a component type at runtime. You can extract the runtime component type of the passed-in array to create a new array object of the same component type. This is exactly how the Java library methods Arrays.copy() and Arrays.copyOfRange() that take a generic array and return a generic array work.

Although the runtime component type of the passed-in array might be a subtype of T, that doesn't matter -- that component type is guaranteed to be able to hold elements of the result array, since the elements of the result array are a subset of the elements of the passed-in array, and that component type was able to hold all the elements of the passed-in array.

So something like this should work:

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)
  );
}

Upvotes: 0

alex
alex

Reputation: 23

For this task i had strict requirements saying that i need to pass generic array and get it in filter method. In the result the only solution i could find:

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);
  }
}

As i understand it is not really good solution to try to figure generic real type. Also this would only work if is not an interface or abstract class.

Upvotes: 1

İsmail Y.
İsmail Y.

Reputation: 3945

I agree with what @rzwitserloot said, summarized the work flow.

I guess it will only happen if you pass the value of the class you're working on to the function.

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);
    }
}

Upvotes: 0

rzwitserloot
rzwitserloot

Reputation: 102872

What you want is impossible.

Arrays and generics hate each other and cannot be coerced to play nicely with each other except in very very limited circumstances; there is no fixing this.

Generics are not 'reified'. Essentially, generics are a figment of the compiler's imagination. That information is either stripped entirely when compiling, or it remains, but as far as the JVM is concerned, it's a comment: The JVM does not modify how it acts, or care in any way or form, about generics; it is there so that javac can see it and compile accordingly. However, javac is very good with generics, understands what they are, and generates appropriate warnings and errors if the generics lead java to conclude that you've messed up.

Arrays are completely reversed: The JVM knows about array types and will ensure no violations can possibly occur; they are reified: Given any instance, if it is an array you can ask, at runtime, what the component type is (in contrast to an ArrayList instance, where it is impossible to figure out what the generics param was. That information is simply not there at runtime). Hwever, javac is stupid and doesn't get array variances. You can write code that has no warnings that nevertheless violates type safety. It is trivial, even:

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

Line 3, at runtime, does not write anything at all into that array and throws an ArrayStoreException. Contrast to generics:

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

Here at line 2 you get a compiler error, but if you use trickery to make line 2 happen, then line 3 executes without issue; the value is set and no exception occurs. If you thus follow that up with:

String z = x.get(0);

you then DO get an error (as x's first value is not a string): A ClassCastException is thrown, which is weird, as there is no (visible) cast on that line. Trickery is easy, though without bending over backwards, the compiler will at least emit a warning informing you that the above can now happen:

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

The upshot is that, given a Predicate<T> lambda, you cannot then figure out at runtime what T actually is, but without knowing what T is, you can't make a T[] properly.

There is no fix for this, at all.

Some notes, however:

  1. Whilst an Object[] and a String[] are not the same, and you can tell at runtime: adding a non-string to a string array causes an ArrayStoreException, and you can invoke .getClass().getComponentType() on an array, which returns String.class or Object.class accordingly, nevertheless, eventhough this is just plain wrong from a type safety point of view, java will let you cast a string array to an object array, and will even let you cast (with warnings) any array to T[], which calling code will then interpret as e.g. String[], even though it isn't. In other words, one way to solve the dilemma is to just close your eyes and hope this type system failure isn't actually going to ever matter, and most java code does this. Stick a @SuppressWarnings on your filter method and carry on.

  2. More generally, the fix is to consider arrays, especially arrays of non-primitives, as obsolete constructs: They exist as low-level 'machine code' like intermediates used by utility classes like ArrayList and are never to be used in business logic code. E.g. unless you are literally writing the very core utility stuff that you're building an application on top of, act as if arrays do not exist. Use List<T>, not T[]. This is the most likely correct course of action for your project. Why even have an ArrayFilter<T> class in the first place?

  3. You can attempt to reify generics by including a Class<T> as parameter on a constructor or static method, and storing this field. However, note that there are types that Class can represent that generics cannot int.class, as well as types that generics can represent that Class cannot (List<String>). Therefore, if you do this, you make it impossible to properly use your code for anything that involves either primitives or generics. You simply cannot use ArrayFilter anymore to filter a List<String>.class, or at least, you can't without a bunch of bizarre compiler warnings.

  4. Note that, given the T[] is fake anyway, it is usually a better idea not to beat around the bush and just use Object[], especially internally. For example, the code of java.util.ArrayList has an Object[] as backing array, not a T[]. This is a good idea: After all, that T[] is a lie and could confuse you: Ordinarily, a String[] cannot possibly hold non-strings; the VM will throw an exception if you try, even if you try really hard with reflection and trickery. However, a T[] can hold non-Ts. Better to make it clear and use Object[], eliminating this false promise entirely.

Upvotes: 3

Related Questions