1.写list的高效for循环时.可以list.for 有自动提示
2.写lambda表达式时 Collections.sort(list,(Person o1,Person o2)->{
return o1.getAge() - o2.getAge();
}); 时,鼠标在o2.getAge()后面.return 自动显示
3.数组也可以遍历 数组.for
4.:: 方法引用
5.
创建一个空的maven项目
创建一个新的线程,指定线程要执行的任务
package cn.tedu.jdk.lambda;
public class Demo01Lambda {
public static void main(String[] args) {
//开启一个新的线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中执行的代码:"+Thread.currentThread().getName());
}
}).start();
System.out.println("主线程中的代码:"+Thread.currentThread().getName());
}
}
代码分析:
1.Thread类需要一个Runnable接口作为参数.其中的run方法用来指定线程任务内容的核心
2.为了指定run方法,不得需要Runnable接口的实现类
3.为了省去定义一个Runnable实现类.,不得不使用匿名内部类
4.必须覆盖抽象run方法,方法名,方法参数,返回值类型,不得不重写一遍,而且不能出错
5.实际上,我们只关注方法体中的代码
Lambda表达式是一个匿名函数,可以理解为是一段可以传递的代码,
new Thread(()->{ System.out.println("新线程Lambda表达式..."+Thread.currentThread().getName()); })
.start();
lambda表达式的优点:简化了匿名内部类的使用,语法更加简单
匿名内部类语法冗余,发现lambda表达式是简化匿名内部类的使用
lambda省去了面向对象的条条框框,lambda语法规则有3部分组成
(参数类型 参数名称): 参数列表
{代码体}: 方法体
-> 箭头 分割参数列表和方法体
lambda练习1
无参无返回值的lambda
定义一个接口,
package cn.tedu.jdk.lambda.service;
public interface UserService {
void show(); //无参无返回值
}
然后创建主方法使用
package cn.tedu.jdk.lambda;
import cn.tedu.jdk.lambda.service.UserService;
public class Demo03Lambda {
public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
System.out.println("show 方法执行了...");
}
});
System.out.println("-----------------");
goShow(()->{
System.out.println("lambda show方法 执行了...");
});
}
public static void goShow(UserService userService){
userService.show();
}
}
输出:
show 方法执行了... ----------------- lambda show方法 执行了...lambda练习2
完成一个有参有返回值的lambda案例
创建一个Person对象
package cn.tedu.jdk.lambda.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Integer height;
}
然后我们在main方法中,.List集合中保存多个person对象,然后对这些对象做根据age排序操作
package cn.tedu.jdk.lambda;
import cn.tedu.jdk.lambda.domain.Person;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Demo04Lambda {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Person("周杰伦", 33, 175));
list.add(new Person("刘德华", 50, 185));
list.add(new Person("许攸", 46, 165));
list.add(new Person("曹操", 30, 169));
// Collections.sort(list, new Comparator() {
// public int compare(Person o1, Person o2) {
// return o1.getAge() - o2.getAge();
// }
// });
// for (Person person : list) {
// System.out.println(person);
// }
System.out.println("------------------");
Collections.sort(list,(Person o1,Person o2)->{
return o1.getAge() - o2.getAge();
});
for (Person person : list) {
System.out.println(person);
}
}
}
我们发现在sort方法的第二个参数是一个Comparator接口的匿名内部类,且执行的方法有参数和返回值,我们可以改写为lambda表达式
输出结果
------------------ Person(name=曹操, age=30, height=169) Person(name=周杰伦, age=33, height=175) Person(name=许攸, age=46, height=165) Person(name=刘德华, age=50, height=185)4.@FunctionalInterface注解
使用lambda时,这个接口只能保证有一个抽象方法
package cn.tedu.jdk.lambda.service;
@FunctionalInterface
public interface UserService {
void show();
}
5.Lambda表达式的原理
lambda中的代码是在这个方法里面执行的
利用JDK自带工具javap 对字节码进行反汇编操作
javap -c -p XXX.class文件
-c 表示对代码进行反汇编
-p 显示所有的类和成员
在这个反编译的源码中我们看到了一个静态方法 lambda$main$0()
小结:匿名内部类在反编译时.会产生一个class文件
lambda表达式在程序运行时会形成一个类
1.在类中会新增一个static方法,该方法体就是lambda表达式的代码
2.还会形成一个匿名内部类,实现接口,重写抽象方法
3.在接口中重写方法,调用新生成的方法
在lambda表达式标准写法基础上,可以省略的语法规则为:
1.小括号内的参数类型可以省略
2.如果小括号内有且仅有一个参数,则小括号可以省略
3.如果大括号内有且仅有一条语句,则可以同时省略大括号,return关键字及语句分号
package cn.tedu.jdk.lambda;
import cn.tedu.jdk.lambda.service.OrderService;
import cn.tedu.jdk.lambda.service.StudentService;
public class Demo05Lambda {
public static void main(String[] args) {
goStudent((String name, Integer age) -> {
return name + age + "6666...";
});
//省略写法
goStudent((name,age)->name + age + "6666...");
System.out.println("--------------");
goOrder((String name) -> {
System.out.println("---->"+name);
return 666;
});
//省略写法
goOrder(name ->666);
}
public static void goStudent(StudentService studentService) {
studentService.show("张三", 22);
}
public static void goOrder(OrderService orderService) {
orderService.show("李四");
}
}
7.Lambda表达式使用前提
lambda使用时有几个条件特别注意:
1.方法的参数或局部变量的类型必须为接口才能使用lambda
2.接口中有且仅有一个抽象方法 @FunctionalInterface
1.所需类型不同:
匿名内部类的类可以类,抽象类.接口,
lambda表达式需要的类型必须为接口
2.抽象方法的数量不一样
匿名内部类所需的接口中的抽象方法数量是随意的
lambda表达式所需的接口中只能有一个抽象方法
3.实现原理不一样
匿名内部类是在编译后生成的class
lambda表达式是在程序运行时动态生成class
JDK8中针对接口有做新增,在JDK8之前
interface 接口名{
静态常量;
抽象方法;
}
在JDK8后,对接口做了增强,接口中可以有默认方法 和 静态方法
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}
默认方法
为什么要增加默认方法?
在JDK8以前,接口中只能有抽象方法和静态常量.会存在以下问题:
如果接口中新增抽象方法,那么实现类都必须抽象这个抽象方法.非常不利于接口的扩展
package cn.tedu.jdk.inter;
public interface Demo01Interface {
public static void main(String[] args) {
A b=new B();
A c=new C();
}
}
interface A{
void test1();
//接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
void test2();
}
class B implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
class C implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
接口中默认方法的格式?
接口中默认方法的语法格式
interface 接口名{
修饰符 default 返回值类型 方法名(){方法体;}
}
package cn.tedu.jdk.inter;
public interface Demo01Interface {
public static void main(String[] args) {
A b=new B();
b.test3();
A c=new C();
}
}
interface A{
void test1();
//接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
void test2();
public default String test3(){
System.out.println("接口中的默认方法执行了...");
return "hello";
}
}
class B implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
@Override
public String test3() {
System.out.println("B 实现类重写了默认方法");
return A.super.test3();
}
}
class C implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
接口中默认方法的使用
接口中默认方法的使用有2种方式
1.实现类直接调用接口中的默认方法
2.实现类重写接口的默认方法
JDK8中为接口新增了静态方法,作用也是为了接口的扩展
语法规则:
interface 接口名{
修饰符 static 返回值类型 方法名(){
方法体;
}
}
代码:
package cn.tedu.jdk.inter;
public interface Demo01Interface {
public static void main(String[] args) {
A b = new B();
b.test3();
A c = new C();
A.test4();
}
}
interface A {
void test1();
//接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
void test2();
public default String test3() {
System.out.println("接口中的默认方法执行了...");
return "hello";
}
public static String test4() {
System.out.println("接口中的静态方法...");
return "hello";
}
}
class B implements A {
@Override
public void test1() {
}
@Override
public void test2() {
}
// @Override
// public String test3() {
// System.out.println("B 实现类重写了默认方法");
// return A.super.test3();
// }
}
class C implements A {
@Override
public void test1() {
}
@Override
public void test2() {
}
}
静态方法的使用
接口中的静态方法是不能被重写的,调用的话,只能通过接口类型来实现, 接口名.方法名
如果扩展的内容支持重写用默认方法default,否则用static
- 默认方法通过实例调用,静态方法通过接口名调用
- 默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
- 静态方法不能被继承,实现类不能重写接口中的静态方法,只能通过接口名调用
使用lambda表达式的前提是需要有函数式接口. 而使用lambda表达式不关心接口名,抽象方法名,
只关心抽象抽象方法的参数列表和返回值类型
package cn.tedu.jdk.fun;
public class Demo01Fun {
public static void main(String[] args) {
fun1(arr -> {
int sum=0;
for (int i : arr) {
sum+=i;
}
return sum;
});
}
public static void fun1(Operator operator) {
int arr[]={1,2,3,4};
int sum=operator.getSum(arr);
System.out.println("sum="+sum);
}
}
@FunctionalInterface
interface Operator{
int getSum(int[] arr);
}
2.函数式接口的由介绍
在JDK8后函数式接口.package java.util.function;包下,
Supplier接口, 无参有返回值方法 T get(); //发音 撒破裂
Consumer接口.有参无返回值的方法 void accept(T t);
Predicate接口, 有参有返回值的方法 boolean test(T t); //发音 破瑞dit
无参有返回值的接口,对于lambda表达式需要提供一个返回数据类型
Supplier 是用来生产数据的
@FunctionalInterface public interface Supplier{ T get(); }
代码:
package cn.tedu.jdk.fun;
import java.util.Arrays;
import java.util.function.Supplier;
public class SupplierTest {
public static void main(String[] args) {
fun1(()-> {
int[] arr = {22, 66, 10, 5, 4, 77, 30};
//计算出数组的最大值
Arrays.sort(arr);
return arr[arr.length - 1];
});
}
private static void fun1(Supplier supplier){
//get()是一个无参有返回值的抽象方法
Integer max = supplier.get();
System.out.println("max="+max);
}
}
Consumer接口
有参无返回值的接口,是用来消费数据的
Consumer,使用的时候需要指定一个泛型.来定义参数类型
@FunctionalInterface public interface Consumer{ void accept(T t);
使用: 将使用的数据统一转换为小写,输出
package cn.tedu.jdk;
import java.util.function.Consumer;
public class ConsumerTest {
public static void main(String[] args) {
fun2(msg -> {
System.out.println(msg + "转换为小写:" + msg.toLowerCase());
});
}
public static void fun2(Consumer consumer) {
consumer.accept("Hello World");
}
}
默认方法:
default ConsumerandThen(Consumer super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; }
package cn.tedu.jdk.fun;
import java.util.function.Consumer;
public class ConsumerAndThenTest {
public static void main(String[] args) {
fun3(msg1->{
System.out.println(msg1 + "->转换为小写:" + msg1.toLowerCase());
},msg2->{
System.out.println(msg2 + "->转换为大写:" + msg2.toUpperCase());
});
}
public static void fun3(Consumer c1,Consumer c2) {
String str = "hello world";
// c1.accept(str);//小写
// c2.accept(str);//大写
// c1.andThen(c2).accept(str);
c2.andThen(c1).accept(str);//大->小
}
}
默认方法为andThen
如果一个方法的参数和返回值类型都是Consumer类型,那么就可以实现效果,消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口的default方法andThen方法
default ConsumerFunction接口andThen(Consumer super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; }
有参有返回值的接口,
Function接口是根据一个类型的数据,.得到另一个类型的数据,前者称为前置条件,后者称为后者条件.
@FunctionalInterface public interface Function{ R apply(T t);
使用: 传递一个String,返回一个Integer类型
package cn.tedu.jdk.fun;
import java.util.function.Function;
public class FunctionTest {
public static void main(String[] args) {
fun4(msg->{
return Integer.parseInt(msg);
});
}
public static void fun4(Function function){
Integer apply = function.apply("666");
System.out.println("apply="+apply);
}
}
andThen 方法
package cn.tedu.jdk.fun;
import java.util.function.Function;
public class FunctionAndThenTest {
public static void main(String[] args) {
fun5(msg1->{
return Integer.parseInt(msg1);
},msg2->{
return msg2*10;
});
}
public static void fun5(Function f1,Function f2){
// Integer i1 = f1.apply("666");
// Integer i2=f2.apply(i1);
Integer i2=f1.andThen(f2).apply("666");
System.out.println("i2="+i2);
}
}
compose方法,默认的compose方法的作用顺序和andThen方法相反
而静态方法identity则是,输入什么参数就返回什么参数
defaultFunction compose(Function super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); }
package cn.tedu.jdk.fun;
import java.util.function.Function;
public class FunctionAndThenTest {
public static void main(String[] args) {
fun5(msg1->{
return Integer.parseInt(msg1);
},msg2->{
return msg2*10;
});
}
public static void fun5(Function f1,Function f2){
// Integer i1 = f1.apply("666");
// Integer i2=f2.apply(i1);
// Integer i2=f1.andThen(f2).apply("666");
Integer i2 = f2.compose(f1).apply("666");
System.out.println("i2="+i2);//倒着,相反
}
}
Predicate接口
有参且有返回值为boolean的接口
@FunctionalInterface public interface Predicate{ boolean test(T t);
代码
package cn.tedu.jdk.fun;
import java.util.function.Predicate;
public class PredicateTest {
public static void main(String[] args) {
test(msg->{
return msg.length() > 3;
},"hello world");
}
public static void test(Predicate predicate,String msg){
boolean b = predicate.test(msg);
System.out.println("b:"+b);
}
}
在Predicate的默认方法提供了逻辑关系操作, and or negate isEquals方法
package cn.tedu.jdk.fun;
import java.util.function.Predicate;
public class PredicateDefaultTest {
public static void main(String[] args) {
test(msg1->{
return msg1.contains("H");
},msg2->{
return msg2.contains("W");
});
}
public static void test(Predicate p1,Predicate p2){
// boolean b1 = predicate.test(msg);
// boolean b2 = predicate.test("Hello");
//b1包含H b2包含W
//b1包含H 同时 b2包含W
boolean b3=p1.and(p2).test("Hello");
//b1 包含H 或者 b2包含W
boolean b4 = p1.or(p2).test("Hello");
//p1 不包含 H
boolean b5 = p1.negate().test("Hello");
System.out.println("b3="+b3);//FALSE
System.out.println("b4="+b4);//TRUE
System.out.println("b5="+b5);//FALSE
}
}
四 方法引用
1.为什么要用方法引用?
lambda表达式冗余
在使用lambda表达式的时候,也会出现代码冗余的时候,比如,用lambda表达式求数组的和
package cn.tedu.jdk.funref;
import java.util.function.Consumer;
public class FunctionRefTest01 {
public static void main(String[] args) {
printMax(arr->{
int sum=0;
for (int i : arr) {
sum+=i;
}
System.out.println("数组之和:"+sum);
});
}
public static void printMax(Consumer consumer){
int[] arr={10,20,30,40,50};
consumer.accept(arr);
}
}
lambda表达式代码冗余
package cn.tedu.jdk.funref;
import java.util.function.Consumer;
public class FunctionRefTest01 {
public static void main(String[] args) {
printMax(arr->{
int sum=0;
for (int i : arr) {
sum+=i;
}
System.out.println("数组之和:"+sum);
});
//代码冗余
int[] arr={10,20,30,40,50};
getTotal(arr);
}
public static void printMax(Consumer consumer){
int[] arr={10,20,30,40,50};
consumer.accept(arr);
}
public static void getTotal(int[] a){
int sum=0;
for (int i : a) {
sum+=i;
}
System.out.println("数组之和:"+sum);
}
}
解决方案:
因为在lambda中要执行的代码和我们另一个方法中的代码是一样的,这时没必要重写一份逻辑了.
这时我们就可以 “引用” 重复代码
package cn.tedu.jdk.funref;
import java.util.function.Consumer;
public class FunctionRefTest02 {
public static void main(String[] args) {
//:: 方法引用,也是JDK8新的语法
printMax(FunctionRefTest02::getTotal);
}
public static void printMax(Consumer consumer){
int[] arr={10,20,30,40,50};
consumer.accept(arr);
}
public static void getTotal(int[] a){
int sum=0;
for (int i : a) {
sum+=i;
}
System.out.println("数组之和:"+sum);
}
}
2.方法引用的格式
:: 方法引用 也是JDK8新的语法
符号说明: 双冒号为方法引用运算符,而它所在的表达式称为方法引用
应用场景: 如果lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用
这是最常见的一个方法,如果一个类中已经存在了一个成员方法.则可以通过对象名引用成员方法
package cn.tedu.jdk.funref;
import java.util.Date;
import java.util.function.Supplier;
public class FunctionRefTest03 {
public static void main(String[] args) {
Date date = new Date();
Supplier supplier=()->{
return date.getTime();
};
System.out.println(supplier.get());//获取当前时间的毫秒值
//通过方法引用的方式来处理
Supplier supplier1=date::getTime;
System.out.println(supplier1.get());
}
}
方法引用的注意事项:
1.被引用的方法,参数要和接口中的抽象方法的参数一样
2.当接口中抽象方法有返回值时,被引用的方法也必须有返回值
package cn.tedu.jdk.funref;
import java.util.function.Supplier;
public class FunctionRefTest04 {
public static void main(String[] args) {
Supplier supplier=()->{
return System.currentTimeMillis();
};
System.out.println(supplier.get());
//通过方法引用来实现
Supplier supplier1=System::currentTimeMillis;
System.out.println(supplier1.get());
}
}
2.3 类名:: 引用实例方法
package cn.tedu.jdk.funref;
import java.util.function.BiFunction;
import java.util.function.Function;
public class FunctionRefTest05 {
public static void main(String[] args) {
Function function=(s -> {
return s.length();
});
System.out.println(function.apply("hello"));
//通过方法引用来实现
Function function1=String::length;
System.out.println(function1.apply("hahahaha"));
//截取字符串
BiFunction function2=String::substring;
String msg=function2.apply("HelloWorld",3);
System.out.println(msg);
}
}
2.4 类名:: 构造器
由于构造器的名称和类名一致,所以构造器引用使用 :: new的格式使用
package cn.tedu.jdk.funref;
import cn.tedu.jdk.lambda.domain.Person;
import java.util.function.BiFunction;
import java.util.function.Supplier;
public class FunctionRefTest06 {
public static void main(String[] args) {
Supplier supplier = () -> {
return new Person();
};
System.out.println(supplier.get());
//然后通过 方法引用来实现
Supplier supplier1=Person::new;
System.out.println(supplier1.get());
BiFunction function=Person::new;
System.out.println(function.apply("张飞",25));
}
}
2.4 数组:: 构造器
数组是怎么构造出来的呢?
package cn.tedu.jdk.funref;
import java.util.function.Function;
public class FunctionRefTest07 {
public static void main(String[] args) {
Function function=(len)->{
return new String[len];
};
String[] a1 = function.apply(5);
System.out.println("数组的长度是:"+a1.length);
//方法的引用方式 来调用数组的构造器
Function function2=String[]::new;
String[] a2 = function.apply(3);
System.out.println("数组的长度是:"+a2.length);
}
}
五 Stream流
1.集合处理的弊端
当我们需要对集合中的元素进行操作的时候.除了基本添加,删除,获取操作外., 最典型的操作就是集合的遍历
针对不同需求,总是一次次循环,需要更高效的循环,.Stream API处理
package cn.tedu.jdk.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StreamTest01 {
public static void main(String[] args) {
//定义一个list集合
List list = Arrays.asList("张三", "李四", "王五", "张飞","周星驰","张三丰");
//1.获取所有姓"张" 的信息
List list1=new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")){
list1.add(s);
}
}
//2.获取所有长度为3的用户
List list2=new ArrayList<>();
for (String s : list1) {
if (s.length()==3){
list2.add(s);
}
}
//3.输出所有的用户信息
List list3=new ArrayList<>();
for (String s : list2) {
if (s.length()==3){
list3.add(s);
}
}
System.out.println(list3);
}
}
Stream API的含义: 获取流,过滤张,获取长度,逐一打印
package cn.tedu.jdk.stream;
import java.util.Arrays;
import java.util.List;
public class StreamTest02 {
public static void main(String[] args) {
//定义一个list集合
List list = Arrays.asList("张三", "李四", "王五", "张飞","周星驰","张三丰");
//1.获取所有姓"张" 的信息
//2.获取所有长度为3的用户
//3.输出所有的用户信息
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s->s.length()==3)
.forEach(s -> {
System.out.println(s);
});
System.out.println("------------------");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s->s.length()==3)
.forEach(System.out::println);
}
}
2.Stream流式思想
Stream和IO没有任何关系.Stream流不是一种数据结构.不保存数据.而是对数据加工处理
3.Stream流的获取方式 3.1根据Collection获取首先java.util.Collection接口中加入了默认方法default的stream,也就是说Collection下的所有实现都可以通过stream方法都可以获取stream流
package cn.tedu.jdk.stream;
import java.util.*;
public class StreamTest03 {
public static void main(String[] args) {
//定义一个list集合
List list = Arrays.asList("张三", "李四", "王五", "张飞","周星驰","张三丰");
list.stream();
Set set=new HashSet<>();
set.stream();
Vector vector = new Vector<>();
vector.stream();
}
}
但是Map没有实现Collection接口,那这时怎么办?我们可以根据map获取对应key,value的集合
package cn.tedu.jdk.stream;
import java.util.*;
import java.util.stream.Stream;
public class StreamTest04 {
public static void main(String[] args) {
HashMap map = new HashMap<>();
map.put("a",111);
map.put("b",122);
Stream stream = map.keySet().stream();//key
Stream stream1 = map.values().stream();//value
Stream> stream2 = map.entrySet().stream();
}
}
3.2 通过Stream的of方法
在实际开发中.我们不可避免的是数组中的数据,由于数组对象不可能添加默认方法,所以stream流提供了静态方法of
package cn.tedu.jdk.stream;
import java.util.stream.Stream;
public class StreamTest05 {
public static void main(String[] args) {
Stream a1 = Stream.of("a1", "a2", "a3", "a4");
String[] arr1={"aa","bb","cc"};
Stream arr11 = Stream.of(arr1);
Integer[] arr2={1,2,3,4,5};
Stream arr21 = Stream.of(arr2);
arr21.forEach(System.out::print);
//注意:基本数据类型的数组是不行的
int[] arr3={1,2,3,4,5};
Stream.of(arr3).forEach(System.out::println);
}
}



