Method to flatten generic array - java

I've been trying to write a method that will flatten a generic array if it is nested.
private static <T> List<T> flatten(T[] in) {
List<T> result = new ArrayList<>();
for (T e : in) {
if (e.getClass().isArray()) {
result.addAll(Arrays.asList(e)); ## Issue is here.
} else {
result.add(e);
}
}
return result;
}
This code does not cause any errors but also does not work. When e is not an array, things work as expected... a list is populated with the elements of in and returned.
However when e.getClass().isArray() == true, the elements of e are not added. Rather the original array is added so I end up with a list of arrays.
My use case here is that I have a method that is being passed generics T[] someArray
public <T> void doSomeStuff(T[] someArray) {
Set<T> unique = Sets.newHashSet(someArray)
... do some stuff with the unique values ...
}
The input someArray may either be nested or not (i.e. T itself may be an array, resulting in T[][]). I want to determine the unique elements contained in the input, whether or not it is nested. Passing the input someArray to a set only works if it's not nested, hence I'm trying to flatten.
So my question is, how can I do this and why is my method above not working? Thanks in advance for the edcuation.

Your code can't work. The generics just don't line up.
Let's say you have an array that is a combination of strings and arrays of strings. That cannot possibly be a T[] unless T is object, which isn't what you want (as that would mean you get a List<Object>. After all, If T is String, then your input array, which is defined as T[] in, is a String[] in, which cannot contain string arrays. After all, a String[] is not a subtype of String, for obvious reasons.
It is impossible to describe in terms of generics the concept of 'an array of Strings, or an array of arrays of Strings, or an array of arrays of arrays of Strings, and so forth'. So, generics have no place here. If you want that, all you can 'type' is 'an array whose component type is unknown and hybrid anyway', which is Object[] in java (this is co/contra-variance wise broken, but this is just part of the java spec: Variance on arrays is incorrect, known problem and not fixable).
This gets you a secondary issue: Generics are erased, and in that model you don't have an actual type to work with. In fact, because it is impossible to use generics to tell the compiler to do some type checking on the input array, there is nothing the compiler can do for you, so any type checking you want (and you clearly want that, you don't want to return a List of who knows what this is), will have to be done at runtime.
Unfortunately, it is impossible to do that, too - you can't check if at runtime if some object is, say, a Map<String, Integer>.
So, what you want is impossible.
It becomes possible if you're okay with this method being only able to do the job for reified types. That is, types that don't contain any <> themselves. So, if you want to take 'an array that contains a combination of "Map of string to integer" and "arrays of Maps of string to integer"', this method will not be able to do that and it is in fact completely impossible to do such a thing in java. But if you're okay with, say, "An array containing a combination of strings and arrays of strings" and want to turn that into a flattened-out list of strings, okay, that's possible.
It's complicated, though:
public <T> List<T> flattenArray(Class<T> type, Object[] in) {
if (type.isArray()) throw new IllegalArgumentException();
var out = new ArrayList<T>();
flattenArray0(type, in, out);
return out;
}
private <T> void flattenArray0(Class<T> type, Object[] in, List<T> out) {
for (Object a : in) {
if (a == null) {
out.add(null);
} else if (a.getClass().isArray()) {
flattenArray0(type, (Object[]) a, out);
} else {
out.add(type.cast(a));
}
}
}
In action:
Object[] test = new Object[3];
test[0] = "Hello";
test[1] = new String[] {"Foo", "Bar"};
Object[] threeDeep = new Object[2];
test[2] = threeDeep;
threeDeep[0] = "Goodbye";
threeDeep[1] = new String[] {"Baz"};
List<String> result = flattenArray(String.class, test);
System.out.println(result);
should print: ["Hello", "Foo", "Bar", "Goodbye", "Baz"].

Related

How to write a generic function which accepts arrays of literals

I am unable to provide values of type int[], float[], etc. to a generic function. I get errors that say basically that float[] is the wrong type and Float[] is what the function actually takes.
Here's an example of a method I wrote, and I'm trying to give it values like new int[]{0,1} (created in library somewhere else).
private static <T> JSONArray encodeArray(T[] array) {
JSONArray arr = new JSONArray();
Collections.addAll(arr, array);
return arr;
}
Is it even possible to write my function signature to accept these arrays of literals?
I could go to the call site, and do a conversion of float[] to Float[], but I don't know how to do that either.
Is it even possible to write my function signature to accept these
arrays of literals?
It is possible, but the parameter type will have to be Object, because that is the only common superclass of "arrays of primitives" and "arrays of references" (e.g. it can't be Object[] since arrays of primitives are not subclasses of Object[]). (There are also some interfaces that all arrays implement, but I will ignore those for now.) Unfortunately, this means that you will lose type safety as the compiler will not be able to give an error at compile time if someone passes a non-array type in.
To do array operations on this Object value, you will need to use the methods in the reflection helper class java.lang.reflect.Array. So you can do something like this:
import java.lang.reflect.Array;
// ...
private static JSONArray encodeArray(Object array) {
JSONArray arr = new JSONArray();
for (int i = 0, n = Array.getLength(array); i < n; i++) {
arr.add(Array.get(array, i)); // primitives are automatically wrapped
}
return arr;
}
The method which accepts a generic array.
public <T> void printArray(T[] array){
for (T element: array){
System.out.println(element);
}
}
You can't use primitives in generic functions. When generic are compiled, you end up with Object[] in the above example as the implementing type. As int[] and byte[] etc, do not extend Object[] you cannot use them interchangeably even if the code involved would be identical (again generics are not templates)
Add on the solution from #Max-Reshetnyk, It is better if you check ArrayList for methods that help you add or remove... elements. Since primitive types are not meant to be used with generics, you should AutoBox them with their respective types and then use generics.
For instance:
class Main {
public static void main(String[] args) {
Integer[] x = new Integer[1];
x[0] = 1;
printArray(x);
}
public static <T> void printArray(T[] array){
for (T element: array){
System.out.println(element);
}
}
}

Java - Generic types and collections [duplicate]

This question already has answers here:
Java generics type erasure: when and what happens?
(7 answers)
Closed 5 years ago.
I'm trying to learn about the use of generic types and I've noticed something weird when I was experimenting with some lines of code.
The first piece of code is inside a class named "A":
public void func(int k, List list) {
list.add(9);
list.add(true);
list.add("a string");
}
The second piece of code is in a different class, inside the main function:
List<Integer> arr = new ArrayList<Integer>();
arr.add(14);
System.out.println(arr.toString());
a.func(8, arr);
System.out.println(arr.toString());
Running the code results in this lines being printed:
[14]
[14, 9, true, a string]
This got me pretty confused since arr is an ArrayList of type Integer, how can it contain objects of type boolean and String? Is there a transformation of the list in the function func to a raw type (which mean it becomes of generic type Object)? And if so how is it possible since you cannot do this for example: List<Integer> arr = new ArrayList<Object>();?
Would love some clarification on this, maybe it will help me grasp this subject of generic types better. Thanks!
Java does not allow the creation of generic Arrays. The Java Collection Classes are mainly implemented using Object arrays. The ArrayList class may look like the following
public class ArrayList<T> implements List<T>, Serializable {
private transient Object[] data;
// more content...
}
When creating a new Instance of the ArrayList a new Object[] array is created that can hold objects of any type. Typesafety is only achieved through using the generic Type Parameter.
Since List did not provide any Type parameter it makes use of the rawtype and anything can be added to the list. Therefore always make sure to infer template arguments to keep the typesafety.
public void func(int k, List<Integer> list) {
list.add(9); // works
list.add(true); // compile error
list.add("a string"); // compile error
}
You should never use rawtypes. Depending on your compiler settings warnings will be omitted.
It's better to use (bound/unbound) wildcards.
Reason behind such kind of output is that you are passing List as an parameter to func( int k , List list ) .
And list in func method is non- generic which allows you to add string as well so you are getting such output.
List<Integer> arr = new ArrayList<Integer>();
arr.add(14);
System.out.println(arr.toString());
a.func(8, arr); // here you are passing a list
System.out.println(arr.toString());
public void func(List list) { // here List in non-generic
list.add(9);
list.add(true);
list.add("a string");
}
The whole point of a Generic code in Java is to provide type-safety before the actual code compilation. The compiled code have no type, that's why we don't face any problem in our code becuase when actually func() method is called, the List arr doesn't have any type.
When we're calling System.out.println(arr.toString()), we're just printing an object. This will fine on any type (i.e., int, boolean, String etc...). That's why we don't encounter any exception.
However, just try to assign a value from this list and we'll get java.lang.ClassCastException
...
func(8, arr);
System.out.println(arr.toString());
int a = arr.get(2);

Using an Array as table in custom generic Hashtable to store entries

I am experimenting with making my own custom Hashtable as a way of understanding the data structure, and have run into what many other people seem to have run into; that you cannot create a generic array the same way you would make another array. I understand the reasons for this, but also know that Java's HashMap itself uses an array to store Entry items. If my understanding is correct, Java's HashMap creates an object[] and then casts each element of the object to the Entry class each time the put or get method is called. Is this correct?
I have read answers about generic arrays saying it is possible to instead do something like having Entry[] table as a class variable and then using table = (Entry[]) new Object[size]; in the constructor as a way of avoiding having to do the casting in both the put and get methods, but this does leads to a ClassCastException, which is understandable since it would have to check each element of the Object array anyway in order to make sure they are the Entry class. Does this mean I cannot use this method in my custom Hashtable?
Finally, another way of creating the Entry array seems to be checking the class type in the constructor and using Entry[] table = (Entry[]) Array.newInstance(c, s); Is this method perhaps more suitable?
Below is a snippet of my own code relevant to this question. I am wondering if my interpretation of everything above is correct, and if this is an acceptable way of going about my own Hashtable. I also understand my method of using determining the index for the given hashCode may be incorrect, but that is outside the scope of my question :), and my put and get methods are definitely incomplete!
public class HashTable<K, V> {
Object[] buckets;
HashTable(int size) {
buckets = new Object[size];
this.size = size;
}
void put(K key, V value) {
int i = key.hashCode()%size;
buckets[i] = (Entry) new Entry(key, value, (Entry) buckets[i]);
}
K get(K key) {
int i = key.hashCode()%size;
Entry entry = (Entry) buckets[i];
return entry.key;
}
}
If my understanding is correct, Java's HashMap
creates an object[] and then casts each element of the object to the
Entry class each time the put or get method is called. Is this
correct?
The standard library's source is available. You could check it for yourself. If you did, you would find that no, that's not quite what java.util.HashMap does.
I have read answers about generic arrays saying it is possible to
instead do something like having Entry[] table as a class variable and
then using table = (Entry[]) new Object[size];
To the extent that such answers recommended exactly what you describe, they are wrong. I suspect, however, that your "something like" does not capture the key elements of the answers you saw.
There are two potential issues
Creating an array whose element type is drawn from a type parameter:
class MyClass<T> {
// CAN'T DO THIS:
T[] array = new T[2];
// can do this:
T[] array = (T[]) new Object[2];
// or this:
Object[] array = new Object[2]; // (and cast later)
}
Creating an array whose element type is parameterized
class MyOtherClass<T> {
// CAN'T DO THIS, EITHER:
SomeType<T>[] array = new SomeType<T>[2];
// can do this:
SomeType<T>[] array = (SomeType<T>) new SomeType[2];
// or this:
SomeType[] array = new SomeType[2]; // (and cast later)
}
As you will have seen in the JDK source (you did follow the above link, right?), HashMap's issue is of the second type, and what it does is create an array of the appropriate raw type, and then cast that to the desired parameterized type -- which will trip the compiler's type safety warnings, but is in fact perfectly type safe as long as no other, raw or differently parameterized, reference escapes.
in the constructor as a
way of avoiding having to do the casting in both the put and get
methods, but this does leads to a ClassCastException [...]. Does this
mean I cannot use this method in my custom Hashtable?
Yes, of course it does. The method you describe and demonstrate is invalid, as the exception tells you. An Object[] is not an Entry[]. But that's not what the answers you reviewed were suggesting you do.
Finally, another way of creating the Entry array seems to be checking
the class type in the constructor and using Entry[] table = (Entry[])
Array.newInstance(c, s); Is this method perhaps more suitable?
Rarely is reflection a better answer for anything. It only makes sense when you don't have all the type information you need at compile time, and that is not your case.
It looks like your Entry class is an inner class, which I'd recommend against because it makes things more complicated. First, let's just assume that we don't have an inner class.
For the illustration, we have a simple generic class:
class Foo<T> {}
There's a difference between these two generic array types:
class Container<T> {
// creating an array with erasure of T[]
// vvvvvvvvvvvvv
T[] arrA = (T[]) new Object[N];
// creating an array with erasure of Foo<T>[]
// vvvvvvvvvv
Foo<T>[] arrB = (Foo<T>[]) new Foo[N];
// Note that the following would be slightly
// better because it doesn't use a raw type,
// but it doesn't work for this illustration
// because it's not the erasure of Foo[]:
// (Foo<T>[]) new Foo<?>[N];
}
Casting checks the erasure of type, so suppose we create a new container and assign those arrays to something in the outside world:
Container<String> c = new Container<String>();
String[] arrA1 = c.arrA;
Foo<String>[] arrB1 = c.arrB;
// After erasure these assignments become:
String[] arrA1 = (String[]) arrA;
Foo[] arrB1 = arrB;
The first assignment, arrA1 = c.arrA throws a ClassCastException, but the second assignment, arrB1 = c.arrB does not. This is because in the first case the conversion is from Object[] to String[] whereas in the second case there is no checked cast because all parameterizations of Foo<T> just become Foo after erasure.
This is all to explain my next point which is that creating an array of a parameterized type is more acceptable than creating an array of a type variable. In the case of the type variable array we have an Object[] masquerading as a T[] but in the case of the parameterized type we actually do have an array of Foo[], it's just that there is no checking for the type arguments to Foo. In other words:
Container<String> c = new Container<String>();
// Recall that this assignment doesn't throw a ClassCastException
Foo<String> arrB = c.arrB;
Object[] arrBAsOBj = arrB;
// This assignment throws an ArrayStoreException
arrBAsObj[0] = new StringBuilder();
// This assignment does not throw an ArrayStoreException
arrBAsObj[0] = new Foo<Integer>();
Although, I'd like to note that you should never expose a generic array to the outside world. I'm just doing that to illustrate the explanation.
Anyway, if you're writing something like a hash table, it's acceptable to create an unchecked array of a parameterized type. I usually write a helper method like this:
private static <K, V> Map.Entry<K, V>[] createUncheckedArray(int length) {
#SuppressWarnings("unchecked")
final Map.Entry<K, V>[] unchecked =
(Map.Entry<K, V>[]) new Map.Entry<?, ?>[length];
return unchecked;
}
Just don't return it to the outside world, because we still don't actually have a generic array, just an array of Map.Entry with unchecked type arguments.
Really Java should just have a simple class like Array<T> for this sort of case when we actually need a fixed-length container.
For an inner class you have to use a parameterized type as a qualifier, something like this:
private Entry[] createUncheckedArray(int length) {
#SuppressWarnings("unchecked")
final Entry[] unchecked =
(Entry[]) new HashTable<?, ?>.Entry[length];
return unchecked;
}

Casting Object[] to String[] error

I need this code, but i get this error:
Ljava.lang.Object; cannot be cast to java.lang.String
public Object[] getAllKeys (){
return keys.toArray(new Object[keys.size()]);
}
public String[] getNames (){
return ((String[])super.getAllKeys()); <- Error here. Can't cast, why?
}
The type of the array is Object[] so it cannot know that it contains only Strings. It is quite possible to add a non-String object to that array. As a result the cast is not allowed.
You can return Object[] and then cast each of the objects within that array to string. i.e. (String)arr[0] or you can create a new String[] array and copy all the elements over before returning it.
toArray() returns an array of Objects. If you want to create an array of Strings out of it, you will have to do it yourself. For example,
Object [] objects = super.getAllKeys();
int size = objects.size();
String [] strings = new String[size];
for (int i = 0; i < size; i++)
strings[i] = objects[i].toString();
or something similar... Hope this is useful.
Every String is an Object. Every Object is NOT a String.
You cannot do the cast, because even though Object is a base class of String, their array classes Object[] and String[] classes are unrelated.
You can fix this problem by introducing an additional method that allows taking a typed array:
public Object[] getAllKeys (){
return getAllKeys(new Object[keys.size()]);
}
// Depending on your design, you may want to make this method protected
public <T> T[] getAllKeys(T[] array){
return keys.toArray(array);
}
...
public String[] getNames (){
return super.getAllKeys(new String[keys.size()]);
}
This code takes advantage of the other overload of toArray, which accepts a typed array as an argument.
This cannot be done implicitly since the runtime cannot know that the elements in Object[] are all String types.
If you don't want to code a loop yourself, then one way to coerce is to use
String[] myStringArray = Arrays.asList(keys).toArray(new String[keys.length]);
I think that this will happen without any string copies being taken: asList() binds to the existing array data and toArray uses generics which are removed at runtime anyway due to type erasure. So this will be faster than using toString() etc. Don't forget to deal with any exceptions though.
Try the following snippet
Object[] obj = {"Red","Green","Yellow"};
String[] strArray = (String[]) obj; // Casting from Object[] to String[]

Equivalent to Arrays.asList() but for arrays?

I'd like a convenience method to take a set of parameters and return an array, much like Arrays.asList(T... items) will take a set of parameters and return a List<T> of those items.
It's easy enough to write one, but does one already exist in java?
UPDATE
My bad! I didn't realize the question was so unclear. Your questions have forced me to realize that the question isn't quite the question I thought it was.
I have several calls like the following that place various key/values into a Map:
put( Key.get(A.class), new Key[] { Key.get(X.class), Key.get(Y.class), Key.get(Z.class)});
... where the map is of type Map<Key<? extends Foo>,Key<? extends Foo>[]>
I was looking for a typesafe and succinct way to execute the above statement, and I thought that something like the following would work:
put( Key.get(A.class), toArray( Key.get(X.class), Key.get(Y.class), Key.get(Z.class)));
... where toArray() is defined as something like
private static <T> T[] toArray( T... t ) {
return t;
}
However, it turns out that this solution is not typesafe itself, and thus it's really not much more succinct than just creating a new array manually using new. This was the first cause of my misunderstanding.
I thought that I could get typesafety by using a List instead of an array and then using Arrays.asList() to populate the values of the list, but it turns out that that's not typesafe either. This was the second cause of my misunderstanding. I thought that Arrays.asList() would make this statement more succinct than it actually does, and thus I was looking for something that would do the same for me for arrays.
So I suppose the question is really - Is there a succinct way to get typesafety in the above situation?
Arrays already have such a shortcut syntax:
String[] strArray = {"one", "two", "three"};
In response to your update:
As it seems like you discovered, arrays of parameterized types can never be type-safe. This is one of several limitations due to the fact that arrays and generics are like oil and water.
A varargs method such as Arrays.asList isn't spared from this limitation since varargs works by implicitly creating an array of the comma delimited arguments. In order to have type-safety, you'll need to avoid any solution involving arrays, including varargs.
First, I recommend you change your map's type to hold Lists instead of arrays:
Map<Key<? extends Foo>, List<Key<? extends Foo>>> map = new HashMap<>();
And then build a List before putting it in the Map:
List<Key<? extends Foo>> lst = new ArrayList<>();
lst.add(Key.get(X.class));
lst.add(Key.get(Y.class));
lst.add(Key.get(Z.class));
map.put(Key.get(A.class), lst);
If you want it all in one statement, it's going to be trickier without varargs. Guava's ImmutableList exposes the of factory methods taking up to 12 elements before falling back to varargs. If the Lists in the map aren't going to be modified later, you could store ImmutableList<Key<? extends Foo>> and use:
map.put(
Key.get(A.class),
ImmutableList.of(Key.get(X.class), Key.get(Y.class), Key.get(Z.class))
);
In fact you could still take advantage of those factory methods even if the List needs to be modifiable by copying the returned ImmutableList:
map.put(
Key.get(A.class),
Lists.newArrayList(ImmutableList.of(
Key.get(X.class),
Key.get(Y.class),
Key.get(Z.class)
))
);
But then you're introducing overhead just for the sake of style.
Side note: if you do happen to be using Guava, you might look at using a Multimap instead of a Map of Lists.
What would such a method do that the constructor for the array doesn't already?
String foo = "FOO";
String bar = "BAR";
String[] strings = new String[]{foo, bar};
How about
public static class ToArray {
public static <T> T[] toArray(T... items) {
return items;
}
}
public void example() {
String[] strings = ToArray.toArray("fred", "bob");
}
?
In order to get a Set and return a List you could use an ArrayList:
Set<String> set = new HashSet<String>();
set.add("str1");
set.add("str2");
set.add("str3");
List<String> list = new ArrayList<String>(set);
If you want an array from a list you could do something like:
myList.toArray();
// or even
String[] myStringArray = myList.toArray(new String[]);
Is this what you want? This will return an array because Java treats the varargs construct as an array. I don't know how to genericize it though.
public Object argsToArray(Object... args) {
return args;
}

Categories

Resources