简体   繁体   中英

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.

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.

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);
                        }
                    }
                }
            }
        };
    }
}

Here is redesigned in its own way user270349 's answer without reflection. With static import it can be used like so:

Stream.of(new Employee("Adam", 38, 3000), new Employee("John", 19, 2000))
        .map(e -> retrieve(e::getName, e::getAge, e::getSalary))
        .forEach(c -> c.call(this::printNameAgeSalary));

or like so:

Stream.of(new Employee("Adam", 38, 3000), new Employee("John", 19, 2000))
        .forEach(e -> retrieve(e::getName, e::getAge).call(this::printNameAge));   

Here is code:

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

private void printNameAge(String name, int age) {
    System.out.format("%s %d\n", name, age);
}

public interface Consumer2<T1, T2> {
    void consume(T1 t1, T2 t2);
}

public interface Consumer3<T1, T2, T3> {
    void consume(T1 t1, T2 t2, T3 t3);
}

public interface Caller<E> {

    void call(E code);

    static <S1, S2, S3> Caller<Consumer3<S1, S2, S3>> retrieve(Supplier<S1> s1, Supplier<S2> s2, Supplier<S3> s3) {
        return (Consumer3<S1, S2, S3> code) -> code.consume(s1.get(), s2.get(), s3.get());
    }

    static <S1, S2> Caller<Consumer2<S1, S2>> retrieve(Supplier<S1> s1, Supplier<S2> s2) {
        return (Consumer2<S1, S2> code) -> code.consume(s1.get(), s2.get());
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM