Java provides language support for defining relationships among
objects, known as inheritance.
Two types of inheritance mechanisms in Java: subtyping (interface inheritance) and subclassing (implementation inheritance) Subtyping allows specifying a relationship between otherwise unrelated classes by specifying a common set of methods in an interface. An interface is a list of instance methods. Interface inheritance allows writing client programs that can manipulate objects of any type that implements the interface. Subclassing allows changing behavior and adding functionality without rewriting an entire class by defining a new class that inherits instance methods and variables from another class. Subclassing is widely used by systems programmers to build extensible libraries. The use of subclassing is controversial among systems and applications programmers and is generally works against encapsulation. Every class is a subtype of Java's Object class, which enables the "convention" that every class includes an implementation of getClass(), toString(), equals(), hashCode() and several other methods. Subclassing is avoided in this book because it generally works against encapsulation. Certain vestiges of the approach are built in to Java and therefore unavoidable: specifically, every class is a subtype of Java's Object class. This structure enables the "convention" that every class includes an implementation of getClass(), toString(), equals(), hashCode(), and several other methods that are not used in this book. The default Object implementation of toString(), equals(), and hashCode() generally does not lead to the desired behavior and is usually overridden in new classes.
Interface inheritance is used in this book for comparison and
iteration concepts. Subclassing is widely used in the development of graphical user interfaces, so that the large amount of code required to provide all the facilities that users expect can be reused. Subclassing allows adding methods to a library built by another programmer, effectively reusing the code in a potentially huge library. The advantages of subclassing over interface inheritance are debatable, and it is generally avoided in this book as it works against encapsulation. Despite this, certain vestiges of subclassing are built into Java and are unavoidable, such as every class being a subtype of Java's Object class. Every Java type inherits the toString() method from Object, allowing any client to invoke it for any object. The toString() method is the basis for Java's automatic conversion of one operand of the concatenation operator + to a String whenever the other operand is a String. If an object's data type does not include an implementation of toString(), the default implementation in Object is invoked, which typically returns a string representation of the memory address of the object. It is generally recommended to include implementations of toString() that override the default in every class that is developed. Java supplies built-in reference types known as wrapper types, one for each of the primitive types, such as Integer, Double, and String. Java automatically converts from primitive types to wrapper types when necessary, such as when an int value is concatenated with a String. Equality for objects can be tested using (a == b) where a and b are reference variables of the same type, but clients typically want to test whether the data-type values (object state) are the same or implement some type-specific rule. Java provides built-in implementations for standard types such as Integer, Double, and String, and for more complicated types such as File and URL. When defining own data types, such as Date or Transaction, it is necessary to override the equals() method. Java's convention for the equals() method is that it must be an equivalence relation: reflexive, symmetric, transitive, consistent, and not null. Ensuring that these properties hold, adhering to Java conventions, and avoiding unnecessary work in an implementation can be tricky, as illustrated for the Date class. A step-by-step approach is recommended, such as first checking if the reference to the object is the same as the reference to the argument object and returning true if so, and then checking if the argument is null and returning false if it is. Another step is to check if the objects are not from the same class and returning false if they are not. The argument should be cast from Object to the specific class being compared (in this case, Date) to ensure that the comparison is accurate. The final step is to return false if any of the instance variables do not match. For other classes, a different definition of equality may be appropriate. It is important to adhere to Java conventions and ensure that the implementation is efficient and accurate.