Java研学-Lambda表达式
一 Lambda表达式 – 箭头函数
1 含义
JDK8首次将函数式编程引入到Java代码中;这是一种新型的方法参数传递的方式;直接将获取参数的步骤传递给需要该参数的方法中–Lambda表达式
2 特点
1 简化代码
2 多核友好
3 面向对象思想不足
public class Play {
public static void main(String[] args) {
// 实现Runnable的优势在于他避免了单继承的局限性,但需要通过Thread构造器将实现接口的对象传给Thread
// 多线程输出 匿名内部类实现run方法,通过Thread类调用start开启
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程操作");
}
}).start();
System.out.println("等价于");
new Thread(()-> System.out.println("新多线程操作")).start();
}
}
3 例子-自定义水果类,以普通方式与Lambda分别实现
自定义水果类
// 注解需导入lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Fruit {
private Long id; // 编号
private String name; // 水果名
private Integer price; // 单价
}
普通方法
public class FruitTest {
// 创建水果集合
private static List<Fruit> fruits=new ArrayList<>();
// 水果只需存储一次,进入直接初始化
static {
fruits.add(new Fruit(1L,"猕猴桃",6));
fruits.add(new Fruit(2L,"苹果",5));
fruits.add(new Fruit(3L,"苹果梨",4));
fruits.add(new Fruit(4L,"橙子",7));
fruits.add(new Fruit(5L,"草莓",6));
fruits.add(new Fruit(6L,"山竹",11));
}
public static void main(String[] args) {
// 查询所有6块钱的水果
findByPrice();
// 查询含苹果名字的水果
findByName();
}
private static void findByPrice(){
List<Fruit> prices=new ArrayList<>();
for (Fruit f:fruits) {
if(f.getPrice()==6){
prices.add(f);
}
}
System.out.println(prices);
}
private static void findByName(){
List<Fruit> names=new ArrayList<>();
for (Fruit f:fruits) {
if(f.getName().contains("苹果")){
names.add(f);
}
}
System.out.println(names);
}
}
Lambda
// 自定义接口
@FunctionalInterface
public interface IFruit {
// 接口中定义统一操作规则 根据指定商品信息判断是否满足需求
boolean test(Fruit f);
}
// 使用
public class FruitTest {
// 创建水果集合
private static List<Fruit> fruits=new ArrayList<>();
// 水果只需存储一次,进入直接初始化
static {
fruits.add(new Fruit(1L,"猕猴桃",6));
fruits.add(new Fruit(2L,"苹果",5));
fruits.add(new Fruit(3L,"苹果梨",4));
fruits.add(new Fruit(4L,"橙子",7));
fruits.add(new Fruit(5L,"草莓",6));
fruits.add(new Fruit(6L,"山竹",11));
}
// 通过匿名内部类改造Lambda实现接口
public static void main(String[] args) {
// 查询所有6块钱的水果
findFruitByI((p)->p.getPrice()==6);
// 查询含苹果名字的水果
findFruitByI(p->p.getName().contains("苹果"));
}
private static void findFruitByI(IFruit ifr){
List<Fruit> ff =new ArrayList<>();
for (Fruit f:fruits) {
if(ifr.test(f)){
ff.add(f);
}
}
System.out.println(ff);
}
}
4 Lambda表达式格式
Lambda的固定格式核心符号是 ->
左侧:表示通过Lambda表达式改造方法的参数部分
右侧:表示通过Lambda表达式进行改造的方法的方法体
5 Lambda表达式改造前提
必须是接口,并且接口中有且仅有一个抽象方法,为保证接口中只定义一个抽象方法,JDK8提出使用@FunctionaIInterface(函数式接口)
@FunctionalInterface
public interface IFruit {
// 接口中定义统一操作规则 根据指定商品信息判断是否满足需求
boolean test(Fruit f);
}
6 Lambda表达式特点演示
public class FruitTest {
......
public static void main(String[] args) {
// ① 没有特殊要求改造方法的参数类型可以省
List<String> list= Arrays.asList("大黄","大白","小黑");
// foreach类似增强for
list.forEach(s-> System.out.println(s));
// ② 改造方法仅有一个参数时()可以省,没有参数或有多个参数时()不能省
new Thread(()-> System.out.println("多线程启动")).start();
// 实例化时通过代码块为map赋值
Map<String,String> map=new HashMap<String,String>(){
{
this.put("大黄","15kg");
this.put("大白","12kg");
}
};
// ③ 改造方法仅有一条代码{}与;都可以省去,若有多条代码则不能省去,一般改造方法只有一条代码
map.forEach((k,v)->{
System.out.println(k);
System.out.println(v);
});
// ④ 改造方法仅一条代码,且为return语句时,return关键字可省去
findFruitByI(new IFruit() {
@Override
public boolean test(Fruit f) {
return f.getPrice()==6;
}
});
findFruitByI(p->p.getPrice()==6);
}
private static void findFruitByI(IFruit ifr){
List<Fruit> ff =new ArrayList<>();
for (Fruit f:fruits) {
if(ifr.test(f)){
ff.add(f);
}
}
System.out.println(ff);
}
}
7 常见函数式接口
函数式接口 | 参数类型 | 返回类型 | 说明 |
---|---|---|---|
Consumer< T>消费型接口 | T | void | 对类型为T的对象进行操作,方法:void accept(T t) |
Supplier< T>供给型接口 | 无 | T | 返回类型为T的对象 方法:T get();(可做工厂) |
Function< T,R>函数型接口 | T | R | 对类型为T的对象进行操作,并且返回结果是R类型(任意数据类型),方法:R apply(T t); |
Predicate< T>断言型接口 | T | boolean | 判断类型为T的对象是否满足条件,返回值固定为布尔类型,方法 boolean test(T t) |
public class FunctionPlay {
// 创建水果集合
private static List<Fruit> fruits = new ArrayList<>();
// 水果只需存储一次,进入直接初始化
static {
fruits.add(new Fruit(1L, "猕猴桃", 6));
fruits.add(new Fruit(2L, "苹果", 5));
fruits.add(new Fruit(3L, "苹果梨", 4));
fruits.add(new Fruit(4L, "橙子", 7));
fruits.add(new Fruit(5L, "草莓", 6));
fruits.add(new Fruit(6L, "山竹", 11));
}
public static void main(String[] args) {
buy(36, new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println("买了"+integer+"元的水果");
}
});
buy(456,(consumer)-> System.out.println("买了"+consumer+"元的水果"));
int it=getRealLength("playthis", new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
});
System.out.println(it);
System.out.println(getRealLength("playthis",s -> s.length()));
System.out.println(getCode(6, new Supplier<Integer>() {
@Override
public Integer get() {
return (int)(Math.random()*10);
}
}));
System.out.println(getCode(8,()->(int)(Math.random()*10)));
isOk(new Predicate<Fruit>() {
@Override
public boolean test(Fruit fruit) {
return fruit.getPrice()>8;
}
});
}
// 购买消费金额
public static void buy(int moeny,Consumer<Integer> consumer){
// 接口对象consumer调用方法accept传递参数moeny
consumer.accept(moeny);
}
// 获取长度(String真实长度)
public static int getRealLength(String let, Function<String,Integer> fun){
return fun.apply(let);
}
// 返回指定位数随机码
public static String getCode(int num, Supplier<Integer> sup){
// 创建StringBuffered
StringBuffer sb=new StringBuffer();
for (int i = 0; i < num; i++) {
sb.append(sup.get());
}
return sb.toString();
}
// 判断水果是否超过8元
public static void isOk(Predicate<Fruit> fp){
for (Fruit f:fruits) {
if(fp.test(f)){
System.out.println(f.toString());
}
}
}
}
8 方法引用
① 普通调用方法的方式
对象.方法名([参数])
类名.方法([参数])–必须被static修饰
② 方法引用
进行Lambda表达式改造后,传递的操作已定义完毕(Java核心类库提供或者是其他程序员定义好的方法),可使用方法引用的方式调用
③ 方法引用的前提
引用的方法必须是定义好的,必须是Lambda表达式改后的方法
④ 格式-- ::(引用运算符,两个引号)
::左侧 – 通过什么方式引用的即 对象名或类名
::右侧 – 调用方法的名字(方法引用只能引用无参方法,故不能写())
public class PlayMethod {
public static void main(String[] args) {
// 对象::方法名
fun1();
// 类名::静态方法
fun2();
// 类名::方法
fun3();
// 类名::new
fun4();
}
// 方法引用有局限性,调用定义好的方法不能灵活应用
// list.forEach(p-> System.out.println(p));
private static void fun1(){
List<String> list= Arrays.asList("大黄","大白","小黑");
list.forEach(System.out::print);
}
// ()->Math.random()
private static void fun2(){
Supplier sup=Math::random;
// get方法获取值
System.out.println(sup.get());
}
// s->s.length()
private static void fun3(){
Function<String,Integer> fun=String::length;
System.out.println(fun.apply("8537矿泉水"));
}
// 类名::new 构造器引用
private static void fun4(){
/*Supplier<String> sup=new Supplier<String>() {
@Override
public String get() {
return new String();
}
};*/
// ()->new String();
Supplier<String> sup=String::new;
System.out.println(sup.get());
}
}
9 延迟执行
又称延迟加载或懒加载
public class PlayMethod {
public static void main(String[] args) {
String name="大黄";
String is="是";
String color="黄色的";
// 拼接 无论是否满足条件此刻都已经完成拼接存放在工具中了(性能浪费),满足条件才提供结果
addString("ok",name+is+color);
}
private static void addString(String info, String s) {
if("ok".equals(info)){
System.out.println(s);
}
}
}
使用供给型接口实现延迟加载
public class PlayMethod {
public static void main(String[] args) {
String name="大黄";
String is="是";
String color="黄色的";
// 拼接
/*addString("no", new Supplier<String>() {
@Override
public String get() {
return name+is+color;
}
});*/
addString("ok",()->name+is+color);
}
private static void addString(String info, Supplier<String> sup) {
if("ok".equals(info)){
// 判断通过才拼接字符串
System.out.println(sup.get());
}
}
}
10 接口的多继承
① 接口能够定义的类成员
成员常量:public static final 数据类型 常量名=值;
抽象方法: 返回 方法名([参数]);
JDK8后,在接口中可定义default和static方法,能够具有方法体
② 代码演示
定义接口A,包含抽象方法fun1()的default方法fun2(),以及static方法fun3();
public interface A {
void fun1();
default void fun2(){
System.out.println("Afun2");
}
static void fun3(){
System.out.println("Afun3");
}
}
定义接口B,包含default方法fun2(),以及static方法fun3();
C类分别实现AB两个接口
public class C implements A,B{
@Override
public void fun1() {
}
// 一个类实现多个接口时,若接口中default方法同名
// 需重写default方法确认调用哪个default方法
@Override
public void fun2() {
B.super.fun2();
}
}