Understanding Object-Oriented Relationships
In an application, the classes and class members are related to each other. For example, a class has a data variable defined in it. So the relationship in this example is that the class has the data variable in it. The classes themselves are related to each other; e.g. a class is derived from another class. There are two kinds of relationships that the classes inside an application may have, and these kinds correspond to the two properties of classes: inheritance and data abstraction.The is-a Relationship The is-a relationship corresponds to inheritance. Consider this statement: a boombox is a stereo. In Java, this statement may be ranslated to the following code:
class Stereo {
}
class Boombox extends Stereo {
}
In Java, this relationship is represented by the keyword extends , whereas in plain English this is an is-a relationship. When we say Boombox is-a Stereo , we mean all the following statements:
• Boombox is a subclass of Stereo .
• Stereo is a superclass of Boombox .
• Boombox inherits from Stereo .
• Boombox is derived from Stereo .
• Boombox extends Stereo .
A rule of thumb to recognize an is-a relationship is that every object of the subclass is also an object of the superclass and not vice versa. For example, every cow on the planet is an animal, but not every animal is a cow. Also remember that a class is in an is-a relationship with any class up in its hierarchy tree. For example, if A extends B , and B extends C , then C is B , and also C is A .
So, a class can relate to another class by inheriting from it. There is another way in which two classes can be related to each other: the has-a relationship.
The has-a Relationship
The has-a relationship
corresponds to an object-oriented characteristic called encapsulation,
which means the data and the methods are combined into a structure
called a class. Consider a class CDPlayer . Now, consider this
statement: the boombox is-a stereo and it has-a CD player. In Java, this statement may be translated to the following: class Stereo {
}
class Boombox extends Stereo {
CDPlayer cdPlayer = new CDPlayer();
}
class CDPlayer {
}
Following are some other examples of is-a and has-a relationships:
• A city has a community center. A community center is a building.
• A classroom has a whiteboard. The classroom is a room.
• A country has citizens. The president is a citizen.
So, a class X has-a class Y , if the class X has a reference to class Y . The classes can have is-a or has-a relationships with each other. A given class is also related to its class members: variables and methods. The methods and variables are encapsulated in a class.
Encapsulation and Data Abstraction Encapsulation facilitates data abstraction , which is the relationship between a class and its data members. Encapsulation refers to the fact that the data variables (also called the properties) and the methods (also called the behavior) are encapsulated together inside a template called a class. The data members encapsulated inside a class may be declared public , private , or protected . However, good object-oriented programming practice requires tight encapsulation. That means all data members of the class should be declared rivate . That is, the code outside of the class in which the data members are declared can access them only through method calls, and not directly. This is called data abstraction (or data hiding), because now the data is hidden from the user, and the user can have access to it only through the methods.
Encapsulation (and data abstraction) makes the code more reliable, robust, and reusable. This is so because the data and the operations on it (methods) are encapsulated into one entity (the class), and the data member itself and the access to it are separated from each ther (tight encapsulation or data abstraction). For example, in tight encapsulation, where your data members are private , if you change the name of a data variable, the access code will still work as long as you don't change the name of the parameters of the method that is used to access it.
1. public class TestEncapsulateBad {
2. public static void main(String[] args) {
3. EncapsulateBad eb = new EncapsulateBad();
4. System.out.println("Do you have a headache? " + eb.headache);
5. }
6. }
7. class EncapsulateBad {
8. public boolean headache = true;
9. public int doses = 0;
10.}
The output from this code follows: Do you have a headache? true
Note : that in line 4, the data variable of class EncapsulateBad is accessed directly by the code piece eb.headache . This is only possible because the variable headache is declared public in the class EncapsulateBad . If you declare this variable private , line 4 will cause a compiler error. Let's say you keep it public , and then you change the name of the variable headache in the ncapsulateBad class to something else. In this case, line 4 will again generate an error, because you need to change the name of the variable headache in line 4 to the new name as well.
However, a better solution is to separate the data variable from the access by declaring it private and provide access to it through methods, as shown below :
1. public class TestEncapsulateGood {
2. public static void main(String[] args) {
3. EncapsulateGood eg = new EncapsulateGood();
4. eg.setHeadache(false);
5. System.out.println("Do you have a headache? " + eg.getHeadache());
6. }
7. }
8. class EncapsulateGood {
9. private boolean headache = true;
10. private int doses = 0;
11. public void setHeadache(boolean isHeadache){
12. this.headache = isHeadache;
13. }
14. public boolean getHeadache( ){
15. return headache;
16. }
17. }
The output from this code follows: Do you have a headache? false
Note that the variable headache is now declared private (line 9), and it can be accessed through methods setHeadache(…) and getHeadache() , which are declared public (lines 11 and 14). The world can still access your data, but this access process is separated from the details of the data—the data variable name, how the change is made to it, and so forth. This hiding of the data details from the access procedure is called data abstraction. In an application, it means you can make changes to the data variables in your class without breaking the API. Such protection also makes the code more extendible and easier to maintain. So, the benefits of encapsulation are: hard-to-break reliable code, easy maintenance, and extensibility.
So, encapsulation (or a lack of it) determines how the classes interact with each other, a property also called coupling . Actually, there are two more characteristics that good object-oriented programming should exhibit: loose coupling and cohesion.
Conversion of Primitive Data Types You know from Chapter 2 that each data variable in Java has a type. For example, there are primitive data types such as boolean , byte , char , short , int , long , float , and double . Furthermore, classes in Java are also data types. Therefore, the number of data types associated with object references is infinite. As you know, data held in memory is referred to by using the primitive variable names. A
variable is declared of a certain type corresponding to the type of the data it will hold or refer to. During data manipulation, the data values may change their types. This is called type conversion .
Type conversion in Java can happen in two ways:
• Implicit type conversion : The programmer does not make any attempt to convert the type; rather, the type is automatically converted by the system under certain circumstances.
• Explicit type conversion : Conversion is initiated by the programmer by making an explicit request for conversion. This is also known as type casting.
Using Method Overriding and Overloading
Overriding
and overloading are two salient features of Java. Overriding allows
you to modify the behavior of an inherited method to meet the specific
needs of a subclass, while overloading allows you to use the same method
name to implement different (but related) functionalities. Therefore,
overriding and overloading facilitate code extensibility and
flexibility. Method Overriding You know that a subclass inherits all the (non-private) methods and variables of the superclass. In some situations, you might need to change the behavior of an inherited method, such as when implementing polymorphism, as explored earlier in the chapter. In that case, you would redefine the method by keeping the same signature but rewriting the body. This is exactly what is called method overriding. In other words, method overriding is a feature of Java that lets the programmer declare and implement a method in a subclass that has the same signature as a method in the superclass. The same signature in this case means the same name and the same number of parameters and their types appearing in the same order. While the signature must be the same, the code in the method body may be, and generally is, different. The body of the overriding method may or may not include a call to the overridden method. That is, you can specifically invoke the overridden method from the body of the overriding method, if you want to. As an example, consider again the code example
1. public class TestPoly {
2. public static void main(String [] args) {
3. Animal heyAnimal = new Animal();
4. Cow c = new Cow();
5. Buffalo b = new Buffalo ();
6. heyAnimal=c;
7. heyAnimal.saySomething();
8. heyAnimal=b;
9. heyAnimal.saySomething();
10. }
11.}
12.class Animal {
13. public void saySomething() {
14. System.out.println("Umm...");
15. }
16.}
17. class Cow extends Animal {
18. public void saySomething() {
19.// super.saySomething();
20. System.out.println("Moo!");
21. }
22. }
23. class Buffalo extends Animal{
24. public void saySomething() {
25. // super.saySomething();
26. System.out.println("Bah!");
27. }
28. }
and 25, which invoke the overridden method saySomething() of the superclass Animal from the subclasses
Cow and Buffalo . If you compile and execute the code, the following is the output:
Umm…, Moo!, Umm…, Bah!
The overridden version and the overriding versions of saySomething() were executed.
The following are the rules for overriding a method:
• You cannot override a method that has the final modifier.
• You cannot override a static method to make it non-static.
• The overriding method and the overridden method must have the same return type. J2SE 5.0 allows a covariant return type as well, as discussed a bit later in this section.
• The number of parameters and their types in the overriding method must be same as in the overridden method and the types must appear in the same order. However, the names of the parameters may be different.
• You cannot override a method to make it less accessible. For example, overriding a public method and declaring it protected will generate a compiler error, whereas overriding a protected method and declaring it public will be fine.
• If the overriding method has a throws clause in its declaration, then the following two conditions must be true:
• The overridden method must have a throws clause, as well.
• Each exception included in the throws clause of the overriding method must be either one of the exceptions in the throws clause of the overridden method or a subclass of it.
• If the overridden method has a throws clause, the overriding method does not have to.
The throws clause has to do with the execution flow when an error happens in the executing program. You will learn more about this topic in Chapter 7. Do not forget the obvious rule that if you cannot inherit a method, you cannot override it either. For example, you cannot override a private method of the superclass because you cannot inherit it. Of course, you can write a method in the subclass with the same signatures (method name and parameters) as a private method in the superclass. But it will not be considered as an overriding. Note that violating any of the overriding rules mentioned here will generate a compiler error.
To elaborate on some of the rules for overriding, consider the following method signature of a superclass:
protected int aMethod(String st, int i, double number); Examples of Valid and Invalid Overriding of the Method protected int aMethod(String st, int i, double number)
Method Signature in a Subclass | Validity | Reason | |||
protected int aMethod(String st, int i, double number) | Valid | Same signature | |||
protected int aMethod(String st, int j, double num) | Valid | Same signature | |||
protected double aMethod(String st, int i, double number) | Invalid | Different return type | |||
protected int aMethod(int i, String st, double number) | Invalid | Argument types are in different order | |||
protected int aMethod(String st, int i, double number, int j) | Invalid | Different number of types | |||
protected int aMethod(String st, int i) | Invalid | Different number of types |
For example, consider the following method:
public Number myMethod();
In J2SE 5.0, it can be legally overridden by the following method:
public Double myMethod();
Note that this overriding is legal because Double is a subclass of Number . So, overriding is rewriting a method that a subclass inherits from its superclass. It does not, however, overwrite the superclass method; you can still invoke the original version of the inherited method by using the keyword super . So after overriding, two versions of a method co-exist. You can also write multiple versions of a method in the same class in order to meet the business requirements of using basically the same functionality but in slightly varying ways. This is called method overloading.
Method Overloading Method overloading is helpful when the same task is to be performed in slightly different ways under different conditions. So, method overloading is a feature of Java that facilitates defining multiple methods in a class with identical names. In contrast to overriding ethods, no two overloaded methods could have the same parameter types in the same order. The return types in overloaded methods may be the same or different, while the return type of an overriding method must match that of the overridden method.
The key points about method overloading are summarized here: • Two or more methods in the same class with the same name are called overloaded if they have either different sets of parameter types or the same set of parameter types in different order.
• The return type of two overloaded methods can be different or the same.
• Overloaded methods are effectively independent of each other.
• The compiler determines which of the overloaded methods to call by looking at the argument list of the method call.
• Any of the methods inherited from the superclass can also be overloaded.
• Overloaded versions of a method can have different or the same checked exceptions in the throws clauses.
• Overloaded versions of a method can have different modifiers.
Constructor Overloading
• A constructor of a class has the same name as the class, and has no explicit return type; it can have zero or more parameters.
• A class may have more than one constructor. If the programmer defines no constructor in a class, the compiler adds the default constructor with no arguments. If one or more constructors are defined in the class, the compiler does not provide any constructor.
When we say a constructor must have the same name as the class, but we can have multiple constructors for a class, we are obviously talking about constructor overloading. Constructor overloading provides flexibility in how you want your constructor to be instantiated and initialized. Inside a constructor, you can call another constructor.
The keyword this is used to call another constructor of the same class, and the keyword super is used to call a constructor of the uperclass. If either this or super is used, it must appear in the beginning of the code block of the constructor. If we add our own super or this call, then the compiler will not add this line.
1. public class ConstOverload{
2. public static void main(String[] args) {
3. new A();
4. }
5. }
6. class A {
7. int x=0;
8. A(){
9. this(5);
10. System.out.println("A() ");
11. }
11. A(int i){
12. // this();
13. System.out.println(i);
14. }
15. }
Note that line 12 is commented. The output from the Code Fragment : 5 A( )
If you uncomment line 12, depending upon a specific compiler, you may get a compiler error. But if you don't, your program will execute in a loop. This is because class A has only two constructors, and both have the this statement in the beginning, so the compiler does not place any super() call. So, both constructors would keep calling each other. Remember the following two things relevant to constructor overloading:
• The first line in a constructor must be a super() or a this() call. If (and only if) you do not place either of these calls, the compiler will place the super() call.
• If (and only if) you do not define a constructor for a class, the compiler will place a no-argument default constructor for you.
No comments:
Post a Comment