Por que não consigo mapear inteiros para strings ao fazer streaming de uma matriz?

92

Este código funciona (obtido no Javadoc):

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

Este não pode ser compilado:

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

IDEA me diz que tenho um "tipo de retorno incompatível String na expressão lambda".

Por quê ? E como consertar isso?

Denys Séguret
fonte

Respostas:

114

Arrays.stream(int[])cria um IntStream, não um Stream<Integer>. Portanto, você precisa chamar, em mapToObjvez de apenas map, ao mapear um intpara um objeto.

Isso deve funcionar conforme o esperado:

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

que você também pode escrever:

String commaSeparatedNumbers = Arrays.stream(numbers)
    .mapToObj(Integer::toString)
    .collect(Collectors.joining(", "));
assilias
fonte
3
Qual é a diferença entre IntStreame Stream<Integer>?
Florian Margaine
8
@FlorianMargaine An IntStreamé uma especialização de fluxo para intvalores primitivos . A Stream<Integer>é apenas um riacho segurando Integerobjetos.
Alexis C.
2
@FlorianMargaine IntStreamé um fluxo ou primitivos (ints) enquanto Steram<Integer>é um fluxo de objetos. Os fluxos primitivos têm métodos especializados por motivos de desempenho.
assylias
7
IntStream.mapToObjespera um IntFunction, uma função que consome um intvalor, portanto  .mapToObj((Integer i) -> i.toString())não funciona. De qualquer forma, não seria recomendado, pois contém uma conversão desnecessária de intpara Integer. Em contraste, .mapToObj(Integer::toString)funciona bem porque chama o staticmétodo Integer.toString(int). Observe que isso é diferente de chamar .map(Integer::toString)a,  Stream<Integer>já que o último não compila porque é ambíguo.
Holger
1
@cic: não, chamar .map(Integer::toString)a Stream<Integer>é realmente ambíguo como ambos .map(i->i.toString())e .map(i->Integer.toString(i))é válido. Mas é facilmente resolvido usando .map(Object::toString).
Holger
19

Arrays.stream(numbers)cria um IntStreamsob o capô e a operação do mapa em um IntStreamrequer um IntUnaryOperator(ou seja, uma função int -> int). A função de mapeamento que você deseja aplicar não respeita este contrato e, portanto, o erro de compilação.

Você precisaria ligar boxed()antes para obter um Stream<Integer>(isto é o que Arrays.asList(...).stream()retorna). Em seguida, basta chamar mapcomo você fez no primeiro trecho.

Observe que, se precisar ser boxed()seguido por, mapprovavelmente você deseja usar mapToObjdiretamente.

A vantagem é que mapToObjnão requer encaixar cada intvalor em um Integerobjeto; dependendo da função de mapeamento que você aplica, é claro; então eu escolheria essa opção, que também é mais curta para escrever.

Alexis C.
fonte
5

Você pode criar um Integer Stream usando Arrays.stream (int []), você pode chamar mapToObjlike mapToObj(Integer::toString).

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

Espero que isto ajude..

codebot
fonte
2

Sem boxe, AFAIK e sem explosão de pequenas cordas adicionadas à pilha:

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
fonte
1

Se o objetivo deste exemplo e pergunta é descobrir como mapear strings para um fluxo de ints (por exemplo, usando um fluxo de ints para acessar um índice em um Array de strings), você também pode usar boxing e, em seguida, lançar para um int (que permitiria acessar o índice do array).

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

A chamada .boxed () converte seu IntStream (um fluxo de ints primitivos) em um Stream (um fluxo de objetos - ou seja, objetos Integer) que, então, aceitará o retorno de um objeto (neste caso, um objeto String) de seu lambda. Aqui, é apenas uma representação de string do número para fins de demonstração, mas poderia ser tão facilmente (e de forma mais prática) qualquer objeto de string - como o elemento de uma matriz de string, conforme mencionado antes.

Só pensei em oferecer outra possibilidade. Na programação, sempre existem várias maneiras de realizar uma tarefa. Conheça o máximo que puder e, em seguida, escolha aquele que melhor se adapta à tarefa em questão, tendo em mente os problemas de desempenho, intuitividade, clareza de código, suas preferências no estilo de codificação e o que mais se autodocumenta.

Boa codificação!

jamesc1101
fonte
1
Você está fazendo um trabalho desnecessário. Você embala cada um intem seu tipo de invólucro Integere desembala logo depois.
Alexis C.