OOP Class Notes for 10/08/13
Arrays in Java
- A Java code snippet that uses arrays:
String[] ss = new String[5];
String[] tt = new String[5];
System.out.println(ss.equals(tt)) // prints false
System.out.println(ss.getclass().getName()) // prints [Ljava.lang.String;
System.out.println(int[7].getClass().getName); //prints [I
System.out.println(new String[5][5].getClass().getName()) //prints [[Ljava.lang.String;
System.out.println(ss.getClass().getSuperclass().getName()) //prints just java.lang.object)
- Java does not provide C-style multidimensional arrays in which all
array elements are stored in a continuous block of memory. Instead
Java only supports arrays of array elements.
- Arrays, like any other nonprimitive type in Java, are objects and
extend objects (and all their methods). However, arrays do not
override any methods of class
Object
.
- Arrays also have an addition public field
length
,
denoting the size of the array.
- All array entries are initialized to the default value of the
element type:
String[] ss = new String[5];
System.out.println(ss[2]); // prints null
int[] is = new int[5];
System.out.println(is[0]); // prints 0
-
All array accesses are guarded by bounds checks:
System.out.println(ss[5]); // throws an ArrayIndexOutOfBoundsException
- Array subtyping:
String[] ss = new String[5];
Object o = ss; // This is an upcast, which is OK both at compile-time and at run-time
Object[] os = ss; // What about this?
-
Java arrays are typed covariantly in there element type: if
S
extends T
, then S[] extends T[]
- Covariant subtyping of arrays is a bad design choice. The
following code is not rejected by the type checker at compile-time:
Object[] os = ss; // OK, because of covariant subtyping of arrays
os[3]=new Object(); // OK, because Object is a subtype of Object
ss[3].charAt(3); // OK, because ss has type String[] and String has a charAt method.
- After executing the first line at run-time, the
arrays
ss
and os
are aliased (i.e., they reference the
same array object in memory).
- Hence, the second line would store
an
Object
into the 4th position of the ss
array.
- The third line would then attempt to call
the
charAt
method on
an Object
. However, charAt
can only be
called safely on objects of class String
and its subclasses.
To prevent such unsafe behavior, the JVM will throw
an ArrayStoreException
before executing the second
line. That is, because of covariant subtyping of arrays, all store
operations on arrays incur an additional dynamic type check.
Summary:
- All java arrays are objects
- All accesses to array elements are bounds-checked
- Java arrays do not override any methods of
Object
- Arrays support covariant subtyping, which is a bad language design
choice: fewer static correctness guarantees by the compile and additional run-time
costs due to dynamic type checks for all array store operations.
Manual translation of Java's int[]
The manual translation for Java's int[]
type is similar to the
translations of the String
and Object
classes. We provide the data layout __ArrayOfInt
and
vtable structure __ArrayOfInt_VT
for the array objects:
// forward declarations
struct __ArrayOfInt;
struct __ArrayOfInt_VT;
typedef __ArrayOfInt* ArrayOfInt;
// The data layout.
struct __ArrayOfInt {
__ArrayOfInt_VT* __vptr;
const int32_t length;
int32_t* __data;
// The constructor.
__ArrayOfInt(int32_t length);
// The function returning the class object representing int[].
static Class __class();
// The vtable for int[].
static __ArrayOfInt_VT __vtable;
};
struct __ArrayOfInt_VT {
Class __isa;
int32_t (*hashCode)(ArrayOfInt);
bool (*equals)(ArrayOfInt, Object);
Class (*getClass)(ArrayOfInt);
String (*toString)(ArrayOfInt);
__ArrayOfInt_VT()
: __isa(__ArrayOfInt::__class()),
hashCode((int32_t(*)(ArrayOfInt))&__Object::hashCode),
equals((bool(*)(ArrayOfInt,Object))&__Object::equals),
getClass((Class(*)(ArrayOfInt))&__Object::getClass),
toString((String(*)(ArrayOfInt))&__Object::toString) {
}
};
Note that:
- We add a field
length
and a
field __data
to the data layout. length
stores the size of the
array instance and __data
stores the actual content of
the array instance.
- We do not override any of
Object
's methods
in the vtable.
- We use a C-style array to store the array content. This array is
allocated on the heap by the constructor
of
__ArrayOfInt
and we store a pointer to
its first element in __data
. Since the entries of
C-style arrays are
not initialized by default, we have to initialize them explicitly:
__ArrayOfInt::__ArrayOfInt(int32_t length)
: __vptr(&__vtable), length(length), __data(new int32_t[length]()) {
// Notice the () at the end of the __data initializer expression!
// The () ensures that the C array is properly initialized by a
// constructor call.
// Bad solution for initialization of __data: reinvents the wheel and potentially slow
// for (int i = 0; i < length; i++) {
// __data[i] = 0;
// }
// Ok solution: the C way
// std::memset(__data, 0, length * sizeof(int32_t));
}
We can now create and use arrays as expected. Accesses to array
elements are implemented via direct access to the underlying C-style
array. Since accesses to C-style arrays are not bounds-checked, we
have to add these checks explicitly:
// int[] = new int[5];
ArrayOfInt a = new __ArrayOfInt(5);
// System.out.println(a[2]);
__rt::checkIndex(a->length, 2);
std::cout << a->__data[2] << std::endl;
The implementation of __rt::checkIndex
is as follows:
inline void checkIndex(int32_t length, int32_t index) {
if (0 > index || index >= length) {
throw java::lang::ArrayIndexOutOfBoundsException();
}
}