2

I have a generic method (called map) which takes a parameter of type Funcn where Funcn<K,T> is an interface having only one method, T eval(K), which takes a K and returns a T. In map, I need use the Funcn to iterate through a List<K> (which is defined in the main method — I use this in the map to access it) and apply the Funcn to each element in the list. Finally, a new List<T> of all the results of eval should be returned. However, I am not sure how this can be done, since I am unfamiliar with the syntax of generics.

/// the Funcn interface ///
package arrayList;
import java.util.*;

public interface Funcn <K,T> {
    public T eval(K k);
}

/// the generic map method(in its own class) ///
public java.util.ArrayList<T> map(Funcn<T,K> fn){
    for(T value:this){
        //fn must be applied to all elements in 'this'  
    }
}
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
Katia Abela
  • 271
  • 7
  • 17

2 Answers2

1

The problem in your code

In your code, the interface Funcn<K,T> declares a method eval that takes a K as an argument and returns a T. Note that K is the first type parameter and T is the second.

public interface Funcn <K,T> {
    public T eval(K k);
}

In the declaration of your map method, though, you've got the type parameters to your Funcn reversed:

public java.util.ArrayList<T> map(Funcn<T,K> fn){ /* … */ }
//                                      * *

This means that fn.eval takes a T and returns a K. Instead, it should be Funcn<K,T> fn so that fn.eval takes a K and returns a T. This would explain the error message that you mentioned in a comment: "The method apply(K) in the type Function<T,K> is not applicable for the arguments (T)" (It wouldn't explain why you've got Function in one place and Funcn in another, though. Are you showing us the real code?)

In general

Swapping the order of those arguments will solve your immediate problem, but in general the type parameters are a little bit more complicated. It's tricky to get the input and output types for these generic functions exactly right, since you should be able map a function that expects an argument of type C over a list whose elements are of a subtype of C. The comments in the code explain this in a bit more detail.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MapExample {

    /**
     * A Function maps an input to an output.
     */
    interface Function<InputType,OutputType> {
        OutputType call( InputType input );
    }

    /**
     * Map returns a list of elements obtained by applying the function
     * to each element of the input list.  The input and output types 
     * of the function do not need to align exactly with the type of 
     * elements in the input list;  but the function's input type must
     * be some supertype of the element type of the input list.  Similarly,
     * the output type of the function does not need to be the element type
     * of the output list;  but it must extend that type so that the result
     * of the function can be stored in the output list.
     */
    public static <OutputType,InputType> List<OutputType> map(
            final Function<? super InputType,? extends OutputType> function,
            final List<? extends InputType> list ) {
        final List<OutputType> results = new ArrayList<>( list.size() );
        for ( InputType in : list ) { 
            results.add( function.call( in ));
        }
        return results;
    }

    public static void main(String[] args) {
        // f takes an integer n to the string "*<n+1>*" (where <n+1> is the value of n+1). 
        Function<Integer,String> f = new Function<Integer, String>() {
            @Override
            public String call(Integer input) {
                return "*"+new Integer( input + 1 ).toString()+"*";
            }
        };
        System.out.println( map( f, Arrays.asList( 1, 3, 6, 8 )));
    }
}

The output is:

[*2*, *4*, *7*, *9*]

(Optional) A case for instance initializers

As an aside, I find that some of these functional problems are nice places to use instance initialization blocks in objects. While the implementation above creates the results list, then populates, and then returns it, you could also have the body of map be:

return new ArrayList<OutputType>( list.size() ) {{
    for ( final InputType in : list ) {
        add( function.call( in ));
    }
}};

which I kind of like, although you'll get a warning about the fact that the new (anonymous) class doesn't have a serialization ID, so you'd either need to @SuppressWarnings("serial") on the map method, or add an ID to the class. Those might not be so desirable, though. There are other issues with this kind of object too, though, as discussed in Efficiency of Java "Double Brace Initialization"?.

Community
  • 1
  • 1
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
0

I assume that T is the input of Funcn, and K the return type of it. It then has to return a list of K to work, else the generic signature of Funcn is useless.

public java.util.ArrayList<K> map(Funcn<T,K> fn){

    ArrayList<K> lst = new ArrayList<K>();

    for(T value:this){
        lst.add( fn.eval(value) );
    }
    return lst;    
}
jcklie
  • 4,054
  • 3
  • 24
  • 42
  • I have tried this already, however I keep getting this error: The method apply(K) in the type Function is not applicable for the arguments (T) – Katia Abela Dec 19 '13 at 15:14
  • Can you post the **Funcn** interface with the *eval* signature? – jcklie Dec 19 '13 at 15:17