阅读更多
根据Oracle 对java 8开发进度安排,java将于2014年的3月份发布。从java8 新引入的特性看,函数式编程语言对java8 影响很大,运行在JVM上的Scala、Groovy等编程语言部分特性被java 8吸收。平台、API等方面先不关注,仅就从编程语言特性来说java8绝对是一次革命性的的改进,有了java 8 java语言的表达能力、简洁性有了很大提高,跟Scala groovy等JVM上的新兴语言差距缩小了很多。语言特性改进概括起来有以下几点
- 函数式接口
- Lambda表达式
- 方法引用
- 接口的改进
- Stream
这篇文章我将说说以上几个语言特性,以及对代码编写的影响
一 函数式接口
所谓函数式接口(FunctionalInterface)是指只有一个抽象方法的接口,比如java 8 之前的Runnabe接口(只有run方法),Comparator接口(comare方法),Comparable(compareTo方法)都属于函数式接口,到了Java 8时代这些接口都被注解为@FunctionalInterface,另外@FunctionalInterface也是java 8 新进入的注解,Java 8新增了一个包java.util.function,在function包下提供了大量常见函数式接口,举例如一下几个
Predicate:谓词,boolean test(T t) 输入类型T,输出布尔值
Supplier 生产者,T get(),无输入,输出类型T
Consumerer 消息者,void accept(T t),输入T,无输出
Function 函数,R apply(T t ) ,输入T 输出R
二 Lambda
Lambda表达式就是匿名方法,方法可以通过名称被重用,而匿名方法不能在被其它代码所调用。lambda也具有输入参数,方法体,返回值,比如(Object o) ->{System.out.println(o.toString()); return o.toSting();}输入参数为Object o 返回值为String类型,方法体为{}之间的语言
比如方法sum方法
public int sum(int x ,int y){
return x + y;
}
改用lambda表达式为
(int x, int y) -> {return x + y;}
在特点场景下通过java 8 类型推导功能,可以省略lambda输入参数的类型,比如上面的sum方法lambda表达式可以改写为
(x, y) -> {return x + y;}
lambda表达式方法体内可以访问lamba外的变量,相比匿名内部类访问外部类变量需要final修饰方便多了
Runnable的run方法没有输入参数,用lambda表达式写Runnable接口实现类可以这样写
Runable r = () -> {System.out.println("Hello");}
Comparator接口实现类可以这样写
Comparator comparator = (String str1,String str2) ->{return s1.compareToIgnoreCase(s2);}
//由于lambda基于类型推导功能,可以省去str1和str2前面的类型String
Comparator comparator = (str1,str2) ->{return s1.compareToIgnoreCase(s2);}
三 方法引用
通过方法引用轻轻松松的实现function as first-classs object(函数作为一类对象)功能,可以将方法作为变量看待,作为参数传递。方法引用符号是::
比如通过 方法引用实现字符串从小写到大写的转换
Function upperfier = String::toUpperCase;
System.out.println(upperfier.apply("Hello"));
比如Set类里有boolean contains(T t)方法,显然方法属于谓词(Predicate)函数,我用可以这用的使用Set的contains方法
Set knowNames = new HashSet<>();
knowNames.add("Zhang");
knowNames.contains("Zhang");
Predicate isKnowName = knowNames::contains;
isKnowName.test("Zhang");
isKnowName.test("Li");
以上举的方法引用都是实例方法引用,方法引用包括(此处方法包括静态方法、实例方法、构造方法、数组构造方法)
四 接口的改进
java 8 中对接口的功能做了改进,包括默认方法和静态方法。
1 默认方法
在java8以前接口不能定义具体方法,只能定义抽象方法。作为一个通用的库或框架,接口一旦发布之后不能轻易的增加新的抽象方法,否则该接口的具体实现类都必须跟着实现新增的方法。java 8 中为了不破坏接口的实现,允许接口有具体的实现方法,实现方法用关键字default修饰 也叫默认方法。
比如JDK中Iterable接口中新增的默认方法 forEach实现遍历功能
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
接口增加了默认方法之后就会存在多继承问题。
比如接口1和接口2都有相同的默认方法method1,接口3同时继承了接口1和接口2,在接口3以及接口3的实现类里 method1到底是继承接口1还是继承接口2的method1方法呢? java 8 通过引入新的语法来i解决问题,接口3必须重写method1方法,在method1方法里可以指定到底是继承接口1还是接口2,比如下面的代码DrawInteface3的draw必须显示指定继承哪个父接口的方法
public interface DrawInterface1 {
default void draw(){
System.out.println("DrawInterface1.draw");
}
}
public interface DrawInterface2 {
default void draw(){
System.out.println("DrawInterface2.draw");
}
}
public interface DrawInterface3 extends DrawInterface1,DrawInterface2 {
default void draw(){
//接口名.super.父接口方法名 来指定调用哪个接口方法
DrawInterface1.super.draw();
}
}
2 静态方法
在java 8 以前接口标准库里定义了Collection接口,然后增加一个Collections工具了辅助Collection接口,java 8 里允许定义接口的静态方法(static修饰)
比如Comparator接口增加了大量的静态放,有了comparing这个静态方法后写比较器非常的轻松
public static > Comparator comparing(
Function super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
//根据名字做比较的比较器
Comparator nameComparator = Comparator.comparing(Employee::getName);
//根据薪水做比较比较器
Comparator salaryComparator = Comparator.comparing(Employee::getSalary);
五 Stream 流
1 本质
java8 新增的Stream非之前IO相关的流,Stream的本质是对数据源进行抽象提供一套通用API,引入Stream、函数式接口、lambda等几大特性候java 8 有了一些函数式编程语言味道
Stream实现map filter reduce等操作,Stream的数据源包括数组、集合、IO通道、随机数生成器等,Stream相关接口在java.util.stream包下
2 使用例子
现在举个List来说明Stream的filter map reduce等常见高阶函数的使用
//对list求和
int result = list.stream().reduce(0,(x,y) -> x + y);
//对list过滤(大于5的数字)再求和
int result = list.stream().filter(x -> x > 5).reduce(0,(x,y) -> x + y);
//对list 过滤(大于5的数字) 映射(求平方) 再求和,先通过mapToInt生成IntStream,调用sum方法
result = list.stream().filter(x -> x > 5).map(x -> x * x).mapToInt(x -> x).sum();
filter参数为谓词Predicate
predicate,map函数参数为Function mapper ,reduce参数为(T identity,BinaryOperator accumulator),identity为起始值,accumulator为二元操作函数。
x -> x > 5为Predicate的lambda写法,(x , y ) -> x + y 为BinaryOperator accumulator的lambda写法
3 Stream的实际应用
当年学习Oracle时入门的例子就用employee表,这里我举例还是用Employee,只是从数据库表换成里java类。现有一个List employees (Employee包括name、salary、departId三个属性),通过Stream的相应方法实现一些常见的"统计功能"
//求所有员工薪资总和
int totalSalary = employees.stream()
.map(e -> e.getSalary())
.reduce(0,(x,y) -> x + y);
System.out.println("totalSalary = " + totalSalary);
//所有雇员姓名
List nameList = employees.stream()
.map(e -> e.getName())
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("nameList = " + nameList);
//求平均工资
double averageSalary = employees.stream()
.mapToInt(e -> e.getSalary())
.average()
.getAsDouble();
//求平均工资,用Collectors工具类的帮助方法
averageSalary = employees.stream()
.collect(Collectors.averagingInt(e -> e.getSalary()));
System.out.println("averageSalary = " + averageSalary);
//雇员按照部门分组
Map> deaprtEmployee = employees.stream()
.collect(Collectors.groupingBy(e -> e.getDepartId()));
//打印每个部门的平均工资
deaprtEmployee.forEach((k,v) -> System.out.println(k+":" + v.stream().mapToInt(
e -> e.getSalary()).average().getAsDouble()));
//查找每个部门工资最低的雇员
deaprtEmployee.forEach((k,v) -> System.out.println(k+":" + v.stream().
sorted(Comparator.comparing(Employee::getSalary)).findFirst().get()));
//对雇员根据名字排序
employees.sort(Comparator.comparing(Employee::getName));
通过以上的例子可以看出java 8 的表达能力有了很大提高,更少的代码能完成更多的事情。另外通过Stream的collect()方法和工具类Collectors可以轻轻松松实现很多运算方法。