programing

컬렉션을 반환해야 합니까, 아니면 스트림을 반환해야 합니까?

bestcode 2022. 8. 12. 23:26
반응형

컬렉션을 반환해야 합니까, 아니면 스트림을 반환해야 합니까?

읽기 전용 뷰를 멤버리스트로 되돌리는 메서드가 있다고 합니다.

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

    // ...

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

또한 클라이언트가 수행하는 모든 작업은 목록을 즉시 한 번 반복한다고 가정합니다.선수들을 JL리스트에 넣거나 뭐 그런 거겠죠클라이언트는 나중에 검사하기 위해 목록에 대한 참조를 저장하지 않습니다!

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

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

아니면 Java에서 스트림을 반환하는 것은 자동적이지 않은가?스트림이 작성된 것과 동일한 표현으로 항상 "종료"되도록 설계되었습니까?

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

첫 번째로, 당신은 항상 얻을 수 있다는 것에 주의하세요.Collection에서Stream, 그 반대도 마찬가지입니다.

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

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

문제는 발신자에게 어떤 것이 더 유용한가 하는 것입니다.

결과가 무한대일 경우 선택할 수 있는 것은 단 하나뿐입니다.Stream.

결과가 매우 클 수 있다면 아마도Stream한 번에 실현하는 것은 가치가 없을 수 있으며, 그렇게 하면 상당한 힙 압력이 발생할 수 있습니다.

발신자가 그것을 통해서 반복하는 것(검색, 필터, 집약)의 경우는, 다음의 순서에 따라 주세요.Stream,부터Stream에는 이러한 기능이 이미 내장되어 있어 수집을 구체화할 필요가 없습니다(특히 사용자가 전체 결과를 처리하지 않을 수 있는 경우).이것은 매우 흔한 경우입니다.

사용자가 여러 번 반복할 것을 알고 있거나, 그렇지 않은 경우 데이터를 계속 보관하고 있는 경우라도,Stream그 대신, 어떤 것이든Collection(예를 들어,ArrayList)가 원하는 형식이 아닐 수 있습니다.또, 발신자는, 그 형식을 카피할 필요가 있습니다.a를 반환할 경우Stream, 그들은 할 수 있다collect(toCollection(factory))원하는 형태로 만들 수 있습니다.

위의 '선호'Stream"사례는 대부분 ...라는 사실에서 비롯된다.Stream유연성이 향상되어 비용이나 제약이 들지 않고 사용 방법을 늦출 수 있습니다.Collection.

반환해야 하는 유일한 케이스는Collection즉, 강력한 일관성 요건이 있는 경우 이동 대상의 일관된 스냅샷을 생성해야 합니다.그런 다음 요소를 변경되지 않는 컬렉션에 넣을 수 있습니다.

그래서 저는 대부분의 경우Stream이것이 정답입니다.유연성이 향상되어 통상 불필요한 실현 비용이 들지 않으며 필요에 따라 쉽게 원하는 컬렉션으로 전환할 수 있습니다.하지만 가끔, 당신은 그것을 반환해야 할 수도 있습니다.Collection(예를 들어, 엄격한 일관성 요건으로 인해) 또는 반환을 원할 수 있습니다.Collection사용자가 어떻게 사용할지, 그리고 이것이 그들에게 가장 편리한 것이라는 것을 알기 때문입니다.

이미 적합한 제품이 있는 경우Collection'거기'를 하고 있는 경우, 유저는 그 조작에 대해서, 그 조작을 실시할 필요가 있는 것 같습니다.Collection그렇다면 가지고 있는 것을 그냥 돌려주는 것이 합리적인 선택이다(유일한 것은 아니지만, 더 취약하다).

Brian Goetz의 훌륭한 답변에 덧붙일 점이 몇 가지 있습니다.

일반적으로 "getter" 스타일의 메서드 호출에서 스트림을 반환합니다.Java 8 javadoc의 Stream usage 페이지를 참조하여 "methods...스트림을 반환한다"는 메시지가 표시됩니다.java.util.Stream이러한 메서드는 보통 여러 개의 값 또는 집약을 나타내거나 포함할 수 있는 클래스에 있습니다.이러한 경우 API는 일반적으로 컬렉션 또는 이들의 배열을 반환합니다.Brian이 답변에서 언급한 모든 이유로 Stream-Return 메서드를 여기에 추가하는 것은 매우 유연합니다.이러한 클래스의 대부분은 스트림 API보다 이전 클래스이기 때문에 컬렉션 또는 배열 반환 메서드를 이미 가지고 있습니다.새로운 API를 설계할 때 Stream-Return 메서드를 제공하는 것이 타당하다면 컬렉션-Return 메서드를 추가할 필요가 없을 수 있습니다.

브라이언은 그 가치를 컬렉션에 "실체화"하는 데 드는 비용을 언급했습니다.이 점을 더 자세히 설명하자면, 실제로는 두 가지 비용이 있습니다. 컬렉션에 값을 저장하는 비용(메모리 할당 및 복사)과 애초에 값을 생성하는 비용입니다.후자의 비용은 종종 스트림의 게으름을 추구하는 행동을 이용하여 줄이거나 피할 수 있다.를 들 수 있습니다.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());
}

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

등장하고 것은 스트림 것 또는 중 을 되돌리는 방법을 ' 방법'으로 명명하는 입니다.get 「」, 「」의 사이에, 「」라고 하는 것도 있습니다.stream()는 반환할 수 있는 값의 세트가1개밖에 없는 경우 스트림 스위칭 방식의 적절한 이름입니다.여러 유형의 값의 집약을 가진 클래스가 있는 경우도 있습니다.예를 들어 속성과 요소를 모두 포함하는 개체가 있다고 가정합니다. API는로 지정할 수 즉, 스트림을 되돌리는 API입니다.

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

스트림이 작성된 것과 동일한 표현으로 항상 "종료"되도록 설계되었습니까?

그것이 대부분의 예에서 사용되는 방법이다.

주의: 스트림을 반환하는 것은 반복기를 반환하는 것과 크게 다르지 않습니다(표현력이 대폭 향상됨).

IMHO는 이 작업을 수행하는 이유를 캡슐화하고 컬렉션을 반환하지 않는 것이 가장 좋은 솔루션입니다.

예.

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

또는 그것들을 세고 싶다면

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

스트림이 유한하고 반환된 객체에 체크된 예외가 발생하는 예상/정상적인 동작이 있을 경우 항상 컬렉션을 반환합니다.각 개체에 대해 체크 예외를 발생시킬 수 있는 작업을 수행할 경우 스트림을 싫어하게 됩니다.흐름의 진정한 부족함 나는 체크된 예외를 우아하게 다룰 수 없다.

체크된 예외가 필요없다는 신호일 수도 있습니다.이것은 공평하지만 때로는 피할 수 없는 경우도 있습니다.

보다 주목받는 응답자 중 일부는 훌륭한 일반적인 조언을 했지만, 아무도 이에 대해 언급하지 않은 것이 놀랍다.

' 것이 Collection콜되어 있습니다.로하는 것은 의미가 없습니다.예를 들어 멤버필드의 경우)로 변환하는 것은 의미가 없습니다.Stream발신자 스스로도 간단하게 할 수 있습니다.한편, 발신자가 원래의 형태로 데이터를 소비하고 싶은 경우는, 데이터를 변환합니다.Stream원래 구조의 복사본을 다시 구현하기 위해 중복 작업을 수행해야 합니다.

컬렉션과 달리 스트림에는 추가적인 특성이 있습니다.어떤 방법으로든 반환되는 스트림은 다음과 같습니다.

  • 유한 또는 무한
  • 병렬 또는 순차(어플리케이션의 다른 부분에 영향을 줄 수 있는 기본 글로벌 공유 스레드풀 포함)
  • 순서부여 또는 순서부여
  • 참조를 닫거나 닫지 않도록 유지하는 것

이러한 차이는 추심에도 존재하지만, 명백한 계약의 일부입니다.

  • 모든 컬렉션에는 크기가 있으며 반복자/반복자 수는 무한대일 수 있습니다.
  • 컬렉션이 명시적으로 정렬되었거나 정렬되지 않았습니다.
  • 다행히 병렬성은 스레드 안전성 이상의 수집이 신경 쓰는 것이 아닙니다.
  • 또한 일반적으로 컬렉션을 닫을 수 없기 때문에 리소스를 사용하여 테스트를 수행하는 것에 대해 걱정할 필요가 없습니다.

스트림(메서드 리턴 또는 메서드 파라미터 중 하나)의 소비자로서 이는 위험하고 혼란스러운 상황입니다.알고리즘이 올바르게 동작하도록 하려면 알고리즘이 스트림 특성에 대해 잘못된 가정을 하지 않도록 해야 합니다.그리고 그것은 매우 어려운 일이다.유닛 테스트에서는 모든 테스트를 동일한 스트림 콘텐츠로 반복해야 하지만 스트림을 사용하여 반복해야 합니다.

  • (순서, 순서, 시퀀셜, requiring-close)
  • (수직, 정렬, 병렬, requiring-close)
  • (정렬, 비순차, 순차, 요구-마감)...

IlgalArgument를 던지는 스트림에 대한 쓰기 메서드 가드입력 스트림에 특성이 있는 경우 속성이 숨겨져 있기 때문에 알고리즘을 중단하는 것은 어렵습니다.

매뉴얼을 사용하면 문제가 완화되지만 결함이 있어 간과되는 경우가 많아 스트림 공급자가 변경되어도 도움이 되지 않습니다.예를 들어 Java8 파일의 다음 javadoc을 참조하십시오.

 /**
  * [...] The returned stream encapsulates a Reader. If timely disposal of
  * file system resources is required, the try-with-resources 
  * construct should be used to ensure that the stream's close 
  * method is invoked after the stream operations are completed.
  */
 public static Stream<String> lines(Path path, Charset cs)
 /**
  * [...] no mention of closing even if this wraps the previous method
  */
public static Stream<String> lines(Path path)

따라서 스트림 생산자와 소비자가 동일한 코드베이스에 있는 경우(예를 들어 많은 장소에서 재사용할 수 있는 클래스의 퍼블릭인터페이스의 일부가 아닌 경우)에는 위의 문제가 발생하지 않고 모든 소비자가 알고 있는 경우에만 메서드시그니처 내에서 스트림은 유효한 선택으로 남습니다.

순서성, 크기 또는 병렬성(및 스레드풀 사용률)에 대한 잘못된 가정을 가진 데이터를 실수로 처리하는 것을 방지하기 위해 명시적인 계약(및 암묵적인 스레드풀 처리가 수반되지 않음)이 있는 메서드 시그니처에 다른 데이터 유형을 사용하는 것이 훨씬 안전합니다.

시나리오에 따라 다르겠죠?그럴 수도 있어요, 만약 당신이Team시행하다Iterable<Player>이 정도면 충분합니다.

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

또는 기능 스타일:

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

그러나 보다 완벽하고 유연한 api를 원한다면 스트림이 좋은 해결책이 될 수 있습니다.

아마도 스트림 공장이 더 나은 선택이 될 거예요.Stream을 통해서만 컬렉션을 노출할 수 있는 가장 큰 이점은 도메인 모델의 데이터 구조를 더 잘 캡슐화할 수 있다는 것입니다.단순히 스트림을 노출하는 것만으로 도메인 클래스를 사용하여 목록 또는 세트의 내부 작업에 영향을 미치는 것은 불가능합니다.

또한 도메인 클래스 사용자가 보다 현대적인 Java 8 스타일로 코드를 작성하도록 권장합니다.기존 getter를 유지하고 새로운 stream-returning getter를 추가함으로써 이 스타일에 대한 증분 리팩터가 가능합니다.List 또는 Set을 반환하는 모든 getter를 최종적으로 삭제할 때까지 시간이 지남에 따라 레거시 코드를 다시 작성할 수 있습니다.이런 리팩터링은 레거시 코드를 모두 클리어하고 나면 매우 기분이 좋습니다!

두 가지 방법이 있을 것 같은데 하나는 A를 반환하는 방법이고Collection그리고 하나는 컬렉션을 반환한다.Stream.

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

// ...

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

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

}

이게 둘 다 최고야.클라이언트는 목록 또는 스트림 중 어느 쪽을 원하는지 선택할 수 있으며, 스트림을 얻기 위해 목록의 불변 복사본을 만드는 추가 개체 생성을 수행할 필요가 없습니다.

또한 API에 메서드가 1개만 추가되므로 메서드가 너무 많지 않습니다.

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

반응형