通用列表中的唯一 类

Unique classes in generic list

我有一个带有通用列表的通用 class。我想确保通用列表只包含唯一的 classes.

到目前为止,我所做的是将 class 名称与反射 (getClass()) 进行比较。但我认为这不是一个干净的解决方案。有没有更好的做法可以检查?

public class MyGenericClass<T extends MyGenericClass.MyInterface> {
  private List<T> members = new ArrayList<>(0);

  public void add(T t) {
    final boolean[] classInMembers = {false};

    members.forEach(member -> {
      if (member.getClass().getName().equals(t.getClass().getName())) {
        classInMembers[0] = true;
      }
    });

    if (!classInMembers[0]) {
      members.add(t);
    }
  }

  public interface MyInterface {
    void doSomething(String text);
  }
}
public class Main {
  public static void main(String[] args) {
    MyGenericClass<MyGenericClass.MyInterface> myGenericClass = new MyGenericClass<>();

    myGenericClass.add(new Performer1());
    myGenericClass.add(new Performer2());
    myGenericClass.add(new Performer3());
    myGenericClass.add(new Performer3()); // should not be inserted!
  }

  private static class Performer1 implements MyGenericClass.MyInterface {
    @Override
    public void doSomething(String text) {
      text = "Hi, I am performer 1!";
    }
  }

  private static class Performer2 implements MyGenericClass.MyInterface {
    @Override
    public void doSomething(String text) {
      text = "Hi, I am performer 2!";
    }
  }

  private static class Performer3 implements MyGenericClass.MyInterface {
    @Override
    public void doSomething(String text) {
      text = "Hi, I am performer 3!";
    }
  }
}

不要使用列表,而是使用 java.util.Set

A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element.

如果迭代顺序很重要,或者如果您想使用自定义 Comparator, the TreeSet 实现,可以使用:

A NavigableSet implementation based on a TreeMap. The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used.

Set 使用 Comparator 的示例:

class MyComparator implements Comparator<Object> {
    @Override
    public int compare(Object e1, Object e2) {
        if (e1.getClass() == e2.getClass())
            return 0;
        //if you wish to have some extra sort order                
        return e1.getClass().getName().compareTo(e2.getClass().getName());
    }
}

。 . .

Set mySet = new TreeSet<Object>(new MyComparator());
mySet.add(new Object());
mySet.add(new Object());//same class already in set
mySet.add("wtf");

//mySet.size() is now 2 - the second "new Object()" was not inserted due to the comparator check

为什么这么复杂?

public class Main {

    public static void main(String[] args) {
        final Class<?> helloClass = "Hello".getClass();
        final Class<?> worldClass = "World".getClass();
        final Class<?> intClass = Integer.class;

        System.out.println(helloClass.equals(worldClass)); // -> true
        System.out.println(helloClass.equals(intClass)); // -> false
    }
}

你可以子class一个java.util.Set interface implementation. It will likely be easiest to subclass java.util.AbstractSet

默认情况下 'Set' 将通过 .equals() 方法比较对象 - 在您的情况下,这还不够。您将需要覆盖 contains 方法以确保仅添加唯一 class 的实例。

在你的 overrideen contains 中,比较 class 实例而不是它们的字符串化包名称

可能相同/更容易

即使用 a.getClass() == b.getClass(),而不是 a.getClass().getName()

您可以在 Set.

中维护成员名册
public static class MyGenericClass<T extends MyGenericClass.MyInterface> {

    private List<T> members = new ArrayList<>(0);
    // Add this.
    private Set<Class<?>> roster = new HashSet<>();

    public void add(T t) {
        if (!roster.contains(t.getClass())) {
            members.add(t);
            roster.add(t.getClass());
        }
    }

    private void soundOff() {
        for (T t : members) {
            t.doSomething();
        }
    }

    public interface MyInterface {

        void doSomething();
    }
}

private static class Performer implements MyGenericClass.MyInterface {

    final int n;

    public Performer(int n) {
        this.n = n;
    }

    @Override
    public void doSomething() {
        System.out.println("Hi, I am a " + this.getClass().getSimpleName() + "(" + n + ")");
    }
}

private static class Performer1 extends Performer {

    public Performer1(int n) {
        super(n);
    }

}

private static class Performer2 extends Performer {

    public Performer2(int n) {
        super(n);
    }
}

private static class Performer3 extends Performer {

    public Performer3(int n) {
        super(n);
    }
}

public void test() {
    MyGenericClass<MyGenericClass.MyInterface> myGenericClass = new MyGenericClass<>();

    myGenericClass.add(new Performer1(1));
    myGenericClass.add(new Performer2(2));
    myGenericClass.add(new Performer3(3));
    myGenericClass.add(new Performer3(4)); // should not be inserted!

    myGenericClass.soundOff();
}

您可以实现一个提供必要比较的包装器,并将包装的实例添加到集合中。这样你就不必在你的具体 Performer class 中覆盖 equalshashcode 并且你不必子 class 一个具体的 Set实现(你与之耦合。当你 subclass a HashSet 时,你必须使用那个具体的 class。但是如果你想在某些时候使用 LinkedHashSet 怎么办点?您还必须覆盖 LinkedHashSet) ,这可能很脆弱,因为您必须确保覆盖的方法与 class.

的其余部分一致
class MyGenericClass<T extends MyInterface> {
    private Set<ClassCompareWrapper<T>> members = new HashSet<>();

    public void add(T t) {
        members.add(new ClassCompareWrapper<T>(t));
    }
}

class ClassCompareWrapper<T> {
    T t;

    public ClassCompareWrapper(T t) {
        this.t = t;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof ClassCompareWrapper))
            return false;
        ClassCompareWrapper<?> that = (ClassCompareWrapper<?>) o;
        return Objects.equals(t.getClass(), that.t.getClass());
    }

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

    @Override
    public String toString() {
        return "Wrapper{" +
                "t=" + t +
                '}';
    }
}

这里有一些其他的想法。

使用流:

public void add(T t) {
    if (!members.stream().anyMatch(m -> m.getClass() == t.getClass())) {
        members.add(t);
    }
}

使用 AbstractSetHashMap:

class ClassSet<E> extends AbstractSet<E> {
    private final Map<Class<?>, E> map = new HashMap<>();
    @Override
    public boolean add(E e) {
        // this can be
        // return map.putIfAbsent(e.getClass(), e) != null;
        // in Java 8
        Class<?> clazz = e.getClass();
        if (map.containsKey(clazz)) {
            return false;
        } else {
            map.put(clazz, e);
            return true;
        }
    }
    @Override
    public boolean remove(Object o) {
        return map.remove(o.getClass()) != null;
    }
    @Override
    public boolean contains(Object o) {
        return map.containsKey(o.getClass());
    }
    @Override
    public int size() {
        return map.size();
    }
    @Override
    public Iterator<E> iterator() {
        return map.values().iterator();
    }
}

A HashMap 也可以不用包裹在 Set 中使用。 Set 接口是围绕 equalshashCode 定义的,因此任何偏离此的实现在技术上都是非契约性的。此外,如果值经常迭代,您可能希望使用 LinkedHashMap