Close

Java notes

General

  • Liskov Substitution principle: a variable of a given type may be assigned by a value of any subtype of this type; same for the method – if argument is of type A and B is a subtype of A then B can be passed
  • .equals() contract:
    1. Reflexive: x.equals(x) == true
    2. Symmetric: x.equals(y) == y.equals(x)
    3. Transitive: x.equals(y) && y.equals(z) == x.equals(z)
    4. x.equals(null) == false
    5. Consistent: if x, y not null and x.equals(y) == true/false then x.equals(y) == true/false for any number of invocation (x,y does not change)
  • There is no way to extend an instantible class and add a value component while preserving the .equals conract. TIP: use composition
  • Override .equals for value types
  • .hashCode contract: if x.equals(y) then x.hashCode() == y.hashCode()
  • Use lazy initialization for heavy-weight hash codes (or precalculate it on object construction)
  • .toString should return all of the interesting information contained in the obejct
  • Provide public accessors for every piece of data provided via .toString
  • Avoid .clone if possible. Use copy constructor or copy factory instead.
  • Classes designed for inheritance should not implement .clone
  • .clone can be implemented if only and only if all the superclasses provide well-behaved implementation of it
  • Comparison:
    1. consistent with .equals()
    2. anti-symmetric: x < y if and only if -y < -x
    3. transitive: if x < y and y < z then x < z
    4. congruence: if two values comparison result is equal then comparison to any third value gives result equal.
      if x.compareTo(y) == 0 => sgn(x.compareTo(z)) == sgn(y.compareTo(z));
      For integers: if x == y => x < z if and only if y < z
      Presumably: if x.compareTo(y) == 0 => x.compareTo(z) raises an exception if and only if y.compareTo(z) raises an exception
    5. reflexive: x.compareTo(x) == 0
    6. x.compareTo(y) raises an exception if and only if y.compareTo(x) raises an exception
    7. x.compareTo(null) throws a NPE
  • Java comparison operators are antisymmetric: a < b & b <= a => a == b only for primitive types!!!
  • It is assumed that Comparable provides a natural ordering while Comparator provides unnatural ordering
  • For each Comparable provide an utility which accepts Comparator
  • it is a good practice to comment code lines where warning may be raised when the later is suppressed
  • arrays in Java are covariant (unlike Generics, which are invariant). This means that Integer[] is a Number[], but List<Integer> is not a List<Number>
  • developer can do almost anything with classes defined as final i.e. there is no risk someone inherited from it and relies on implementation
  • by default Java compiler looks for the files in the local directory first (even for library). But vice versa for runtime
  • type inference does not work for anything other than assignment
  • method overloading: actual method is chosen during compilation according to defined type not actual:
    A a = new B(); // B extends A
    some(a); // always call some(A)
    some(new B()); // calls some(B) if present, otherwise some(A)
  • Use overloading only to add new params or use naming pattern
  • Use varargs to exceed more than three params
  • Serialization should be avoided unless special cases (already introduced). Use serialization proxy pattern instead.
  • Always favor immutability rather then mutability
  • Serialization proxy pattern fits perfectly to immutability
  • Provide mutable companion or cache fields to increase performance
  • Use static factories and cache entities
  • No need to provide copy facilities (copies always equal)
  • It is eligible to share internals of immutable objects
  • Inheritance violates encapsulation principle
  • A class for inheritance must not self use overridable methods
  • It is a good practice to limit inheritance within single package
  • To simulate inheritance outside a package one could introduce a forwarding class (wrapper) and then inherit it. This practice especially usefull when a commen interface is present.
  • Cases for inheritance:
    1. provide skeletal implementation of the complex exported interface
    2. for defining a type with an abstract class which could be evolved later
  • Public interface can not be changed after release
  • Use enum for any set of values that is known at compilation time
  • Nested strategy enum pattern
  • Use EnumSet for configuration options
  • Prefer annotations over naming pattern
  • Marker interface is compile time safe rather then marker annotation. But marker annotation is more flexible and valuable in the future
  • Annotations are useful for simplifying initialization facilities
  • Java 8 introduces default methods for the interfaces, such methods are injected into concrete instances whose classes implement this interface. Beware though that somethime it is possible that such default implementation may violate class’s invariants e.g. do not synchronize properly (removeIf and apache’s commons SynchronizedCollection)

Java Puzzlers

  • i % 2 == 1 works only 75% of the time. This is because % does not work correctly for negative integers. In Java spec: (a / b)*b + (a % b) == a, see JSL 15.17.3, and, taking ointo account, Java truncates integer division operator [JLS 15.17.2], it implies that wjen the remainder operation returns a non-zero result it has the same sign as its left operand
  • Java always truncates to zero 19/5 = 3 and -19/5 = -3
  • for performance critical operations it is better to use bitwise AND (&) instead of %
  • always think about the signs of the operands and of the result whenever % is used
  • Double.toString() prints the shortest decimal fraction sufficient to distinguish the double value from the nearest neighbor, with at least one digit before and after the point
  • with binary floating point it is impossible to represent 0.1 or other negative power of 10 exactl as a finitive-length binary fraction [Effective Java, Item 31]
  • always use new BigDecimal(String) constructor as new BigDecimal(double) creates BD with the exact double i.e. new BD(0.1) creates 0.100..0555111…
  • avoid float and double in calculations where exact answers are required aka money, time etc. Use int, long or BigDecimal instead
  • microseconds per day does overflow: 24*60*60*1000*1000 > Integer.MAX_VALUE. Here int-casting is performed after all operations
  • use L-letter to force long: 24L*60*60*1000*1000 < Long.MAX_VALUE
  • when working with large numbers watch out for overflow. It does not necessary mean that, even if target type is big enough, leading to it computations may not overflow
  • do not use l as a varibale name
  • avoid mixed type computations
  • while casting problems may occur when dealing with signed and unsigned types: char c = (char) -1. This is due to sign extension in Java conversion (widening conversion) smaller or bigger (byte -> int -> long): for negative values all leading bits will be filled with 1; for positive – with 0: 1111 0000 => 1..1 1111 0000 (byte => int)
  • Example: byte b = (byte)(b >>> 4); and suppose b = 0xF0 (1111 0000):
    1. >>> requires int => b is being widening converted to int: 1111 0000 => 1..1 1111 0000
    2. perform shift: 0000 1..1 1111
    3. truncating (cast to byte) just cuts off leading bits: 0000 1..1 1111 => 1111 1111
  • using bit-mask supresses sign extension: char c = (char) (b & 0xFF)

  • write a comment when sign extension is needed: char c = (char)b; // sign extension
  • if you can’t tell what a program does by looking at it, it probably does not what you want
  • compound assignment operator: E1 = (T)(E1 op E2), where T is a type of E1. When using compound operator ensure the following:
    1. Let T = {byte, short, char} – do not use compound assignment at allow
    2. Let T = {int} – ensure that right expression is not of type {long, float, double}
    3. Let T = {float} – ensure that the right expression is not double
      Generally speaking type of E1 is not allowed in compound assignment:
  Object x = o;
  String y = str;
  x += y; //won't compile
  x = x + y; //compiles fine
  • using the following bitmask will always promote value to a wider type without sign extension: int v = b & 0xFF
  • the return value of the postfix incrementation is a value before incrementation: j = j++ does not change j!!!
  • Any i of type Integer => i <= Integer.MAX_VALUE
  • when it is required to iterate near the bounds of a type (int), it is always a better way to use wider type (long), but when it is required to iterate over all 4 billion values of the int use the following idiom:
  do {
    f(i);
  } while (i++ != Integer.MAX_VALUE)
  • shift distances are calculated as mod 32 or 64 (if left operand is long). It is therefore impossible to shift away entire value by using shift operator or distance i.e. it is impossible to perform leftshift using rightshift and vice versa
  • Principle of least suprise: the new UI or API should match to the user’s experience as much as possible

Collections

Collection -- List
 |              \
Set           Queue
 |                    |    \______   
SortedSet              |                \
 |                  Deque       BlockingQueue
 |                            \         |
NavigableSet                    \        |
                                  BlockingDeque
Map_______________
 |                               \
ConcurrentMap          \
 |                                SortedMap
 |                                  |
 |                                 NavigableMap
 |                                 /
ConcurrentNavigableMap 
  • Bubble sort has O(n^2) time. That means that if a number of elements is doubled then time gets 4x
  • differenet mechanisms to achieve thread safety in the Collection framework:
    1. copy-on-write
    2. compare-and-swap
    3. read/write locks
  • Collection framework does not include a method to convert a Collection into a primitive array

Set

  • There are 2 subinterfaces and 6 concrete implementations of Set:
    1. SortedSet [interface]
    2. NavigableSet [interface]
    3. HashSet
    4. LinkedHashSet [HashSet]
    5. CopyOnWriteArraySet
    6. EnumSet
    7. TreeSet [NavigableSet]
    8. ConcurrentSkipListSet [NavigableSet]
  • Collection framework classes use bit masks rather than division (traditionaly hash tables obtain an index from the hash code by taking the remainder after division by the table length)
  • HashSet basic operations (add, contains, remove, size) are constant time; iteration is slow (should visit every bucket)
  • LinkedListHashSet
  • CopyOnWriteArraySet – a thin wrapper around an instance of CopyOnWriteArrayList: add, contains are linear i.e. O(n) due to implementation based on linear search; iterations are O(1) per element
  • CopyOnWriteArraySet is usefull to store subject observers
  • NavigableSet appears in Java 1.6
  • SortedSet.add() is O(nlogn)
  • NavigableSet unlike SortedSet provides methods: subSet, headSet, tailSet in which user can specify include or exclude border elements. In SortedSet these methods always include lower bound and always exclude upper.
  • TreeSet provides fast insertion and retrieval + keep elements sorted. TreeSet is good when one needs to compare words against prefix typed by user
  • BalancedBinaryTree depth is bounded by logn
  • RedBlackTree can be rebalanced in O(logn)
  • user of TreeSet may encounter a problem while using its constructors: TreeSet(Collection<? extends E>), TreeSet(SortedSet<E>). Because overload is resolved during compilation using static types it is possible to end up with different iterating order casting constructor’s argument

    See Joshua Bloch, effective Java, Item 26 -- design overloaded methods with care: 
    use argument types that can not be casted to each other; guarantee identical behaviour
    
  • ConcurrentSkipListSet is introduced in Java 1.6 as the first concurrent set implementation. It is backed by a skip list, a modern alternative to the binary trees: elements are inserted and retrieved in O(1) (reference rearranging)
    skip list
    In general skip list gives comparable performance to trees (search, insert, remove take O(logn)). The advantage of the skip list is that it is an effecient lock free insertion and deletion algorithm

  • Different Set implementations:

add contains next
HashSet O(1) O(1) O(h/n)h – bucket capacity
LinkedListHashSet O(1) O(1) O(1)
CopyOnWriteArraySet O(n) O(n) O(1)
EnumSet O(1) O(1) O(1)
TreeSet O(logn) O(logn) O(logn)
ConcurrentSkipListSet O(logn) O(logn) O(1)
  • if order is essential then choose TreeSet and ConcurrentSkipListSet, the later is the only choice in mutltithreaded applications
  • for larger sets, or for applications with frequent elements deletion TreeSet performs better

Queue and Deque

  • usually inherited method will throw an exception on empty queues. Methods defined in Queue interface do not throw exceptions (return null or false)
  • usage of Collection methods on Queue is not advisable
  • PriorityQueue is not suitable for concurrent use. It iterators are fail-fast, and it does not offer support for client-side locking. A thread-safe version is PriorityBlockingQueue
  • for non-blocking algorithm use ConcurrentLinkedQueue
  • BlockingXX usually provides additional methods where user may specify timeout for the operation
  • only BlockingXX methods are guaranteed to be thread safe, inherited from Collection methods — ain’t
  • fair blocking policy means that it is guaranteed that the thread which waited the longest time to perform blocking operation will be chosen when it became possible ot perform this operation. Non-fair — thread will be chosen randomly
  • Deque and BlockingDeque are introduced in java 1.6
  • ArrayDeque uses a circualr array and is now the implementation of choice for stacks and queues
  • Deque exetends Queue and adds simmetric methods for head and tail: offerFirst(E), offerLat(E). Methods from Queue work as expected on Deque as it was Queue e.g. peek() takes first element; offer(E) – adds last element
  • Queue does not allow null values, because null is treated as a special value e.g. to mark emptiness
  • Work stealing algorithm is one of the best load balancing algorithms. When idle worker (thread) consumes work from the tail of the other’s thread working queue
  • ArrayBlockingQueue is always bound, unlike LinkedBlockingQueue
  • if the application uses a List in which the performance of insertion and removal from the beginning of the list is more important than that of random-access operations, consider using Deque

Map

  • LinkedHashMap provides removeEldestEntry method. One can override it to implement LRU cache
  • WeakHashMap allows values to be GCed if corresponding key has no strong referneces on it i.e. from outside
  • WeakHashMap especially usefull when unique objects (.equal() means ==) are used as keys and for the cache implementations
  • IdentityHashMap uses identity check (==) for keys comparison
  • NavigableMap (as NavigableSet) is introduced in Java 1.6 to replace SortedMap (same for SortedSet)

Threading

  • Never call alien methods from within synchronized block
  • Always synchronize modifications of a public static field
  • Synchronization guarantees that:
    1. no other thread will see an object’s internal state inconsistent
    2. shared data will be always up-to-date from other thread perspective
  • Use volatile for flags and ensuring the visibility of the object state
  • Use -server key for testing and debugging as JVM has more optimizations in the server mode that could affect the behaviour of the programm
  • Locking can guarantee both visibility and atomicity; volatile guarantees only visibility
  • Use volatile when:
    1. writes to the variable do not depend on its value (or only one thread performs writing)
    2. the variable does not participate in invariants with other state variables
    3. locking is not required for any other reason while the variable is being accessed
  • Use ThreadLocal for shared global variables
  • Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them
  • Static initializers are executed by the JVM at class initialization time and this is synchronized by JVM internal synchronization
  • Immutable objects can be published freely
  • Effectively immutable objects must be published safely
  • Mutable objects must be safely published and must be either thread-safe or quarded by a lock
  • Quantity of the running threads must be not greater than quantity of CPUs
  • Use a private lock object in favor for using synchronized methods. It is not possible in case of conditional locking. But it is much more preffered idiom in inheritable classes.
  • Do not use wait, notify or notifyAll
  • Use ConcurrentHashMap in preference to Collections.synchronized or Hashtable.
  • Catching InterruptedException is essential:
    1. propogate it higher (either by rethrowing or declaring)
...
} catch(InterruptedException e){
  Thread.currentThread().interrupt();
}
  • Use Exchanger as a 2-party barrier
  • Consider DelayedQueue to implement scheduled tasks mechanism
  • Use encapsulation for non-standard cases: blockin method with no InterruptedException i.e. IO read/write
  • Thread-owning service should provide both shutdown possibilities:
    1. shutdown with awaiting for currently running tasks to finish
    2. shutdown – immediate cancelation
  • UncaughtExceptionHandler handles only exceptions are thrown from .execute; if submitted task ends with an exception the exception will be rethrown by Future.get and wrapped with ExecutionException
  • Future.cancel(boolean) Set param to false for tasks that are not designed to handle interruption. This parameter can be safely set to true for futures from standard execution framework.
  • Use .cancel for timeouted tasks:
try{
  future.get();
} finally {
  future.cancel(true);
}
  • Override .cancel to implement custom interruption policy
  • Do not rely on a task that relies on a different task result or side effect. This may lead to a deadlock
  • Long running task could hurt overall system responsiveness e.g. the thread pool which is full with such tasks and a short task will be performed only after one of the long running finishes. One may use timeout policy. This will guarantee that each task eventually will make progress
  • Nthreads = Ncpu * Ncpu * (1 + W/C), Ncpu — target CPU utilization; W — wait time; C — compute time
  • The quantity of the external resources usually is the upper bound of the thread pool size e.g. DB connections
  • For dependent tasks one can use bounded pool; SynchronousQueue and CallerRunsPolicy to prevent thread starvation
  • newCacheThreadPool uses SynchronousQueue
  • Saturation policies:
    1. Abort — throws RejectedEE
    2. Discard — silently drops new takss
    3. DiscardOldest — tries ti discard next tasks and submit the new ones
    4. CallerRuns — tries to execute new task in the caller thread
  • Saturation policy can be customized through .setRejectedExecutionHandler
  • There is no predefined saturation policy that blocks the .execute method. Same can be achieved by using semaphores
  • Thread pool hooks (.beforeExecute, .afterExecute, .terminate) are called from within the thread that executes the task. Use ThreadLocal to share data between hooks.
  • For long running tasks CachedThreadPool is always a good choice.
  • MVC pattern in GUI apps may lead to inconsistent locking order and therefore to dead locks
  • Using open calls is a good practice to avoid dead locks. An open call – calling a method with no locks held
  • intrinsic locking V.S. ReentrantLock: use intrinsic locking in all cases except cases when the folloing is required:
    1. timed
    2. polled
    3. interruptable lock acquisition
    4. fair queueing
    5. non-blocking structured locking
  • Timed abd polled lock acquisition provided by TryLock. This helps to avoid fatal consequenses of dead locks in case of intrinsic locking:
while(true){
  if(tryLock()){...}
  if(waitTime > waitThreshold) return;
    waitFor(fixedTime+endTime)
}
  • use interruptable locking with canceable activities
  • work queue fairness policy (specified by boolean param in constructor), if set to true, guarantees FIFO

wait, notify and notifyAll

  • use them to implement conditional sleep mechanism
  • these methods should be called only on currently locked object i.e. x.wait should be called only within synchronized(x)
  • document the condition predicates e.g. buffer is full and opearations that base on the predicates
  • the lock object and the condition queue object (x above) must be the same object wich is held while testing the predicate
  • single notify can be used instead of notifyAll only when both of the following are true:
    1. uniform waiters: only one condition predicate is associated with the condition queue and each thread executes the same logic upon returning from wait
    2. One-in, one-out: a notification on the condition variable enables at most one thread to proceed
  • prefer explicit condition queue to intrinsic condition queue if only you really need the advantages providfed by explicit condition queue

Atomic variables

  • since Java 5 JVM tries to inline CAS operations into a single CPU instruction. If it is not available then JVM uses spin-locking to perform CAS operation

Concurrency

  • Amdahl’s law: speedup <= 1/(F + 1-F/N); F – fractions of the calculation that must be executed sequentialy; N – CPUs
  • Total order (linear order) [<=;>=]
    1. transitive
    2. antisymmetric: if a <= b && b <= a -> a = b
    3. total: Any a, b -> a <= b || b <= a
  • Strict total order [<;>]
  • Partial order:
    1. reflexive a <= a
    2. antisymmetric
    3. transitive
  • any good lock algo must satisfy the following:
    1. mutual exclusion: Suppose A,B — threads && any j, k from int -> CS[A,j] -> CS[B, k] || CS[B,j] -> CS[A, k]
    2. deadlock freedom
    3. starvation freedom — every thread must eventually succeed in acquiring the lock
  • Starvation free algo is Deadlock free
  • Fairness: if D[A] -> D[B] => CS[A] -> CS[B], where D is a doorway section. This means it consists of a bounded number of steps
  • any algo that is both deadlock free and FIFO is always starvation free
  • bounded timestamps system: for two threads assume the following precedence graph


    A[0] / \ B[1] / \_2

    two threads have always tokens in the adjacent nodes: suppose A[0], B[1]: B[1] << A[0] => B[2] >> A[0], i.e. to acquire the greater timestamp B places its token into the node 2 (or leaves its token at 1 and place A’s into 2)

  • graph non-commutative multiplication (GoH): replace every node n of G by a copy of H (H[n]), and let H[n] dominate (Any n[i] in H[i] => n >> Any n[j] in H[j]) H[j] in GoH if i dominates j in G
  • to define graph T[k] inductively to be:
    1. T[1] – single node
    2. T[2] – three nodes graph
    3. T[k], k > 2, T[k] = T[2] o T[k – 1]
  • any deadlock-free Lock requires allocating and then reading or writing at least n distinct locations in the worst case
  • a lock state s is inconsistent in any global state where some thread is in CS, but the lock s is compatible with a global state (G[s]) in which no thread is in CS or is trying to enter CS
  • deadlock-free Locks can not enter inconsistent state
  • covering state for a Lock object is one in which there is at least one thread about to write to each shared location, but the Lock’s locations look like the CS is empty
  • in the covering state, we say that each thread covers the location it is about to write
  • any lock that by reading and writing memory, solves deadlock-free mutual exclusion for three threads, must use at least three distinct memory locations
  • Correctness – is a property of a concurrent object that express its safety
  • Progress – another property that describes liveness
  • Correctness could be of a variety of types. Most usefull:
    1. Quiescent consistency is appropriate for applications that require high performance but place weak constraints on object behavior
    2. Sequential consistency – implemented in hardware memory interface (and JMM)
    3. Linearizability for high level systems that consist of linearizable components
  • progress guarantee can be either blocking or non-blocking
  • an important principle when reasoning about concurrent objects is to map their behavior on a sequential one
  • defining objects in terms of preconditions and post conditions is a good way (almost perfect) of describing the object in sequential programm
  • the most important is to understand about multithreaded programms is that objects may never be between method calls
  • a method call is the interval that starts with an invocation event and ends with a response event
  • method calls of an one thread are always sequential
  • an object is quiescent if it has no pending method calls (no methods are being executed)
  • Quiescent consistency informally means that any time an object becomes quiescent, then the execution so far is equivalent to some sequential execution of the completed calls. An example of such an object can be a distributed counter. This counter quarantees that in retrospective it will act as a sequential one(no numbers are omitted nor skipped), but it does not guarantee the order in which the numbers will be distributed
  • a method is total if it is defined for every object state; otherwise it is partial. Ex.: for Stack enque method is total (if unbound) and deque is partial (undefined for empty queues)
  • in any concurrent execution, for any pending invocation of a total method there exists a quiescently consistent response.
  • Quiescent consistency is compositional
  • Sequential consistency is based on two principles:
    1. every method call should appear in one-at-a-time manner (sequential order)
    2. method calls should appear to take effect in programm order
      In other words, Sequential consistency is an order of method calls in which (1) they are consistent with program order, and (2) meet the object’s sequential specification. There might be more than one of such sequential order.
  • Sequential consistency is not compositional
  • Linearability is like a sequential consistency but: in replace of the second principle we state that each method call should appear to take effect instantaneously at some moment between its invocation and response.
  • Linearability is compositional
  • the absolute wait-free and lock-free progress properties have good theoretical properties and they provide real-time guarantees (music, games). The dependent deadlock-free and starvation-free properties rely on guarantees provided by the platform. These guarantees however admit simpler and more efficient implementations (hardware)
  • Java does not guarantee linearability nor sequential consistency, when reading or writing fields of a shared object
  • CompletableFuture (@since 1.8) may be executed in any thread. For instance one thread defines execution chain and another calls complete() here if future is already finished final call will be made in the second thread.
  • CachedThreadPool executor creates too many threads usually, try to avoid it and use FixedThreadPool or measure performance

Generics

  • Java Generics differ from C++ templates in implementation: Java uses erasure – generics are being replaced with explicit casts durign compilation; C++ uses expansion – code generation
  • erasure: during compilation all type params are dropped from parametrized type; all type variable relaced with the erasure of its bound or Object if it has no bound, or with an erasure of the leftmost bound if it has multiple bounds.
  • in Java two distinct methods can not have same signature. Similar to generic methods – they can not have similar erasure. A class can not implement two interfaces with the same erasure.
  • migration compatability of Generics in Java is achieved by implementation of the Liskov Substitution principle: any Foo<E> corresponds to Foo therefore instances of Foo<E> may be passed whenever is Foo required and vice versa (which is generally an error). In other words Foo<E> is a subtype of Foo.
  • type parameter for a method can be added explicitly e.g. Lists.<Integer>toList()
  • if a result of a method call (with generic output) is passed to another method compiler won’t try to perform type inference, instead it treats the result of the generic method as an Object
  • if a structure contains (List, Array, Custom class etc) elements with a type ? extends E we can get elements out of the structure, but we can not put. On the other hand if elements are of type ? super E we can put, but not get.
  • Type variables must be always bound using extends, not super
  • it is possible to have mutually recursive bounds e.g. <T extends C<T,U>, U extends D<T, U>>
  • Enum declaration example: Enum<E extends Enum<E>> implements Comparable<E>. Suppose: Season extends Enum<Season> means Season implements Comparable<Season>. This is important because without E in Enum declaration Season will be defined as follows: Season extends Enum implements Comparable<Enum> and hence it will be allowed to compare Season with any other Enum.
  • Multiple bounds clauses can be achieved by using & in type declaration: <T extends Readable & Closeable>
  • For backward compatability Java library classes use special methods called “bridges” (accessible via reflection) e.g.

    //Integer
    Object compareTo(Object o){...}
    
    Integer compareTo(Integer i){
      return (Integer) compareTo((Object)i);
    
    } 
    
  • Java 1.5 also introduces covariant overriding: <1.4 – method can only be overriden if exact signature is provided; 1.5> overriding method may have return type of subtype. This is done by bridges: in fact each class contains two methods: one with current type return value and another with bridge modifier thet returns Object
  • conversion from raw type to generic generates warning
  • static members can not refer to any type parameter. This is because static members are shared across all class instances and type parameter is a feature of a concrete instance.
  • if nested class is not static then it can see the type parameter defined in the outer class
  • wildcard capture technique:
    public foo(Class<?> clazz) { bar(clazz) }
    
    private<T> bar(Class<T> clazz) { ... } 
    
  • to generify a library three techniques can be used:
    1. change methods’ signatures, but not methods’ bodies
    2. stub files: new files used for compilation, in runtime old files are used
    3. wrappers – worth solution
  • reifiable types in Java (which has type information in Runtime) are:
    1. primitives
    2. non-parametrized class or interfaces
    3. a parametrized class or interface where every parameter is a wildcard
    4. raw types
    5. an array with reifiable component type
  • the following depends on reifiablity:
    1. instance tests
    2. exceptions (underlying instance test)
    3. array creation
  • instance test can not be performed against non-reifiable type; cast to non-reifiable tyep issues a warning
  • The principle of Truth in Advertising:

    class Wrong { public static <T> T[] toArray(Collection<T> col){ T[] a = Object[col.size] for(T el : col){ a[next] = el } return a; } } //String[] strs = (String[])toArray(...)` => ClassCastException
  • The principle of Truth in Advertising: the reified type of an array must be a subtype of the erasure of its static type.
  • to create an array of Generic type one has to have other array from which an information could be copied
  • int[] is not a Object[], but is a Object
  • The principle of Indecent Exposure: a library should never expose an array with non-reifiable component type.
  • Combining these two principles (Truth in Advertising and Indecent Exposure) gives the following: first requires thet the runtime type of an array is properly reified, and the second requires that the compile-time type of an array must be reifiable.
  • Java libary contains a violation of these principles: TypeVariable<Class<T>>[] java.lang.Class.getTypeParameters(). Inaccurate usage of this method may lead to ClassCastException:

    List<Integer>[] intList = new ArrayList<Integer>[]{Arrays.asList(1)}; List<? extends Number>[] numList = intList; numList[0] = Arrays.asList(1.01) // ClassCastException
  • whenever a type of the form Class<T> appears, the type T should be a reifiable type. The same is true for types of the form T[]
  • reflection and primitive types: int.class == Class<Integer>, because int is not a reference type:

    int.class.newInstance() // Exception int.class.cast(o) // Exception
  • Array.newInstance(int.class, size) // int[] because int[] as a reference type (int[].class == Class<int[]>)
  • collections checked XXX provides runtime security for adding new elements to the underlying collection
  • to ensure security when dealing with 3rd pfd8syt&0)u@FTZUWrC&yziSYfd8syt&0)u@FTZUWrC&yziSYarty library or legacy code one may use the following:
    1. use CheckedCollectionsfd8syt&0)u@FTZUWrC&yziSY
    2. use specialization (ListString implements List<String>): delegation, iheritance — are two possibilities
  • specialization is not applyable to wildcards
  • in case multiple bounds e.g. <T extends Interface1 & Interface2> the mostleft is taken for the erasure i.e. Interface1 is the type of T in runtime
  • generification is hard to apply to class hierarchy because of the difference in the method sugnatures

  • interface Function<A, B, X extends Throwable> { B apply(A) throws X; }
  • when dealing with the class hierarchy and Generics e.g. Strategy pattern. it is a good idea to make all classes that have subclasses abstract. And all concrete classes — final.
  • .getThis() method is usefull when this needs to be provided in the Base class with more specific type:

    abstract Type<T extends Type<T>> { private final Strategy<T> str; public void foo(){ str.apply(getThis()); } protected abstract T getThis(); }

Networking

  • always use TCP_NODELAY when opening a new socket. There are two algos used in TCP: “Nagle’s” and “Delayed ACK”
  • HTTP/2 window flow control may slow down performance due to window update response. TIP: sendWindowUpdate() once for a bunch of requests
  • HPACK is a stateful compressing algo (compresses headers in HTTP/2)
  • writeBuffers() uses internal global lock protecting socket i.e. no need to lock it on Java layer
  • HttpClient API based on ByteBuffer pool. But only 20% of buffers return to pool
  • DirectByteBuffer is better for IO while HeapByteBuffer is better for SSL. As SSL is the future of the web prefer HeapByteBuffer
  • ByteBuffer.wrap() wraps raw data with array without copying

Servlets

  • all servelts are executed in a single JVM
  • Servlet is stored in the server as an object instance. Therefore all of its external resources (DB coonection; threads etc) can be stored too
  • from developer’s perspective each client is a dedicated thread that calls servlet.doXX() method
  • init() is guaranteed to be called before this servlet processes any request (always call super.init())
  • destroy() is called before servlet is unloaded. In its implementation destroy() must free all resources, possibly persisting them
  • ~~SingleThreadServlet~~ is deprecated – never use it.
  • for handling uploads an utility class MultiPartRequest can be used
  • parameters from request can be read by: getParameter(name) OR getInputStream(). Do not mix these two approaches
  • there sre several ways for session tracking:
    1. user authorisation
    2. hidden fields: pros – works everywhere; cons – requires sequence of dynamically generated forms
    3. URL rewriting:
      a) as extra path information;
      b) parameters (may not work with POST)
      c) custom change
    4. cookies – most widely used. Cons – user may not accept cookies
    5. In servelt API >= 2.0 HttpSession object is denoted to perform session tracking. underlying implementation varies between cookies and URL rewriting
  • HttpSession may be used to store arbitrary number of objects associated with the user. Such object must implement Serializable
  • If server uses URL rewriting each URL published by this server must be encoded using HttpSeesion.encodeRedirectUrl()
  • HttpSessionBindingListner can be implemented to catch bind and unbind object events on HttpSession
  • Being secure for a web app means:
    1. authentication – being able to identify parties involved
    2. confident – ensuring that only authorised parties can understand each other
    3. integrity – being able to verify that the content of the communication is not changed during the transmission
  • Basic Http authentication does not provide neither confidentiality nor integrity
  • HttpSession.isNew() has some pitfalls:
    1. it returns true if the client has never seen the session
    2. it returns false for preexisting sessions and it shows that the users have visited the WebApp before, not that they have visited the servlet before
  • only responses could be compressed. This is due to initial HTTP design as req-res protocol. It is not designed to send big requests. Another idea — is that compression is a part of communication and browser do not know if the server supports compression.
  • to enable HTTPS add the following to web.xml:
<security-constraint>
    <!-- ... -->
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>

</security-constraint>

Database connectivity

  • JDBC steps:
    1. load driver e.g. Class.forName()
    2. open connection: DriverManager.getConnection()
    3. acquire statement: connection.generateStatement()
    4. execute SQL: statement.executeQuery()
  • ResultSet.wasNull() is usefull when checking for null record is needed. But using this method is messy:

    int age = rs.getInt("age"); if(!rs.wasNull()) // do stuff
  • ResultSet.getObject() returns null if the record was null.
  • neither statement.getResultSet() nor ResultSet.getUpdateCount() must not be called more than once per each Statement.execute()
  • PreparedStatement.setString(str) automatically encodes any special character in the str
  • a new connection object has autocommit property set to true. This means that each SQL statement will be executed and immideatelly commited. To avoid this use Connection.setAutoCommit(false)
  • Connection object with autocommit set to false can not be shared
  • use connection pool to increase performance
  • CallableStatement executes precompiled procedure on the RDBS
  • Large data sets can cause troubles. The driver must be choosen and tested prior deployment to handle such data.
  • To read large data use ResultSet.getXXStream(). Important is that any other getXX() called on this ResultSet will close the stream.

References