我们来思考一个题目,有一个 List List list = new ArrayList(1, 2, 3, 4, 5)
,我想实现这样的一个功能,把 list 里面的每个元素乘以 2 ,用 Java 怎么写?
import java.util.ArrayList;
public class ListDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
ArrayList<Integer> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
result.add(list.get(i) * 2);
}
}
}
如果我现在不想乘以 2 ,我想将逻辑做一下变化,现在不乘以 2 了,要加 3 ,那怎么办?
import java.util.ArrayList;
public class ListDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
ArrayList<Integer> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
result.add(list.get(i) * 2);
}
ArrayList<Integer> result1 = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
result1.add(list.get(i) * 3);
}
}
}
如果这么写,很多的代码是一样的,只有一个地方不一样。从 list 中拿出来的一个元素,做映射,转变成另外一个元素,然后设置到新的 list 中去,中间的这个映射逻辑我们没搞定,为了提高这个代码的通用性,我们应该怎么做?我希望以后不管是什么逻辑,不用写重复的这一堆代码。
sc
.textfile("hdfs://hadoop277ha/spark/wc/input/words.txt")
.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.saveAsTextFile("hdfs://hadoop277ha/spark/wc/output")
在 Scala 的 WordCount 程序里面有这样一段代码,这个 map 的工作就是你给我一个单词,我把单词变成单词对返回。这个里面的逻辑 map 做了什么?就是上一个结果集当中每次拿出来一个元素,然后交给这个逻辑,这个逻辑最终把那个元素变成了一个单词对,这就是所谓的映射。我们能不能也去做这个事儿呢?
public class ListMapReduce {
// list.map(x => x+1)
// map 方法有吗? x=>x+1 计算逻辑,方法,Java 当中,方法里面能传方法作为参数吗?
private List list;
public List map() {
ArrayList<Integer> newResult = new ArrayList<>();
return newResult;
}
}
list.map
Java 中 list 没有这个方法,我想实现这个功能,给我一个 x ,我帮你把 x 加 1 ,这里面有两个难点,List 当中没有 map 方法,x 变成 x+1 是个计算逻辑,我们可以认为在 Java 中定义计算逻辑的东西是一个方法。
假设有 map 方法,但是在 Java 当中,方法里面能传方法作为参数吗?也不能。但是我们有方式可以去实现,既然没有这个 map 方法给 list 直接去调用,但是我可以定义一个 map 方法作用在这个 list 之上,我们有个成员变量 list 存数据,跟 map 方法是平级的,这个 map 方法可以操作这个 list 。
另外,x 变成 x+1 是个计算逻辑,map 方法需要一个参数,这个参数该怎么定义呢,不能往里面传一个方法,但我们可以把这个逻辑放在某个对象里面,传这个对象,我往 map 里面传一个对象,这个对象里面有一个方法,这个方法就是我想要的计算逻辑。这个对象现在是什么我可能不太知道,但是我希望它里面有一个方法能够帮我去执行计算逻辑,这肯定是一个映射。
import java.util.ArrayList;
import java.util.List;
public class ListMapReduce {
// list.map(x => x+1)
// map 方法有吗? x=>x+1 计算逻辑,方法,Java 当中,方法里面能传方法作为参数吗?
private List<Integer> list;
public List map(Operator o) {
ArrayList<Integer> newResult = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
int result = o.f(list.get(i));
newResult.add(result);
}
return newResult;
}
}
interface Operator {
int f(int a);
}
这个方法具不具备通用性呢?我们做一个测试,我们看 result1 这个结果,我针对 listMapReduce 做了一个 map 操作,里面 map 的具体实现是在遍历成员变量 list ,然后把每个元素拿出来,用 o 对象当中的 f 方法,做了映射,你把每个元素给我,我给你反馈一个结果,map 是做了一个映射,具体的逻辑怎么做映射,传给 f 的每个元素到底变成什么结果呢?我们在 Operator 接口的方法中实现,这变得非常灵活,我可以加 1 ,可以乘 2 ,无论如何 map 的逻辑不用动,调用逻辑不用动,只需要在 f 里面定义方法,定义具体的实现逻辑,这个方法里面已经写好了一部分,如参数是什么,返回值是什么,需要自己定一个逻辑,然后得到一个返回值。
import java.util.ArrayList;
import java.util.List;
public class ListMapReduce {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
ListMapReduce listMapReduce = new ListMapReduce();
listMapReduce.setList(list);
List result1 = listMapReduce.map(new Operator() {
@Override
public int f(int a) {
return a + 1;
}
});
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
// list.map(x => x+1)
// map 方法有吗? x=>x+1 计算逻辑,方法,Java 当中,方法里面能传方法作为参数吗?
private List<Integer> list;
public List map(Operator o) {
ArrayList<Integer> newResult = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
int result = o.f(list.get(i));
newResult.add(result);
}
return newResult;
}
}
interface Operator {
int f(int a);
}
map 做了什么呢?你给我一个值,这个值从成员变量 list 当中遍历得来,我通过 f 的逻辑,帮你把这个值变成另外一个值,这个值放到最终要返回的 newResult 里面去,这里 new 了一个 newResult ,去装每一次映射得到的结果,把这个 newResult 返回。map 方法在 ListMapReduce 里面,把 ListMapReduce 中的成员变量 list 转成新的List newResult/result1 ,具体怎么转的,映射的逻辑在我们 o 对象的方法 f 里面定义,我们在用的时候,要用什么逻辑,就临时性的创建一个临时接口,创建一个匿名对象。
这个计算逻辑,在 Java 当中,只能够定义方法,那怎么能够让方法传到一个方法里面去呢?在 Java 当中不支持这个语法,我们可以把一个方法定义在一个接口当中,未来我们在用的时候,想要实现什么样的功能,随时随地传一个包含该方法实现的一个对象就可以了。
按照这个思路,我们还可以实现 reduce 。
import java.util.ArrayList;
import java.util.List;
public class ListMapReduce {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
ListMapReduce listMapReduce = new ListMapReduce();
listMapReduce.setList(list);
// 调用
List result1 = listMapReduce.map(new Operator() {
@Override
public int f(int a) {
return a + 1;
}
});
int result2 = listMapReduce.reduce(new ReduceOperator() {
@Override
public int reduce(int a, int b) {
return a + b;
}
});
System.out.println(result2);
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
// list.map(x => x+1)
// map 方法有吗? x=>x+1 计算逻辑,方法,Java 当中,方法里面能传方法作为参数吗?
private List<Integer> list;
public List map(Operator o) {
ArrayList<Integer> newResult = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
int result = o.f(list.get(i));
newResult.add(result);
}
return newResult;
}
public int reduce(ReduceOperator o) {
// 这个地方需要加判断(空、越界)
int result = list.get(0);
for (int i = 1; i < list.size(); i++) {
result = o.reduce(result, list.get(i));
}
return result;
}
}
interface Operator {
int f(int a);
}
interface ReduceOperator {
int reduce(int a, int b);
}
以上 map 和 reduce 方法的实现,其实是一个设计模式,叫策略模式,就是不同的业务场景用不同的需求实现,就是不同的情况我有不同的策略去针对你。
微信公众号「padluo」,分享数据科学家的自我修养,既然遇见,不如一起成长。关注【数据分析】公众号,后台回复【文章】,获得整理好的【数据分析】文章全集。