Transitioning into OOP: Using complex data types in Java

When a basic data type in Java just isn't enough, you still have complex types available to you. Learn more about reference, array, interface, and other data types here.

In our previous article "Transitioning into OOP: What can you do with Java's primitive data types?" we discussed Java's support for primitive data types and the initialization processes and values for these primitive data types. In this article, we’ll begin to discuss the complex data-type capabilities defined by Java. We’ll talk about the initialization processes, default initialization values, and some operations that can be performed on complex data types.

Reference data types
Along with primitive data types, the Java Virtual Machine (JVM) defines data types known as reference data types. Reference types "refer" to variables and, even though Java doesn’t have an explicit pointer type, can be thought of as pointers to the actual value or set of values represented by variables. An object can be "pointed to" by more than one reference. The JVM never addresses objects directly; instead, the JVM operates on the objects' references.

There are three distinct reference types: class, interface, and array. Reference types can contain references to dynamically created instances of classes, instances, and arrays. References may also contain a special value known as the null reference. The null reference has no type at runtime but may be cast to any reference type. The default value for reference types is null.

Class types
Classes refer to data types that define methods and data. Internally, the JVM typically implements a class type object as a set of pointers to methods and data. Variables defining class types can refer only to instances of the class types or to the null reference, as the following snippet demonstrates:
MyObject     anObject = new MyObject();   // legal
MyObject      anotherObject = null;   // legal
MyObject      stillAnotherObject = 0;   // illegal

Interface types
An interface acts as a template defining methods that an object must implement in order to be referred to as an instance of the interface. An interface cannot be instantiated. A class can implement multiple interfaces and be referred to by any of the interfaces that it implements. An interface variable can refer only to instances of classes that implement that interface. For example, assuming we have defined an interface called Comparable and a class called SortItem, which implements Comparable, we can define the following code:
Comparable   c = new SortItem();

If the Comparable interface defines one method, public void compare(Comparable item), then the SortItem class must provide an implementation of the compare method, as follows:
public class SortItem implements Comparable
      public void compare(Comparable item)
            ...method implementation here

Array types
Java arrays are reference objects that are dynamically created, much like a class. Like a class, they can refer only to instances of arrays or to the null reference, as the following snippet demonstrates:
int[]      myIntArray = new int[5];
int[]      anotherIntArray = null;

An array is a descendant of the Object class and, thus, all methods of class Object may be invoked on an array. An array object contains elements. The number of elements may be zero, in which case the array is said to be empty. All arrays are zero-based, meaning the first element is indexed by the number 0. All accesses on the elements of an array are checked at runtime, and an attempt to use an index that is less than zero or greater than or equal to the length of the array causes an ArrayIndexOutOfBoundsException to be thrown.

The elements of an array are referenced by integer index values, as follows:
int[]   myIntArray = { 9, 5, 6 };
int     int1 = myIntArray[0];
int     int2 = myIntArray[1];
int    int3 = myIntArray[2];

An array object's length can never change. To change the length of an array variable, another array must be created and assigned to the variable, as we see here:
int[]   myIntArray = { 9, 5, 6 };
System.out.println("myIntArray length = " +myIntArray.length);  // output is 3
myIntArray = new int[] { 3, 6, 4, 2, 8 };
System.out.println("myIntArray length = " +myIntArray.length);  // output is 5

Composite data types
The Java language does not support structs or unions. Instead, classes or interfaces are used to build composite types. In defense of this limitation, classes provide the means to bundle data and methods together, as well as a way to restrict access to the private data of the class. For example, using a struct in C, we might define a car this way:
struct Car
      char*       model;
      char*       make;
      int           year;
      Engine*   engine;
      Body*      body;
      Tire**      tires;  

The example above implies that we have previously defined Engine, Body, and Tire structs. In Java, the car could be defined as in Listing A.

The example above assumes that we have previously defined the CarModels and CarMakes interfaces and that we have previously defined the classes Engine, Body, Tire, DurangoEngine, DurangoBody, and GoodyearTire.

Initializing composite data types
Composite data types are initialized by the code defined in the constructor called when a creation technique, such as "new," is applied to a variable of a class type. Fields of a class are initialized with their default values or with explicit value assignments before the constructor is called. Let's look at Listing B for an example to better explain this process.

In Listing B, when the instance of myClass is created using the new operator, the constructor, public MyClass(), is called to allow the class to initialize itself. The initialization process occurs as follows:
  1. 1.      The statement "int      myInt;" is called and assigns the default value, 0, to myInt.
  2. 2.      The statement "AnotherClass      anotherClass;" is called and assigns the default value, null, to anotherClass.
  3. 3.      The statement within the constructor "myint = 2;" is called and assigns the value 2 to myInt.

Predefined composite data types
Java supplies a vast array of predefined composite data types. One of these is the String class belonging to the java.lang package. The String class provides methods to perform a number of commonly used string operations, such as length(), substring(int beginIndex), toUpperCase(), toLowerCase(), equals() and others. Another commonly used composite data type Java provides is the Vector class belonging to the java.util package. The Vector class defines methods to perform commonly used operations on an expandable array of objects. Some of these methods are add(int index, Object element), elementAt(int index), isEmpty(), and remove(int index). These are a small sample of the predefined composite data types that Java supplies. In future articles, we’ll discuss these and other predefined types in detail.

What next?
Along with primitive data types, Java defines the three kinds of reference data types we discussed here. Java also allows the creation of composite data types using combinations of primitive and reference data types. The combination of user-defined composite data types and Java's predefined composite data types provides programmers with a powerful object-oriented toolbox. In our next article, we’ll discuss more of the commonly used complex data types defined by Java and the operations that can be performed using them. We’ll also begin discussing the collections framework Java provides.

Editor's Picks