How to initialize generic array? - java

This method is used to split array into chunks reference. I want to make this method generic.
Problem is, I can not initialize array like this.
T[][] arrays = new T[chunks][];
complete method
public <T> T[][] splitArray(T[] arrayToSplit, int chunkSize) {
if (chunkSize <= 0) {
return null;
}
int rest = arrayToSplit.length % chunkSize;
int chunks = arrayToSplit.length / chunkSize + (rest > 0 ? 1 : 0);
T[][] arrays = new T[chunks][];
for (int i = 0; i < (rest > 0 ? chunks - 1 : chunks); i++) {
arrays[i] = Arrays.copyOfRange(arrayToSplit, i * chunkSize, i * chunkSize + chunkSize);
}
if (rest > 0) {
arrays[chunks - 1] = Arrays.copyOfRange(arrayToSplit, (chunks - 1) * chunkSize, (chunks - 1) * chunkSize + rest);
}
return arrays;
}
What is correct way to initialize generic array?

You cannot initialise arrays with a generic parameter. That is a restriction on generics.
A workaround is to create an Object[][] and cast it to T[][]:
T[][] arrays = (T[][])new Object[chunks][];

You can't create a generic array, but you can declare a generic array.
T[] test = null; // works
T[] test2 = new T[10]; // fails
T[] test3 = (T[]) new Object[10]; // works
At the same time, you should be careful with this

You can't make a generic array directly since type variable information is lost at runtime due to erasure.
However, in your case there is a workaround, since the output type of the method is just an array of type arrayToSplit and arrays in Java do have their type information available at runtime.
So instead of:
T[][] arrays = new T[chunks][];
you can do:
T[][] arrays = (T[][])Array.newInstance(arrayToSplit.getClass(), chunks);
Mixing arrays and generics can be confusing and error-prone though. If possible, I would use the collections API where possible and use List<T> instead of arrays.
Guava even has a method that I think does exactly what you want:
Lists.partition(List, int)
Returns consecutive sublists of a list, each of the same size (the final list may be smaller). For example, partitioning a list containing [a, b, c, d, e] with a partition size of 3 yields [[a, b, c], [d, e]] -- an outer list containing two inner lists of three and two elements, all in the original order.

Related

Question on the Arrays.sort with lambda function's parameter type omitting

I was trying to understand the code that other user submitted as an answer to the leetcode binary search question called "kWeakestRows".
Suppose I have a 2D integer array called "a".
[[1,0],[3,1],[0,2],[1,3],[4,4]]
And if I want to sort the array "a" by the first value of each rows.
So, sort by a[0][0], a[1][0], a[2][0], a[3][0], a[4][0].
Arrays.sort(a, (b, c) -> b[0] - c[0]);
After calling the sort method, I will have an array that looks like this
[[0,2],[1,0],[1,3],[3,1],[4,4]]
Then I was not sure how the lambda parameter types (b,c) could be omitted ?
Where does the compiler infer the type from ?
Notice how the parameter type int[] for b and c are omitted.
Arrays.sort(a, (int[] b, int[] c) -> b[0] - c[0]);
Arrays.sort(a, (b, c) -> b[0] - c[0]);
For reference, here is the code example I was trying to understand.
public int[] kWeakestRows(int[][] mat, int k) {
//2D matrix to hold the number of solders in a row and the index the row is at
int[][] weakest = new int[mat.length][];
int i = 0;
for (int[] row : mat) {
/*
search method returns the index of the first civilian, so if there
are only 2 solders, the first index of a civilian is 2
*/
int solders = binarySearch(row); //if there are no civilians, the number of soldiers is the length of the row
weakest[i] = new int[]{solders, i};
i++;
}
//sort weakest by the first value of soldiers
Arrays.sort(weakest, (a, b) -> a[0] - b[0]);
//add the first k indexes to the result array
int[] res = new int[k];
for (int j = 0; j < k; j++) {
res[j] = weakest[j][1];
}
return res;
}
Thank you for your time.
The declaration of the method you are calling is the following:
public static <T> void sort(T[] a, Comparator<? super T> c)
This declares a generic method that introduces the type parameter T. This parameter enables you to use the method with any reference type. It's like using a list of e.g. Strings: List<String>, where String is the argument for the type parameter E within List.
The type of T can be infered at compile time (Type Inference) as the type of the elements of the array is known as you call the method. When you call the method like Arrays.sort(weakest, (a, b) -> a[0] - b[0]) and weakest is declared as int[][], then it's clear that the type of the elements is int[].
The type is infered implicitly but you can provide it explicitly like Arrays.<int[]>sort(weakest, (a, b) -> a[0] - b[0]), which is redundant. The types of the parameters for the lambda expression are also infered implicitly. See Target Typing for more details.
My understanding is as follows -
Given that the second argument is basically a short-hand implementation of the comparator (functional-interface) and that the type-parameter for the comparator is expected to be a super-type of the first argument, then the type for the comparator arguments would be inferred from the first argument and given that Integer is a supertype of itself as explained here, it would be the case that compiler would not have any issues with it.
The method signature looks something like this -
public static <T> void sort(T[] a, Comparator<? super T> c)

Can't cast array of Object to array of generic type when generic type implements interface

I'm implementing a Quicksort class in Java. For sorting to work, the items that are to be sorted need to be comparable, for example by implementing the Comparable interface. However, implementing Comparable gives me problems when casting at runtime in a method that returns array slices:
public class QuickSorter<T extends Comparable<T>> {
...
private T[] sliceOfArray(T[] arr, int start, int end) {
Object[] slice = new Object[end - start];
for (int i = start; i < end; i++) {
slice[i] = arr[i];
}
return (T[]) slice; // Throws exception at runtime.
}
}
The exception is:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Comparable;
at com.sortingalgos.QuickSorter.sliceOfArray(QuickSorter.java:55)
at com.sortingalgos.QuickSorter.sort(QuickSorter.java:11)
at com.sortingalgos.MainApp.main(MainApp.java:11)
How can I get around this?
The simple fix is to change this line
Object[] slice = new Object[end - start];
to
Comparable[] slice = new Comparable[end - start];
The business of casting to T[] is a hack. It doesn't actually change tbe type of the array. You don't want to be passing it around pretending that it really is correctly typed. It may be clearer to use Object[] (or Comparable[]) or just plain old List<T>.
You can't even do the following:
Object[] a = new Object[] {1,3,4,5};
Integer[] b = (Integer[])a;
System.out.println(Arrays.toString(b));
This is because there is no guarantee that Object[] a doesn't really look like the following:
Object[] a = new Object[]{1,3,4,5.7}
Which clearly is not an array of Integer. But you could do this.
Object[] a = new Integer[] {1,3,4,5};
Integer[] b = (Integer[])a;
System.out.println(Arrays.toString(b));
The problem is that an array of Object isn't an array of Comparable. You need the slice array to have the same element type as the arr array. You can achieve this by changing
Object[] slice = new Object[end - start];
to
T[] slice = java.lang.reflect.Array.newInstance(arr.class.getComponentType(), end - start);
In other words, use reflection to determine at runtime the element type, and create slice arrays of the same type.
Using reflection is likely to be less performant, however. For an in-place sort algorithm like QuickSort, you shouldn't need to create any new arrays at all, you could just use the input array. (Or clone it, if you aren't allowed to modify the input.)

Is it bad practice to make generic arrays, and what would be the alternative?

I've been coding with C++ in school for 3 years now. I've started coding in Java just 2 days ago; my question is:
Is it bad practice to make generic arrays? What would be the alternative?
I am stumped and I can't seem to make a generic array besides doing something weird such as this example:
//Class implementing the MergeSort algorithm with generic types
// Revised by Doina January 2014
package Sorting;
import java.lang.*;
public class MergeSort {
// Wrapper method for the real algorithm
// T is the generic type which will be instantiated at runtime
// elementas are required to be comparable
public static <T extends Comparable<T>> void sort(T[] a) {
mergesort(a, 0, a.length - 1);
}
// Recursive mergesort method, following the pseudocode
private static <T extends Comparable<T>> void mergesort(T[] a, int i, int j) {
if (j - i < 1) return;
int mid = (i + j) / 2;
mergesort(a, i, mid);
mergesort(a, mid + 1, j);
merge(a, i, mid, j);
}
// Merge method
// Here we need to allocate a new array, but Java does not allow allocating arrays of a generic type
// As a work-around we allocate an array of type Object[] the use type casting
// This would usually generate a warning, which is suppressed
#SuppressWarnings("unchecked")
private static <T extends Comparable<T>> void merge(T[] a, int p, int mid, int q) {
Object[] tmp = new Object[q - p + 1];
int i = p;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= q) {
if (a[i].compareTo(a[j]) <= 0)
tmp[k] = a[i++];
else
tmp[k] = a[j++];
k++;
}
if (i <= mid && j > q) {
while (i <= mid)
tmp[k++] = a[i++];
} else {
while (j <= q)
tmp[k++] = a[j++];
}
for (k = 0; k < tmp.length; k++) {
a[k + p] = (T) (tmp[k]); // this is the line that woudl generate the warning
}
}
// Main methos to test the code, using Integer Objects
public static void main(String[] args) {
Integer[] a = new Integer[5];
a[0] = new Integer(2);
a[1] = new Integer(1);
a[2] = new Integer(4);
a[3] = new Integer(3);
a[4] = new Integer(-1);
// T will be instantiated to Integer as a resutl of this call
MergeSort.sort(a);
// Print the result after the sorting
for (int i = 0; i < a.length; i++)
System.out.println(a[i].toString());
}
}
It's not that it's a bad idea per se; it's just that generics and arrays don't mix very well.
The reason is due to covariance and invariance. Arrays are covariant (Integer[] is an Object[] because Integer is an Object, but generic classes are invariant (List<Integer> is not a List<Object> even though an Integer is an Object).
You also have to deal with unchecked casts, which defeat the entire purpose of generics. The most common way to create a generic array - E[] foo = (E[]) new Object[10]; - is not type-safe and can't be enforced at compile time. It's possible to reason about it at runtime, but the compile-time checks which generics bring to the table are lost at that point.
To answer the question directly, where and when possible, you want to use Java Collections instead, as they play very nicely with generics.
Just glancing at your supplied code, I imagine that using List<T> instead of T[] would get you by most of your problems (and I would hope that you're passing an ArrayList in since those operations can become expensive with a linked list).
It's not bad practice to create a generic array, but doing so correctly is so cumbersome people usually avoid it.
The reason it is cumbersome is that generics are erased, while arrays are reified. That is, type parameters are erased during compilation, while the component type of arrays is retained. Therefore, the runtime knows the component type of every array, but has forgotten the type arguments of all objects, i.e. the line
E[] array = new E[10];
does not compile because the runtime needs to know the component type for the new array, but has forgotten was E is.
The workaround in Makoto's answer:
E[] array = (E[]) new Object[10];
is not a good idea, as it actually creates an Object[], but then pretends to the compiler that is an E[]. As the the runtime has forgotten was E is, this cast also succeeds at runtime, even though it is not type correct. However, the runtime still enforces memory safety, by performing an additional check as soon as it is able, i.e. when the object is stored in a variable whose type is not generic. For instance:
static <E> E[] createArray(int size) {
return (E[]) new Object[size];
}
public static void main(String[] args) {
String[] array = createArray(size); // throws ClassCastException
for (String s : array) {
// whatever
}
}
That is, this workaround is hack that only works under certain circumstances, and will cause highly puzzling behaviour otherwise (a ClassCastException in a line of code that does not contain a cast ...).
The only way to create an E[] is through reflection, by providing the class object of our desired component type:
Class<E> eClass = ...;
E[] array = Arrays.newInstance(eClass, 10);
but how can we get this class object? If our caller knows, they can pass us a class literal (like Integer.class), or we can use reflection on some other object. In your case, you have another E[] at hand, so you can ask that array what E is:
E[] originalArray = ...;
Class<E> eClass = (Class<E>) originalArray.getClass().getComponentType();
E[] newArray = (E[]) Array.newInstance(eClass, size);
This will ensure the new array is of the same type as the old one, which is E[], unless somebody lied to us about the type of that array using Makoto's workaround.
As you can see, it is possible to create a generic array, but it is so cumbersome that people usually go the great lengths to avoid it. The usual alternative are using an array of some super type (in your merge sort, Comparable[] might work even better than Object[], because you would not have to cast), or using an ArrayList instead.
Adding to Makoto's answer,I would say that Arrays are covariant due to the fact that their type information is available at runtime whereas generic classes are invariant as type information is not available due to type erasure at compile time.
Covariant Arrays :-
Object[] covariantArrays = new String[5];
covariantArrays[0] = new Dog(); // will give java.lang.ArrayStoreException
Invariant Arrays :-
List invariantArrays = new List<String>();
invariantArrays.add(new Dog()); // Works fine as type information is not available
For this reason Generic arrays don't go well as Generics are limited to compile type safety and Arrays have real type information available even at Runtime

Java primitive array List.contains does not work as expected

Why when I use this code,
int[] array = new int[3];
array[0] = 0;
array[1] = 1;
array[2] = 2;
System.out.println(Arrays.asList(array).contains(1));
it outputs false. But when I use this code,
Integer[] array = new Integer[3];
array[0] = 0;
array[1] = 1;
array[2] = 2;
System.out.println(Arrays.asList(array).contains(1));
it outputs true?
Arrays.asList(int[]) will return a List<int[]>, which is why the output is false.
The reason for this behavior is hidden in the signature of the Arrays.asList() method. It's
public static <T> List<T> asList(T... a)
The varargs, internally, is an array of objects (ot type T). However, int[] doesn't match this definition, which is why the int[] is considered as one single object.
Meanwhile, Integer[] can be considered as an array of objects of type T, because it comprises of objects (but not primitives).
Arrays.asList(array) converts an int[] to a List<int[]> having a single element (the input array). Therefore that list doesn't contain 1.
On the other hand, System.out.println(Arrays.asList(array).contains(array)); will print true for the first code snippet.

Simplify this generic method to concatenate Java arrays

My goal here is to implement a method that will concatenate an arbitrary number of arrays into a single array of their common supertype, returning the resulting (typed) array. I have two implementations.
The first (this one doesn't need to be simplified):
public static <T> T[] concatArrays(Class<T> type, T[]... arrays) {
int totalLen = 0;
for (T[] arr: arrays) {
totalLen += arr.length;
}
T[] all = (T[]) Array.newInstance(type, totalLen);
int copied = 0;
for (T[] arr: arrays) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return all;
}
Let's create some arrays:
Long[] l = { 1L, 2L, 3L };
Integer[] i = { 4, 5, 6 };
Double[] d = { 7., 8., 9. };
Our method is called with:
Number[] n = concatArrays(Number.class, l, i, d);
This works and is completely type-safe (e.g., concatArrays(Long.class, l, i, d) is a compiler error), but it's somewhat annoying to specify Number.class if it's not necessary. So I implemented the following method (this is the one I want to simplify):
public static <T> T[] arrayConcat(T[] arr0, T[]... rest) {
Class commonSuperclass = arr0.getClass().getComponentType();
int totalLen = arr0.length;
for (T[] arr: rest) {
totalLen += arr.length;
Class compClass = arr.getClass().getComponentType();
while (! commonSuperclass.isAssignableFrom(compClass)) {
if (compClass.isAssignableFrom(commonSuperclass)) {
commonSuperclass = compClass;
break;
}
commonSuperclass = commonSuperclass.getSuperclass();
compClass = compClass.getSuperclass();
}
}
T[] all = (T[]) Array.newInstance(commonSuperclass, totalLen);
int copied = arr0.length;
System.arraycopy(arr0, 0, all, 0, copied);
for (T[] arr: rest) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return all;
}
This is nicer to use from the client's perspective:
Number[] n = arrayConcat(l, i, d);
And again, the compiler is smart enough to give an appropriate error on Long[] all = arrayConcat(l, i, d). Since the compiler is able to recognize this error, it is clear that I am performing work at runtime (determining the common superclass of the given arrays) that the compiler is able to perform at compile time. Is there any way to implement my method without using my reflection-based method for determining a common superclass for the array creation step?
I tried this approach:
public static <T> T[] arrayConcat(T[]... arrays) {
int totalLen = 0;
for (T[] arr: arrays) {
totalLen += arrays.length;
}
Object[] all = new Object[totalLen];
int copied = 0;
for (T[] arr: arrays) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return (T[]) all;
}
but this throws a ClassCastException upon returning. Obviously new T[totalLen] is also out. Does anyone have any other ideas?
You can do something like this:
public static <T> T[] arrayConcat(T[]... arrays) {
int totalLen = 0;
for (T[] arr: arrays) {
totalLen += arr.length;
}
T[] all = (T[])Array.newInstance(
arrays.getClass().getComponentType().getComponentType(), totalLen);
int copied = 0;
for (T[] arr: arrays) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return all;
}
This takes advantage of the fact that when using varargs, the compiler constructs an array of the components, and the array's type is properly set up such that the component type is the vararg elements type. In this case the array has type T[][], so we can extract T and use it to construct our T[].
(One exception is if the caller calls it using a generic type as the varargs type, then the compiler can't construct the proper array type. However, if the caller does this it will generate a warning in the caller code (the infamous varargs generics warning), and so the caller is warned that bad things will happen, so it's not our fault.)
One amazing thing about this solution is that it does not produce the wrong answer even when the user passes zero arrays! (As long as the compiler compiles it successfully, it would have inferred (or been specified explicitly) some concrete type T such that T[] is a valid return type. That type T is given to us in the type of arrays) Apparently the compiler doesn't ever infer correctly in this case.
Note that a caller can manually pass the "arrays" argument, and in such case, it could have runtime type of U[][], where U is a subtype of T. In such a case, our method will return an array of runtime type U[], which is still a correct result, as U[] is a subtype of T[].
Simple answer: no. Although the type checker has done some work as far as inferring T, this information is erased by the time it gets to bytecode. Erasure is at the core of Java generics; understand it, and you'll understand generics.
Btw, generics and arrays don't mix well. If you try to concatenate a bunch of List<String>[]s into a single List<String>[], you're going to get compiler warnings (and general lack of type safety).
Your example #2 is actually doing more work at runtime than the compiler is able to do. Consider:
Number[] longs1 = new Long[] { 1L, 2L, 3L };
Number[] longs2 = new Long[] { 4L, 5L, 6L };
Number[] concatted = arrayConcat(longs1, longs2);
The compiler only knows that concatted is a Number[], but your method will (at runtime) figure out that the common type is actually Long[].

Categories

Resources