Java 8 Stream — NullPointerException при работе с Custom Collector

Я реализовал собственный сборщик, внедрив интерфейс Collector и переопределив его методы. Реализация коллектора выглядит следующим образом:

class MyCustomCollector implements Collector<Person, StringJoiner, String>{

    @Override
    public Supplier<StringJoiner> supplier() {
        // TODO Auto-generated method stub
        return () -> new StringJoiner("|");
    }

    @Override
    public BiConsumer<StringJoiner, Person> accumulator() {
        // TODO Auto-generated method stub
        return (joiner,person) -> joiner.add(person.name.toUpperCase());
    }

    @Override
    public BinaryOperator<StringJoiner> combiner() {
        // TODO Auto-generated method stub
        return (joiner1, joiner2) -> joiner1.merge(joiner2);
    }

    @Override
    public Function<StringJoiner, String> finisher() {
        // TODO Auto-generated method stub
        return StringJoiner::toString;
    }

    @Override
    public Set<java.util.stream.Collector.Characteristics> characteristics() {
        // TODO Auto-generated method stub
        return null;
    }
}

Вот мой класс Person:

class Person implements Comparable<Object>{
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name;
    }

    public int compareTo(Object obj){
        int returnValue;
        if(age ==((Person) obj).age)
            returnValue=0;
        else
            if(age >((Person) obj).age)
                returnValue = 1;
            else
                returnValue =-1;

        return returnValue;
    }

    public boolean equals(Object obj)
    {
        if(!(obj instanceof Person))
            return false;

        return (age == ((Person) obj).age); 
    }

    public int hashCode()
    {  
        return name.hashCode();  
    } 

}

И вот мои призывные заявления ...

List<Person> persons =
        Arrays.asList(
            new Person("Max", 18),
            new Person("Peter", 23),
            new Person("Pamela", 23),
            new Person("David", 12),
            new Person("Pam", 23));
String names2 = persons.stream()
            .collect(new MyCustomCollector());

Когда приведенный выше оператор выполняется, я получил исключение NullPointerException следующим образом:

Исключение в потоке «основной» java.lang.NullPointerException в java.util.stream.ReduceOps$3.getOpFlags(ReduceOps.java:185) в java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) в java.util .stream.ReferencePipeline.collect(ReferencePipeline.java:499) в com.my.j8.TestStreams.main(TestStreams.java:231)

Может ли кто-нибудь сказать, где я делаю ошибку?


person KayV    schedule 01.12.2016    source источник
comment
Подсказка: начните с поиска null в вашей программе.   -  person Brian Goetz    schedule 01.12.2016
comment
GhostCat Я вообще этого не делал.   -  person KayV    schedule 02.12.2016
comment
Спасибо за быстрый отзыв!   -  person GhostCat    schedule 02.12.2016


Ответы (1)


Здесь:

public Set<java.util.stream.Collector.Characteristics> characteristics() {
    return null;

Возвращает пустой набор вместо нуля; как

private final static Set<Characteristics> EMPTY = Collections.emptySet();

...

return EMPTY;

или просто

return Collections.emptySet();

(поскольку в классе Collections уже есть некоторая константа EMPTY).

Суть в следующем: этот интерфейс содержит этот метод; и «Я не использую эту часть» по-прежнему требует от вас «разумной» реализации этого метода. Так что имейте это в виду на будущее: любой интерфейс, возвращающий коллекцию, позволяет сказать "нет контента", возвращая пустую вещь; вместо null!

Конечно, в конце концов проблема может быть замечена окружающей структурой, не проверяющей, возвращает ли этот метод значение null. Но, как сказано: когда вы имеете дело с коллекциями любого типа, забудьте об использовании null. Если ничего нет, создайте пустой объект коллекции этого типа!

Один из важных шагов, чтобы избежать NPE, — это прежде всего не возвращать null.

И помимо этого: как указывает Юджин, вы, вероятно, захотите очень тщательно проверить, следует ли вам действительно использовать здесь пустой набор. Другими словами: вы изучали характеристики и понимать их концепцию в целом; а вы на 100% уверены, что хотите здесь "никто"?!

person GhostCat    schedule 01.12.2016
comment
Это решило проблему. Но даже если я не использую Характеристики, то зачем мне возвращать какой-то пустой набор из Характеристик? - person KayV; 01.12.2016
comment
Смотрите мой обновленный ответ. И поскольку этот ответ помог, рассмотрите возможность его принятия. - person GhostCat; 01.12.2016
comment
@GhostCat, вы должны тщательно обдумать это, прежде чем НЕ предоставлять какие-либо характеристики. На их основе выполняются различные внутренние оптимизации, которые не могут нарушить их. - person Eugene; 01.12.2016
comment
@Eugene Моей главной задачей было помочь с его NPE; но вы правы; Я расширил свой ответ в этом отношении. - person GhostCat; 01.12.2016
comment
характеристики — позволяет указать характеристики сборщика, чтобы его можно было вызывать безопасно и оптимально. например указание Characteristics.IDENTITY_FINISH позволяет Java узнать, что, поскольку вы не выполняете окончательное преобразование, ей даже не нужно вызывать вашу финишную функцию. - person KayV; 01.12.2016
comment
@KaranVerma Нет особого смысла цитировать нам этот javadoc. Дело в том, что это ваш проект, ваш код; вы хотите использовать этот интерфейс, поэтому вы сами должны решить, что вы хотите здесь делать. И если вы действительно не понимаете значения этих значений; подумайте о том, чтобы задать еще один вопрос. - person GhostCat; 01.12.2016
comment
Нет необходимости хранить Set в переменной. Collections.emptySet() уже возвращает константу, хранящуюся в Collections.EMPTY_SET - person Holger; 01.12.2016
comment
@Karan Verma: вам не нужно указывать пустые характеристики, так как вам вообще не нужно реализовывать интерфейс Collector. Просто используйте статический фабричный метод: Collector.of( () -> new StringJoiner("|"), (joiner,person) -> joiner.add(person.name.toUpperCase()), (joiner1, joiner2) -> joiner1.merge(joiner2), StringJoiner::toString ). Но стоит отметить, что даже это коллекционное творение — ненужное изобретение велосипеда. Вы можете просто использовать Collectors.mapping( person -> person.name.toUpperCase(), Collectors.joining("|")) для достижения того же. - person Holger; 01.12.2016