Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Splitting objects inside Java stream

I am wondering if it is possible to split an object inside a Stream. For example, for this Employee:

public class Employee {

    String name;
    int age;
    double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() { return name; }

    public int getAge() { return age; }

    public double getSalary() { return salary; }
}

I would like to perform some operation in the stream. For simplicity, let it be something like this (assume my code architecture does not allow to put this inside Employee class - otherwise it would be too easy):

public void someOperationWithEmployee(String name, int age, double salary) {
    System.out.format("%s %d %.0f\n", name, age, salary);
}

Now it looks like this:

Stream.of(new Employee("Adam", 38, 3000), new Employee("John", 19, 2000))
        // some conversations go here ...
        .forEach(e -> someOperationWithEmployee(e.getName, e.getAge(), e.getSalary));

The question is, is it possible to put some code inside a stream like this?

Stream.of(new Employee("Adam", 38, 3000), new Employee("John", 19, 2000))
        // some conversations go here
        .forEach((a, b, c) -> someOperationWithEmployee(a, b, c));

What am I trying to achieve? - I think if I could map some object fields and then process them like .forEach(this::someOperationWithEmployee) code readability would be improved slightly.


Update 14.05.2015

Without a doubt to pass an Employee object to someOperationWithEmployee is prettiest solution in this case but sometimes we can not do this in real life and should be universal solution with lambdas.

like image 330
ytterrr Avatar asked May 14 '15 08:05

ytterrr


People also ask

Are Java streams multi threaded?

Java 8 introduced the concept of Streams as an efficient way of carrying out bulk operations on data. And parallel Streams can be obtained in environments that support concurrency. These streams can come with improved performance – at the cost of multi-threading overhead.


2 Answers

The short answer is no, you cannot do this. The shortest solution I can think up is to define your own functional interface like this:

import java.util.function.Function;

@FunctionalInterface
public interface TriFunction<A,B,C,R> {
    R apply(A a, B b, C c);

    static <I,A,B,C,R> Function<I,R> convert(TriFunction<A,B,C,R> triFn, Function<I,A> aFn, 
                                             Function<I,B> bFn, Function<I,C> cFn) {
        return i -> triFn.apply(aFn.apply(i), bFn.apply(i), cFn.apply(i));
    }
}

And use it like this:

Stream.of(new Employee("Adam", 38, 3000), new Employee("John", 19, 2000))
    // some conversations go here
    .forEach(TriFunction.convert((a, b, c) -> someOperationWithEmployee(a, b, c), 
         Employee::getName, Employee::getAge, Employee::getSalary));

Though it's far from being beautiful.

I think it would be much better if your someOperationWithEmployee took the Employee object as an argument.

Update: for the pair of values you may use my free StreamEx library like this:

StreamEx.of(new Employee("Adam", 38, 3000), new Employee("John", 19, 2000))
    // some conversations go here
    .mapToEntry(Employee::getName, Employee::getAge)
    .forKeyValue((a, b) -> someOperationWithEmployee(a, b));

However it's limited to pairs only, so you cannot handle three or more values in this way (and I'm not going to add such functions).

I also checked the jOOL library as it's concentrated on tuples and already provides interfaces like Function3. However it seems that there's no easy way to use it for your problem either.

like image 97
Tagir Valeev Avatar answered Oct 24 '22 07:10

Tagir Valeev


I am not sure this fit your needs but it works with a bit of refection and not checking some types.

You can run my solution this way:

    Stream.of(new Employee("Adam", 38, 3000), new Employee("John", 19, 2000))
        .forEach(
                e->ArrayCaller.<TriConsumer<String, Integer, Double>>convert(e::getName, e::getAge, e::getSalary)
                                                                     .call((a, b, c) -> operation(a, b, c)));

It would call this simple method of the 'main' class:

private void operation(String name, int age, double salary) {
    System.out.format("%s %d %.0f\n", name, age, salary);
}

Of course it needs this auxiliary types:

/** Extending interfaces must have a method called consume with N args */
interface NConsumer {}

/*
 * Method must be called consume for reflection.
 *
 * You can define N interfaces like this.
 */
nterface TriConsumer<A, B, C> extends NConsumer {
    void consume(A a, B b, C c);
}

interface ArrayCaller<E extends NConsumer> {
    void call(E code);
    static <T extends NConsumer> ArrayCaller<T> convert(Supplier<?>...argSuppliers) {
        final Object[] args = new Object[argSuppliers.length];
        for (int i = 0; i < argSuppliers.length; i++) {
            args[i] = argSuppliers[i].get();
        }
        return new ArrayCaller<T>() {
            @Override
            public void call(T code) {
                for (Method m: code.getClass().getMethods()) {
                    if (m.getName().equals("consume")) {
                        try {
                            m.invoke(code, args);
                        } catch (IllegalAccessException
                                | IllegalArgumentException
                                | InvocationTargetException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        };
    }
}
like image 2
aalku Avatar answered Oct 24 '22 06:10

aalku