Skip to content
Snippets Groups Projects
Commit e7e7d788 authored by Juuso Rytilahti's avatar Juuso Rytilahti
Browse files

Added part 4 materials

parent f3059748
No related branches found
No related tags found
No related merge requests found
Showing
with 1996 additions and 0 deletions
# Object Properties
Today's Topics:
- Basic properties of objects
- Initialization
- Equivalence
- Copying
- Hash
- Comparison
## Basic properties of objects
In object-oriented languages, there is often a parent class that contains methods common to all objects.
- In Java the parent class is `java.lang.Object` and it has the following methods:
- **clone()**
- **equals()**
- **getClass()**
- **hashCode()**
- **notify()** / **notifyAll()** / **wait()**
- **toString()**
- Canonical Entity (Venners 1998)
- has implementation of basic functionality.
- recommended target state unless there is a reason to deviate.
- ideas: entity should be copyable or immutable object, comparability (equals), serialization.
A few comments:
- `Object` part of the class hierarchy.
- `toString` familiar from previous material. Used to display object in String format.
- Next we will look at these basic properties: `clone`, `equals`, `getClass` & `hashCode`
- `notify()`, `notifyAll()`, `wait()` relate to thread programming and will not be covered on this course.
Methods later identified as problematic:
- `finalize()`
Legacy way of cleaning up when the object is no longer used. This method will be removed from Java and shouldn't be studied.
→ now these cases are handled with Try-with-resources and other solutions.
- `Serializable`
Way to transform objects to bits. Oracle has said in an article that this was not a good idea.
→ Now serializing is done more efficiently with separate libraries & `record`
- `clone()`
This is a borderline case. Works and can be used, but there are many articles that higlight problems in this method.
Let's look at the following basic object properties in Java that, according to the use case, you may need to write an implementation:
- Initialization
- Equivalence `equals`
- Cloning `clone`
- Hashing `hashCode`
- Comparing `compareTo`
## Initialization
Task:
- Need to create objects from classes.
In Java, by default, objects can always be created in some context. (can be limited by e.g. `private`)
### Constructor
Signature:
```java
class Number {
private int a;
Number() { a = 2; } // #1
public Number(int x) { a = x; } // #2
protected Number(String x) { a = Integer.parseInt(x); } // #3
}
```
There is three constructors listed in the code example. First can be called from the sam package, second can be called everywhere and third also from different packages with inheritance.
- The signature differs from method signatures: no return type/separate name, only class name.
- Call: not directly, but with `new` operator: `new Number()`
- Default visibility is **package** (works inside the package as public, not visible outside the package)
- Can be overloaded (= alternative initializations)
- Contractually: sets the class invariant into effect.
### Default constructor
If the class doesn't have any defined constructors like below...
```java
class Example {
private int a;
}
```
… The compiler implicitly adds a default constructor to the class:
```java
class Example {
private int a;
public Example() {}
}
```
- Initializes variables as the default value of the data type (e.g. integers = 0, reference =`null`).
- Requires parent class constructor to have no parameters (e.g. `Object`).
### Inheritance
The constructor is not inherited! It must be defined in every class.
```java
class Parent {
Parent() { } // not inherited!
}
class Child extends Parent {
Child(int a) { }
}
class Example {
Child a = new Child(); // error!
}
```
Above code does not work because Child class constructor needs a parameter and none are provided when called. Parent class constructor isn't in use because it isn't inherited. Program doesn't even compile with this implementation.
What problems does the following code have?
```java
public class A {
final protected int a;
public A(int b) { a = b; }
}
public final class B extends A {
public int aa() { return a; }
}
void main() { System.out.println( (new B(1)).aa() ); }
```
The example shows another wrong use of a constructor that was not inherited (the previous code would also not even compile). Must add following line (define the constructor) to class B:
```java
public B(int b) { super(b); } // !!
```
### Calling other constructors
The 1st statement of the constructor must be a call to the parent class (`super()`) or another constructor (`this()`):
This rule is changing in java but different implementations pose risks for the class invariant.
```java
// defined in the standard library
public class Object { public Object() { ... } ... }
class Parent extends Object { Parent(int a) { super(); } }
class Child extends Parent { Child() { super(42); } }
class ChildsChild extends Child {
ChildsChild(int x) { /* super(); */ }
ChildsChild() { this(42); }
}
```
Even if it is not written, `super()` is implicitly called (this is also the case in the default constructor)
## Equivalence
Problems where equivalence is used:
1) Are the objects at references **a** and **b** "same"?
2) Does the collection include item **x**?
- What does "same" mean?
- What is the relationship between inclusion and sameness?
Java assumes the following:
- Universal equivalence: all objects are always comparable.
- The programmer determines which features make objects the same.
- Collections are based on basic features (here: equivalence).
### equals()
Method signature:
```java
public boolean equals(Object obj)
```
- Recommendation: always use `@Override`
- Note! Type always `Object obj` for variable that is compared to.
- Default implementation: compares are the references identical. Which means are the objects exactly the same (are they stored in the same place in memory, can be also marked `==`).
Below is a table of different kinds of sameness.
![](images/part-13/equivalence.PNG)
Let's look at sameness in a picture format. In the next pictures boxes represent references (variables) and the outlines represent objects.
This represents a situation inside a Java virtual machine.
![](images/part-13/eq1.PNG)
Here colored variabes are identical. They both are references to exact same object.
![](images/part-13/eq2.PNG)
Here colored variabes have a shallow equivalence. They are references to objects that have identical member variables. Member variables that are reference types are references to identical variables and boolean type members have the same values.
![](images/part-13/eq3.PNG)
Here colored variabes have a deep equivalence. They all have references to similar data. This means that when inspected deep enough all data is the same or seems to be the same.
![](images/part-13/eq4.PNG)
Use is dependent on the needs of the user. The less we need to compare objects is always faster. But if we want to compare variables inside the objects it may be more beneficial to use deeper equivalence.
### Equivalence as a relation
The correct equals relation must have:
1) Reflexivity:
x.equals(x)
2) Symmetry:
x.equals(y) <==> y.equals(x)
3) Transitivity:
x.equals(y) & y.equals(z) ⇒ x.equals(z)
4) Consistency:
If the data relating to (surface) sameness is not changed, the comparison must return consistently true or false.
5) Null nonequality:
x.equals(null) == false
6) The routine must work regardless of the type of objects; `equals` has `Object` in signature.
### Example: equals-implementation
In this example, the conditions of sameness are numbered in comments as above. This example is an example of a deep equivalence comparison.
```java
class Container {
int val1;
Object val2;
int[] vals;
@Override public boolean equals(Object other) { // 6) for all
if (other == null) return false; // 5) nullnonequality
if (other == this) return true; // 1) reflexivity
if (other instanceof Container t) // 6)
return t.val1 == val1 && t.val2.equals(val2) && Arrays.equals(vals, t.vals); // 2,3,4)
return false; // 6)
}
}
```
### Equals and collections
The functionality of Java collection classes (see java.util.Collection) is based on the correct definition of `equals`.
- E.g. `elements` = items in a collection → contains, can be defined:
```java
boolean contains(Object a) {
for(var b: elements)
if (a.equals(b)) return true;
return false;
}
```
- Consequence: Let **a** & **b** be the same, add **a** to collection **k****k** contains **b**. A and b don't have to be the same object, only same when compared with equals.
- Different collections have different optimizations based on the above. relational rules.
## Copying
Problems:
1) How to create a copy of an object?
2) How to create a copy of a collection?
There are a couple of options in Java:
- Copy constructor
- Immutable data structures
- Cloning (`Object.clone()`)
Analogy: degree of copying ~ degree of sameness.
### Cloning: clone() and Clonable
Interface signature:
```java
interface Cloneable { }
```
- Does not contain anything (dealt with specifically by JVM).
- Interface implemented by classes using cloning.
Method signature:
```java
protected Object clone() throws CloneNotSupportedException
```
- Methods default value:
```java
if (this instanceof Cloneable)
/* JVM magic */
else throw CloneNotSupportedException()
```
### Cloning usage
- Implement `Cloneable` interface (`implements Cloneable`)
- `clone()` by default with **protected** visibility
- Change to **public** if necessary.
- Call `super.clone()` inherited from `Object`
- Provides a surface-identical copy of an object.
- Recursively clones members that require deep copy.
- Return type of `clone()` is `Object` by default.
- You can change the return value type to the exact class type (covariance).
- Note! Subclasses are also clonable by default.
- Can be blocked at runtime by raising `CloneNotSupportedException`
### Example: Cloning
```java
class Bot { String name; }
class ChatBot extends Bot { String[] languages; }
class C3PO extends ChatBot implements Cloneable {
public C3PO clone() throws CloneNotSupportedException {
return (C3PO)super.clone();
}
}
```
```java
class Jedi implements Cloneable {
C3PO friend;
public Jedi clone() throws CloneNotSupportedException {
Jedi j = (Jedi)super.clone();
j.friend = friend.clone();
return j;
}
}
```
### Problems / limitations of cloning
- Critics include Joshua Bloch and Doug Lea.
- Is the class invariant valid in the copy after cloning?
- `final`-marked members locked for `clone()` implementation.
- The `Cloneable` interface does not guarantee that you can call `clone()`.
- Cannot change the type of the copy from the original.
- Once publicly revealed (`public`), cloning can no longer be prevented in subclasses during runtime.
- Implementation may require forced type conversions.
### Copy constructor
- A constructor that creates a new object, copying the structure of the object given as a parameter.
- Class invariant easy to verify; It's like creating a completely new object.
- Can not be called polymorphicly, the exact type of copy may have to be known.
- Alternative is to define the polymorphic method `.copy()`.
```java
class Point {
int x, y;
Point(Point other) {
x = other.x; y = other.y;
}
Point copy() { return new Point(x, y); }
}
```
### Immutable data structures
- If the class is immutable (recursively `final` or a `record` class), does it make sense to copy at all?
- Partial copying: otherwise the same parent object, but some fields are changed.
- Planned: with-syntax (JEP 468: Derived Record Creation) that would look something like this:
```java
record Point(int x, int y, int z) { }
void main() {
var p1 = new Point(1, 2, 3);
var p2 = p1 with { x = 0; };
}
```
### Copying collections
- Copy constructor often offered in collection classes, e.g. `new ArrayList<>(oldCollection)`.
- Tables e.g. `Arrays.copy(array, array.length)`
```java
void main() {
var list = List.of(1, 2, 3);
var copy = new ArrayList<>(list);
}
```
## Hash
Problem:
- How to specify the place of an object in the hash table?
In Java, designed as follows:
- In the standard library, hash table based collection type.
- The hash function required by the hash table is
a basic property for every object (`Object.hashCode`).
![](/images/part-13/hash.PNG)
### hashCode
Method signature:
```java
public int hashCode()
```
- The default implementation is based on the memory address of the object creation phase: `System.identityHashCode(this)`
- As with the definition of sameness, the following must apply: `a.equals(b) ⇒ a.hashCode() == b.hashCode()`
- You want to fill the hash table as evenly as possible.
- Equal distribution of hashes (within `int`)
→ Also (hash **%** data structure size) even
- How to calculate?
- Auxiliary method: `Objects.hash(member1, member2, ...)` derives from the value of members.
- Sometimes it is necessary to calculate yourself (e.g. calculation too heavy). This is out of scope of this course.
- In `enum` and `record` hash is pre-calculated.
- The hash is not the object's memory address or a unique value identifying the object
- Java is a high-level language → memory address abstracted.
- Garbage collection can even move/merge objects in memory.
- Possible `hashCode()` values 2<sup>32</sup> pcs, and addresses 2<sup>64</sup>.
- Two objects can have the same `hashCode()`,if need to identify, create an **id** field for identification.
### Example: Hash
```java
class Player {
public final Location location;
public final Job job;
@Override public int hashCode() {
// recursively calls members hashCode()
return Objects.hash(location, job);
}
public Player(Location l, Job j) {
this.location = l;
this.job = j;
}
}
```
In the case of the record, `Objects.hash()` is implicitly called for all fields:
```java
record Player(
Location location,
Job job
) {
/*
@Override public int hashCode() {
return Objects.hash(location, job);
}
*/
}
```
### Hash and collections
- Usage: necessary in data structures where the hash value is calculated, name starts with `Hash`, e.g. `HashMap`, `HashSet`.
- What would happen if a.equals(b) ⇒ a.hashCode() != b.hashCode() ?
- Same member would not be found because we would look in different places in the hash table.
- At worst, values added to the collection would not be found!
## Comparison
Problem:
- Is object **a** greater than **b**?
- Does object **a** precede object **b** in collection?
- What is the greatest/smallest/next/previous item in the collection?
- How to organize a collection?
In Java, order comparison is
- optional additional property of the object (= natural order, `Comparable.compareTo`) or a separate order (`Comparator.compare`).
- Integrated into ordered collection types.
### Comparable<T> interface
Interface signature:
```java
interface Comparable<T> {
int compareTo(T other);
}
```
- `Comparable` interface method `compareTo` defines the *natural order* of the object.
- One of the objects to be compared (the class implementing the interface) is the object itself.
- Another is of abstract type **"T"**. In practice, **T** is always the class of an object or a definition to which the object includes.
- Enum: natural order by default member listing order
### Comparator<T> interface
Interface signature:
```java
interface Comparator<T> {
int compare(T one, T other)
}
```
- The object of the class implementing the `Comparator` interface acts as an external comparator.
- Compares two **T**-shaped objects.
- Many different orders can be implemented in addition to/instead of the natural order.
- In addition to the objects being compared, the order can be affected by the state of the comparator! (e.g. a `boolean` variable can be used to select ascending/descending order)
- `Comparator.naturalOrder()`: creates a `Comparator` from `Comparable`.
### Comparable<T> and Comparator<T>
The following apply to both:
- Compare two objects of the same type and return information about the order of magnitude: `a.compareTo(b)` or `compare(a, b)`
- a negative integer if **a** is less than **b**
- zero, if equal
- a positive integer if **a** is greater than **b**
- `ClassCastException` if **a** and **b** are of incompatible types
- Note! `a.compareTo(b)==0` <==> `a.equals(b)`
- methods can be used for the same purpose
- Java does not require this, but the requirement is justified and reasonable
### Comparison and collections
- The Java standard library has a set of ordered collections, eg. name starts with `Tree`.
- In addition to comparison, the collections rely on the correct definition of sameness.
- Three ways to choose an order
- natural order of items (preserved)
- order assigned to the collection (preserved)
- organizing the collection in an external order (redo if necessary)
### Example: Comparator<T>
For an example, let's define a set of elements:
```java
enum Element {
Hydrogen(1, "H"), Helium(2, "He"),
Lithium(3, "Li"), Carbon(6, "C");
Element(Integer i, String b) { id = i; symbol = b; }
Integer id;
String symbol;
Integer id() { return id; }
String symbol() { return symbol; }
}
```
There are many ways to express `Comparator` in Java:
```java
var id = new Comparator<Element>() {
@Override public int compare(Element a, Element b) {
return a.id - b.id;
}
}; // anonymous class
// function literal &
Comparator<Element> id2 = (a, b) -> a.id - b.id;
// function literal + delegate to Integer.compareTo
Comparator<Element> id3 = (a, b) -> a.id.compareTo(b.id);
// uses id()-method
var id4 = Comparator.comparing(Element::id);
```
The following demonstrates the use of Comparator-class auxiliary methods:
```java
var id = Comparator.comparing(Element::id);
var symbol = Comparator.comparing(Element::symbol);
// values() creates a new table
var elements = Element.values();
Arrays.sort(elements, id);
Arrays.sort(elements, id.reversed);
// primary and secondary order (cf. databases / excel)
Arrays.sort(elements, id.thenComparing(symbol));
// Element = enum -> member listing order
Arrays.sort(elements, Comparator.naturalOrder());
```
The following demonstrates the use of Comparator-class auxiliary methods:
```java
var id = Comparator.comparing(Element::id);
var symbol = Comparator.comparing(Element::symbol);
// a collection that has order of addition
var list = new ArrayList<Element>(
Arrays.asList(Element.values())
);
// arranging in a separately defined order
list.sort(id);
// collection that has a defined order
var tree = new TreeSet<>(symbol);
tree.addAll(Arrays.asList(Element.values()));
```
### Example: Comparable<T>
For the example, let's define a category representing a person:
```java
record Person(
String name,
int yearOfBirth,
String address
) {}
```
Let's compare by name, year of birth and finally, address:
```java
record Person(...) implements Comparable<Person> {
@Override public int compareTo(Person other) {
int result = this.name.compareTo(other.name);
if (result != 0) return result;
// yearOfBirth = int, compareTo doesn't work
if (this.yearOfBirth != other.yearOfBirth)
return (this.yearOfBirth < other.yearOfBirth) ? -1 : 1;
else
return this.address.compareTo(other.address);
}
}
```
You can try to optimize your code with gimmicks, but usually readability suffers...
```java
record Person(...) implements Comparable<Person> {
@Override public int compareTo(Person other) {
int nameDiff = name.compareTo(other.name),
yearDiff = yearOfBirth - other.yearOfBirth;
return
nameDiff != 0 ? nameDiff : yearDiff != 0 ? yearDiff :
address.compareTo(other.address);
}
}
```
Using the Comparator class (record includes get methods)
```java
record Person(...) implements Comparable<Person> {
@Override public int compareTo(Person name) {
return Comparator.comparing(Person::name)
.thenComparing(Comparator.comparing(Person::yearOfBirth))
.thenComparing(Comparator.comparing(Person::address))
.compare(this, name);
}
}
```
Next up: [Part 4.2 - Data Structures and Algorithms](Part%204.2%20-%20Data%20Structures%20and%20Algorithms.md)
\ No newline at end of file
# Data Structures and Algorithms
## Today's Topics:
- Java Collections
- Types of Collections
- Collection Hierarchy
- Concrete Collection Classes
- Tips for Using Collections
## Java Collections
Next we are going to look at Java collections. Let's first look at collection and container classes.
Collection and Container classes are Java's standard framework for composite data. They are basic data types for everyday use that aim for [simplicity](https://docs.oracle.com/javase/8/docs/technotes/guides/collections/designfaq.html).
- Structure:
- Interfaces: description of abstract data types
- Implementations: concrete implementations of data types
- Algorithms: helper methods and classes for data manipulation
- History of Java Collections:
- Introduced in Java 1.2 s("Java 2.0" / Java 2 Platform)
- Java 5: generics, **java.util.concurrent, for(X x: iterable)**
- Java 6-7: additional methods, interface rationalization
- Java 8: **Stream**, **Optional**, lambdas
- Java 9-20: more classes and helper methods (e.g., static **.of** methods)
- Java 21: **SequencedCollection** (well-defined order handling)
## Collection types
One could say that there are two types of Collections in Java, one containing only one type of data, where map contains key-value pairs (two types of data)
![](images/part-14/Collection_types.PNG)
Below is a breakdown of Collection class:
- **Collection\<E>**
- A container holding homogeneous data of type E.
- **List\<E>**
- Maintains insertion order. Elements have recursively defined successors.
- **Queue\<E>**
- A queue-like container. Elements are added at one end, removed from the other.
- **Set\<E>**
- A set of elements. Key feature: cannot contain duplicates.
![](images/part-14/Collection_types2_inverted.PNG)
- **Map<K, V>**
- A general description of key-value pairs.
- Contrast: **Collection**-based data structures are homogeneous.
## Collection Hierarchy
Top level of the collection hierarchy with helper classes:
![](images/part-14/Collection_hierarchies.PNG)
Here is also a chart displaying some of the concrete classes.
![](images/part-14/Collection_hierarchies2.PNG)
### Collection: collection
Here is an example of interface of an collection:
```java
public interface Collection<E> extends Iterable<E> {
int size(); // size
boolean isEmpty(); // size == 0?
boolean contains(Object o); // Does it contain the object o?
boolean containsAll(Collection<?> c); // for collection c
Iterator<E> iterator(); // iterating
Object[] toArray(); // array ops (operations)
<T> T[] toArray(T[] a);
void clear(); // Optional
boolean add(E o); // Optional
boolean addAll(Collection<? extends E> c); // Optional
boolean retainAll(Collection<?> c); // Optional
boolean remove(Object o); // Optional
boolean removeAll(Collection<?> c); // Optional
default boolean removeIf(Predicate<? super E> f); // Optional
default Stream<E> stream(); // Stream ops
default Stream<E> parallelStream();
default Spliterator<E> spliterator();
```
### Collections: Map<K, V>
- Elements defined pair-wise: each key K points to a value V.
- K keys form a set (Set\<K>), values a collection (Collection\<V>)
- Note: does not inherit from Collection\<E>!
- Also note how instead of add Map uses a `put` and `get`.
```java
public interface Map<K, V> {
V put(K key, V value); // Basic ops.
V get(Object key); // Note! Object
V remove(Object key); // Note! Object
default V putIfAbsent(K key, V value);
default V getOrDefault(Object key, V defaultValue);
default boolean remove(Object key, Object value);
default V replace(K key, V value);
default boolean replace(K key, V oldValue, V newValue);
boolean containsKey(Object key);
boolean containsValue(Object value);
int size();
boolean isEmpty();
void clear(); // Collection ops
void putAll(Map<? extends K,? extends V> t);
Set<K> keySet(); // Views to collections
Collection<V> values();
Set<Map.Entry<K,V>> entrySet();
... // functional interfaces
interface Entry<K,V> { // internal Interface, models a
K getKey(); // key-value pair
V getValue();
V setValue(V value);
}
}
```
### Collections: Iterator
Iterator provides a way to traverse through the data in an ordered sequence. As you can see the interface is quite simple:
```java
interface Iterator<E> {
boolean hasNext();
E next();
default void remove() { throw new UnsupportedOperationException(); }
}
```
```java
interface Iterable<T> {
default void forEach(Consumer<? super T> action);
Iterator<T> iterator();
}
```
Older way to use iterator:
```java
for(var elem: collection) { ... }
for(var i = c.iterator(); i.hasNext();) { Object o = i.next(); ... }
```
The older way can still be useful, when using an old fashioned `for` loop instead of e.g. `forEach` the iteraring can be stopped the index (`i`) stored and the iteration can be continued later from the same position.
### Collections: Streams
Stream provides a way to process the information in the collection in an aggregated and incremental way. It is either sequential or [parallel](https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html) processing (more efficient). Unfortunately functional approach is currentlly [slower](https://openjdk.org/jeps/8139731).
Example of how stream can be used:
First, let's create classes `Color` and `Creature`
```java
enum Color { Blue, Red, Yellow, Green }
record Creature(Color color, int weight)
```
Then let's iterate through the list using a stream, remove all other than the red creatures and count their total weight.
```java
import java.util.*;
int redWeightSum() {
return List.of(new Creature(Color.Red, 5), new Eliö(Color.Blue, 4))
.stream()
.filter(e -> e.color() == Color.Red)
.mapToInt(Creature::weight).sum();
}
```
Quite often using streams increases the code readability, but as stated above, it also increases overhead. This is good to keep in mind if there is ever a performance problems in the back-end.
### Concrete collection classes
Let’s take a look at some collections implementations
![](images/part-14/Collection_hierarchies2.PNG)
### Concrete collection classes
Next, lets look through a couple concrete collection classes in Java.
- Lists (**List**)
- **LinkedList**: linked list
- **ArrayList**: provides both list and array-like access (one of the most commonly used classes)
- Queues (**Queue**)
- **PriorityQueue**: a queue with a priority order
- **ArrayDeque**: supports manipulating both ends of the queue
- Sets (**Set**)
- **HashSet**: based on a hash table, fast basic operations (**add**, **remove**, **contains**, **size**)
- **TreeSet**: ordered, rather speedy basic operations (**add**, **remove**, **contains**)
- **EnumSet**: ordered, for enums, really efficient
- Maps (**Map**)
- **HashMap**: the hash value determines the “bucket”, fast basic operations (**get**, **put**)
- **TreeMap**: ordered, rather speedy basic operations (**get**, **put**, **remove**, **constainsKey**)
- **EnumMap**: ordered, for enums, really efficient
- Notice the similarity: **.keySet(): Map -> Set**
- **hashCode** ⇔ HashXXX
- **Comparable** / **Comparator** ⇔ TreeXXX
- **enum** ⇔ EnumXXX
## Backing storage type of different containers
| | Set | List | Queue | Map |
|------------------------- |--------------- |------------ |------------ |--------------- |
| Hash table | HashSet | - | - | Hashmap |
| Custom table | - | ArrayList | ArrayDeque | - |
| Red-black tree | - | - | - | TreeMap |
| Linked list | TreeSet | LinkedList | LinkedList | - |
| Hash table+ linked list | LinkedHashSet | - | - | LinkedHashMap |
- [Overview of the collections](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/doc-files/coll-overview.html)
- [Collection API and design](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/doc-files/coll-index.html)
- Deque = double-ended queue
## Properties of the collections
The different classes can also be classified using their properties:
![](images/part-14/Properties_of_collections.PNG)
## Collections class
- Utility class methods, among other things:
- copying
- shuffling
- sorting
- filling
- search for the minimum and
- maximum values
- Wrappers
- synchronization wrapper
- for multi-thread programming...
- unmodifiable wrapper
- multi-thread programming, contracts, …
- checked wrapper
- adds runtime type checks (generics can be worked around :-/)
## Tips for using Collections
Here are some tips on how to use collections:
- Favor the use of collection interfaces as input instead of concrete implementations.
- As a return value, specify the concrete implementation type if possible.
- Dynamic binding in Java is pretty cheap resource-wise, but not free.
- Ensure the consistency of **equals**/**hashCode**/**compareTo**.
- Remember immutable data structures.
- Synchronization is important in multithreaded programming.
- **UnsupportedOperationException**: Do not handle. It indicates a conceptual error/error in the thinking!
## Using collections: multi-threading
Here is an example displaying benefits of multi-threading and parallelization:
*“In each run, n threads concurrently executed a tight loop where they retrieved random key values values
from either a Hashtable or a ConcurrentHashMap, with 80 % of the failed retrievals performing a put()
operation and 1 % of the successful retrievals performing a remove(). Tests were performed on a
dual-processor Xeon system running Linux. The data shows run time in millisecs for 10M iterations,
normalized to the 1-thread case for ConcurrentHashMap.”*
![](images/part-14/threadprogramming.PNG)
## Example: Extending Collection Classes
- Extending functionality
- Refer to the collection classes' javadoc.
- Understand the various interfaces.
- Intermediate interfaces, helper classes - **Iterator**, **Stream**, etc.
- Does an existing collection implement part of the functionality?
- Boldly implement your own collection classes.
Example: Implement a ring buffer
- The collection can hold X elements, after which adding a new element always overwrites the oldest element.
- Consider how to make the collection operate without memory allocations after initialization (Java allocates memory with the **new** keyword).
- Example: Implement a mapping that duplicates values with respect to the mirror image of the key
- **Map<Integer, X>**, where each key-value pair **(Y, X)** duplicates to position **(-Y, X)**.
Next up: [Part 4.3 - Data types and variance](Part%204.3%20-%20Data%20types%20and%20variance.md)
# Data types and variance
Todays topics
- Data types and variance
- Subtyping
- Liskov substitution principle
- Variance
- Variance rules
We have covered object-oriented programming through the use and behavior of objects. Now we are going more to a meta-level where we are speaking of types that cover the different objects and classes.
## Subtyping
Subtyping is a feature in languages that uses types and hierarchical type system where you can form a relation between two different types. Some languages (for example functional or low level languages) don't have typing mechanisms ans thus no subtyping.
- Syntax: **Subtype <∶ Overtype**
- Binary relation <∶
- reflexive (**A<∶A**)
- transitive (**A <∶ B** and **B <∶ C****A <∶ C**)
- Java links subtyping **A <∶ B** strictly to the inheritance relationship of classes:
**class A extends B**
- Same also for interface classes: `implements` / `extends`.
- Note! Also objects and function literals of the anonymous class
## Liskov substitution principle
Barbara Liskov (1987):
"What is wanted here is something like the following substitution property: If for each object **o<sub>1</sub>** of type **S** there is an object **o<sub>2</sub>** of type **T** such that for all programs **P** defined in terms of **T**, the behavior of **P** is unchanged when **o<sub>1</sub>** is substituted for **o<sub>2</sub>**, then **S** is a subtype of **T**."
Describes the mechanism of implementation of **S** < **T** based on the desired **behavior** when **o<sub>1</sub>** and **O<sub>2</sub>** are objects of **S** and **T**.
The described situation occurs in connection with polymorphism, eg. when a method defined by a certain signature deals with objects of the subclasses of the classes it lists.
## Subtyping and inheritance
How is the type theory described above used correctly?
- Subtypes behave according to their supertype definitions.
- The inheritor is a subtype of the class it inherited: **Subclass <∶ Superclass**
- The inheritor follows the definitions of methods of the class it has inherited.
- Automatically implemented for methods that are not overriden.
- Definitions of methods that are overriden are compatible (Java quarantees for signature).
- Design by contract defines a more specific set od rules for the pre- and post-conditions and class invariant in inheritance.
## Consequences of the substitution principle
The relation between superclass and subclass is not symmetric
- **Employee** may replace **Person**.
- **Person** cannot replace **Employee**.
For example, every employee is a person by every person may not be an employee.
Superclass cannot be aware of
- what classes are (or will be) inherited from it (except `final` / `sealed class`, `record` & `enum`)
- what (dynamically bound) features they override.
An operation can be invoked via a supertype reference
- without having exact knowledge of the location of the implementation (dynamic binding is always applied, excl. `final` / `private` / `static`)
- Thus, the call mechanism is separated from the operation being executed.
## Variance
**Order of types** (≤): A partially ordered set of all subtype relations described by the program definition.
E.g. `{ Object <: Top, Upper <: Ali, Object <: Ali, ... }`
**Variance** describes the order of the two types in relation to the prevailing order of types (≤).
Determines the compatibility of types, e.g. when replacing a method.
Next, let's look at the applications of variance rules that are consistent with with the principle of substitution.
## Covariance
**Covariance** refers to a type rule that preserves the order of types.
In the next example, **GoodSolution <∶ Solution**
Covariance is applied to the type of the return value.
The post-condition is thus tightened; a smaller set for selecting the type of a return value:
**{Solution, GoodSolution} ⊆ {GoodSolution}**
```java
interface Solution { String giveSolution(); }
interface GoodSolution extends Solution { String reason(); }
class Student {
Solution doExcercise(Excercise e) { ... }
}
class GreatStudent extends Student {
@Override GoodSolution doExcercise(Excercise e) { ... }
}
```
Covariance tightens the post-condition. The post-condition of the agreement will be specified by adding new subconditions with &&-connector.
```java
interface Solution { String giveSolution(); }
interface GoodSolution extends Solution { String reason(); }
class Student {
/* @.post RESULT != null */
Solution doExcercise(Excercise e) { ... }
}
class GreatStudent extends Student {
/* @.post RESULT != null && RESULT.giveSolution().length > 100 */
@Override GoodSolution doExcercise(Excercise e) { ... }
}
```
## Countervariance
**Contravariance** refers to a type rule that reverses the order of types.
In the example, **EasyExcercise <∶ Excercise**
**In override the type of parameters cannot change in Java**, but logically in pairs contravariance of the corresponding parameters would be justified.
The pre-condition would therefore be loosened. Objects from a wider range of classes would be accepted.
```java
interface Exercise { String description(); }
interface EasyExcercise extends Excercise { String hint(); }
class Student {
Solution doExercise(EasyExercise e) { ... }
}
class GreatStudent extends Student {
@Override Solution doExercise(Exercise e) { ... }
}
```
The pre-condition clause `instanceof` accepts a wider range of types: **{EasyExercise} ⊇ {Exercise, EasyExercise}**
```java
interface Exercise { String description(); }
interface EasyExcercise extends Excercise { String hint(); }
class Student {
/* @.pre e instanceOf EasyExcercise */
Solution doExercise(EasyExercise e) { ... }
}
class GreatStudent extends Student {
/* @.pre e instanceOf Exercise (= always true) */
@Override Solution doExercise(Exercise e) { ... }
}
```
## Invariance
**Invariance** refers to a type rule in which the type does not change.
In the next example invariance is applied to `towho` type of the `doWork` parameter:
```java
class Employer {}
abstract class Employee {
abstract void doWork(Employer towho);
}
class OfficeWorker extends Employee {
@Override void doWork(Employer towho) { ... }
}
```
- Note! In Java, invariance is always applied to parameters when overriding.
- The use of `@Override` is always recommended, otherwise it is easy to mistakenly overload instead of override.
## Variance rules
Override
- Parameters: invariance (in Java!)
- Return value: covariance
- Exceptions: covariance (only same/subtypes can be raised)
Note! also via inclusion, **A <∶ B ⇒ E(A) ⊆ E(B)**, i.e. can not raise new exceptions that are not covered in the superclass declaration!
- Design by contract (inclusion relation):
- Pre-conditions: contravariance (looser)
- Post-conditions: covariance (tighter)
- Class invariant: covariance (tighter)
- Arrays: covariance (problematic)
- Assignment: covariance (setting to more general type)
• Visibility attributes: contravariance (looser)
Below is an overview of different types of variance.
![](images/part-15/invariance.PNG)
Next up: [Part 4.4 - Generics](Part%204.4%20-%20Generics.md)
\ No newline at end of file
# Generics
## Today's Topics:
- Generics
- Motivating example
- Generics and polymorphism
- Generics in Java
- Specification of a generic abstraction
- Use of a generic abstraction
- Examples
- Example: Java implementation problems
## Motivating example
Lets create a data container **Pair**, for storing any polymorphic pair of object references.
In the example A and B are auxiliary classes and the implementation is very simple. Most likely there would also be for example get and set methods in the implementation.
Class definitions:
```java
class A { }
class B { }
class Pair {
Object x, y;
}
```
TWe can see the following problem in the next example: if we set A and B as values of x and y and try to access the data, java has lost the information of what is stored inside the pair object. This is the main problem that generics tries to solve. In other words the problem is that every time we store data inside a class we may have lost the type of the data when trying to access the data of the class.
Usage:
```java
void main() {
var pair = new Pair();
pair.x = new A();
pair.y = new B();
A a = (A) pair.x;
}
```
- **A a = (A) pair.x;**
Idea: Convert the type (Object → A), trust that the conversion always works.
Problems: prone to errors and relatively slow.
1) How the Java Virtual Machine works: check and raise `ClassCastException` if necessary
2) After the conversion, the possible dynamic binding is made
3) During development, the tools (IDEs) may not notice the incorrectness of the conversion
Another way to implement this is to program checks that check for the type of data. This way the program doesn't crash because we have planned for the x to possibly be some other than type A.
Usage:
```java
void main() {
var pair = new Pair();
pair.x = new A();
pair.y = new B();
A a = pair.x instanceof A ? (A) pair.x : null;
}
```
- **A a = pair.x instanceof A ? (A) pair.x : null;**
Idea: check if you can convert, convert only if possible
Problems: slower than before, still prone to errors
1) The type conversion is checked once or even twice if the conversion is successful
2) The right side of instanceof can be mistakenly referred to incorrectly (e.g. pair.y)
Solution to these problems is to create a new reference **px** wich is of type A.
Usage:
```java
void main() {
var pair = new Pair();
pair.x = new A();
pair.y = new B();
A a = pair.x instanceof A px ? px : null;
}
```
- **A a = pair.x instanceof A px ? px : null;** (Java 14+)
Idea: check if you can convert. Convert only if possible + new reference
A slight improvement on the previous one
1) As slow as traditional instanceof
2) Works as long as you remember to use px, the risk of error decreases
Generics tries to remove these problems. We can define the **Pair** class so that it "knows" what kind of information is stored within it. And when an object is created from the class, the types of the variables are bound staticly.
Class definition:
```java
class A { }
class B { }
class Pair<First, Second> {
First x;
Second y;
}
```
Usage:
```java
void main() {
var pair = new Pair<A, B>();
pair.x = new A();
pair.y = new B();
A a = pair.x;
}
```
**A a = pair.x**; and generic definitions (1) class 2) object creation)
Idea: type information of members part of class type, not Convert
Improvements to the previous ones
- The compatibility of the types in the set sentence is already checked in the translation, before running the program and before running the tests!
- Types do not need to be converted, dynamic binding can be avoided at best
- Code refactoring is safer
## How and when to use generics?
The example described above brings out two things that are typically aimed at with genericity:
```java
class Pair<First, Second> {
First x;
Second y;
}
```
Generalizing the definition
- abstracted to cover different data types
- the same implementation is suitable for different types
(format = other code except type signatures)
```java
class A { }
class B { }
var pair = new Pair<A, B>();
var pair2 = new Pair<B, A>();
var pair3 = new Pair<A, A>();
```
Using the same definition in different contexts with different compatible data types so that accurate information of the types is preserved.
## Generics and polymorphism
Polymorphism with subtyping
- inheritance and polymorphism allow methods to accept different concrete types (subtypes) according to an interface BUT:
- only a static type can be assumed from a polymorphic input value and
- polymorphic method calls require (slow) dynamic binding
(when the left side of the method call is polymorphic)
The parameter `object` can refer to different types of objects:
```java
abstract class X { abstract void x(); }
class Y extends X { @Override void x() { System.out.print("y"); } }
class Z extends X { @Override void x() { System.out.print("z"); } }
```
```java
void method(X object) { object.x(); }
```
Polymorphism with subtyping
- Parametric polymorphism (“genericity”)
- In Java: parameterization of types (classes/interfaces) (+ methods)
- a more precise static type can be bound at compile time.
- method calls can be made with (more efficient) static binding.
The parameter `object` can refer to different types of objects:
```java
class Y { void y() { System.out.print("y"); } }
class Z { void z() { System.out.print("z"); } }
```
```java
<X> void fill(X[] objects, X object) {
for(int i=0; i<objects.length; i++) objects[i] = object;
}
```
## Generics in Java
The roles of using generics are analogous to those of methods and classes.
![](images/part-16/generics.PNG)
(Genericity works on a meta level - above results in a class that is usually still instantiated)
## Specification of a generic abstraction
In Java, two types: generic class / method
Formal type parameters
- are presented after the type **in angle brackets** <>, separated by a comma
- positioned after the class name / before the return value of the method
- **named** similarly as method parameters
- appear in the definition area like method parameters inside the method
- the parameter is still a black box inside the definition, without the limitations of generics
- limited oper. for parameter: `(X)` and `instanceof X` work, `new X(..)` and `new X[]` don't!
Generic class: (class NAME<G-PARAMS> { BODY })
```java
class Generic<G> { List<G> data; Generic(G d) { data.add(d); } }
```
Generic method: (<G-PARAMS> RETURN TYPE NAME(PARAMS) { BODY })
```java
<G> G loop(G g) { return g == null ? loop(g) else g; }
```
## Use of generic abstraction
```java
TypeToBeParametered<TypeArgumenti1, TypeArgument2>
```
- A generic instance of a class or method, where each generic parameter of the abstraction is replaced with the corresponding generic argument. (cf. calling a method)
- Instantiation = doing, e.g. method call, class creation, defining a second generic type
- As a result, a concrete class or method
- Type arguments
- be concrete or so-called free type (?)
- primitive types do not work though! (array works - array can be a generic type parameter, but the elements of arrays cannot be generic)
- **enum** or table act as arguments, although not as a basis for generic specifications
Generating and shuffling a generic Integer list:
```java
var list = new ArrayList<Integer>(1, 2, 3); // gen. creating a class
Collections.<Integer>shuffle(list); // gen. method call
```
## Benefits of generics
- Helps to focus on the implementation of data structure behavior
- Makes it easier to generalize the class
- Leads to universality
- the selection of features is defined independently of the specific context
- Defining the properties of type parameters provides insight into the role of a generic class in the class hierarchy
- Type safety and efficiency
## Example: Generic pair
Generic Pair class
- type parameters **S** and **T**
- binds together two references of different types
```java
public class Pair<S,T> {
private S first;
private T second;
public Pair(S s, T t) { first = s; second = t; }
public S getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(S e) { first = e; }
public void setSecond(T e) { second = e; }
}
```
Examples of generating generic pairs:
1) a string and a Double floating-point number
2) a pair of integers
3) Double floating-point number and a pair of strings (so a pair recursively!)
```java
void main() {
// Pair<String, Double> would be handy to name as "type variable"
Pair<String, Double> p1 = new Pair<String, Double>("foo", 43.2);
Pair<Integer, Integer> p2 = new Pair<Integer, Integer>(1, 2);
Pair<Double, Pair<String, String>> p3 =
new Pair<Double, Pair<String, String>>(19.1,
new Pair<String, String>("ab", "ba"));
}
```
Diamond Notation (<>)
- generic types are inferred from the context inside the <> signs (the left side of the = sign)
- note! creating a generic object without <> characters is an error, resulting in a warning
```java
void main() {
Pair<String, Double> p1 = new Pair<>("foo", 43.2);
Pair<Integer, Integer> p2 = new Pair<>(1, 2);
Pair<Double, Pair<String, String>> p3 = new Pair<>(19.1, new Pair<>("ab", "ba")
);
}
```
Java 10+: type for local (= inside a method ) variables in the left of = can be deduced (var).
```java
void main() {
// Pair<Integer, Integer> deduced
var p1 = new Pair<>(1,2);
// Pair<String, Double> deduced
var p2 = new Pair<>("foo", 43.2);
// Pair<Double, Pair<String, String>> deduced
var p3 = new Pair<>(19.1, new Pair<>("ab", "ba"));
}
```
## Example: Generic method and class
A generic method can be defined in a generic class.
- In the Connector class, the visibility of the type parameter S is the entire range of the class and T only appears inside method foo
```java
class Pair<S, T> {
S s; T t;
Pair(S s_, T t_) { s = s_; t = t_; }
}
class Connector<S> {
S first;
Connector(S first) { this.first = first; }
<T> Pair<S, T> foo(T t) { return new Pair<>(first, t); }
}
```
## Example: Java implementation problems
Java's implementation of genericity doesn't always convey the exact generic parameter type information (reifed generics) on the use side, which results in problems unlike, for example, in the languages C# and C++:
```java
class GenericArray<T> {
T[] array = new T[] {}; // Cannot create a generic array
}
```
And also in this case:
```java
class Container<T> {
T t = new T(); // Cannot instantiate, T's concrete type is
// not fully known at runtime
}
```
Next up: [Part 4.5 - Generics and Variance](Part%204.5%20-%20Generics%20and%20Variance.md)
\ No newline at end of file
# Generics and variance
## Today's topics:
Generics and variance
- Generics and inheritance
- Wildcard type
- Type parameter constraints
- Generics as a contract
Examples
## Generics and inheritance
Following is a basic non-generic class hierarchy.
```java
class A {}
class B extends A {}
class C extends A {}
class D extends A {}
class BB extends B {}
var a = new A();
var b = new B();
var c = new C();
var d = new D();
...
```
This class hierarchy is shown as a tree on the following image. The point is to observe than this type of class hierarchy forms one tree like hierarchy.
![](images/part-17/hierarchy1.PNG)
What if we make this hierarchy generic? The next example covers a case when we have selected a X, not all possible cases.
The subtypes of a generic type, same type argument.
```java
class A<X> {}
class B<X> extends A<X> {}
class C<X> extends A<X> {}
class D<X> extends A<X> {}
class BB<X> extends B<X> {}
class T {}
var a = new A<T>();
var b = new B<T>();
var bb = new BB<T>();
var c = new C<T>();
...
```
When X is assigned some specific value the hierarchy is very similar to the non-generic case.
![](images/part-17/hierarchy2.PNG)
Each generic type argument creates a new branch in the inheritance hierarchy.
```java
class Å {}
class A<X> extends Å {}
class B<X> extends A<X> {}
class BB<X> extends B<X> {}
class T {}
class S {}
var as = new A<S>();
var at = new A<T>();
var aat = new A<A<T>>();
...
```
When rype parameters are for example S and T as in our code example the inheritance hierarchy gets much more complicated. Every **generic** instance forms a new branch on the hierarchy.
![](images/part-17/hierarchy3.PNG)
- A basic generic type can be instantiated with any concrete type
- Each instantiated type is a subtype of the supertype of the generic class via inheritance
- Two subclasses represent the same type exactly when the type parameters and the generic class are equal.
- These instantiated subclasses form new branches to the class hierarchy, and are only
related via their superclass.
![](images/part-17/java-hierarchy.PNG)
## Wildcard type
- Java’s generic type parameter is always invariant.
- Type parameters break the supertype–subtype contract
In this example case we have a collection of animals and a collection of dogs. We know that a dog is an animal so it can do everything that an animal can and it may also have some unique capabilities of dogs.
![](images/part-17/wildcard.PNG)
- Assume Dog <: Animal
- Although (in Java) List <: Collection
- `List<Dog>` <: `Collection<Animal>` is not true
- neither is `List<Dog>` <: `List<Animal>`.
- According to the replacement principle, `List<Dog>` should work with `Collection<Animal>` (methods can be called). But we can see that in Java this is not true.
What if you want to extend the variance property of the information to the structure surrounding it?
- Solution in Java: wilcard type `<?>`
Wildcard type can be used as the type of variables, method return value and parameters and in specific situations as a generic parameter constraint!
Unrestricted format `<?>`:
- e.g. `List<?> foo =` qualifies any list reference
Free type `<? extends Foo>`: covariant
- e.g. the producer - a more specialized producer replaces a more general one
Free type `<? super Foo>`: contravariant
- e.g. the consumer - a more general consumer replaces a more special one
Wildcard type can also be used in the context of generic methods for example:
**`List<Dog> <: Collection<Dog> <: Collection<?>`**
```java
import java.util.List;
class Example {{
List<?> list = List.of( 9, "hello", new Object() );
for(Object o: list)
System.out.println(o);
}}
```
The next code snippet shows how this is different than `List<Object>`. You cant assign these List variables to a `List<Object>` but assignment to `List<?>` work. Biggest benefit is that we can write generic code with more comlex relations between the types.
```java
class Hammer {}
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
class Example {{
List<Cat> a;
List<Dog> b;
List<Hammer> c;
List<?> x;
List<Object> y = a; // error!
x = a; x = b; x = c; // ok
}}
```
### Variance rules of generics
Generic parameters: invariants
- Applies to `C <: B` and `B <: A`, but not `MyList<C> <: MyList<B>`
```java
class A {}
class B extends A {}
class C extends B {}
class MyList<X extends B> {}
```
```java
// MyList<A> a = null; // not even A :< B
MyList<B> b = null;
MyList<C> c = null;
// b = c; fails even if B <: C
// c = b; fails as expected
```
### Variance rules of the wildcard type
Wilcard type `<? extends Foo>`: covariant
- For example we can imagine a producer of something
```java
class A {}
class B extends A {}
class C extends B {}
class MyList<X> {}
```
```java
MyList<? extends A > a = null;
MyList<? extends B > b = null;
MyList<? extends C > c = null;
a = b;
b = c;
// c = b; // fails
// b = a; // fails
```
Wildcard type `<? super Foo>`: contravariant
- For example we can imagine a consumer of something
```java
MyList<? super A> a = null;
MyList<? super B> b = null;
MyList<? super C> c = null;
// a = b; // fails
// b = c; // fails
c = b;
b = a;
```
### Wildcard type and subtyping
![](images/part-17/wildcard-subtyping.PNG)
## Type parameter constraints
1) Declaration of the generic abstraction / type parameters
- is upper bound
- the type constraint lists at most one concrete or abstract class
(can be generic & contain wildcard parameters, see point 2)
- may also contain multiple interface classes (connected with **&**)
2) Use of a generic abstraction / type arguments
- a wildcard type can have an upper or lower bound
Constraints
- **extends**: upper bound, specifies the class with which the parameter needs to be
compatible
- **super**: lower bound, specifies the class for which the parameter needs to be one of the
superclasses
## Generics as a contract
The generic type is part of the interface contract.
1) specification of a generic abstraction:
- “client of the type”
- e.g. `class List<X>`: we barely know that X is a reference and that` X extends Object`
- e.g. `class List<X extends Dog>`: X inherits from Dog, can call `fetch()`
- e.g. class `List<X extends Dog & Tame>`: X inherits from Dog, implements Tame, can call `fetch()` and `givePaw()`
2) The use of a generic abstraction
- “provider of the type”
- note the analogy: dynamic & static type ~ variable content & signature (e.g. `List<? extends Dog>`)
## Examples
### Example 1: Constraining a type parameter
Here is an example of type parameter constrains.
Unbound X:
```java
class User {
<X> void use(X device) {
// ???
}
}
```
What can be do with X? What do we know about it?
-> X can at least represent an Object. (so we know almost nothing about X)
Unbound X:
```java
class User {
<X extends Object> void use(X device) {
// ???
}
}
```
Basically the same as above, for all reference types extends Object holds.
When X has an upper bound:
```java
class Phone {
void call(int number) { ... }
}
class User {
<X extends Phone> void use(X device) {}
void use2(Phone device) {}
}
```
The parameters always have an implicit upper bound. So the constraint achieves very little.
Biggest difference is visible in the next case. There is two interfaces defined. We can reference them both and the Phone via the IPhone14ProMax class. But if only one of the interfaces would be enough for our case we could have the user implementation not care about what kind of phone we are using. Without generics we would have to code some helper classes to achieve this.
X has an upper bound:
```java
class Phone { void call(int number) { ... } }
interface Smart { default void loadSite(String address) {} }
interface Luxury { default double price() { return 9e307*9;} }
class iPhone14ProMax extends Phone implements Smart, Luxury {}
class User {
<X extends Phone & Smart> void use(X device) {
device.call(112);
device.loadSite("https://utu.fi");
}
}
```
```java
new User().use(new iPhone14ProMax());
```
### Example 2: Problem - covariant parameters
The inherited return type is covariant, the parameters are not.
This example shows that parameters in java cannot change even though we would want to change their types. When we inherit a class and want to override some method, the method parameters cannot changein the subclass. Focus on the different types of parameters when reading the code.
```java
class Stick {}
class Animal { void eat() {} void drink() {} void play() {} }
class Dog extends Animal { void fetch(Stick k) {} }
class AnimalWatch {
void watch(Animal e) { e.eat(); e.drink(); e.play(); }
}
class CertifiedDogWatch extends AnimalWatch {
@Override void watch(Dog e) { // error! should be: void watch(Animal e)
super.watch(e);
e.fetch(new Stick());
}
}
```
```java
var spot = new Dog();
new CertifiedDogWatch().watch(spot);
```
Generic type can be further refined (still invariant X <: Animal)
If the `AnimalWatch` class is generic we can specify wich type of class we want to inherit. In this case we create `CertifiedDogWatch` to extend `AnimalWatch<Dog>`. This code works and compiles while the previous code doesn't.
```java
class Stick {}
class Animal { void eat() {} void drink() {} void play() {} }
class Dog extends Animal { void fetch(Stick k) {} }
class AnimalWatch<X extends Animal> {
void watch(X e) { e.eat(); e.drink(); e.play(); }
}
class CertifiedDogWatch extends AnimalWatch<Dog> {
@Override void watch(Dog e) {
super.watch(e);
e.fetch(new Stick());
}
}
```
```java
var spot = new Dog();
new CertifiedDogWatch().watch(spot);
```
### Example 3: Problem - merging iterators
This example is somewhat artificial. It has non-generic animal classes and an switch iterator. The `SwitchIterator` checks the position of a flag and according to the position delegates methods to selected iterator given as a parameter. The structure is a bit complicated because we did not want to use wilcard types.
The original iterators are non-generic and incompatible. However we can pick one element at a time and make the code work because the elements are compatible with the subtyping rules.
```java
abstract class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
class SwitchIterator extends Iterator<Animal> {
private final boolean flag;
private Iterator<Cat> iter1;
private Iterator<Dog> iter2;
SwitchIterator(boolean v, Iterator<Cat> i1, Iterator<Dog> i2) {
flag = v; iter1 = i1; iter2 = i2;
}
@Override boolean hasNext() {
flag ? iter1.hasNext() : iter2.hasNext();
}
@Override Animal next() {
flag ? iter1.next() : iter2.next();
}
}
```
We can simplify this to one static method. Parameters are similar to previous code. Now when we have used the wildcard type, we can return a type that is compatible in a iterator level. This code is also a bit more efficient than the previous one.
Wildcard type + constraints
```java
abstract class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
class SwitchIteratorFactory {
static Iterator<? extends Animal> choose(
boolean flag,
Iterator<? extends Cat> i1
Iterator<? extends Dog> i2) {
flag ? i1 : i2;
}
}
```
### Example 4: : Task: maximum
Implement a generic class method max, which returns the larger of the two inputs. Especially
focus on how the types have to be constrained.
Class method max:
```java
class Example {
static int max(int x, int y) {
return x > y ? x : y;
}
}
```
The method gives an idea, but only works with integers.
```java
class Example {
static <S> S max(S x, S y) {
return x > y ? x : y; // error!
}
}
```
Problem: x > y does not work for all generic types S.
Generic class method max:
```java
class Example {
static <S extends ???> S max(S x, S y) {
return // x > y ? x : y; ??? //error!
}
}
```
Let’s solve this with the aid of Comparable:
```java
interface Comparable<T> {
int compareTo(T other);
}
```
Generic class method max:
```java
class Example {
static <S extends Comparable<S>> S max(S x, S y) {
return x.compareTo(y) > 0 ? x : y;
}
}
```
The generic type parameter can also be omitted:
```java
class Examples {{
var max1 = Example.<String>max("Buu", "Baa");
var max2 = Example.max("Buu", "Baa");
}}
```
### Example 5:
Collections class defines an overloaded generic class method sort for sorting
collections:
```java
public static
<T extends Comparable<? super T>> void sort(List<T> l)
```
- What does the method require from the type parameter T?
- Ponder what differences do the types
Comparable<T> and
Comparable<? extends T>
have – in the same class / superclass / subclass..
### Example 6: Generics and polymorphism
The following record structure distracts attention away from the operation logic with the types
because of the fight.
```java
interface Number { Number sum(Number second); }
record Size(int value) implements Number {
public Number sum(Number second) {
if (!(another instanceof Size)) throw new Error("Does not add up!");
return new Size( ((Size)second).value() + value() );
}
}
```
```java
Number number = new Size(1);
number = number.sum( new Size(2) );
if (!(number instanceof Size)) throw new Error("Not an integer!");
System.out.println( ((Integer)number).value() );
```
The generic Luku structure makes implementation much easier, but requires so-called F-limited /
the use of recursively bounded quantification
```java
interface Luku<L extends Luku<?>> { L sum(L second); }
record Size(int value) implements Number<Size> {
public Integer sum(Integer second) {
return new Size( second.value() + value() );
}
}
```
```java
Size number = new Size(1);
number = number.sum( new Size(2) );
System.out.println( number.value() );
```
Congratulations on reaching the final part of the study material for this course. Now all that is left is the part 4 exercises and after that the exam, both in ViLLE.
\ No newline at end of file
images/part-13/eq1.PNG

87.1 KiB

images/part-13/eq2.PNG

110 KiB

images/part-13/eq3.PNG

87.3 KiB

images/part-13/eq4.PNG

87.8 KiB

images/part-13/equivalence.PNG

78.7 KiB

images/part-13/hash.PNG

21.3 KiB

images/part-14/Collection_hierarchies.PNG

44.6 KiB

images/part-14/Collection_hierarchies2.PNG

49.9 KiB

images/part-14/Collection_types.PNG

11.4 KiB

images/part-14/Collection_types2_inverted.PNG

11.7 KiB

images/part-14/Properties_of_collections.PNG

106 KiB

images/part-14/threadprogramming.PNG

47.5 KiB

images/part-15/invariance.PNG

83.1 KiB

images/part-16/generics.PNG

52.7 KiB

images/part-17/hierarchy1.PNG

8.93 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment