Java

Advanced Topics






By Daniël Bos

Agenda

  1. Enumerations
  2. Generics
  3. Collections
  4. Reflection
  5. Annotations
  6. Concurrency
  7. Syntactic Sugar

Enumerations

Looks like C++ ...


enum Animal { CAT, DOG, MOUSE }

Animal animal = Animal.CAT;
switch(animal) {
   case CAT:
      // do cat things
      break;
   case DOG:
      // do dog things
      break
   default:
      // do default things
}

... but very different

Enums are classes


public class Animal {
   public static final Animal CAT   = new Animal();
   public static final Animal DOG   = new Animal();
   public static final Animal MOUSE = new Animal();
}

Cool stuff


Animal.values() => Animal[];
Animal.valueOf(String s) => Animal;

Animal cat = Animal.CAT;
cat.name() => "CAT"

Cooler stuff


enum Animal {
   CAT("meow"), DOG("woof"), MOUSE("squeek");
	
   private String sound;
	
   Animal(String sound) {
      this.sound = sound;
   }
	
   String getSound() {
      return sound;
   }
}

Animal cat = Animal.CAT;
cat.getSound();

Generics

Similar to C++ templates

What's the problem?


List strings = new ArrayList();
strings.add("hello");
strings.add("world");
// no problem!
strings.add(42);

This works, until ...


for (Object o : strings) {
   // ... RuntimeException here!
   String s = (String) o;
}

What's the solution?


List<String> strings = new ArrayList<String>();
strings.add("hello");
strings.add("world");
// Compile-time exception!
strings.add(42);

And when you use it ...


for (String s : strings) {
   // ... No need to cast!
}

This is going to look ugly ...

Creating generic code


public Pair<K, V> {
   private K key;
   private V value;
	
   public Pair(K key, V value) {
      this.key = key;
      this.value = value;
   }
}

public class Util {
   public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2);
}

This is going to be confusing ...

Does this work?


public void print(List<Object> list) {
   for (Object elem : list)
      System.out.println(elem);
}

List<Integer> ii = new ArrayList<Integer>();
print(ii);

No! :-( Integer is not the same as Object, though it inherits from Object.

This does work


public void print(List<?> list) {
   for (Object elem : list)
      System.out.println(elem);
}

List<Integer> ii = new ArrayList<Integer>();
print(ii);

But now we throw out type-safety!

If all objects that can be printed implement the "Printable" interface, we can do better.

Generic inheritance


public void print(List<? extends Printable> list) {
   for (Object elem : list)
      System.out.println(elem);
}

List<Integer> ii = new ArrayList<Integer>();
print(ii);

Confusing: extends is used for both classes and interfaces.

The other way around

Suppose your method accepts a List that you plan to add numbers to. How to make sure you can't use other lists?


public void addNumbers(List<? super Integer> list) {
   for (int i = 0; i < 10; i++)
      list.add(i);
}

Now you can supply a List of Integers, Numbers or Objects, but not a List of Strings or Widgets.

A word of warning

Erasure

Because Generics were added to the language (in SE 1.5/5.0) and byte-code compatibility is an important feature of Java, all Generics are erased during compilation, after type-checking.


This is usually okay, but it limits the information you can get using reflection.

Collections

Collections

Don't implement them yourself, almost any type already exist!

  • Set: HashSet, SortedSet, TreeSet, ...
  • List: ArrayList, LinkedList, Stack, ...
  • Queue: LinkedList, PriorityQueue, ...
  • Map: HashMap, Properties, ...

If you really need to implement one yourself, start from one of the abstractXxx collection types.

Algorithms

  • Sorting
  • Shuffling
  • Searching

  • Arrays.asList(array)
  • Collections.synchronizedList(list)
  • Collections.unmodifiableList(list)
  • Collections.singleton(value)
  • Collections.nCopies(num, value)
  • Collections.emptyList()

Type-safe


Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "One");
map.put(2, "Two");
//map.put("Three", 3);

for (String value : map.values()) {
   // ...
}
for (Integer key : map.keySet()) {
   // ...
}
for (Map.Entry<Integer, String> entry : map.entrySet()) {
   entry.getKey();
   entry.getValue();
}

Comparable


class YourType {
   @Override
   public boolean equals(Object other);
	
   @Override
   public int hashCode();
}

class YourType implements Comparable<YourType> {
   @Override
   public int compareTo(YourType other) {
      // return -1, 0, +1
   }
}

Comparator


public class Person {
   public static final Comparator<Person> ORDER_FIRSTNAME
      = new Comparator<Person>() {
         @Override
         public int compare(Person p1, Person p2) {
            return p1.firstname.compareTo(p2.firstname);
         }
   };
   public static final Comparator<Person> ORDER_AGE
      = new Comparator<Person>() {
         @Override
         public int compare(Person p1, Person p2) {
            return p1.age - p2.age;
         }
   };
}

Collections.sort(persons, Person.ORDER_FIRSTNAME);

Reflection

Meta-class

Every class has an associated meta-class that can be used to get all sorts of information about the class and to interact with the class.


Object.class;

Object o = new Object();
o.getClass();

Besides that, there's a Class utility class (note the capitalization):


Class c = Class.forName("...");

Instanciating


try {
   Class c = Class.forName("com.example.Test");
   Test t = (Test) c.newInstance();
} catch (ClassNotFoundException e) {
   // ...
} catch (InstantiationException e) {
   // ...
} catch (IllegalAccessException e) {
   // ...
}

Many many possible exception!

Inspecting a class


// Get all the constructors:
Constructor[] constructors = clazz.getDeclaredConstructors();
// Get the constructor matching a specific signature:
Constructor constructor = clazz.getDeclaredConstructor(String.class);
// Instantiate an object:
Example example = (Example) constructor.newInstance("String");

// Get all the methods (public and private):
Method[] methods = clazz.getDeclaredMethods();
// Get all visible methods (including from super-classes)
Method[] methods = clazz.getMethods();
// Get the method for a specific signature:
Method method = clazz.getDeclaredMethod("setExample", String.class);
// Invoke the method on an object:
method.invoke(example, "New String");

Annotations

You've probably seen


@Override
public void someMethod() {}

@SuppressWarnings
public void badMethod() {}

@Deprecated
public void oldMethod() {}

Annotations provide information to the compiler, some can be made available at runtime through reflection.

You can create them


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Shared {
   boolean restricted() default false;
}

And use them like:


public class Users {
   @Shared
   public int getCount();
	
   @Shared(restricted=true)
   public User getUserById(int id);
   
   public void hidden();
}

And use them


// Iterate over all the methods in the class
for (Method m : Users.class.getMethods()) {
   Shared shared = m.getAnnotation(Shared.class);
   // If the annotation is present
   if (shared != null) {
      if (shared.restricted()) {
         // share privately
      } else {
         // share publicly
      }
   }
}

Concurrency

Threads


public class MyRunnable implements Runnable {
   @Override
   public void run() {
      // ...
   }
}
Runnable r = new MyRunnable();
new Thread(r).start()

public class MyThread extends Thread {
   @Override
   public void run() {
      // ...
   }
}
new MyThread().start()

Sleeping/Waiting


try {
   Thread.sleep(4000);
} catch (InterruptedException e) {
   // ...
}

while (true) {
   doHeavyWork();
   if (Thread.interrupted()) {
      // ...
   }
}

try {
   AnotherThread.join()
} catch (InterruptedException e) {
   // ...
}

Synchronization (1)

The problem:


public class Counter {
   private int c = 0;
   public void inc() { c = c + 1; }
   public void dec() { c = c - 1; }
}

One way to solve it:


public class Counter {
   private int c = 0;
   public synchronized void inc() { c = c + 1; }
   public synchronized void dec() { c = c - 1; }
}

Atomic variables


public class Counter {
   private AtomicInteger c = new AtomicInteger();
   
   public void inc() { c.incrementAndGet(); }
   public void dec() { c.decrementAndGet(); }
}

Synchronization (2)


public void inc() {
   synchronized(this) {
      c = c + 1;
   }
}

private Object lock = new Object();

public void inc() {
   synchronized(lock) {
      c = c + 1;
   }
}   

Deadlock


public class Friend {
   public synchronized void greet(Friend f) {
      System.out.println("Hi!");
      f.greetBack(this);
   }
   
   public synchronized void greetBack(Friend f) {
      System.out.println("Hi too!");
   }
}

Friend f1 = new Friend();
Friend f2 = new Friend();

new Thread() {
   public void run() { f1.greet(f2); }
}.start();
new Thread() {
   public void run() { f2.greet(f1); }
}.start();

Starvation


public class Example {
   private boolean busy = false;
   public synchronized boolean isBusy() {
      return busy;
   }
   public synchronized void doHeavyWork() {
      busy = true; heavyWork(); busy = false;
   }
}

Example e = new Example();
new Thread() {
   public void run() {
      while(true) { e.doHeavyWork(); }
   }
}.start();

while(true) {
   Thread.sleep(1000);
   System.out.println("busy? " + e.isBusy());
}

Livelock


public class Person {
   private boolean left;
   
   public void pass(Person p) {
      while (true) {
         if (p.canPass(left)) return;
            else left = !left;
         Thread.yield();
      }
   }
   public boolean canPass(boolean other) {
      if (left == other) {
         left = !left;
         return false;
      } else return true;
   }
}

Wait/Notify


public class Drop {
   private boolean empty = true;
   private String message;
   
   public synchronized String take() {
      while (empty) wait();
      empty = true;
      notifyAll();
      return message;
   }
   public synchronized void put(String message) {
      while (!empty) wait();
      empty = false;
      this.message = message;
      notifyAll();
   }
}

Executors


ExecutorService pool = Executors.newFixedThreadPool(10);

while (!pool.isShutdown()) {
    pool.execute(new Handler(serverSocket.accept()));
}

Fork/Join


public class MyAction extends RecursiveAction {
   protected void compute() {
      int size = list.size();
      if (size < 100) {
         computeDirectly();
      } else {
         invokeAll(new MyAction(list.subList(0, size / 2)),
                   new MyAction(list.subList(size / 2 + 1, size)));
   }
   protected void computeDirectly() {
      // ...
   }
}

ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new MyAction(bigList));

Futures


ExecutorService pool = Executors.newFixedThreadPool(10);

public Future<Document> download(String url) {
   return pool.submit(new Callable<Document>() {
      @Override
      public Document call() {
         return doDownload(url);
      }
   }
}

Future<Document> future = download("http://example.com/robots.txt");

future.isDone();
future.cancel(mayInterrupt);
future.isCancelled();
Document future.get(timeout);
Document future.get();

Syntactic Sugar

  • Variadic arguments
  • For-each syntax
  • Diamond operator
  • Lambda's
  • Method references
  • Optionals

Variadic arguments


void example(String ... args);

example("one");
example("one", "two");

void example(String[] args);

For-each syntax


for (int i = 0; i < list.size(); i++) {
   String item = list.get(i);
   // ...
}

List list;
for (String item : list) {
    // ...
}

Diamond operator


Map<Integer, Pair<String, List<Double>>> map
   = new HashMap<Integer, Pair<String, List<Double>>>()

Map<Integer, Pair<String, List<Double>>> map = new HashMap<>()

Lambda's


List<Person> persons;
Collections.sort(persons, new Comparator<Person>() {
   @Override
   public int compare(Person p1, Person p2) {
      return p1.age - p2.age;
   }
});

Collections.sort(persons, (p1, p2) -> p1.age - p2.age);

Method references


public class Person {
   public static int CompareByAge(Person p1, Person p2) {
      return p1.age - p2.age;
   }
}

Collections.sort(persons, Person::CompareByAge);

Optionals


for (Person p : db.getPeople()) {
   if (p.age > 20) {
      Address a = p.getAddress();
      if (a != null) {
         System.out.println(a.getCity());
      }
   }
}

db.streamPeople()
   .filter(p -> p > 20)
   .flatMap(p -> p.getAddress())
   .flatMap(Address::getCity)
   .forEach(System.out::println);

Questions?











THE END

Created with Reveal.JS