I've been told by several people that Java allows covariant array subtyping in other words if A is a subtype of B, then A[] is a subtype of B[], but that this is a bad feature because it can lead to runtime errors. Can someone give me a concrete example to illustrate how it causes runtime errors and if/how does Java address this problem?
Very simple.
String strings[] = {"Broken","Type", "system"};
Object objects[] = strings;
objects[0] = 5; // compiles fine, but throws ArrayStoreException at runtime
Covariant types are not bad as long as you as you take things out, but the moment you put things in, the whole thing breaks.
Imagine you have a method takes an Object[] as a parameter.
fn(Object[]a){
...
}
wouldn't it be nice to be able to call it with a String[]?
String[] s = {"I","didn't","know","that","this","was","broken"}
fn(s);
Well, it sounds natural to be able to do so, especially in the early days when we didn't have generics in the language. And all this works fine as long as nothing get mutated, and Java doesn't provide any mechanism to guarantee that.
You should always favour Lists over arrays, because Lists use generics which are invariant.
Related
public T[][] getArrayOfBlocks() {
Node node = this.first;
#SuppressWarnings("unchecked")
T[][] result = (T[][]) new Object[this.nNodes][this.arraySize];
for(int i = 0; i < this.nNodes; i++)
{
for(int j = 0; j < this.arraySize; j++)
if(node.a[j] != null)
result[i][j] = node.a[j];
node = node.next;
}
return result;
}
(Im a newbie in java so my wording will be a bit weird)
Im trying to make a method that creates a 2d array out of a T type unrolled linked list. When i test out the method above using the Integer class instead of the T type i get an error that says
Exception in thread "main" java.lang.ClassCastException: class [[Ljava.lang.Object; cannot be cast to class [[Ljava.lang.Integer; ([[Ljava.lang.Object; and [[Ljava.lang.Integer; are in module java.basa of loader 'bootstrap')
So yeah i would like to know if theres any way to solve this error without changing the return type.
Thanks in advance :)
y out of a T type unrolled linked list.
This is impossible.
Generics are a figment of the compiler's imagination. Generics completely disappear once your java code has been turned into a class file, or if they don't (generics in signatures), the JVM treats it as a comment. It has absolutely no effect. Instead, the compiler uses it to generate errors or warnings and inject invisible casts. This:
List<String> x = new ArrayList<String>();
x.add("Hello");
String y = x.get(0);
ends up in class code as indistinguishable from compiling:
List x = new ArrayList();
x.add("Hello");
String y = (String) x.get(0);
Try it if you're having a hard time getting your head around this idea. Write both, compile it, run javap -c -v to see the bytecode. Identical.
The reason x.add(5) would not work as replacement for x.add("Hello") is simply because javac won't let it happen. If you hack javac to allow it, you get a class file just fine and it verifies just fine. The x.add(5) will even execute just fine. You'd get a ClassCastException on the next line, simply because you're casting an instance of Integer to String.
As a consequence, there is no way to tell the difference between a new ArrayList<String>(); and a new ArrayList<Integer>() at runtime. Obviously; generics disappears; those are both just new ArrayList(), that's it.
In contrast, arrays are 'reified': They aren't a figment of javac's imagination. You can actually get this stuff at runtime. There is a difference between new String[0] and new Integer[0]:
Object[] arr1 = new String[0];
System.out.println(arr1.getClass().getComponentType()); // prints 'String'
It is impossible to write the identical code for generics:
List<?> list1 = new ArrayList<String>();
System.out.println(--nothing you can write here will print String--);
Hence, in your 'unrolled code with T', T is not something you can translate to an actual runtime type, and that means it is impossible to make an array of T.
Still having a hard time believing this? Peruse the API of java.util.List, specifically the various toArray methods it contains.
Look at the no-args one: toArray(). There are two explanations here:
The designer of this class was an utter idiot, because that returns Object[], which is stupid, because clearly that should return T[].
Or, perhaps something else is going on and they 'know' that it is in fact impossible to return a T[] there.
It's, as the rest of this post hopefully already suggested, the second reason.
Fortunately, there are 2 other toArray methods and those two do both return T[] as you desire. They are both based around the notion that the caller puts in the effort of providing that T type for you.
The first version is toArray(T[] in). The toArray code will use the provided array if it is large enough, but if not, it just makes a new one that is the right size and returns it. In practice, you always call listOfStrings.toArray(new String[0]) (you may think new String[list.size()] would be faster - no, that is slower1. A nice example of why writing more complex code because it seems faster is a bad idea. JVMs are far too complex to predict performance like this).
The trick here is that the code in list's toArray will take that array, grab its class (tossing the created array aside), get the component type from that, and then use that to make a new array.
There is another one, too: toArray(IntFunction<T[]> arrayCreator) (you need to look at the javadoc of Collection to see it; it is inherited).
Here we ask the caller to provide code that makes a new array. You use it like this: listOfStrings.toArray(String[]::new).
Pick your poison, or add both. Either trick will work here:
public T[][] getArrayOfBlocks(T[] dummy) {
Class<?> componentType = dummy.getClass().getComponentType();
#SuppressWarnings("unchecked")
T[][] arr = (T[][]) java.lang.reflect.Array.newInstance(componentType, this.nNodes, this.arraySize);
.. code continues here ..
}
or:
public T[][] getArrayOfBlocks(BiFunction<Integer, Integer, T[][]> arrayMaker) {
T[][] arr = arrayMaker.apply(this.nNodes, this.arraySize);
.. code continues here ..
}
Yes, they are both annoying. There are other options but they have significant downsides - the above 2 options are your best bet. That or forget about arrays. Why do you even want a `T[][]` in the first place? Arrays can't grow or shrink, assuming it's not a primitive array (and this isn't, by definition; generics cannot be primitive) they are not more performant, and their toString/equals/hashCode implementations are surprising (that's programmer-ese for 'basically broken'). Their API is non-existent. Why would you want to offer it?
1) In case you desire explanations for this one: It's because the toArray code is hotspot intrinsiced and knows it doesn't need to wipe out the memory space, whereas with `new String[100]`, those 100 references all need to be nulled out first because java guarantees you can't 'see' uninitialized memory.
I want to create a two-dimensional array (yes I know that this is actually an array of arrays) holding Optionals. The normal approach for generic array creation does not work though as it fails with a ClassCastException. Here is my code:
#SuppressWarnings("unchecked")
Optional<Integer>[][] arr = (Optional<Integer>[][]) new Object[5][5];
Is there a way to create such an array, if yes what would be the approach for that?
In Java "it is illegal to create an array of a generic type, a parameterized type, or a type parameter". "Why is it illegal to create a generic array? Because it isn’t typesafe. If it were legal, casts generated by the compiler in an otherwise correct program could fail at runtime with a ClassCastException. This would violate the fundamental guarantee provided by the generic type system." [Joshua Bloch - Effective Java]
So what solutions are to be able to create multidimensional arrays?
The recommended one would be to use a container:
List<List<Optional<Integer>>> arr = new ArrayList<>();
for (int i = 0; i < 5; i++) {
arr.add(new ArrayList<Optional<Integer>>());
}
Generics aside, you can't cast an Object[][] to a raw-typed Optional[][]. You'll get a ClassCastException at runtime. The array has to be created as an Optional[][], not as Object[][]. But generics are usually preferred raw types.
It's not that you can never create arrays of generics. You have to do so indirectly. Typically the way to do it is to create arrays of unbounded-wildcard generics, and then do an unchecked cast -- as you've done -- to the right type:
#SuppressWarnings("unchecked")
Optional<Integer>[][] arr = (Optional<Integer>[][]) new Optional<?>[5][5];
The above applies to the creation of any arrays of some specific generic type. In this case, you might consider using OptionalInt instead of Optional<Integer>. This bypasses any concerns about arrays of generics.
(Overall I'm somewhat suspicious of the notion of creating arrays or collections of Optionals of any flavor. It just seems like an odd thing to do. There are often better alternatives. But it might be justified in some cases. Anyway, whether an array of Optionals is appropriate for whatever problem you're trying to solve is a separate question.)
With the fear of sounding stupid.
Recently, I started with java/android.
I am loving it, but for the love of all that is good, I have come across an operator i can't seem to understand.
The thing is, I do not even know the name of it, so googling for it has been close to impossible. I have not found anything about it, not because it is not there, but because I do not even know where to start.
The operator is <someObject>. The same operator used in List<object>.
I actually became fascinated with it when using the AsyncTask class in android where I had to do something like
MyClass extends AsyncTask<String[], Drawable[], Drawable[]>{
...
Any info on this will be greatly appreciated.
It's not an operator - it's how you specify a generic type parameter.
It's probably best to start off with the Generics part of the Java Tutorial, then Gilad Bracha's paper, then consume the Java Generics FAQ for anything else. (There are lots of knotty corners in Java generics.)
It is not an operator, it is how you declare a parameterized type.
Before Java 5, you coulnd't say that your List was a "List of something", just a List with no type. So when you took an object out of your list, you had to cast it back to a specific type :
List strings = new ArrayList();
strings.add("hello");
String s = (String) strings.get(0);
With Java 5, you can specify the type of the elements, using angular brackets :
List<String> strings = new ArrayList<String>();
strings.add("hello");
String s = strings.get(0);
Because you know the exact type of the elements now, you don't have to cast the objects you get from the list anymore. Plus, the compiler won't let you add incompatible objects in the list :
List<String> strings = new ArrayList<String>();
strings.add(42); // Compiler error : expected a String, got an int
It's to do with generic types and type safety.
In "old java" you just had a List and it contained Objects - not type safe.
List l = new List();
l.add(new Type1()); // valid
l.add(new SomeOtherType()); // valid
These days you say
List<Type1> l = new List<Type1>();
l.add(new Type1()); // valid
l.add(new SomeOtherType()); // invalid since it is a list of Type1
The items in the List can be Type1 or any of it's subclasses.
If I understand your question, then List<Object> myList = new List<Object> means myList can hold Objects or any of its subclasses. This means myList can hold any object as they're all subclasses of Object.
Prior to Java 1.5, Java's collection classes were "untyped" (technically they still are). They could only store items that were derived from java.lang.Object. This was a bit cumbersome as you were forced to cast Object to your assumed contained type. This lead to all kinds of issues and oddities here and there.
Sun (now Oracle), added a "trick" to the language that would allow developers to specify a "type" for a container or other object. As I said, this is a "trick", the compiler performs type safety checks but in reality there is no change to the signature of the object emitted. It cleans up the code and adds a small amount of type safety but in reality its nothing more than a parlor trick.
Look up the Generics documentation provided with the JDK and the tutorials available.
Actually, the question should be
Creating an array of generic anything.
Why can't the compiler take care of it?
The following would be flagged as an error - cannot create generic array.
List<MyDTO>[] dtoLists = {new ArrayList<MyDTO>(), anExistingDtoList};
To overcome that, I need to
List<MyDTO>[] dtoLists = (List<MyDTO>[])Array.newInstance(ArrayList.class, 2);
dtoLists[0] = new ArrayList<MyDTO>();
dtoLists[1] = anExistingDtoList;
So, why can't the compiler convert the first case into the second case?
I do realise that generics are compile-time determinate and not run-time determinate, while arrays are run-time determinate and therefore need a determinate type in order to create an array.
What are the technological/logical barriers compiler designers would encounter that would prevent them being able to implement this?
Is the issue purely philosophical, concerning language orthogonality? If so, how would such a behaviour violate language orthogonality?
Is it a question of complexity? Explain the complexity.
I am hoping answers to my question would give me better insight into java compiler behaviour when it concerns generics.
Side note:
c'mon stop being trigger happy. The answers Array of Generic List
do not answer my question. Why can't compilers spontaneously perform the conversion?
Actually Java does create generic array for varargs, so you can do
List<MyDTO>[] dtoLists = array(new ArrayList<MyDTO>(), anExistingDtoList);
#SafeVarargs
static <E> E[] array(E... array)
{
return array;
}
As to why is explicit generic array creation forbidden, it has something to do with type erasure. (The same concern exists in the above solution, but suppressed by #SafeVarargs) However it is debatable; there are different ways to handle the concern, a compiler warning is probably enough. But they chose to outright ban it, probably because arrays are no longer important anyway now that we have generic collections
I do know that, relative to the workarounds to this issue, Array.newInstance() is an expensive method to call. IIRC it uses a native method to instantiate the array, amidst the other reflection involved. I can't offer any statistics, but this seems like a good enough reason for such functionality not to be automatically substituted in by the compiler in order to allow generic array creation. Especially given the existence of ArrayList, etc. it just doesn't seem like a pressing issue.
Compilers can spontaneously perform the conversion, they are just specified not to because generic arrays can't behave like non-generic arrays.
See 10.5. Array Store Exception:
For an array whose type is A[], where A is a reference type, an assignment to a component of the array is checked at run time to ensure that the value being assigned is assignable to the component.
If the type of the value being assigned is not assignment-compatible with the component type, an ArrayStoreException is thrown.
If the component type of an array were not reifiable, the Java Virtual Machine could not perform the store check described in the preceding paragraph. This is why an array creation expression with a non-reifiable element type is forbidden.
A List<MyDTO>[] would not throw if we put some other kind of List in it, so it doesn't behave as an array. Note the last sentence from the quote: "This is why an array creation expression with a non-reifiable element type is forbidden." This is the reason, it's specified to be so. (And, for the record, this reasoning has always existed, so it was present when the question was posted in 2011.)
We can still do this:
#SuppressWarnings({"unchecked","rawtypes"})
List<MyDTO>[] dtoLists = new List[] {
new ArrayList<MyDTO>(), anExistingDtoList
};
Or this:
#SuppressWarnings("unchecked")
List<MyDTO>[] dtoLists = (List<MyDTO>[]) new List<?>[] {
new ArrayList<MyDTO>(), anExistingDtoList
};
(Besides statically checking the argument types, the varargs thing is equivalent: it creates a List[] and suppresses warnings.)
Now, sure, the specification could be changed to something like "If the type of the value being assigned is not assignment-compatible with the raw type of the component type...", but what is the point? It would save a handful of characters in some unusual situations but otherwise suppress warnings for those who don't understand the implications.
Furthermore, what the tutorial and other typical explanations I've seen don't demonstrate is just how baked in to the type system covariant arrays are.
For example, given the following declaration:
// (declaring our own because Arrays.fill is defined as
// void fill(Object[], Object)
// so the next examples would more obviously pass)
static <T> void fill(T[] arr, T elem) {
Arrays.fill(arr, elem);
}
Did you know that this compiles?
// throws ArrayStoreException
fill(new String[1], new Integer(0));
And this compiles too:
// doesn't throw ArrayStoreException
fill(dtoLists, new ArrayList<Float>());
Before Java 8, we could make those calls to fill fail by giving it the following declaration:
static <T, U extends T> void fill(T[] arr, U elem) {...}
But that was only a problem with type inference, and now it works "correctly", blindly putting List<Float> in to a List<MyDTO>[].
This is called heap pollution. It can cause a ClassCastException to be thrown sometime later, likely somewhere completely unrelated to the actions that actually caused the problem. Heap pollution with a generic container like List requires more obvious unsafe actions, like using raw types, but here, we can cause heap pollution implicitly and without any warnings.
Generic arrays (and really, arrays in general) only give us static checking in the simplest of circumstances.
So it's evident that the language designers thought it was better to just not allow them, and programmers who understand the problems they present can suppress warnings and bypass the restriction.
It's possible add an int and an String in the array ? I said in the same array.
No, Java is a strongly-typed language. So you cannot add a String and an int to the same array if the array is typed as either String or int.
However if your array is typed as Object, you can add a String and an Integer (an integer literal will be autoboxed) to that same array. This is not recommended and is probably a sign that you should think more about your design. The first question you need to ask yourself is why you need to do this. If you do have a valid reason, then it would be better to convert from one to the other instead of having an array typed as Object.
Having a catch-call array where you can shove in any object in a bad idea for many reasons:
You are enforcing no separation between the objects. Are the objects actually related to each other? If so you type then using an interface or create an abstract class that each of the types extend.
Since you have no separation between the objects, anything you pull out of the array is an Object. How would you know what it is? You need to inspect its type explicitly. This is an extremely cumbersome and unmaintainable design.
You essentially end up losing type-safety and will not be able to benefit from type-mismatch errors that will show up during compilation. This will hide possible errors in your code where you may have forgotten to inspect the type, or where you are casting an object to the wrong type. This can lead to all kinds of nightmarish bugs.
Your code is going to be littered with explicit checks and casts and will be unmaintainable (by you or anyone else).
Your code leaks abstraction everywhere. No one can look at the array and realize what the array contains. Anyone who uses your code needs to remember an overwhelming amount of detail as to what types of objects the array can contain.
Obfuscation is never a valid reason. Code should be clear, easy to read, easy to maintain, and easy to understand (for you and for anyone else who will read your code). Any code that looks obfuscated or is "clever" either needs to be rewritten or documented extensively to explain the reason for its "cleverness". As far as obfuscating the source, it is a non-issue since you're going to be distributing the .class files anyway. You can run that through a decompiler to look at the source code. There is nothing you can do at the source level to satisfactorily obfuscate your code; you're only going to make it difficult for you or anyone else to maintain. Obfuscation can be done at the byte-code level and so that doesn't really apply to this situation.
Yes it is possible, but it is not good practice.
Object[] myObjects = new Object[] {array1[i], array2[i], "name1", value1, value2, "name2", value1, value....};
It must be array of objects
Strictly speaking: No.
Otherwise: Yes for most practical purposes:
Object[] array = { 42, "foo" };
Please note, that the 42 is not an int but an `Integer´. But due to autoboxing and unboxing you wont notice the difference. The tradeoff is of course performance and garbage collector overhead.
Also the array must be of type Object[], not of type String[] nor of type int[].
In your string array you could have "123" and then convert it to an int later when you need it.
You can't add a primitive types (including int) to an array with Objects such as String. However, autoboxing of int to Integer will make this possible if you declare an Object[] array.
Object[] array = new Object[2];
array[0] = "Hello";
array[1] = 42;
Though I wouldn't recommend doing this if modeling this String and int as attributes of a class would work.
You can use java.util.ArrayList to do this. You will need to make sure that you check carefully what you are getting when you pull items out though.
Yes it definitely is possible, just have an array of raw objects.
For example:
Object[] arr = new Object[10];
arr[0] = 10; // boxed to Integer class
arr[1] = "foo"; // String class
Then you can use instanceof to determine the type of object stored at a particular index.
For example:
if (arr[0] instanceof Integer) ((Integer) arr[0]) += 10;
Note that this is not necessarily a good practise to get used to, but it does have applications.