29

My question may be too broad and probably the answer is a simple NO, but I have to ask.

Is there any equivalent implementation of (Java 8) streams* in Java 7?

I am familiar with (Java 8) streams but my project requirement is to use Java 7.

*Not to be confused with inputStream and outputStream.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
Athafoud
  • 2,898
  • 3
  • 40
  • 58
  • 5
    No. But you can look into Scala's stream API which runs in the JVM so you can mix your code. Maybe it's not exactly what you are looking for but it's worth a try if needed. – Alexis C. May 05 '15 at 14:20
  • I think this is an interesting question. For sure, you need a Java8 JVM regarding the syntactic changes to the language for lambda expressions. But streams itself is a concept that is not implemented by the compiler; but by classes in libraries. It might be a futile activity, but you might want to simply check the streams sources from Java8; and see if they compile&run in a Java7 environment. – GhostCat May 05 '15 at 14:23
  • I believe there are many of them, but not such powerful as Java 8 streams. One example: https://github.com/wagnerandrade/coollection#readme – Alex Salauyou May 05 '15 at 14:24
  • @SashaSalauyou I came across that lib some years ago and seems very nice. But I think it does not fit to my current problem since it has to do with numerical data (time-series). – Athafoud May 05 '15 at 14:35
  • As far as I know, Java 8 Streams make use of the new INVOKEDYNAMIC opcode, which only exists in Java 8 JVMs. I do not know if this is strictly necessary, of if it is "just" a performance optimization, though. – C-Otto Mar 07 '17 at 13:54

3 Answers3

26

In the official API, no.

There is no more public updates for Java 7. If you're a customer, you might still get minor updates, but this not (or very very very unlikely) for back-porting the Stream API.

With a bit of digging you may look at StreamSupport. I've never tested it but apparently its goal is to backport the Stream API to Java 6/7 and if you want to combine it with lambda expressions there's also retrolambda.

Functional Java can be interesting. It's not exactly the same intent as the Stream API but if your goal is to filter/map/etc. a list/array it might suits your needs. For example:

final List<Integer> b = list(1, 2, 3).map(add.f(-1));
listShow(intShow).println(b); // [0, 1, 2]

Finally you can look into the Scala's Stream API. As Scala runs also on the JVM, you can mix your code. Maybe it's not exactly what you are looking for but it's worth a try if needed.

Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
  • Java 7 can still be used for production environments - the "end of life" is just for the free updates. If a customer buys some support from Oracle he will still get updates and bugfixes (even for previous releases like Java 6 or Java 5). So in that case it is perfecly okay to develop software with target runtime Java 7. – mschenk74 May 05 '15 at 14:50
  • 2
    The idea of mixing the java with scalla code is very interesting. streamsupport is also good way to go. – Athafoud May 05 '15 at 14:53
  • 2
    @mschenk74 I didn't say it shouldn't be used for production environments. You might get minor updates, but I wanted to point that these updates are very unlikely to backport the Stream API into Java 7, IMO. – Alexis C. May 05 '15 at 14:57
  • 1
    Alright - one could read your sentence as "do not program for java 7 since it has reached EOL" - I just wanted to make clear that it is still possible to get updates. But you are perfectly right when you write that there will be no backports of new features. – mschenk74 May 05 '15 at 15:01
  • @mschenk74 I updated my answer to avoid confusions :-) – Alexis C. May 05 '15 at 15:04
9

Google's Guava library contains some functional idioms for Java versions 5 to 7:

https://github.com/google/guava/wiki/FunctionalExplained

Also, there is this library that you may want to check (I haven't heard of it up until some minutes ago when I performed a Google search :-) )

http://www.functionaljava.org/

Community
  • 1
  • 1
mavroprovato
  • 8,023
  • 5
  • 37
  • 52
  • I think 'functional' is the magic word for the google search, I did not thought it earlier. I was not aware for that functionality of Guava, but the documentation looks good. functionaljava.org looks a little 'messy' but I will give it a try! – Athafoud May 05 '15 at 14:46
3

An another choice is here for Java 6+

interfaces:

interface TransformRule<In, Out> {
    Out extract(In obj);
}

interface FilterRule<T> {
    boolean apply(T obj);
}

And Java8Stream-like container class for Collection/Map:

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

class FPMapContainer<KeyType, ValueType> extends FPContainer<Map<KeyType, ValueType>, Map.Entry<KeyType, ValueType>, ValueType> {
    FPMapContainer(Map<KeyType, ValueType> container) {
        super(container);
    }

    @Override
    public <Out> FPMapContainer<KeyType, Out> map(TransformRule<Map.Entry<KeyType, ValueType>, Out> rule) {
        return new FPMapContainer<>(handleContainer(getMapMapRule(rule)));
    }

    @Override
    public FPMapContainer<KeyType, ValueType> filter(FilterRule<Map.Entry<KeyType, ValueType>> rule) {
        return new FPMapContainer<>(handleContainer(getMapFilterRule(rule)));
    }

    @Override
    public FPMapContainer<KeyType, ValueType> concat(Map<KeyType, ValueType> another) {
        HashMap newOne = new HashMap(container);
        newOne.putAll(another);
        return new FPMapContainer<>(newOne);
    }

    @Override
    public FPMapContainer<KeyType, ValueType> concat(FPContainer<Map<KeyType, ValueType>, Map.Entry<KeyType, ValueType>, ValueType> another) {
        return concat(another.get());
    }

    protected <Out> TransformRule<Map<KeyType, ValueType>, Map<KeyType, Out>> getMapMapRule(final TransformRule<Map.Entry<KeyType, ValueType>, Out> rule) {
        return new TransformRule<Map<KeyType, ValueType>, Map<KeyType, Out>>() {
            @Override
            public Map<KeyType, Out> extract(Map<KeyType, ValueType> obj) {
                Map<KeyType, Out> newOne = new HashMap<>();
                for (Map.Entry<KeyType, ValueType> entry : obj.entrySet()) {
                    newOne.put(entry.getKey(), rule.extract(entry));
                }
                return newOne;
            }
        };
    }

    protected TransformRule<Map<KeyType, ValueType>, Map<KeyType, ValueType>> getMapFilterRule(final FilterRule<Map.Entry<KeyType, ValueType>> rule) {
        return new TransformRule<Map<KeyType, ValueType>, Map<KeyType, ValueType>>() {
            @Override
            public Map<KeyType, ValueType> extract(Map<KeyType, ValueType> obj) {
                Map<KeyType, ValueType> newOne = new HashMap<>();
                for (Map.Entry<KeyType, ValueType> entry : obj.entrySet()) {
                    KeyType key = entry.getKey();
                    ValueType value = entry.getValue();
                    boolean isValid = rule.apply(entry);

                    if (isValid) {
                        newOne.put(key, value);
                    }
                }
                return newOne;
            }
        };
    }
}

class FPCollectionContainer<ValueType> extends FPContainer<Collection<ValueType>, ValueType, ValueType> {
    FPCollectionContainer(Collection<ValueType> container) {
        super(container);
    }

    @Override
    public <Out> FPCollectionContainer<Out> map(TransformRule<ValueType, Out> rule) {
        return new FPCollectionContainer<>(handleContainer(getCollectionMapRule(rule)));
    }

    @Override
    public FPCollectionContainer<ValueType> filter(FilterRule<ValueType> rule) {
        return new FPCollectionContainer<>(handleContainer(getCollectionFilterRule(rule)));
    }

    @Override
    public FPCollectionContainer<ValueType> concat(Collection<ValueType> another) {
        ArrayList<ValueType> newOne = new ArrayList<>(container);
        newOne.addAll(another);
        return new FPCollectionContainer<>(newOne);
    }

    @Override
    public FPCollectionContainer<ValueType> concat(FPContainer<Collection<ValueType>, ValueType, ValueType> another) {
        return concat(another.get());
    }

    protected <Out> TransformRule<Collection<ValueType>, Collection<Out>> getCollectionMapRule(final TransformRule<ValueType, Out> rule) {
        return new TransformRule<Collection<ValueType>, Collection<Out>>() {
            @Override
            public Collection<Out> extract(Collection<ValueType> obj) {
                Collection<Out> newOne = new ArrayList<>();
                for (ValueType entry : obj) {
                    newOne.add(rule.extract(entry));
                }
                return newOne;
            }
        };
    }

    protected TransformRule<Collection<ValueType>, Collection<ValueType>> getCollectionFilterRule(final FilterRule<ValueType> rule) {
        return new TransformRule<Collection<ValueType>, Collection<ValueType>>() {
            @Override
            public Collection<ValueType> extract(Collection<ValueType> obj) {
                Collection<ValueType> newOne = new ArrayList<>();
                for (ValueType entry : obj) {
                    if (rule.apply(entry)) {
                        newOne.add(entry);
                    }
                }
                return newOne;
            }
        };
    }
}

abstract class FPContainer<ContainerTypeWithValueType, ContainerIterableItemType, ValueType> {

    protected ContainerTypeWithValueType container;

    protected FPContainer(ContainerTypeWithValueType container) {
        this.container = container;
    }

    public static <KeyType, ValueType> FPMapContainer<KeyType, ValueType> from(Map<KeyType, ValueType> container) {
        return new FPMapContainer<>(container);
    }

    public static <ValueType> FPCollectionContainer<ValueType> from(Collection<ValueType> container) {
        return new FPCollectionContainer<>(container);
    }

    public abstract <Out> Object map(TransformRule<ContainerIterableItemType, Out> rule);

    public abstract FPContainer<ContainerTypeWithValueType, ContainerIterableItemType, ValueType> filter(FilterRule<ContainerIterableItemType> rule);

    public abstract FPContainer<ContainerTypeWithValueType, ContainerIterableItemType, ValueType> concat(FPContainer<ContainerTypeWithValueType, ContainerIterableItemType, ValueType> another);

    public abstract FPContainer<ContainerTypeWithValueType, ContainerIterableItemType, ValueType> concat(ContainerTypeWithValueType another);

    public <Out> Out reduce(TransformRule<ContainerTypeWithValueType, Out> rule) {
        return rule.extract(container);
    }

    public ContainerTypeWithValueType get() {
        return container;
    }

    protected <ContainerTargetType> ContainerTargetType handleContainer(TransformRule<ContainerTypeWithValueType, ContainerTargetType> collectionMapRule) {
        if (collectionMapRule != null) {
            return collectionMapRule.extract(container);
        }

        return (ContainerTargetType) container;
    }
}

Now you can use it like Java8Stream in this way:

TransformRule<Integer, String> integerStringTransform = new TransformRule<Integer, String>() {
    @Override
    public String extract(Integer obj) {
        return "" + obj;
    }
};

TransformRule<Collection<String>, String> collectionStringTransform = new TransformRule<Collection<String>, String>() {
    @Override
    public String extract(Collection<String> obj) {
        String result = "";
        for (String item : obj) {
            result += item;
        }

        return result;
    }
};

FilterRule<Integer> ltFourFilter = new FilterRule<Integer>() {
    @Override
    public boolean apply(Integer obj) {
        return obj != null && obj < 4;
    }
};

// ==============================================

String reduced;

// Collection case:
// `reduced` would be "123"
reduced = FPContainer.from(Arrays.asList(1, 4))
        .concat(FPContainer.from(Arrays.asList(2)))
        .concat(Arrays.asList(3))
        .filter(ltFourFilter)
        .map(integerStringTransform).reduce(collectionStringTransform);

// Map case:
reduced = FPContainer.from(stringIntegerHashMap)
        .filter(new FilterRule<Map.Entry<String, Integer>>() {
            @Override
            public boolean apply(Map.Entry<String, Integer> obj) {
                return obj.getKey().charAt(0) < 'c' && obj.getValue() < 4;
            }
        })
        .map(new TransformRule<Map.Entry<String,Integer>, String>() {
            @Override
            public String extract(Map.Entry<String, Integer> obj) {
                return ""+obj.getValue();
            }
        }).reduce(new TransformRule<Map<String, String>, String>() {
            @Override
            public String extract(Map<String, String> obj) {
                String result = "";

                Map<String, String> objectStringMap = sortByValue(obj);
                for (Map.Entry<String, String> entry : objectStringMap.entrySet()) {
                    result += entry.getKey().toString() + entry.getValue();
                }

                return result;
            }
        });

P.S.

sortByValue(map) is here: (credit: https://stackoverflow.com/a/109389/2293635)

public static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
    List<Map.Entry<K, V>> list = new LinkedList<>(map.entrySet());
    Collections.sort(list, new Comparator<Object>() {
        @SuppressWarnings("unchecked")
        public int compare(Object o1, Object o2) {
            return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
        }
    });

    Map<K, V> result = new LinkedHashMap<>();
    for (Iterator<Map.Entry<K, V>> it = list.iterator(); it.hasNext();) {
        Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
        result.put(entry.getKey(), entry.getValue());
    }

    return result;
}
John Lee
  • 91
  • 2
  • 5
  • I think the most important thing from Streams is the support for parallelism more than support for functional programming. – luis.espinal Apr 18 '19 at 02:23