Related
I have a float[] and i would like to get a list with the same elements. I could do the ugly thing of adding them one by one but i wanted to use the Arrays.asList method. There is a problem though. This works:
List<Integer> list = Arrays.asList(1,2,3,4,5);
But this does not.
int[] ints = new int[] {1,2,3,4,5};
List<Integer> list = Arrays.asList(ints);
The asList method accepts a varargs parameter which to the extends of my knowledge is a "shorthand" for an array.
Questions:
Why does the second piece of code returns a List<int[]> but not List<int>.
Is there a way to correct it?
Why doesn't autoboxing work here; i.e. int[] to Integer[]?
There's no such thing as a List<int> in Java - generics don't support primitives.
Autoboxing only happens for a single element, not for arrays of primitives.
As for how to correct it - there are various libraries with oodles of methods for doing things like this. There's no way round this, and I don't think there's anything to make it easier within the JDK. Some will wrap a primitive array in a list of the wrapper type (so that boxing happens on access), others will iterate through the original array to create an independent copy, boxing as they go. Make sure you know which you're using.
(EDIT: I'd been assuming that the starting point of an int[] was non-negotiable. If you can start with an Integer[] then you're well away :)
Just for one example of a helper library, and to plug Guava a bit, there's com.google.common.primitive.Ints.asList.
How about this?
Integer[] ints = new Integer[] {1,2,3,4,5};
List<Integer> list = Arrays.asList(ints);
Because java arrays are objects and Arrays.asList() treats your int array as a single argument in the varargs list.
Enter Java 8, and you can do following to collect in a boxed Array:
Integer[] boxedInts = IntStream.of(ints).boxed().toArray(Integer[]::new);
Or this to collect in a boxed List
List<Integer> boxedInts = IntStream.of(ints).boxed().collect(Collectors.toList());
However, this only works for int[], long[], and double[]. This will not work for byte[].
Note that Arrays.stream(ints) and IntStream.of(ints) are equivalent. So earlier two examples can also be rewritten as:
Integer[] boxedIntArray = Arrays.stream(ints).boxed().toArray(Integer[]::new);
List<Integer> boxedIntList = Arrays.stream(ints).boxed().collect(Collectors.toList());
This last form could be favored as it omits a primitive specific subtype of Stream. However, internally it is still a bunch of overloaded's which in this case still create a IntStream internally.
The problem is not with Arrays.asList(). The problem is that you expect autoboxing to work on an array - and it doesn't. In the first case, the compiler autoboxes the individual ints before it looks at what they're used for. In the second case, you first put them into an int array (no autoboxing necessary) and then pass that to Arrays.asList() (not autoboxing possible).
Arrays.asList(T... a) effectively takes a T[] which will match any array of true objects (subclasses of Object) as an array. The only thing that won't match like that is an array of primitives, since primitive types do not derive from Object. So an int[] is not an Object[].
What happens then is that the varags mechanism kicks in and treats it as if you had passed a single object, and creates a single element array of that type. So you pass an int[][] (here, T is int[]) and end up with a 1-element List<int[]> which is not what you want.
You still have some pretty good options though:
Guava's Int.asList(int[]) Adapter
If your project already uses guava, it's as simple as using the adapter Guava provides: Int.asList(). There is a similar adapter for each primitive type in the associated class, e.g., Booleans for boolean, etc.
int foo[] = {1,2,3,4,5};
Iterable<Integer> fooBar = Ints.asList(foo);
for(Integer i : fooBar) {
System.out.println(i);
}
The advantage of this approach is that it creates a thin wrapper around the existing array, so the creation of the wrapper is constant time (doesn't depend on the size of the array), and the storage required is only a small constant amount (less than 100 bytes) in addition to the underlying integer array.
The downside is that accessing each element requires a boxing operation of the underlying int, and setting requires unboxing. This may result in a large amount of transient memory allocation if you access the list heavily. If you access each object many times on average, it may be better to use an implementation that boxes the objects once and stores them as Integer. The solution below does that.
Java 8 IntStream
In Java 8, you can use the Arrays.stream(int[]) method to turn an int array into a Stream. Depending on your use case, you may be able to use the stream directly, e.g., to do something with each element with forEach(IntConsumer). In that case, this solution is very fast and doesn't incur any boxing or unboxing at all, and does not create any copy of the underlying array.
Alternately, if you really need a List<Integer>, you can use stream.boxed().collect(Collectors.toList()) as suggested here. The downside of that approach is that it fully boxes every element in the list, which might increase its memory footprint by nearly an order of magnitude, it create a new Object[] to hold all the boxed elements. If you subsequently use the list heavily and need Integer objects rather than ints, this may pay off, but it's something to be aware of.
Why doesn't autoboxing work here; i.e. int[] to Integer[]?
While autoboxing will convert an int to an Integer, it will not convert an int[] to an Integer[].
Why not?
The simple (but unsatisfying) answer is because that is what the JLS says. (You can check it if you like.)
The real answer is fundamental to what autoboxing is doing and why it is safe.
When you autobox 1 anywhere in your code, you get the same Integer object. This is not true for all int values (due to the limited size of the Integer autoboxing cache), but if you use equals to compare Integer objects you get the "right" answer.
Basically N == N is always true and new Integer(N).equals(new Integer(N)) is always true. Furthermore, these two things remain true ... assuming that you stick with Pure Java code.
Now consider this:
int[] x = new int[]{1};
int[] y = new int[]{1};
Are these equal? No! x == y is false and x.equals(y) is false! But why? Because:
y[0] = 2;
In other words, two arrays with the same type, size and content are always distinguishable because Java arrays are mutable.
The "promise" of autoboxing is that it is OK to do because the results are indistinguishable1. But, because all arrays are fundamentally distinguishable because of the definition of equals for arrays AND array mutability. So, if autoboxing of arrays of primitive types was permitted, it would undermine the "promise".
1 - ..... provided that you don't use == to test if autoboxed values are equal.
If you pass an int[] to Arrays.asList(), the list created will be List<int[]>, which is not vaild in java, not the correct List<Integer>.
I think you are expecting Arrays.asList() to auto-box your ints, which as you have seen, it won't.
It's not possible to convert int[] to Integer[], you have to copy values
int[] tab = new int[]{1, 2, 3, 4, 5};
List<Integer> list = ArraysHelper.asList(tab);
public static List<Integer> asList(int[] a) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < a.length && list.add(a[i]); i++);
return list;
}
Alternatively, you can use IntList as the type and the IntLists factory from Eclipse Collections to create the collection directly from an array of int values. This removes the need for any boxing of int to Integer.
IntList intList1 = IntLists.mutable.with(1,2,3,4,5);
int[] ints = new int[] {1,2,3,4,5};
IntList intList2 = IntLists.mutable.with(ints);
Assert.assertEquals(intList1, intList2);
Eclipse Collections has support for mutable and immutable primitive List as well as Set, Bag, Stack and Map.
Note: I am a committer for Eclipse Collections.
int is a primitive type. Arrays.asList() accept generic type T which only works on reference types (object types), not on primitives. Since int[] as a whole is an object it can be added as a single element.
There is a better solution for this starting with Java 9:
List<Integer> list = List.of(null, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8);
I have a float[] and i would like to get a list with the same elements. I could do the ugly thing of adding them one by one but i wanted to use the Arrays.asList method. There is a problem though. This works:
List<Integer> list = Arrays.asList(1,2,3,4,5);
But this does not.
int[] ints = new int[] {1,2,3,4,5};
List<Integer> list = Arrays.asList(ints);
The asList method accepts a varargs parameter which to the extends of my knowledge is a "shorthand" for an array.
Questions:
Why does the second piece of code returns a List<int[]> but not List<int>.
Is there a way to correct it?
Why doesn't autoboxing work here; i.e. int[] to Integer[]?
There's no such thing as a List<int> in Java - generics don't support primitives.
Autoboxing only happens for a single element, not for arrays of primitives.
As for how to correct it - there are various libraries with oodles of methods for doing things like this. There's no way round this, and I don't think there's anything to make it easier within the JDK. Some will wrap a primitive array in a list of the wrapper type (so that boxing happens on access), others will iterate through the original array to create an independent copy, boxing as they go. Make sure you know which you're using.
(EDIT: I'd been assuming that the starting point of an int[] was non-negotiable. If you can start with an Integer[] then you're well away :)
Just for one example of a helper library, and to plug Guava a bit, there's com.google.common.primitive.Ints.asList.
How about this?
Integer[] ints = new Integer[] {1,2,3,4,5};
List<Integer> list = Arrays.asList(ints);
Because java arrays are objects and Arrays.asList() treats your int array as a single argument in the varargs list.
Enter Java 8, and you can do following to collect in a boxed Array:
Integer[] boxedInts = IntStream.of(ints).boxed().toArray(Integer[]::new);
Or this to collect in a boxed List
List<Integer> boxedInts = IntStream.of(ints).boxed().collect(Collectors.toList());
However, this only works for int[], long[], and double[]. This will not work for byte[].
Note that Arrays.stream(ints) and IntStream.of(ints) are equivalent. So earlier two examples can also be rewritten as:
Integer[] boxedIntArray = Arrays.stream(ints).boxed().toArray(Integer[]::new);
List<Integer> boxedIntList = Arrays.stream(ints).boxed().collect(Collectors.toList());
This last form could be favored as it omits a primitive specific subtype of Stream. However, internally it is still a bunch of overloaded's which in this case still create a IntStream internally.
The problem is not with Arrays.asList(). The problem is that you expect autoboxing to work on an array - and it doesn't. In the first case, the compiler autoboxes the individual ints before it looks at what they're used for. In the second case, you first put them into an int array (no autoboxing necessary) and then pass that to Arrays.asList() (not autoboxing possible).
Arrays.asList(T... a) effectively takes a T[] which will match any array of true objects (subclasses of Object) as an array. The only thing that won't match like that is an array of primitives, since primitive types do not derive from Object. So an int[] is not an Object[].
What happens then is that the varags mechanism kicks in and treats it as if you had passed a single object, and creates a single element array of that type. So you pass an int[][] (here, T is int[]) and end up with a 1-element List<int[]> which is not what you want.
You still have some pretty good options though:
Guava's Int.asList(int[]) Adapter
If your project already uses guava, it's as simple as using the adapter Guava provides: Int.asList(). There is a similar adapter for each primitive type in the associated class, e.g., Booleans for boolean, etc.
int foo[] = {1,2,3,4,5};
Iterable<Integer> fooBar = Ints.asList(foo);
for(Integer i : fooBar) {
System.out.println(i);
}
The advantage of this approach is that it creates a thin wrapper around the existing array, so the creation of the wrapper is constant time (doesn't depend on the size of the array), and the storage required is only a small constant amount (less than 100 bytes) in addition to the underlying integer array.
The downside is that accessing each element requires a boxing operation of the underlying int, and setting requires unboxing. This may result in a large amount of transient memory allocation if you access the list heavily. If you access each object many times on average, it may be better to use an implementation that boxes the objects once and stores them as Integer. The solution below does that.
Java 8 IntStream
In Java 8, you can use the Arrays.stream(int[]) method to turn an int array into a Stream. Depending on your use case, you may be able to use the stream directly, e.g., to do something with each element with forEach(IntConsumer). In that case, this solution is very fast and doesn't incur any boxing or unboxing at all, and does not create any copy of the underlying array.
Alternately, if you really need a List<Integer>, you can use stream.boxed().collect(Collectors.toList()) as suggested here. The downside of that approach is that it fully boxes every element in the list, which might increase its memory footprint by nearly an order of magnitude, it create a new Object[] to hold all the boxed elements. If you subsequently use the list heavily and need Integer objects rather than ints, this may pay off, but it's something to be aware of.
Why doesn't autoboxing work here; i.e. int[] to Integer[]?
While autoboxing will convert an int to an Integer, it will not convert an int[] to an Integer[].
Why not?
The simple (but unsatisfying) answer is because that is what the JLS says. (You can check it if you like.)
The real answer is fundamental to what autoboxing is doing and why it is safe.
When you autobox 1 anywhere in your code, you get the same Integer object. This is not true for all int values (due to the limited size of the Integer autoboxing cache), but if you use equals to compare Integer objects you get the "right" answer.
Basically N == N is always true and new Integer(N).equals(new Integer(N)) is always true. Furthermore, these two things remain true ... assuming that you stick with Pure Java code.
Now consider this:
int[] x = new int[]{1};
int[] y = new int[]{1};
Are these equal? No! x == y is false and x.equals(y) is false! But why? Because:
y[0] = 2;
In other words, two arrays with the same type, size and content are always distinguishable because Java arrays are mutable.
The "promise" of autoboxing is that it is OK to do because the results are indistinguishable1. But, because all arrays are fundamentally distinguishable because of the definition of equals for arrays AND array mutability. So, if autoboxing of arrays of primitive types was permitted, it would undermine the "promise".
1 - ..... provided that you don't use == to test if autoboxed values are equal.
If you pass an int[] to Arrays.asList(), the list created will be List<int[]>, which is not vaild in java, not the correct List<Integer>.
I think you are expecting Arrays.asList() to auto-box your ints, which as you have seen, it won't.
It's not possible to convert int[] to Integer[], you have to copy values
int[] tab = new int[]{1, 2, 3, 4, 5};
List<Integer> list = ArraysHelper.asList(tab);
public static List<Integer> asList(int[] a) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < a.length && list.add(a[i]); i++);
return list;
}
Alternatively, you can use IntList as the type and the IntLists factory from Eclipse Collections to create the collection directly from an array of int values. This removes the need for any boxing of int to Integer.
IntList intList1 = IntLists.mutable.with(1,2,3,4,5);
int[] ints = new int[] {1,2,3,4,5};
IntList intList2 = IntLists.mutable.with(ints);
Assert.assertEquals(intList1, intList2);
Eclipse Collections has support for mutable and immutable primitive List as well as Set, Bag, Stack and Map.
Note: I am a committer for Eclipse Collections.
int is a primitive type. Arrays.asList() accept generic type T which only works on reference types (object types), not on primitives. Since int[] as a whole is an object it can be added as a single element.
There is a better solution for this starting with Java 9:
List<Integer> list = List.of(null, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8);
I'm learning how to use stream, and I get a problem with this method.
public static String[] inArray(String[] array1, String[] array2) {
return Arrays.stream(array1)
.filter(str -> Arrays.stream(array2).anyMatch(s -> s.contains(str)))
.distinct().sorted().toArray(**String[]::new**);
}
I'm so confused about String[]::new, could you give me a hint?
String[]::new means size -> new String[size].
When Stream#toArray(IntFunction<A[]> generator) is ready to produce an array, it calls generator and passes (generator.apply) the size of the inner collection to get a collection to fill it up.
I would say the existing answers provide some insight but none of them yet talk about IntFunction<R>.
To add to them explain, what it means in the context of Stream.toArray(String[]::new) is that it represents an IntFunction implementation such as :
new IntFunction<String[]>() {
#Override
public String[] apply(int value) {
return new String[value];
}
}
where the code creates a newly allocated String[] of size value and produces the array of that size as an output.
You are right to be confused, because Java isn't really super clear about types vs. classes.
We know that String[] is a type, as you can declare variables of that type:
jshell> String[] s = new String[]{"Hello", "world"}
s ==> String[2] { "Hello", "world" }
However, String[] actually is treated as a class in Java and not just a type:
jshell> s.getClass()
$2 ==> class [Ljava.lang.String;
That funny looking [Ljava.lang.String, representing the type "array of string" shows up in response to the getClass invocation. I agree that it is surprising. But every object in Java has to have a class, and String[] is that class. (In other languages, you might see something like Array<String> which might be a dash clearer. But then Java has type erasure so again, things look a little confusing.)
In your particular case, here's what's going on. You need to be careful with types when making arrays from streams. Naively, you might get:
jshell> Arrays.asList("a", "b").stream().toArray()
$5 ==> Object[2] { "a", "b" }
So we want the version of toArray that gives us an array:
jshell> Arrays.asList("a", "b").stream().toArray((n) -> new String[n])
$7 ==> String[2] { "a", "b" }
That's better! The result type is an array of strings, instead of just an array of obejcts. Now the (n)->new String[n] can be replaced with a method reference for construction. Java allows array types in method references! So we can write:
jshell> Arrays.asList("a", "b").stream().toArray(String[]::new)
$8 ==> String[2] { "a", "b" }
Aside: There are some caveats when using array types in method references like this, such as the requirement that the array type must be reifiable, but I think that's a little beyond what you might have been asking. The TL;DR here is that, by design, Java allows array types in (constructor-like) method references with ::new.
This is a method reference expression see JLS 15.13. The syntax for method references is:
MethodReference:
ExpressionName :: [TypeArguments] Identifier
Primary :: [TypeArguments] Identifier
ReferenceType :: [TypeArguments] Identifier
super :: [TypeArguments] Identifier
TypeName . super :: [TypeArguments] Identifier
ClassType :: [TypeArguments] new
ArrayType :: new
The particular case you are looking at is the last one. In your example, String[] is an ArrayType which means that it consists of a type name followed by one or more [].
There shouldn't be a class named String[] which is very lame and I could not interpret what it is actually meant for.
See above: it is a type specification not a class name. From a syntactic / linguistic perspective, this usage is analogous to:
Class<?> c = String[].class;
or
if (a instanceof String[])
or even
public void myMethod(String[] arg)
(You wouldn't call those "lame" ... would you?)
Now you could have a valid case for saying that it is syntactically unexpected (especially to a pre-Java 8 programmer) to be able to use the new keyword like this. But this unexpected syntax is a consequence of the strong imperative that the designers have to NOT break backwards compatibility when adding new language features to Java. And it is not unintuitive. (At least, I don't think so. When I first saw this construct, is was obvious to me what it meant.)
Now, if they were starting with a clean slate in 2018, a lot of details of the Java language design would be simpler and cleaner. But they don't have the luxury of doing that.
The documentation of Stream#toArray says it exactly:
The generator function takes an integer, which is the size of the desired array, and produces an array of the desired size.
for example:
IntFunction<int[]> factory = int[]::new;
// v--- once `apply(3)` is invoked,it delegates to `new int[3]`
int [] array = factory.apply(3);
// ^--- [0, 0, 0] create an int array with size 3
String[]::new is a method reference expression and it must be assigned/casted to a certain functional interface type at compile time:
A method reference expression is used to refer to the invocation of a method without actually performing the invocation. Certain forms of method reference expression also allow class instance creation (§15.9) or array creation (§15.10) to be treated as if it were a method invocation.
A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.
Edit
As #Eugene mentioned in comments below. It's necessary to let you know how and where the stream create an fixed size array to collecting all elements.
The following table is showing the stream how to calculates the array size:
sequential stream - AbstractSpinedBuffer#count
parallel stream
stateless OPs with known/fixed size Spliterator - AbstractConcNode#AbstractConcNode
stateful OPs
fixed size Spliterator - Spliterator#estimateSize
unknown size Spliterator - AbstractConcNode#AbstractConcNode
The following table is showing the stream where to creates a fixed size array by array generator IntFunction:
sequential stream
stateful/stateless OPs with unknown/fixed size Spliterator - SpinedBuffer#asArray
parallel stream
stateless OPs with known/fixed size Spliterator - Nodes#flatten
stateful OPs
fixed size Spliterator - Nodes#collect
unknown size Spliterator - Nodes#flatten
String[]::new
This is lambda for the following method:
public String[] create(int size) {
return new String[size];
}
Your whole stream operation is terminating converting that into an array, that is what you do with the last method toArray(), but an array of what?....
of Strings ( thus String[]::new)
The parameter of toArray(...) is a Functional Interface (namely IntFunction<R> and then String[]::new is defined as the Method Reference or in that case constructor to use that generates an array of the desired type.
See https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html
And https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
Adding to the answer of Andrew Tobilko:
"String[]::new means size -> new String[size]"
which, since toArray takes an IntFunction, is similar to:
IntFunction<String[]> generator = new IntFunction<String[]>() {
#Override
public String[] apply(int size) {
return new String[size];
}
};
To convert your stream to another List, you can use:
.collect(Collectors.toList());
Is there no any short cut to do this nicely?
That ugly two loops (One loop to read the pmList and second loop to add to the markupArray) is the only option (Instead of ArrayUtils).
ArrayList<Double> pmList = new ArrayList<Double>();
pmList.add(0.1); // adding through a loop in real time.
pmList.add(0.1);
pmList.add(0.1);
pmList.add(0.1);
double[] markupArray = new double[pmList.size()];
arkupArray = pmList.toArray(markupArray); // This says The method toArray(T[]) in the type ArrayList<Double> is not applicable for the arguments (double[])
Simply use a Double[] array, instead of double[] then everything works fine. If you know the size of the list ahead of time, you can also skip the list and insert directly into the array. It might even be worth to traverse the input two times: Once for retrieving its size and once for insertion.
Auto boxing only works for primitive types, not for arrays of primitive types. A double[] array is no T[] array, since a type parameter T must always be an Object. While a double may be autoboxed to T (with T=Double), a double[] cannot be autoboxed to T[].
The reason why arrays are not autoboxed is probably that this operation would be very costly: Boxing an array means creating a new array and boxing each element. For large arrays, this has a huge performance hit. You don't want such a costly operation to be done implicitly, hence no autoboxing for arrays. In addition, boxing a complete array would yield a new array. Thus, when writing to the new array, the changes would not write through to the old array. So you see, there are some semantics problems with array-boxing, so it is not supported.
If you must return a double[] array, then your must either write your own function or use a third-party library like Guava (see msandiford's answer). The Java Collections framework has no methods for (un)boxing of arrays.
You could use TDoubleArraList or guava's primitive list collection.
You could also determine the size in advance in one loop and add the values in another.
Why not make your own shortcut?
static double[] doubleListToArray(List<Double> list) {
int k = 0;
double[] result = new double[list.size()];
for(double value : list)
result[k++] = value;
return result;
}
Google guava has Doubles#asList(...) and Doubles#toArray(...) which provide conversions from double[] to List<Double> and from Collection<? extends Number> to double[] respectively.
You are right that this is not very intuitive at first look. However, this limitation is related to the way the Java language implements generic types and auto-boxing:
Generic types are erased at runtime. This implies that any ArrayList<Double> is represented by a single compiled Java class ArrayList which is shared with other generic representations of ArrayList such as for example ArrayList<String>. As a consequence, the compiled method ArrayList::toArray does not (and must not) know what generic type an instance represents as the single compiled method must be applicable for any generic type. As the elements could therefore be anything like String or Double, you need to provide an array to the method. The method can then check the type of the target array at runtime and check the elements that are filled into the array at runtime to be assignable to the array's component type. All this logic can be implemented by a single compiled method.
Secondly, auto-boxing and -unboxing is something that only exists at compile time. This means that the statements
Integer i = 42;
int j = i;
are compiled as if you wrote
Integer i = new Integer(42);
int j = i.intValue();
It is the Java compiler that adds the boxing instructions for you. The Java runtime applies a slightly different type system where boxing is not considered. As a consequence, the single compiled method ArrayList::toArray that we mentioned in (1) cannot know that this boxing needs to be applied as we argued that the method must be applicable for any type T which might not always represent a Double.
In theory, you could alter the implementation of ArrayList::toArray to explicitly checks if an array's component type and a lists element type are applicable for unboxing but this approach would result in several branches which would add quite a runtime overhead to the method. Rather, write a small utility method that specializes on the Double type and applies the implicit unboxing due to the specialization. An iteration over all list items suffices for this purpose, this is how the ArrayList::toArray is implemented as well. If your array is small, consider to use an array of boxed values Double[] instead of double[]. If your array is however large, lives long or you are restrained to primitive types in order to comply to a third-party API, use the utility. Also, look out for implementations of primitive collections if you want to ungo the overall boxing. With Java 8, use a Stream in order to inline the array conversion.
Can someone explain what the following does?
private HashSet nodes[];
nodes = new HashSet[21];
I'm a little confused... in the difference between
private HashSet nodes = new HashSet;
and the above, particularly in terms of the square brackets syntax. Is this an array of HashSets? Because normally I'm used to seeing
int[] myarray = new int[21];
Or something like that.
They're just alternatives - both are valid, unfortunately.
Heck, even this would be valid:
int[] bad [] = null;
That's equivalent to
int[][] bad = null;
Don't do this, obviously :)
From section 10.2 of the JLS:
The [] may appear as part of the type at the beginning of the declaration, or as part of the declarator for a particular variable, or both.
And
We do not recommend "mixed notation" in an array variable declaration, where brackets appear on both the type and in declarators.
Basically, use the form that keeps all the type information in one place - the form you're used to. That's the overwhelmingly idiomatic form.
private HashSet nodes = new HashSet;
is not valid Java. Unlike JavaScript, the new operator in Java always requires a parenthesized argument list.
private HashSet nodes = new HashSet(21);
differs from
private HashSet[] nodes = new HashSet[21];
in that the former constructs one HashSet set that initially has space enough for 21 set items while the latter is an array of 21 null values that can be filled with references to sets.
private HashSet nodes[];
declares a member variable that can refer to any array whose elements are of type HashSet.
nodes = new HashSet[21];
creates an array with space for 21 HashSet references and assigns it to that member variable.
Remember that in Java, unlike in C, HashSet[21] is not a type so you can't just allocate space for an array in Java by doing
int[21] myints;
At some point you have to create an array via
new <type>[size],
the abbreviated syntax new <type> { element0, element1, element2, ... },
or reflectively via java.lang.reflect.Array.newInstance.
Yes, it is an array of HashSets.
HashSet nodes[];
is the same as
HashSet[] nodes;
The difference in where you place the brackets only becomes important when you use commas to declare a bunch of variables at a time:
HashSet[] alpha, bravo, charlie; // Three arrays of hashsets
HashSet delta[], echo, foxtrot; // One array (delta) and two hashsets (echo and foxtrot)
In Java, the declaration
private HashSet nodes[];
is equivalent to the declaration
private HashSet[] nodes;
It can be pronounced "an array of HashSets" or "a HashSet array."