Understanding Exercise 10: Java Type Erasure & Information Hiding with OOP, Lecture notes of Object Oriented Programming

The concepts of type erasure, templates, and information hiding in Java through an exercise focused on the 'concatenate' method. The exercise highlights the challenges of performing instanceof checks on generic types due to type erasure and proposes solutions using wildcards and method overloading. Additionally, it discusses the limitations of using raw types and the implications for method overloading.

Typology: Lecture notes

2021/2022

Uploaded on 08/05/2022

nguyen_99
nguyen_99 🇻🇳

4.2

(80)

1K documents

1 / 10

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
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<String>) {
result = "String:";
separator = " ";
}
else if(list instanceof List<Integer>) {
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<String>. Use the form List<?> instead since further
generic type information will be erased at runtime
Cannot perform instanceof check against parameterized type
List<Integer>. Use the form List<?> instead since further
generic type information will be erased at runtime
This happens because of type erasure in Java.
pf3
pf4
pf5
pf8
pf9
pfa

Partial preview of the text

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).