Every class that you create in Java is a subclass, its ultimate parent is the Object class that every class inherits even if you dont declare it. To inherit a class we use the extends keyword the extended class is known as the parent or base class or superclass, the object performing the extending is known as the child (derived) or subclass
There are three types of inheritance:A subclass is said to be of type superclass and an instance of the subclass can be used anywhere an instance of the superclass can be used basically subclass 'IS A' of the superclass
There are a few rules regarding IS-A and HAS-A relationships
IS-A (inheritance) |
In OO, the concept of IS-A is based on inheritance, IS-A is a way of saying "this thing is a type of that thing", for example a dog is a type of animal so a dog IS-A animal, bentley IS-A car, apple IS-A fruit, etc This is expressed in Java through the keyword extends So we can say that "Ka IS-A car" and "Ka IS-A Vehicle" because a class is said to be "a type of" anything further up in its inheritance tree. When using the extends keyword a class inherits all accessible member variables and methods public class TestAnimal { h.getEat(); // getEat() method inherited from Animal class Animal { class Horse extends Animal { |
HAS-A (usage) |
HAS-A relationships are based on usage, rather than inheritance, in other words class A HAS-A B if code in class A has a reference to an instance of class B for example "city HAS-A road". The below Horse class has an instance variable of type Halter, so you can say that "Horse HAS-A Halter" (Horse has a reference to Halter). This means that you can invoke any of the methods in Halter, this is good OO practice as we do not need to duplicate the methods in Halter and this means that any other class can use Halter methods thus reducing code. public class Animal { ... } public void tie(LeadRope rope) { Users of the Horse class think that the Horse class has Halter behavior, the Horse class may have a method called tieRope but this method could delegate this to the Halter tieRope method (as seen above), users of Horse class should never know that there is a Halter, only that is capable of things you ask it to do. |
There are visual object modelling languages such has the Unified Modeling Language (UML) which allow designers to design and easily modify classes without having to write code first because object-oriented components are represented graphically. This helps coders to create a map of the class relationships and helps them recognize errors before coding begins. There are many design patterns available which cover a lot of different types of projects.
To obtain what class an object belongs to you can use the following
what class an object belongs to | System.out.println("The class of object " + obj + " is " + obj.getClass().getName() ); |
A class consists of two types of elements:
The rules for inheritance is as follows:
Create and Extend Abstract Classes
An abstract class is a class that is declared with the abstract modifier and it may or may not include abstract methods. An abstract class defers some or all of its implementation to its subclass and thus an abstract class cannot be instantiated. An abstract class is use to define common attributes and behavior for a class that will extend it.
The class example of an abstract class is the Animal class which is extended by the Dog class, as you normally will not instantiate an Animal class, the Animal class will contain common attributes and behavior that can be used by several animals, for example walk, bite, number of legs, etc. A class that is not abstract is called a concrete class. Note to remember is that an abstract class needs a subclass to implement its methods, thereby forcing commonality of interface. If one method is declared abstract then the class itself needs to be abstract as well, fields cannot be declared abstract.
Abstract class rules are as follows:
Minimal abstract class | abstract class AbstractClass {} |
abstract class | public abstract class Vehicle { // We create an enum which describes possible subclasses protected enum VehicleType { Automobile, Motorcycle, Moped, Bicycle, Scooter } // These attributes will be common to all subclasses private VehicleType type; private String owner; private String make; // Standard Constructor public Vehicle(VehicleType type, String owner, String make) { this.type = type; this.owner = owner; this.make = make; } // toString method public String toString() { return "Vehicle{" + "type=" + type + ", owner='" + owner + '\'' + ", make='" + make + '\'' + '}'; } // As the below are all abstract they will need to be implemented by the subclass public abstract void drive(); public abstract void park(); public abstract void makeNoise(); } // Vehicle is a concrete class which extends the abstract class Vehicle public class Auto extends Vehicle { // Create a constructor that is a pass thru to Vehicle constructor public Auto(VehicleType type, String owner, String make) { super(type, owner, make); } // Implement Vehicle's drive method public void drive() { System.out.println("Release Brake, go"); } // Implement Vehicle's park method public void park() { System.out.println("Parallel or back in?"); } // Implement Vehicle's makeNoise method public void makeNoise() { System.out.println("Vroom Vroom"); } // Main method creates an instance of Auto and demonstrates calling the // concrete methods public static void main(String[] args) { Auto car = new Auto(VehicleType.Automobile, "Allen", "Ford"); System.out.println(car); car.drive(); car.park(); car.makeNoise(); } } |
I have not covered Interfaces yet but here is a table of the differences between abstract class and an interface. If you want to provide common, implemented functionality among all implementations of your component, use an abstract class. Abstract classes allow you to partially implement your class, whereas interfaces contain no implementation for any members.
Abstract class | Interface |
---|---|
Abstract class can have abstract and non-abstract methods. | Interface can have only abstract methods. Since Java 8, it can have default and static methods also. |
Abstract class can define public, protected, package-private or private concrete methods | All abstract methods are public |
An abstract method must be declared abstract | If a method has no body by default its implicity abstract |
Abstract class doesn't support multiple inheritance. | Interface supports multiple inheritance. |
Abstract class can have final, non-final, static and non-static variables. | Interface has only public, static and final variables. |
Abstract class can provide the implementation of interface. | Interface can't provide the implementation of abstract class. |
The abstract keyword is used to declare abstract class. | The interface keyword is used to declare interface. |
An abstract class can extend another Java class and implement multiple Java interfaces. | An interface can extend another Java interface only. |
An abstract class can be extended using keyword "extends". | An interface can be implemented using keyword "implements". |
A Java abstract class can have class members like private, protected, etc. | Members of a Java interface are public by default. |
Whenever a subclass needs to refer to it's immediate superclass it can do so by using the keyword super. super has two general forms, the first calls the superclass constructor, the second is to access a member of the superclass that has been hidden by a member of a subclass.
Super example | public class SuperTest extends SuperClass { int i = 10; // hides the superclass variable i public SuperTest() { super(); // we call the superclasses constructor } public static void main(String[] args) { SuperTest st = new SuperTest(); st.getSuperVariable(); } public void getSuperVariable() { System.out.println("local variable i is : " + i); // getting the local variable i System.out.println("superclass variable i is : " + super.i); // getting the superclasses variable i } } class SuperClass { int i = 5; // this gets hidden by the local variable i public SuperClass() { System.out.println("This is the super constructor"); } } |
There is a special class in Java called the Object Class, all other classes are subclasses of Object, its a superclass of all other classes. Object defines a number of mewthod that all classes inherit or can override.
Object clone() | Creates a new object that is the same as the object being cloned |
boolean equals(Object object) | Determines whether one object is equal to another |
void finalize() | Called before an unused object is recycled |
Class<?> getClass() | Obtains the class of an object at runtime |
int HashCode() | Returns the hash code assciated with the invoking object |
void notify() | Resume execution of a Thread waiting on the invoking object |
void notifyAll() | Resumes execution of all Threads waiting on the invoking obect |
String toString() | Returns a string that describes the object. |
void wait() void wait(long milliseconds) void wait(long milliseconds, int nanoseconds |
Waits on another thread of execution |
When you want a to read something more meaningful about an object of your own class you can override the toString() method.
overriding toString() example | public class myTest() { public class Test() { Note: we can put lots more information in the returning String if we wish |
There are cases when you want to compare two objects that contain the same attributes and because the equals() method in the Object class uses only the = = operator for comparison, the two objects are only considered equal if the references refer to the same object. This method for comparing has a problem when using Hashtables, lets say we have a number of car objects with the same attributes and you put these into a Hashtable, when you search the Hashtable to get a number of objects with the same attributes, you will only retrieve the one which matches the reference you have, this is because the equals() method only uses the = = operator and hence only one object will match within the Hashtable
Not overriding equals() | public class stringTest { class Moof { Note: even though objects one and two are the same (same attributes) they are not equal because we use the Object class equals which uses the == operator to compare and as objects one and two do not point to the same reference they are not considered equal |
overriding equals() | public class stringTest { public static void main(String[] args) { Moof one = new Moof(8); Moof two = new Moof(8); if ( one.equals(two) ) { System.out.println("One equals two"); // using the overridden equals objects are equal } else { System.out.println("One does not equal Two"); } } class Moof { Note: now we override equals so that the objects are now equal |
The equals() method has a contract which has a number of rules that must be followed (obtained from the Java docs)
equals() and hashCode() are bound together by a joint contract, that states two equal objects should have the same hashcode as well, so to be very safe if you override the equals() method then you should override the hashCode() method.
The hashcode value of an object is used in collection classes, it's basically a object ID number, however it is not unique. The hashcode is used to store the object in collections such as HashMap and HashSet, this code is also used to locate the object as well.
hash code example | String s1 = "Hello"; String s2 = "hello"; System.out.println("The hashcode for " + s1 + " is " + s1.hashCode() ); // results in 69609650 System.out.println("The hashcode for " + s2 + " is " + s2.hashCode() ); // results in 99162322 |
How do hashcode's work, is that the collection will use a number of buckets that will contain hashcode's that point to data. For example lets say we have four people objects, each object is hashed by using an hashing algorithm and then placed into a bucket
Key | HashCode Algorithm | HashCode | |
Alex | A(1) + L(12) + E(5) + x(24) | = 42 | |
Bob | B(2) + o(15) + b(2) | = 19 | |
Dirk | D(4) + i(9) + r(18) + k(11) | = 42 | |
Fred | F(6) + r(18) + e(5) + d(4) | = 33 |
In real life the hash algorithm is a lot more advanced than this, as we can see Alex and Dirk will go into one bucket, Bob in another and Fred in another, so we have a bucket that contains two people objects which is acceptable. So now when you perform a search you no longer need to check each object, you simple hash the object you want to search for then go straight to that bucket and then only search through that bucket cutting down the number of objects to check against thus speeding up processing., in this case if you searched for Alex we would go straight to bucket 42 then search the two objects in there for Alex (thus only two searches instead of 4).
Now the problems arise if you do not pass the right name to search for (missing letter, upper case instead of lower case, thus the hashcode produced for you search would not match any bucket, now you see why if two objects are considered equal, their hashcode's must also be equal, Otherwise you'd never be able to find the object since the default hashcode method in class Object virtually always comes up with a unique number for each object. so if two objects are equal their hashcode's must be equal as well.
overriding hashcode | class HasHash { public int x; HasHash(int xVal) { x = xVal; } // Override the equals method public boolean equals(Object o) { HasHash h = (HasHash) o; // don't try without instanceof test, use the code from the example above if ( h.x == this.x) { return true; } else { return false; } } // Override the hashCode method public int hashCode() { return (x * 17); // does not need to complicated - see below // return 1234; // could even just return the same value to make objects equal } } |
What we are trying to achieve is a even distribution of data across the buckets, thus speeding up searches. Again the hashCode contract states the following rules
In short this means
Condition | Required | Not required (but allowed) |
x.equals(y) == true | x.hashCode() == y.hashCode() | |
x.hashCode() == y.hashCode() | x.equals(y) == true | |
x.equals(y) == false | no hashCode() requirement | |
x.hashCode() != y.hashCode() | x.equals(y) == false |
Sometimes you need to know what type an object is during runtime, for example you could have one thread creating objects and another thread processing them, the processing thread can then check th type of object to confirm that it can process it, also you can check before you cast an object to make sure it can be casted (otherwise you could get errors). Java uses the instanceof operator to confirm the type of an object.
instanceof example | class A { int i, j; } class B { int i, j; } class C extends A { int k; } class D extends A { int k; } class InstanceofTest { public static void main(String args[]) { A a = new A(); B b = new B(); C c = new C(); D d = new D(); if(a instanceof A) System.out.println("a is instance of A"); if(b instanceof B) System.out.println("b is instance of B"); if(c instanceof C) System.out.println("c is instance of C"); if(c instanceof A) System.out.println("c can be cast to A"); if(a instanceof C) System.out.println("a can be cast to C"); System.out.println(); // compare types of derived types A ob; ob = d; // A reference to d System.out.println("ob now refers to d"); if(ob instanceof D) System.out.println("ob is instance of D"); System.out.println(); ob = c; // A reference to c System.out.println("ob now refers to c"); if(ob instanceof D) System.out.println("ob can be cast to D"); else System.out.println("ob cannot be cast to D"); if(ob instanceof A) System.out.println("ob can be cast to A"); System.out.println(); // all objects can be cast to Object if(a instanceof Object) System.out.println("a may be cast to Object"); } } |
Using static import will import all static members of a class or interface, you can then access directly using their names with qualifying the with their class name.
static import example | // Use static import to bring sqrt() and pow() into view. import static java.lang.Math.sqrt; // you can use a wildcard here to import all static members import static java.lang.Math.pow; // Compute the hypotenuse of a right triangle. class Hypot { public static void main(String args[]) { double side1, side2; double hypot; side1 = 3.0; side2 = 4.0; // Here, sqrt() and pow() can be called by themselves, without their class name. hypot = sqrt(pow(side1, 2) + pow(side2, 2)); System.out.println("Given sides of lengths " + side1 + " and " + side2 + " the hypotenuse is " + hypot); } } |