Studiewijzer module 02
Design Patterns and Principles


Deze samenvatting is geschreven door Aron Kool tijdens het leren voor het OCP examen. Hij gaf geen garanties over de juistheid van deze samenvatting.

Algemeen

  • null instanceof Object levert false op. Compileert dus wel en geeft geen NullPointerException.

  • FunctionalInterface is altijd een interface met exact 1abstracte methode.

  • Volgens OCP heeft Composition altijd de voorkeur boven inheritance

  • java.util.Comparator heeft de methode int compare(T o1, To2)

  • java.lang.Comparable heeft de methode int compareTo(T o)

  • Classes als OptionalInt hebben een methode getAsInt() deze geeft een primitieve int terug

  • Classes als IntStream geven bij methodes als min() of max() een OptionalInt terug. Omdat deze bij een lege lijst geen waarde opleveren geven ze dus een OptionalInt terug.

  • Thread implements Runnable.

  • Een peek() op een stream is niet terminating, dus zonder een terminating methode zoals collect() doet de peek() niets.

Collections

ArrayDeque

  • add(e) voegt toe aan het einde

  • offer(e) voegt toe aan het einde

  • push(e) voegt toe aan het begin

  • remove() verwijdert en get aan het begin

  • poll() verwijdert en get aan het begin

  • pop() verwijdert en get aan het begin

  • element() get aan het begin, laat element in collection

  • peek() get aan het begin, laat element in collection

Map

De merge(key,value,BiFunction<V,V,V>) methode in een map werkt als volgt:

  • Bestaat de key niet, dan wordt value gezet voor die key

  • Bestaat de key wel, dan gaat de oude value en de nieuwe value in de BiFunction en het resultaat wordt gezet voor de key

Generics

Simpel voorbeeldje voor de ? extends …​ en de ? super …​:

List<? extends Number> foo3 = new ArrayList< Number >();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList< Integer >(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList< Double >();  // Double extends Number
List<? super Integer> foo3 = new ArrayList< Integer >();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList< Number >();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList< Object >();   // Object is a superclass of Integer

DateTime

  • Duration gaat over tijd (seconden, minuten, uren)

  • Period gaat over datums (days, months, years)

  • Duration.ofMinutes(1).equals(Duration.ofSeconds(60)); = true

  • Duration heeft wel een ofDays methode die 24 uur representeert.

  • Period.ofDays(7).equals(Period.ofWeeks(1)); = true

  • Period.ofDays(365).equals(Period.ofYears(1)); = false

Functional Interfaces

Consumer<T>

Represents an operation that accepts a single input argument and returns no result.

BiConsumer<T,U>

Represents an operation that accepts two input arguments and returns no result.

Supplier<T>

Represents a supplier of results.

Function<T,R>

Represents a function that accepts one argument and produces a result.

BiFunction<T,U,R>

Represents a function that accepts two arguments and produces a result.

Predicate<T>

Represents a predicate (boolean-valued function) of one argument.

BiPredicate<T,U>

Represents a predicate (boolean-valued function) of two arguments.

UnaryOperator<T>

Represents an operation on a single operand that produces a result of the same type as its operand.

BinaryOperator<T>

Represents an operation upon two operands of the same type, producing a result of the same type as the operands.

Exceptions

Closable en Autoclosable

Closable

void close() throws IOException

Autoclosable

void close() throws Exception

De close wordt in het geval van een try-with-resource aangeroepen in het 'magische' finally-block. Dit zorgt voor een verschil in de afhandeling ten opzicht van Closable:

  • Als zowel de try als de handmatige close() een exception gooien, wordt de exceptie uit de try gesuppressed.

  • Als zowel de try als de close() van een AutoClosable in een try-with-resource een exception gooien, wordt de exceptie uit de close() gesuppressed.

Binnen de try met auto closables, moet iedere constructor die een exception kan gooien, los gedefinieerd worden, omdat er een referentie naar het autocloseable object nodig is om deze te kunnen closen. Dus niet:

try(InputStream i = new ObjectInputStream(new FileInputStream())) { // (1)
}
  1. Er is geen referentie naar de FileInputStream dus die kan niet automatisch geclosed worden

Concurrency

CyclicBarrier

Een CyclicBarrier laat threads wachten op andere threads. Dit is een handige manier om bijvoorbeeld threads tegelijkertijd te laten beginnen.

Een CyclicBarrier maak je aan via:

CyclicBarrier barrier = new CyclicBarrier(2); // (1)
... doe werk ...
barrier.await(); // (2)
  1. Het constructor argument 2 is het aantal threads die de Barrier moeten passeren om verder te gaan.

  2. laat de thread wachten totdat het aantal wachtende threads voor deze barrier de ingestelde limiet bereikt heeft. Alle wachtende threads worden daarna vrijgegeven.

Om live- en deadlocks te voorkomen kun je een timeout meegeven aan de barrier: barrier.await(10, TimeUnit.SECONDS);

De CyclicBarrier#await(timeout, unit) kan de volgende exceptions gooien (overgenomen vanuit de JavaDoc):

InterruptedException

if the current thread was interrupted while waiting

TimeoutException

if the specified timeout elapses. In this case the barrier will be broken.

BrokenBarrierException

if another thread was interrupted or timed out while the current thread was waiting, or the barrier was reset, or the barrier was broken when await was called, or the barrier action (if present) failed due to an exception

ExecutorService

De class Executors is een factory voor executor services. Hiermee kunnen eenvoudig Runnables en Callables uitgevoerd worden. Een executor service is eigenlijk een thread pool of een object die het uitvoeren van meerdere taken tegelijkertijd beheert, zoals hoeveel er tegelijkertijd plaats kunnen vinden.

Voorbeelden hoe je een ExecutorService aanmaakt:

ExecutorService executorService1 = Executors.newSingleThreadExecutor();   // 1
ExecutorService executorService2 = Executors.newFixedThreadPool(10);      // 2
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);  // 3

De FixedScheduledThreadPool lijkt op de ScheduledThreadPool. De laatste heeft de mogelijkheid om taken pas uit te voeren na een gespecificeerde tijd.

De methode execute() accepteert alleen een Runnable.

De methode submit() accepteert zowel een Runnable als een Callable.

Callable heeft een generic, welke de methode call() throws Exception, returned. Runnable is een void

De Execute() is een Void. De submit() levert een Future op. Via future.get() kun je de resultwaarde opvragen. Voor Runnable is dit null, zodra de taak klaar is, voor Callable is dit het geretourneerde object.

Als je klaar bent met de ExecutorService moet je de ExecutorService.shutdown() aanroepen. Deze zorgt ervoor dat er geen nieuwe executes/submits meer geaccepteerd worden. Alles wat al bezig is, gaat gewoon door.

Note
De combinatie tussen een FixedThreadPool en een CyclicBarrier waarbij de ThreadPool kleiner is dan de Barrier, levert een Deadlock op omdat de Barrier te threads vasthoudt!

ForkJoinTask

Een ForkJoinTask maak je aan via een JoinForkPool welke je aanmaakt als: ForkJoinPool pool = new ForkJoinPool(numberOfProcessors); of Executors.newWorkStealingPool(numberOfProcessors);.

ForkJoinPool is een implementatie van ExecutorService. Er is een extra submit(ForkJoinTask) en execute(ForkJoinTask).

In OCP komen de subclasses RecursiveTask<T> en RecursiveAction aan bod. RecursiveAction is in principe een RecursiveTask, maar dan zonder return type.

De methode die verplicht geimplementeerd moet worden is public T compute(). De inhoud is vergelijkbaar met een Runnable of een Callable behalve een recursieve toevoeging, wat meestal hier op neerkomt:

protected List< Integer > compute()
{
  List< VoorbeeldTask > subTasks = new ArrayList<>();
  List< Integer > result = new ArrayList<>();
  if(zwareTaak.size()==1)
  {
    result.add(doeZelf(zwareTaak.remove(0)));
  }
  else
  {
    VoorbeeldTask subTask1 = new VoorbeeldTask(eersteHelft(zwareTaak));
    VoorbeeldTask subTask2 = new VoorbeeldTask(tweedeHelft(zwareTaak));
    subTask1.fork(); // (1)
    subTask2.fork(); // (1)
    subTasks.add(subTask1);
    subTasks.add(subTask2);
  }
  for(VoorbeeldTask task : subTasks)
  {
    result.addAll(task.join()); // (2)
  }
  return result;
}
  1. De fork methode laat dus een subtaak draaien met daarin een deelprobleem. De later aangeroepen join() geeft het resultaat van de subtaken terug en voegt dat bij het eigen resultaat.

  2. De submit methode op de Pool geeft in dit geval ook zelf een taak terug (wat in principe gewoon een fork() is). Om het eindresultaat te bekijken, kun je hierop een join() aanroepen.

ForkJoinTask< List< Integer > > task = pool.submit(new VoorbeeldTask(beginProbleem));
List< Integer > result = task.join();

Concurrent Collections

  • ConcurrentHashMap is een map met synchronized mutatiemethoden. De ConcurrentHashMap geeft nog steeds een ConcurrentModificationException bij aanpassen tijdens het itereren.

  • CopyOnWriteArrayList geeft iedere lezer een eigen lijst van de huidige status. Een ConcurrentModificationException kan hierbij dus niet optreden. Het schrijven naar de lijst heeft wel een lock. Hetzelfde geldt voor de CopyOnWriteArraySet.

  • ConcurrentSkipListSet garandeert sortering en is de threadsafe equivalent van de TreeSet. Er bestaat ook een ConcurrentSkipListMap.

Java IO

Serialisatie

  • ObjectInputStream, welke in de constructor een (File)InputStream meekrijgt, heeft een methode readObject() waarmee je een Object op kunt vragen.

  • ObjectOutputStream, welke in de constructor een (File)OutputStream meekrijgt, heeft een methode writeObject() waarmee je een Object weg kunt schrijven.

  • Deserialisatie zet de state terug in het object zoals het was, daarbij wordt dus geen constructor of andere initialiserende code aangeroepen.

  • Deserialisatie levert een Object op, die je zelf nog moet casten. Dit komt omdat tijdens deserialisatie je een ander type object terug kan geven dan in de stream staat. Het type dat teruggegeven wordt is dus op compilatie tijd niet bekend en kan niet opgenomen worden als return type of via generics getypeerd worden.

Mark, reset, skip

InputStream heeft onder andere de methoden mark(), reset() en skip().

  • Skip(n) slaat de eerst volgende n bytes over

  • Mark(readlimit) markeert de huidige cursor, het limit is om de mark te invalideren na x aantal bytes (en heeft dus niets te maken met de plaats van de mark).

  • Reset() zet de cursor terug op de plaats van de eerder gemaakte mark. Als er geen geldige mark meer is, wordt er een IOException gegooid.

Niet iedere stream heeft ondersteuning voor marks

NIO2

  • Files.lines() levert een Stream<String> op.

  • Files.readAllLines() levert een List<String> op.

  • Files.isSameFile(p1,p2) controleert of er echt naar hetzelfde pad gewezen wordt. Er wordt dus niet gecontroleerd of een bestand identiek is.

  • In het geval van een isSamePath() geeft de equals() ook een true terug.

Normalize

The normalize method removes any redundant elements, which includes any "." or "directory/.." occurrences. Both of the preceding examples normalize to /home/joe/foo.

It is important to note that normalize doesn’t check at the file system when it cleans up a path. It is a purely syntactic operation. In the second example, if sally were a symbolic link, removing sally/.. might result in a Path that no longer locates the intended file.

Relativize

  1. Path p1 = Paths.get("home");

  2. Path p3 = Paths.get("home/sally/bar");

  3. // Result is sally/bar

  4. Path p1_to_p3 = p1.relativize(p3);

  5. // Result is ../..

  6. Path p3_to_p1 = p3.relativize(p1);

Find

Files.find(start, maxDepth, matcher, options). De MaxDepth is om lussen de voorkomen bij symbolic links.

De FileVisitOptions kunnen alleen een FOLLOW_LINKS zijn.

In tegenstelling tot de find() uit java.io.File is de matcher geen Predicate< File >, maar een BiPredicate< Path, BasicFileAttributes >.

JDBC

Een JDBC 4+ Driver heeft altijd het bestand: META-INF/services/java.sql.Driver.

Bij JDBC <4, moest altijd een eerste classloader-aanroep gedaan worden naar de Driver. Bijvoorbeeld: Class.forName("oracle.jdbc.driver.OracleDriver").

Statement

  • execute(sql) geeft een boolean terug, met true als het resultaat een resultset is, false als het een updatecount is.

  • executeUpdate(sql) geeft het aantal rijen terug dat geüpdated is.

  • executeQuery(sql) geeft een ResultSet terug.

  • execute(sql) kan voor zowel SELECT als CUD-queries gebruikt worden. De anderen gooien een SQLExecption bij verkeerd gebruik.

Resultset

  • CONCUR_READ_ONLY: The ResultSet object cannot be updated using the ResultSet interface.

  • CONCUR_UPDATABLE: The ResultSet object can be updated using the ResultSet interface.

  • TYPE_FORWARD_ONLY: The result set cannot be scrolled; its cursor moves forward only, from before the first row to after the last row. The rows contained in the result set depend on how the underlying database generates the results. That is, it contains the rows that satisfy the query at either the time the query is executed or as the rows are retrieved.

  • TYPE_SCROLL_INSENSITIVE: The result can be scrolled; its cursor can move both forward and backward relative to the current position, and it can move to an absolute position. The result set is insensitive to changes made to the und


« Vorige module Volgende module »