Cannot resolve method Character::hashCode in a Java stream

问题: In my example, I try to create an ASCII table from a sequence of characters. I managed to do it with a List of strings but failed with an array of chars. I get an error...

问题:

In my example, I try to create an ASCII table from a sequence of characters. I managed to do it with a List of strings but failed with an array of chars.

I get an error that Character::hashCode cannot be resolved in Collectors.toMap().

Error:(26, 17) java: method collect in interface java.util.stream.IntStream cannot be applied to given types;
  required: java.util.function.Supplier<R>,java.util.function.ObjIntConsumer<R>,java.util.function.BiConsumer<R,R>
  found: java.util.stream.Collector<java.lang.Object,capture#1 of ?,java.util.Map<java.lang.Object,java.lang.Object>>
  reason: cannot infer type-variable(s) R
    (actual and formal argument lists differ in length)
Error:(26, 42) java: incompatible types: cannot infer type-variable(s) T,K,U,T
    (argument mismatch; invalid method reference
      incompatible types: java.lang.Object cannot be converted to char)

Is there a way to do it?

public class JavaCollectToMapEx2 {

    public static void main(String[] args) {
        // list of ASCII characters
        var chars = List.of("a", "b", "c", "d", "e", "f",
                "g", "h", "i", "j", "k", "l", "m", "n",
                "o", "p", "q", "r", "s", "t", "u", "v",
                "w", "x", "y", "z");

//      CharSequence chars2 = "abcdefghijklmnopqrstuvwxyz";
        char[] letters = "abcdefghijklmnopqrstuvwxyz".toCharArray();

        // Map to represent ASCII character table
        Map<Integer, String> asciiMap = chars.stream()
           .collect(Collectors.toMap(String::hashCode, Function.identity()));

        Map<Integer, Character> asciiMap2 = CharBuffer.wrap(letters).chars() 
            .collect(Collectors.toMap(Character::hashCode, Function.identity()));

        System.out.println(asciiMap);
        System.out.println(asciiMap2);
    }
}

回答1:

.chars() is giving you an IntStream, which is a stream of primitive int, and not a stream of characters (more info). This is why no method references on Character will work.

To achieve what you're looking for, you'll need a Stream<Character> first:

Map<Integer, Character> asciiMap2 = CharBuffer.wrap(letters)
        .chars()
        .mapToObj(e -> (char) e)
        .collect(Collectors.toMap(e -> e.hashCode(), Function.identity()));

Now, you still have the issue of using a method reference for getting the hash code. You can't use Character::hashCode because it's ambiguous as to which method you want, as there are two that are possible:

  1. The override of Object#hashCode,
  2. The static method int hashCode(char value)

You can see from this code, that both satisfy the first argument to toMap():

Function<Character, Integer> f1 = e -> Character.hashCode(e);
Function<Character, Integer> f2 = e -> e.hashCode();

To resolve this, you can use Object::hashCode for the non-static method call.


回答2:

First you need to map the IntStream to a Stream<Character>. But after that you can not use the Method reference Character::hashCode because it is ambiguous (object level and class level):

Map<Integer, Character> asciiMap2 = CharBuffer.wrap(letters).chars()
        .mapToObj(i -> (char) i)
        .collect(Collectors.toMap(i -> Character.hashCode(i), Function.identity()));

Alternatively you can just use Object::hashCode instead of i -> Character.hashCode(i) because the Character class overrides it's hashCode() method using Character.hashCode():

public final class Character ... {
    @Override
    public int hashCode() {
        return Character.hashCode(value);
    }
}

So finally you can just use this:

Map<Integer, Character> asciiMap2 = CharBuffer.wrap(letters).chars()
        .mapToObj(i -> (char) i)
        .collect(Collectors.toMap(Object::hashCode, Function.identity()));

回答3:

Since you are using the method collect() after CharBuffer::chars which returns IntStream, the only collecting method you can use is IntStream::collect(Supplier<R> supplier, ObjIntConsumer<R> accumulator, BiConsumer<R,R> combiner) taking 3 parameters.

If you wish to use one-parameter collecting method, place IntStream::boxed before it to return Stream<Integer>. Then the method Character::hashCode becomes ambiguous and the lambda expression cannot be used:

To avoid this, simply use a better method mapToObj to cast to char directly without need of boxing and then use Object::hashCode inherited from the `Object:

Map<Integer, Character> asciiMap2 = CharBuffer.wrap(letters).chars()
    .mapToObj(ch -> (char) ch)
    .collect(Collectors.toMap(Object::hashCode, Function.identity()));
  • 发表于 2019-07-07 22:11
  • 阅读 ( 815 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除