From 373f075c89c9e612ef6256629bc19ac68d717841 Mon Sep 17 00:00:00 2001 From: 100yo Date: Fri, 8 Nov 2024 23:39:34 +0200 Subject: [PATCH] Add Generics code snippets --- 05-generics/snippets/README.md | 1 + 05-generics/snippets/src/BridgeMethods.java | 41 +++++++ 05-generics/snippets/src/Container.java | 11 ++ 05-generics/snippets/src/Fruit.java | 6 + 05-generics/snippets/src/GenericRecord.java | 3 + .../snippets/src/GenericsPlayground.java | 111 ++++++++++++++++++ .../src/LowerBoundedWildcardGenerics.java | 86 ++++++++++++++ 05-generics/snippets/src/Pair.java | 59 ++++++++++ 8 files changed, 318 insertions(+) create mode 100644 05-generics/snippets/README.md create mode 100644 05-generics/snippets/src/BridgeMethods.java create mode 100644 05-generics/snippets/src/Container.java create mode 100644 05-generics/snippets/src/Fruit.java create mode 100644 05-generics/snippets/src/GenericRecord.java create mode 100644 05-generics/snippets/src/GenericsPlayground.java create mode 100644 05-generics/snippets/src/LowerBoundedWildcardGenerics.java create mode 100644 05-generics/snippets/src/Pair.java diff --git a/05-generics/snippets/README.md b/05-generics/snippets/README.md new file mode 100644 index 00000000..31c90b58 --- /dev/null +++ b/05-generics/snippets/README.md @@ -0,0 +1 @@ +# Generics / Code snippets diff --git a/05-generics/snippets/src/BridgeMethods.java b/05-generics/snippets/src/BridgeMethods.java new file mode 100644 index 00000000..bfa6d4cc --- /dev/null +++ b/05-generics/snippets/src/BridgeMethods.java @@ -0,0 +1,41 @@ +class Box { + private T value; + + public void setValue(T value) { + this.value = value; + } +} + +class BoxOfInt extends Box { + private Integer value; + + @Override + public void setValue(Integer value) { + this.value = value; + } + +/* + After type parameter erasure, the overriden setValue() method in the superclass and here + would have different signatures: + + public void setValue(Object value) { this.value = value; } // in Box + public void setValue(Integer value) { this.value = value; } // in BoxOfInt + + To solve this, the compiler generates a synthetic method in BoxOfInt, called a "bridge method": + + public void setValue(Object value) { setValue((Integer) value); } // in BoxOfInt + + Bridge methods are not visible in the source code but can be seen in the resulting bytecode. + Have a look disassembling it with javap -c -v BoxOfInt.class + */ + +} + +public class BridgeMethods { + + public static void main(String... args) { + Box boxOfInt = new BoxOfInt(); + boxOfInt.setValue(1); + } + +} diff --git a/05-generics/snippets/src/Container.java b/05-generics/snippets/src/Container.java new file mode 100644 index 00000000..9e8279aa --- /dev/null +++ b/05-generics/snippets/src/Container.java @@ -0,0 +1,11 @@ +public class Container { + + public static void main(String[] args) { + Container ci = new Container<>(); + Container cd = new Container<>(); + + // Container cs = new Container<>(); // will not compile + + } + +} diff --git a/05-generics/snippets/src/Fruit.java b/05-generics/snippets/src/Fruit.java new file mode 100644 index 00000000..073b2d58 --- /dev/null +++ b/05-generics/snippets/src/Fruit.java @@ -0,0 +1,6 @@ +final class Pineapple extends Fruit {} +final class Melon extends Fruit {} + +public sealed class Fruit permits Pineapple, Melon { + private T t; +} diff --git a/05-generics/snippets/src/GenericRecord.java b/05-generics/snippets/src/GenericRecord.java new file mode 100644 index 00000000..d718b9ef --- /dev/null +++ b/05-generics/snippets/src/GenericRecord.java @@ -0,0 +1,3 @@ +public record GenericRecord(K key, V value) { + +} diff --git a/05-generics/snippets/src/GenericsPlayground.java b/05-generics/snippets/src/GenericsPlayground.java new file mode 100644 index 00000000..ce8f6bf0 --- /dev/null +++ b/05-generics/snippets/src/GenericsPlayground.java @@ -0,0 +1,111 @@ +import java.util.ArrayList; +import java.util.List; + +class LivingThing { + +} + +class Human extends LivingThing { + private String name; + + public Human(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} + +class Student extends Human { + private int fn; + + public Student(String name, int fn) { + super(name); + this.fn = fn; + } + + @Override + public String toString() { + return "Student{" + + "fn=" + fn + + '}'; + } +} + +class FMIStudent extends Student { + public FMIStudent(String name, int fn) { + super(name, fn); + } + +} + +public class GenericsPlayground { + + // The Get & Put principle in action + + private static void getHumans(List listOfHumans) { + + // we can safely get, and we can rely on always getting a Human + for (Human h : listOfHumans) { // we can safely iterate as Human and call methods of Human + System.out.println(h.getName()); + } + + // we cannot add any elements to the list, because the compiler cannot verify the type safety. + // The only exception to this is adding `null`, as `null` is a valid value for any reference type. + listOfHumans.add(null); + + } + + private static void putHumans(List listOfSuperHumans) { + + // we can safely put instances of Human and successors of Human + listOfSuperHumans.add(new Student("Georgi Todorov", 62348)); + listOfSuperHumans.add(new Human("Anelia Angelova")); + listOfSuperHumans.add(new FMIStudent("Zahari Zvezdomirov", 62216)); + // listOfSuperHumans.add(new LivingThing()); // will not compile, why? + + // if we get, we can just rely on getting a java.lang.Object + Object o = listOfSuperHumans.get(0); + if (o instanceof Student) { + System.out.println(((Student) o).getName()); + } + System.out.println(o); + + } + + private static int neitherGetNorPut(List listOfUnknown) { + + // if we get, we can just rely on getting a java.lang.Object + Object o = listOfUnknown.get(0); + + // we can add only `null` + listOfUnknown.add(null); + // listOfUnknown.add("kuku"); // will not compile + + // we can use only methods that are agnostic to the type of elements + return listOfUnknown.size(); + + } + + public static void main(String[] args) { + + List listOfFMIStudents = new ArrayList<>(); + List listOfStudents = new ArrayList<>(); + List listOfHumans = new ArrayList<>(); + List listOfLivingThings = new ArrayList<>(); + List listOfObjects = new ArrayList<>(); + + getHumans(listOfHumans); + getHumans(listOfStudents); + getHumans(listOfFMIStudents); + + putHumans(listOfHumans); + putHumans(listOfLivingThings); + putHumans(listOfObjects); + + System.out.println(neitherGetNorPut(new ArrayList<>(List.of(1, 2, 3)))); // 4 + + } + +} diff --git a/05-generics/snippets/src/LowerBoundedWildcardGenerics.java b/05-generics/snippets/src/LowerBoundedWildcardGenerics.java new file mode 100644 index 00000000..5796b773 --- /dev/null +++ b/05-generics/snippets/src/LowerBoundedWildcardGenerics.java @@ -0,0 +1,86 @@ +interface Shape { + double area(); +} + +class Rectangle implements Shape, Comparable { + private double length; + private double width; + + public Rectangle(double length, double width) { + this.length = length; + this.width = width; + } + + @Override + public double area() { + return length * width; + } + + @Override + public int compareTo(Shape other) { + return Double.compare(this.area(), other.area()); + } + + @Override + public String toString() { + return "Rectangle[Area=" + area() + "]"; + } +} + +class Square extends Rectangle { + + public Square(double side) { + super(side, side); + } + + @Override + public String toString() { + return "Square[Area=" + area() + "]"; + } +} + +// A utility class that will use lower-bounded wildcard generics +class ShapeUtils { + + // This method leverages generics and bounded wildcards to handle a flexible range of types + // while maintaining type safety. + public static T findLargestShape(T[] shapes) { + T largest = shapes[0]; + for (T shape : shapes) { + // The expression `Comparable` is used to cast `shape` to ensure compatibility + // with the `compareTo` method. The lower bound (`super`) is necessary for handling the case where `T` + // could be a subtype of `Rectangle`, allowing comparison across derived classes like `Square`. + if (((Comparable) shape).compareTo(largest) > 0) { + largest = shape; + } + } + return largest; + } + + // Note that if we change the generic type of the method above to >, + // the `shapes` array can only contain elements of the same type `T`, because `Comparable` restricts `T` + // to be compared only with objects of the same type. + // As a result, it would not be possible to use this method with an array of mixed types like + // `[Rectangle, Rectangle, Square, Square]`, reducing its flexibility. +} + +// Main class to test the implementation +public class LowerBoundedWildcardGenerics { + public static void main(String[] args) { + Rectangle rect1 = new Rectangle(4, 5); + Rectangle rect2 = new Rectangle(2, 3); + Square square1 = new Square(3); + Square square2 = new Square(4); + + Shape[] shapes = {rect1, rect2, square1, square2}; + Rectangle[] rectangles = {rect1, rect2, square1, square2}; + Square[] squares = {square1, square2}; + + // Find the largest shape + Shape largestShape = ShapeUtils.findLargestShape(shapes); + //Shape largestRectangle = ShapeUtils.findLargestShape(rectangles); + //Shape largestSquare = ShapeUtils.findLargestShape(squares); + + System.out.println("The largest shape is: " + largestShape); + } +} diff --git a/05-generics/snippets/src/Pair.java b/05-generics/snippets/src/Pair.java new file mode 100644 index 00000000..f941f88b --- /dev/null +++ b/05-generics/snippets/src/Pair.java @@ -0,0 +1,59 @@ +import java.util.List; +import java.util.Set; + +public class Pair { + + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + public V getValue() { + return value; + } + + public void setValue(V value) { + this.value = value; + } + + @Override + public String toString() { + return "Pair{" + + "key=" + key + + ", value=" + value + + '}'; + } + + public static void main(String[] args) { + Pair pair1 = new Pair<>("Stoyo", 1); + Pair pair2 = new Pair<>("Boyo", 6); + + Pair pair3 = new Pair<>(1.0, 1.0); + + System.out.println(Util.areEqual(pair1, pair2)); + // the next line will not compile: "reason: Incompatible equality constraint: Double and String" + //System.out.println(Util.areEqual(pair1, pair3)); + + Pair, Set> pair4 = new Pair<>(List.of("FMI", "rulez"), Set.of(2024, 2025)); + } + +} + +class Util { + // Generic static method + public static boolean areEqual(Pair p1, Pair p2) { + return p1.getKey().equals(p2.getKey()) && + p1.getValue().equals(p2.getValue()); + } +}