Java Generics Flashcards
Collection extends Number> is a GET-ONLY subtype of Collection where T is subtype of Number
REMOVE considered as GET
When you see
void foo(Collection extends Number>) { }
It says this function accepts collections of Integer|Double… to READ/GET (include remove) but not to add new element (How is this enforced by JVM?)
The Java Generics Get (? extend from T) and Put (? super of T) Principle
Get|Read via casting (concrete-type pointer)
Put|Write via void* (opaque pointer)
List extends T> e.g. List extends Number>
List super T> e.g List super Number>
T can only be bound to 1 specific type at a time, which is unknown to code inside generics.
List extends Number> means this list holds object of Number or subtype (Float, Integer…) It is safe to read all elements as Number, but it is not safe to put elements Integer or Float into it, because it could be List when you put Float into it.
List super Number> means the list holds object of Number or Object. It is safe to write Number or its subtypes to list, because the list can only be List or List which is safe to read Number|Float as Object or Number
void foo(List super Number> list);
what can we do with list inside foo?
TLDR: we can only write Integer or Float element into list because the list can only be List or List in either case it is to safe to read Float or Integer as Number or Object
void foo(List list);
what can we do with list inside foo?
TLDR: we can only read elements as Number because the list can be List or List (and inside foo() we don’t know which)
wildcard type does not guarantee immutability
wildcard is a compile time enforcement
[*] Covariant vs Invariant
Array of Box Types is Covariant, in that
S extends T then S[] extends T[]
Integer[] and Number[] are covariant
int[] is primitive type array, does not apply
Collection/List is invariant in that
S extends T but List does NOT extend List
Wildcard introduces Covariant to Collection/List
S extends T then List extend List extend T>
GET/PUT principal applies to Covariant types but violation of the principal is detected at different times (compile time vs run time)
List/Collection Covariant:
Get/PUT violation is detected at COMPILE time
Array Covariant:
GET/PUT violation is detected at RUN time (ArrayStoreException)
What is Covariance? (Origin of Array Covariant)
TLDR:
Covariance is the “is-a” relationship in inheritance tree.
We know that “Integer is-a Object”
Java, before generics, also defined that
Integer[] is-a Object[]
This allows void foo(Object[] a) to be called with foo(new Integer[3])
Unbounded Wildcard List> vs Bounded wildcard List extends T> or List super T> vs Bounded Type List
Unbounded wildcard: operations that does not depend on type (such as list.size())
Bounded wildcard : Put or Get but not both
Bounded Type: Put and Get
What is wrong with this (signage)
public int compareTo(Integer that) { // bad implementation -- don't do it this way! return this.value - that.value; }
this code may give the wrong answer when there is overflow. For instance, when
comparing a large negative value to a large positive value, the difference may be more
than the largest value that can be stored in an integer, Integer.MAX_VALUE
Type-Erasure (C++ Code specialization vs Java Code sharing)
C++ template uses Code Specialization (1 class generated for each concrete type used with template) Java generics uses Code sharing (1 class generated for ALL concrete types used with the template)
C++
template
class CList {
}
Java class JList { T t = new T(); // this is not possible since there is no "Type information" at run time. (Type-erasure) }
CList and CList are two different classes in C++
JList and JList are same class in Java. To do this java adopt type-erasure to remove all type information inside JList implementation. (I.e. you cannot instantiate objects of T inside JList)
Therefore, In java, Generics and Collection are tightly coupled where generics are usually used with collections where collection operation (len, size, iterate) does not require type info.
If type info is needed, then use Bounded Type extends Number> then you can reference elements as Number.
Types of outter class is visible to non-static inner class. (static nested class are like namespace, thus its type is separated from outter class types)
If the outer class has type parameters and the inner class is not static, then type parameters of the outer class are visible within the inner class.
ERASURE
what is it?
(Remember type bound is only done through extends not through “super” and there is no “bounding” done for > )
Erasure is process of enforcing type constraints only at compile time and discarding the element type information at runtime.
Drop all type parameters from parameterized types, and replace any type variable with its bound, or with Object if it has no bound, or with the interfaces if it has multiple bounds
Multiple bounds: A type T can have multiple bounds
List \ extends C1 & C2 & IF1 & IF2>
Thus the list element must be a subtype of C1&C2 and implements IF1 and IF2.
(C1 &C2 indicates multiple inheritance, BAD DESIGN!)
Java requires Class type precedes interface type
In Multiple bounds, usually 1 class and multiple interfaces, compiler erased the left most bound (class bound), and keep the remaining (interface bounds)
So compiler will erase Number and keep comparable.
This the generated class is Comparable max( Comparable x,...)
Signatures for methods should be as general as possible to maximize utility. If you can
replace a type parameter with a wildcard then you should do so.
We can improve the Signature of max by replacing: T max(Collection collection) with: T max(Collection \< ? extends T> coll)
Why is System::arraycopy() a native method?
In native mode, it allows using pointers to fast access to consecutive objects in continuous memory block (e.g. memcpy()) and other optimizations.
I.e. do a block memcpy() instead of object-wise copy
List < T extends Comparable < ? super T> >
it means that T can implement Comparable super T>, not just Comparable.
For example, if Student is-a Person it means that a Student class can implement Comparable, where Student is a subclass of Person:
Usually, Student implements Comparable
and Person implements Comparable
but since Student inherits Person, student inherits Comparable as well
Thus Student satisfies > even if Student itself does not implement Comparable
Java Generics does not support primitive types.
List asList(T…a);
when you call Arrays.asList(1,2,3)
it is same as Arrays.asList(Integer(1), Integer(2))
Java infers type from argument 1,2,3
Thus compiler sees
List Arrays.asList(Integer(1), Integer(2)..)
Because java generics uses Code Sharing (no code expansion or specialization as in C++) a generic template has only 1 class|method|implementation for all types meaning inside the template there is only 1 type “Object” meaning type-erasure at run time
Because of type-erasure, you cannot refers to the type symbol (e.g. “T”) in side the code (because it is removed prior to runtime) that has any run-time behavior (execution|storage etc).
T is valid in declaration (compile-time)
but invalid in definition (implementation, run-time)
Thus these sorts of references of type T in code is invalid:
(static data member declaration) static private T t; new T(); instead of T
(T is pretty much an invalid symbol other than in class or function declarations or type conversion)
Java Lambda is a syntax sugar for Anonymous (Inner) class, where a function object is created.
/* anonymous class */
Runnable myTask = new Runnable() { // impl }
/* lambda */ Runnable myTask = () -> {/*impl*/};
Anonymous Inner class can only access FINAL variables from outer method.
Because anonymous class is like a callback, and the outer variables used inside there are like callback data, they are not allow to be changed to refer to different object.
Thus the variable needs to be final so it cannot be reassigned. (But the object the variable refers to can be mutable)
final vs effective final
A variable is final when it is explicitly declared be final.
A variable is effective final when it is only assigned once.
(E.g. c++ reference is effective final)
Lambda Expression and Functional Interface
Lambda Expressions are expressions, thus like other expressions, they have types
When Lambda expressions are assigned as function arguments, they have types
E.g.
expression (3+2) is of type int
Runnable myTask = () -> {/impl/};
void foo(Runnable task) { /* task is a "Function Pointer" */ }
Lambda expression is function object (or C++ function pointer).
Lambda expression implements interface, thus is an function object of that interface.
But 1 lambda expression can only implement 1 method of the interface. Thus interfaces with 1 method is called Functional Interface.
Runnable myTask = () -> {/impl/};
where the lambda expression is a Runnable function object.
Raw type of Generics is Object
Prior to Java 5, Collections support only Raw Type (i.e. Object), thus to create a list you do List cars = new ArraysList();
In java 1.5, generics are introduced thus we do
List cars = new ArraysList<>()
But for backward compatibility the old grammar is still kept List cars = new ArraysList();
which means
List cars = new ArraysList<>();
A raw type is the name of a generic class or interface without any type arguments. For example, given the generic Box class:
public class Box { public void set(T t) { /* ... */ } // ... } To create a parameterized type of Box, you supply an actual type argument for the formal type parameter T:
Box intBox = new Box<>();
If the actual type argument is omitted, you create a raw type of Box:
Box rawBox = new Box();
Streams is Functional Programming
Functional Programming: Input->filter->output with filter having no mutable states.
stream operations are composed into a stream pipeline
, a pipeline of “functional filters”.
Java Stream pipeline ~=> gstreamer pipeline ~=> mediapipe pipeline (all pipelines are declarative, not imperative programming)
A pipeline of “functional filters” with each filter have no mutable states.
Each filter must be non-interfering (they do not modify the stream source); and in most cases must be stateless (their result should not depend on any state that might change during execution of the stream pipeline).
This matches functional programming paradigms:
Functional methods are stateless. It has no hidden input or hidden output.
All its input are in the function signature.
All its output are in the return value.
No side effects.