Inheritance - Polymorphism ITI 1121 Nour El Kadri Polymorphism From the Greek words polus = many and morphˆe = forms, literally means has many forms. In Java, a variable or a method is polymorphic if it refers to objects of more than one “class/type”. Method overloading Method overloading means that two methods can have the same name but different signatures (the signature consists of the name and formal parameters of a method but not the return value). Constructors are often overloaded, this occurs for the class Shape: Shape () { x = 0.0; y = 0.0; } Shape (int x, int y) { this.x = x; this.y = y; } method overloading is sometimes referred to as ad hoc polymorphism. Overloading (cont’d) In Java certain operators are overloaded, consider the “+” which adds two numbers or concatenates two strings, a user can overload a method but not an operator. Since the signatures are different, Java has no problem finding the right method: static int sum(int a, int b, int c) { return a + b + c; } static int sum(int a, int b) { return a + b; } static double sum(double a, double b) { return a + b; } Motivation We need to write a new method that compares two Shapes. In particular, a shape will be considered smaller than another if its area is smaller than the area of that shape. Since there are more than one kind of Shapes, we could write methods with the same name and all four possible signatures (method overloading): (Circle, Circle), (Circle, Rectangle), (Rectangle, Circle) and (Rectangle, Rectangle). public class Test extends Object { public static int compare(Circle c, Circle d) { return compare(c.area(), d.area()); } public static int compare(Circle c, Rectangle r) { return compare(c.area(),r.area()); } public static int compare(Rectangle r, Circle c) { return compare(r.area(),c.area()); } public static int compare(Rectangle r, Rectangle s) { return compare(r.area(),s.area()); } private static int compare(double a, double b) { int result; if (a < b) result = -1; else if (a == b) result = 0; else result = 1; return result; } public static void main(String args[]) { Circle c = new Circle(100, 100, 5); Rectangle r = new Rectangle(0, 0, 10, 15); int result = compare(c, r); } } What do you think? What are the problems with this proposal? the bodies of all the methods are identical; if there were 3 kinds of shapes there would be 9 methods! each time a new shape is defined new methods must be created. Clearly there has to be a better way to implement such a method! How does it work? What would be necessary for such a method to work? The arguments can be any objects that implement the method area. static int compare(‘‘Any shape’’ a, ‘‘Any shape’’ b) { return compare(a.area(), b.area()); } Polymorphic variables A polymorphic variable refers to objects of more than one class. Consider the following statement, s is a polymorphic variable, Shape s = new Circle(); It declares a reference variable to an object of the class Shape or any of its subclasses, Shape s; in other words, this variable refers to any object that implements all the methods of the public interface of the class Shape and has all the same public variables (of course, there could be more methods and variables but at least those of the interface of Shape must be defined). In particular, since Circle is a subclass of Shape it inherits all the characteristics of a shape. s = new Circle(); Key elements 1. The class of an object never changes during the execution of a program; 2. A reference can be used to designate successively more than one object during the execution of a program; 3. It’s the object’s class that determine the characteristics of the object (variables + methods) and not the type of the reference. class A { public void a() { System.out.println("method ‘‘a’’ was called!"); } } class B extends A { public void b() { System.out.println("method ‘‘b’’ was called!"); } } class Test { public static void main(String args[]) { A a = new A(); B b = new B(); A c = new B(); // B d = new A(); does not work, why? } } A a = new A(); // ok a.a(); // ok a.b(); // doesn’t work, a is an object // of class A and A does not // have a method b() B b = new B(); // ok b.a(); // ok b.b(); // ok A c = new B(); // ok c.a(); // ok B d = new A(); // doesn’t work why? We can now define compare as follows: public static int compare(Shape a, Shape b) { return compare(a.area(), b.area()); } and it would work for any pair of shapes. Circle c = new Circle(100, 100, 5); Rectangle r = new Rectangle(0, 0, 10, 15); int result = compare(c, r); this would work for any subclass of shape; even the ones that are not yet defined. Since a is a parameter and a refers to objects of more than one class we say that a is a polymorphic parameter (similarly for b). Since “compare” has polymorphic parameters we say that “compare” is a polymorphic method, it’ll compare the areas of the objects that are instances of any subclasses of Shape. public class Test { public static void main(String args[]) { Shape[ ] shapes = new Shape[4]; shapes[0] = new Circle(); shapes[1] = new Circle(100, 200, 10); shapes[2] = new Rectangle(); shapes[3] = new Rectangle(50, 50, 10, 15); for (int i=0; i<shapes.length; i++) { System.out.println (shapes[i].area()); if (shapes[i] instanceof Rectangle) { Rectangle r = (Rectangle) shapes[i]; r.flip(); System.out.println ("flip: " + r); } } } } Late binding also called dynamic binding. Shape s = new Circle(); System.out.println(s); Remember that we overrode toString() within the class Circle. There is a method toString() in both classes Shape and Circle, s is of type Shape but currently refers to an object of the class Circle. Which method toString() will be called? Dynamic binding (cont’d) The closest method to the object in the hierarchy is used/called. Shape s = new Circle(10, 10); System.out.println(s); Which method toString will be called? The one which is defined in Shape or the one defined in Circle? The following example illustrates the fact that the actual class of the object referenced by c is known at runtime: Shape s; Circle c; if (Math.Random() >= 0.5) s = new Circle(); else s = new Rectangle(); c = s; // would not be allowed by the compiler instanceof Sometimes we need to know if the object designed by a reference variable is of a given type. Shape s; if (Math.Random() >= 0.5) s = new Circle(); else s = new Rectangle(); if (s instanceof Circle) { Circle c = (Circle) s; // type casting int r = c.getRadius(); // and this would work } instanceof (cont’d) The type casting and the method call can be combined: Shape s; if (Math.Random() >= 0.5) s = new Circle(); else s = new Rectangle(); if (s instanceof Circle) { // tells the compiler to consider s as an instance of the //class Circle int r = ((Circle) s).getRadius(); } Caution! Use type casting with parsimony. It is sometimes an indication of a bad design. Try to see if the method could be implemented elsewhere in the hierarchy. Finally, remember that Java would not allow the following statement since Shape is an abstract class and there can be no instance of an abstract class. Shape s = new Shape(); Summary — definitions This section summarizes the main definitions related to object-oriented programming. encapsulation: the implementation details of a concept are concentrated in one place. In object oriented programming, the data (variables) and the methods that act on the data are encapsulated into a single entity called the object. An object has an interface, which consists of the public methods and variables of the object. This allows to clearly separate the implementation of a class from its usage. The (client) programmer using the class doesn’t need to know about the implementation of the class. The information that’s hidden inside the class can be changed without affecting the programs using it. information hiding: the details of the implementation of a concept are not visible to the user of the class or the method. inheritance: most object-oriented programming languages organize classes as a hierarchy such that a class has only one parent but can have many descendants. The descendants of a class inherit the characteristics (variables and methods) of the parent. The relationship is transitive, which means that a class inherits all the characteristics of its ancestors. polymorphism: polymorphism is the ability of a method or a variable (parameter) to refer to objects of different classes. superclass: also called parent or base class, is the class immediately above in the hierarchy; every class except Object has one. subclass: also called child or derived class, this the class immediately below; every class except Object is the subclass of another class. In Java, a subclass is declared with the keyword extends followed by the name of the superclass. A class can have no subclass or many subclasses but it only has one superclass. The relationship between the subclass and its superclass is sometimes referred to as “is-a” relationship. abstract class: An abstract is a class that’s never instantiated. Other classes are called concrete classes. An abstract class is declared by prefixing the keyword class by abstract in the class definition. abstract method: is a method which is defined in an abstract class and has no implementation. The implementation for the method will be provided by the subclass(es). It is placed in the superclass to ensure that every class derived from the superclass will have it; it’s a “place holder”. overloading: overloading consist in declaring two or more methods with the same name but different signatures. In the class Shape two constructors were defined, one with no arguments the other with three. overriding: method overriding consists of providing a new implementation for a method that was defined in the superclass. The two methods have the same name and the same signature (i.e. same list of formal parameters). In an instance of the subclass, the definition of the method in the superclass would be shadowed by that of the subclass. The keyword final in the declaration of a method prevents overriding by the subclass. Since Object has a method toString, any class that supplies its own version is overriding the definition of toString in Object. The method that’s closest to the object is always used (follow the arrows).