Java : Why can't I declare an array as a simple Object? - java

In Java, I can compile
Object[] obj = {new Object[1], new Object[2]};
But I cannot compile
Object obj = {new Object(), new Object()};
In the first example I declare a one-dimensional array of Objects and assign it a two-dimensional array. In the second I declare an Object and assign it a one dimensional array.
If a Java array extends Object, why doesn't the second code fragment compile? Why does the first?

Assigning an array to an Object isn't a problem, but you have to create the array like this
Object obj = new Object[] { new Object(), new Object[2] };
Otherwise the compiler won't know that it's an Object array and not some other kind of array.

Because Array is not just a subclass of Object. Arrays also have language-level semantics and syntax.
Separately, your second example begs the question: Where would the object store those two things you're trying to initialize it with? You've just declared an object, but it has no named fields, and lacks the numerically-indexed slots arrays have.
Your first example compiles because you've declared an array of Object (which is to say, object references), and the elements you're giving it to initialize that array are object references (references to the single-element arrays you're creating via new Object[1]).
This may help as well: Java doesn't really have two-dimensional arrays, although there is some convenience syntax that makes it look like it does. It has (single dimensional) arrays of (single dimensional) arrays (of...you get the idea).

The problem is that when you create an array using an initializer, the compiler needs to ensure all elements from the initializer are of the same provided type by checking the type of the element against the provided type.
That said, you always need to provide the type information when initializing an array. Otherwise, the compiler doesn't know how to verify if the array initialization is valid, thus giving an illegal initializer error.
There's no problem assigning an array to an object. For example, you can do the following:
int[] arr = {1,2};
Object obj = arr;
The following code won't compile:
Object obj = {1,2};
Because you didn't explicitly provide the type of the element that the compiler needs to verify the values in the initializer against. And this is required for array initialization in Java.
The following code will compile:
Object[] obj = {1,2};
Because the type of the element was provided(i.e.,Object) and the compiler will check the type of 1, 2 against the type Object(which succeeds since Integer is subtype of Object).

You cannot compile
Object obj = {new Object(), new Object()};
because obj is not an array therefore declare like this
Object[] obj = {new Object(), new Object()};
or
Object obj = new Object[] { new Object(), new Object() };
Edited after the below conversation

Related

Multidimensional Object array behaviour

I am trying to understand some fundamentals about arrays. I thought multidimensional arrays were really just arrays of arrays. With that in mind, I don't understand the following behavior:
Object [] object = {new Object[1]};
Object anotherObject = object[0][0]; //doesn't work
Object yetAnotherObject = object[0]; //does work
I am confused why the second line does not compile ("Array type expected"). I thought since "object[0]" was in fact an object array, I could use the square brackets to access its contents.
What am I missing?
The type of object is Object[], which is a 1 dimensional array. The fact that you store an array element in that array doesn't make it a 2 dimensional array.
Object anotherObject = object[0][0];
doesn't work since the compiler doesn't know that object[0] is an array (and it doesn't have to be - object[0] can hold a reference to any Object).
You'll need casting in order for it to work:
Object anotherObject = ((Object[])(object[0]))[0];

Can we create an object without parentheses?

According to this site the syntax for creating a Java object is:
<JavaType> <variable> = new <JavaObject>();
Though you don't use any parantheses when creating an Array object and instead type brackets which contains the length of each dimension.
Example:
String[][] stringMatrix = new String[5][10];
What I am wondering is if this syntax is specifically and only for creating an Array object or I can make a custom class whose objects are created in a different way then usual
new <JavaObject>();
statement.
new keyword is used to allocate memory for the specific type, which is followed by new keyword.
MyClass obj = new MyClass();
Above line will create an object (allocate memory) for MyClass and initialize member variable by invoking default constructor.
But, below line of code will only allocate memory and initialize each element of array with default value null.
MyClass[][] objMatrix = new MyClass[5][10];
So, we are just declaring an array of size 5x10(allocating memory), but each element in array need some object reference (since, currently they have null reference). So, for that reason, you need to initialize each objMatrix array element by creating object of MyClass and assigning them to each element.
objMatrix[0][0] = new MyClass();
It is specifically for creating arrays. You're stuck with parentheses for your classes.
You can not create a custom class that changes the way the new operator works. However, there are some special cases where an object can be created without the usual new operator.
E.g.
String[] array = { "foo", "bar", "baz" };
Integer value = 42;
demonstrating that you can create arrays without the new keyword in a variable initializer, use String objects using literals and autobox primitive values to their object counterpart without the usual new syntax, but of course, this is not possible with custom types.
Another possibility to create objects without the new operator is deserializing them, which also works for custom types if they are Serializable.
Starting with Java 8, you can use constructor references if you have an appropriate context, e.g.
BigDecimal[] array = new BigDecimal[20];
Arrays.setAll(array, BigDecimal::new);
System.out.println(Arrays.toString(array));
Here, BigDecimal::new is a reference to a constructor of BigDecimal and it is implied from the context, i.e. Arrays.setAll that the function must be able to consume an int value, as the setAll method will evaluate it for every array element, passing the array index, so we initialize the array with ascending numbers in this example.
Another example would be
BigDecimal[] array = Stream.of("1.23", "4.56", "7.89")
.map(BigDecimal::new)
.toArray(BigDecimal[]::new);
System.out.println(Arrays.toString(array));
where it is implied from the context that the constructor used in the .map(BigDecimal::new) step must consume a String, as it will be evaluated for every stream element, ending up at a different constructor than in the first example.
BigDecimal is an ordinary class and these examples would work with a custom class as well, if it has matching constructors.

How can you store an array object in a single reference of Object class?

As far as my understanding goes Object is also a class in java. So,how is it that we can write
Object ob = new Integer[2];
and not
Integer i = new Integer[2];
How is it that a single reference ob can point to array of Integers but reference of type Integer can't?
Because every array is an object. So polymorphism rules tell us that Integer[] (array of Integer) can be stored in an Object, but obviously Integer[] (array of Integer) can not be stored in an Integer.
From JLS#Chapter 10. Arrays
In the Java programming language, arrays are objects (§4.3.1), are dynamically created, and may be assigned to variables of type Object (§4.3.2). All methods of class Object may be invoked on an array.
All the components of an array have the same type, called the component type of the array. If the component type of an array is T, then the type of the array itself is written T[].
Because every array is a subtype of Object. But no array is a subtype of Integer.
Since the following expression on the RHS creates an array object: -
new Integer[2];
So, your reference type on the LHS should be compatible to be able to hold a reference to an array.
Now, since array in Java is a subtype of Object, so an Object type can hold a reference to an array.
But, an Integer reference of course cannot point to an array.
So,
Object ob = new Integer[2]; // is valid. as `Object` type reference can point to an array object
Integer i = new Integer[2]; // is not valid. as `Integer` type reference cannot point to an array object.

Object type in Java and referencing arrays

public class RefMix {
public static void main(String[] args) {
Object[] a = {null, "foo"};
Object[] b = {"bar", b};
a[0] = b;
System.out.println(a[0][0]);
}
}
My understanding is that arrays are objects in Java, and therefore a subclass of the Object type. My further understanding is that a 2-dim array is implemented as an array of references to arrays. Therefore I don't understand why my a[0][0] does not produce bar in the code above. Instead it doesn't compile:
RefMix.java:7: array required, but java.lang.Object found
My understanding is that arrays are objects in Java, and therefore a subclass of the Object type. My further understanding is that a 2-dim array is implemented as an array of references to arrays.
This is all correct and explains why you can do the assignment
a[0] = b;
without any complaints from the compiler.
Therefore I don't understand why my a[0][0] does not produce bar in the code above.
Okay, let's take a look at the types in this expression:
a is a Object[] -- that is an array of Objects
a[0] is an Object
a[0][0] -- Now you are trying to use an array subscript on an Object. The compiler does not know that the Object is in fact an array, so it complains.
The runtime type of an object instance differs from the statically inferred type. The compiler will try to estimate what type each variable could be in the program, in order to catch certain types of errors early. In this case, a[0] will always be an array, but the compiler doesn't know this. Since you are getting an object out of an object array, all the compiler knows is that a[0] is an object. Therefore it raises an error.
In cases where you know something will always be a certain type but the compiler can't figure it out, you can get around this by inserting an explicit cast.
System.out.println(((Object[])a[0])[0]);
You are right, arrays are always Objects in Java, but Objects are not always arrays, therefore you get a compile error, because a is an Object[] (one dimensional). You can not access
a[0][0];
because a is not a two dimensional array (at least it's not declared as such). However in this case, you are sure, that a[0] is an array of Objects. Therefore, you can do this:
Object[] c = (Object[]) a[0];
System.out.println(c[0]);
// or directly:
System.out.println(((Object[])a[0])[0]);
This casts the return type of a[0] (which is Object), into a Object[] and you can then access the "second layer" of the array.
Since array in Java is an object, so the 1st and 2nd assignment, will store your array as the 1st element of your Object array..
Object[] a = {null, "foo"};
Object[] b = {"bar", b};
Now, you have changed your 1st element of a object array to contain value b instead of array.. But since it is an array of object. Everything coming out of it will be object..
Since a[0] is an object..
So, you clearly can't access something like this: -
System.out.println(a[0][0]);
You can try to typecast your object a[0] to object array..: -
System.out.println(((Object[])a[0])[0]);
Do:
System.out.println(((Object[])a[0])[0]);
This will "cast" the object to an object array at runtime.
As I do a lot of C++ coding lately, I find that Java is a lot more strict in type checking then C++ is. Java sees everything but primitive types as Object but Java goes one step further in distinguishing Array and non-Array types. During assignment like "a[0] = b;" , Java first check to see if it is an Array type or not, after that it goes through regular polymorphic type checking procedure. If you wish to make your code work, you should do...
Object[][] a = {{null}, {"foo"}};
Object[] b = {"bar", new java.util.Date()};
a[0] = b;
You can see how java take special care on array types by looking into Java Class signature that is passed to Class.forName() as parameter. For example, data type ..
com.foo.Bar[][] barsIn2D;
can be loaded with signature below...
// [ => Array
// [[ => Array of Array type
// L<object>; => Object, not Array
Class.forName("[[Lcom/foo/Bar;");
As you see, signature starts with either '[' or 'L'.
This tells us whether its an array or not takes precedence over "Lcom/foo/Bar;".
Everything that you are doing is equivalent to this
Object a = {"bar", "foo"};
System.out.println(a[0]);
which doesn't compile either.

Convert ArrayList<String> to String[] array [duplicate]

This question already has answers here:
Converting 'ArrayList<String> to 'String[]' in Java
(17 answers)
Closed 8 years ago.
I'm working in the android environment and have tried the following code, but it doesn't seem to be working.
String [] stockArr = (String[]) stock_list.toArray();
If I define as follows:
String [] stockArr = {"hello", "world"};
it works. Is there something that I'm missing?
Use like this.
List<String> stockList = new ArrayList<String>();
stockList.add("stock1");
stockList.add("stock2");
String[] stockArr = new String[stockList.size()];
stockArr = stockList.toArray(stockArr);
for(String s : stockArr)
System.out.println(s);
Try this
String[] arr = list.toArray(new String[list.size()]);
What is happening is that stock_list.toArray() is creating an Object[] rather than a String[] and hence the typecast is failing1.
The correct code would be:
String [] stockArr = stockList.toArray(new String[stockList.size()]);
or even
String [] stockArr = stockList.toArray(new String[0]);
For more details, refer to the javadocs for the two overloads of List.toArray.
The latter version uses the zero-length array to determine the type of the result array. (Surprisingly, it is faster to do this than to preallocate ... at least, for recent Java releases. See https://stackoverflow.com/a/4042464/139985 for details.)
From a technical perspective, the reason for this API behavior / design is that an implementation of the List<T>.toArray() method has no information of what the <T> is at runtime. All it knows is that the raw element type is Object. By contrast, in the other case, the array parameter gives the base type of the array. (If the supplied array is big enough to hold the list elements, it is used. Otherwise a new array of the same type and a larger size is allocated and returned as the result.)
1 - In Java, an Object[] is not assignment compatible with a String[]. If it was, then you could do this:
Object[] objects = new Object[]{new Cat("fluffy")};
Dog[] dogs = (Dog[]) objects;
Dog d = dogs[0]; // Huh???
This is clearly nonsense, and that is why array types are not generally assignment compatible.
An alternative in Java 8:
String[] strings = list.stream().toArray(String[]::new);
I can see many answers showing how to solve problem, but only Stephen's answer is trying to explain why problem occurs so I will try to add something more on this subject. It is a story about possible reasons why Object[] toArray wasn't changed to T[] toArray where generics ware introduced to Java.
Why String[] stockArr = (String[]) stock_list.toArray(); wont work?
In Java, generic type exists at compile-time only. At runtime information about generic type (like in your case <String>) is removed and replaced with Object type (take a look at type erasure). That is why at runtime toArray() have no idea about what precise type to use to create new array, so it uses Object as safest type, because each class extends Object so it can safely store instance of any class.
Now the problem is that you can't cast instance of Object[] to String[].
Why? Take a look at this example (lets assume that class B extends A):
//B extends A
A a = new A();
B b = (B)a;
Although such code will compile, at runtime we will see thrown ClassCastException because instance held by reference a is not actually of type B (or its subtypes). Why is this problem (why this exception needs to be cast)? One of the reasons is that B could have new methods/fields which A doesn't, so it is possible that someone will try to use these new members via b reference even if held instance doesn't have (doesn't support) them. In other words we could end up trying to use data which doesn't exist, which could lead to many problems. So to prevent such situation JVM throws exception, and stop further potentially dangerous code.
You could ask now "So why aren't we stopped even earlier? Why code involving such casting is even compilable? Shouldn't compiler stop it?". Answer is: no because compiler can't know for sure what is the actual type of instance held by a reference, and there is a chance that it will hold instance of class B which will support interface of b reference. Take a look at this example:
A a = new B();
// ^------ Here reference "a" holds instance of type B
B b = (B)a; // so now casting is safe, now JVM is sure that `b` reference can
// safely access all members of B class
Now lets go back to your arrays. As you see in question, we can't cast instance of Object[] array to more precise type String[] like
Object[] arr = new Object[] { "ab", "cd" };
String[] arr2 = (String[]) arr;//ClassCastException will be thrown
Here problem is a little different. Now we are sure that String[] array will not have additional fields or methods because every array support only:
[] operator,
length filed,
methods inherited from Object supertype,
So it is not arrays interface which is making it impossible. Problem is that Object[] array beside Strings can store any objects (for instance Integers) so it is possible that one beautiful day we will end up with trying to invoke method like strArray[i].substring(1,3) on instance of Integer which doesn't have such method.
So to make sure that this situation will never happen, in Java array references can hold only
instances of array of same type as reference (reference String[] strArr can hold String[])
instances of array of subtype (Object[] can hold String[] because String is subtype of Object),
but can't hold
array of supertype of type of array from reference (String[] can't hold Object[])
array of type which is not related to type from reference (Integer[] can't hold String[])
In other words something like this is OK
Object[] arr = new String[] { "ab", "cd" }; //OK - because
// ^^^^^^^^ `arr` holds array of subtype of Object (String)
String[] arr2 = (String[]) arr; //OK - `arr2` reference will hold same array of same type as
// reference
You could say that one way to resolve this problem is to find at runtime most common type between all list elements and create array of that type, but this wont work in situations where all elements of list will be of one type derived from generic one. Take a look
//B extends A
List<A> elements = new ArrayList<A>();
elements.add(new B());
elements.add(new B());
now most common type is B, not A so toArray()
A[] arr = elements.toArray();
would return array of B class new B[]. Problem with this array is that while compiler would allow you to edit its content by adding new A() element to it, you would get ArrayStoreException because B[] array can hold only elements of class B or its subclass, to make sure that all elements will support interface of B, but instance of A may not have all methods/fields of B. So this solution is not perfect.
Best solution to this problem is explicitly tell what type of array toArray() should be returned by passing this type as method argument like
String[] arr = list.toArray(new String[list.size()]);
or
String[] arr = list.toArray(new String[0]); //if size of array is smaller then list it will be automatically adjusted.
The correct way to do this is:
String[] stockArr = stock_list.toArray(new String[stock_list.size()]);
I'd like to add to the other great answers here and explain how you could have used the Javadocs to answer your question.
The Javadoc for toArray() (no arguments) is here. As you can see, this method returns an Object[] and not String[] which is an array of the runtime type of your list:
public Object[] toArray()
Returns an array containing all of the
elements in this collection. If the collection makes any guarantees as
to what order its elements are returned by its iterator, this method
must return the elements in the same order. The returned array will be
"safe" in that no references to it are maintained by the collection.
(In other words, this method must allocate a new array even if the
collection is backed by an Array). The caller is thus free to modify
the returned array.
Right below that method, though, is the Javadoc for toArray(T[] a). As you can see, this method returns a T[] where T is the type of the array you pass in. At first this seems like what you're looking for, but it's unclear exactly why you're passing in an array (are you adding to it, using it for just the type, etc). The documentation makes it clear that the purpose of the passed array is essentially to define the type of array to return (which is exactly your use case):
public <T> T[] toArray(T[] a)
Returns an array containing all of the
elements in this collection; the runtime type of the returned array is
that of the specified array. If the collection fits in the specified
array, it is returned therein. Otherwise, a new array is allocated
with the runtime type of the specified array and the size of this
collection. If the collection fits in the specified array with room to
spare (i.e., the array has more elements than the collection), the
element in the array immediately following the end of the collection
is set to null. This is useful in determining the length of the
collection only if the caller knows that the collection does not
contain any null elements.)
If this collection makes any guarantees as to what order its elements
are returned by its iterator, this method must return the elements in
the same order.
This implementation checks if the array is large enough to contain the
collection; if not, it allocates a new array of the correct size and
type (using reflection). Then, it iterates over the collection,
storing each object reference in the next consecutive element of the
array, starting with element 0. If the array is larger than the
collection, a null is stored in the first location after the end of
the collection.
Of course, an understanding of generics (as described in the other answers) is required to really understand the difference between these two methods. Nevertheless, if you first go to the Javadocs, you will usually find your answer and then see for yourself what else you need to learn (if you really do).
Also note that reading the Javadocs here helps you to understand what the structure of the array you pass in should be. Though it may not really practically matter, you should not pass in an empty array like this:
String [] stockArr = stockList.toArray(new String[0]);
Because, from the doc, this implementation checks if the array is large enough to contain the collection; if not, it allocates a new array of the correct size and type (using reflection). There's no need for the extra overhead in creating a new array when you could easily pass in the size.
As is usually the case, the Javadocs provide you with a wealth of information and direction.
Hey wait a minute, what's reflection?

Categories

Resources