171

I need a map function. Is there something like this in Java already?

(For those who wonder: I of course know how to implement this trivial function myself...)

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Albert
  • 65,406
  • 61
  • 242
  • 386

6 Answers6

110

Since Java 8, there are some standard options to do this in JDK:

Collection<E> in = ...
Object[] mapped = in.stream().map(e -> doMap(e)).toArray();
// or
List<E> mapped = in.stream().map(e -> doMap(e)).collect(Collectors.toList());

See java.util.Collection.stream() and java.util.stream.Collectors.toList().

YasirA
  • 9,531
  • 2
  • 40
  • 61
leventov
  • 14,760
  • 11
  • 69
  • 98
  • 191
    This is so much verbose that is hurts me inside. – Natix Jan 29 '14 at 13:48
  • @Natix if the collection is ArrayList and you don't need it after the mapping there is simplier and more efficient [`ArrayList.replaceAll`](http://download.java.net/jdk8/docs/api/java/util/ArrayList.html#replaceAll-java.util.function.UnaryOperator-). – leventov Jan 29 '14 at 18:03
  • @Natix verbose, but agile. `stream()` or `parallelStream()`, collecting to List or to Set, etc. But I agree, there should be shortcuts for most common use cases. – leventov Jan 29 '14 at 18:05
  • leventov: `replaceAll()` cannot map to a different type, is usable only for lists, and modifies the original list. That's not good. I can accept that you need to create a `Stream` from every collection before you can perform some functional operations on it, but WHY OH WHY are there no `toList()`, `toSet()` etc. methods directly on it? Who the hell cares about some collectors? I just want to map/filter a collection. – Natix Jan 29 '14 at 18:30
  • 1
    @Natix agree about `toList()`. Replacing to different type: `(List)((List) list).replaceAll(o -> doMap((E) o));` – leventov Jan 29 '14 at 18:35
  • leventov: Please don't do such hacks. Ever. :) – Natix Jan 29 '14 at 18:39
  • If you don't want to bother converting to a stream and back, you can use Guava's `Collections2.transform` with a lambda. That's reasonably terse, but it is disappointing that there's no terse way to map a collection in the JRE. IMO `Collection` should have been given `map` and `filter` with default implementations. – Daniel Lubarov Apr 04 '14 at 19:38
  • 3
    Can `e -> doMap(e)` be replaced with just `doMap`? – jameshfisher Aug 26 '14 at 15:02
  • 4
    @jameshfisher, yes, something like `foo::doMap` or `Foo::doMap`. – leventov Aug 27 '14 at 06:26
  • 11
    I guess this is why Scala exists. Wait for Java 12 to have something readable. – JulienD Jul 02 '17 at 10:44
  • In Java 12: `[1, 2, 3].collect { it * 2 } //result: [2, 4, 6]` It's going to be a groovy time! – Dem Pilafian Oct 01 '18 at 19:47
  • @DemPilafian Imagine if Java 12 had actually done something like that... – ReinstateMonica3167040 Dec 13 '21 at 02:27
91

There is no notion of a function in the JDK as of java 6.

Guava has a Function interface though and the
Collections2.transform(Collection<E>, Function<E,E2>)
method provides the functionality you require.

Example:

// example, converts a collection of integers to their
// hexadecimal string representations
final Collection<Integer> input = Arrays.asList(10, 20, 30, 40, 50);
final Collection<String> output =
    Collections2.transform(input, new Function<Integer, String>(){

        @Override
        public String apply(final Integer input){
            return Integer.toHexString(input.intValue());
        }
    });
System.out.println(output);

Output:

[a, 14, 1e, 28, 32]

These days, with Java 8, there is actually a map function, so I'd probably write the code in a more concise way:

Collection<String> hex = input.stream()
                              .map(Integer::toHexString)
                              .collect(Collectors::toList);
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 8
    It's worth noting that while with Guava you *can* do this, you might not want to: http://code.google.com/p/guava-libraries/wiki/FunctionalExplained (read the "Caveats" section). – Adam Parkin Mar 07 '13 at 22:32
  • 2
    @AdamParkin true, but I'm pretty sure that refers to more advanced functional concepts than this, otherwise they wouldn't have developed the *transform(*) methods in the first place – Sean Patrick Floyd Mar 08 '13 at 10:35
  • 2
    Actually, no, there is often a definite performance hit with functional idioms, which is why they stress you should only use the facilities if you are certain it meets the two criteria outlined: net savings of LOC for the codebase as a whole, and *proven* performance gains due to lazy evaluation (or at least not performance hits). Not arguing against the use of them, just indicating that if you're going to, you should heed the warnings of the implementers. – Adam Parkin Mar 08 '13 at 16:45
  • 4
    @SeanPatrickFloyd now that Java 8 is out, want to update this with an example involving lambdas? Like `Collections2.transform(input -> Integer.toHexString(intput.intValue())` – Daniel Lubarov Apr 04 '14 at 19:41
  • 2
    @Daniel In Java 8, I don't see a reason to do that with Guava. Instead I'd go for leventov's answer – Sean Patrick Floyd Apr 05 '14 at 09:48
  • that is the right answer but writing a loop a lot simpler and smaller – Hossein Shahdoost Nov 20 '16 at 00:05
  • @HosseinShahdoost true. Imperative code is usually more efficient, but functional code is less error prone and more maintainable – Sean Patrick Floyd Dec 02 '16 at 09:02
  • @SeanPatrickFloyd, I agree, but in this specific subject imperative code is more maintainable and error prone too (I mean how can this loop cause any error?) – Hossein Shahdoost Dec 03 '16 at 08:52
  • @HosseinShahdoost first of all, this is no longer the answer I'd suggest, after 6 years. But the OP is asking about a map function, not about imperative code.. I updated my answer to show how I'd write the code these days – Sean Patrick Floyd Dec 03 '16 at 11:10
  • 1
    But one important difference: the old code was lazy, this code is eager. Depending on your input and use case, this may be a huge difference – Sean Patrick Floyd Dec 03 '16 at 11:11
27

There is a wonderful library called Functional Java which handles many of the things you'd want Java to have but it doesn't. Then again, there's also this wonderful language Scala which does everything Java should have done but doesn't while still being compatible with anything written for the JVM.

wheaties
  • 35,646
  • 15
  • 94
  • 131
  • I am interested in how did they enable following syntax: `a.map({int i => i + 42});` did they extend compiler? or added preprocessor? – Andrey Oct 11 '10 at 15:07
  • @Andrey - You can either ask them that yourself or check out the source code to see how it's done. Here's a link to the source: http://functionaljava.org/source/ – wheaties Oct 11 '10 at 15:09
  • 1
    @Andrey: examples use syntax from BGGA closures proposal. While there is running prototype, it's not in 'official' Java yet. – Peter Štibraný Oct 11 '10 at 15:10
  • @Andrey: that syntax is part of a proposed specification for closures in Java (see second-to-last paragraph on homepage). There's only a prototypical implementation. – Michael Borgwardt Oct 11 '10 at 15:12
  • to be fair: scala is not the only choice. Groovy, Clojure, JRuby and probably many others all offer this functionality. – Sean Patrick Floyd Oct 11 '10 at 15:13
  • @Peter Štibraný but who compiles that code? javac? or they first use their preprocessor? – Andrey Oct 11 '10 at 15:13
  • 2
    Scala thread hijack :( I hope SO won't become like the JavaPosse mailing list ;) – Jorn Oct 11 '10 at 15:13
  • @Andrey on the Java VM? I don't think so... :-) – Sean Patrick Floyd Oct 11 '10 at 15:15
  • There are more functional libraries for Java, Functional Java being oen of them. See http://stackoverflow.com/questions/1267297/functional-programming-in-java – Adam Schmideg Oct 11 '10 at 15:25
  • @Adam true, but I'd still say that FunctionalJava and Guava are the two most promising options – Sean Patrick Floyd Oct 11 '10 at 15:42
  • @Andrey: modified Javac can compile that code. It is available at http://javac.info/ (look for 'prototype'). But I think that Functional Java page use it only as an uncompilable example. – Peter Štibraný Oct 11 '10 at 16:09
  • Having recently used FunctionalJava, I have to say I'm now worried that it seems to no longer be actively supported ... – Matt Fenwick Sep 27 '12 at 17:59
  • @wheaties I am 10 years late, but your link is broken. – mazunki Apr 22 '20 at 19:57
9

Be very careful with Collections2.transform() from guava. That method's greatest advantage is also its greatest danger: its laziness.

Look at the documentation of Lists.transform(), which I believe applies also to Collections2.transform():

The function is applied lazily, invoked when needed. This is necessary for the returned list to be a view, but it means that the function will be applied many times for bulk operations like List.contains(java.lang.Object) and List.hashCode(). For this to perform well, function should be fast. To avoid lazy evaluation when the returned list doesn't need to be a view, copy the returned list into a new list of your choosing.

Also in the documentation of Collections2.transform() they mention you get a live view, that change in the source list affect the transformed list. This sort of behaviour can lead to difficult-to-track problems if the developer doesn't realize the way it works.

If you want a more classical "map", that will run once and once only, then you're better off with FluentIterable, also from Guava, which has an operation which is much more simple. Here is the google example for it:

FluentIterable
       .from(database.getClientList())
       .filter(activeInLastMonth())
       .transform(Functions.toStringFunction())
       .limit(10)
       .toList();

transform() here is the map method. It uses the same Function<> "callbacks" as Collections.transform(). The list you get back is read-only though, use copyInto() to get a read-write list.

Otherwise of course when java8 comes out with lambdas, this will be obsolete.

patstuart
  • 1,931
  • 1
  • 19
  • 29
Emmanuel Touzery
  • 9,008
  • 3
  • 65
  • 81
2

This is another functional lib with which you may use map: http://code.google.com/p/totallylazy/

sequence(1, 2).map(toString); // lazily returns "1", "2"
Pedro Rolo
  • 28,273
  • 12
  • 60
  • 94
  • Here's a guide on how to use TotallyLazy: http://intrepidis.blogspot.com/2013/07/using-totallylazy-functional-library.html – intrepidis Jul 11 '13 at 20:34
2

Even though it's an old question I'd like to show another solution:

Just define your own operation using java generics and java 8 streams:

public static <S, T> List<T> map(Collection<S> collection, Function<S, T> mapFunction) {
   return collection.stream().map(mapFunction).collect(Collectors.toList());
}

Than you can write code like this:

List<String> hex = map(Arrays.asList(10, 20, 30, 40, 50), Integer::toHexString);
IPP Nerd
  • 992
  • 9
  • 25