Dlaczego nie mogę zamapować liczb całkowitych na ciągi podczas przesyłania strumieniowego z tablicy?

94

Ten kod działa (pobrany w Javadoc):

List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
String commaSeparatedNumbers = numbers.stream()
    .map(i -> i.toString())
    .collect(Collectors.joining(", "));

Tego nie można skompilować:

int[] numbers = {1, 2, 3, 4};
String commaSeparatedNumbers = Arrays.stream(numbers)
    .map((Integer i) -> i.toString())
    .collect(Collectors.joining(", "));

IDEA mówi mi, że mam „niezgodny ciąg typu zwracanego w wyrażeniu lambda”.

Czemu ? Jak to naprawić?

Denys Séguret
źródło

Odpowiedzi:

121

Arrays.stream(int[])tworzy IntStream, a nie Stream<Integer>. Więc musisz wywołać mapToObjzamiast tylko map, podczas mapowania an intdo obiektu.

To powinno działać zgodnie z oczekiwaniami:

String commaSeparatedNumbers = Arrays.stream(numbers)
    .mapToObj(i -> ((Integer) i).toString()) //i is an int, not an Integer
    .collect(Collectors.joining(", "));

które możesz też napisać:

String commaSeparatedNumbers = Arrays.stream(numbers)
    .mapToObj(Integer::toString)
    .collect(Collectors.joining(", "));
asylias
źródło
3
Jaka jest różnica między IntStreami Stream<Integer>?
Florian Margaine
8
@FlorianMargaine An IntStreamto specjalizacja strumienia dla intwartości pierwotnych . A Stream<Integer>to po prostu strumień zawierający Integerobiekty.
Alexis C.
2
@FlorianMargaine IntStreamto strumień lub prymitywy (ints), podczas gdy Steram<Integer>jest to strumień obiektów. Strumienie pierwotne mają wyspecjalizowane metody ze względu na wydajność.
asylias
7
IntStream.mapToObjoczekuje IntFunctionfunkcji, która zużywa intwartość, dlatego  .mapToObj((Integer i) -> i.toString())nie działa. I tak nie byłby zalecany, ponieważ zawiera niepotrzebną konwersję z intna Integer. W przeciwieństwie do tego .mapToObj(Integer::toString)działa ładnie, ponieważ wywoła staticmetodę Integer.toString(int). Zauważ, że różni się to od wywołania .map(Integer::toString)a,  Stream<Integer>ponieważ ten ostatni nie skompiluje się, ponieważ jest niejednoznaczny.
Holger
1
@cic: nie, dzwoniąc .map(Integer::toString)na zasadzie Stream<Integer>jest naprawdę dwuznaczne jak zarówno .map(i->i.toString())i .map(i->Integer.toString(i))są ważne. Ale można to łatwo rozwiązać za pomocą .map(Object::toString).
Holger
19

Arrays.stream(numbers)tworzy IntStreampod maską, a operacja na mapie IntStreamwymaga IntUnaryOperator(tj. funkcji int -> int). Funkcja mapowania, którą chcesz zastosować, nie jest zgodna z tym kontraktem, a zatem błąd kompilacji.

Musisz zadzwonić boxed()wcześniej, aby uzyskać Stream<Integer>(to jest to, co Arrays.asList(...).stream()zwraca). Następnie po prostu zadzwoń, maptak jak w pierwszym fragmencie.

Zauważ, że jeśli potrzebujesz, boxed()a następnie mapprawdopodobnie chcesz użyć mapToObjbezpośrednio.

Zaletą jest to, że mapToObjnie wymaga pakowania każdej intwartości do Integerobiektu; oczywiście w zależności od zastosowanej funkcji mapowania; więc wybrałbym tę opcję, która również jest krótsza do napisania.

Alexis C.
źródło
5

Możesz utworzyć strumień całkowity za pomocą Arrays.stream (int []), możesz wywołać mapToObjtakie jak mapToObj(Integer::toString).

String csn = Arrays.stream(numbers) // your numbers array
.mapToObj(Integer::toString)
.collect(Collectors.joining(", "));

Mam nadzieję że to pomoże..

codebot
źródło
2

Bez boksu, AFAIK i bez eksplozji małych sznurków dodanych do stosu:

public static void main(String[] args) {
    IntStream stream = IntStream.of(1, 2, 3, 4, 5, 6);
    String s = stream.collect(StringBuilder::new, (builder, n) -> builder.append(',').append(n), (x, y) -> x.append(',').append(y)).substring(1);
    System.out.println(s);
}
AbuNassar
źródło
1

Jeśli celem tego przykładu i pytania jest ustalenie, jak odwzorować ciągi znaków na strumień liczb całkowitych (na przykład użycie strumienia liczb całkowitych w celu uzyskania dostępu do indeksu w tablicy ciągów), możesz również użyć boksu, a następnie rzutować int (co umożliwiłoby wtedy dostęp do indeksu tablicy).

int[] numbers = {0, 1, 2, 3}; 
String commaSeparatedNumbers = Arrays.stream(numbers)
    .boxed()
    .map((Integer i) -> Integer.toString((int)i))
    .collect(Collectors.joining(", "));

Wywołanie .boxed () konwertuje Twój IntStream (strumień pierwotnych liczb całkowitych) na Stream (strumień obiektów - mianowicie obiekty typu Integer), który następnie zaakceptuje zwrot obiektu (w tym przypadku obiektu String) z twoja lambda. Tutaj jest to po prostu ciąg znaków reprezentujący liczbę do celów demonstracyjnych, ale równie łatwo (i bardziej praktycznie) mógłby to być dowolny obiekt ciągu - taki jak element tablicy ciągów, jak wspomniano wcześniej.

Pomyślałem, że zaoferuję inną możliwość. W programowaniu zawsze istnieje wiele sposobów wykonania zadania. Poznaj tyle, ile możesz, a następnie wybierz ten, który najlepiej pasuje do danego zadania, mając na uwadze kwestie wydajności, intuicyjność, przejrzystość kodu, preferencje dotyczące stylu kodowania i najbardziej samodokumentujące się.

Miłego kodowania!

jamesc1101
źródło
1
Wykonujesz niepotrzebną pracę. Pakujesz każdy intdo jego typu opakowania Integeri rozpakowujesz go zaraz potem.
Alexis C.