import java.util.*;
import java.util.stream.*;

class Scratch {
  public static void main(String[] args) {
    final List<ExampleObject> objects = List.of(
        new ExampleObject(1, "Real Name", "Real Value"),
        new ExampleObject(2, "Duplicate Name", "Duplicate Value"),
        new ExampleObject(3, "Duplicate Name", "Duplicate Value"),
        new ExampleObject(4, "Duplicate Name", "Duplicate Value"),
        new ExampleObject(5, "Real Name 2", "Real Value 2"),
        new ExampleObject(6, "Duplicate Name 2", "Duplicate Value 2"),
        new ExampleObject(7, "Duplicate Name 2", "Duplicate Value 2"));
    final TreeSet<ExampleObject> deduped = objects.stream()
        .collect(Collectors.toCollection(() -> new TreeSet<>(
            Comparator.comparing(ExampleObject::getName)
                .thenComparing(ExampleObject::getValue))));
    System.out.println(deduped
        .stream()
        .map(Object::toString)
        .collect(Collectors.joining(System.lineSeparator())));
  }
}

class ExampleObject {
  final int id;
  final String name;
  final String value;

  public ExampleObject(int id, String name, String value) {
    this.id = id;
    this.name = name;
    this.value = value;
  }

  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public String getValue() {
    return value;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    ExampleObject that = (ExampleObject) o;
    return id == that.id;
  }

  @Override
  public int hashCode() {
    return Objects.hash(id);
  }

  @Override
  public String toString() {
    return "ExampleObject{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", value='" + value + '\'' +
        '}';
  }
}