jdk1.8的lambda(optional、predicate等)

lambda:

Lambdas及函数式接口的例子

使用案例Lambda的例子对应的函数式接口
布尔表达式(List list) -> list.isEmpty()Predicate<List>
创建对象()->new Apple(10)Supplier
消费一个对象(Apple a) -> System.out.println(a.getWeight())Consumer
从一个对象中选择/提取(String s) -> s.length()Function<Sring, Integer>或ToIntFunction
合并两个值(int a, int b) -> a*bIntBinaryOperator
比较两个对象(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())Comparator或BiFunction<Apple, Apple, Integer>或ToIntBiFunction<Apple, Apple>

可以直接在Lambda表达式中访问外层的局部变量,但在Lambda表达式内部不能修改定义在Lambda表达式外部的局部变量,否则会编译错误。Lambda表达式的局部变量可以不用声明为final,但是必须不可被后面的代码修改(即隐性的具有final的语义)。在Lambda表达式当中不允许声明一个与外部变量同名的参数或者局部变量。

​ Predicate 断言型接口

Predicate接口中定义了一个名为test的抽象方法,它接受泛型T对象,并返回一个boolean。还有and/or等方法。

@FunctionalInterface
public interface Predicate<T> {

   boolean test(T t);

   default Predicate<T> and(Predicate<? super T> other) {
       Objects.requireNonNull(other);
       return (t) -> test(t) && other.test(t);
   }

   default Predicate<T> negate() {
       return (t) -> !test(t);
   }

   default Predicate<T> or(Predicate<? super T> other) {
       Objects.requireNonNull(other);
       return (t) -> test(t) || other.test(t);
   }

   static <T> Predicate<T> isEqual(Object targetRef) {
       return (null == targetRef)
               ? Objects::isNull
               : object -> targetRef.equals(object);
   }
}

使用示例:

//定义一个对象product,代码省略
//private Long id;
//private Integer num;
//private Double price;
//private String name;

Product prod1 = new Product(1L, 1, new Double(15.5), "面包");
Product prod2 = new Product(2L, 2, new Double(20), "饼干");
Product prod3 = new Product(3L, 3, new Double(30), "月饼");
List<Product> prodList = Lists.newArrayList(prod1, prod2, prod3);
//使用:
//定义一个filter方法
public static <T> List<T> filter(List<T> list, Predicate<T> p){
    	List<T> results = new ArrayList<>();
    	for(T s :list) {
    		if(p.test(s)) {
    			results.add(s);
    		}
    	}
    	return results;
}

测试:

//测试代码
Predicate<Product> pre = prod -> prod.getNum() > 1;

//and
//Predicate<Product> andpre = pre.and(prod -> prod.getNum() < 3);

//or和negate类似

List<Product> filterList = filter(prodList, pre);
filterList.forEach(item -> {
	System.out.println(item.getNum());
});
//输出2 3

//Java 8提供了一个声明函数式接口注解@FunctionalInterface,示例代码如下。
//Calculable.java文件
package com.a51work6;
//可计算接口
@FunctionalInterface
public interface Calculable {
// 计算两个int数值
int calculateInt(int a, int b);
}

在接口之前使用@FunctionalInterface注解修饰,那么试图增加一个抽象方法时会发生编译错误。但可以添加默认方法和静态方法。
提示 Lambda表达式是一个匿名方法代码,Java中的方法必须声明在类或接口中,那么Lambda表达式所实现的匿名方法是在函数式接口中声明的。

例子:

//可计算接口
@FunctionalInterface
public interface Calculable {
// 计算一个int数值
int calculateInt(int a);
}

/**
* 通过幂计算
* @param power 幂
* @return 实现Calculable接口对象
*/
public static Calculable calculate(int power) {
Calculable result;
if (power == 2) {
// Lambda表达式实现Calculable接口
result = (int a) -> { //标准形式 ①
return a * a;
};
} else {
// Lambda表达式实现Calculable接口
result = a -> { //省略形式 ②
return a * a * a;
};
}
return result;
}
}

如果Lambda表达式体中只有一条语句,那么可以省略return和大括号,代码如下:

public static Calculable calculate(int power) {
Calculable result;
if (power == 2) {
// Lambda表达式实现Calculable接口
result = (int a) -> { //标准形式
return a * a;
};
} else {
// Lambda表达式实现Calculable接口
result = a -> a * a * a; //省略形式 ①
}
return result;
}


📣📣作为参数使用Lambda表达式
Lambda表达式一种常见的用途是作为参数传递给方法。这需要声明参数的类型声明为函数式接口类型。
示例代码如下:

@FunctionalInterface
public interface Calculable {
// 计算两个int数值
int calculateInt(int a, int b);
}

public class HelloWorld {
public static void main(String[] args) {
int n1 = 10;
int n2 = 5;
// 打印计算结果加法计算结果
display((a, b) -> {
return a + b;
}, n1, n2);// 打印计算结果减法计算结果
display((a, b) -> a - b, n1, n2);}
/**
* 打印计算结果
*
* @param calc Lambda表达式
* @param n1 操作数1
* @param n2 操作数2
*/
public static void display(Calculable calc, int n1, int n2) {System.out.println(calc.calculateInt(n1, n2));
}
}

Lambda表达式可以访问所在外层作用域内定义的变量,包括:成员变量和局部变量。
-访问成员变量
成员变量包括:实例成员变量和静态成员变量。在Lambda表达式中可以访问这些成员变量,此时的Lambda表达式与普通方法一样,可以读取成员变量,也可以修改成员变量。

public class LambdaDemo {
// 实例成员变量
private int value = 10;
// 静态成员变量
private static int staticValue = 5;
// 静态方法,进行加法运算
public static Calculable add() {Calculable result = (int a, int b) -> {// 访问静态成员变量,不能访问实例成员变量
staticValue++;
int c = a + b + staticValue; // this.value;
return c;
};
return result;
}
// 实例方法,进行减法运算
public Calculable sub() {Calculable result = (int a, int b) -> {// 访问静态成员变量和实例成员变量
staticValue++;
this.value++;
int c = a - b - staticValue - this.value;
return c;
};
return result;
}
}

静态方法中不能访问实例成员变量,所以代码第②行的Lambda表达式中也不能访问实例成员变量,也不能访问实例成员方法。sub方法是实例方法,实例方法中能够访问静态成员变量和实例成员变量,所以代码第④行的Lambda表达式中可以访问这些变量

方法引用
Java 8之后增加了双冒号“::”运算符,该运算符用于“方法引用”,注意不是调用方法。“方法引用”虽然没有直接使用Lambda表达式,但也与Lambda表达式有关,与函数式接口有关。
方法引用分为:静态方法的方法引用和实例方法的方法引用。它们的语法形式如下:
类型名::静态方法 // 静态方法的方法引用
实例名::实例方法 // 实例方法的方法引用

注意 被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型
一致。
示例代码如下:

//LambdaDemo.java文件
package com.a51work6;
public class LambdaDemo {
// 静态方法,进行加法运算
// 参数列表要与函数式接口方法calculateInt(int a, int b)兼容
public static int add(int a, int b) {
return a + b;
}
// 实例方法,进行减法运算
// 参数列表要与函数式接口方法calculateInt(int a, int b)兼容
public int sub(int a, int b) {
return a - b;
}
}

LambdaDemo类中提供了一个静态方法add,一个实例方法sub。这两个方法必须与函数式接口方法参数列表一致,方法返回值类型也要保持一致。

public class HelloWorld {
public static void main(String[] args) {
int n1 = 10;
int n2 = 5;
// 打印计算结果加法计算结果
display(LambdaDemo::add, n1, n2); //①
LambdaDemo d = new LambdaDemo();
// 打印计算结果减法计算结果
display(d::sub, n1, n2); //②
}
    /**
* 打印计算结果
* @param calc Lambda表达式
* @param n1 操作数1
* @param n2 操作数2
*/
public static void display(Calculable calc, int n1, int n2) {System.out.println(calc.calculateInt(n1, n2));
}
}

代码第③行声明display方法,第一个参数calc是Calculable类型,它可以接受三种对象:Calculable实现对象、Lambda表达式和方法引用。代码第①行中第一个实际参数LambdaDemo::add是静态方法的方法引用。代码第②行中第一个实际参数d::sub,是实例方法的方法引用,d是LambdaDemo实例。
提示 代码第①行的LambdaDemo::add和第②行的d::sub中Lambda是方法引用,此时并没有调用方法,只是将引用传递给display方法,在display方法中才真正调用方法。

foreach

我们写一个范例使用 forEach() 方法和 Java 8 提供的 lambda 表达式来迭代一个哈希表:

Map<String, Integer> items = new HashMap<>();
        items.put("A", 10);
        items.put("B", 20);
        items.put("C", 30);
        items.put("D", 40);
        items.put("E", 50);
        items.put("F", 60);

        items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v));

        items.forEach((k,v)->{
            System.out.println("Item : " + k + " Count : " + v);
            if("E".equals(k)){
                System.out.println("Hello E");
            }

原文:

JDK15就要来了,你却还不知道JDK8的新特性!_didiao java的博客-CSDN博客_jdk15和jdk8区别

从某些方面说,Optional 类型就是 「那里有一个值,它等于 x,或者那里没有那个值」

在 Optional 类出现之前,为了防止空指针异常,可以这样做。(每一层都添加判空处理)

private static String getUserAddr(User user){

    if(user != null){

        Address address = user.getAddress();

        if(address != null){
            return address.getDetails();
        }else {
            return "地址信息未填写";
        }
    }else {
        return "地址信息未填写";
    }

可以发现,代码冗长,还不利于维护,随着层级关系更深,将会变成灾难(是否依稀记得js的回调地狱)。

optional

那么,有了 Optional 类,我们就可以写出更优雅的代码,并且防止空指针异常。(后边就填坑)

下面,就一起领略一下 Optional 的魅力吧!

它把真正需要操作的对象 T 封装成 value 属性。构造器私有化,并提供三种静态的创建 Optional 对象的方法。

public final class Optional<T> {
    //EMPTY 代表一个值为空的 Optional 对象
    private static final Optional<?> EMPTY = new Optional<>();
 
    //用 value 来代表包装的实际值
    private final T value;
 
    //值为null的构造函数
    private Optional() {
        this.value = null;
    }
 
    //要求值不为null的构造函数,否则抛出空指针异常,见requireNonNull方法
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }
    
    /** 此为Objects类的requireNonNull方法
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
    */
 
    // 1. 创建一个值为空的 Optional 对象
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    
    // 2. 创建一个值不为空的 Optional 对象
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
 
    // 3. 创建一个值可为空的 Optional 对象
    // 如果值 value 为空,则同1,若不为空,则同2
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
}

当我们十分确定传入的user对象不为空时,可以用 Optional.of(user)方法。若不确定,则用 Optional.ofNullable(user),这样在后续的操作中可以避免空指针异常(后续map说明)。

if(user.isPresent()){}和手动判空处理 if(user!=null){}实质上是没有区别的。这就是受之前一直以来的代码思维限制了。

所以,我们不要手动调用 isPresent 方法 。

不要奇怪,isPresent 方法,其实是为了 Optional 中的其他方法服务的(如map方法),本意并不是为了让我们手动调用。你会在后续多个方法中,见到 isPresent 的身影。

如可以这样判空,

Optional<User>user = Optional.ofNullable(new User());
user.ifPresent(System.out::println);
//不要用下面这种
if(user.isPresent())
{

  System.out.println(user.get());
}
}

我们可以通过 Optional 的链式调用,通过 map,orElse 等操作改写。如下,

private static String getUserAddr1(Optional<User> user){
    //先获取address对象
    return user.map((u)->u.getAddress())
            //再获取details值,
            .map(e -> e.getDetails())
            //若detail为null,则返回一个默认值
            .orElse("地址信息未填写");
}

中间所有可能出现空指针的情况,Optional都会规避。因为 value!=null这个操作已经被封装了。而且在不同的处理阶段,Optional 会自动帮我们包装不同类型的值。

filter

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
} 

,filter 是用来根据条件过滤的,如果符合条件,就返回当前 Optional 对象本身,否则返回一个值为 null的 Optional 对象。

如下,过滤姓名为空的 user。

User user = new User();//由于user没有设置 name,所以返回一个值为 null 的 optionalUser
Optional<User>optionalUser = Optional.of(user).filter((u)->this.getUserName(u).isPresent());
//由于值为 null,所以get方法抛出异常 NoSuchElementException
optionalUser.get();
public static void useStream(List<Person> personList) {

    List<String> nameList = personList.stream().filter(person -> person.getAge() >= 10)// 1.过滤大于等于10的age字段值
            .map(person -> person.getName())// 2.使用map映射元素
            .collect(Collectors.toList());// 3.收集映射后元素

    nameList.stream().forEach(name -> System.out.println(name));
} 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值