490 likes | 662 Views
Implementation: Java Classes and Objects. Download as Power Point file for saving or printing . Overview.
E N D
Implementation: Java Classes and Objects Download as Power Point file for saving or printing.
Overview Java, as many other object oriented languages, generally support polymorphism and inheritance. Implementing these features require some means of maintaining and referencing the class structure and hierarchy at runtime. For example, the new Stack( ) method, to create a Stack object, must allocate storage for each object attribute, of the Stack class (i.e. data and top ). In order to determine the amount of storage and bindings the new method must have access to the Stack class description. In Java this ability of discover the description of a class is termed reflection. JavaBeans, a component technology to be examined later in the course, makes use of reflection to discover classes, events, attributes and methods supported by the component. Java provides a reflection API used by the compiler and user applications to examine the public definitions of a class.
Reflection • Reflection - The class of an object can be determined at runtime using the getClass( ) method. For example: Rectangle r = new Rectangle(); r.getClass(); returns class Rectangle • Applet to reflect Java classes - To demonstrate what reflection implies in a programming language an applet is provided that will examine a class file. Remember that the class file is essentially an executable Java program but contains class description information. The applet uses reflection to reconstruct the class hierarchy, attributes and methods from the class file. Implementation: Classes and Objects
Exercise 1 - Reflection • Exercise 1 • Click to execute Reflect applet to examine class files. • Examine the Stack class and compare with the definitions below.
Exercise 2 – Trace the execution starting at line 1. Exercise 2 - Stack Definition and Representation Stack a = new Stack(); a.push(-4); a.push(7); a.push(9); a.pop(); class Stack extends Object { private double data[ ]; private int top; public Stack() { top = -1; data = new double[5]; } public void push ( double e ) { top++; data[top] = e; } public double pop() { top--; return data[top+1]; } }
Exercise 3 • Click to execute Reflect applet to examine class files. • Examine the SimpleStack class. • What are the methods?
Exercise 4 – Trace the execution for lines 5-9. Exercise 4 - Stack Definition • class Stack extends Object • { private double data[]; • private int top; • public Stack() • { this.top = -1; • this.data = new double[5]; • } • public void push ( double e ) • { this.top++; • this.data[this.top] = e; • } • public double pop() • { this.top--; • return this.data[this.top+1]; • } • } • public class SimpleStack • { public static void printTop( Stack s) { • System.out.println( s.pop( ) ); • } • public static void main(String args[]) • { Stack a = new Stack(); • a.push(-4); • a.push(7); • a.push(9); • Stack b = new Stack(); • b.push(8); • b.push(-3); • SimpleStack.printTop( b ); • } • }
Object construction & representation • public class SimpleStack • { public static void printTop( Stack s) { • System.out.println( s.pop( ) ); • } • public static void main(String args[]) • { Stack a = new Stack(); • a.push(-4); a.push(7); a.push(9); • Stack b = new Stack(); • b.push(8); b.push(-3); • SimpleStack.printTop( b ); • } • } • class Stack extends Object • { private double data[]; • private int top; • public Stack() • { this.top = -1; • this.data = new double[5]; • } • public void push ( double e ) • { this.top++; • this.data[this.top] = e; • } • public double pop() • { this.top--; • return this.data[this.top+1]; • } • }
Tracing method execution Start: a.push(-4); • follow reference through a to object, • follow reference through class attribute of object to class definition for Stack, • follow reference through methods attribute of class definition to push method, • follow reference through push attribute of methods to executable code for push method. • The object referenced by a is the implicit parameter this to the push method. The push method has full access to the Stack object referenced by a.
Java Parameter passing is by Value • Java passes parameters by value meaning that a copy of the parameters value is made and passed. • When the parameter is an object its value is a reference to the object, an address. • The object reference is copied and passed.
Java Parameter passing is by Value SimpleStack.printTop( a ); void printTop( Stack s) { System.out.println( s.pop( ) ); } passes a by value a.push(-4); passes -4 by value • Questions • How is a passed to this? • Does top field of a change in pop? public double pop() { this.top--;return this.data[this.top+1];}
SimpleStack and Stack definitions. Exercise 4 - Stack Definition • class Stack extends Object • { private double data[]; • private int top; • public Stack() • { this.top = -1; • this.data = new double[5]; • } • public void push ( double e ) • { this.top++; • this.data[this.top] = e; • } • public double pop() • { this.top--; • return this.data[this.top+1]; • } • } • public class SimpleStack • { public static void printTop( Stack s) { • System.out.println( s.pop( ) ); • } • public static void main(String args[]) • { Stack a = new Stack(); • a.push(-4); • a.push(7); • a.push(9); • Stack b = new Stack(); • b.push(8); • b.push(-3); • SimpleStack.printTop( b ); • } • }
Exercise 4 Continued • Trace on the diagram how the following is executed:SimpleStack.printTop( b ); • What is the value of thisin pop() method? public static void printTop( Stack s) { System.out.println( s.pop( ) ); } public double pop() { this.top--;return this.data[this.top+1];}
Inheritance Terms • inheritance - defines one class as an extension on another. • super (parent) class - The class that is extended with additional fields and methods. Polygon is the super or parent class. • sub (child) class - The class that inherits all fields and methods of the super class. Rectangle is the subclass of Polygon. • private - Subclasses inherit but cannot access private methods or fields. • public - Polygon methods that are public can be called as internal methods (i.e. called on a Rectangle object). Public fields are accessible in subclass. • constructor - Polygon constructors can be called as super followed by any normal parameters. The Polygon(int sides) constructor is called by super(4) in the Rectangle( double length, double width ) constructor. • over-riding methods - The area() method is defined in both classes, the definition used is the closest class. Rectangle objects call Rectangle area() method, Polygon objects call Polygon area() method. • super - Over-riding methods as with area() blocks access to inherited methods. To call a super class method, precede the method call with super.
class Polygon extends Object { private int sides; public Polygon(int sides) { this.sides = sides; } public double area( ) { return 0.0; } public void printOn( ) { System.out.println( this.getClass( ) + " " + this.area( ) ); } } Inheritance public class Shapes { public static void main(String args[ ]) { Polygon p = new Polygon(8); Rectangle r=new Rectangle(5.0, 3.0); r.area( ); p.printOn( ); r.printOn( ); } } class Rectangle extends Polygon { private double length, width; public Rectangle(double length, double width) { super(4); this.length = length; this.width = width; } public double area( ) { return length * width; } } Object Polygon() area() printOn() Rectangle() area()
public class Shapes { • public static void main(String args[ ]) • { Polygon p = new Polygon(8); • Rectangle r = new Rectangle(5.0, 3.0); • r.area( ); • p.printOn( ); • r.printOn( ); • } • } • class Polygon extends Object { • private int sides; • public Polygon(int sides) { this.sides = sides; } • public double area( ) { return 0.0; } • public void printOn( ) { System.out.println( this.getClass( )+""+this.area( ));} • } • class Rectangle extends Polygon { • private double length, width; • public Rectangle(double length, double width) { • super(4); • this.length = length; this.width = width; • } • public double area( ) { return length * width; } • } Exercise 5 - Inheritance Exercise 5 • Trace execution from 3-7. • What is the program output?
Polymorphism Polymorphism p.printOn( ); r.printOn( ); public void printOn( ) { System.out.println( this.getClass( ) + " " + this.area( ) ); } • Polymorphism in the printOn( ) method of the Polygon class when invoking the area( ) method. • The printOn( ) method is invoked using both a Polygon p and a Rectangle r object. • Class of the object determines which area( ) method is invoked within the printOn( ) method.
public class Shapes { • public static void main(String args[ ]) • { Polygon p = new Polygon(8); • Rectangle r=new Rectangle(5.0, 3.0); • r.area( ); • p.printOn( ); • r.printOn( ); • } • } • class Polygon extends Object { • private int sides; • public Polygon(int sides) { • this.sides = sides; } • public double area( ) { return 0.0; } • public void printOn( ) { • System.out.println( this.getClass( )+"" • +this.area( ));} • } • class Rectangle extends Polygon { • private double length, width; • public Rectangle(double length, • double width) { • super(4); • this.length = length; this.width = width; • } • public double area( ) { return length * width; } • } Inheritance Diagram
Subtyping and assignment • Subtype - Defined by subclass hierarchy. • A CD object is of type CD, CD is a subtype of Item. • A VideoGame object is of type VideoGame, VideoGame is a subtype of Game. • Game is a subtype of Item. The two following definitions are compatible. • Variables and subtypes -Variables reference objects of their type or subtype. Cannot reference (i.e. be assigned) supertype objects. • Substitution -Subtype objects can be substituted wherever supertype object allowed.
Subtyping and assignment Item item1 = new Item();Game game1 = new Game();VideoGame vg1 = new VideoGame(); • Game variable game1 can reference Game or VideoGame objects (same type or subtype) but not Item. • Item variable item1 can reference Item objects and CD, Video, Game or VideoGame objects. • VideoGame variable vg1 can reference VideoGame objects but not Game or Item. Compatible types item1 = game1; game1 = vg1; Item item2 = new Game();Game game2 = new VideoGame(); Incompatible game1 = item1;vg1 = game1;vg1 = item1; VideoGame vg2 = new Game();Game game2 = new Item();
Exercise 5.5 Item item1 = new Item();Game game1 = new Game();VideoGame vg1 = new VideoGame(); Which statements are true? • Game is a subclass of Item. • Game is a superclass of VideoGame. • Game is a subclass of Video. • Game is a superclass of Video. Which are valid (using above item1, game1 and vg1 definitions)? • item1 = game1; • game1 = item1; • game1 = vg1; • vg1 = item1; • Item item2 = new Game(); • Game game2 = new Item(); • Video video1 = new Game();
Subtyping and parameter passing • Passing subtypes -Actual parameters can be passed to formal parameters of the same or supertype. • Passing supertypes -Actual parameters cannot be passed to formal parameters of a subtype.
Subtyping and parameter passing public class Database{ public void addItem( Item theItem ) {} public void addGame( Game theGame ) {} public void addVideoGame( VideoGame theVideoGame ) {}} Database db = new Database(); Item item1 = new Item(); Game game1 = new Game(); VideoGame vg1 = new VideoGame(); Compatible types Incompatible db.addItem( item1 ); db.addItem( game1 ); db.addItem( vd1 ); db.addGame( vd1 ); db.addVideoGame( vd1 ); db.addGame( item1 );db.addVideoGame( item1 );db.addVideoGame( game1 );
public class Database{ public void addItem( Item theItem ) {} public void addGame( Game theGame ) {} public void addVideoGame( VideoGame theVideoGame ) {}} Database db = new Database(); Item item1 = new Item(); Game game1 = new Game(); VideoGame vg1 = new VideoGame(); Exercise 5.7 • Which are valid using above db, item1, game1 and vg1 definitions? • db.addItem(item1); • db.addItem(game1); • db.addItem(vg1); • db.addGame(item1); • db.addGame(game1); • db.addGame(vg1); • db.addVideoGame(item1); • db.addVideoGame(vg1); • db.addVideoGame(game1);
The Object class • Object -The top-level class. All classes inherit from Object class. • Object is the superclass of all classes. • Item is a subclass of Object. • The following are equivalent: public class Item public class Item extends Object
public class Database{ public void addObject( Object theObject ) public void addItem( Item theItem ) Database db = new Database(); Object object1 = new Object(); Item item1 = new Item(); Exercise 5.8 • Which are valid using above db, item1, • and object1 definitions? • item1 = object1; • object1 = item1; • db.addObject( item1 ); • db.addObject( object1 ); • db.addItem( item1 ); • db.addItem( object1 );
Polymorphic collections • Polymorphic collections can reference different types of objects. The Java collections ArrayList and HashMap are examples, able to reference String, Polygon, Game or other objects. • This is possible because these collections are defined to reference Object types. A variable of type Object can reference an object any subtype. • ArrayList signatures are given below for the add() and get() methods. Because Object is the super-type of all types, an ArrayList element can reference any other type. public class ArrayList { public void add( Object theObject ) public Object get( int index)
The ArrayList method get() returns a reference to an Object.Object is the superclass so cannot be assigned to a subtypes such as Item, TicketMachine, etc. It must be cast to the correct type for the assignment. Compile errors - The compiler can detect errors where a supertype is assigned to a subtype variable in // 1 below. Runtime errors - The compiler can not detect errors where a subtype is assigned to the incorrect type variable as in // 3 below. However, at runtime // 3 will cause the program to fail. Why can't the compiler catch the error in // 3? Because at runtime the ArrayList element 1 could be a CD, Video or any other subtype of Object. It depends upon what we have added to the ArrayList. The ArrayList method get() returns a reference to an Object.Object is the superclass so cannot be assigned to a subtypes such as Item, TicketMachine, etc. It must be cast to the correct type for the assignment. Compile errors - The compiler can detect errors where a supertype is assigned to a subtype variable in // 1 below. Runtime errors - The compiler can not detect errors where a subtype is assigned to the incorrect type variable as in // 3 below. However, at runtime // 3 will cause the program to fail. Why can't the compiler catch the error in // 3? Because at runtime the ArrayList element 1 could be a CD, Video or any other subtype of Object. It depends upon what we have added to the ArrayList. The Need for Casting The ArrayList method get() returns a reference to an Object.Object is the superclass so cannot be assigned to a subtypes such as Item, TicketMachine, etc. It must be cast to the correct type for the assignment. Compile errors - The compiler can detect errors where a supertype is assigned to a subtype variable in // 1 below. Runtime errors - The compiler can not detect errors where a subtype is assigned to the incorrect type variable as in // 3 below. However, at runtime // 3 causes the program to fail. Why can't the compiler catch the error in // 3? Because at runtime the ArrayList element 1 could be a CD, Video or any other subtype of Object. It depends upon what we have added to the ArrayList. public class ArrayList { public void add( Object theObject ) public Object get( int index) ArrayList aL = new ArrayList();aL.add( new CD() );aL.add( new Video() );CD cd1 = aL.get( 0 ); // 1. Compile error, CD = Object Video vd1 = (Video) aL.get( 1 ); // 2. Compile OK, runtime OK. Video = Video CD cd2 = (CD) aL.get( 1 ); // 3. Compile OK but runtime error! CD = Video.
Which of the following are valid syntax? • Which give a runtime error? • aOne = aL.get(0); • aOne = (One) aL.get(0); • aThree = (One) aL.get(2); • aThree = (Three) aL.get(2); • aThree = (Two) aL.get(1); • ((Three) aL.get(2)).getS(); Exercise 5.9
public class Shapes { • public static void main(String args[ ]) • { Polygon p = new Polygon(8); • Rectangle r=new Rectangle(5.0, 3.0); • r.area( ); • p.printOn( ); • r.printOn( ); • } • } • class Polygon extends Object { • private int sides; • public Polygon(int sides) { • this.sides = sides; } • public double area( ) { return 0.0; } • public void printOn( ) { • System.out.println( this.getClass( )+"" • +this.area( ));} • } • class Rectangle extends Polygon { • private double length, width; • public Rectangle(double length, • double width) { • super(4); • this.length = length; this.width = width; • } • public double area( ) { return length * width; } • } Inheritance Diagram
Exercise 6 • From examination of the object diagram, is multiple inheritance possible (i.e. inheritance from more than one parent class)? • The following statement is legal: Object o = new Polygon(8); // any Polygon is an Object but Polygon p = new Object( ); // any Object is not a Polygon is illegal while the following is legal: Polygon p = (Polygon) (new Object( )); // Cast an Object to a Polygon Why? Look at the next questions and the diagram for clues. Object Polygon() Rectangle()
Exercise 6 Continued • With the legal statement: Object o = new Polygon(8); // a Polygon is an Object the following is illegal, producing the error: Method printOn( ) not found in class java.lang.Object o.printOn( ); // Object does not have a // printOn() method but ((Polygon) o).printOn( ); // Cast an Object to Polygon prints from the area() method:class Polygon 0.0Why? Examine the object diagram above. Object Polygon() printOn() area() Rectangle() area()
Exercise 6 Continued • With the legal statement: Polygon p = (Polygon) (new Object()); the following is legal syntax but produces the runtime error: Exception in thread "main" java.lang.ClassCastException: java.lang.Object p.printOn( ); // An Object cast as a Polygon //is still an Object What can you say in general about casting within an inheritance hierarchy?
C++ Template Stack • Templates automate implementation of abstract algorithms that can operate on multiple data types. In the stack example below, a different data class can be given for each instantiation of the Stack class without manually changing the Stack class definition. Instead, the compiler generates code specific for each data type listed as class MyObject. For example the printOn method must be defined differently to print each different data type stored in the Stack class such as Age or Income, this is done automatically by the template facility. • Templates are valuable in promoting code reuse; for example, different Stack code is generated for different data types from a single copy of the algorithm. • Templates are satisfactory for constructing distinct Age and Income stacks. But suppose a single stack that held AgeandIncome data were required? Because C++ templates generate code specific for each data type statically (at compile time), the data types stored in the stack must remain static during program execution. Different code is generated for an Age or an Income stack, each able to hold only the respective data.
class Age { int years; public: Age(int y) { years = y; }; void printOn() { cout << years; };}; class Stack { Age* data[10]; int top; public: Stack( ) { top = -1; }; Stack( Age &) { }; void push( Age* anObject ) { top++; data[ top ] = anObject; } Age* pop() { return data[top--];}; void printOn() { for (int i = top; i >= 0; i-- ) data[i]->printOn(); } }; C++ Simple Stack Exercise 7 • What type of data can the Stack contain. • Accurately diagram the Stack object. • What is the program output? void main() { Stack *s1 = new Stack( ); s1->push(new Age(40)); s1->push(new Age(23)); s1->push(new Age(18)); s1->push(new Age(53)); s1->printOn(); s1->pop()->printOn();}
template <class MyObject> class Stack { MyObject* data[5]; int top; public: Stack() { top = -1; }; Stack( MyObject &) { }; void push( MyObject* anObject ) { top++; data[ top ] = anObject; } MyObject* pop() { return data[top--];}; void printOn( ) { for (int i = 0; i <= top; i++ ) data[i]->printOn(); } }; class Income { double dollars; public: Income(double d) { dollars = d; }; void printOn( ) { cout << dollars; };}; class Age { int years; public: Age(int y) { years = y; }; void printOn( ) { cout << years; }; }; void main() { Stack<Age> *s1 = new Stack<Age> ( ); Stack<Income> *s2 = new Stack< Income>( ); s1->push(new Age(40)); s1->push(new Age(30)); s2->push(new Income(80000.00)); s2->push(new Income(50000.00)); s1->printOn(); s2->printOn();} C++ Template Stack - Exercise 8 • Draw an accurate representation of the stack(s) for the execution above.
Exercise 8 Continued • With the instantiation:Stack<Age> *s1 = new Stack<Age> ( ); is the following legal? s1.push(new Income(15000)); • There are no syntax tricks such as a missing semicolon. Is the following valid? Explain. Stack<Age> *s1 = new Stack<Age> ( ); Stack<Income> *s2 = new Stack<Income>( ); Stack<Stack> *s3 = new Stack<Stack> ( ); • What is the implication of your answers to Questions 2 and 3 with regard to the generality of templates?
Java Generics • Eliminates need for casting when retrieving from generic container • As with casting, potentially unsafe operations allowed Non-Generic List myIntList = new LinkedList(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = (Integer) myIntList.iterator().next(); // 3 Generic List<Integer> myIntList = new LinkedList<Integer>(); // 1 myIntList.add(new Integer(0)); //2 Integer x = myIntList.iterator().next(); // 3
class Stack <MyObject> { MyObject data[]= (MyObject[]) new NewObject[5]; int top; Stack() { top = -1; } void push( MyObject anObject ) { top++; data[ top ] = anObject; } MyObject pop( ) { return data[ top-- ]; } void printOn( ) { System.out.print("Stack ["); for (int i = 0; i <= top; i++ ) ((NewObject)data[ i ]).printOn(); System.out.print(']'); } } abstract class NewObject extends Object { void printOn(){ }; }; class Age extendsNewObject { int years; Age(int y) { years = y; }; void printOn() { System.out.print( years+" "); } }; class Income extendsNewObject { double dollars; Income(double d) { dollars = d; }; public void printOn(){System.out.print(dollars+" "); } }; public class StackTest { public static void main(String args[]) { Stack <Age> s1 = new Stack<Age>(); Stack <Income> s2 = new Stack<Income>(); s1.push(new Age(30)); s1.push(new Age(20)); s2.push(new Income(50000.00)); s2.push(new Income(30000.00)); s1.printOn(); s2.printOn(); s2.pop().printOn(); s1.pop().printOn(); } } Java Template Stack
Generics Example Discussion • Following has a potentially unsafe cast, detected by compiler • Java arrays must be statically typed (i.e. at compile-time) • Generic array not allowed; new MyObject[5]; • MyObject can safely be instance of NewObject class Income extends NewObject class Stack <MyObject> { MyObject data[] =(MyObject[]) new NewObject[5]; ((NewObject)data[ i ]).printOn(); Stack <Income> s2 = new Stack<Income>();
Generics Example Discussion • MyObject not an instance of NewObject is allowed but potentially unsafe class Income extends NewObject class Stack <MyObject> { MyObject data [] =(MyObject[]) new NewObject[5]; void push( MyObject anObject ) { top++; data[ top ] = anObject; } Stack <Income> s2 = new Stack<Income>(); Stack <Stack> s3 = new Stack<Stack>(); // Compile warning // not type-safe s3.push(s2); // Run-time error // s2 not derived from NewObject
Exercise 9 • With the instantiation:Stack<Age> s1 = new Stack<Age> ( ); is the following legal? s1.push(new Income(15000)); • The following is valid syntax. Explain. Stack<Age> s1 = new Stack<Age> ( ); Stack<Stack> s3 = new Stack<Stack> ( ); s1.push(new Income(15000)); s3.push(s1); s1.printOn(); s3.printOn(); • The above execution fails at s3.push(s1); Why? • The above executes after changing the following. Does that make sense?MyObject data[]= (MyObject[]) new NewObject[5];MyObject data[]= (MyObject[]) new Object[5];
import java.util.Vector; public class SimpleStack { public static void main(String args[]) { Stack <Integer> a = new Stack <Integer>(); a.push(new Integer(-4)); a.push(new Integer(9)); System.out.println(a.pop()); Stack <Stack> b = new Stack <Stack>(); b.push( a ); System.out.println( b.pop().pop() ); } } class Stack <MyObject> { private Vector <MyObject> data=new Vector <MyObject>(); public void push ( MyObject o ) { data.add(o); } public MyObject pop() { return data.remove(data.size()-1); } } Generic Stack
Exercise 9.5 • What is the output? • What can be push’ed onto a Stack? • Can different objects be pushed onto the same Stack? • Does Stack allow a stack of stacks? • What is the difference between the two Java and the C++ implementations of generics? • Any advantages of one approach over the other?
C++/Java Generic Classes Compared • C++ • Generates specific code for each instance • Parameterized arrays allowed • MyObject* data[5]; • Determines methods to call at compile time • data[i]->printOn(); • Java - Version 1.5 only • Generates only one copy, erasing typing information • Parameterized arrays not allowed • MyObject data[]= new MyObject[5]; • Determines methods to call at runtime • ((NewObject)data[]).printOn(); • b.pop().pop()
Java Containers • Java – Object at top of object hierarchy • Define as container of Object • Store any class object since all subclass of Object • Retrieve as Object then castto appropriate class. • Can determine class using instanceof method.
abstract class MyObject extends Object { void printOn( ) { }; }; class Age extends MyObject { int years; Age(int y) { years = y; }; void printOn() { System.out.print(years + " ");} }; class Income extends MyObject { double dollars; Income(double d) { dollars = d; } void printOn(){ System.out.print(dollars+" "); } }; class Stack extends MyObject { MyObject data[ ]; int top; Stack( ) { data = new MyObject[10]; top = -1; } void push( MyObject anObject ) { data[ ++top ] = anObject; } MyObject pop( ) { return data[ top-- ]; } void printOn( ) { for (int i = 0; i <= top; i++ ) data[ i ].printOn( ); } } public class StackTest { public static void main(String a[ ]){ Stack s1 = new Stack(); Stack s2 = new Stack(); s1.push(new Age(40)); s1.push(new Age(30)); s2.push(new Income(80000.00)); s2.push(new Income(50000.00)); s2.push(s1); s1.printOn(); s2.printOn(); } } Java Stack
Exercise 10 • Diagram the class hierarchy. • Draw an accurate representation (similar to below) of the stack(s) for the given execution. • Would it be possible to push a Rectangle, Polygon or Stack onto one of the stacks? Why or why not? This is a little tricky. • Can the Stack printOn be recursive? Also a little tricky.
Java Generics & Inheritance • Java • MyObject serves as root of inheritance • Determines methods to call at runtime • The generics example using arrays is not type-safe, any MyObject can be stored in data[] causing run-time failure. • Generics compromised for legacy code compatibility.