Java SE 8'de Çiftler veya Tuples var mı?


185

Java SE 8'de tembel fonksiyonel işlemler ile oynuyorum ve bir çift / tuple için mapbir indeks istiyorumi(i, value[i]) , sonra filterikinci value[i]elemente dayanıyorum ve nihayet sadece endeksler çıktı istiyorum.

Hala acı çekmeliyim: Java C + + Pair <L, R> eşdeğeri nedir?lambdas ve akarsuların cesur yeni döneminde?

Güncelleme: Aşağıdaki cevaplardan birinde @dkatzel tarafından sunulan temiz bir çözümü olan oldukça basitleştirilmiş bir örnek sundum. Ancak, yok değil genelleme. Bu nedenle, daha genel bir örnek ekleyeyim:

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))
    );
  }

}

Bu , tümü üç sütun için sayılara karşılık gelen yanlış çıktı verir . İhtiyacım olan bu üç sütunun endeksleridir . Doğru çıktı olmalıdır . Bu sonucu nasıl alabilirim?[0, 0, 0]false[0, 2, 4]


2
Zaten AbstractMap.SimpleImmutableEntry<K,V>yıllarca var… Ama yine de, sadece filtreleme ve eşleme için eşleme iyapmak yerine : neden yalnızca eşleme olmadan filtreleme yapmıyorsunuz ? (i, value[i])value[i]ivalue[i]
Holger

@Holger Bir dizinin hangi dizinlerinin bir ölçütle eşleşen değerler içerdiğini bilmem gerekir. iAkışta muhafaza etmeden yapamam . value[i]Kriterlere de ihtiyacım var . Bu yüzden ihtiyacım var(i, value[i])
necromancer

1
@necromancer Doğru, yalnızca diziden, bir dizi, rasgele erişim koleksiyonu veya ucuz bir işlev gibi değere ulaşmak ucuzsa işe yarar. Sorun sanırım basitleştirilmiş bir kullanım durumu sunmak istediniz, ancak aşırı basitleştirildi ve böylece özel bir davaya yenik düştü.
Stuart Marks

1
@necromancer İstediğinizi düşündüğüm soruyu açıklığa kavuşturmak için son paragrafı biraz düzenledim. Doğru mu? Ayrıca, bu yönlendirilmiş (döngüsel olmayan) bir grafikle ilgili bir soru mu? (Çok önemli değil.) Son olarak, istenen çıktı olmalı [0, 2, 4]mı?
Stuart Marks

1
Bunu düzeltmek için doğru çözüm, bir dönüş türü olarak (Object'in özel bir durumu olarak) gelecekteki bir Java sürüm destek tuplesine sahip olmak ve lambda ifadelerinin parametreleri için doğrudan böyle bir demet kullanabilmek olduğuna inanıyorum.
Thorbjørn Ravn Andersen

Yanıtlar:


206

GÜNCELLEME: Bu yanıt asıl soruya yanıt niteliğinde, Java SE 8'in Çiftleri veya Tuples'i var mı? (Ve örtük olarak, değilse, neden olmasın?) OP soruyu daha eksiksiz bir örnekle güncelledi, ancak herhangi bir Çifti yapısı kullanılmadan çözülebileceği anlaşılıyor. [OP'den not: İşte diğer doğru cevap .]


Kısa cevap hayır. Ya kendi başınıza dönmeli ya da onu uygulayan birkaç kütüphaneden birini getirmelisiniz.

Sahip olmak PairJava SE'de sınıfa en az bir kez önerildi ve reddedildi. OpenJDK posta listelerinden birinde bu tartışma konusuna bakın . Ödünleşmeler açık değildir. Bir yandan, diğer kütüphanelerde ve uygulama kodunda birçok Pair uygulaması vardır. Bu bir ihtiyaç olduğunu gösterir ve Java SE'ye böyle bir sınıf eklemek yeniden kullanımı ve paylaşımı artıracaktır. Öte yandan, bir Pair sınıfına sahip olmak, gerekli tür ve soyutlamaları yaratmadan Çiftler ve koleksiyonlardan karmaşık veri yapıları yaratma cazibesine katkıda bulunur. (Bu Kevin Bourillion'un mesajından çıkan mesajın bir açıklamasıdır .)

Herkesin tüm e-posta dizisini okumasını tavsiye ederim. Oldukça içgörüseldir ve hiçbir parlaması yoktur. Oldukça inandırıcı. Başladığında, "Evet, Java SE'de bir Pair sınıfı olmalı" diye düşündüm, ancak iş parçacığının sonuna geldiğinde fikrimi değiştirdim.

Ancak JavaFX'in javafx.util.Pair sınıfına . JavaFX'in API'leri, Java SE API'lerinden ayrı olarak gelişti.

Bağlantılı sorudan görebileceğiniz gibi Java C ++ Çifti eşdeğeri nedir? görünüşte bu kadar basit bir API'yi çevreleyen oldukça geniş bir tasarım alanı var. Nesneler değişmez mi olmalı? Serileştirilebilir olmalı mı? Karşılaştırılabilir mi? Sınıf final mi olmalı yoksa değil mi? İki unsur sipariş edilmeli mi? Bir arayüz mü yoksa sınıf mı olmalı? Neden çiftler halinde durmalıyız? Neden üçlü, dörtlü veya N-tuples olmasın?

Ve elbette elementler için kaçınılmaz adlandırma var:

  • (a, b)
  • (birinci, ikinci)
  • (sol sağ)
  • (araba, cdr)
  • (foo, bar)
  • vb.

Bahsedilen büyük bir mesele, Çiftlerin ilkellerle olan ilişkisidir. (int x, int y)2B alanda bir noktayı temsil eden bir referans noktanız varsa, bunu iki 32 bit sözcük yerine üç nesnePair<Integer, Integer> tüketir . Ayrıca, bu nesneler öbek üzerinde bulunmalı ve GC yüküne neden olacaktır.

Akımlar gibi, Çiftler için ilkel uzmanlaşmaların olması gerektiği açıktır. Görmek ister miyiz:

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

Bir IntIntPairyığın bile hala bir nesne gerektirir.

Bunlar elbette, java.util.functionJava SE 8'deki paketteki fonksiyonel arayüzlerin çoğalmasını anımsatıyor. Şişirilmiş bir API istemiyorsanız, hangilerini dışarıda bırakacaksınız? Bunun yeterli olmadığını ve uzmanlık alanlarının,Boolean da ekleneceğini .

Benim düşüncem, Java uzun bir süre önce bir Pair sınıfı ekleseydi, basit ya da basit olurdu ve şu anda öngördüğümüz birçok kullanım örneğini tatmin etmeyecekti. Çifti JDK 1.0 zaman dilimine eklenmiş olsaydı, muhtemelen değişebilir olurdu! (Bkz. Java.util.Date.) İnsanlar bundan memnun olur muydu? Tahminimce, Java'da bir Pair sınıfı olsaydı, bu tür-gerçekten-kullanışlı olmazdı ve herkes hala ihtiyaçlarını karşılamak için kendi başlarına dönecek, dış kütüphanelerde çeşitli Pair ve Tuple uygulamaları olacaktı, ve insanlar hala Java'nın Pair sınıfını nasıl düzelteceklerini tartışıyor / tartışıyorlardı. Başka bir deyişle, bugün bulunduğumuz yerde.

Bu arada, JVM'de (ve nihayetinde Java dilinde) değer türleri için daha iyi destek olan temel sorunu ele almak için bazı çalışmalar devam etmektedir . Bu Değerlerin Durumu belgesine bakın. Bu ön, spekülatif çalışmadır ve sadece JVM perspektifinden meseleleri kapsar, ancak zaten arkasında oldukça fazla düşünce vardır. Elbette bunun Java 9'a gireceği veya herhangi bir yere gireceği konusunda hiçbir garanti yoktur, ancak bu konuyla ilgili mevcut düşünme yönünü gösterir.


3
@necromancer İlkel içeren fabrika yöntemleri işe yaramıyor Pair<T,U>. Çünkü jenerikler referans tipinde olmalıdır. Herhangi bir temel öğe saklandığında kutuya alınacaktır. İlkelleri depolamak için gerçekten farklı bir sınıfa ihtiyacınız var.
Stuart Marks

3
@necromancer Ve evet geriye dönük olarak kutulu ilkel kurucuların halka açık olmaması ve valueOfkutulu bir örnek almanın tek yolu olması gerekirdi. Ama bunlar Java 1.0'dan beri oradalar ve muhtemelen bu noktada değişmeye çalışmak değmez.
Stuart Marks

3
Açıkçası, arka planda şeffaf bir şekilde gerekli uzmanlık sınıflarını (optimize edilmiş depolama ile) yaratan fabrika yöntemine sahip sadece bir kamu Pairveya Tuplesınıf olmalıdır . Sonunda, lambdalar tam olarak bunu yaparlar: gelişigüzel türde rastgele sayıda değişken yakalayabilirler. Ve şimdi bir invokedynamictalimatla tetiklenen çalışma zamanında uygun tuple sınıfını oluşturmaya izin veren bir dil desteğini
Holger

3
@Holger Birisi mevcut JVM'ye değer türlerini uyarlıyorsa, ancak Değer Türleri önerisi (şimdi "Project Valhalla" ) çok daha radikalse böyle bir şey işe yarayabilir . Özellikle, değer türlerinin öbek tahsis edilmesine gerek yoktur. Ayrıca, günümüzdeki nesnelerden farklı olarak ve günümüzdeki ilkel öğeler gibi, değerlerin de kimliği olmayacaktır.
Stuart Marks

2
@Stuart İşaretleri: Açıkladığım tip böyle bir değer türü için “kutulu” tip olabileceğinden bu müdahale etmez. invokedynamicLambda oluşumuna benzer bir fabrika ile bu tür daha sonra güçlendirme sorun olmaz. Bu arada, lambdaların da kimliği yok. Açıkça belirtildiği gibi, bugün algılayabileceğiniz kimlik, mevcut uygulamanın bir eseridir.
Holger

46

Bu yerleşik sınıflara bir göz atabilirsiniz:


3
Çiftler için yerleşik işlevsellik kadar doğru cevap budur. Bağlanan ve nesnelerin (veya bağlantı verdikleri nesnelerin) alanlarının değişmeyeceğini değil , SimpleImmutableEntryyalnızca saklanan referansların Entrydeğişmeyeceğini garanti ettiğini unutmayın . keyvalue
Luke Hutchison

22

Ne yazık ki, Java 8 çiftler veya tuples getirmedi. Her zaman org.apache.commons.lang3.tuple kullanabilirsiniz (ki şahsen Java 8 ile birlikte kullanıyorum) veya kendi paketleyicilerinizi oluşturabilirsiniz. Veya Google Haritalar'ı kullanın. Ya da bağlandığınız bu sorunun kabul edilen cevabında açıklandığı gibi bunun gibi şeyler .


GÜNCELLEME: JDK 14, kayıtları bir önizleme özelliği olarak sunar. Bunlar tuples değil, aynı sorunların çoğunu kurtarmak için kullanılabilir. Yukarıdaki özel örneğinizde, bu şöyle görünebilir:

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))
        );
    }
}

Bayrağını kullanarak JDK 14 (yazma sırasında, bu bir erken erişim derlemesi) ile derlendiğinde ve çalıştırıldığında --enable-preview, aşağıdaki sonucu alırsınız:

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

Aslında @StuartMarks'ın cevaplarından biri bunu tuplesiz çözmeme izin verdi, ancak genelleme gibi görünmediği için muhtemelen sonunda buna ihtiyacım olacak.
Necromancer

@necromancer Evet, çok iyi bir cevap. Apache kütüphanesi bazen kullanışlı olabilir, ancak hepsi Javas dil tasarımına gelir. Temel olarak, tupller diğer dillerde olduğu gibi çalışmak için ilkel (veya benzeri) olmalıdır.
blalasaadri

1
Bunu farketmediyseniz, cevap şu son derece bilgilendirici bağlantıyı içeriyordu: cr.openjdk.java.net/~jrose/values/values-0.html tuples dahil olmak üzere bu ilkellerin ihtiyacı ve beklentileri hakkında.
necromancer

17

Görünüşe göre tam örnek, herhangi bir Pair yapısı kullanılmadan çözülebilir. Anahtar, sütun dizinlerini falseo sütundaki giriş sayısı ile eşlemek yerine, tüm sütunu kontrol ederek, sütun dizinlerini filtrelemektir .

Bunu yapan kod burada:

    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()));

Bu sonuç çıktı [0, 2, 4]hangi OP tarafından istenen doğru sonuç olduğunu düşünüyorum.

Ayrıca değerleri nesnelere boxed()kutulama işlemini de not edin . Bu , boksu yapan toplayıcı işlevlerini yazmak yerine önceden var olan toplayıcıyı kullanabilmenizi sağlar .intIntegertoList()


1
+1 ace kolunuzu yukarı kaldırın :) Bu hala genelleme yapmıyor, değil mi? Sorunun en önemli yönü buydu, çünkü bunun gibi bir şemanın işe yaramayacağı diğer durumlarla (örneğin en fazla 3 değer içeren sütunlar true) karşılaşmayı umuyorum . Buna göre, diğer cevabınızı doğru olarak kabul edeceğim, ama aynı zamanda bunu da işaret edeceğim! Çok teşekkürler :)
necromancer

Bu doğrudur ancak aynı kullanıcı tarafından diğer yanıtı kabul etmektedir. (yukarıdaki ve başka yerlerdeki yorumlara bakın.)
büyücü

1
@necromancer Doğru, bu teknik dizini istediğiniz durumlarda tam olarak genel değildir, ancak veri öğesi dizin kullanılarak alınamaz veya hesaplanamaz. (En azından kolay değil.) Örneğin, bir ağ bağlantısından metin satırlarını okurken ve N. Satırın bazı kalıplarla eşleşen satır numarasını bulmak istediğinizde bir sorun düşünün. En kolay yol, satırları numaralandırmak için her satırı bir Pair veya bir kompozit veri yapısına eşlemektir. Muhtemelen yeni bir veri yapısı olmadan bunu yapmanın hileli, yan etkili bir yolu var.
Stuart Marks

@StuartMarks, Bir çift <T, U>. üçlü bir <T, U, V>. Örneğin, bir çift değil bir listedir.
Pacerier

7

Vavr (eski adıyla Javaslang) ( http://www.vavr.io ) da tuples (8 büyüklüğünde) sağlar. İşte javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .

Bu basit bir örnek:

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

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

JDK'nın neden şimdiye kadar basit bir tür tuple ile gelmediği benim için bir gizem. Paketleyici sınıfları yazmak her gün yapılan bir iş gibi görünüyor.


Vavr'ın bazı versiyonları kaputun altında sinsi atışlar kullandı. Bunları kullanmamaya dikkat edin.
Thorbjørn Ravn Andersen

7

Java 9'dan bu yana, Map.Entryöncekinden daha kolay örnekler oluşturabilirsiniz :

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

Map.entrydeğiştirilemez döndürür Entryve null'ları yasaklar.


6

Yalnızca dizinleri önemsediğiniz için, gruplarla eşlemenize gerek yoktur. Neden sadece dizinizdeki arama öğelerini kullanan bir filtre yazmıyorsunuz?

     int[] value =  ...


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

Mükemmel, pratik çözüm için +1. Ancak, değerleri anında oluşturduğum durumumda genelleme yapıp yapmadığından emin değilim. Sorumu düşünmek için basit bir durum sunmak için bir dizi olarak sordum ve mükemmel bir çözüm buldunuz.
büyücü

5

Evet.

Map.Entryolarak kullanılabilir Pair.

Ne yazık ki, Java 8 akışlarına yardımcı olmuyor çünkü lambdas birden fazla argüman alabilse de, Java dili sadece tek bir değer (nesne veya ilkel tip) döndürmeye izin veriyor. Bu, bir akışınız olduğunda, önceki işlemden tek bir nesne geçirmeniz anlamına gelir. Bu, Java dilinde bir eksikliktir, çünkü birden fazla dönüş değeri destekleniyorsa ve akışlar bunları destekliyorsa, akışlar tarafından yapılan çok daha güzel önemsiz olmayan görevler alabiliriz.

O zamana kadar, sadece çok az kullanım var.

EDIT 2018-02-12: Bir proje üzerinde çalışırken, daha sonra ihtiyacınız olan akışta daha önce bir tanımlayıcıya sahip olmanın özel durumunun ele alınmasına yardımcı olan bir yardımcı sınıf yazdım, ancak aradaki akış kısmı bunu bilmiyor. Tek başına serbest bırakana kadar IdValue.java'da IdValueTest.java'da birim testi ile kullanılabilir.


2

Eclipse Koleksiyonlarında Pairve ilkel / nesne Çiftlerinin tüm kombinasyonları vardır (sekiz ilkel için de).

TuplesFabrika örneklerini oluşturabilir PairvePrimitiveTuples fabrika ilkel / nesne çiftleri tüm kombinasyonlarını oluşturmak için kullanılabilir.

Bunları Java 8 yayınlanmadan önce ekledik. Tüm ilkel / nesne kombinasyonlarında da desteklediğimiz ilkel haritalarımız için anahtar / değer Yineleyicileri uygulamakta faydalı oldular.

Ekstra kütüphane yükünü eklemek istiyorsanız, Stuart'ın kabul edilen çözümünü kullanabilir ve IntListbokstan kaçınmak için sonuçları ilkel olarak toplayabilirsiniz . AkışlardanInt/Long/Double koleksiyonların oluşturulmasına izin vermek için Eclipse Collections 9.0'a yeni yöntemler ekledik Int/Long/Double.

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

Not: Eclipse Collections için bir komisyoncuyum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.