Download Understanding Exercise 10: Java Type Erasure & Information Hiding with OOP and more Lecture notes Object Oriented Programming in PDF only on Docsity!
Concepts of Object-Oriented Programming
AS 2021
Exercise 10
Type Erasure, Templates and Information Hiding
December 3, 2021
Task 1
Consider the following Java method:
String concatenate(List<?> list) { String result = ""; String separator = ""; if (list instanceof List) { result = "String:"; separator = " "; } else if (list instanceof List) { result = "Integers:"; separator = "+"; } for (Object el : list) result = result + separator + el.toString(); return result; }
A) This program is rejected by the Java compiler. Why?
solution
The Oracle and the Open JDK compilers both produce these short errors:
illegal generic type for instanceof
illegal generic type for instanceof
The Eclipse compiler tries to be more helpful:
Cannot perform instanceof check against parameterized type
List. Use the form List<?> instead since further
generic type information will be erased at runtime
Cannot perform instanceof check against parameterized type
List. Use the form List<?> instead since further
generic type information will be erased at runtime
This happens because of type erasure in Java.
B) Using the advice given by the Eclipse Java compiler (replace List<...> with List<?>),
rewrite and compile the program. What are the results of executing the method passing each
of the following:
- A list of strings containing only one element "word"?
- A list of Integers containing only one element Integer(1)?
- A list of Objects containing only one element (initialized by new Object())?
solution
First of all, we follow the output of the compiler, and so we rewrite the method to:
String concatenate(List<?> list) { String result = ""; String separator = ""; if (list instanceof List<?>) { result = "String:"; separator = " "; } else if (list instanceof List<?>) { result = "Integers:"; separator = "+"; } for (Object el : list) result = result + separator + el.toString(); return result; }
The Java compiler will compile this program without any warning. The output of the
method is:
String: word
String: 1
String: java.lang.Object@3e25a
C) Is this behaviour consistent with what you would expect from the initial program? If not,
how can you fix it?
solution
No, in the original program we expected:
String: word
Integers:+
java.lang.Object@3e25a
We can try to fix it in the following way:
String concatenate(List<?> list) { String result = ""; String separator = ""; if (list.size() >= 1 ) if (list.get(0) instanceof String) { result = "Strings:"; separator = " "; } else if (list.get(0) instanceof Integer) {
5 class Ship<T extends Animal> { 6 public T content; 7 } 8 9 class Cage<T extends Mammal> { 10 public T content; 11 12 void takeFromShip(Ship other) { 13 this .content = other.content; 14 other.content = null ; 15 } 16 } 17 18 class Zoo<T extends Mammal> { 19 void swapTigers(Ship mammalShip, Cage tigerCage) { 20 Tiger tiger = tigerCage.content; 21 Cage tmpCage = new Cage(); 22 tmpCage.takeFromShip((Ship) mammalShip); 23 mammalShip.content = (T) tiger; 24 tigerCage.content = tmpCage.content; 25 } 26 }
A) List all the typecasts that the virtual machine will perform at runtime, when executing the
methods takeFromShip and swapTigers. For each cast, write at which line number in the
original program it is performed, and what expression is cast to which type. Do not optimize
away casts that are statically known to succeed.
solution
- At line 13 : (Mammal) other.content
- At line 20 : (Tiger) tigerCage.content
- At line 22 (Ship) mammalShip
- At line 23 : (Mammal) tiger
B) For each of the following two methods (from B.1 and B.2), write if they would compile
without errors if added to the class Cage. If they do not compile, briefly explain why.
B.
Cage[] getTigers( int number) { Cage[] cages = new Cage[number]; for ( int i = 0; i < number; i++) { cages[i] = new Cage(); cages[i].content = new Tiger(); } return cages; }
B.
int numCageFields() { Class cl = Cage. class ; return cl.getFields().length; }
solution
1. Compiler error: array of generic types not allowed.
2. Compiler error: class object of generic types not available, or the type parameter does
not respect the constraint of class Cage.
Task 3
A C++ template class can inherit from its template argument:
template class SomeClass : public T { ... }
A) Using this technique and given the following class definition
class Cell { public : virtual void setVal( int x) { x_ = x; } virtual int value() { return x_; } private : int x_; }
write two template classes that can be used as “mixins” for the class Cell:
- Doubling - doubles the value stored in the cell.
- Counting - counts the number of times the value of the cell was read.
Do not use multiple inheritance. It should be possible to use the classes like this:
auto c = new Doubling<Counting| >(); // instantiation c->setVal(5); c->value(); // returns 10 c->numRead(); // returns 1 |
solution
template class Doubling : public T { public : virtual void setVal( int x) override { T::setVal(x (^) * 2); } }
template class Counting : public T { public : virtual int value() override { ++numRead_; return T::value(); } int numRead() { return numRead_; } private : int numRead_; }
B) Describe how the instantiation above will look like.
x->value(); // returns 20
An advantage of the C++ solution is that we do not need to declare the base class that the
mix-ins extend. Thus it is possible to use them with different base classes as long as they
have matching virtual methods.
Task 4
The type correctness of a C++ template class is checked only when the template is instantiated.
This makes it difficult to develop templates modularly. We can try to make templates more
modular by extending C++ with a new way to declare type arguments:
template <T s_extends SomeClass> class TemplateClass {...}
Here T is the template argument and SomeClass is the name of a class which is an upper
type bound for T. A template defined in this way may only be instantiated with a class T
that is a structural subtype of SomeClass. Assume that the type checker checks such a
template definition without having any concrete instantiation, under the assumption that T is
a structural subtype of SomeClass.
This new feature is the only place where we introduce structural subtyping in C++, all other
subtype relations in the language remain nominal as usual. Assume in general for any subtyping
mode that method argument types are contravariant and method return types are covariant.
Also, assume that all the methods are public and virtual.
A) Provide a declaration of the Operation class such that the class Compose can be type-
checked before it is instantiated.
template <T s_extends Operation , U s_extends Operation> class Compose : public Operation { public : T* t; U* u; int compute( int x) { return t->compute(u->compute(x)); } }
solution
class Operation { int compute( int x); }
B) We also allow template parameters to occur as type arguments in upper bounds of the same
template:
template <T s_extends Bound> class TemplateClass{...}
The above limits the possibilities for T to only structural subtypes of Bound.
Consider the classes below:
class A : { void foo(A* a); }; class B : public A { B* bar(); };
class C : public B {};
template < class T> class FOO { void foo(T* t){...} };
template <T s_extends FOO> class X { ... };
template < class T> class BAR { T* bar(){...} };
template <T s_extends BAR> class Y { ... };
Which of the following instantiations typecheck:
X
X
Y
Y
Explain why each combination does or does not typecheck.
solution
X can be instantiated with both classes:
- B: B.foo(A) overrides FOO.foo(B)
- C: C.foo(A) overrides FOO.foo(C)
Y can be instantiated only with B:
- B: B* B.bar() overrides B* BAR.bar()
- C: B* C.bar() does not override C* BAR.bar(), therefore C is not a structural
subtype of BAR
C) As a bound we also allow the template that is being declared:
template <T s_extends X> class X { int foo(T* t) {...} }
Let the class A be:
class A {};
- Write an implementation of the body of the foo method of X such that X typechecks with
the bound above (T s_extends X) and also typechecks if the bound is changed to
T s_extends A.
- Write an implementation of the body of the foo method of X such that X typechecks with
the bound above (T s_extends X), but does not typecheck if the bound is changed
to T s_extends A.
- Write a class B that can be used to instantiate X.
1 package A; 2 3 public abstract class Person { 4 _______ int tickets = 0; 5 _______ final int maxTickets = 3; 6 7 _______ abstract void buy( int t); 8 } 9 10 public class Buyer extends Person { 11 _______ void inc( int t) { 12 if ( this .tickets + t <= this .maxTickets) this .tickets += t; 13 } 14 _______ void buy( int t) { if (t >= 0) inc(t); } 15 } 16 17 18 19 package B; 20 import A.*; 21 22 public class SmartBuyer extends Buyer { 23 _______ void inc( int t) { this .tickets += t; } 24 } 25 26 public class Main { 27 public static void main(String args[]) { 28 Buyer b = new SmartBuyer(); 29 b.buy(9); 30 } 31 }
Provide the most restrictive access modifiers for the fields tickets and maxTickets and the
methods inc() and buy() such that the program is still accepted by the compiler.
solution
The field tickets must be protected (since we need to access it from the class
SmartBuyer which belongs to another package). The field maxTickets must have a
default access modifier (because we need to access it from the class Buyer which be-
longs to the same package). The method inc() can be declared private in both Buyer
and SmartBuyer. The method buy() in class Person must have a default access modi-
fier (because abstract methods cannot be private), while the method buy() in class Buyer
must be public (because we need to access it from the class Main which belongs to another
package and is not a subclass of Buyer).