I think this is impossible but let's try:
Is it possible to write the routine analyzeArrayList()
in such way that it prints dimension of ArrayList
and the bottomtype of list.
The example code should print:
Dimension = 2 and Bottom type = class java.lang.Float
Dimension = 3 and Bottom type = class java.lang.Integer
We suppose that the parameter s is always instance of ArrayList,
not instance of some other type. Note that list may be empty but not null.
void analyzeArrayList(Object s)
{
int dimension;
String typeString;
//<Some code here!>
System.out.println("Dimension = "+dimension+" and Bottom type = "+typeString);
}
Object s = new ArrayList<ArrayList<Float>>();
Object t = new ArrayList<ArrayList<ArrayList<Integer>>>();
.
.
analyzeArrayList(s);
analyzeArrayList(t);
Comment for this:
It is funny that if you have a object with field
ArrayList<ArrayList<Float>>
then you can write the method
void analyzeArrayList(Field s)
if you know that Field s represents ArrayList.
It is not possible due to type erasure.
Java only considers generics at compile time. If you have a List<String> and List<Integer>, the compiler knows that the lists are from different types which make it capable of creating some type of type-safe environment on a per-instance basis. Because of type erasure, at runtime it is like both lists contain objects and are equivalent to each other.
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>();
Specifically, if I return a filled ArrayList do I have to return the type with it such as ArrayList<modNode>? To add onto this, if i'm using a generic typing for a custom link list that uses the <T> tag would I have to return ArrayList<modNode<T>>? Just a little curious on the properties of ArrayLists containing generic objects... Thanks in advance!
Let's say you have a method that returns an ArrayList of String objects:
public ArrayList<String> foo() {
ArrayList<String> list = new ArrayList<>();
// ... fill list ...
return list;
}
This is how you would normally1 declare the method in Java 5+ (since the addition of generics). But you don't have to do this. You could declare the method as:
public ArrayList foo() {
// ... code ...
return list;
}
This would mean you are using raw types. Unless you are interacting with a legacy (pre Java 5) library/application you never want to use raw types. The reason is because a raw type is (nearly?) equivalent to returning ArrayList<Object>. You've just lost all type safety given by generics. The reason for generics is to provide compile-time2 type checks so you don't accidentally use the wrong types. For instance, you could now add a Dog to the ArrayList returned by foo even though you intended it to only contain String objects. Also, code using the returned ArrayList has no guarantee that there will only be String objects inside the ArrayList which can result in all sorts of headaches.
You could get around raw types by casting:
String element = (String) foo().get(0);
However, that's basically what generic code compiles down to anyway; except you no longer have compile-time safety.
If the element type is also generic then yes you would want to return that information as well. Let's say you return an ArrayList of Supplier objects instead. It will be each Supplier that returns the needed String objects.
public ArrayList<Supplier<String>> foo() {
// ... code ...
}
It's important you give the Supplier's generic signature here so you can do things like:
for (Supplier<String> sup : foo()) {
String str = sup.get();
// .. do something with "str" ...
}
If you returned ArrayList<Supplier> then each Supplier.get() would return an Object. You've lost all type safety again.
1. You would actually, in virtually all cases, want to return List rather than ArrayList. It's best to program to an interface.
2. It only works at compile-time due to type erasure. Also see this.
The type parameter <T> depends on the actual Type Parameter you supply to Generic Type. For example:-
List<String> names = new ArrayList<>();
String is the actual type parameter of parameterized type List<String>. Behind the scene compiler did casting on each elements automatically. So you can safely expect get method will return String type.
The modNode class is generic type. Then caller has to declare what is the actual type parameter. It could be String, type that extends Node or whatever. Example below:-
List<modeNode<String>> modeNodes = new ArrayList<>();
However your ArrayList<modNode<T>> actual type parameter is already modeNode. Hence get method will probably returns some kind parameterized type modeNode<T>. Ex:-
List<modeNode<String>> modeNodes = new ArrayList<>();
....
modeNode<String> mn = modeNodes.get(0);
Notes:-
** Rename modNode type class name to ModNode to follow Java convention. Class name must start with Capital letter.
ModeNode<Node> mn = ModeNode.getInstance();
//More readable due to following naming convention.
List<ModeNode<Node>> mns = new ArrayList<>();
** It is preferable to declare as interface type Listthan concrete type ArrayList. Unless if you want to use specific ArrayList behaviour implementation.
Previous question
I have the following code:
ArrayList<Object> list = new ArrayList<Object>();
list.add("StringType");
list.add(5);
list.add(new RandomClass());
List<Class<?>> classes = new ArrayList<>();
classes.add(String.class);
classes.add(int.class);
classes.add(RandomClass.class);
for (int i = 0; i < list.size(); i++) {
if (classes.get(i).isInstance(list.get(i))) {
...
}
}
if (isvalid)
mymethod(...);
public void mymethod(String string, int num, RandomClass randomClass){ }
Now I'm trying to cast the object into the right type with a method using a string argument.
Instead of:
mymethod( (String) list.get(0), (int) list.get(1), (RandomClass) list.get(2) );
I would like to reuse the definition created above for the cast.
mymethod( ( define.get(0) ) list.get(0), .... );
I've also tried using the Class.cast(obj) but of course it returns a type '?' which again defeats the purpose of casting it again using (String).
What is type safety?
In computer science, type safety is the extent to which a programming
language discourages or prevents type errors.
If code is type safe, then the compiler can verify, at compile time, that all the types are correct:
String getName() {
return "name";
}
The compiler knows that "name" must be a String so it can verify that this code will never throw a type error.
Once you do something like:
int getNumber() {
(int) number;
}
The need to explicitly cast to int tells you that this code has an error condition, namely when number is not of type int or a type that is assignable to int.
How does it affect you?
Your code:
define.get(0).cast(list.get(0))
You want the return type of this statement to be of the type of get(0). But the compiler has no way of knowing, at compile time, what define.get(0) returns. This is inidcated to you by the return type.
You have a List<Class<?>>, i.e. a List of a class of "I don't care what type". You then use a member of this List to cast a member of your other List - the only result can be an "I don't care what type".
You can hack around this with:
<T> T get(final int i) {
return (T) define.get(i).cast(list.get(i));
}
This will happily compile:
final String thing = get(0);
As will:
final int thing = get(0);
i.e. all that you have done is to endogenise the cast. The error condition still exists.
define.get(0).cast(list.get(0)) would attempt to cast list.get(0) to the required type.
In order to be able to select the appropriate method, the compiler needs to know at compile time what the types of the arguments are. Or at least a general category such as List<?> etc.
This is needed to support overloading of methods. There can be many methods with the same name, but with different parameter types.
Since you are asking the VM to call a method when it can't determine which exact method you want to call, because it doesn't know at compile time what the types of your parameters are, what you ask cannot be done in Java.
Here is the relevant section from the Java Language Specification.
What it says is that the system selects at compile time which method signature to use, and then, at run time, the particular implementation of that method signature that's correct for the given instance.
You don't actually need to store object's class separately
list.get(0).getClass()
will get you the class of the stored object and then you can use what #Eran suggested
and
list.get(0).getClass().getName()
will get you the String name of your class
Java allows me to return a String from a function of return type Object but it does not allow me to return ArrayList of Strings from a function of return type ArrayList of Objects
in the second function if java can check at runtime that s is an object .Then why cant it check that ArrayList of Strings is actually an ArrayList of Objects.
That's why Java has Generics, image this case:
public ArrayList<Object> hola(){
return new ArrayList<String>();
}
it even won't pass the compiler because compiler is expecting an ArrayList that accept "Objects" not String, also generics was created to avoid the use of casting and to help the programmer in compile time to check if whats inside the collection is the same type its meant to hold. You might think it violate the point of Polymorphism but it doesn't really, using Generic Class help alot if you think about it in a positive way.
If you want to do something like that you would have to do this way:
public <T extends Object> ArrayList<T> take(ArrayList<T> list){
return new ArrayList<T>();
}
That's the correct way doing it, the <T extends Object> mean accept an Object that is subclass of Object, so it'd accept everything because all the classes are subclass of Object.
As #arshaji said, generics are not covariant; for a quick definition of covariance, see this Wikipedia link.
What it means is that while:
Object < String
It is NOT true that:
List<Object> < List<String>
This is because of type erasure. At runtime, a List<Whatever> becomes a List (for backwards compatibility reasons): the type information of elements of the List in code are lost at runtime.
In essence, and while not technically accurate, this Java 5+ code:
for (final String s: listOfStrings)
doSomethingWith(s);
is equivalent, at runtime, to this Java 4- code:
for (final Object o: listOfStrings)
doSomethingWith((String) o); // note the cast
why cant it check that ArrayList of Strings is actually an ArrayList of Objects.
Its because although String IS-AN Object, ArrayList<String> IS-NOT ArrayList<Object>. You cannot do the following in Java:
ArrayList<Object> objs = new ArrayList<String>();
It means that ArrayList<String> cannot be assigned to an ArrayList<Object>. Even though at runtime the type of Arraylist is erased, you cannot assign one type of ArrayList to another type at compile time.
If you want your method to return an ArrayList of any Object, then you can change your method signature to:
public ArrayList<? extends Object> methodName()
{
//.. Your code here
return new ArrayList<String>();
}
I understand that for every primitive type in java there is a reference type that can represent the same values, plus null. For example, int and java.lang.Integer.
I also understand that while all arrays (even java.lang.Object[]) derive from java.lang.Object, only arrays of non-primitive types can be converted to java.lang.Object[].
In other words, we can do:
Class type = Integer.class;
Object[] o = (Object[])java.lang.reflect.Array.newInstance(type, 4);
but the following results in an exception:
Class type = int.class;
Object[] o = (Object[])java.lang.reflect.Array.newInstance(type, 4);
so in that particular case we could do:
Class type = int.class;
Object o = java.lang.reflect.Array.newInstance(type, 4);
...which seems fine, until one wonders how to add elements to an Object that represents an array that cannot be converted to Object[].
is the only way around this problem to check the type of the elements of the array and cast the Object as appropriate?
ie:
if (type == int.class)
((int[])o)[0] = getFirstElement();
else if (type == short.class)
((short[])o[0] = getFirstElement();
Besides creating arrays, java.lang.reflect.Array also offers methods to get and set elements in arrays:
Class<?> type = int.class;
Object array = java.lang.reflect.Array.newInstance(type, 4);
int index = 0;
Object value = getFirstElement();
java.lang.reflect.Array.set(array, index, value);
Also note from the documentation that the value is first automatically unwrapped if the array has a primitive component type. So this should work for any array, including those that do not inherit from Object[].
There is a corresponding Object get(Object array, int index) method too.
Well, in the first case you can assign to Object and then use isInstanceOf Object[] to see if you have an array of Object.
But ultimately you have to have some sort of conditional operation to do element access/assignment. Thankfully there are only (IIRC) 7 different scalar data types.
(The language spec could have defined the Integer/Float/et al to include array member access methods -- eg, Integer would define get and set methods for int[]. But they didn't. At best you could define wrappers for the (alas, final) Number subclasses to define these methods.)