Manuel Mauky@manuel_mauky
Unveränderliche Daten im GriffImmutable Data mit Java
Immutable Object
"An immutable object is an object whose state cannot be modified after it is created"
wikipedia
- Immutable Data und funktionale Programmierung?- Nutzen?- Herausforderungen?- Immutable Collections- Immutable Objekte- Optics- andere JVM-Sprachen
- Kotlin- Frege
Immutable Data und
Funktionale Programmierung?
Definition "pure Funktion"- Das Ergebnis der Funktion hängt nur von den Argumenten ab
- Bei gleichen Argumenten liefert die Funktion stets das gleiche Ergebnis
- Die Funktion produziert keine Seiteneffekte
class SomeClass {
int doSomething(String s) {return s.trim().length();
}
}
class SomeClass {private int x = 0;
int doSomething(String s) {x = 1;return s.trim().length();
}
}
class SomeClass {private final int x = 0;
int doSomething(String s) {x = 1; //compile errorreturn s.trim().length();
}
}
class SomeClass {private final Person person = new Person();
int doSomething(String s) {person.setName(s);
return s.trim().length();}
}
int x = 0;
Function<String, Integer> doSomething = (s) -> {
x = 1;
return s.trim().length();}
Person person = new Person();
Function<String, Integer> doSomething = (s) -> {
person.setName(s);
return s.trim().length();}
class SomeClass {
static int doSomething(String s, Person person) {person.setName(s);
return s.trim().length();}
}
Nutzen von Immutable Data
"Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more
secure."Joshua Bloch - Effective Java, second edition
Nachvollziehbarkeit von Code
Minimierung von Einflussfaktoren
SomeClass someObject = new SomeClass();
unpureMethod(someObject);
System.out.println(someObject); // ?
List<String> strings = …
int x = somePureFunction(strings);
int y = x + 15;
List<String> strings = …
int x = someOtherPureFunction(strings); // replace without worrying
int y = x + 15;
public String doSomething(List<Person> persons) {…
}
Was könnte diese Methode tun?
public String doSomething(List<Person> persons) {…
}
Was könnte diese Methode tun?
Möglichkeiten:- Klassen-Variablen (Felder) lesen, verändern, neu zuweisen- Parameter "persons":
- Elemente hinzufügen, löschen, ersetzen- Instanzen in der Liste verändern- Irgendwo im Code die Referenz neu belegen (person = new ArrayList<>())
- Andere Methoden aufrufen- Seiten-Effekte (z.B. Datenbank-Zugriffe) durchführen- ...
public static String doSomething(List<Person> persons) {…
}
Was könnte diese Methode tun?
Möglichkeiten:- Klassen-Variablen (Felder) lesen, verändern, neu zuweisen- Parameter "persons":
- Elemente hinzufügen, löschen, ersetzen- Instanzen in der Liste verändern- Irgendwo im Code die Referenz neu belegen (persons = new ArrayList<>())
- Andere Methoden aufrufen- Seiten-Effekte (z.B. Datenbank-Zugriffe) durchführen- ...
public static String doSomething(final List<Person> persons) {…
}
Was könnte diese Methode tun?
Möglichkeiten:- Klassen-Variablen (Felder) lesen, verändern, neu zuweisen- Parameter "persons":
- Elemente hinzufügen, löschen, ersetzen- Instanzen in der Liste verändern- Irgendwo im Code die Referenz neu belegen (persons = new ArrayList<>())
- Andere Methoden aufrufen- Seiten-Effekte (z.B. Datenbank-Zugriffe) durchführen- ...
public static String doSomething(final ImmutableList<Person> persons) {…
}
Was könnte diese Methode tun?
Möglichkeiten:- Klassen-Variablen (Felder) lesen, verändern, neu zuweisen- Parameter "persons":
- Elemente hinzufügen, löschen, ersetzen- Instanzen in der Liste verändern- Irgendwo im Code die Referenz neu belegen (person = new ArrayList<>())
- Andere Methoden aufrufen- Seiten-Effekte (z.B. Datenbank-Zugriffe) durchführen- ...
public static String doSomething(final ImmutableList<ImmutablePerson> persons) {…
}
Was könnte diese Methode tun?
Möglichkeiten:- Klassen-Variablen (Felder) lesen, verändern, neu zuweisen- Parameter "persons":
- Elemente hinzufügen, löschen, ersetzen- Instanzen in der Liste verändern- Irgendwo im Code die Referenz neu belegen (person = new ArrayList<>())
- Andere Methoden aufrufen- Seiten-Effekte (z.B. Datenbank-Zugriffe) durchführen- ...
DebuggingHerausfinden, wo, warum und von wem ein Objekt verändert wurde
Mutation ist easy aber nicht unbedingt simple.
→ Empfehlung: Rich Hickey Talk "Simple made Easy"
easy = schnell und einfach verwendbar
simple = Gegenteil von "Complex"
Immutables sind Thread-Safe
Historisierung
Anforderung:Verschiedene Versionsstände sollen persistiert werden
Anforderung:Verschiedene Versionsstände sollen persistiert werden
SomeObject object = new SomeObject();
object.setValue(15);
object.setValue(30);
Funktionale UI-Entwicklung:Redux
Zustandsbaum
Reducer-Funktion
Action
State reduce(State oldState, Action action) {...
}
ReduxUI- und Applikations-zustand wird als Immutable Objekt modelliert
Pure Reducer-Funktion bildet aktuellen Zustand auf neuen Zustand ab
UI rendert sich stets passend zum neuen Zustand
Herausforderungen
HerausforderungenAuch in funktionalen Programmen muss mit Datenänderung umgegangen werden
Statt Objekte zu verändern müssen Kopien erzeugt werden
Herausforderung:
1) Speicher- und Laufzeit-Performance?2) API zum Entwickeln?
Immutable Collections
List<String> mutableList = new ArrayList<>();mutableList.add("foo");mutableList.add("bar");
List<String> immutableList = Collections.unmodifiableList(mutableList);
List<String> mutableList = new ArrayList<>();mutableList.add("foo");mutableList.add("bar");
List<String> immutableList = Collections.unmodifiableList(mutableList);
System.out.println(immutableList.size()); // 2
List<String> mutableList = new ArrayList<>();mutableList.add("foo");mutableList.add("bar");
List<String> immutableList = Collections.unmodifiableList(mutableList);
immutableList.add("baz"); // UnsupportedOperationException
List<String> mutableList = new ArrayList<>();mutableList.add("foo");mutableList.add("bar");
List<String> immutableList = Collections.unmodifiableList(mutableList);
mutableList.add("baz");
System.out.println(immutableList.size()); //?
List<String> mutableList = new ArrayList<>();mutableList.add("foo");mutableList.add("bar");
List<String> immutableList = Collections.unmodifiableList(mutableList);
mutableList.add("baz");
System.out.println(immutableList.size()); //3
(formerly known as Java-Slang)
Vavr- Bibliothek für funktionale Programmierung mit Java
- u.a. Collections Library
- http://www.vavr.io/
import io.vavr.collection.List;
List<String> list = List.of("foo", "bar");
import io.vavr.collection.List;
List<String> list = List.of("foo", "bar");
List<String> newList = list.append("baz");
System.out.println(list.size()); // 2
Iterable
Traversable
Seq
IndexedSeq
Array CharSeq Vector Stack List QueueStream
LinearSeq
Iterable
Traversable
Set
SortedSet
LinkedHashSet HashSet TreeSet
Map
SortedMap
LinkedHashMap HashMap TreeMap
list = list.remove("foo");
list = list.remove("foo");
list = list.filter(x -> !x.isEmpty());
list = list.remove("foo");
list = list.filter(x -> !x.isEmpty());
List<Integer> lengths = list.map(x -> x.length());
list = list.remove("foo");
list = list.filter(x -> !x.isEmpty());
List<Integer> lengths = list.map(x -> x.length());
String folded = list.fold("", (a,b) -> a + "." + b);// ["a","b","c"] => "a.b.c"
Guava Collections
import com.google.common.collect.ImmutableList;
ImmutableList<String> list = ImmutableList.of("foo", "bar");
import com.google.common.collect.ImmutableList;
ImmutableList<String> list = ImmutableList.of("foo", "bar");
list = ImmutableList.builder().addAll(list).add("baz").build();
// remove?
- PCollections
- cli-ds (Clojure collections outside of Clojure)
- functional-java
Andere Bibliotheken
Immutable Objekte
public class Person {private String firstname;private String lastname;
public String getFirstname() {return firstname;
}
public void setFirstname(String name) {this.firstname = name;
}
public String getLastname() {return lastname;
}
public void setLastname(String name) {this.lastname = name;
}}
public class Person {private String firstname;private String lastname;
public String getFirstname() {return firstname;
}
public void setFirstname(String name) {this.firstname = name;
}
public String getLastname() {return lastname;
}
public void setLastname(String name) {this.lastname = name;
}}
public class Person {private String firstname;private String lastname;
public String getFirstname() {return firstname;
}
public String getLastname() {return lastname;
}}
public class Person {private final String firstname;private final String lastname;
public Person(String firstname, String lastname) {this.firstname = firstname;this.lastname = lastname;
}
public String getFirstname() {return firstname;
}
public String getLastname() {return lastname;
}}
public class Person {private final String firstname;private final String lastname;
public Person(String firstname, String lastname) {this.firstname = firstname;this.lastname = lastname;
}
// getters
public boolean equals(Object other) {// generate by IDE
}
public int hashCode() {// generate by IDE
}}
public class Student extends Person {private String studentId;
// constructor
public String getStudentId() {return studentId;
}}
public class Student extends Person {private String studentId;
// constructor
public String getStudentId() {return studentId;
}
public void setStudentId(String studentId) {this.studentId = studentId;
}}
public class Student extends Person {private String studentId;
// constructor
public String getStudentId() {return studentId;
}
public void setStudentId(String studentId) {this.studentId = studentId;
}}
...
Person p = new Student(...);
somePureFunction(p);
p.setStudentId("sima123");
public final class Person {private final String firstname;private final String lastname;
public Person(String firstname, String lastname) {this.firstname = firstname;this.lastname = lastname;
}
// getters + equals + hashCode
}
public final class Person {private final String firstname;private final String lastname;private final Address address;
public Person(String firstname, String lastname, Address address) {this.firstname = firstname;this.lastname = lastname;this.address = address;
}
public Address getAddress() {return address;
}
// getter + equals + hashCode}
Address address = new Address("Görlitz", "02826", "Berliner Straße", "15");
Person person = new Person("Luise", "Müller", address);
Address address = new Address("Görlitz", "02826", "Berliner Straße", "15");
Person person = new Person("Luise", "Müller", address);
person.getAddress().setZipCode("02827");
Wie geht man mit Veränderung um?
public final class Person {private final String firstname;private final String lastname;private final Address address;
public Person(String firstname, String lastname, Address address) {// ...
}
public Person withLastname(String newLastName) {// TODO implement
}
// getter + equals + hashCode}
public final class Person {private final String firstname;private final String lastname;private final Address address;
public Person(String firstname, String lastname, Address address) {// ...
}
public Person withLastname(String newLastname) {return new Person(this.firstname, newLastname, this.address);
}
// getter + equals + hashCode}
public final class Person {private final String firstname;private final String lastname;private final Address address;
public Person(String firstname, String lastname, Address address) {// ...
}
public Person withLastname(String newLastname) {if(this.lastname.equals(newLastname)) {
return this;} else {
return new Person(this.firstname, newLastname, this.address);}
}
// getter + equals + hashCode}
Address address = new Address("City", "Street", "House number");
Person person = new Person("Luise", "Müller", address);
Address address = new Address("City", "Street", "House number");
Person person = new Person("Luise", "Müller", address);
Person newPerson = person.withLastname("Maier");
Address address = new Address("City", "Street", "House number");
Person person = new Person("Luise", "Müller", address);
person = person.withLastname("Maier");
public final class Person {private final String firstname;private final String lastname;private final Address address;private final String emailAddress;
public Person(String firstname, String lastname, Address address) {// ...
}
public Person withLastname(String newLastname) {if(this.lastname.equals(newLastname)) {
return this;} else {
return new Person(this.firstname, newLastname, this.address);}
}
// getter + equals + hashCode}
public final class Person {private final String firstname;private final String lastname;private final Address address;private final String emailAddress;
public Person(String firstname, String lastname, Address address, String email) {// ...
}
public Person withLastname(String newLastname) {if(this.lastname.equals(newLastname)) {
return this;} else {
return new Person(this.firstname, newLastname, this.address);}
}
// getter + equals + hashCode}
public final class Person {private final String firstname;private final String lastname;private final Address address;private final String emailAddress;
public Person(String firstname, String lastname, Address address, String email) {// ...
}
public Person withLastname(String newLastname) {if(this.lastname.equals(newLastname)) {
return this;} else {
return new Person(this.firstname, newLastname, this.address);}
}
// getter + equals + hashCode}
Lombok
Lombok- lombok Abhängigkeit ins Projekt ziehen- Annotationen setzen- Lombok generiert Boilerplate-Code
https://projectlombok.org/
@Datapublic class BlogArticle {
String title;String text;Author author;
}
public class BlogArticle { String title; String text;
Author author;
public BlogArticle() { }
public String getTitle() { return this.title; }
public String getText() { return this.text; }
public Author getAuthor() { return this.author; }
public void setTitle(String title) { this.title = title; }
public void setText(String text) { this.text = text; }
public void setAuthor(Author author) { this.author = author; }
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof BlogArticle)) return false; final BlogArticle other = (BlogArticle) o; if (!other.canEqual((Object) this)) return false; final Object this$title = this.getTitle(); final Object other$title = other.getTitle(); if (this$title == null ? other$title != null : !this$title.equals(other$title)) return false; final Object this$text = this.getText(); final Object other$text = other.getText(); if (this$text == null ? other$text != null : !this$text.equals(other$text)) return false; final Object this$author = this.getAuthor(); final Object other$author = other.getAuthor(); if (this$author == null ? other$author != null : !this$author.equals(other$author)) return false; return true; }
public int hashCode() { final int PRIME = 59; int result = 1; final Object $title = this.getTitle(); result = result * PRIME + ($title == null ? 43 : $title.hashCode()); final Object $text = this.getText(); result = result * PRIME + ($text == null ? 43 : $text.hashCode()); final Object $author = this.getAuthor(); result = result * PRIME + ($author == null ? 43 : $author.hashCode()); return result; }
protected boolean canEqual(Object other) { return other instanceof BlogArticle; }
public String toString() { return "BlogArticle(title=" + this.getTitle() + ", text=" + this.getText() + ", author=" + this.getAuthor() + ")"; }}
vs.
Constructor
Getter + Setter
Equals
HashCode
ToString
Lombok für Immutables@Valuepublic class BlogArticle {
String title;String text;Author author;
}
// Usage
BlogArticle article = new BlogArticle("title", "text", author);
System.out.println(article.getTitle());
Lombok für Immutables@Value@Witherpublic class BlogArticle {
String title;String text;Author author;
}
// Usage
BlogArticle article = new BlogArticle("title", "text", author);
article = article.withTitle("New title");
Lombok für Immutables@Value@Wither@Builderpublic class BlogArticle {
String title;String text;Author author;
}
// Usage
BlogArticle article = BlogArticle.builder().title("Title").text("Text").author(author).build();
Lombok für Immutables@Value@Wither@Builder(toBuilder = true)public class BlogArticle {
String title;String text;Author author;
}
// Usage
BlogArticle article = BlogArticle.builder().title("Title").text("Text").author(author).build();
article = article.toBuilder().title("New Title").build();
Lombok - Kombination mit Collections?@Value@Witherclass BlogArticle {
Seq<Comment> comments;}
// usage
BlogArticle article = new BlogArticle(List.empty());
BlogArtcle article = article.withComments(article.getComments().append(comment));
Lombok - Kombination mit Collections?@Value@Witherclass BlogArticle {
Seq<Comment> comments = List.empty();}
// usage
BlogArticle article = new BlogArticle();
BlogArtcle article = article.withComments(article.getComments().append(comment));
Lombok - Kombination mit Collections?@Value@Witherclass BlogArticle {
Seq<Comment> comments = List.empty();
BlogArticle addComments(Comment...comments) {// ?
}
BlogArticle removeComments(Comment...comments) {// ?
}}
// usage
BlogArticle article = new BlogArticle();
BlogArtcle article = article.addComments(comment);
Lombok- guter IDE support
- einfache Integration
- vermeidet Boilerplate
https://projectlombok.org/
- Features für Immutability begrenzt
- Manchmal Probleme im Build-Prozess
- Annotationen sind nicht composable
Immutables.org
Immutables"Java annotation processors to generate simple, safe and consistent value objects"
Setup wie Lombok
https://immutables.github.io/
@Value.Immutablepublic interface BlogArticle {
String getTitle();String getText();
Author getAuthor();
List<Comment> getComments();}
// generiertImmutableBlogArticle article = ImmutableBlogArticle.builder()
.title("Title")
.text("Text")
.build();
article = article.withTitle("New Title");
// generiertImmutableBlogArticle article = ImmutableBlogArticle.builder()
.title("Title")
.text("Text")
.build();
article = article.withComments(comment); // collections support
// generiertImmutableBlogArticle article = ImmutableBlogArticle.builder()
.title("Title")
.text("Text")
.build();
article.getComments().add(comment); // UnsupportedOperationException
// generiertImmutableBlogArticle article = ImmutableBlogArticle.builder()
.title("Title")
.text("Text")
.build();
article = ImmutableBlogArticle.builder().from(article).addComments(comment).build();
Immutables.org- Optimiert für unveränderliche Daten
- Umfangreiche Builder werden generiert
- einfache Integration
https://immutables.github.io/
- kein spezieller IDE Support
- funktioniert aber trotzdem ohne Probleme
Immutables.org - Other Features
[email protected]@Value.Style(
init = "set*",typeAbstract = {"Abstract*"},typeImmutable = "*"
)public interface AbstractBlogArticle {
String getTitle();String getText();
}
// generated Class without "Immutable" prefix
BlogArticle article = BlogArticle.Builder().setTitle("Title").setText("Content").build();
ModifiableBlogArticle modifiableArticle = ModifiableBlogArticle.create().setTitle("Title").setText("Text");
modifiableArticle.setTitle("New Title");
ImmutableBlogArticle article = modifiableArticle.toImmutable();
ImmutableBlogArticle article = …
ModifiableBlogArticle modifiableArticle = ModifiableBlogArticle.create().from(article);
modifiableArticle.setTitle("New Title");
article = modifiableArticle.toImmutable();
Deeply Nested Structures
Optics
Address address = new Address("Görlitz", "02826", "Berliner Straße", "15");
Person person = new Person("Luise", "Müller", address);
person.getAddress().setZipCode("02827"); // mutation
Address address = new Address("Görlitz", "02826", "Berliner Straße", "15");
Person person = new Person("Luise", "Müller", address);
person.getAddress().setZipCode("02827");
person = person.withAddress(person.getAddress().withZipCode("02827"));
City goerlitz = new City("Görlitz", "02826");
Address address = new Address(goerlitz, "Berliner Straße", "15");
Person person = new Person("Luise", "Müller", address);
person.withAddress(person.getAddress().withCity(person.getAddress().getCity().withZipCode("02827")));
Tief verschachtelte Strukturen?
LensesLens: Objekt, welches auf eine bestimmte Stelle einer Datenstruktur "zeigt".
Person person = new Person("Luise", "Müller", address);
Lens personLastnameLens = …
Person person = new Person("Luise", "Müller", address);
Lens personLastnameLens = …
String lastname = personLastnameLens.get(person);
System.out.println(lastname); // "Müller"
Person person = new Person("Luise", "Müller", address);
Lens personLastnameLens = …
Person person = new Person("Luise", "Müller", address);
Lens personLastnameLens = …
Person newPerson = personLastameLens.set("Maier").apply(person);
System.out.println(newPerson.getLastname()); // "Maier"
Person person = new Person("Luise", "Müller", address);
Lens personLastnameLens = …
Person newPerson = personLastameLens.set("Maier").apply(person);
System.out.println(newPerson.getLastname()); // "Maier"
System.out.println(person.getLastname()); // "Müller"
LensesLens: Objekt, welches auf eine bestimmte Stelle einer Datenstruktur "zeigt".
Kann Werte abrufen und "setzen" (Kopie mit geänderten Daten erzeugen)
LensesLens: Objekt, welches auf eine bestimmte Stelle einer Datenstruktur "schaut".
Kann Werte abrufen und "setzen" (Kopie mit geänderten Daten erzeugen)
Lenses sind composable!
Person person = new Person("Luise", "Müller", address);
Lens.lens(person -> person.getAddress(), …)
Person person = new Person("Luise", "Müller", address);
Lens.lens(Person::getAddress, …)
Person person = new Person("Luise", "Müller", address);
Lens.lens(Person::getAddress, address -> person -> person.withAddress(address))
Person person = new Person("Luise", "Müller", address);
Lens<Person, Address> personAddressLens = Lens.lens(Person::getAddress,
address -> person -> person.withAddress(address))
Person person = new Person("Luise", "Müller", address);
Lens<Person, Address> personAddressLens = …
Lens<Address, City> addressCityLens = …
Person person = new Person("Luise", "Müller", address);
Lens<Person, Address> personAddressLens = …
Lens<Address, City> addressCityLens = …
Lens<City, String> cityZipcodeLens = …
Person person = new Person("Luise", "Müller", address);
Lens<Person, Address> personAddressLens = …
Lens<Address, City> addressCityLens = …
Lens<City, String> cityZipcodeLens = …
Lens<Person, String> personZipcodeLens =personAddressLens.compose(addressCityLens).compose(cityZipcodeLens);
Person person = new Person("Luise", "Müller", address);
Lens<Person, Address> personAddressLens = …
Lens<Address, City> addressCityLens = …
Lens<City, String> cityZipcodeLens = …
Lens<Person, String> personZipcodeLens =personAddressLens.compose(addressCityLens).compose(cityZipcodeLens);
Person newPerson = personZipcodeLens.set("02827").apply(person);
System.out.println(newPerson.getAddress().getCity().getZipCode())); // "02827"
Hilfsmittel zum Einsehen und "Verändern" von immutable Objekten
Abstraktion von konkreten Pfad zum Ziel-Wert
→ Ändert sich die Struktur, muss nur die Lens angepasst werden, nicht derbenutzende Code
Optics
Lens: "zeigt" auf einen stets vorhandenen Wert
Prism: "zeigt" auf einen möglicherweise vorhandenen Wert (Optional)
...
Bibliothek: Functional-Java
Optics
Andere JVM-Sprachen
Kotlin
Kotlindata class BlogArticle (
val title: String,val text: String
)
Kotlindata class BlogArticle (
val title: String,val text: String
)
val article = BlogArticle(title="Title", text="Text")
val newArticle = article.copy(title="New Title")
Kotlin Collectionsval list = listOf("foo", "bar")
list.add("baz");
Kotlin Collectionsval list = listOf("foo", "bar")
list.add("baz");
val mutableList = mutableListOf("foo", "bar")
FregeHaskell for the JVM
data Person = Person { firstname :: String, lastname :: String }
data Person = Person { firstname :: String, lastname :: String }
luise = Person "Luise" "Müller"
data Person = Person { firstname :: String, lastname :: String }
luise = Person "Luise" "Müller"
hugo = Person { lastname = "Bauer", firstname = "Hugo" }
data Person = Person { firstname :: String, lastname :: String }
luise = Person "Luise" "Müller"
hugo = Person { lastname = "Bauer", firstname = "Hugo" }
luise2 = luise.{ lastname = "Maier" }
Java Value-Types?JEP 169 - Project Valhalla
Project Valhalla: Value-Types für JavaValue-Types existieren direkt auf dem Call Stack und nicht im Heap
Keine Identität → Immutable
Fokus auf Performance und Speicher-Nutzung (keine Dereferenzierung mehr nötig)
"Enable functional-style computation with pure data, for optimized parallel computations."
"Increase safety and security and decrease "defensive copying" in applications which must share structured data across trust boundaries."
Fragen?@manuel_mauky
github.com/lestardwww.lestard.eu