按Java Streams中的字段分组

时间:2019-02-01 22:49:06

标签: java java-8 java-stream

因此,我有一个输入JSON,如下所示:

[{
  "added": "2014-02-01T09:13:00Z",
  "author": {
    "id": "1",
    "name": "George R R Martin",
    "added_on": "2013-02-01T09:13:00Z"
  },
  "book": {
    "id": "12",
    "name": "Game of Thrones",
    "genre": "Fantasy Fiction"
  }
},
{
  "added": "2015-02-01T09:13:00Z",
  "author": {
    "id": "2",
    "name": "Patrick Rothfuss",
    "added_on": "2012-09-13T011:40:00Z"
  },
  "book": {
    "id": "15",
    "name": "The Name of the Wind",
    "genre": "Fantasy Fiction"
  }
}, {
  "added": "2016-02-01T09:13:00Z",
  "author": {
    "id": "2",
    "name": "Patrick Rothfuss",
    "added_on": "2012-09-13T011:40:00Z"
  },
  "book": {
    "id": "17",
    "name": "The Wise Man's Fear",
    "genre": "Fantasy Fiction"
  }
}]

我需要根据author.id对其进行分组。作者将拥有一个对象和他所创作的所有书籍的清单。 这是我期望的输出:

[
  {
    "author": "George R R Martin",
    "added_on": "2013-02-01T09:13:00Z",
    "books": [
      {
        "book_name": "Game of Thrones",
        "added": "2014-02-01T09:13:00Z"
      }
    ]
  },
  {
    "author": "Patrick Rothfuss",
    "added_on": "2012-09-13T011:40:00Z",
    "books": [
      {
        "book_name": "The Name of the Wind",
        "added": "2015-02-01T09:13:00Z"
      }, {
        "book_name": "The Wise Man's Fear",
        "added": "2016-02-01T09:13:00Z"
      }
    ]
  }
]

我尝试通过普通的for循环进行操作-它可以工作。但是,只是为了学习更多有关Streams的知识,我想使用Streams进行尝试。

我尝试过:

Map<Author, List<Book>> collect = authorsList.stream()
                .collect(Collectors.groupingBy(AuthorBookObj::getAuthor,
                        Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));

但是,没有得到我所需要的。相反,它创建了三个地图,而不是两个。

也尝试过:

Map<AuthorTuple, List<Book>> collect = authorsList.stream()
                .collect(Collectors.groupingBy(authors -> new AuthorTuple(authors.getAuthor().getId(),
                                authors.getAuthor().getName(), authors.getAuthor().getAddedOn()),
                        Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));

它在列表中也给了我三个对象。我期望有两位作者和每位作者对应的书籍。

AuthBookObj:

public class AuthorBookObj
{

    private String id;

    private Author author;

    private Book book;

    private String added;
    //getter, setter
}

public class Article
{
    private String name;

    private String id;

    private String genre;
}


public class Author
{
    private String name;

    private String added_on;

    private String id;
}

4 个答案:

答案 0 :(得分:1)

问题不是您处理流的方式,而是对象的相等性。

正确的方法是使用以下代码:

Map<Author, List<Book>> collect = authorsList.stream()
                .collect(Collectors.groupingBy(AuthorBookObj::getAuthor,
                        Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));

但是现在您正在比较Author对象,因为对象不同,您将获得三个条目。您需要在Author对象中添加一个哈希码和equals,以比较作者ID上的对象。

//code generated from intellij. 
// Author.java
 @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Author author = (Author) o;
        return getId() == author.getId();
    }

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

答案 1 :(得分:1)

如果您没有按需创建新的POJO类的限制,我将以这种方式进行

首先将输入的JSON解析为java对象

使用-Pattern '.z|f.'AuthorDetails类的响应类

BookDetails

作者详细信息

class Response {

private String addedOn;

private AuthorDetails author;

private BookDetails book;
 }

图书详细信息

class AuthorDetails {
private String id;

private String name;

private String addedOn;
 }

然后我将输入json映射到class BookDetails { private String id; private String name; private String gener; }

List<Response>

然后将List<Response> list = Arrays.asList(new Response()); 转换为所需的输出,我添加了几个POJO类

AuthorAndBooks

List<Response>

AuthorBooks

class AuthorAndBooks {

@JsonProperty("author")
private String author;

@JsonProperty("added_on")
private String addedOn;

@JsonProperty("books")
List<AuthorBooks> books;
 }

现在按作者姓名进行分组

class AuthorBooks {

@JsonProperty("book_name")
private String name;

@JsonProperty("added")
private String added;

  }

现在为每个作者添加书籍

Map<String, List<Response>> group = list.stream().
            collect(Collectors.groupingBy(res->res.getAuthor().getName()));

答案 2 :(得分:1)

您必须覆盖equalshashCode。如果您不这样做,您的课程将违反hashCode的一般合同,这将阻止其在HashMapHashSet等集合中正常运行。 Author类无法覆盖hashCode,导致两个相等的实例具有不相等的哈希码,这违反了hashCode协定。将此添加到您的Author类中。

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

@Override
public boolean equals(Object obj) {
    return obj instanceof Author && ((Author) obj).getId().equals(id);
}

有了这些,下面的代码片段应该可以正常工作。

Map<Author, List<Article>> booksByAuthor = authorsList.stream()
    .collect(Collectors
        .groupingBy(AuthorBookObj::getAuthor, 
            Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));

答案 3 :(得分:1)

首先要注意输入JSON中的"added"字段。这是什么意思我猜它属于Book对象。如果是这样,最好将此字段(如果可能)放在Book对象内。然后,您需要将此json反序列化为Java对象。可以通过com.fasterxml.jackson.databind.ObjectMapper完成,但是您可以为此使用任何json框架。

ObjectMapper mapper = new ObjectMapper();
AuthorBookObj[] objs = mapper.readValue(inputJson, AuthorBookObj[].class);

然后,您需要对这些对象进行分组,并且第一个解决方案非常适合:

Map<Author, List<Book>> collect = Arrays.stream(objs)
    .collect(groupingBy(AuthorBookObj::getAuthor,
            mapping(AuthorBookObj::getBook, toList())));

在上一个答案中如何提到它,您需要确保您的类中有equals/hashcode个方法用作Map(在这种情况下为Author)中的键。现在的主要困惑是期望的json输出不代表Map。它只是带有字段authoradded_onbooks之类的一些自定义对象的列表。

因此,要实现此目标,您需要将Map<Author, List<Book>>转换为自定义对象列表。例如:

public class PublicationInfo {

    private String author;
    private String added_on;
    private List<BookBriefInfo> books;
    ...
}

public class BookBriefInfo {
    private String book_name;
    private String added;
    ...
}


List<PublicationInfo> infos = new ArrayList<>();

for (Map.Entry<Author, List<Book>> entry : collect.entrySet()) {
    PublicationInfo info = new PublicationInfo();
    info.setAuthor(entry.getKey().getName());
    info.setAdded_on(entry.getKey().getAdded_on());
    List<BookBriefInfo> bookInfos = new ArrayList<>();
    for (Book book : entry.getValue()) {
        bookInfos.add(new BookBriefInfo(book.getBook_name(), book.getAdded()))
    }
    info.setBooks(bookInfos);
}

最后可以序列化:

String jsonResult = mapper.writeValueAsString(infos);

顺便说一句,要对json输出进行格式化,只需对其进行配置:

mapper.configure(SerializationFeature.INDENT_OUTPUT, true);