Java SE 8 có Cặp hoặc Bộ dữ liệu không?


185

Tôi đang chơi xung quanh với các hoạt động chức năng lười biếng trong Java SE 8 và tôi muốn maplập chỉ mục icho một cặp / tuple (i, value[i]), sau đó filterdựa trên value[i]phần tử thứ hai và cuối cùng chỉ xuất ra các chỉ mục.

Tôi vẫn phải chịu điều này: Tương đương với cặp C ++ <L, R> trong Java là gì? trong kỷ nguyên mới táo bạo của lambdas và suối?

Cập nhật: Tôi đã trình bày một ví dụ khá đơn giản, trong đó có một giải pháp gọn gàng được cung cấp bởi @dkatzel trong một trong những câu trả lời dưới đây. Tuy nhiên, nó không khái quát. Do đó, hãy để tôi thêm một ví dụ tổng quát hơn:

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

}

Điều này cung cấp đầu ra không chính xác[0, 0, 0] tương ứng với số đếm cho ba cột là tất cả false. Những gì tôi cần là các chỉ số của ba cột. Đầu ra chính xác phải là [0, 2, 4]. Làm thế nào tôi có thể nhận được kết quả này?


2
Có đã AbstractMap.SimpleImmutableEntry<K,V>nhiều năm ... Nhưng dù sao, thay cho bản đồ iđể (i, value[i])chỉ cho lọc theo value[i]và lập bản đồ trở lại i: tại sao không chỉ lọc theo value[i]ở nơi đầu tiên, mà không có bản đồ?
Holger

@Holger Tôi cần biết chỉ số nào của mảng chứa các giá trị khớp với tiêu chí. Tôi không thể làm điều đó mà không bảo tồn itrong dòng. Tôi cũng cần value[i]cho các tiêu chí. Đó là lý do tại sao tôi cần(i, value[i])
necromancer

1
@necromancer Đúng, nó chỉ hoạt động nếu giá rẻ để lấy giá trị từ chỉ mục, chẳng hạn như một mảng, bộ sưu tập truy cập ngẫu nhiên hoặc một hàm rẻ tiền. Tôi đoán vấn đề là bạn muốn trình bày một trường hợp sử dụng đơn giản hóa, nhưng nó đã được áp dụng quá mức và do đó bị khuất phục trong trường hợp đặc biệt.
Stuart Marks

1
@necromancer Tôi đã chỉnh sửa đoạn cuối một chút để làm rõ câu hỏi tôi nghĩ bạn đang hỏi. Đúng không? Ngoài ra, đây có phải là một câu hỏi về đồ thị có hướng (không phải chu kỳ) không? (Không phải là nó quan trọng lắm.) Cuối cùng, đầu ra mong muốn có nên [0, 2, 4]không?
Stuart Marks

1
Tôi tin rằng giải pháp phù hợp để khắc phục điều này là có một bộ hỗ trợ phát hành Java trong tương lai dưới dạng kiểu trả về (như một trường hợp đặc biệt của Object) và các biểu thức lambda có thể sử dụng trực tiếp một tuple như vậy cho các tham số của nó.
Thorbjørn Ravn Andersen

Câu trả lời:


206

CẬP NHẬT: Câu trả lời này là để trả lời cho câu hỏi ban đầu, Java SE 8 có Cặp hay Tuples không? (Và ngầm, nếu không, tại sao không?) OP đã cập nhật câu hỏi với một ví dụ đầy đủ hơn, nhưng có vẻ như nó có thể được giải quyết mà không cần sử dụng bất kỳ loại cấu trúc Cặp nào. [Lưu ý từ OP: đây là câu trả lời đúng khác .]


Câu trả lời ngắn gọn là không. Bạn có thể tự cuộn hoặc mang vào một trong nhiều thư viện thực hiện nó.

Có một Pairlớp trong Java SE đã được đề xuất và từ chối ít nhất một lần. Xem chủ đề thảo luận này trên một trong các danh sách gửi thư OpenJDK. Sự đánh đổi là không rõ ràng. Một mặt, có nhiều triển khai Cặp trong các thư viện khác và trong mã ứng dụng. Điều đó thể hiện nhu cầu và việc thêm một lớp như vậy vào Java SE sẽ tăng khả năng sử dụng lại và chia sẻ. Mặt khác, việc có một lớp Cặp làm tăng thêm sự cám dỗ của việc tạo các cấu trúc dữ liệu phức tạp từ các Cặp và bộ sưu tập mà không tạo ra các kiểu và trừu tượng cần thiết. (Đó là một cách diễn đạt thông điệp của Kevin Bourillion từ chủ đề đó.)

Tôi khuyên mọi người nên đọc toàn bộ chủ đề email. Đó là sâu sắc đáng chú ý và không có flamage. Nó khá thuyết phục. Khi nó bắt đầu, tôi đã nghĩ, "Vâng, cần phải có một lớp Pair trong Java SE" nhưng đến khi chủ đề kết thúc, tôi đã thay đổi quyết định.

Tuy nhiên, xin lưu ý rằng JavaFX có lớp javafx.util.Pair . API của JavaFX được phát triển riêng biệt với API Java SE.

Như người ta có thể thấy từ câu hỏi được liên kết Tương đương với cặp C ++ trong Java là gì? có một không gian thiết kế khá lớn xung quanh một API rõ ràng đơn giản như vậy. Các đối tượng có nên bất biến? Họ có nên được tuần tự hóa? Họ có nên được so sánh? Lớp học có nên cuối cùng hay không? Hai yếu tố nên được đặt hàng? Nó nên là một giao diện hay một lớp học? Tại sao dừng lại ở cặp? Tại sao không phải là ba, bốn hoặc N-tuples?

Và tất nhiên, không thể tránh khỏi việc đặt tên cho các yếu tố:

  • (a, b)
  • (thứ nhất, thứ hai)
  • (trái phải)
  • (xe hơi, cdr)
  • (foo, thanh)
  • Vân vân.

Một vấn đề lớn hầu như không được đề cập là mối quan hệ của Cặp với người nguyên thủy. Nếu bạn có một (int x, int y)mốc thời gian biểu thị một điểm trong không gian 2D, thì biểu thị điều này là Pair<Integer, Integer>tiêu thụ ba đối tượng thay vì hai từ 32 bit. Hơn nữa, các đối tượng này phải nằm trên đống và sẽ phải chịu chi phí hoạt động.

Có vẻ như rõ ràng rằng, giống như Streams, điều cần thiết là phải có các chuyên môn nguyên thủy cho các Cặp. Chúng tôi có muốn xem:

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

Ngay cả một IntIntPairvẫn sẽ yêu cầu một đối tượng trên đống.

Tất nhiên, những điều này gợi nhớ đến sự phổ biến của các giao diện chức năng trong java.util.functiongói trong Java SE 8. Nếu bạn không muốn một API cồng kềnh, bạn sẽ bỏ qua cái nào? Bạn cũng có thể lập luận rằng điều này là không đủ, và các chuyên ngành cho, Booleancũng nên được thêm vào.

Cảm giác của tôi là nếu Java đã thêm một lớp Pair từ lâu, thì nó sẽ đơn giản hoặc thậm chí đơn giản và nó sẽ không thỏa mãn nhiều trường hợp sử dụng mà chúng ta đang hình dung bây giờ. Hãy xem xét rằng nếu Pair đã được thêm vào trong khung thời gian JDK 1.0, thì có lẽ nó đã có thể thay đổi! (Nhìn vào java.util.Date.) Mọi người có hài lòng với điều đó không? Tôi đoán là nếu có một lớp Pair trong Java, thì nó sẽ là một loại không thực sự hữu ích và mọi người vẫn sẽ tự lăn lộn để đáp ứng nhu cầu của họ, sẽ có nhiều triển khai Pair và Tuple khác nhau trong các thư viện bên ngoài, và mọi người vẫn sẽ tranh cãi / thảo luận về cách sửa lớp Cặp của Java. Nói cách khác, loại ở cùng một nơi chúng ta ngày nay.

Trong khi đó, một số công việc đang diễn ra để giải quyết vấn đề cơ bản, đó là sự hỗ trợ tốt hơn trong JVM (và cuối cùng là ngôn ngữ Java) cho các loại giá trị . Xem tài liệu Trạng thái giá trị này . Đây là công việc sơ bộ, đầu cơ và nó chỉ bao gồm các vấn đề từ phối cảnh JVM, nhưng nó đã có một lượng lớn suy nghĩ đằng sau nó. Tất nhiên, không có gì đảm bảo rằng điều này sẽ đi vào Java 9 hoặc đã từng xuất hiện ở bất cứ đâu, nhưng nó cho thấy hướng suy nghĩ hiện tại về chủ đề này.


3
@necromancer Phương pháp nhà máy với người nguyên thủy không giúp đỡ Pair<T,U>. Vì thuốc generic phải thuộc loại tham chiếu. Bất kỳ nguyên thủy nào sẽ được đóng hộp khi chúng được lưu trữ. Để lưu trữ nguyên thủy bạn thực sự cần một lớp khác.
Stuart Marks

3
@necromancer Và vâng, khi nhìn lại các hàm tạo nguyên thủy được đóng hộp không nên công khai, và valueOfnên là cách duy nhất để có được một thể hiện được đóng hộp. Nhưng những thứ đã có trong đó kể từ Java 1.0 và có lẽ không đáng để thử thay đổi vào thời điểm này.
Stuart Marks

3
Rõ ràng, chỉ nên có một công chúng Pairhoặc một Tuplelớp với một phương thức xuất xưởng tạo ra các lớp chuyên môn hóa cần thiết (với lưu trữ được tối ưu hóa) trong suốt trong nền. Cuối cùng, lambdas thực hiện chính xác điều đó: họ có thể nắm bắt một số lượng biến tùy ý của loại tùy ý. Và bây giờ hình ảnh một hỗ trợ ngôn ngữ cho phép tạo lớp tuple thích hợp trong thời gian chạy được kích hoạt bởi một invokedynamiclệnh Hướng dẫn
Holger

3
@Holger Một cái gì đó tương tự có thể hoạt động nếu một người đang trang bị thêm các loại giá trị cho JVM hiện tại, nhưng đề xuất Loại giá trị (bây giờ là "Dự án Valhalla" ) triệt để hơn nhiều. Cụ thể, các loại giá trị của nó không nhất thiết phải được phân bổ heap. Ngoài ra, không giống như các đối tượng ngày nay và giống như các nguyên thủy ngày nay, các giá trị sẽ không có bản sắc.
Stuart Marks

2
@Stuart Marks: Điều đó sẽ không can thiệp vì loại tôi đã mô tả có thể là loại hình hộp được đóng hộp của kiểu Cameron cho loại giá trị như vậy. Với một invokedynamicnhà máy dựa trên tương tự như việc tạo ra lambda, việc trang bị thêm sau này sẽ không có vấn đề gì. Nhân tiện, lambdas cũng không có danh tính. Như đã nêu rõ ràng, danh tính bạn có thể nhận thấy ngày hôm nay là một tạo tác của việc thực hiện hiện tại.
Holger

46

Bạn có thể xem qua các lớp dựng sẵn này:


3
Đây là câu trả lời chính xác, theo như chức năng tích hợp cho các cặp. Lưu ý rằng SimpleImmutableEntrychỉ đảm bảo rằng các tham chiếu được lưu trữ trong Entrykhông thay đổi, không phải các trường của các đối tượng được liên kết keyvaluecác đối tượng (hoặc các đối tượng mà chúng liên kết đến) không thay đổi.
Luke Hutchison

22

Đáng buồn thay, Java 8 đã không giới thiệu các cặp hoặc bộ dữ liệu. Tất nhiên, bạn luôn có thể sử dụng org.apache.commons.lang3.tuple (cá nhân tôi sử dụng kết hợp với Java 8) hoặc bạn có thể tạo các trình bao bọc của riêng mình. Hoặc sử dụng Bản đồ. Hoặc những thứ như vậy, như được giải thích trong câu trả lời được chấp nhận cho câu hỏi mà bạn liên kết đến.


CẬP NHẬT: JDK 14 đang giới thiệu các bản ghi như một tính năng xem trước. Đây không phải là bộ dữ liệu, nhưng có thể được sử dụng để lưu nhiều vấn đề tương tự. Trong ví dụ cụ thể của bạn ở trên, có thể trông giống như thế này:

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

Khi được biên dịch và chạy với JDK 14 (tại thời điểm viết bài, đây là bản dựng truy cập sớm) bằng cách sử dụng --enable-previewcờ, bạn sẽ nhận được kết quả sau:

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

Trên thực tế, một trong những câu trả lời của @StuartMarks đã cho phép tôi giải quyết nó mà không cần bộ dữ liệu, nhưng vì nó dường như không khái quát hóa nên cuối cùng tôi có thể sẽ cần nó.
Necromancer

@necromancer Vâng, đó là một câu trả lời rất hay. Thư viện apache vẫn có thể có ích đôi khi nhưng tất cả đều nhờ vào thiết kế ngôn ngữ Javas. Về cơ bản, các bộ dữ liệu sẽ phải là nguyên thủy (hoặc tương tự) để hoạt động như chúng làm trong các ngôn ngữ khác.
blalasaadri

1
Trong trường hợp bạn không chú ý đến nó, câu trả lời bao gồm liên kết cực kỳ nhiều thông tin này: cr.openjdk.java.net/~jrose/values/values-0.html về sự cần thiết và triển vọng cho các nguyên thủy như vậy bao gồm các bộ dữ liệu.
Necromancer

17

Có vẻ như ví dụ đầy đủ có thể được giải quyết mà không cần sử dụng bất kỳ loại cấu trúc Cặp nào. Điều quan trọng là lọc các chỉ mục cột, với vị từ kiểm tra toàn bộ cột, thay vì ánh xạ các chỉ mục cột thành số lượng falsemục trong cột đó.

Mã thực hiện điều này là ở đây:

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

Điều này dẫn đến kết quả đầu ra [0, 2, 4]mà tôi nghĩ là kết quả chính xác theo yêu cầu của OP.

Cũng lưu ý các boxed()hoạt động mà hộp các intgiá trị thành Integercác đối tượng. Điều này cho phép một người sử dụng toList()bộ sưu tập có sẵn thay vì phải viết ra các chức năng của bộ sưu tập tự đóng hộp.


1
+1 ace lên tay áo của bạn :) Điều này vẫn không khái quát, phải không? Đó là khía cạnh quan trọng hơn của câu hỏi bởi vì tôi hy vọng sẽ đối mặt với các tình huống khác trong đó một lược đồ như thế này sẽ không hoạt động (ví dụ: các cột có không quá 3 giá trị true). Theo đó, tôi sẽ chấp nhận câu trả lời khác của bạn là chính xác, nhưng cũng chỉ ra câu trả lời này! Cảm ơn rất nhiều :)
necromancer

Điều này đúng nhưng chấp nhận câu trả lời khác bởi cùng một người dùng. (xem bình luận ở trên và ở nơi khác.)
necromancer

1
@necromancer Đúng, kỹ thuật này không hoàn toàn chung trong các trường hợp bạn muốn chỉ mục, nhưng phần tử dữ liệu không thể được truy xuất hoặc tính toán bằng chỉ mục. (Ít nhất là không dễ dàng.) Ví dụ, hãy xem xét một vấn đề trong đó bạn đang đọc các dòng văn bản từ kết nối mạng và bạn muốn tìm số dòng của dòng N phù hợp với một số mẫu. Cách dễ nhất là ánh xạ mỗi dòng thành một cặp hoặc một số cấu trúc dữ liệu tổng hợp để đánh số các dòng. Có lẽ có một cách hacky, tác dụng phụ để làm điều này mà không có cấu trúc dữ liệu mới.
Stuart Marks

@StuartMarks, Một cặp là <T, U>. một bộ ba <T, U, V>. vv Ví dụ của bạn là một danh sách, không phải là một cặp.
Pacerier

7

Vavr (trước đây gọi là Javaslang) ( http://www.vavr.io ) cũng cung cấp các bộ dữ liệu (kích thước cho đến 8). Đây là javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .

Đây là một ví dụ đơn giản:

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

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

Tại sao bản thân JDK không đi kèm với một loại tuples đơn giản cho đến bây giờ là một bí ẩn đối với tôi. Viết các lớp bao bọc dường như là một công việc hàng ngày.


Một số phiên bản của vavr đã sử dụng những cú ném lén lút dưới mui xe. Hãy cẩn thận không sử dụng những người.
Thorbjørn Ravn Andersen

7

Kể từ Java 9, bạn có thể tạo các cá thể Map.Entrydễ dàng hơn trước:

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

Map.entrytrả về một không thể thay đổi Entryvà cấm null.


6

Vì bạn chỉ quan tâm đến các chỉ mục, bạn không cần phải ánh xạ tới các bộ dữ liệu. Tại sao không chỉ viết một bộ lọc sử dụng các phần tử tra cứu trong mảng của bạn?

     int[] value =  ...


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

+1 cho giải pháp tuyệt vời, thiết thực. Tuy nhiên, tôi không chắc liệu nó có khái quát đến tình huống của tôi hay không, nơi tôi đang tạo ra các giá trị một cách nhanh chóng. Tôi đặt ra câu hỏi của mình như một mảng để đưa ra một trường hợp đơn giản để suy nghĩ và bạn đã đưa ra một giải pháp tuyệt vời.
Necromancer

5

Đúng.

Map.Entrycó thể được sử dụng như một Pair.

Thật không may, nó không giúp ích gì với các luồng Java 8 vì vấn đề là mặc dù lambdas có thể nhận nhiều đối số, ngôn ngữ Java chỉ cho phép trả về một giá trị duy nhất (loại đối tượng hoặc kiểu nguyên thủy). Điều này ngụ ý rằng bất cứ khi nào bạn có một luồng, bạn sẽ bị truyền một đối tượng từ hoạt động trước đó. Đây là một ngôn ngữ Java thiếu, vì nếu nhiều giá trị trả về được hỗ trợ VÀ các luồng hỗ trợ chúng, chúng ta có thể có các tác vụ không tầm thường đẹp hơn được thực hiện bởi các luồng.

Cho đến lúc đó, chỉ có ít sử dụng.

EDIT 2018-02-12: Trong khi thực hiện dự án, tôi đã viết một lớp trợ giúp giúp xử lý trường hợp đặc biệt có một mã định danh sớm hơn trong luồng bạn cần sau đó nhưng phần luồng ở giữa không biết về nó. Cho đến khi tôi tự mình phát hành nó, nó có sẵn tại IdValue.java với một bài kiểm tra đơn vị tại IdValueTest.java


2

Bộ sưu tập Eclipse có Pairvà tất cả các kết hợp của cặp nguyên thủy / đối tượng (cho tất cả tám nguyên thủy).

Nhà Tuplesmáy có thể tạo các thể hiện PairPrimitiveTuplesnhà máy có thể được sử dụng để tạo tất cả các kết hợp của các cặp nguyên thủy / đối tượng.

Chúng tôi đã thêm chúng trước khi Java 8 được phát hành. Chúng rất hữu ích để triển khai các Iterators khóa / giá trị cho các bản đồ nguyên thủy của chúng tôi, chúng tôi cũng hỗ trợ trong tất cả các kết hợp đối tượng nguyên thủy / đối tượng.

Nếu bạn sẵn sàng thêm chi phí thư viện bổ sung, bạn có thể sử dụng giải pháp được chấp nhận của Stuart và thu thập kết quả vào thời nguyên thủy IntListđể tránh quyền anh. Chúng tôi đã thêm các phương thức mới trong Bộ sưu tập Eclipse 9.0 để cho phép các Int/Long/Doublebộ sưu tập được tạo từ Int/Long/DoubleLuồng.

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

Lưu ý: Tôi là người đi làm cho Bộ sưu tập Eclipse.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.