I am learning the Generics and tried to alter one of the examples. I expected the code will not compile even with the different types passed into the generic method arraysEqual(). Turned out it complies and runs.
Why <T> type is not limiting input parameters to be the same (in this case Integer and String arrays)?
public class GenericMethodDemo {
static <T> boolean arraysEqual (T x[], T y[]){
if (x.length != y.length) return false;
for (int i = 0; i < x.length; i++)
if (!x[i].equals(y[i])) return false;
return true;
}
public static void main(String[] args) {
Integer[] nums = {1,2,3,4,5},
nums2 = {1, 2, 3, 4, 5};
String svals[] = {"1", "2","3","4","5"};
if (arraysEqual(nums, nums2)) System.out.println("nums is equal nums2");
if (!arraysEqual(nums, svals)) System.out.println("nums is not equal svals");
}
}
output is:
nums is equal nums2
nums is not equal svals
Arrays and generics do not work well together. Also, arrays have a broken type system. That broken type system part, that explains why your code compiles here.
To explain what's broken about it, and why that then results in your code working, first, some background info is required.
Variance
In java, the types themselves are covariant. This means Whenever something of type A is required, anything of a subtype of A is also okay.
This works fine:
Object o = "hello";
Because "hello" is a string, and String is a subtype of Object, therefore this is okay.
But generics are invariant: Whenever something of type A is required, only A will do.
Let's try the same construct in generics:
List<Object> o = new ArrayList<String>();
This is a compiler error: thus showing that generics start off invariant.
But, generics are interesting in that you can choose your variance. For example, if you want covariance, you can ask for this:
List<? extends Object> o = new ArrayList<String>();
That does compile: You now have a List of 'covariant Object'.
The reason that generics are invariant by default is because math / life / the universe. It is the correct way to do it. After all, imagine that generics were covariant like the basic types were, then you could write:
List<String> stringsOnly = new Arraylist<String>();
List<Object> objects = stringsOnly;
objects.add(5);
String string = stringsOnly.get(0); // but this is '5', a number!
The above code fortunately does not compile, because the second line won't, because generics are invariant. Opt into covariance and it still does not compile:
List<String> stringsOnly = new Arraylist<String>();
List<?> objects = stringsOnly;
objects.add(5);
String string = stringsOnly.get(0); // but this is '5', a number!
This time, the third line won't compile. You can't add anything to a List of covariant anything (well, except the literal null).
Arrays, unfortunately, breaks all this. Let's do the first snippet in array form:
String[] stringsOnly = new String[1];
Object[] objects = stringsOnly;
objects[0] = 5;
String string = stringsOnly[0];
This actually compiles. However, if you run it, you get an ArrayStoreException on the third line.
The reason your code does work, is:
T is bound to be Object. Which works: String[] and Integer[] are both valid, in that Object[] x = new String[10]; is valid java. This is mathematically unsound, as this last snippet showed, but java thinks it is okay.
As your arraysEqual method only reads, and never writes anything, you don't run into the ArrayStoreException.
I strongly recommend you actually write these snippets and experiment; variance is a hard concept, but fundamental to programming.
Arrays in java
Arrays are low level constructs, especially arrays of non-primitive types. You should not use them unless you have a performance reason (and you rarely should).
They have broken variance.
They will never be fixed (because there are better alternatives, and their problems are sometimes used by code that has performance requirements, so these problems cannot be fixed without breaking old code, and for the past 25 years, java has been highly resistant to making changes that break old code, and there are no indications this is going to change anytime soon).
They have broken hash and equals and toString implementations.
They cannot be coerced into having a read-only view or being immutable.
They cannot grow or shrink.
Use List<T> instead which suffers from none of these problems.
Related
I am going through the Java Arrays and additionally, I am looking into generics. Below are the two methods of initializing an array
int[] data = {1,2,3,4,5,6,7,8,9};
// or
int[] data;
data = new int[] {1,2,3,4,5,6,7,8,9};
But when I am using generics, I have mixture of data types for example,
String[] outcome = {"0 wins", "Tie" , "X wins"};
Above array has a single data type of String. What if in an array I have something like below
outcome = {7, "Tie" , 9.0};
now I have a mixture of data types in an array. How I can write it or if it is possible to do with generics? Can I do it with ArrayList?
Thank you
I'd like to correct this:
But when I am using generics, I have mixture of data types
Generics require homogeneous data types. For instance, a List<Integer> is a list that can only ever hold an Integer, and a List<? extends Number> can only ever hold Numbers, which covers other number types like Long, Short, Double, etc...but are referred to by the single type Number.
Anyway, what you're looking for is a Bag - a collection which can hold any arbitrary object. You can implement this with an Object[] or a List<Object>, and you're going to have to check the type of each element you pull out when you want to use it, since there's no way in Java to have a heterogeneous data type, which is what you're looking for.
tl;dr: In my opinion, arrays are not a good fit for the problem, you should use objects instead.
This is not a direct answer to your question, but an answer in the form of a redesign.
First of, let us tackle your statement about generics and arrays. Arrays are covariant and retained, while generics are invariant and erased.
Covariant means that when B extends A, you can Write A[] aArray = new B[someSize];. Invariant means that this is not possible: ArrayList<A> aList = new ArrayList<B>(); will lead to a compile time error.
Retained means that the information about the type is retained at runtime: an array always "knows* what type its elements has. Erased means that the type information is gone after compilation. This is also called Type Erasure.
The mixture of covaraint and retained vs. invariant and erased has good potential to get you into trouble. That is the reason why ArrayList uses an Object[] instead of a T[] as its backing datastructure.
Now to the actual question. As was already said by others, we could go down the road ande create an Object[]. I would strongly advice against this since we loose all type information. The only way to get back that information is a instanceof check, which makes your code rigid. Imagine you change the type of an entry. In this case, the instanceof will return false, possibly leading to unwanted behavior and (best case) some test turning red or (worst case) we may not notice it.
Now how to get around this? We create a class representing (what I infer are) match results:
public class MatchResult {
private final int firstTeamScore;
private final int secondTeamScore;
public MatchResult(final int firstTeamScore, final int secondTeamScore) {
this.firstTeamScore = firstTeamScore;
this.secondTeamScore = secondTeamScore;
}
public int getFirstTeamScore() {
return firstTeamScore;
}
public int getSecondTeamScore() {
return secondTeamScore;
}
public String getResultForFirstTeam() {
if (firstTeamScore > secondTeamScore) {
return "Win"; // In an actual implementation, I would replace this with an enum
} else if(firstTeamScore = secondTeamScore) {
return "Tie";
} else {
return "Lose";
}
}
// You can add a method public String getResultForSecondTeam(), I omitted it for brevity
}
What have we won? We have types. The scores are always ints, the results always Strings. If we were, for example, change the type of the getReultforFirstTeam() from String to, e.g., an Enum, we would get compiler errors for all locations where the type does not match anymore. Thus, we hone the fail-fast design and are forced to modify the code where necessary. And thus, we do not even have the chance to get the sneaky, unwanted behaviour we had before.
1 way to handle this is that you create an array of Object, that can accommodate all of data types
Object[] outcome = {7, "Tie" , 9.0};
And later you can access objects like:
if(outcome[0] instanceof Integer){
Integer i = (Integer) outcome[0];
}
and vice versa..
outcome = {7, "Tie" , 9.0};
is simply not legal.
You can only use this syntax - an array initializer, where the element type is omitted after the equals - in a variable declaration, e.g.
Object[] outcome = {7, "Tie" , 9.0};
As mentioned earlier, you could use an Object array. Alternatively, you can use a generic class. Here's an example:
public class Queue<E> {
private ArrayList<E> queue;
/**Unparametrized constructor**/
public Queue() {
queue = new ArrayList<E>();
}
/**Enqueues an element into the queue.**/
public void enqueue(E val) {
queue.add(val);
}
/**Dequeues an element from the queue.**/
public E dequeue() {
E output = queue.get(0);
queue.remove(0);
return output;
}
/**Gets the current size of the queue.**/
public int size() {
return queue.size();
}
}
Read up on generics and how to use them.
You're going to have to create an array of objects, since all objects in java extends Object:
Object[] arr = new Object[3];
//to add objects to it:
arr[0]=new String("element at index 0");
arr[1]=new Integer(1);
arr[2]=new Character('2');
And to find if the object at index x is (for example) an Integer then your going to have to use a cast:
int x = (Integer)arr[x]; //x could be 0 or 1 or 2
Also you can do it with an ArrayList:
List<Object> listObjects = new ArrayList<Objects>();
Could you help me understand the difference between unbounded wildcard type List and raw type List?
List<?> b; // unbounded wildcard type
List a; // raw type
Along with this can anybody help me understand what is a bounded type parameter list?
List<E extends Number> c;
Here's a summary of the three:
List: A list with no type parameter. It is a list whose elements are of any type -- the elements may be of different types.
List<?>: A list with an unbounded type parameter. Its elements are of a specific, but unknown, type; the elements must all be the same type.
List<T extends E>: A list with a type parameter called T. The supplied type for T must be of a type that extends E, or it is not a valid type for the parameter.
You should really look at Effective Java, Item 23: Don't use raw types in new code.
To use the example from that book, consider the following example... what if you have a collection where you do not care what types of elements are in it. For example, you want to see how many elements are in common between two sets. You might come up with the following:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
This example, while it works, is not a good idea to use because of the use of raw types. Raw types just aren't type safe at all... you could end up modifying the set in a way that is not type safe and corrupt your program. Instead, err on the side of caution and use the type safe alternative:
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
The difference is that you can only add null to a Set<?>, and you CANNOT assume anything about the element you take out of a Set<?>. If you use a raw Set, you can add anything you want to it. The numElementsInCommon method is a good example where you don't even need to add anything and you don't need to assume anything about what is in the set. That's why it's a good candidate for using the ? wildcard.
Hope this helps. Read that whole Item in Effective Java and it will really become clear.
To answer the second part of your question... remember that I said when you use the ? wildcard, you cannot assume anything about the element you take out of the set? What if you do need to make an assumption about the interface of the object you removed from the set. For example, suppose you want to keep track of a set of Cool things.
public interface Cool {
// Reports why the object is cool
void cool();
}
Then you might have some code like this:
public static void reportCoolness(Set s) {
for (Object item : s) {
Cool coolItem = (Cool) item;
coolItem.cool();
}
}
This is not type safe... you need to make sure you passed in a set with only Cool objects. To fix it, you might say:
public static void reportCoolness(Set<Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
This is great! Does exactly what you want and is type safe. But what if later you have this:
public interface ReallyCool extends Cool {
// Reports why the object is beyond cool
void reallyCool();
}
Since all ReallyCool objects are Cool, you ought to be able to do the following:
Set<ReallyCool> s = new HashSet<ReallyCool>();
// populate s
reportCoolness(s);
But you can't do that because generics have the following property: Suppose B is a subclass of A, then Set<B> is NOT a subclass of Set<A>. The technical talk for this is "Generic types are invariant." (As opposed to covariant).
To get the last example to work you would need to create a Set<Cool> by casting (safely) every element in the Set<ReallyCool>. To avoid letting clients of your api go through this nasty, unnecessary code, you can just make the reportCoolness method more flexible like this:
public static void reportCoolness(Set<? extends Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
Now your method takes any Set that contains elements that are Cool or any subclass of Cool. All of these types adhere to the Cool api... so we can safely call the cool() method on any element
Make sense? Hope this helps.
On your first question, the difference between List and List<?>:
One significant difference between the two is that when you have an wildcard as the type, the type of the Collection is unknown, so the add method will throw a compile time error.
You can still get values out of the List<?>, but you need an explicit cast.
Both cases let us put into this variable any type of list:
List nothing1 = new ArrayList<String>();
List nothing2 = new ArrayList();
List nothing3 = new ArrayList<>();
List nothing4 = new ArrayList<Integer>();
List<?> wildcard1 = new ArrayList<String>();
List<?> wildcard2 = new ArrayList();
List<?> wildcard3 = new ArrayList<>();
List<?> wildcard4 = new ArrayList<Integer>();
But what elements can we put into this objects?
We can put only String into List<String>:
List<String> strings = new ArrayList<>();
strings.add("A new string");
We can put any object into List:
List nothing = new ArrayList<>();
nothing.add("A new string");
nothing.add(1);
nothing.add(new Object());
And we can't add anything (but for null) into List<?>! Because we use generic. And Java knows that it is typed List but doesn't know what type it is exact. And doesn't let us make a mistake.
Conclusion: List<?>, which is generic List, gives us type safety.
P.S. Never use raw types in your code.
Please explain what are the differences between List - raw type and List<Object>.
The below code gives run time error:
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); // Compiler-generated cast
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
And this gives compile time error:
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); // Compiler-generated cast
}
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
In the second case, you are doing something the compiler can workout is not safe. In the first case, you are using raw types so the compiler doesn't perform the same checks.
Java has not inheritance for parametric types. So List<Integer> is not a subclass of List<Object>, then you can't use List<Integer> or List<String> as parameter for the unsafeAdd method. But you can write:
private static <T> void unsafeAdd(List<T> list, T o) {
list.add(o);
}
and safelly call it:
List<String> strings = ...
unsafeAdd(string, "42");
and get error while:
List<String> strings = ...
unsafeAdd(strings, 42); // error!
You can see more information in the Oracle Generics Tutorial, Generics, Inheritance, and Subtypes
In the first case you pass unparametrized List to unsafeAdd, so compiler has no way to figure out something is wrong.
Passing List<String> to method which expects List is ok. Adding object to List is ok.
In the second case, you are passing List<String> to method which expects List<Object> - and that's not ok. Because this way you are implicitely allowing to add non-String to List<String> - compiler can figure it out in this case and raises an error.
As Peter already said, in the first example you're telling the compiler to use raw types and thus not to perform any checks on the list. Thus it will allow you to add any object to the passed list, even if it is defined to just allow for strings.
In the second example you tell the compiler that it should assume the list to allow any object, thus the add operation would compile. However, passing a List<String> as a List<Object> is not allowed, since the list of strings has more specific restrictions than the contents just being objects and hence the compiler knows that this is unsafe and error-prone.
If you'd define the parameter to be List<? extends Object> list instead, the compiler would accept passing a List<String> since you tell it that the minimum requirement is that the list must accept objects, but it could also impose harder constraints. However, the add operation wouldn't compile now, since the compiler doesn't know if there are harder constraints and if so what these constraints are. Hence it can't assure that the add operation is safe and refuses to compile that line.
However it looks the same, it is not.
List (raw type) does not care what you insert into it.
List<String> is not compatible with List<Object>. You may do this:
String s = "Hello";
Object o = s;
but you must not do this:
List<String> ls = ...;
List<Object> lo = ls; // Compilation error!
Your example is a good illustration why the Java language does not allow it. By allowing such an assignment a malicious method would be able to put anything into a list which a client consider as a list of Strings.
Consider the following code:
public void method changeObject(Object o) {
o = 42;
}
public void method changeList(List<Object> lo) {
lo.add(42);
}
...
String str = "Hello";
changeObject(str);
// Here it is still safe to use str, its value has not been modified
String str2 = str; // OK
List<String> list = new ArrayList<>();
changeList(list); // it is not allowed, but let's assume it is
// here the list variable would violate its contract - everybody has a right
// to expect it is a list of Strings
String str3 = list.get(0); // Ouch! :(
I have an ArrayList of my own class Case. The class case provides the method getCaseNumber() I want to add all of the cases casenumber to a String[] caseNumber. I've tried this
public String[] getCaseNumberToTempList(ArrayList<Case> caseList) {
String[] objectCaseNumber = null;
for(int i = 0; i < caseList.size(); i++) {
objectCaseNumber[i] = caseList.get(i).getCaseNumber();
}
return objectCaseNumber;
}
But my compiler complaints about that the objectCaseNumber is null at the point insid the for-loop. How can I manage to complete this?
Well, you need to create an array to start with, and initialize the variable with a reference to the array. (See the Java tutorial for arrays for more information.) For example:
String[] objectCaseNumber = new String[caseList.size()];
Alternatively, build a List<String> (e.g. using ArrayList) instead. That's more flexible - in this case it's simple as you know the size up front, but in other cases being able to just add to a list makes life a lot simpler.
In idiomatic Java, you wouldn't use ArrayList as a parameter type. Use List.
Slightly more overhead, but simpler and more readable code is to accumulate in another List and then convert into an arrray:
public String[] getCaseNumberToTempList(List<Case> caseList) {
final List<String> r = new ArrayList<String>();
for (Case c : caseList) r.add(c.getCaseNumber());
return r.toArray(new Case[0]);
}
In your code it does make sense to insist on ArrayList due to performance implications of random access via get, but if you use this kind of code (and I suggest making a habit of it), then you can work with any List with the same results.
Well, as I think you may have misunderstood Arrays as a primitive type. Arrays in java are objects and they need to be initialized before you access it.
I was running some tests earlier and could not find an explanation as to why this code does what it does:
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList(Arrays.asList(Double.valueOf(0.1234)));
System.out.println(list.get(0)); //prints 0.1234
Object d = list.get(0);
System.out.println(d.getClass()); // prints class java.lang.Double
System.out.println(list.get(0).getClass()); // ClassCastException
}
}
That raises a few questions:
why does the List<Integer> accept a Double in the first place (should it compile at all)?
why does the second print work and not the third, although it looks like they are doing the same thing?
EDIT
I understand the following 2 statements:
List aList = new ArrayList(); //I can add any objects in there
List<Integer> aList = new ArrayList<Integer>(); //I can only add something that extends Integer in there
But I don't understand why this one is authorised and why it actually works to some extent at runtime although some operations produce a ClassCastException - I would have expected a ClassCastException at the first line of the code posted above:
List<Integer> aList = new ArrayList(); //I can any objects in there
This:
new ArrayList(Arrays.asList(Double.valueOf(0.1234)))
creates a raw (untyped) ArrayList, in to which you can place anything. This is the correct way to do it:
new ArrayList<Integer>(Arrays.asList(Double.valueOf(0.1234)))
which should now not compile.
If you write
... new ArrayList<Integer>(...
instead it will cause a compiler exception.
On why it works:
System.out.println(list.get(0)); //prints 0.1234
The method Object.toString() is the same in Double and Integer (And because System.out.println() expects an Object this is not cast into an Integer (the compiler optimized the cast away))
Object d = list.get(0);
System.out.println(d.getClass()); // prints class java.lang.Double
Same goes for .getClass(). Here the optimizer again dropped the cast.
System.out.println(list.get(0).getClass()); // ClassCastException
This actually creates an Integer from the list and that fails. It does the cast because the optimizer thought it need to do it, because its not obvious that it doesn't need to.
If you would change that last line to:
System.out.println(((Object)list.get(0)).getClass());
it works :)
The second one does not work because when you use generics the compiler inserts the casts for you (you don't have to). The compiler tries to cast the element to Integer because that is the generic type of the list.
Because you added to the list via an unchecked add, something is now in the list that was not checked when it was going in, thus it fails on coming out.