java8新特性源码解析

1 Lambda表达式与Functional接口
Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据:函数式程序员对这一概念非常熟悉。在JVM平台上的很多语言(Groovy, Scala,……)从一开始就有Lambda,但是Java程序员不得不使用毫无新意的匿名类来代替lambda。
关于Lambda设计的讨论占用了大量的时间与社区的努力。可喜的是,最终找到了一个平衡点,使得可以使用一种即简洁又紧凑的新方式来构造Lambdas。在最简单的形式中,一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:
Arrays.asList( "a" , "b" , "d" ).forEach( e -> System.out.println( e ) );
Arrays.asList( "a", "b", "d" ).forEach( e -> {
    System.out.print( e );
    System.out.print( e );
} );
Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。 如果lambda的函数体只有一行的话,那么没有必要显式使用return语句。下面两个代码片段是等价的:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
语言设计者投入了大量精力来思考如何使现有的函数友好地支持lambda。最终采取的方法是:增加函数式接口的概念。函数式接口就是一个具有一个方法的普通接口。像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。在实际使用过程中,函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface(Java 8中所有类库的已有接口都添加了@FunctionalInterface注解)。让我们看一下这种函数式接口的定义:
@FunctionalInterface
public interface Functional {
    void method();
}
需要记住的一件事是: 默认方法与静态方法并不影响函数式接口的契约,可以任意使用:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
         
    default void defaultMethod() {           
    }       
}
2.  Default关键字
在Java 8中,接口可以包含带有实现代码的方法,这些方法称为default方法。
3.  Optional(见5.函数式接口详述)
到目前为止, 臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常, Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。更多详情请参考 官方文档
我们下面用两个小例子来演示如何使用Optional类:一个允许为空值,一个不允许为空值。
1
2
3
4
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );       
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
如果Optional类的实例为非空值的话,isPresent()返回true,否从返回false。为了防止Optional为空值,orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,然后返回一个新的Optional实例。orElse()方法和orElseGet()方法类似,但是orElse接受一个默认值而不是一个回调函数。下面是这个程序的输出:
1
2
3
Full Name is set ? false
Full Name: [none]
Hey Stranger!
让我们来看看另一个例子:
1
2
3
4
5
Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );       
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
下面是程序的输出:
1
2
3
First Name is set ? true
First Name: Tom
Hey Tom!
********************************************************************************************************************************************************************************************************************************************************

比如以前你从数据库里获取一个对象,然后对他进行操作,可能是这样的代码
1
2
3
4
5
Object o = dao.find()
if (o !=  null )
{
    doSomething(o)
}
但是用 函数式编程来写就该是这样
1
2
Optional<Object> o = dao.find()
o.ifPresent(item->doSometing(o));
再举个例子,若你从数据库查出来的对象要是空的话需要报错,以前会这样写
1
2
3
4
5
6
Object o = dao.find()
if (o ==  null )
{
     throw  Exception
}
return  o;
但是现在你可以这样写
1
2
Optional<Object> o = dao.find()
return  o.orElseThrow(Exception:: new )
还可以简化成 
1
return   dao.find().orElseThrow(Exception:: new );
********************************************************************************************************************************************************************************************************************************************************
optional的正确用法!!!
先又不得不提一下 Optional 的三种构造方式: Optional.of(obj),  Optional.ofNullable(obj) 和明确的 Optional.empty()
Optional.of(obj): 它要求传入的 obj 不能是 null 值的, 否则还没开始进入角色就倒在了 NullPointerException 异常上了.
Optional.ofNullable(obj): 它以一种智能的, 宽容的方式来构造一个 Optional 实例. 来者不拒, 传 null 进到就得到 Optional.empty(), 非 null 就调用 Optional.of(obj).
那是不是我们只要用 Optional.ofNullable(obj) 一劳永逸, 以不变应二变的方式来构造 Optional 实例就行了呢? 那也未必, 否则 Optional.of(obj) 何必如此暴露呢, 私有则可?
我本人的观点是:  1. 当我们非常非常的明确将要传给 Optional.of(obj) 的 obj 参数不可能为 null 时, 比如它是一个刚 new 出来的对象(Optional.of(new User(...))), 或者是一个非 null 常量时;  2. 当想为 obj 断言不为 null 时, 即我们想在万一 obj 为 null 立即报告 NullPointException 异常, 立即修改, 而不是隐藏空指针异常时, 我们就应该果断的用 Optional.of(obj) 来构造 Optional 实例, 而不让任何不可预计的 null 值有可乘之机隐身于 Optional 中.
存在即返回, 无则提供默认值
1
2
return user.orElse( null );  //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);
存在即返回, 无则由函数来产生
1
return user.orElseGet(() -> fetchAUserFromDatabase()); //而不要 return user.isPresent() ? user: fetchAUserFromDatabase();
存在才对它做点什么
1
2
3
4
5
6
user.ifPresent(System.out::println);
 
//而不要下边那样
if (user.isPresent()) {
  System.out.println(user.get());
}
map 函数隆重登场
当 user.isPresent() 为真, 获得它关联的 orders, 为假则返回一个空集合时, 我们用上面的 orElse, orElseGet 方法都乏力时, 那原本就是 map 函数的责任, 我们可以这样一行
1
2
3
4
5
6
7
8
return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
 
//上面避免了我们类似 Java 8 之前的做法
if (user.isPresent()) {
   return user.get().getOrders();
} else {
   return Collections.emptyList();
}
map  是可能无限级联的, 比如再深一层, 获得用户名的大写形式 *map自带空指针判断
1
2
3
return user.map(u -> u.getUsername())
           .map(name -> name.toUpperCase())
           .orElse( null );
这要搁在以前, 每一级调用的展开都需要放一个 null 值的判断
1
2
3
4
5
6
7
8
9
10
11
User user = .....
if (user != null ) {
  String name = user.getUsername();
   if (name != null ) {
     return name.toUpperCase();
  } else {
     return null ;
  }
} else {
   return null ;
}
一句话小结: 使用 Optional 时尽量不直接调用 Optional.get() 方法, Optional.isPresent() 更应该被视为一个私有方法, 应依赖于其他像 Optional.orElse(), Optional.orElseGet(), Optional.map() 等这样的方法.
4.Stream
public class Streams  {
     private enum Status {
        OPEN, CLOSED
    };
     
     private static final class Task {
         private final Status status;
         private final Integer points;
 
        Task( final Status status, final Integer points ) {
             this .status = status;
             this .points = points;
        }
         
         public Integer getPoints() {
             return points;
        }
         
         public Status getStatus() {
             return status;
        }
         
         @Override
         public String toString() {
             return String.format( "[%s, %d]" , status, points );
        }
    }
}
Task类有一个分数的概念(或者说是伪复杂度),其次是还有一个值可以为OPEN或CLOSED的状态.让我们引入一个Task的小集合作为演示例子:
1
2
3
4
5
final Collection< Task > tasks = Arrays.asList(
     new Task( Status.OPEN, 5 ),
     new Task( Status.OPEN, 13 ),
     new Task( Status.CLOSED, 8 )
);
我们下面要讨论的第一个问题是所有状态为OPEN的任务一共有多少分数?在Java 8以前,一般的解决方式用foreach循环,但是在Java 8里面我们可以使用stream:一串支持连续、并行聚集操作的元素。
1
2
3
4
5
6
7
8
// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();
         
System.out.println( "Total points: " + totalPointsOfOpenTasks );
程序在控制台上的输出如下:
1
Total points: 18
这里有几个注意事项。第一,task集合被转换化为其相应的stream表示。然后,filter操作过滤掉状态为CLOSED的task。下一步 ,mapToInt操作通过Task::getPoints这种方式调用每个task实例的getPoints方法把Task的stream转化为Integer的stream。最后,用sum函数把所有的分数加起来,得到最终的结果.
在继续讲解下面的例子之前,关于stream有一些需要注意的地方(详情 在这里).stream操作被分成了中间操作与最终操作这两种。
中间操作返回一个新的stream对象。中间操作总是采用惰性求值方式,运行一个像filter这样的中间操作实际上没有进行任何过滤,相反它在遍历元素时会产生了一个新的stream对象,这个新的stream对象包含原始stream中符合给定谓词的所有元素。
像forEach、sum这样的最终操作可能直接遍历stream,产生一个结果或副作用。当最终操作执行结束之后,stream管道被认为已经被消耗了,没有可能再被使用了。在大多数情况下,最终操作都是采用及早求值方式,及早完成底层数据源的遍历。
stream另一个有价值的地方是能够原生支持并行处理。让我们来看看这个算task分数和的例子。
1
2
3
4
5
6
7
8
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );
     
System.out.println( "Total points (all tasks): " + totalPoints );
这个例子和第一个例子很相似,但这个例子的不同之处在于这个程序是并行运行的,其次使用reduce方法来算最终的结果。
下面是这个例子在控制台的输出:
1
Total points (all tasks): 26.0
经常会有这个一个需求:我们需要按照某种准则来对集合中的元素进行分组。Stream也可以处理这样的需求,下面是一个例子:
1
2
3
4
5
// Group tasks by their status
final Map< Status, List< Task > > map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
这个例子的控制台输出如下:
1
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
让我们来计算整个集合中每个task分数(或权重)的平均值来结束task的例子。****
1
2
3
4
5
6
7
8
9
10
11
12
// Calculate the weight of each tasks (as percent of total points)
final Collection< String > result = tasks
    .stream()                                        // Stream< String >
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream< Double >
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream< String>
    .collect( Collectors.toList() );                 // List< String >
         
System.out.println( result );
下面是这个例子的控制台输出:
1
[19%, 50%, 30%]
最后,就像前面提到的,Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理。下面用一个例子来应证这一点。
1
2
3
4
final Path path = new File( filename ).toPath();
try ( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println( "Done!" ) ).forEach( System.out::println );
}
对一个stream对象调用onClose方法会返回一个在原有功能基础上新增了关闭功能的stream对象,当对stream对象调用close()方法时,与关闭相关的处理器就会执行。
5.函数式接口(Functional Interface)
所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。函数式接口里是 可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;函数式接口里是 可以包含静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的; 函数式接口里是可以包含Object里的public方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自java.lang.Object里对这些抽象方法的实现; 函数式接口里 允许子接口继承多个父接口,但每个父接口中都只能存在一个抽象方法,且必须的相同的抽象方法。
/**
* java 8 之前通常使用匿名内部类完成
*/
Collections.sort(dtoList, new Comparator<PlatformCouponOrderDTO>() {
@Override
public int compare(PlatformCouponOrderDTO a, PlatformCouponOrderDTO b) {
return DateUtils.getMinutesBetween(a.getTime(), b.getTime());
}
});

/**
* java 8 之后使用Lambda表达式实现函数式接口,使代码量明显减少许多
*/
Collections.sort(dtoList, (a, b) -> DateUtils.getMinutesBetween(a.getTime(), b.getTime()));

举个栗子:Predicate 和Consumer接口( java.util.function包下的接口
在Predicate接口中,有以下5个方法:(判断输入的对象是否符合某个条件,返回一个boolean)
@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);
}
defaultPredicate<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);
}
Consumer接口(接受单个输入参数且没有返回值,Consumer接口期望执行带有副作用的操作, 即改变输入参数的内部状态)源码实现如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
我们来考虑一下学生的例子:Student类包含姓名,分数以及待付费用,每个学生可根据分数获得不同程度的费用折扣:
class Student{
String firstName;
String lastName;
Double grade;
Double feeDiscount = 0.0;
Double baseFee = 20000.0;
public Student(String firstName, String lastName, Double grade) 
{
this.firstName = firstName;
this.lastName = lastName;
this.grade = grade;
}
public void printFee(){
Double newFee = baseFee - ((baseFee * feeDiscount) / 100);
System.out.println("The fee after discount: " + newFee);
}
}
使用predicate的test方法判断学生满足的折扣条件,Consumer接口的accept方法来更改学生的折扣属性。
public class PreidcateConsumerDemo {
public static Student updateStudentFee(Student student, Predicate<Student> predicate, Consumer<Student> consumer){ //Use the predicate to decide when to update the discount.
if ( predicate.test(student)){
//Use the consumer to update the discount value.
consumer.accept(student);
}
return student;
}
}
updateStudentFee方法的调用如下所示:
public static void main(String[] args) {
Student student1 = new Student("Ashok","Kumar", 9.5);
student1 = updateStudentFee(student1,
//Lambda expression for Predicate interface
student -> student.grade > 8.5,
//Lambda expression for Consumer inerface
student -> student.feeDiscount = 30.0);
student1.printFee();
Student student2 = new Student("Rajat","Verma", 8.0);
student2 = updateStudentFee(student2, student -> student.grade >= 8, student -> student.feeDiscount = 20.0);
student2.printFee();
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值