IT

컬렉션이나 스트림을 반환해야하나요?

lottoking 2020. 6. 11. 08:13
반응형

컬렉션이나 스트림을 반환해야하나요?


읽기 전용 뷰를 멤버 목록으로 리턴하는 메소드가 있다고 가정하십시오.

class Team {
    private List < Player > players = new ArrayList < > ();

    // ...

    public List < Player > getPlayers() {
        return Collections.unmodifiableList(players);
    }
}

또한 모든 클라이언트가 즉시 목록을 한 번 반복한다고 가정하십시오. 플레이어를 JList 또는 다른 것에 넣을 수도 있습니다. 클라이언트는 나중에 검사하기 위해 목록에 대한 참조를 저장 하지 않습니다 !

이 일반적인 시나리오에서 스트림을 대신 반환해야합니까?

public Stream < Player > getPlayers() {
    return players.stream();
}

아니면 Java에서 스트림을 비이 디오 틱으로 반환합니까? 스트림은 생성 된 것과 동일한 표현 내에서 항상 "종료"되도록 설계 되었습니까?


대답은 언제나 그렇듯이 "의존적"입니다. 반환 된 컬렉션의 크기에 따라 다릅니다. 시간이 지남에 따라 결과가 변경되는지 여부와 반환 된 결과의 일관성이 얼마나 중요한지에 따라 다릅니다. 그리고 사용자가 어떻게 대답을 사용할 지에 달려 있습니다.

먼저 스트림에서 컬렉션을 항상 가져올 수 있으며 그 반대도 가능합니다.

// If API returns Collection, convert with stream()
getFoo().stream()...

// If API returns Stream, use collect()
Collection<T> c = getFooStream().collect(toList());

따라서 질문은 발신자에게 더 유용합니다.

결과가 무한 할 수있는 경우 스트림 중 하나만 선택할 수 있습니다.

결과가 매우 클 경우 스트림을 한 번에 구체화하는 데 아무런 가치가 없기 때문에 스트림을 선호 할 수 있으며, 그렇게하면 상당한 힙 압력이 발생할 수 있습니다.

모든 발신자가 처리 (검색, 필터, 집계)를 반복하는 경우 Stream에 이미 내장되어 있고 컬렉션을 구체화 할 필요가 없으므로 (특히 사용자가 전체 결과입니다.) 이것은 매우 일반적인 경우입니다.

사용자가 여러 번 반복하거나 주변에 유지한다는 것을 알고 있더라도 스트림을 반환하기로 선택한 컬렉션 (예 : ArrayList)이 그렇지 않을 수도 있다는 사실 때문에 스트림을 대신 반환 할 수 있습니다 원하는 형식으로 발신 한 다음 발신자는 어쨌든 복사해야합니다. 스트림을 반환하면 collect(toCollection(factory))원하는 형식으로 할 수 있습니다.

위의 "스트림 선호"사례는 대부분 스트림이 더 유연하다는 사실에서 비롯됩니다. 컬렉션에 구체화하는 데 드는 비용과 제약을받지 않으면 서 사용 방법에 늦게 묶을 수 있습니다.

컬렉션을 반환해야하는 경우는 일관성 요구 사항이 강력하고 움직이는 대상의 일관된 스냅 샷을 만들어야하는 경우입니다. 그런 다음 변경되지 않는 컬렉션에 요소를 넣기를 원할 것입니다.

따라서 대부분의 경우 Stream이 정답입니다. 더 유연하고 일반적으로 불필요한 materialization 비용을 부과하지 않으며 필요한 경우 원하는 컬렉션으로 쉽게 전환 할 수 있습니다. 그러나 때로는 일관성이 높은 요구 사항으로 인해 컬렉션을 반환해야 할 수도 있고, 사용자가 컬렉션을 사용하는 방법을 알고 이것이 가장 편리한 방법임을 알고 컬렉션을 반환해야 할 수도 있습니다.


Brian Goetz의 훌륭한 답변 에 추가해야 할 몇 가지 사항이 있습니다 .

"getter"스타일 메소드 호출에서 Stream을 반환하는 것이 일반적입니다. Java 8 javadoc Stream 사용법 페이지참조하고 이외의 패키지에 대해 "Streams를 반환하는 메소드 ..."를 찾으십시오 java.util.Stream. 이러한 메소드는 일반적으로 여러 값 또는 무언가의 집합을 나타내거나 포함 할 수있는 클래스에 있습니다. 이러한 경우 API는 일반적으로 컬렉션이나 그 배열을 반환했습니다. Brian이 자신의 답변에서 언급 한 모든 이유 때문에 여기에 스트림 반환 방법을 추가하는 것이 매우 유연합니다. 클래스는 Streams API보다 먼저 사용되기 때문에 이러한 클래스 중 다수에는 이미 컬렉션 또는 배열 반환 메소드가 있습니다. 새로운 API를 디자인 할 때 스트림 리턴 메소드를 제공하는 것이 합리적이라면 콜렉션 리턴 메소드도 추가하지 않아도됩니다.

Brian은 값을 컬렉션으로 "구체화"하는 비용을 언급했습니다. 이 점을 증폭시키기 위해 실제로 두 가지 비용이 있습니다 : 콜렉션에 값을 저장하는 비용 (메모리 할당 및 복사)과 처음에 값을 생성하는 비용. 후자의 비용은 종종 스트림의 게으름을 찾는 행동을 이용하여 줄이거 나 피할 수 있습니다. 이에 대한 좋은 예는 다음과 같은 API입니다 java.nio.file.Files.

static Stream<String>  lines(path)
static List<String>    readAllLines(path)

뿐만 아니라 않는 readAllLines결과리스트에 저장하기 위해 메모리에 전체 파일 내용을 유지해야한다, 그것은 또한 목록을 반환하기 전에 끝까지 파일을 읽을 수 있습니다. lines메소드는 설정을 수행 한 후 거의 즉시 리턴하여 필요할 때까지 파일 읽기 및 줄 바꿈을 남겨 둘 수 있습니다. 예를 들어, 발신자가 처음 10 개 라인에만 관심이있는 경우 이는 큰 이점입니다.

try (Stream<String> lines = Files.lines(path)) {
    List<String> firstTen = lines.limit(10).collect(toList());
}

물론 호출자가 패턴과 일치하는 행만 반환하도록 스트림을 필터링하면 상당한 메모리 공간을 절약 할 수 있습니다.

An idiom that seems to be emerging is to name stream-returning methods after the plural of the name of the things that it represents or contains, without a get prefix. Also, while stream() is a reasonable name for a stream-returning method when there is only one possible set of values to be returned, sometimes there are classes that have aggregations of multiple types of values. For example, suppose you have some object that contains both attributes and elements. You might provide two stream-returning APIs:

Stream<Attribute>  attributes();
Stream<Element>    elements();

Were streams designed to always be "terminated" inside the same expression they were created in?

That is how they are used in most examples.

Note: returning a Stream is not that different to returning a Iterator (admitted with much more expressive power)

IMHO the best solution is to encapsulate why you are doing this, and not return the collection.

e.g.

public int playerCount();
public Player player(int n);

or if you intend to count them

public int countPlayersWho(Predicate<? super Player> test);

If the stream is finite, and there is an expected/normal operation on the returned objects which will throw a checked exception, I always return a Collection. Because if you are going to be doing something on each of the objects that can throw a check exception, you will hate the stream. One real lack with streams i there inability to deal with checked exceptions elegantly.

Now, perhaps that is a sign that you don't need the checked exceptions, which is fair, but sometimes they are unavoidable.


In contrast to collections, streams have additional characteristics. A stream returned by any method might be:

  • finite or infinite
  • parallel or sequential (with a default globally shared threadpool that can impact any other part of an application)
  • ordered or non-ordered

These differences also exists in collections, but there they are part of the obvious contract:

  • All Collections have size, Iterator/Iterable can be infinite.
  • Collections are explicitly ordered or non-ordered
  • Parallelity is thankfully not something the collection care about beyond thread-safety.

As a consumer of a stream (either from a method return or as a method parameter) this is a dangerous and confusing situation. To make sure their algorithm behaves correctly, consumers of streams need to make sure the algorithm makes no wrong assumption about the stream characteristics. And that is a very hard thing to do. In unit testing, that would mean that you have to multiply all your tests to be repeated with the same stream contents, but with streams that are

  • (finite, ordered, sequential)
  • (finite, ordered, parallel)
  • (finite, non-ordered, sequential)...

Writing method guards for streams that throw an IllegalArgumentException if the input stream has a characteristics breaking your algorithm is difficult, because the properties are hidden.

That leaves Stream only as a valid choice in a method signature when none of the problems above matter, which is rarely the case.

It is much safer to use other datatypes in method signatures with an explicit contract (and without implicit thread-pool processing involved) that makes it impossible to accidentally process data with wrong assumptions about orderedness, sizedness or parallelity (and threadpool usage).


I think it depends on your scenario. May be, if you make your Team implement Iterable<Player>, it is sufficient.

for (Player player : team) {
    System.out.println(player);
}

or in the a functional style:

team.forEach(System.out::println);

But if you want a more complete and fluent api, a stream could be a good solution.


Perhaps a Stream factory would be a better choice. The big win of only exposing collections via Stream is that it better encapsulates your domain model’s data structure. It’s impossible for any use of your domain classes to affect the inner workings of your List or Set simply by exposing a Stream.

It also encourages users of your domain class to write code in a more modern Java 8 style. It’s possible to incrementally refactor to this style by keeping your existing getters and adding new Stream-returning getters. Over time, you can rewrite your legacy code until you’ve finally deleted all getters that return a List or Set. This kind of refactoring feels really good once you’ve cleared out all the legacy code!


I would probably have 2 methods, one to return a Collection and one to return the collection as a Stream.

class Team
{
    private List<Player> players = new ArrayList<>();

// ...

    public List<Player> getPlayers()
    {
        return Collections.unmodifiableList(players);
    }

    public Stream<Player> getPlayerStream()
    {
        return players.stream();
    }

}

This is the best of both worlds. The client can choose if they want the List or the Stream and they don't have to do the extra object creation of making an immutable copy of the list just to get a Stream.

This also only adds 1 more method to your API so you don't have too many methods

참고URL : https://stackoverflow.com/questions/24676877/should-i-return-a-collection-or-a-stream

반응형