Почему не вызываются методы объекта из arraylist с помощью stream (кроме toString)?

Рейтинг: 1Ответов: 1Опубликовано: 26.02.2023

Целый день пытаюсь разобраться с java stream, но не могу понять одной вещи.

Есть класс SomeClass, в нем есть переопределенный public String toString() и есть другие методы, например, public void someMethod().

Далее, есть ArrayList экземпляров этого класса и Stream:

List<SomeClass> someList = new ArrayList<>();
Stream someStream = someList.stream();

Если я пытаюсь с помощью stream.filter вызвать toString(), он вызывается, а если пытаюсь вызвать любой другой метод, то он не доступен.

Так как же вызывать другие методы?!

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

public class SomeClass {
    String name;

    SomeClass(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public void someMethod(){
        System.out.println("someMethod()" + name);
    }
}

Вот класс с методом main():

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class Test {
    public static void main(String[] args) {
        List<SomeClass> someList = new ArrayList<>();
        someList.add(new SomeClass("A"));
        someList.add(new SomeClass("B"));
        Stream someStream = someList.stream();
        someStream.filter(x -> "A".equals(x.toString())).forEach(x -> System.out.println(x.toString())); //работает
        someStream.filter(x -> "A".equals(x.toString())).forEach(x -> x.someMethod()); //someMethod() не доступен
        someStream.filter(x -> "A".equals(x.getName())).forEach(x -> x.someMethod()); //getName() тоже не доступен
    }
}

Ошибка:

Cannot resolve method 'someMethod' in 'Object'

Ответы

▲ 3Принят

Интерфейс Stream<T>, так же как и интерфейсы коллекций Collection<T>, List<T>, Set<T> являются обобщёнными, а в объявлении:

Stream someStream = someList.stream();

для экземпляра потока someStream не указан тип объектов, то есть он объявлен как "сырой" (компилятор должен был показать соответствующее предупреждение), а значит, при обработке элементов в этом потоке они обрабатываются как простые объекты, у которых существуют только методы toString, hashCode, equals и т.п.

Для решения этой проблемы достаточно правильно объявить поток:

Stream<SomeClass> someStream = someList.stream();

Но даже при корректном объявлении повторное использование потока указанным способом не будет работать, так как после первого же использования (вызова терминального метода типа forEach) поток будет закрыт, и соответственно будет выброшено исключение java.lang.IllegalStateException: stream has already been operated upon or closed.

Если нужно использовать один поток несколько раз, понадобится определить его Supplier, и вызывать поток при помощи Supplier::get:

import java.util.function.Supplier;
// ...

List<SomeClass> someList = new ArrayList<>();
someList.add(new SomeClass("A"));
someList.add(new SomeClass("B"));
Supplier<Stream<SomeClass>> someStream = () -> someList.stream();
someStream.get().filter(x -> "A".equals(x.toString())).forEach(x -> System.out.println(x.toString())); //работает
someStream.get().filter(x -> "A".equals(x.toString())).forEach(SomeClass::someMethod);
someStream.get().map(SomeClass::getName).forEach(System.out::println);

Или же можно просто каждый раз вызывать метод stream у списка someList, соответственно, проблем с типизацией также не будет:

List<SomeClass> list = Arrays.asList(new SomeClass("A"), new SomeClass("B"));

list.stream().filter(x -> "A".equals(x.toString())).forEach(System.out::println);
List<String> names = list.stream().map(SomeClass::getName).collect(Collectors.toList());