Lambda,响应式编程从0到1
前沿
随着jdk1.8的流行广泛的使用和流行,这里面的lambda语法也变的火了起来,使用lambda大大简化了代码的编写,而且让编码也变的美化了起来。这个格式不是必须的,但是大多数人在用,你不用你就out了。你能多用绝对加分项,这个算是查漏补缺,如果你需要也可以借鉴一下。
初体验
这里主要是做个对比,和之前的不用lambda表达式,接下来的所有的方式实现的东西是一样的
准备
1.接口Factory
public interface Factory {
public Worker getWorker();
}
2.实体类Worker
package com.duncan.demo;
public class Worker {
private int id;
private String name;
public Worker() {
}
public Worker(int id,String name) {
this.id = id;
this.name = name;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return " Worker{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
3.接口实现(主要是为了实例化调用用的)
package com.duncan.demo;
public class WorkerImpl implements Factory{
@Override
public Worker getWorker() {
return new Worker();
}
}
对比测试
- 接口实例化调用
/**
* 1. 具体的类实现
*/
Factory factory = new WorkerImpl();
System.out.println("具体类的实现: " + factory.getWorker());
- 内部类实现
/**
* 2. 内部类的实现
*/
Factory factory1 = new Factory() {
@Override
public Worker getWorker() {
return new Worker(1, "sun");
}
};
System.out.println("内部类的实现: " + factory1.getWorker());
- Lambda
/**
* 3. lambda的实现
*/
Factory factory2 = () -> {
return new Worker(2, "xu");
};
System.out.println("lambda的实现: " + factory2.getWorker());
结果
初步观察总结
- 从上面的代码可以看出这个lambda必须要有一个接口定义,
- 实际上可以看出它就是一个实现类,这个可以当作参数传递,和结果传递
- 这个接口对外开放的函数就只有一个,因为这边没有办法写两个()
语法
- 基本语法
(param...) -> {statement;}
- 特殊的情况
// 1.假如只有一个参数的情况可以省略()
msg -> {System.out.println(msg);}
// 2. 假如只有一个语句,那可以省略 {}
msg -> System.out.println(msg);
// 3. 如果是简单的表达式可以省略return
(int a, int b) -> a + b;
// 4. 参数也可以省略类型
(a, b) -> a + b;
// 为什么这边有的没有类型也行,不是说java是强类型的语言吗?其实这边并没有违背这个,这边因为lambda必须要去实现interface,所以参数的类型是可以根据interface中定义的来的
- 注解
@FunctionalInterface
// 这个注解表明这个接口是函数式接口,也可以用来判断自己的函数式接口写的是否正确
常见函数式编程练习
Runnable
这个是常见的多线程定义的方式,实现的接口是 run
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "开始了~");
}).start();
Callable
这个是另外的多线程实现的方式,实现的接口是 call
Callable<Object> callable = ()->{
// 这边在做测试
return null;
};
Supplier
这个接口其实主要是定义了一个规范,可以由名字看出,这里的Supplier实现的是一个提供某一个规则的数据,实现的是get
具体的实例: 找出数组中的最小值 (lambda),作为值传递
int[] numbers = {98,9,8,0,7,6,0,92};
int min = getMin(() -> {
int temp = numbers[0];
for(int num : numbers) {
if (num < temp) {
temp = num;
}
}
return temp;
});
System.out.println(min);
public static int getMin(Supplier<Integer> supplier) {
return supplier.get();
}
Consumer
这个接口和上面的Supplier是相对应的,主要用来接受数据,然后做处理,实现的是accept
calConsumer((a) -> {
System.out.println(a);
}, 2);
int b = 1;
calConsumerAndThen((a) -> {
System.out.println("then1");
},(a) -> {
System.out.println("then2");
}, b);
public static void calConsumer(Consumer<Integer> a, int c) {
a.accept(c);
}
public static void calConsumerAndThen(Consumer<Integer> a, Consumer<Integer> b, int c) {
a.andThen(b).accept(c);
}
Comparator
这个是比较常见的比较函数,具体用法如下,实现的是compare
Comparator<String> c = (a, b) -> {
return a.length() - b.length();
};
String[] strs = {"abc","asdasdas","ss","s"};
Arrays.sort(strs, c);
System.out.println(Arrays.toString(strs));
Predicate
这个是断言的接口,主要可以用来判断两个对象的关系,与或非, 实现的是test
Predicate<String> predicate = (name) -> {
return name.contains("sun");
};
System.out.println(predicate.test("sunmaoyun")); // true
System.out.println(predicate.and((String name) -> {
return name.equals("s");
}).test("sunmaoyun")); // false
System.out.println(predicate.or((String name) -> {
return name.equals("s");
}).test("sunmaoyun")); // true
System.out.println(predicate.negate().test("sunmaoyun")); // false
Function
这个是功能函数,每个实现类可以实现一个功能. 实现的是 apply
Function<Integer,Integer> function = (str) -> {
return str + 1;
};
System.out.println(function.apply(1));
方法引用
从上面的体验我们可以看出来lambda是为了简化内部类的实现,那么方法引用就是为了简化lambda
假如你的lambda的对象要实现的方法在别的类已经实现了,可以直接用别人实现好的
举个简单的例子,使用方法:
- Object::method
- 构造方法 Object::new
- 本类的普通方法: this::method, 父类的普通方法: super::method
这边注意参数要相同,返回结果兼容
// main函数中实现的是同一个东西,打印的结果都是Duncan
public class TestDemo {
public static void main(String[] args) {
printInfoByLambda((str) -> {
System.out.println(str);
});
printInfoByLambda(System.out::println);
printInfoByLambda(Person::new);
printInfoByLambda(Person::print);
// 普通方法需要先实例化
Person person = new Person();
printInfoByLambda(person::printInfo);
}
public static void printInfoByLambda(PrintInfo b) {
b.print("Duncan");
}
}
class Person {
public Person() {
}
private String name;
public Person(String name) {
this.name = name;
System.out.println(name);
}
public static void print(String name) {
System.out.println(name);
}
public void printInfo(String name) {
System.out.println(name);
}
}
interface PrintInfo {
public void print(String str);
}
Stream流
初体验
这个其实是把数据集合做了一些优化,不仅使代码的可读性变强,其效率也变高了
举个例子
/**
* 这边主要是来使用1.8的stream流,这个比普通的效率更高
* 找出姓孙的人
* 且名字的长度等于3
*/
List<String> list = new ArrayList<>();
list.add("张三");
list.add("赵四");
list.add("孙悟空");
list.add("齐天大圣");
list.add("孙悟饭");
list.add("孙悟天");
list.add("孙俪");
/**
* 如果说是常用的方法估计就是 for()
*/
/**
* jdk8 可以使用流来实现
*/
list.stream().filter(name->name.startsWith("孙"))
.filter(name -> name.length() == 3)
.forEach(System.out::println);
结果
Stream的生命周期
啥叫做中间操作呢?
先说一下这个流的生命周期
开始->中间->结束 (利用的聚合的思想)
举例:stream().filter(// 断言接口).forEach(// Consumber接口)
在这个里面filter属于中间操作,forEach()属于结束操作,如果没有结束操作的话,这个实际上是没有做操作的
Stream中间操作常用的api
- map(mapToInt,flatMap)
- filter
- limit
- skip
- concat
Stream结束(终结操作)
- forEach
- count
- collect
- min
- max
简单的使用
如果遇到集合的话,可以建议你使用这种流的方式
- 获取流
/**
* 数组获取 用 Stream.of
*/
Integer[] numbers = {1,2,3};
Stream<Integer> stream1 = Stream.of(numbers);
stream1.forEach(System.out::print);
System.out.println();
/**
* map集合获取
*/
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
// 1. 通过key获取
Stream<String> stream2 = map.keySet().stream();
stream2.forEach(System.out::print);
System.out.println();
// 2. 通过value获取
Stream<Integer> stream3 = map.values().stream();
stream3.forEach(System.out::print);
System.out.println();
// 3. 通过Entry获取
Stream<Map.Entry<String, Integer>> stream4 = map.entrySet().stream();
stream4.forEach(System.out::print);
- map的使用,这个map其实实现的接口是 Function的接口
// 比如这边可以实现一个加密的操作,将数字转化为相应的小写字母,超过26的数字,用取余作映射
Integer[] numbers = {1,2,26, 3,28,29, 23, 30, 10, 27};
Stream<Integer> stream1 = Stream.of(numbers);
stream1.map((a) -> {
return (char)('a' - 1 + ((a % 26) == 0 ? 26 : a % 26));
}).forEach((str) -> {
System.out.print(str + " ");
});
// 结果
a b z c b c w d j a
3.collect: 这个可以是操作这个集合然后再返回一个集合
/**
* 找出这个数组字符串中男的元素
* 并且按照 name -> age的方式存到一个map中
*/
String[] strs = {"sun,男,26", "xu,女,24", "zhao,男,89", "qian,女,18"};
Stream<String> stream5 = Stream.of(strs);
Map<String, Integer> mapChange = stream5.filter(str -> str.split(",")[1].equals("男"))
.collect(Collectors.toMap(
k -> k.split(",")[0],
v -> Integer.valueOf(v.split(",")[2])
)
);
mapChange.forEach((k,v) -> {
System.out.println(k + "->" + v);
});
4.skip,这个是跳过前面几个元素
Integer[] numbers = {1,2,26, 3,28,29, 23, 30, 10, 27};
Stream<Integer> stream1 = Stream.of(numbers);
stream1.skip(6).forEach(str -> System.out.print(str + " "));
// 结果
23 30 10 27
5.concat 连接2个流
List<String> list = new ArrayList<>();
list.add("张三");
list.add("赵四");
List<String> list1 = new ArrayList<>();
list1.add("孙悟空");
list1.add("齐天大圣");
list1.add("孙悟饭");
Stream.concat(list.stream(), list1.stream()).forEach(str -> System.out.print(str + " "));
最后的话
以上就是lambda的常见用法和应用,弄完以后其实也发现了代码的魅力,有的人写的代码就是好看,我们需要往这些人靠齐。
那什么时候用最好呢?
- 如果你的集合数据量特别大,然后还要做一些操作
- 如果定义的接口只有一个抽象方法的时候,可以使用,简化代码
最后一句,再好的东西只有自己多用了,熟练了才是好的。