Object-Oriented Programming

CSCI-UA.0470-001

NYU, Undergraduate Division, Computer Science Course - Fall 2013

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();
  }
}