Skip to content

Commit

Permalink
Add Generics code snippets
Browse files Browse the repository at this point in the history
  • Loading branch information
100yo committed Nov 8, 2024
1 parent 1ecf726 commit 373f075
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 0 deletions.
1 change: 1 addition & 0 deletions 05-generics/snippets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Generics / Code snippets
41 changes: 41 additions & 0 deletions 05-generics/snippets/src/BridgeMethods.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class Box<T> {
private T value;

public void setValue(T value) {
this.value = value;
}
}

class BoxOfInt extends Box<Integer> {
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<Integer> boxOfInt = new BoxOfInt();
boxOfInt.setValue(1);
}

}
11 changes: 11 additions & 0 deletions 05-generics/snippets/src/Container.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public class Container<T extends Number> {

public static void main(String[] args) {
Container<Integer> ci = new Container<>();
Container<Double> cd = new Container<>();

// Container<String> cs = new Container<>(); // will not compile

}

}
6 changes: 6 additions & 0 deletions 05-generics/snippets/src/Fruit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
final class Pineapple extends Fruit {}
final class Melon extends Fruit {}

public sealed class Fruit<T> permits Pineapple, Melon {
private T t;
}
3 changes: 3 additions & 0 deletions 05-generics/snippets/src/GenericRecord.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public record GenericRecord<K, V>(K key, V value) {

}
111 changes: 111 additions & 0 deletions 05-generics/snippets/src/GenericsPlayground.java
Original file line number Diff line number Diff line change
@@ -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<? extends Human> 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<? super Human> 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<FMIStudent> listOfFMIStudents = new ArrayList<>();
List<Student> listOfStudents = new ArrayList<>();
List<Human> listOfHumans = new ArrayList<>();
List<LivingThing> listOfLivingThings = new ArrayList<>();
List<Object> 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

}

}
86 changes: 86 additions & 0 deletions 05-generics/snippets/src/LowerBoundedWildcardGenerics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
interface Shape {
double area();
}

class Rectangle implements Shape, Comparable<Shape> {
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 extends Shape> T findLargestShape(T[] shapes) {
T largest = shapes[0];
for (T shape : shapes) {
// The expression `Comparable<? super T>` 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<? super T>) shape).compareTo(largest) > 0) {
largest = shape;
}
}
return largest;
}

// Note that if we change the generic type of the method above to <T extends Shape & Comparable<T>>,
// the `shapes` array can only contain elements of the same type `T`, because `Comparable<T>` 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);
}
}
59 changes: 59 additions & 0 deletions 05-generics/snippets/src/Pair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import java.util.List;
import java.util.Set;

public class Pair<K, V> {

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<String, Integer> pair1 = new Pair<>("Stoyo", 1);
Pair<String, Integer> pair2 = new Pair<>("Boyo", 6);

Pair<Double, Double> 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<List<String>, Set<Integer>> pair4 = new Pair<>(List.of("FMI", "rulez"), Set.of(2024, 2025));
}

}

class Util {
// Generic static method
public static <K, V> boolean areEqual(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}

0 comments on commit 373f075

Please sign in to comment.