通过Java中的多个属性对对象进行分组的一般方法

时间:2016-03-31 13:43:41

标签: java algorithm list collections grouping

我有一系列项目:

class Item {        
    String type;
    boolean flag;
    int size;
    ...
}

有几种可能的类型(例如" a"," b"和" c")因此有几种类型标志值的可能组合({ {1}},...)。我需要折叠具有相同值组合的项目,因此我使用此签名的["a" ; false], ["a" ; true"], ["b" ; false]方法

collapse

我需要的是将输入项目列表分成具有相同Item collapse(Collection<Item> items) type值的组

flag

所以我可以崩溃每个小组

List<Collection<Item>> getGroups(Collection<Item> items) // method I need

所以我可以创建一个Map of Map或制作一些复合键,但它需要一些我想避免的样板代码。将来我可以有更多的分组属性,因此解决方案不应该在这两个属性上进行硬编码,而是可以轻松扩展到新的属性。

如何做得好呢?这个问题有一个众所周知的解决方案吗?

3 个答案:

答案 0 :(得分:3)

您可以使用Collectors.groupingBy

public static void main(String[] args) {
    List<Item> list = new ArrayList<>();
    list.add(new Item(1, true, 1));
    list.add(new Item(1, true, 2));

    list.add(new Item(1, false, 3));
    list.add(new Item(1, false, 4));

    list.add(new Item(2, true, 5));
    list.add(new Item(2, false, 6));

    Collection<List<Item>> result = list.stream()
            .collect(Collectors.groupingBy(x -> Arrays.<Object>asList(x.keyA, x.keyB)))
            .values();

    for (List<Item> items : result) {
        System.out.println(items);
    }
}

static class Item {
    Integer keyA;
    Boolean keyB;
    Integer value;

    public Item(Integer keyA, Boolean keyB, Integer value) {
        this.keyA = keyA;
        this.keyB = keyB;
        this.value = value;
    }

    @Override
    public String toString() {
        return "Item{" +
                "keyA=" + keyA +
                ", keyB=" + keyB +
                ", value=" + value +
                '}';
    }
}

答案 1 :(得分:1)

为了知道哪个Item在逻辑上等于某些其他Item进行折叠,我会将其责任放在Item类本身上。您可以覆盖equals方法,但是如果您要将它们放在Set某处,这可能会导致不良结果,因此用于检查的单独方法可能是最好的。

另一种选择是获取将用于此检查的那些字段,并将它们转换为Item的内部类。然后可以仅为内部类重写equalshashCode,并将其实例用作Map的键。

然而,这些都不会自动包含您稍后添加的任何新字段。因此,维护类的任何人都要确保将需要包含在check(或equals / hashCode)中的任何内容添加到方法中。

我真正想到的唯一方法就是使用反射。如果必须考虑的任何事情只放在内部类中,那就行了。如果必须直接将它保留在Item类上,那么定义注释(具有运行时保留)可能很有用。执行检查的代码(或者使用等于/ hashCode)可以反映在类上并使用每个带注释的字段。

注释可能如下所示:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CollapseField {

}

然后像这样使用:

class Item {      
    @CollapseField  
    String type;
    @CollapseField
    boolean flag;
    int size;
    ...
}

使用它的代码然后需要检查哪些字段被注释并获取它们的值(这两个操作都使用反射)并检查与其他对象的相等性以确定哪些属于一起。由于这可能会对性能产生相当大的影响,因此使用缓存来处理类似哈希代码的事情将是一个好主意。

最后,我不确定硬编码使用过的值是否值得,除非你要在大量的类中使用它,否则字段的数量可能变得非常大。

最后,Java似乎有些奇怪,但也许使用属性模式而不是字段可能有意义。虽然你会失去一些类型的安全性。史蒂夫·叶格(Steve Yegge)做了一篇很长但很有趣的帖子:http://steve-yegge.blogspot.be/2008/10/universal-design-pattern.html

这几乎是我能想到的最重要的事情。据我所知,没有标准方法。也许有人知道一些方便的图书馆提供解决方案。

编辑:这是一个示例,其中用于键的字段被制作成一个内部类,它实现equalshashCode,因此它可以用作{{1}的键}}:

Map

import java.util.Objects; public class Item { int size; final Key key; public class Key { String type; boolean flag; public Key(String type, boolean flag) { this.type = type; this.flag = flag; } public String getType() { return type; } public void setType(String type) { this.type = type; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } @Override public int hashCode() { int hash = 5; hash = 89 * hash + Objects.hashCode(this.type); hash = 89 * hash + (this.flag ? 1 : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Key other = (Key) obj; if (!Objects.equals(this.type, other.type)) { return false; } if (this.flag != other.flag) { return false; } return true; } } public Item(String type, boolean flag, int size) { key = new Key(type, flag); this.size = size; } public String getType() { return key.type; } public void setType(String type) { this.key.type = type; } public boolean isFlag() { return key.flag; } public void setFlag(boolean flag) { this.key.flag = flag; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public Key getKey() { return key; } } 级别的getter和setter将部分字段委托给Item。请注意,如果您只浏览Key,则可能不需要Key中的getter和setter,因为这些字段可以直接访问包含的类。如果您需要添加必须属于密钥的字段,请将其添加到Item。如果不能用于识别,请将其直接添加到Key。如果您必须更新它们,任何体面的IDE都可以轻松自动生成Itemequals

请注意,如果您在某个框架中使用该类进行反射或内省,则此解决方案可能会中断。根据接近的方式,hashCode中的字段最终可能会被视为Key的属性(由于getter / setter)。像JPA或直接通过反射接近字段的EJB容器可能无法使用它。

答案 2 :(得分:0)

使用复合键放置我当前的方法,但仍在等待新的想法

 $('#test').keyboard({ layout: 'qwerty', usePreview: false});

可以轻松添加新字段,但字符串键看起来不太好。