Java SE 8에 페어 또는 튜플이 있습니까?


185

Java SE 8에서 게으른 기능 연산으로 놀고 있으며 두 번째 요소 를 기반으로 한 쌍 / 튜플에 map색인 i을 만들고 마지막으로 색인 만 출력 하려고합니다 .(i, value[i])filtervalue[i]

여전히이 문제를 겪어야 합니까? Java에서 C ++ Pair <L, R>에 해당하는 것은 무엇입니까? 람다와 개울의 대담한 새로운 시대에?

업데이트 : 나는 아래 답변 중 하나에서 @dkatzel이 제공하는 깔끔한 솔루션을 가진 다소 단순화 된 예를 제시했습니다. 그러나 일반화 되지는 않습니다 . 따라서보다 일반적인 예를 추가하겠습니다.

package com.example.test;

import java.util.ArrayList;
import java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    boolean [][] directed_acyclic_graph = new boolean[][]{
        {false,  true, false,  true, false,  true},
        {false, false, false,  true, false,  true},
        {false, false, false,  true, false,  true},
        {false, false, false, false, false,  true},
        {false, false, false, false, false,  true},
        {false, false, false, false, false, false}
    };

    System.out.println(
        IntStream.range(0, directed_acyclic_graph.length)
        .parallel()
        .mapToLong(i -> IntStream.range(0, directed_acyclic_graph[i].length)
            .filter(j -> directed_acyclic_graph[j][i])
            .count()
        )
        .filter(n -> n == 0)
        .collect(() -> new ArrayList<Long>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
    );
  }

}

이것은 모두 세 개의 열에 대한 카운트 에 해당하는 잘못된 출력을 제공 합니다 . 내가 필요한 것은 이 세 열의 지표 입니다. 올바른 출력은이어야합니다 . 이 결과를 어떻게 얻을 수 있습니까?[0, 0, 0]false[0, 2, 4]


2
벌써 AbstractMap.SimpleImmutableEntry<K,V>몇 년이 지났지 만 ... 어쨌든 그냥 필터링으로 매핑하고 다시 매핑 i하는 대신에 : 매핑없이 처음부터 필터링하는 것이 어떻습니까? (i, value[i])value[i]ivalue[i]
Holger 2016 년

@Holger 배열의 어떤 인덱스에 기준과 일치하는 값이 들어 있는지 알아야합니다. i스트림 을 보존하지 않고는 할 수 없습니다 . 또한 value[i]기준이 필요 합니다. 그래서 내가 필요한 이유(i, value[i])
necromancer

1
@necromancer 맞습니다. 배열, 랜덤 액세스 컬렉션 또는 저렴한 함수와 같이 인덱스에서 값을 얻는 것이 저렴한 경우에만 작동합니다. 문제는 단순화 된 유스 케이스를 제시하고 싶지만 지나치게 단순화되어 특별한 경우에 굴복 한 것입니다.
스튜어트 마크

1
@necromancer 나는 당신이 묻는 생각을 명확하게하기 위해 마지막 단락을 약간 편집했습니다. 맞아? 또한,이은에 대한 질문 감독 (비순환되지 않음) 그래프? (별로 중요하지 않습니다.) 마지막으로 원하는 출력은 [0, 2, 4]?
스튜어트 마크

1
이 문제를 해결하는 올바른 솔루션은 미래의 Java 릴리스 지원 튜플을 반환 유형으로 (Object의 특수한 경우) 지원하고 람다식이 해당 튜플을 매개 변수에 직접 사용할 수 있다고 생각합니다.
Thorbjørn Ravn Andersen

답변:


206

업데이트 : 이 답변은 원래 질문에 대한 답변입니다 . Java SE 8에 페어 또는 튜플이 있습니까? (그리고 암시 적으로, 그렇지 않다면 왜 안됩니까?) OP는 더 완전한 예제로 질문을 업데이트했지만 어떤 종류의 쌍 구조를 사용하지 않고도 해결할 수있는 것처럼 보입니다. [OP의 메모 : 여기 에 다른 정답이 있습니다.]


짧은 대답은 '아니요'입니다. 자신의 롤을 구현하거나이를 구현하는 여러 라이브러리 중 하나를 가져와야합니다.

PairJava SE에서 클래스를 갖는 것이 제안되고 적어도 한 번 거부되었습니다. OpenJDK 메일 링리스트 중 하나 에서이 토론 스레드 를 참조하십시오 . 트레이드 오프는 분명하지 않습니다. 한편으로 다른 라이브러리와 응용 프로그램 코드에는 많은 Pair 구현이 있습니다. 이는 필요를 보여 주며, 이러한 클래스를 Java SE에 추가하면 재사용 및 공유가 증가합니다. 반면에 Pair 클래스를 사용하면 필요한 유형과 추상화를 만들지 않고 Pairs 및 Collections에서 복잡한 데이터 구조를 만드는 유혹을 더할 수 있습니다. (그것이 스레드에서 보낸 Kevin Bourillion의 메시지에 대한 역설입니다 .)

나는 모든 사람이 그 전체 이메일 스레드를 읽는 것이 좋습니다. 놀랍도록 통찰력이 있으며 흠이 없습니다. 꽤 설득력이 있습니다. 처음 시작했을 때, "예, Java SE에는 Pair 클래스가 있어야합니다"라고 생각했지만 스레드가 끝날 무렵에는 마음이 바뀌 었습니다.

그러나 JavaFX에는 javafx.util.Pair 클래스가 있습니다. JavaFX의 API는 Java SE API와 별도로 발전했습니다.

링크 된 질문에서 알 수 있듯이 Java의 C ++ 쌍에 해당하는 것은 무엇입니까? 이러한 단순한 API를 둘러싼 상당히 큰 디자인 공간이 있습니다. 객체가 불변이어야합니까? 그것들은 직렬화 가능해야합니까? 비교할 수 있어야합니까? 수업은 마지막이어야합니까? 두 요소를 주문해야합니까? 인터페이스 또는 클래스 여야합니까? 왜 쌍에서 멈춰? 트리플, 쿼드 또는 N 튜플이 아닌 이유는 무엇입니까?

물론 요소의 피할 수없는 이름 지정이 있습니다.

  • (a, b)
  • (첫번째 두번째)
  • (왼쪽 오른쪽)
  • (차, cdr)
  • (푸, 바)
  • 기타

거의 언급되지 않은 한 가지 큰 문제는 페어와 프리미티브의 관계입니다. 당신은 경우 (int x, int y)로 이것을 나타내는 2 차원 공간의 점을 나타내는 자료 Pair<Integer, Integer>소비하는 세 개의 객체 대신 두 개의 32 비트 단어를. 또한 이러한 객체는 힙에 상주해야하며 GC 오버 헤드가 발생합니다.

스트림과 마찬가지로 페어에 대한 기본 전문화가 반드시 필요한 것 같습니다. 우리는보고 싶습니까?

Pair
ObjIntPair
ObjLongPair
ObjDoublePair
IntObjPair
IntIntPair
IntLongPair
IntDoublePair
LongObjPair
LongIntPair
LongLongPair
LongDoublePair
DoubleObjPair
DoubleIntPair
DoubleLongPair
DoubleDoublePair

심지어 IntIntPair힙에 하나의 객체가 여전히 필요합니다.

물론 이것들은 java.util.functionJava SE 8 의 패키지에서 기능적 인터페이스의 확산을 연상시킵니다. 만약 부풀린 API를 원하지 않는다면, 어떤 API를 사용하지 않겠습니까? 이것으로는 충분하지 않으며, 전문화 Boolean도 추가해야 한다고 주장 할 수 있습니다 .

내 생각에 Java가 오래 전에 Pair 클래스를 추가했다면 단순하거나 단순 해졌으며 지금 우리가 계획하고있는 많은 유스 케이스를 만족시키지 못했을 것입니다. JDK 1.0 시간대에 Pair가 추가 되었다면, 아마도 변경 가능했을 것입니다! (java.util.Date를보십시오.) 사람들이 그것에 만족했을까요? 내 생각에 Java에 Pair 클래스가 있다면 실제로 유용하지는 않지만 모든 사람들이 여전히 자신의 요구를 충족시키기 위해 자신의 롤링을 수행 할 것이며 외부 라이브러리에는 다양한 Pair 및 Tuple 구현이있을 것입니다. 사람들은 여전히 ​​Java의 Pair 클래스를 수정하는 방법에 대해 논쟁하고 있습니다. 다시 말해, 오늘날 우리와 같은 장소에 있습니다.

한편, 일부 문제는 근본적인 문제를 해결하기 위해 진행 중이며, 이는 값 유형 에 대한 JVM (및 결국 Java 언어)에서 더 잘 지원됩니다 . 이 Value of States 문서를 참조하십시오 . 이것은 예비적이고 추론적인 작업이며 JVM 관점의 문제 만 다루지 만 이미 그 뒤에 상당한 생각이 있습니다. 물론 이것이 Java 9에 들어가거나 어느 곳에 나 들어갈 것이라는 보장은 없지만이 주제에 대한 현재의 사고 방향을 보여줍니다.


3
프리미티브가있는 @necromancer 팩토리 메소드는 도움이되지 않습니다 Pair<T,U>. 제네릭은 참조 형식이어야합니다. 모든 프리미티브는 저장 될 때 박스에 저장됩니다. 프리미티브를 저장하려면 실제로 다른 클래스가 필요합니다.
스튜어트 마크

3
@necromancer 그리고 네, 박스형 프리미티브 생성자는 공개 valueOf되어서는 안되며 박스형 인스턴스를 얻는 유일한 방법이어야합니다. 그러나 그것들은 Java 1.0 이후로 거기에 있었고 아마도이 시점에서 변경하려고 시도 할 가치가 없습니다.
스튜어트 마크

3
물론, 단지이 있어야 공공 Pair또는 Tuple투명 배경 (최적화 된 스토리지)에 필요한 전문 클래스를 생성하는 팩토리 메소드와 클래스입니다. 결국 람다는 정확히 다음을 수행합니다. 임의의 임의의 수의 변수를 캡처 할 수 있습니다. 그리고 invokedynamic명령 에 의해 실행되는 런타임에 적절한 튜플 클래스를 생성 할 수있는 언어 지원을 이미지화합니다 .
Holger

3
@Holger 기존 JVM에 값 유형을 개조하는 경우에는 이와 같은 것이 작동하지만 값 유형 제안 (현재 "Project Valhalla" )이 훨씬 더 급진적입니다. 특히, 값 유형이 반드시 힙 할당되는 것은 아닙니다. 또한 오늘날의 객체와는 달리 오늘날의 프리미티브와 마찬가지로 가치에는 정체성이 없습니다.
스튜어트 마크

2
@Stuart Marks : 내가 설명한 유형이 이러한 값 유형의 "박스형"유형일 수 있으므로 방해하지 않습니다. invokedynamic람다 생성과 유사한 기반 공장을 사용하면 나중에 개조하면 아무런 문제가 없습니다. 그건 그렇고, 람다는 정체성이 없습니다. 명시 적으로 언급했듯이 오늘날 인식 할 수있는 ID는 현재 구현의 결과물입니다.
Holger


22

슬프게도 Java 8은 쌍이나 튜플을 도입하지 않았습니다. 당신은 항상 사용할 수 있습니다 org.apache.commons.lang3.tuple (물론 개인적으로 자바 (8)와 함께 사용을 할 수있는) 또는 당신은 당신의 자신의 래퍼를 생성 할 수 있습니다. 또는지도를 사용하십시오. 또는 링크 된 해당 질문에 대한 대답 에 설명 된 것과 같은 것들 .


업데이트 : JDK 14는 미리보기 기능으로 레코드 를 소개 합니다. 이것들은 튜플이 아니지만 많은 동일한 문제를 저장하는 데 사용될 수 있습니다. 위의 특정 예에서 다음과 같이 보일 수 있습니다.

public class Jdk14Example {
    record CountForIndex(int index, long count) {}

    public static void main(String[] args) {
        boolean [][] directed_acyclic_graph = new boolean[][]{
                {false,  true, false,  true, false,  true},
                {false, false, false,  true, false,  true},
                {false, false, false,  true, false,  true},
                {false, false, false, false, false,  true},
                {false, false, false, false, false,  true},
                {false, false, false, false, false, false}
        };

        System.out.println(
                IntStream.range(0, directed_acyclic_graph.length)
                        .parallel()
                        .mapToObj(i -> {
                            long count = IntStream.range(0, directed_acyclic_graph[i].length)
                                            .filter(j -> directed_acyclic_graph[j][i])
                                            .count();
                            return new CountForIndex(i, count);
                        }
                        )
                        .filter(n -> n.count == 0)
                        .collect(() -> new ArrayList<CountForIndex>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
        );
    }
}

플래그를 사용하여 JDK 14 (작성 당시의 초기 액세스 빌드)로 컴파일하고 실행 --enable-preview하면 다음 결과가 나타납니다.

[CountForIndex[index=0, count=0], CountForIndex[index=2, count=0], CountForIndex[index=4, count=0]]

실제로 @StuartMarks의 답변 중 하나는 튜플없이 해결할 수 있었지만 일반화되지 않기 때문에 아마도 필요할 것입니다.
necromancer

@necromancer 네, 아주 좋은 답변입니다. 아파치 라이브러리는 여전히 유용 할 수 있지만, 모두 자바 언어 디자인으로 귀결됩니다. 기본적으로 튜플은 다른 언어에서와 같이 작동하려면 기본 (또는 유사)이어야합니다.
blalasaadri

1
당신이 그것을 눈치 채지 못한 경우, 대답은 매우 유익한 링크를 포함했습니다 : cr.openjdk.java.net/~jrose/values/values-0.html 튜플을 포함한 그러한 프리미티브의 필요성과 전망에 대한 정보.
necromancer

17

전체 예제는 어떤 종류의 페어 구조를 사용하지 않고도 해결할 수 있습니다. 핵심은 열 색인을 false해당 열의 항목 수에 맵핑하는 대신 술어가 전체 열을 점검하여 열 색인을 필터링하는 것입니다 .

이를 수행하는 코드는 다음과 같습니다.

    System.out.println(
        IntStream.range(0, acyclic_graph.length)
            .filter(i -> IntStream.range(0, acyclic_graph.length)
                                  .noneMatch(j -> acyclic_graph[j][i]))
            .boxed()
            .collect(toList()));

결과 [0, 2, 4]는 OP가 요청한 올바른 결과라고 생각합니다.

또한 값을 객체 로 boxed()상자에 int넣는 작업에 유의하십시오 Integer. 이것은 toList()복싱 자체를 수행하는 콜렉터 기능을 작성하는 대신 기존 콜렉터 를 사용할 수있게 합니다.


1
+1 에이스 위로 소매 :) 여전히 일반화되지 않습니까? 이것은 이와 같은 구성표가 작동하지 않는 다른 상황 (예 : 3 개 이하의 열)에 직면 할 것으로 예상되기 때문에 질문의보다 실질적인 측면이었습니다 true. 따라서 나는 당신의 다른 대답을 옳게 받아 들일 것입니다. 대단히 감사합니다 :)
necromancer

이것은 정확하지만 동일한 사용자가 다른 답변을 수락합니다. (위와 다른 곳의 의견 참조)
necromancer

1
@necromancer 맞아요.이 기술은 인덱스를 원하는 경우에 일반적이지 않지만 인덱스를 사용하여 데이터 요소를 검색하거나 계산할 수 없습니다. (적어도 쉽지는 않습니다.) 예를 들어, 네트워크 연결에서 텍스트 줄을 읽고 일부 패턴과 일치하는 N 번째 줄의 줄 번호를 찾는 문제를 고려하십시오. 가장 쉬운 방법은 각 줄을 한 쌍 또는 복합 데이터 구조로 매핑하여 줄 번호를 매기는 것입니다. 새로운 데이터 구조 없이이 작업을 수행하는 해킹, 부작용이있을 수 있습니다.
스튜어트 마크

@StuartMarks, 쌍은 <T, U>입니다. 트리플 <T, U, V>. 등. 귀하의 예는 쌍이 아닌 목록입니다.
Pacerier

7

Vavr (이전의 Javaslang) ( http://www.vavr.io )은 튜플 (크기 8까지)도 제공합니다. 여기의 javadoc은 다음과 같습니다 https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .

이것은 간단한 예입니다.

Tuple2<Integer, String> entry = Tuple.of(1, "A");

Integer key = entry._1;
String value = entry._2;

왜 지금까지 JDK 자체에 간단한 종류의 튜플이 제공되지 않았습니까? 랩퍼 클래스 작성은 매일 비즈니스 인 것 같습니다.


vavr의 일부 버전은 후드 아래에서 교활한 던지기를 사용했습니다. 사용하지 않도록주의하십시오.
Thorbjørn Ravn Andersen 님이

7

Java 9부터는 Map.Entry이전보다 쉬운 인스턴스를 작성할 수 있습니다 .

Entry<Integer, String> pair = Map.entry(1, "a");

Map.entry수정 불가능한 Entry값을 리턴 하고 널을 금지합니다.


6

인덱스에만 관심이 있으므로 튜플에 매핑 할 필요가 없습니다. 배열에서 룩업 요소를 사용하는 필터를 작성하는 것이 어떻습니까?

     int[] value =  ...


IntStream.range(0, value.length)
            .filter(i -> value[i] > 30)  //or whatever filter you want
            .forEach(i -> System.out.println(i));

훌륭하고 실용적인 솔루션 +1 그러나 값을 즉시 생성하는 상황에 일반화되는지 확실하지 않습니다. 나는 내 생각을 간단한 사례를 제시하기 위해 배열로 제시했으며 훌륭한 해결책을 찾았다.
necromancer 2016 년

5

예.

Map.Entry로 사용할 수 있습니다 Pair.

불행히도 람다는 여러 개의 인수를 취할 수 있지만 Java 언어는 단일 값 (객체 또는 기본 유형) 만 리턴 할 수 있기 때문에 Java 8 스트림에는 도움이되지 않습니다. 이것은 스트림이있을 때마다 이전 작업에서 단일 객체가 전달된다는 것을 의미합니다. 여러 반환 값이 지원되고 스트림이 지원되는 경우 스트림에서 수행하는 훨씬 더 중요한 작업을 수행 할 수 있기 때문에 Java에는 부족합니다.

그때까지는 거의 사용되지 않습니다.

편집 2018-02-12 : 프로젝트에서 작업하는 동안 나중에 필요한 스트림에서 식별자를 일찍 갖는 특별한 경우를 처리하는 데 도움이되는 도우미 클래스를 작성했지만 그 사이의 스트림 부분은 알지 못합니다. 독자적으로 릴리스 할 때까지 IdValueTest.java 에서 단위 테스트를 통해 IdValue.java 에서 사용할 수 있습니다.


2

Eclipse Collections에는 Pair기본 / 개체 쌍의 모든 조합 (8 개의 기본 요소 모두)이 있습니다.

Tuples공장의 인스턴스를 만들 수 있습니다 PairPrimitiveTuples공장 원시 / 객체 쌍의 모든 조합을 만들 수 있습니다.

Java 8이 출시되기 전에이를 추가했습니다. 프리미티브 맵에 키 / 값 반복자를 구현하는 데 유용했으며, 모든 프리미티브 / 오브젝트 조합에서도 지원됩니다.

여분의 라이브러리 오버 헤드를 추가하려는 경우 Stuart의 승인 된 솔루션을 사용하여 결과를 기본으로 수집하여 IntList권투를 피할 수 있습니다. 스트림 에서 콜렉션을 작성할 수 있도록 Eclipse Collections 9.0 에 새 메소드를 추가 Int/Long/Double했습니다 Int/Long/Double.

IntList list = IntLists.mutable.withAll(intStream);

참고 : 저는 Eclipse Collections의 커미터입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.