Express Yourself

with Java 8

@benjiweber

@benjiweber

http://tech.unruly.co

I <3 Java

"I wish Java had X"

"How can I do X with Java?"

Heresy

Old News

Exciting Times

Usually

Streams

CompletableFutures

Optionals

What I find exciting…

Our features are cut

These days are over!

Now it's up to us

Language features can become library features

Without magic

Readable

Debuggable

() -> {
	someCode();
}

void foo() {
	someCode();
}
(param1, param2) -> {
        return "foo";
}

String foo(String param1, String param2) {
        return "foo";
}
BiFunction<String, String, String> f = (param1, param2) -> {
        return "foo";
}

Why?

Extend

public static <T> Optional<T> 
    when(boolean condition, Supplier<T> then) {
        return condition 
            ? Optional.ofNullable(then.get()) 
            : Optional.empty();
}

Project Coin

try (BufferedReader br = reader()){
    br.read();
}

If only we had had Lambdas

using(reader(), br -> {
    br.read();
});
public static <T extends AutoCloseable> 
    void using(T t, ExceptionalConsumer<T> consumer) {
        try {
            consumer.accept(t);
        } finally {
            try {
                t.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
}

No more waiting

JLS

Java Laws of physicS

Know them well and you can bend them

New Laws

Lambdas

Method References

Default Interface Methods

Static Interface Methods

Structural Typing

Structural Typing

Function<String, Boolean> f = s -> true;
Predicate<String> p = s -> true;

Lambdas & existing code

new File(".").list((name, file) -> true);

Expressive code

BiFunction<Integer, Integer, Integer> adder = (a,b) -> a+b;

interface Calculation {
    int operation(int a, int b);
}
Calculation adder = (a,b) -> a+b;

Avoid

java.util.function.*

(like primitives)

Partial Application

track("example.com", userData, CLICK);
track("example.com", userData, PLAY);

Partial Application

interface ExampleComTracker {
    void track(EventType event);
}

ExampleComTracker exampleCom = event ->
    track("example.com", userData, event);
        
exampleCom.track(CLICK);
exampleCom.track(PLAY);

Bending the Laws

Same Erased Type

public void doSomething(List<String> strings) { }
public void doSomething(List<Integer> ints) { }

Same Erased Type

public void doSomething(List<> strings) { }
public void doSomething(List<> ints) { }

Same Erased Type

public interface ListStringRef extends Supplier<List<String>>{}
public void doSomething(ListStringRef strings) { }

public interface ListIntegerRef extends Supplier<List<Integer>>{}
public void doSomething(ListIntegerRef ints) { }

doSomething(() -> asList("aa","b"));
doSomething(() -> asList(1,2));

Know the laws

Bend the laws

Java Lang #1 complaint?

Verbosity

Problem?

Primitive Obsession

void update(int volume) { }

Max value?

Where are all the places volume is used?

Why is it needed?

Primitive Obsession

void update(Volume newValue) { }

Why do we persist?

public class Volume {
    
    private int volume;

    public Volume(int volume) {
        if (volume > 11) throw new IllegalArgumentException();
        this.volume = volume;
    }
    public int getVolume() {
        return volume;
    }

    public void setVolume(int volume) {
        this.volume = volume;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Volume volume1 = (Volume) o;

        return volume == volume1.volume;

    }
    @Override
    public int hashCode() {
        return volume;
    }
    @Override
    public String toString() {
        return "Volume{" +
                "volume=" + volume +
                '}';
    }
    
}

42 Lines

All I wanted was a single value

Start Small

public class Volume {
    public final int value;

    public Volume(int value) {
        if (value > 11) throw new IllegalArgumentException();
        this.value = value;
    }
}

Irrational Fear of public

Microtypes

public interface Volume {
    int value();
    static Volume volume(int value) { return () -> value; }
}

update(volume(11));

Microtypes

public interface Volume {
    int value();
    static Volume volume(int value) { 
        if (value > 11) throw new IllegalArgumentException();
        return () -> value; 
    }
}

update(volume(11));

Add Behaviour

public interface Volume {
    int value();
    static Volume volume(int value) { return () -> value; }
    
    default Volume turnUp() { return volume(value() + 1); }
}

More Fields

interface Paint {
    int red();
    int green();
    int blue();
    static Paint paint(int red, int green, int blue) {
        return new Paint() {
            public int red() { return red; }
            public int green() { return green; }
            public int blue() { return blue; }
        };
    }
}

Equals/hashCode

interface Paint {
    int red();
    int green();
    int blue();
    static Paint create(int red, int green, int blue) {
	abstract class PaintValue extends Value<Paint> 
                                  implements Paint {}
        return new PaintValue() {
            public int red() { return red; }
            public int green() { return green; }
            public int blue() { return blue; }
        }.using(Paint::red, Paint::green, Paint::blue);
    }
}

No Magic

public class Value<T> {

    public T using(Function<T,?>... props) { 
        this.props = asList(props); 
    }

    int hashCode() {
        return props.stream()...
    }
    
}

As simple as a class

Named Parameters

Builder Pattern

Callsite Clarity

Person person = new Person("benji", "weber", 180);

Callsite Clarity

Person person = new Person(
    firstName: "benji",
    lastName: "weber", 
    heightInCentimetres: 180
);

Callsite Clarity

Person person = person()
    .firstName("benji")
    .lastName("weber")
    .height(centimetres(180));

What stops us?

public class PersonBuilder {
    private String firstName;
    private String lastName;
    private Person.Centimetres height;

    public PersonBuilder setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }
    public PersonBuilder setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    public PersonBuilder setHeight(Person.Centimetres height) {
        this.height = height;
        return this;
    }

    public Person createPerson() {
        return new Person(firstName, lastName, height);
    }
}

26 lines; Duplication

All I wanted was names

Apply laws

Structural Typing

Goal

Person person = person()
    .firstName("benji")
    .lastName("weber")
    .height(centimetres(180));
    public static SpecifyFirstName person() {
        return firstName 
            -> lastName 
            -> height 
            -> new Person(firstName, lastName, height);
    }
    
    public interface SpecifyFirstName {
        SpecifyLastName firstName(String firstName);
    }
    public interface SpecifyLastName {
        SpecifyHeight lastName(String lastName);
    }
    public interface SpecifyHeight {
        Person height(Centimetres height);
    }

Now

Half as long

Compile-time safety

Pattern Matching

shape.match()
    .when(Rectangle.class, 
        rect -> "Rectangle " + rect.width() + "x" + rect.length())
    .when(Circle.class, 
        circle -> "Circle with radius " + circle.radius())
    .when(Cube.class, 
        cube -> "Cube with size " + cube.size());

Not a new lang feature

interface Shape extends Case3<Rectangle, Circle, Cube> { }
interface Cube extends Shape { ... }

public interface Case3<T,U,V> {
    default MatchBuilderNone<T,U,V> match() { ...

Decomposition

String address = customer.match()
    .when(a(Customer::customer).matching(
        "Benji",
        "Weber",
        an(Address::address).matching(
            a(FirstLine::firstLine).matching(_,_),
            _
        )
    )).then((houseNo, road, postCode) -> houseNo + " " + road + " " + postCode)
    .otherwise("unknown");.

What about Libraries?

Tedious

asList("foo","bar")
    .stream()
    .map(String::length)
    .collect(Collectors.toList());

Why not?

asList("foo","bar")
    .map(String::length);

List is a pain to extend

interface ForwardingList<T> extends List<T> {
    List<T> impl();
 
    default int size() {
        return impl().size();
    }

    ...
}
interface Mappable<T> extends ForwardingList<T> {
    default <R> List<R> map(Function<T,R> f) {
        return impl().stream().map(f).collect(Collectors.toList());
    }
}

Mappable<T> mappable = () -> asList("foo","bar");
List<Integer> lengths = mappable.map(String::length);

Wrapping up

Language to Library

Question the Impossible

Question Tradition

How do I express myself, in Java

Thanks for Listening!

@benjiweber

Send me your missing lang features!

http://benjiweber.com/blog/posts/java

Any questions?

We're hiring!

http://tech.unruly.co

talent@unrulygroup.com