在列表上映射通用方法并生成结果列表

时间:2013-12-19 15:04:23

标签: java generics functional-programming

我有一个通用方法(称为map),它采用Funcn类型的参数,其中Funcn<K,T>是一个只有一个方法T eval(K)的接口, K并返回T。在map中,我需要使用Funcn来迭代List<K>(在main方法中定义 - 我在{{1}中使用this访问它)并将map应用于列表中的每个元素。最后,应返回Funcn的所有结果的新List<T>。但是,我不确定如何做到这一点,因为我不熟悉泛型的语法。

eval

2 个答案:

答案 0 :(得分:1)

代码中的问题

在您的代码中,接口Funcn<K,T>声明了一个方法eval,它将K作为参数并返回T。请注意,K是第一个类型参数,T是第二个。

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

在您的map方法的声明中,您已将Funcn的类型参数反转:

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

这意味着fn.eval需要T并返回K。相反,它应为Funcn<K,T> fn,以便fn.eval获取K并返回T。这将解释您在评论中提到的错误消息:“类型apply(K)中的方法Function<T,K>不适用于参数(T)”(这不能解释您为什么这么做'但是,我在一个地方获得了Function而在另一个地方获得了Funcn。您是否向我们展示了真实的代码?)

一般

交换这些参数的顺序将解决您的直接问题,但通常类型参数有点复杂。让这些泛型函数的输入和输出类型完全正确是很棘手的,因为你应该能够映射一个函数,该函数需要一个类型为C的参数,而该列表的元素属于C的子类型。代码中的注释解释了这个更详细一点。

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

输出结果为:

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

(可选)例如初始值设定项

的情况

顺便说一句,我发现其中一些功能问题是在对象中使用实例初始化块的好地方。虽然上面的实现创建了results列表,然后填充,然后返回它,您也可以将map的主体设为:

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

我有点喜欢,虽然您会收到关于新(匿名)类没有序列化ID这一事实的警告,因此您需要@SuppressWarnings("serial") map {{1}} 1}}方法,或在类中添加ID。不过,那些可能并不那么令人满意。不过,正如Efficiency of Java "Double Brace Initialization"?中讨论的那样,这类对象也存在其他问题。

答案 1 :(得分:0)

我假设T是 Funcn 的输入,K是它的返回类型。然后它必须返回一个K列表才能工作,否则 Funcn 的通用签名就没用了。

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