Java ScreenShot
Screenshot      

Screenshot Core Java 2: Volume I - Fundamentals

Table of Contents
 5.  Inheritance


Object: The Cosmic Superclass

The Object class is the ultimate ancestor—every class in Java extends Object. However, you never have to write:

class Employee extends Object


The ultimate superclass Object is taken for granted if no superclass is explicitly mentioned. Because every class in Java extends Object, it is important to be familiar with the services provided by the Object class. We will go over the basic ones in this chapter and refer you to later chapters or to the on-line documentation for what is not covered here. (Several methods of Object come up only when dealing with threads—see Volume 2 for more on threads.) You can use a variable of type Object to refer to objects of any type:

Object obj = new Employee("Harry Hacker", 35000);


Of course, a variable of type Object is only useful as a generic holder for arbitrary values. To do anything specific with the value, you need to have some knowledge about the original type and then apply a cast:

Employee e = (Employee)obj;


Java graphics cplus_icon.gif

In C++, there is no cosmic root class. Of course, in C++, every pointer can be converted to a void* pointer. Java programmers often use Object references for generic programming, to implement data structures and algorithms that support a variety of data types. In C++, templates are commonly used for generic programming. But Java has no templates, so Java programmers often have to give up compile-time typing and make do with code that manipulates Object references.

The equals and toString methods

The equals method in the Object class tests whether or not one object is equal to another. The equals method, as implemented in the Object class, determines whether or not two objects point to the same area of memory. This is not a useful test. If you want to test objects for equality, you will need to override equals for a more meaningful comparison. For example,

class Employee
{ // . . .
 public boolean equals(Object otherObject)
 {
 // a quick test to see if the objects are identical
 if (this == otherObject) return true;
 // must return false if the explicit parameter is null
 if (otherObject == null) return false;
 // if the classes don't match, they can't be equal
 if (getClass() != otherObject.getClass())
 return false;
 // now we know otherObject is a non-null Employee
 Employee other = (Employee)otherObject;
 // test whether the fields have identical values
 return name.equals(other.name)
 && salary == other.salary
 && hireDay.equals(other.hireDay);
 }
}


The getClass method returns the class of an object—we will discuss this method in detail later in this chapter. For two objects to be equal, they must first be objects of the same class.

Java graphics notes_icon.gif

How should the equals method behave if the implicit and explicit parameters don't belong to the same class? Unfortunately, different programmers take different actions in this case. We recommend that equals should return false if the classes don't match exactly. But many programmers use a test:

if (!(otherObject instanceof Employee)) return false;


This leaves open the possibility that otherObject can belong to a subclass. Other programmers use no test at all. Then the equals method throws an exception if otherObject cannot be cast to an Employee object. Technically speaking, both of these approaches are wrong. Here is why. The Java Language Specification requires that the equals method has the following properties:

  1. It is reflexive: for any non-null reference x, x.equals(x) should return true.

  2. It is symmetric: for any references x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

  3. It is transitive: for any references x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

  4. It is consistent: If the objects to which x and y refer haven't changed, then repeated calls to x.equals(y) return the same value.

  5. For any non-null reference x, x.equals(null) should return false.

Rule 5 mandates that you include the test

if (otherObject == null) return false;


in your equals method. What is less obvious is that Rule 2 requires you to test for class equality. Consider a call

e.equals(m)


where e is an Employee object and m is a Manager object, both of which happen to have the same name, salary, and hire date. If you don't check that the class of m is the same as the class of e, this call returns true. But that means that the reverse call

m.equals(e)


also needs to return true—Rule 2 does not allow it to return false, or to throw an exception. Unfortunately, the Java Language Specification does a poor job of explaining this consequence, and the majority of programmers seem to be unaware of it. The standard Java library contains over 150 implementations of equals methods, with a mishmash of using instanceof, calling getClass, catching a ClassCastException, or doing nothing at all. Only a tiny minority of implementations fulfills Rule 2. You can do better, by following our recipe for the perfect equals method.

Here is a recipe for writing the perfect equals method:
  1. Call the explicit parameter otherObject—later, you need to cast it to another variable that you should call other.

  2. Test whether this happens to be identical to otherObject:

    if (this == otherObject) return true;
    


    This is just an optimization. In practice, this is a common case. It is much cheaper to check for identity than to compare the fields.

  3. Test whether otherObject is null and return false if it is. This test is required.

    if (otherObject == null) return false;
    


  4. Test whether this and otherObject belong to the same class. This test is required by the "symmetry rule".

    if (getClass() != otherObject.getClass()) return false;
    


  5. Cast otherObject to a variable of your class type:

    ClassName other = (ClassName)otherObject
    


  6. Now compare all fields. Use == for primitive type fields, equals for object fields. Return true if all fields match, false otherwise.

    return field1 == other.field1
     && field2.equals(other.field2)
     && . . .;
    


If you define the equals method for a subclass of a class that follows these rules, first call equals on the superclass. If that test doesn't pass, then the objects can't be equal. If the superclass fields are equal, then you are ready to compare the instance fields of the subclass.

class Manager extends Employee
{
 . . .
 public boolean equals(Object otherObject)
 {
 if (!super.equals(otherObject)) return false;
 Manager other = (Manager)otherObject;
 // super.equals checked that this and otherObject
 // belong to the same class
 return bonus == other.bonus;
 }
}


Another important method in Object is the toString method that returns a string that represents the value of this object. Almost any class will override this method to give you a printed representation of the object's current state. Here is a typical example. The toString method of the Point class returns a string like this:

java.awt.Point[x=10,y=20]


Most (but not all) toString methods follow this format: the name of the class, followed by the field values enclosed in square brackets. Here is an implementation of the toString method for the Employee class:

public String toString()
{
 return "Employee[name=" + name
 + ",salary=" + salary
 + ",hireDay=" + hireDay
 + "]";
}


Actually, you can do a little better. Rather than hardwiring the class name into the toString method, call getClass().getName() to obtain a string with the class name.

public String toString()
{
 return getClass().getName()
 + "[name=" + name
 + ",salary=" + salary
 + ",hireDay=" + hireDay
 + "]";
}


Then the toString method also works for subclasses. Of course, the subclass programmer should define its own toString method and add the subclass fields. If the superclass uses getClass().getName(), then the subclass can simply call super.toString(). For example, here is a toString method for the Manager class:

class Manager extends Employee
{
 . . .
 public String toString()
 {
 return super.toString()
 + "[bonus=" + bonus
 + "]";
 }
}


Now a Manager object is printed as:

Manager[name=...,salary=...,hireDay=...][bonus=...]


The toString method is ubiquitous for an important reason: whenever an object is concatenated with a string, using the "+" operator, the Java compiler automatically invokes the toString method to obtain a string representation of the object. For example,

Point p = new Point(10, 20);
String message = "The current position is " + p;
 // automatically invokes p.toString()


Java graphics exclamatory_icon.gif

Instead of writing x.toString(), you can write "" + x. This concatenates the empty string with the string representation of x that is exactly x.toString().

If x is any object and you call
System.out.println(x);


then the println method simply calls x.toString() and prints the resulting string. The Object class defines the toString method to print the class name and the memory location of the object. For example, the call

System.out.println(System.out)


produces an output that looks like this:

java.io.PrintStream@2f6684


The reason is that the implementor of the PrintStream class didn't bother to override the toString method. The toString method is a great debugging tool. Many classes in the standard class library define the toString method so that you can get useful debugging information. Some debuggers let you invoke the toString method to display objects. And you can always insert trace messages like this:

System.out.println("Current position = " + position);


Java graphics exclamatory_icon.gif

We strongly recommend that you add a toString method to each class that you write. You, as well as other programmers who use your classes, will be grateful for the debugging support.

The program in Example 5-3 implements the equals and toString methods for the Employee and Manager classes.
Example 5-3 EqualsTest.java
 1. import java.util.*;
 2.
 3. public class EqualsTest
 4. {
 5. public static void main(String[] args)
 6. {
 7. Employee alice1 = new Employee("Alice Adams", 75000,
 8. 1987, 12, 15);
 9. Employee alice2 = alice1;
 10. Employee alice3 = new Employee("Alice Adams", 75000,
 11. 1987, 12, 15);
 12. Employee bob = new Employee("Bob Brandson", 50000,
 13. 1989, 10, 1);
 14.
 15. System.out.println("alice1 == alice2: "
 16. + (alice1 == alice2));
 17.
 18. System.out.println("alice1 == alice3: "
 19. + (alice1 == alice3));
 20.
 21. System.out.println("alice1.equals(alice3): "
 22. + alice1.equals(alice3));
 23.
 24. System.out.println("alice1.equals(bob): "
 25. + alice1.equals(bob));
 26.
 27. System.out.println("bob.toString(): " + bob);
 28.
 29. Manager carl = new Manager("Carl Cracker", 80000,
 30. 1987, 12, 15);
 31. Manager boss = new Manager("Carl Cracker", 80000,
 32. 1987, 12, 15);
 33. boss.setBonus(5000);
 34. System.out.println("boss.toString(): " + boss);
 35. System.out.println("carl.equals(boss): "
 36. + carl.equals(boss));
 37. }
 38. }
 39.
 40. class Employee
 41. {
 42. public Employee(String n, double s,
 43. int year, int month, int day)
 44. {
 45. name = n;
 46. salary = s;
 47. GregorianCalendar calendar
 48. = new GregorianCalendar(year, month - 1, day);
 49. hireDay = calendar.getTime();
 50. }
 51.
 52. public String getName()
 53. {
 54. return name;
 55. }
 56.
 57. public double getSalary()
 58. {
 59. return salary;
 60. }
 61.
 62. public Date getHireDay()
 63. {
 64. return hireDay;
 65. }
 66.
 67. public void raiseSalary(double byPercent)
 68. {
 69. double raise = salary * byPercent / 100;
 70. salary += raise;
 71. }
 72.
 73. public boolean equals(Object otherObject)
 74. {
 75. // a quick test to see if the objects are identical
 76. if (this == otherObject) return true;
 77.
 78. // must return false if the explicit parameter is null
 79. if (otherObject == null) return false;
 80.
 81. // if the classes don't match, they can't be equal
 82. if (getClass() != otherObject.getClass())
 83. return false;
 84.
 85. // now we know otherObject is a non-null Employee
 86. Employee other = (Employee)otherObject;
 87.
 88. // test whether the fields have identical values
 89. return name.equals(other.name)
 90. && salary == other.salary
 91. && hireDay.equals(other.hireDay);
 92. }
 93.
 94. public String toString()
 95. {
 96. return getClass().getName()
 97. + "[name=" + name
 98. + ",salary=" + salary
 99. + ",hireDay=" + hireDay
100. + "]";
101. }
102.
103. private String name;
104. private double salary;
105. private Date hireDay;
106. }
107.
108. class Manager extends Employee
109. {
110. public Manager(String n, double s,
111. int year, int month, int day)
112. {
113. super(n, s, year, month, day);
114. bonus = 0;
115. }
116.
117. public double getSalary()
118. {
119. double baseSalary = super.getSalary();
120. return baseSalary + bonus;
121. }
122.
123. public void setBonus(double b)
124. {
125. bonus = b;
126. }
127.
128. public boolean equals(Object otherObject)
129. {
130. if (!super.equals(otherObject)) return false;
131. Manager other = (Manager)otherObject;
132. // super.equals checked that this and other belong to the
133. // same class
134. return bonus == other.bonus;
135. }
136.
137. public String toString()
138. {
139. return super.toString()
140. + "[bonus=" + bonus
141. + "]";
142. }
143.
144. private double bonus;
145. }


Generic Programming

All values of any class type can be held in variables of type Object. In particular, String values are objects:

Object obj = "Hello"; // OK


However, numbers, characters, and boolean values are not objects.

obj = 5; // ERROR obj = false; // ERROR


You will see later in this chapter how you can turn these types into objects by using wrapper classes such as Integer and Boolean. Furthermore, all array types, no matter whether they are arrays of objects or arrays of primitive types, are class types that derive from Object.

Employee[] staff = new Employee[10];
Object arr = staff; // OK arr = new int[10]; // OK


An array of objects of class type can be converted to an array of any superclass type. For example, an Employee[] array can be passed to a method that expects an Object[] array. That conversion is useful for generic programming. Here is a simple example that illustrates the concept of generic programming. Suppose you want to find the index of an element in an array. This is a generic situation, and by writing the code for objects, you can reuse it for employees, dates, or whatever.

static int find(Object[] a, Object key)
{
 int i;
 for (i = 0; i < a.length; i++)
 if (a[i].equals(key)) return i;
 return -1; // not found
}


For example,

Employee[] staff = new Employee[10];
Employee harry;
. . .
int n = find(staff, harry);


Note that you can only convert an array of objects into an Object[] array. You cannot convert an int[] array into an Object[] array. (However, as previously pointed out, both arrays can be converted to Object.) If you convert an array of objects to an Object[] array, the generic array still remembers its original type at run time. You cannot store a foreign object into the array.

Employee[] staff = new Employee[10];
. . . // fill with Employee objects Object[] arr = staff;
arr[0] = new Date();
 // not legal, but suppose it was for (i = 0; i < n; i++) staff[i].raiseSalary(3);
 // ouch, now the date gets a raise!


Of course, this must be checked at run time. The code above compiles without error—it is legal to store a Date value in arr[0], which has type Object. But when the code executes, the array remembers its original type and monitors the type of all objects that are stored in it. If you store an incompatible type into an array, an exception is thrown.

Java graphics cplus_icon.gif

C++ programmers may be surprised that the cast from Employee[] to Object[] is legal. Even if Object was a superclass of Employee in C++, the equivalent cast from Employee** to Object** would not be legal. (Of course, the cast from Employee* to Object* is legal in C++.) There is a security reason behind this restriction. If the cast "Subclass** Screenshot Superclass**" were permitted, you could corrupt the contents of an array. Consider this code:

Employee** staff; // C++
Object** arr = staff;
 // not legal, but suppose it was arr[0] = new Date();
 // legal, Date also inherits from Object for (i = 0; i < n; i++) staff[i]->raiseSalary(3);
 // ouch, now the date gets a raise!


In Java, this problem is averted by remembering the original type of all arrays and by monitoring all array stores for type compatibility at run time.

java.lang.Object 1.0

Java graphics api_icon.gif
  • Class getClass()

    returns a class object that contains information about the object. As you will see later in this chapter, Java has a runtime representation for classes that is encapsulated in the Class class that you can often use to your advantage.

  • boolean equals(Object otherObject)

    compares two objects for equality; returns true if the objects point to the same area of memory, and false otherwise. You should override this method in your own classes.

  • Object clone()

    creates a clone of the object. The Java runtime system allocates memory for the new instance and copies the memory allocated for the current object.

  • String toString()

    returns a string that represents the value of this object. You should override this method in your own classes.

Java graphics notes_icon.gif

Cloning an object is important, but it also turns out to be a fairly subtle process filled with potential pitfalls for the unwary. We will have a lot more to say about the clone method in .

java.lang.Class 1.0

Java graphics api_icon.gif
  • String getName()

    returns the name of this class.

  • Class getSuperclass()

    returns the superclass of this class as a Class object.

Array Lists

In many coding languages—in particular in C—you have to fix the sizes of all arrays at compile time. Programmers hate this because it forces them into uncomfortable trade-offs. How many employees will be in a department? Surely no more than 100. What if there is a humongous department with 150 employees? Do we want to waste 90 entries for every department with just 10 employees? In Java, the situation is much better. You can set the size of an array at run time.

int actualSize = . . .;
Employee[] staff = new Employee[actualSize];


Of course, this code does not completely solve the problem of dynamically modifying arrays at run time. Once you set the array size, you cannot change it easily. Instead, the easiest way in Java to deal with this common situation is to use another Java class that works much like an array that will shrink and grow automatically. This class is called ArrayList. Thus, in Java, array lists are arraylike objects that can grow and shrink automatically without you needing to write any code.

Java graphics notes_icon.gif

In older versions of the Java coding language, programmers used the Vector class for automatically resizing arrays. However, the ArrayList class is more efficient, and you should generally use it instead of vectors. See of Volume 2 for more information about vectors.

There is an important difference between an array and an array list. Arrays are a feature of the Java language, and there is an array type T[] for each element type T. However, the ArrayList class is a library class, defined in the java.util package. This is a single "one size fits all" type which holds elements of type Object. In particular, you will need a cast whenever you want to take an item out of an array list. Use the add method to add new elements to an array list. For example, here is how you create an array list and populate it with employee objects:
ArrayList staff = new ArrayList();
staff.add(new Employee(. . .));
staff.add(new Employee(. . .));


The ArrayList class manages an internal array of Object references. Eventually, that array will run out of space. This is where array lists work their magic: If you call add and the internal array is full, the array list automatically creates a bigger array, and automatically copies all the objects from the smaller to the bigger array. If you already know, or have a good guess, how many elements you want to store, then call the ensureCapacity method before filling the array list:

staff.ensureCapacity(100);


That call allocates an internal array of 100 objects. Then you can keep calling add, and no costly relocation takes place. You can also pass an initial capacity to the ArrayList constructor:

ArrayList staff = new ArrayList(100);


Java graphics caution_icon.gif

Allocating an array list as

new ArrayList(100) // capacity is 100


is not the same as allocating a new array as

new Employee[100] // size is 100


There is an important distinction between the capacity of an array list and the size of an array. If you allocate an array with 100 entries, then the array has 100 slots, ready for use. An array list with a capacity of 100 elements has the potential of holding 100 elements (and, in fact, more than 100, at the cost of additional relocations), but at the beginning, even after its initial construction, an array list holds no elements at all.

The size method returns the actual number of elements in the array list. For example,
staff.size()


returns the current number of elements in the staff array list. This is the equivalent of

a.length


for an array a. Once you are reasonably sure that the array list is at its permanent size, you can call the trimToSize method. This method adjusts the size of the memory block to use exactly as much storage space as is required to hold the current number of elements. The garbage collector will reclaim any excess memory.

Java graphics notes_icon.gif

Once you trim the size of an array list, adding new elements will move the block again, which takes time. You should only use trimToSize when you are sure you won't add any more elements to the array list.

Java graphics cplus_icon.gif

The ArrayList class differs in a number of important ways from the C++ vector template. Most noticeably, since vector is a template, only elements of the correct type can be inserted, and no casting is required to retrieve elements from the vector. For example, the compiler will simply refuse to insert a Date object into a vector<Employee>. The C++ vector template overloads the [] operator for convenient element access. Since Java does not have operator overloading, it must use explicit method calls instead. C++ vectors are copied by value. If a and b are two vectors, then the assignment a = b; makes a into a new vector with the same length as b, and all elements are copied from b to a. The same assignment in Java makes both a and b refer to the same array list.

java.util.ArrayList 1.2

Java graphics api_icon.gif
  • ArrayList()

    constructs an empty array list.

  • ArrayList(int initialCapacity)

    constructs an empty array list with the specified capacity.

    Parameters:

    initialCapacity

    the initial storage capacity of the array list

  • boolean add(Object obj)

    appends an element at the end of the array list. Always returns true.

    Parameters:

    obj

    the element to be added

  • int size()

    returns the number of elements currently stored in the array list. (This is different from, and, of course, never larger than, the array list's capacity.)

  • void ensureCapacity(int capacity)

    ensures that the array list has the capacity to store the given number of elements without relocating its internal storage array.

    Parameters:

    capacity

    the desired storage capacity

  • void trimToSize()

    reduces the storage capacity of the array list to its current size.

Accessing array list elements

Unfortunately, nothing comes for free. The automatic growth convenience that array lists give requires a more complicated syntax for accessing the elements. The reason is that the ArrayList class is not a part of the Java language; it is just a utility class programmed by someone and supplied in the standard library. Instead of using the pleasant [] syntax to access or change the element of an array, you must use the get and set methods. For example, to set the ith element, you use:

staff.set(i, harry);


This is equivalent to

a[i] = harry;


for an array a. Getting an array list element is more complex because the return type of the get method is Object. You need to cast it to the desired type:

Employee e = (Employee)staff.get(i);


This is equivalent to, but much more cumbersome than,

Employee e = a[i];


Java graphics notes_icon.gif

Array lists, like arrays, are zero-based.

Java graphics exclamatory_icon.gif

You can sometimes get the best of both worlds—flexible growth and convenient element access—with the following trick. First, make an array list and add all the elements.

 ArrayList list = new ArrayList();
while (. . .)
{
 x = . . .;
 list.add(x);
}


When you are done, use the toArray method to copy the elements into an array.

X[] a = new X[list.size()];
list.toArray(a);
Java graphics caution_icon.gif

Do not call list.set(i, x) until the size of the array list is larger than i. For example, the following code is wrong:

ArrayList list = new ArrayList(100); // capacity 100, size 0
 list.set(0, x); // no element 0 yet


Use the add method instead of set to fill up an array, and use set only to replace a previously added element.

Array lists are inherently somewhat unsafe. It is possible to accidentally add an element of the wrong type to an array list.
Date birthday = . . .;
staff.set(i, birthday);


The compiler won't complain. It is perfectly willing to convert a Date to an Object, but when the accidental date is later retrieved out of the array list, it will probably be cast into an Employee. This is an invalid cast that will cause the program to abort. That is a problem! The problem arises because array lists store values of type Object. Had staff been an array of Employee references, then the compiler would not have allowed a calendar inside it.

Employee[] a = new Employee[100];
a[i] = calendar; // ERROR


On very rare occasions, array lists are useful for heterogeneous collections. Objects of completely unrelated classes are added on purpose. When an entry is retrieved, the type of every retrieved object must be tested, as in the following code:

ArrayList list;
list.add(new Employee(. . .));
list.add(new Date(. . .));
. . .
Object obj = list.get(n);
if (obj instanceof Employee)
{
 Employee e = (Employee)obj;
 . . .
}


However, this is generally considered a poor way to write code. It is not a good idea to throw away type information and laboriously try to retrieve it later.

java.util.ArrayList 1.2

Java graphics api_icon.gif
  • void set(int index, Object obj)

    puts a value in the array list at the specified index, overwriting the previous contents.

    Parameters:

    index

    the position (must be between 0 and size() - 1)

     

    obj

    the new value

  • Object get(int index)

    gets the value stored at a specified index.

    Parameters:

    index

    the index of the element to get (must be between 0 and size() - 1)

Inserting and removing elements in the middle of an array list

Instead of appending elements at the end of an array list, you can also insert them in the middle.

int n = staff.size() / 2;
staff.add(n, e);


The elements at locations n and above are shifted up to make room for the new entry. If the new size of the array list after the insertion exceeds the capacity, then the array list reallocates its storage array. Similarly, you can remove an element from the middle of an array list.

Employee e = (Employee)staff.remove(n);


The elements located above it are copied down, and the size of the array is reduced by one. Inserting and removing elements is not terribly efficient. It is probably not worth worrying about for small array lists. But if you store many elements and frequently insert and remove in the middle of the sequence, consider using a linked list instead. We will explain how to program with linked lists in Volume 2. Example 5-4 is a modification of the EmployeeTest program of . The Employee[] array is replaced by an ArrayList. Note the following changes:

  • You don't have to specify the array size.
  • You use add to add as many elements as you like.
  • You use size() instead of length to count the number of elements.
  • You use (Employee)a.get(i) instead of a[i] to access an element.
Example 5-4 ArrayListTest.java
 1. import java.util.*;
 2.
 3. public class ArrayListTest
 4. {
 5. public static void main(String[] args)
 6. {
 7. // fill the staff array list with three Employee objects
 8. ArrayList staff = new ArrayList();
 9.
10. staff.add(new Employee("Carl Cracker", 75000,
11. 1987, 12, 15));
12. staff.add(new Employee("Harry Hacker", 50000,
13. 1989, 10, 1));
14. staff.add(new Employee("Tony Tester", 40000,
15. 1990, 3, 15));
16.
17. // raise everyone's salary by 5%
18. for (int i = 0; i < staff.size(); i++)
19. {
20. Employee e = (Employee)staff.get(i);
21. e.raiseSalary(5);
22. }
23.
24. // print out information about all Employee objects
25. for (int i = 0; i < staff.size(); i++)
26. {
27. Employee e = (Employee)staff.get(i);
28. System.out.println("name=" + e.getName()
29. + ",salary=" + e.getSalary()
30. + ",hireDay=" + e.getHireDay());
31. }
32. }
33. }
34.
35. class Employee
36. {
37. public Employee(String n, double s,
38. int year, int month, int day)
39. {
40. name = n;
41. salary = s;
42. GregorianCalendar calendar
43. = new GregorianCalendar(year, month - 1, day);
44. // GregorianCalendar uses 0 for January
45. hireDay = calendar.getTime();
46. }
47.
48. public String getName()
49. {
50. return name;
51. }
52.
53. public double getSalary()
54. {
55. return salary;
56. }
57.
58. public Date getHireDay()
59. {
60. return hireDay;
61. }
62.
63. public void raiseSalary(double byPercent)
64. {
65. double raise = salary * byPercent / 100;
66. salary += raise;
67. }
68.
69. private String name;
70. private double salary;
71. private Date hireDay;
72. }


java.util.ArrayList 1.2

Java graphics api_icon.gif
  • void add(int index, Object obj)

    shifts up elements to insert an element.

    Parameters:

    index

    the insertion position (must be between 0 and size())

     

    obj

    the new element

  • void remove(int index)

    removes an element and shifts down all elements above it.

    Parameters:

    index

    the position of the element to be removed (must be between 0 and size() - 1)

Object Wrappers

Occasionally, you need to convert a basic type like int to an object. All basic types have class counterparts. For example, there is a class Integer corresponding to the basic type int. These kinds of classes are usually called object wrappers. The wrapper classes have obvious names: Integer, Long, Float, Double, Short, Byte, Character, Void, and Boolean. (The first six inherit from the common wrapper Number.) The wrapper classes are final. (So you can't override the toString method in Integer to display numbers using Roman numerals, sorry.) You also cannot change the values you store in the object wrapper. Suppose we want an array list of floating-point numbers. As mentioned previously, simply adding numbers won't work.

ArrayList list = new ArrayList();
list.add(3.14); // ERROR


The floating-point number 3.14 is not an Object. Here, the Double wrapper class comes in. An instance of Double is an object that wraps the double type.

list.add(new Double(3.14));


Of course, to retrieve a number from an array list of Double objects, we need to extract the actual value from the wrapper by using the doubleValue() method in Double.

double x = ((Double)list.get(n)).doubleValue();


Ugh. Here it really pays off to define a class we will call DoubleArrayList that hides all this ugliness once and for all.

class DoubleArrayList
{
 public DoubleArrayList()
 {
 list = new ArrayList();
 }
 public void set(int n, double x)
 {
 list.set(n, new Double(x));
 }
 public void add(double x)
 {
 list.add(new Double(x));
 }
 public double get(int n)
 {
 return ((Double)list.get(n)).doubleValue();
 }
 public int size()
 {
 return list.size();
 }
 private ArrayList list;
}


Java graphics caution_icon.gif

Some people think that the wrapper classes can be used to implement methods that can modify numeric parameters. However, that is not correct. Recall from that it is impossible to write a Java method that increments an integer because parameters to Java methods are always passed by value.

public static void increment(int x) // won't work
{
 x++; // increments local copy
}
public static void main(String[] args)
{
 int a = 3;
 increment(a);
 . . .
}


Changing x has no effect on a. Could we overcome this by using an Integer instead of an int?

public static void increment(Integer x) // won't work
{
 . . .
}
public static void main(String[] args)
{
 Integer a = new Integer(3);
 increment(a);
 . . .
}


After all, now a and x are references to the same object. If we managed to update x, then a would also be updated. The problem is that Integer objects are immutable: the information contained inside the wrapper can't change. In particular, there is no analog to the statement x++ for Integer objects. Thus, you cannot use these wrapper classes to create a method that modifies numeric parameters.

Java graphics notes_icon.gif

If you do want to write a method to change numeric parameters, you can use one of the holder types defined in the org.omg.CORBA package. There are types IntHolder, BooleanHolder, and so on. Each holder type has a public (!) field value through which you can access the stored value.

public static void increment(IntegerHolder x)
{
 x.value++;
}
public static void main(String[] args)
{
 IntegerHolder a = new IntegerHolder(3);
 increment(a);
 int result = a.value;
 . . .
}
You will often see the number wrappers for another reason. The designers of Java found the wrappers a convenient place to put certain basic methods, like the ones for converting strings of digits to numbers. To convert a string to an integer, you need to use the following statement:
int x = Integer.parseInt(s);


This has nothing to do with Integer objects—parseInt is a static method. But the Integer class was a good place to put it. Similarly, you can use the Double.parseDouble method to parse floating-point numbers.

Java graphics notes_icon.gif

Until Java 2, there was no parseDouble method in the Double class. Instead, programmers had to use the cumbersome

double x = new Double(s).doubleValue();


or

double x = Double.valueOf(s).doubleValue();


This statement constructs a Double object from the string s and then extracts its value. The static Double.parseDouble method is more efficient because no object is created.

Java graphics notes_icon.gif

There is another method for parsing numbers, although it isn't any simpler. You can use the parse method of the NumberFormat class. When s is a string and formatter is an object of type NumberFormat, then the method call formatter.parse(s) returns an object of type Number.

NumberFormat formatter = NumberFormat.getNumberInstance();
Number n = formatter.parse(s);


Actually, Number is an abstract class, and the returned object is an object of either type Long or Double, depending on the contents of the string s. You can use the instanceof operator to find out the return type of the object:

if (n instanceof Double) Double d = (Double)n;


But in practice, you don't usually care about the return type. The doubleValue method is defined for the Number class, and it returns the floating-point equivalent of the number object, whether it is a Long or a Double. That is, you can use the following code:

x = formatter.parse(s.trim()).doubleValue();


Using the NumberFormat has one advantage: the string can contain group separators for thousands such as "12,301.4".

The API notes show some of the more important methods of the Integer class. The other number classes implement corresponding methods.

java.lang.Integer 1.0

Java graphics api_icon.gif
  • int intValue()

    returns the value of this Integer object as an int (overrides the intValue method in the Number class).

  • static String toString(int i)

    returns a new String object representing the specified integer in base 10.

  • static String toString(int i, int radix)

    lets you return a representation of the number i in the base specified by the radix parameter.

  • static int parseInt(String s)

    returns the integer's value, assuming the specified String represents an integer in base 10.

  • static int parseInt(String s, int radix)

    returns the integer's value, assuming the specified String represents an integer in the base specified by the radix parameter.

  • static Integer valueOf(String s)

    returns a new Integer object initialized to the integer's value, assuming the specified String represents an integer in base 10.

  • static Integer valueOf(String s, int radix)

    returns a new Integer object initialized to the integer's value, assuming the specified String represents an integer in the base specified by the radix parameter.

java.text.NumberFormat 1.1

Java graphics api_icon.gif
  • Number parse(String s)

    returns the numeric value, assuming the specified String represents a number.


Java ScreenShot
Screenshot      
Top
 

Comments