Using the Object and Class Classes
In this chapter, you find out how to use two classes of the Java API that are important to object-oriented programming:
- The Object class, which every other class inherits-including all the classes in the Java API and any classes you create yourself
- The Class class, which is used to get information about an object's type
TECHNICAL STAUFF |
If I could, I'd plant a huge Technical Stuff icon on this entire chapter. All this stuff is a bit on the technical side, and many Java programmers get by for years without understanding or using it. Still, I recommend you read this chapter carefully. Even if it all doesn't sink in, it may help explain why some things in Java don't work quite the way you think they should, and the information in this chapter may someday help you program your way out of a difficult corner. |
The Mother of All Classes Object
Object is the mother of all classes. In Java, every class ultimately inherits the Object class. This class provides a set of methods that are available to every Java object.
Every object is an Object
Any class that doesn't have an extends clause implicitly inherits Object. Thus you never have to code a class like this:
public class Product extends Object...
If a subclass has an extends clause that specifies a superclass other than Object, the class still inherits Object. That's because the inheritance hierarchy eventually gets to a superclass that doesn't have an extends clause, and that superclass inherits Object and passes it down to all its subclasses.
For example, suppose you have these classes:
public class Manager extends SalariedEmployee... public class SalariedEmployee extends Employee... public class Employee extends Person... public class Person...
Here the Manager class inherits the Object class indirectly because it inherits SalariedEmployee, which inherits Employee, which inherits Person, which inherits Object.
REMEMBER |
In Java, creating a class that doesn't inherit Object is not possible. |
Using Object as a type
If you don't know or care about the type of an object referenced by a variable, you can specify its type as Object. For example, the following is perfectly legal:
Object emp = new Employee();
However, you can't do anything useful with the emp variable, because the compiler doesn't know it's an Employee. For example, if the Employee class has a method named setLastName, the following code doesn't work:
Object emp = new Employee(); emp.setLastName("Smith"); // error: won't compile
Because emp is an Object, not an Employee, the compiler doesn't know about the setLastName method.
Note that you could still cast the object to an Employee:
Object emp = new Employee(); ((Employee)emp).setLastName("Smith"); // this works
But what's the point? You may as well make emp an Employee in the first place.
Declaring a variable, parameter, or return type as Object, however, in certain situations does make perfect sense. For example, the Java API provides a set of classes designed to maintain collections of objects. One of the most commonly used of these classes is the ArrayList class. It has a method named add that accepts an Object as a parameter. This method adds the specified object to the collection. Because the parameter type is Object, you can use the ArrayList class to create collections of any type of object. (For more information about the ArrayList class and other collection classes, see Book IV.)
Methods of the Object class
Table 6-1 lists all the methods of the Object class. Ordinarily, I wouldn't list all the methods of a class-I'd just list the ones that I think are most useful. However, because Object is such an important player in the game of object-oriented programming, I thought showing you all its capabilities is best, even though some of them are a bit obscure.
Method |
What It Does |
---|---|
protected Object clone() |
Returns a copy of this object. |
boolean equals(Object obj) |
Indicates whether this object is equal to the obj object. |
protected void finalize() |
Called by the garbage collector when the object is destroyed. |
Class getClass() |
Returns a Class object that represents this object's runtime class. |
int hashCode() |
Returns this object's hash code. |
void notify() |
Used with threaded applications to wake up a thread that's waiting on this object. |
void notifyAll() |
Used with threaded applications to wake up all threads that are waiting on this object. |
String toString() |
Returns a String representation of this object. |
void wait() |
Causes this object's thread to wait until another thread calls notify or notifyAll. |
void wait(Long timeout) |
A variation of the basic wait method. |
void wait (Long timeout, int nanos) |
Yet another variation of the wait method. |
TECHNICAL STAUFF |
I warned you-this entire chapter should have a Technical Stuff icon. |
Note |
Almost half of these methods (notify, notifyAll, and the three wait methods) are related to threading. You find complete information about those five methods in Book V, Chapter 1. Here's the rundown on the remaining six methods:
|
Primitives aren t objects
I need to note that primitive types, such as int and double, are not objects. As a result, they do not inherit the Object class and don't have access to the methods listed in the previous section.
As a result, the following code won't work:
int x = 50; String s = x.toString(); // error: won't compile
If you really want to convert an int to a string in this way, you can use a wrapper class such as Integer to create an object from the value, and then call its toString method:
String s = new Integer(x).toString(); // OK
Each of the wrapper classes also defines a static toString method you can use like this:
String s = Integer.toString(x);
Tip |
Sometimes using the compiler shortcut that lets you use primitive types in string concatenation expressions is easier: String s = "" + x; |
Here the int variable x is concatenated with an empty string.
REMEMBER |
The point of all this is that primitive types aren't objects, so they don't inherit anything from Object. If you want to treat a primitive value as an object, you can use the primitive type's wrapper class (as I describe in Book II, Chapter 2). |
The toString Method
The toString method returns a String representation of an object. By default, the toString method returns the name of the object's class plus its hash code. In the sections that follow, I show you how to use the toString method and how to override it in your own classes to create more useful strings.
Using toString
Here's a simple program that puts the toString method to work:
public class TestToString { public static void main(String[] args) { Employee emp = new Employee("Martinez", "Anthony"); System.out.println(emp.toString()); } } class Employee { private String lastName; private String firstName; public Employee(String lastName, String firstName) { this.lastName = lastName; this.firstName = firstName; } }
Here this code creates a new Employee object, and then the result of its toString method is printed to the console. When you run this program, the following line is printed on the console:
Employee@82ba41
Note |
The hash code-in this case, 82ba41-may be different on your system. |
It turns out that the explicit call to toString isn't really necessary in this example. I could have just as easily written the second line of the main method like this:
System.out.println(emp);
That's because the println method automatically calls the toString method of any object you pass it.
Overriding toString
The default implementation of toString isn't very useful in most situations. For example, you don't really learn much about an Employee object by seeing its hash code. Wouldn't it be better if the toString method returned some actual data from the object, such as the employee's name?
To do that, you must override the toString method in your classes. In fact, one of the basic guidelines of object-oriented programming in Java is to always override toString. Here's a simple program with an Employee class that overrides toString:
public class TestToString { public static void main(String[] args) { Employee emp = new Employee("Martinez", "Anthony"); System.out.println(emp.toString()); } } class Employee { private String lastName; private String firstName; public Employee(String lastName, String firstName) { this.lastName = lastName; this.firstName = firstName; } public String toString() { return "Employee[" + this.firstName + " " + this.lastName + "]"; } }
When you run this program, the following line is displayed on the console:
Employee[Anthony Martinez]
Note that the output consists of the class name followed by some data from the object in brackets. This convention is common in Java programming.
Tip |
The only problem with the preceding example is that the class name is hard-coded into the toString method. You can use the getClass method to retrieve the actual class name at runtime: public String toString() { return this.getClass().getName() + "[" + this.firstName + " " + this.lastName + "]"; } |
Here the getClass method returns a Class object that represents the class of the current object. Then the Class object's getName method is used to get the actual class name. (You discover more about the Class object later in this chapter.)
The equals Method
Testing objects to see if they are equal is one of the basic tasks of any object-oriented programming language. Unfortunately, Java isn't very good at it. For example, consider this program:
public class TestEquality1 { public static void main(String[] args) { Employee emp1 = new Employee( "Martinez", "Anthony"); Employee emp2 = new Employee( "Martinez", "Anthony"); if (emp1 == emp2) System.out.println( "These employees are the same."); else System.out.println( "These are different employees."); } } class Employee { private String lastName; private String firstName; public Employee(String lastName, String firstName) { this.lastName = lastName; this.firstName = firstName; } }
Here the main method creates two Employee objects with identical data, and then compares them. Alas, the comparison returns false. Even though the Employee objects have identical data, they're not considered to be equal. That's because the equality operator (==) compares the object references, not the data contained by the objects. Thus the comparison returns true only if both emp1 and emp2 refer to the same instance of the Employee class.
If you want to create objects that are considered to be equal if they contain identical data, you have to do two things:
- Compare them with the equals method rather than the equality operator.
- Override the equals method in your class to compare objects based on their data.
The following sections describe both of these steps.
Using equals
To test objects using the equals method rather than the equality operator, you simply rewrite the comparison expression like this:
if (emp1.equals(emp2)) System.out.println("These employees are the same."); else System.out.println ("These are different employees.");
Here the equals method of emp1 is used to compare emp1 with emp2.
By default, the equals operator returns the same result as the equality operator. So just replacing == with the equals method doesn't have any effect unless you also override the equals method, as explained in the next section.
Tip |
Which object's equals method you use shouldn't matter. Thus, this if statement returns the same result: if (emp2.equals(emp1)) System.out.println("These employees are the same."); else System.out.println ("These are different employees."); |
TECHNICAL STAUFF |
Note that I said it shouldn't matter. Whenever you override the equals method, you're supposed to make sure that comparisons work in both directions. However, sloppy programming sometimes results in equals methods where a equals b but b doesn't equal a. So be on your toes. |
Overriding the equals method
You can override the equals method so that objects can be compared based on their values. At the surface, you might think this is easy to do. For example, you might be tempted to write the equals method for the Employee class like this:
// warning -- there are several errors in this code! public boolean equals(Employee emp) { if (this.getLastName().equals(emp.getLastName()) && this.getFirstName().equals(emp.getFirstName())) return true; else return false; }
The basic problem with this code-and the challenge of coding a good equals method-is that the parameter passed to the equals method must be an Object, not an Employee. That means that the equals method must be prepared to deal with anything that comes its way. For example, someone might try to compare an Employee object with a Banana object. Or with a null. The equals method must be prepared to deal with all possibilities.
Specifically, the Java API documentation says that whenever you override the equals method, you must ensure that the equals method meets five specific conditions. Here they are, quoted right out of the API documentation:
- It is reflexive. For any non-null reference value x, x.equals(x) should return true.
- It is symmetric. For any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- It is transitive. For any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- It is consistent. For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
- For any non-null reference value x, x.equals(null) should return false.
Sound confusing? Fortunately, it's not as complicated as it seems at first. You can safely ignore the transitive rule, because if you get the other rules right, this one happens automatically. And the consistency rule basically means that you return consistent results. As long as you don't throw a call to Math.random into the comparison, that shouldn't be a problem.
Here's a general formula for creating a good equals method (assume the parameter is named obj):
- Test the reflexive rule.
Use a statement like this:
if (this == obj) return true;
In other words, if someone is silly enough to see whether an object is equal to itself, it returns true.
- Test the non-null rule.
Use a statement like this:
if (this == null) return false;
Null isn't equal to anything.
- Test that obj is of the same type as this.
You can use the getClass method to do that, like this:
if (this.getClass() != obj.getClass()) return false;
The two objects can't possibly be the same if they aren't of the same type. (It may not be apparent at first, but this test is required to fulfill the symmetry rule-that if x equals y, then y must also equal x.)
- Cast obj to a variable of your class. Then compare the fields you want to base the return value on and return the result.
Here's an example:
Employee emp = (Employee) obj; return this.lastName.equals(emp.getLastName()) && this.firstname.equals(emp.getFirstName());
Notice that the field comparisons for the String values use the equals method rather than ==. This is because you can't trust == to compare strings. If you need to compare primitive types, you can use ==. But you should use equals to compare strings and any other reference types.
Putting it all together, Listing 6-1 shows a program that compares two Employee objects using a properly constructed equals method.
Listing 6-1: Comparing Objects
public class TestEquality2 { public static void main(String[] args) { Employee emp1 = new Employee( → 5 "Martinez", "Anthony"); Employee emp2 = new Employee( → 7 "Martinez", "Anthony"); if (emp1.equals(emp2)) → 9 System.out.println( "These employees are the same."); else System.out.println( "These are different employees."); } } class Employee → 18 { private String lastName; private String firstName; public Employee(String lastName, String firstName) { this.lastName = lastName; this.firstName = firstName; } public String getLastName() { return this.lastName; } public String getFirstName() { return this.firstName; } public boolean equals(Object obj) → 39 { // an object must equal itself if (this == obj) → 42 return true; // no object equals null if (this == null) → 46 return false; // objects of different types are never equal if (this.getClass() != obj.getClass()) → 50 return false; // cast to an Employee, then compare the fields Employee emp = (Employee) obj; → 54 return this.lastName.equals(emp.getLastName()) → 55 && this.firstName.equals(emp.getFirstName()); } }
Following are some noteworthy points in this listing:
→ 5 |
Creates an Employee object with the name Anthony Martinez. |
→ 7 |
Creates another Employee object with the name Anthony Martinez. |
→ 9 |
Compares the two Employee objects using the equals method. |
→ 18 |
The Employee class. |
→ 39 |
The overridden equals method. |
→ 42 |
Returns true if the same object instances are being compared. This meets the first equality test, that an object must always be equal to itself. |
→ 46 |
Returns false if the object being compared is null. This meets the last equality test, that nothing is equal to null. |
→ 50 |
Returns false if the object being compared isn't of the correct type. This helps ensure the symmetry test, that if x equals y, then y must equal x. |
→ 54 |
Having slid through the other tests, you can now assume that you're comparing two different Employee objects. So the next step is to cast the other object to an Employee. |
→ 55 |
Having cast the other object to an Employee, the two fields (lastName and firstName) are compared and the result of the compound comparison is returned. |
The clone Method
Cloning refers to the process of making an exact duplicate of an object. Unfortunately, this process turns out to be a pretty difficult task in an object-oriented language such as Java. You'd think cloning would be as easy as this:
Employee emp1 = new Employee("Stewart", "Martha"); Employee emp2 = emp1;
However, this code doesn't make a copy of the Employee object at all. Instead, you now have two variables that refer to the same object, which usually isn't what you want. For example, suppose you execute these statements:
emp1.setLastName("Washington"); emp2.setLastName("Graham"); String lastName = emp1.getLastName();
After these statements execute, does lastName return Washington or Graham? The correct answer is Graham, because both emp1 and emp2 refer to the same Employee object.
In contrast, a clone is an altogether new object that has the same values as the original object. Often you can create a clone manually by using code like this:
Employee emp1 = new Employee("Stewart", "Martha"); Employee emp2 = new Employee(); emp2.setLastName(emp1.getLastName()); emp2.setFirstName(emp1.getFirstName()); emp2.setSalary(emp1.getSalary());
Here a new Employee object is created and its fields are set to the same values as the original object.
Tip |
Java provides a more elegant way to create object copies-the clone method, which is available to all classes because it's inherited from the Object class. However, as you discover in the following sections, the clone method can be difficult to create and use. For this reason, you want to implement it only for those classes that you think can really benefit from cloning. |
Implementing the clone method
The clone method is defined by the Object class, so it's available to all Java classes. However, clone is declared with protected access in the Object class. As a result, the clone method for a given class is available only within that class. If you want other objects to be able to clone your object, you must override the clone method and give it public access.
TECHNICAL STAUFF |
Note that the clone method defined by the Object class returns an Object type. That makes perfect sense, because the Object class doesn't know the type of the class that you'll be overriding the clone method in. An inconvenient side effect of returning an Object is that whenever you call the clone method for a class that overrides clone, you must cast the result to the desired object type. |
Listing 6-2 gives a simple example of a program that clones Employee objects. In a nutshell, this program overrides the clone method for the Employee class: It creates an Employee object, clones it, changes the name of the original Employee object, and prints out both objects to the console.
Listing 6-2: A Cloning Example
public class CloneTest { public static void main(String[] args) { Employee emp1 = new Employee( → 5 "Martinez", "Anthony"); emp1.setSalary(40000.0); → 7 Employee emp2 = (Employee)emp1.clone(); → 8 emp1.setLastName("Smith"); → 9 System.out.println(emp1); → 10 System.out.println(emp2); → 11 } } class Employee → 15 { private String lastName; private String firstName; private Double salary; public Employee(String lastName, String firstName) { this.lastName = lastName; this.firstName = firstName; } public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public Double getSalary() { return this.salary; } public void setSalary(Double salary) { this.salary = salary; } public Object clone() → 57 { Employee emp; emp = new Employee( → 60 this.lastName, this.firstName); emp.setSalary(this.salary); → 62 return emp; → 63 } public String toString() { return this.getClass().getName() + "[" + this.firstName + " " + this.lastName + ", " + this.salary + "]"; } }
When you run this program, the following lines appear on the console:
Employee[Anthony Smith, 40000.0] Employee[Anthony Martinez, 40000.0]
As you can see, the name of the second Employee object was successfully changed without affecting the name of the first Employee object.
The following paragraphs draw your attention to some of the highlights of this program:
→ 5 |
Creates the first Employee object for an employee named Anthony Martinez. |
→ 7 |
Sets Mr. Martinez's salary. |
→ 8 |
Creates a clone of the Employee object for Mr. Martinez. Notice that the return value must be cast to an Employee. That's because the return value of the clone method is Object. |
→ 9 |
Changes the last name for the second Employee object. |
→ 10 |
Prints the first Employee object. |
→ 11 |
Prints the second Employee object. |
→ 15 |
The Employee class. This class defines private fields to store the last name, first name, and salary, as well as getter and setter methods for each field. |
→ 57 |
This method overrides the clone method. Notice that its return type is Object, not Employee. |
→ 60 |
Creates a new Employee object using the last name and first name from the current object. |
→ 62 |
Sets the new employee's salary to the current object's salary. |
→ 63 |
Returns the cloned Employee object. |
Using clone to create a shallow copy
In the previous example, the clone method manually creates a copy of the original object and returns it. In many cases, this is the easiest way to create a clone. However, what if your class has a hundred or more fields that need to be duplicated? Then the chance of accidentally forgetting to copy one of the fields is high. And, if you later add a field to the class, you may forget to modify the clone method to include the new field.
Fortunately, you can solve this problem by using the clone method of the Object class directly in your own clone method. The clone method of the Object class can automatically create a copy of your object that contains duplicates of all the fields that are primitive types (such as int and double) as well as copies of immutable reference types-most notably, strings. So, if all the fields in your class are either primitives or strings, you can use the clone method provided by the Object class to clone your class.
TECHNICAL STAUFF |
This type of clone is known as a shallow copy for reasons I explain in the next section. |
To call the clone method from your own clone method, just specify super.clone(). Before you can do that, however, you must do two things:
- Declare that the class supports the Cloneable interface. The Cloneable interface is a tagging interface that doesn't provide any methods. It simply marks a class as being appropriate for cloning.
- Enclose the call to super.clone() in a try/catch statement that catches the exception CloneNotSupportedException. This exception is thrown if you try to call clone on a class that doesn't implement the Cloneable interface. Provided you implement Cloneable, this exception won't ever happen. But because CloneNotSupportedException is a checked exception, you must catch it.
Here's an example of an Employee class with a clone method that uses super.clone() to clone itself:
class Employee implements Cloneable { // Fields and methods omitted... public Object clone() { Employee emp; try { emp = (Employee) super.clone(); } catch (CloneNotSupportedException e) { return null; // will never happen } return emp; } }
Notice that this method doesn't have to be aware of any of the fields declared in the Employee class. However, this clone method works only for classes whose fields are all either primitive types or immutable objects such as strings.
Creating deep copies
TECHNICAL STAUFF |
It's not uncommon for some fields in a class actually to be other objects. For example, the Employee class might have a field of type Address that's used to store each employee's address: class Employee { public Address address; // other fields and methods omitted } |
If that's the case, the super.clone() method won't make a complete copy of the object. The clone won't get a clone of the address field. Instead, it has a reference to the same address object as the original.
To solve this problem, you must do a deep copy of the Employee object. A deep copy is a clone in which any subobjects within the main object are also cloned. To accomplish this feat, the clone method override first calls super.clone() to create a shallow copy of the object. Then it calls the clone method of each of the subobjects contained by the main object to create clones of those objects. (Of course, for a deep copy to work, those objects must also support the clone methods.)
Listing 6-3 shows an example. Here an Employee class contains a public field named address, which holds an instance of the Address class. As you can see, the clone method of the Employee class creates a shallow copy of the Employee object, and then sets the copy's address field to a clone of the original object's address field. To make this example work, the Address class also overrides the clone method. Its clone method calls super.clone() to create a shallow copy of the Address object.
Listing 6-3: Creating a Deep Copy
public class CloneTest2 { public static void main(String[] args) { Employee emp1 = new Employee( → 5 "Martinez", "Anthony"); emp1.setSalary(40000.0); emp1.address = new Address( → 8 "1300 N. First Street", "Fresno", "CA", "93702"); Employee emp2 = (Employee)emp1.clone(); → 11 System.out.println( → 13 "**** after cloning **** "); printEmployee(emp1); printEmployee(emp2); emp2.setLastName("Smith"); → 18 emp2.address = new Address( → 19 "2503 N. 6th Street", "Fresno", "CA", "93722"); System.out.println( → 23 "**** after changing emp2 **** "); printEmployee(emp1); printEmployee(emp2); } private static void printEmployee(Employee e) → 30 { System.out.println(e.getFirstName() + " " + e.getLastName()); System.out.println(e.address.getAddress()); System.out.println("Salary: " + e.getSalary()); System.out.println(); } } class Employee implements Cloneable → 40 { private String lastName; private String firstName; private Double salary; public Address address; → 46 public Employee(String lastName, String firstName) { this.lastName = lastName; this.firstName = firstName; this.address = new Address(); } public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public Double getSalary() { return this.salary; } public void setSalary(Double salary) { this.salary = salary; } public Object clone() → 85 { Employee emp; try { emp = (Employee) super.clone(); → 90 emp.address = (Address)address.clone(); → 91 } catch (CloneNotSupportedException e) → 93 { return null; // will never happen } return emp; → 97 } public String toString() { return this.getClass().getName() + "[" + this.firstName + " " + this.lastName + ", " + this.salary + "]"; } } class Address implements Cloneable → 109 { private String street; private String city; private String state; private String zipCode; public Address() { this.street = ""; this.city = ""; this.state = ""; this.zipCode = ""; } public Address(String street, String city, String state, String zipCode) { this.street = street; this.city = city; this.state = state; this.zipCode = zipCode; } public Object clone() → 133 { try { return super.clone(); →137 } catch (CloneNotSupportedException e) { return null; // will never happen } } public String getAddress() { return this.street + " " + this.city + ", " + this.state + " " + this.zipCode; } }
The main method in the CloneTest2 class creates an Employee object and sets its name, salary, and address. Then it creates a clone of this object and prints the data contained in both objects. Next it changes the last name and address of the second employee and prints the data again. Here's the output that's produced when this program is run:
**** after cloning **** Anthony Martinez 1300 N. First Street Fresno, CA 93702 Salary: 40000.0 Anthony Martinez 1300 N. First Street Fresno, CA 93702 Salary: 40000.0 **** after changing emp2 **** Anthony Martinez 1300 N. First Street Fresno, CA 93702 Salary: 40000.0 Anthony Smith 2503 N. 6th Street Fresno, CA 93722 Salary: 40000.0
As you can see, the two Employee objects have identical data after they are cloned. But they have different data after the fields for the second employee have been changed. Thus you can safely change the data in one of the objects without affecting the other object.
The following paragraphs describe some of the highlights of this program:
→ 5 |
Creates an employee named Anthony Martinez. |
→ 8 |
Sets the employee's address. |
→ 11 |
Clones the employee. (Okay, just the object. Not the coworker.) |
→ 13 |
Prints the two Employee objects after cloning. They should have identical data. |
→ 18 |
Changes the second employee's name. |
→ 19 |
Changes the second employee's address. |
→ 23 |
Prints the two Employee objects after changing the data for the second employee. The objects should now have different data. |
→ 30 |
A utility method that prints the data for an Employee object. |
→ 40 |
The Employee class. Notice that this class implements Cloneable. |
→ 46 |
The address field, which holds an object of type Address. |
→ 85 |
The clone method in the Employee class. |
→ 90 |
Creates a shallow copy of the Employee object. |
→ 91 |
Creates a shallow copy of the Address object and assigns it to the address field of the cloned Employee object. |
→ 93 |
Catches CloneNotSupportedException, which won't ever happen because the class implements Cloneable. The compiler requires the try/catch statement here because CloneNotSupportedException is a checked exception. |
→ 97 |
Returns the cloned Employee object. |
→ 109 |
The Address class, which also implements Cloneable. |
→ 133 |
The clone method of the Address class. |
→ 137 |
Returns a shallow copy of the Address object. |
The Class Class
Okay class, it's time for one last class before finishing this chapter: the Class class. This might get confusing, so put your thinking cap on. Every class used by a Java application is represented in memory by an object of type Class. For example, if your program uses Employee objects, there's also a Class object for the Employee class. This Class object has information not about specific employees, but about the Employee class itself.
You've already seen how you can get a Class object by using the getClass method. This method is defined by the Object class, so it's available to every object. Here's an example:
Employee emp = new Employee(); Class c = emp.getClass();
Warning |
Note that you have to initialize a variable with an object instance before you can call its getClass method. That's because the getClass method returns a Class object that corresponds to the type of object the variable refers to, not the type the variable is declared as. |
For example, suppose an HourlyEmployee class extends the Employee class. Then consider these statements:
HourlyEmployee emp = new Employee(); Class c = emp.getClass();
Here c refers to a Class object for the HourlyEmployee class, not the Employee class.
TECHNICAL STAUFF |
The Class class has more than 50 methods, but only two of them are worthy of your attention:
|
If you're interested in the other capabilities of the Class class, you can always check it out in the Java API documentation.
Tip |
One of the most common uses of the getClass method is to tell if two objects are of the same type by comparing their Class objects. This works because Java guarantees that the Class object has only one instance for each different class used by the application. So, even if your application instantiates 1,000 Employee objects, there is only one Class object for the Employee class. |
As a result, the following code can determine if two objects are both objects of the same type:
Object o1 = new Employee(); Object o2 = new Employee(); if (o1.getClass() == o2.getClass()) System.out.println("They're the same."); else System.out.println("They are not the same.");
In this case, the type of both objects is Employee, so the comparison is true.
To find out whether an object is of a particular type, use the object's getClass method to get the corresponding Class object. Then use the getName method to get the class name, and use a string comparison to check the class name. Here's an example:
if (emp.getClass().getName().equals("Employee")) System.out.println("This is an employee object.");
If all the strung-out method calls give you a headache, you can break it apart:
Class c = emp.getClass(); String s = c.getName(); if (s.equals("Employee")) System.out.println("This is an employee object.");
The result is the same.