1.lambda: 返回指定接口的对象实例;
1.jdk8自带的函数式接口:
2.方法引用:
@Test
public void testMethodApply(){
// 方法引用
Consumer<String> consumer = s -> System.out.println(s);
Consumer<String> consumer1 = System.out::println;
consumer1.accept("我是测试方法应用");
// 静态方法的方法引用
Consumer<Dog> consumer2 = Dog::bark;
Dog dog = new Dog();
consumer2.accept(dog);
// 非静态方法的方法引用
Function<Integer, Integer> function = dog::eat;
log.info("还剩 {} 斤狗粮", function.apply(1));
IntFunction intfunction = dog::eat;
log.info("还剩 {} 斤狗粮", intfunction.apply(2));
UnaryOperator<Integer> unaryOperator = dog::eat;
log.info("还剩 {} 斤狗粮", unaryOperator.apply(4));
// 非静态方法的方法引用--类名引用
BiFunction<Dog, Integer, Integer> biFunction = Dog::eat;
log.info("还剩 {} 斤狗粮", biFunction.apply(dog, 3));
// 构造函数的函数引用
Supplier<Dog> supplier = Dog::new;
log.info("创建了一个对象:{}", supplier.get());
Function<String, Dog> function1 = Dog::new;
log.info("创建了一个带参数对象:{}", function1.apply("旺财"));
}
@Slf4j
class Dog {
private String name = "哮天犬";
private int food = 8;
public Dog() {
}
public Dog(String name) {
this.name = name;
}
public static void bark(Dog dog){
log.info("{} 叫了好几声~", dog.toString());
}
public int eat(Dog this, int num){
log.info("吃掉了{} 斤狗粮", num);
this.food -= num;
return this.food;
}
@Override
public String toString() {
return this.name;
}
}
推荐字节码查看工具:jclasslib bytecode viewer ,查看字节码
java编译器给每个非静态方法新增this指针的原理:给方法新增一个对象实例类型的this参数;例如:
3.类型推断:
@Test
public void testTypeGuess () {
//1.正常书写
IMath math = (x,y)->x-y;
log.info("类型推断:{}", math);
//2.强制转换
Object object = (IMath)(x,y)->x-y;
//3.定义方法
IMath math1 = createSubLambda();
//4.强制转换,否则报错提示方法有两重性
test((IMath) (x, y)->x-y);
test((IMath2) (x, y)->x-y);
}
public void test (IMath iMath) {
}
public void test (IMath2 iMath) {
}
public static IMath createSubLambda(){
return (a,b)->a-b;
}
4.变量引用
java中参数是传值的不传引用,内部类使用外部类的变量时,该变量必须是final的(jdk8虽然可以省略final,但被引用后该变量将不可修改);原理:传值不传引用,避免内外两个变量的值不一致,造成二义性;
5.级联表达式&柯里化
柯里化:把多个参数的函数转为只有一个参数的函数
科里化的目的:函数标准化 (方便统一处理、批量处理)
高阶函数:返回函数的函数
@Test
public void testHighGradeFunc() {
Function<Integer, Function<Integer, Integer>> fun1 = x->y->x+y;
log.info("测试函数级联与科里化:{}", fun1.apply(1).apply(2));
Function<Integer, Function<Integer, Function<Integer, Integer>>> function2 = x->y->z->x+y+z;
log.info("测试函数级联与科里化:{}", function2.apply(1).apply(2).apply(1));
int[] nums = {1,2,4};
Function f = function2;
for (int i = 0; i < nums.length; i++) {
if (f instanceof Function) {
Object f2= f.apply(nums[i]);
if (f2 instanceof Function) {
f= (Function) f2;
} else {
log.info("测试函数级联与科里化,调用结束,结果为:{}", f2);
}
}
}
二.流式编程
1.stream: 是高级的迭代器,不是一个数据结构、不是一个集合、不会存放数据;只关注怎样高效处理数据
2.外部迭代( 之前的for循环操作etc )&内部迭代(stream)
3.短路操作:不需要全部结果都出来在总计,只要中间结果满足要求,就可以拿出来运算;
4.中间操作/终止操作/惰性求值;
中间操作:返回值为流;
终止操作:返回值不是流;
惰性求值:终止操作没被调用之前,中间操作不会被执行;
惰性求值也叫惰性计算、延迟求值,在函数式编程语言中随处可见。可以这样通俗地理解为:**不对给出的表达式立即计算并返回值,而是在这个值需要被用到的时候才会计算。**这个是个人理解
@Test
public void testCreateStream() {
// 1.集合list创建流
List<Integer> list = new ArrayList<>();
list.stream();
list.parallelStream();
// 2.数组创建流
Arrays.stream(new int[]{1,2,3});
// 3.创建数字流
IntStream.of(1,2,3);
IntStream.rangeClosed(1,10);
// 4.random创建无限流
Random random = new Random();
random.ints().limit(100);
// 5.自己产生流
Stream.generate(()-> random.nextInt()).limit(100);
}
重要:
中间操作(有状态操作、无状态操作):都返回stream;
有状态操作:当前操作的操作结果与其他元素有依赖关系
无状态操作:当前操作被执行前后与其他元素无依赖关系
@Test
public void testOperationStream () {
String str = "My name is 007";
// 打印每个单词的长度
Stream.of(str.split(" ")).filter(s-> s.length()>2).map(s->s.length()).forEach(System.out::println);
Stream.of(str.split(" ")).flatMap(s-> s.chars().boxed()).forEach(System.out::println);
Stream.of(str.split(" ")).flatMap(s-> s.chars().boxed()).forEach(i-> {
System.out.println((char) i.intValue());
});
// peek
Stream.of(str.split(" ")).peek(System.out::println).forEach(s -> System.out.println(s));
//limit
new Random().ints().limit(10).forEach(System.out::println);
new Random().ints().filter(i-> i>0 && i<1000).limit(10).forEach(System.out::println);
}
flatMap: A对象有B属性(B是个集合),得到A对象中所有的B属性(集合)
6.终止操作:
短路操作:不需等待所有结果都计算完,就可以结束这个流的操作;
带order的都是和并行流相关的;
reduce:减少,把流合成一个数据;
7.并行流
@Test
public void testParallelStream() {
// 改变系统的线程数
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","10");
// 并行流
//IntStream.range(1, 100).parallel().forEachOrdered(TestStream::debug);
// 串行流
//IntStream.range(1, 100).parallel().forEach(TestStream::debug);
//IntStream.range(1, 100).peek(TestStream::debug).count();// 无效代码
//log.info("测试并行流总计:{}", count);
// 不实用默认的线程池,防止任务被阻塞,自定义线程池
ForkJoinPool forkJoinPool = new ForkJoinPool(20);
forkJoinPool.submit(
()-> {
//IntStream.range(1, 100).parallel().peek(TestStream::debug).count(); // 不奏效
IntStream.range(1, 100).parallel().forEach(TestStream::debug); // 不奏效
}
);
forkJoinPool.shutdown();
// 由于是守护线程,主线程会自动退出,不打印任何东西,故需要主线程等待一下;
synchronized (forkJoinPool) {
try {
forkJoinPool.wait();// 调用等待防止线程自动退出
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 在一个流操作中,先并行,再串行,不能同时满足,只会以最后一次调用为准;
// 多次调用 parellel/sequential, 以最后一次为准
IntStream.range(1, 100)
.parallel().peek(TestStream::debug)
.sequential().peek(TestStream::debug)
.count();
}
8.收集器: 把流处理后的数据收集起来,处理为list/set/map、字符串拼接、处理为一个数据;
@Test
public void testCollector(){
List<Student> students = Arrays.asList(
new Student("peter1", 11, Gender.MALE, Grade.ONE),
new Student("peter12", 51, Gender.FEMALE, Grade.THREE),
new Student("peter3", 41, Gender.MALE, Grade.TWO),
new Student("peter4", 31, Gender.FEMALE, Grade.FORE),
new Student("peter5", 21, Gender.MALE, Grade.ONE),
new Student("peter6", 11, Gender.FEMALE, Grade.TWO),
new Student("peter7", 1, Gender.MALE, Grade.THREE),
new Student("peter8", 113, Gender.FEMALE, Grade.TWO),
new Student("peter9", 11, Gender.MALE, Grade.ONE),
new Student("peter10", 12, Gender.FEMALE, Grade.TWO),
new Student("peter11", 13, Gender.MALE, Grade.ONE)
);
List<Integer> collect = students.stream().map(student -> student.getAge()).collect(Collectors.toList());
log.info("测试收集器:{}", collect);
// Student::getAge 可以少生成一个lambda$0 函数,性能比:student -> student.getAge() 稍微好些,
List<Integer> collect1 = students.stream().map(Student::getAge).collect(Collectors.toList());
log.info("测试收集器使用方法引用:{}", collect1);
TreeSet<Integer> collect2 = students.stream().map(Student::getAge).collect(Collectors.toCollection(TreeSet::new));
log.info("测试收集器使用方法引用TreeSet:{}", collect2);// 排序且去重
// 统计汇总信息
IntSummaryStatistics collect3 = students.stream().collect(Collectors.summarizingInt(Student::getAge));
log.info("测试统计年龄汇总信息:{}", collect3);
// 分块
Map<Boolean, List<Student>> collect5 = students.stream().collect(Collectors.partitioningBy(student -> student.getGender() == Gender.MALE));
MapUtils.verbosePrint(System.out, "男女参列表", collect5);
// 分组
Map<Grade, Long> collect4 = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
MapUtils.verbosePrint(System.out, "班级分组列表", collect4);
}
enum Gender {
MALE,
FEMALE
}
enum Grade {
ONE,
TWO,
THREE,
FORE
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student {
private String name;
private int age;
private Gender gender;
private Grade grade;
}
9.stream的运行机制
1.所有操作是链式调用,一个元素只迭代一次;
2.每个中间操作返回一个新的流,流里面有属性:sourceStage,指向同一个地方:Head
3.Head->nextStage->nextStage->......->null;
4.有状态操作会把无状态操作截断,单独处理;
5.并行环境下,有状态的中间操作不一定可以并行操作;
6. parallel/sequetial 都是中间操作,但是他们不创建流,只修改Head的并行标准;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.stream.Stream;
/**
* @Description:验证流的运行机制
* @Author: NGQ
* @Date: 2021/6/3 11:08
*/
@Slf4j
public class RunStreamPrinciple {
public static void main(String[] args) {
Random random = new Random();
// 随机产生数据
//Stream.generate(random::nextInt)
// .limit(200) //产生200个,无限流需要短路操作
// .peek(i -> print("peek: "+ i)) // 第一个无状态操作
// .filter(i -> { // 第二个无状态操作
// print("filter: "+i);
// return i>1000000;
// })
// .count();
Stream.generate(random::nextInt)
.limit(200)
.peek(i -> {
print("peek: " + i);
})
.filter(i -> {
print("filter: " + i);
return i > 1000000;
})
.sorted((o1, o2) -> {
print("sorted: " + o1 + ", " + o2);
return o1.compareTo(o2);
})
.peek(i -> print("peek2: " + i))
.parallel()
.count();
}
public static void print(String s) {
log.info(Thread.currentThread().getName() + "==>" +s);
}
}
小结:
三. JDK9 Reactive Stream
基于发布/订阅 模式的数据处理的规范;又叫:flow API
背压:发布者与订阅者之间的互动调节机制;类比水龙头(发布者与订阅者之间有交流信息)
解决订阅者被动接受到的消息太多的问题,
Reactive Streams:一种支持背压的异步数据流处理标准,主流实现有RxJava和Reactor,Spring WebFlux默认集成的是Reactor。 Reactive Streams主要解决背压(back-pressure)问题。当传入的任务速率大于系统处理能力时,数据处理将会对未处理数据产生一个缓冲区。 背压依我的理解来说,是指订阅者能和发布者交互(通过代码里面的调用request和cancel方法交互),可以调节发布者发布数据的速率,解决把订阅者压垮的问题。关键在于上面例子里面的订阅关系Subscription这个接口,他有request和cancel 2个方法,用于通知发布者需要数据和通知发布者不再接受数据。 我们重点理解背压在jdk9里面是如何实现的。关键在于发布者Publisher的实现类SubmissionPublisher的submit方法是block方法。订阅者会有一个缓冲池,默认为Flow.defaultBufferSize() = 256。当订阅者的缓冲池满了之后,发布者调用submit方法发布数据就会被阻塞,发布者就会停(慢)下来;订阅者消费了数据之后(调用Subscription.request方法),缓冲池有位置了,submit方法就会继续执行下去,就是通过这样的机制,实现了调节发布者发布数据的速率,消费得快,生成就快,消费得慢,发布者就会被阻塞,当然就会慢下来了。
普通案例:
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Flow; import java.util.concurrent.SubmissionPublisher; /** * @Description: * @Author: NGQ * @Date: 2021/6/3 13:50 */ @Slf4j public class FlowDemo { public static void main(String[] args) throws InterruptedException { SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>(); Flow.Subscriber<Integer> subscriber = new Flow.Subscriber<>() { private Flow.Subscription subscription; @Override public void onSubscribe(Flow.Subscription subscription) { this.subscription = subscription; this.subscription.request(1); } @Override public void onNext(Integer item) { // 此处调节发布和订阅的速率 》》》》此处是响应式编程中的关键 log.info("测试接收到的数据-模拟处理数据:{}", item); this.subscription.request(1); //this.subscription.cancel(); } @Override public void onError(Throwable throwable) { throwable.printStackTrace(); this.subscription.cancel(); } @Override public void onComplete() { // publisher.close(); 被调用时触发此处 log.info("处理完了>发布者关闭了~"); } }; publisher.subscribe(subscriber); Integer data = 111; publisher.submit(data); publisher.submit(222); publisher.submit(333); publisher.submit(555); publisher.close(); // 防止主线程延迟停止,否则数据没消费就停止 Thread.currentThread().join(1000); } }
带processor案例:
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Flow; import java.util.concurrent.SubmissionPublisher; /** * @Description: * @Author: NGQ * @Date: 2021/6/3 14:18 */ @Slf4j public class MyProcessor extends SubmissionPublisher<String> implements Flow.Processor<Integer, String> { private Flow.Subscription subscription; @Override public void onSubscribe(Flow.Subscription subscription) { this.subscription = subscription; this.subscription.request(1); } @Override public void onNext(Integer item) { log.info("处理器中接收到的数据:{}", item); if (item>0) { this.submit("转换后的数据:"+item); } this.subscription.request(1); } @Override public void onError(Throwable throwable) { throwable.printStackTrace(); this.subscription.cancel(); } @Override public void onComplete() { // 注意 此处如果不关闭processor,则subscribeer无法关闭; log.info("processor 关闭发布者~"); this.close(); } } @Slf4j class FlowDemo2{ public static void main(String[] args) throws InterruptedException { SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>(); MyProcessor myProcessor = new MyProcessor(); publisher.subscribe(myProcessor); Flow.Subscriber<String> subscriber = new Flow.Subscriber<>() { private Flow.Subscription subscription; @Override public void onSubscribe(Flow.Subscription subscription) { this.subscription = subscription; this.subscription.request(1); } @Override public void onNext(String item) { log.info("发布者接收的数据:{}", item); this.subscription.request(1); } @Override public void onError(Throwable throwable) { throwable.printStackTrace(); this.subscription.cancel(); } @Override public void onComplete() { log.info("subscriber 处理完毕~"); } }; myProcessor.subscribe(subscriber); publisher.submit(-111); publisher.submit(222); publisher.close(); Thread.currentThread().join(1000); } }