如何在 Java 中使用 objects 正确复制 collections

How to correctly copy collections with objects in Java

我有一个方法 (List<Book> getListWithPrefixInName(List<Book> books)) 接受 collection 作为输入并对每个 collection object 执行转换。而且我不希望这些变化反映在转移collection上,只是为了return修改collection。因为我需要我原来的collection.

因此,我想通过创建一个新的 collection:

来复制
List<Book> clone = new ArrayList<>(books);

可是我怎么错了... 我的代码:

public class App {

    public static final String PREFIX = "PREFIX";

    public static void main(String[] args) {
        List<Book> books = new ArrayList<>();
        books.add(Book.of(1L, "The Catcher in the Rye"));
        books.add(Book.of(2L, "The Green Mile"));
        List<Book> booksWithPrefix = getListWithPrefixInName(books);

        for (Book book : books) {
            if (book.getName().contains(PREFIX)) {
                System.out.println(String.format("original book: '%s' have been changed", book));
            }
        }
    }

    public static List<Book> getListWithPrefixInName(List<Book> books) {
        List<Book> clone = new ArrayList<>(books);  // I thought that this cloning would be enough
        return clone.stream()
                .peek(b -> b.setName(PREFIX + ": " + b.getName()))
                .collect(toList());
    }
}

class Book {
    private Long id;
    private String name;

    private Book(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public static Book of(Long id, String name) {
        return new Book(id, name);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Book{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(id, book.id) &&
                Objects.equals(name, book.name);
    }

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

因此我的整个原collection都被改变了,太糟糕了。

所以我不得不通过 third-party 库使用深度克隆:

import org.apache.commons.lang3.SerializationUtils;

public class App {

    public static final String PREFIX = "PREFIX";

    public static void main(String[] args) {
        List<Book> books = new ArrayList<>();
        books.add(Book.of(1L, "The Catcher in the Rye"));
        books.add(Book.of(2L, "The Green Mile"));
        List<Book> booksWithPrefix = getListWithPrefixInName(books);

        for (Book book : books) {
            if (book.getName().contains(PREFIX)) {
                System.out.println(String.format("original book: '%s' have been changed", book));
            }
        }
    }

    public static List<Book> getListWithPrefixInName(List<Book> books) {
        return books.stream()
                .map(b -> SerializationUtils.clone(b))  // deep clone
                .peek(b -> b.setName(PREFIX + ": " + b.getName()))
                .collect(toList());
    }
}

class Book implements Serializable {
    private Long id;
    private String name;

    private Book(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public static Book of(Long id, String name) {
        return new Book(id, name);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Book{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(id, book.id) &&
                Objects.equals(name, book.name);
    }

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

这个问题有没有更优雅的解决方案?还是使用深度克隆是唯一正确的解决方案?

在流中更改状态(就像您在 peek 中所做的那样)是一种反模式。不要这样做。

我推荐这样的东西:

  public static List<Book> getListWithPrefixInName(List<Book> books) {
        return books.stream()
                .map(b -> Book.of(b.getId(), PREFIX + ": " + b.getName()))
                .collect(toList());
    }