文章目录
Java8 函数式编程及 Lambda
1 函数式编程
(1)函数式编程思想
函数式编程是对行为的抽象。核心思想是:使用不可变的值和函数,函数对一个值进行处理映射成另一个值。
(2)函数式编程警惕
无状态
一般所说的状态可视为【reference, store, value】这样的三元组(引用,存储, 值)。
❶ reference 也可以叫 index 或 pointer 或 location,store 则可看做是一个接受一个 reference 返回 value 的函数(具体实现可以是内存单元,或外部文件等);
❷ value 就是存储的值了;
❸ 状态变化则是指两方面了①.reference 的改变,②.reference 所指的value 改变,一般情况下指后者(将名称绑定机制和赋值机制分开,如 Java等语言);
❹那函数式编程的无状态就是指,以上两者都是不可变的。
无副作用
❶函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
❷函数的副作用指的是函数在调用过程中,除了给出了返回值外,还修改了函数外部的状态,比如,函数在调用过程中,修改了某一个全局状态。函数式编程认为,函数的副用作应该被尽量避免。
❸Java 函数式编程中,函数式接口对象的实现会引用方法,引用的方法必须返回具体的对象值。不允许引用的方法抛异常!这点坑要注意。
2 函数式接口
(1)函数式接口
函数式接口是只包含一个抽象方法的接口。如 Runnable、Comparator 接口。Java 中 Lambda 无法单独出现,需要一个函数式接口来盛放。Lambda 表达式或方法具体实现就是函数式接口实现。(函数式接口标记注解 @FunctionalInterface)
(2)Java 中最常用的函数式接口
//Java8 中所有函数式接口在包 java.util.function 下。
//可参考(http://www.runoob.com/java/java8-functional-interfaces.html)
Function<T,R>
接受一个输入参数,返回一个结果。
Consumer<T>
代表了接受一个输入参数并且无返回的操作。
Predicate<T>
接受一个输入参数,返回一个布尔值结果。
Supplier<T>
无参数,返回一个结果。
UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。
BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。
3 Lambda
面对大型的数据集合,Java 欠缺高效的并行操作,Lambda 的就是解决这一问题。Lambda 表达式的入参必须是 final 类型。Lambda 表达式本身类型是:函数式接口类型。
(1)流(Stream)操作集合
Stream是用函数式编程方式在集合类上进行复杂操作的工具。
惰性求值:返回类型是Stream;
及早求值:返回类型是另一个值或空;
- collect() 由stream值生成列表; map() 值转换;
- filter() 条件过滤;
- flatMap() 多个Stream
- reduce() 从一组值中生成一个值;
- limit: 对一个Stream进行截断操作,获取其前N个元素,如果原 Stream中包含的元素个数小于N,那就获取其所有的元素;
- distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;
(2)过滤器
- allMatch:是不是Stream中的所有元素都满足给定的匹配条件
- anyMatch:Stream中是否存在任何一个元素满足匹配条件
- findFirst: 返回Stream中的第一个元素,如果Stream为空,返回空Optional
- noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
(3)收集器
收集器作用:从流生成复杂结构的结果。
数据集合转换–toList()等。
转换成值–averagingInt()等。
数据分块–partitioningBy()。
数据分组–groupingBy()。
(4)数据并行化
并行:多个任务在同一时间段发生,每个任务分别在不同的cpu上并行处理。
并发:两个或多个任务共享一个时间段在一个cup上处理。
集合的并行流操作,把stream替换成parallelStream就能获得一个拥有并行能力的流。要避免持有锁,必要时流框架会自己同步操作。并行流底层沿用fork/join框架实现。
(5)影响并行流操作的性能因素
1 数据规模大小,数据够大并行才有意义
2 数据源结构 对于集合,ArrayList(优);HashSet,TreeSet(一般);LinkedList(差)
3 装箱 基本类型处理要更快
4 核的数量 多核cpu才有意义
5 无状态操作性能更优 如map,filter无状态操作,sorted 有状态操作。
4 Lambda单元测试
1 lambda 表达式中建议使用方法引用。
2 使用 peek 方法,加入断点调试。
5 Optional方法
.of
为非null的值创建一个Optional。为null,则抛出NullPointerException。
.ofNullable
为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。
.isPresent
如果值存在返回true,否则返回false。
.get
如果Optional有值则将其返回,否则抛出NoSuchElementException。
.ifPresent
如果Optional实例有值则为其调用consumer,否则不做处理。
.orElse
如果有值则将其返回,否则返回指定的其它值。
.orElseGet
orElseGet与orElse方法类似,区别在于得到的默认值。
orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。
.orElseThrow
如果有值则将其返回,否则抛出supplier接口创建的异常。
在orElseGet方法中,我们传入一个Supplier接口。
然而,在orElseThrow中我们可以传入一个lambda表达式或方法,如果值不存在来抛出异常。
6 代码示例
Lamba使用示例
/**
* @author bilahepan
* @version $Id: LambdaTest.java, v 0.1 2017年6月5日 下午3:32:55 bilahepan Exp $
*/
public class LambdaTest {
@Test
public void testUseThread() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类开启线程。");
}
}).start();
new Thread(() -> System.out.println("使用lambda开启线程。")).start();
//直接使用Runnable
Runnable thread1 = new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类开启线程。");
}
};
Runnable thread2 = () -> System.out.println("使用lambda开启线程。");
thread1.run();
thread2.run();
}
@Test
public void testCommonMethod() {
List<List<String>> list = DataFactory.initData();
//lambda基本用法
list.parallelStream()
.flatMap(item -> item.stream())
.filter(item -> isStart(item))//redicate<T>
.peek(item->System.out.println(item.toString()))
.map(item -> caseConvert(item))//Function<T,R>
.collect(Collectors.toList())
.forEach(item -> System.out.println(item));
}
@Test
public void testReduce()
{
//规约操作-从一组值中生成一个值。count,min,max
//BinaryOperator<T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。
System.out.println(DataFactory.initList().stream().count());
//这几个操作返回的都是一个Optional对象
System.out.println(DataFactory.initList().stream().min(Comparator.comparing(item->item.getPrice())).get());
System.out.println(DataFactory.initList().stream().max(Comparator.comparing(item->item.getPrice())).get());
System.out.println(DataFactory.initList().stream().map(item->item.getPrice()).reduce(add()).get());
}
@SuppressWarnings("unused")
@Test
public void testCollector() {
//收集器
//使用内置的收集器--数据集合转换
DataFactory.initList() .stream()
.map(item -> item.getName())
.collect(Collectors.toList())
.forEach(item -> System.out.println(item));
//使用内置的收集器--转换成值
double average = DataFactory.initList()
.stream()
.collect(Collectors.averagingInt(item -> item.getPrice()));
//数据分块--partitioningBy
Map<Boolean, List<Book>> partMap = DataFactory.initList()
.stream()
.collect(Collectors.partitioningBy(item -> item.getPrice() > 40));
List<Book> bookList1 = partMap.get(true);
List<Book> bookList2 = partMap.get(false);
//数据分组--groupingBy
Map<String, List<Book>> groupMap = DataFactory.initList().stream().collect(Collectors.groupingBy(item->item.getName()));
groupMap.entrySet().iterator();
}
//--------------------------------------------------------私有成员方法--------------------------------------------------------
/**
*
* @return BinaryOperator<Integer>
*/
private BinaryOperator<Integer> add() {
return (a, b) -> a + b;
}
/**
*
* @param str
* @return
*/
private String caseConvert(String str) {
if (str.startsWith("a", 0)) {
return str.toUpperCase();
} else {
return str.toLowerCase();
}
}
/**
*
* @param str
* @return
*/
private boolean isStart(String str) {
return (str.startsWith("a", 0) || str.startsWith("b", 0));
}
}
DataFactory初始化数据使用类
/**
* @author bilahepan
* @version $Id: DataFactory.java, v 0.1 2017年11月3日 下午12:35:08 bilahepan Exp $
*/
public class DataFactory {
/**
* @return
*/
public static LinkedList<Book> initList() {
LinkedList<Book> list = new LinkedList<>();
list.add(new Book("a", 10));
list.add(new Book("a", 20));
list.add(new Book("b", 20));
list.add(new Book("b", 30));
list.add(new Book("c", 30));
list.add(new Book("c", 40));
list.add(new Book("d", 40));
list.add(new Book("d", 50));
return list;
}
/**
* @return
*/
public static List<List<String>> initData() {
List<List<String>> list = new LinkedList<>();
LinkedList<String> item1 = new LinkedList<>();
item1.add("anbnb");
item1.add("ahhhk");
item1.add("a1212");
LinkedList<String> item2 = new LinkedList<>();
item2.add("bnbnb");
item2.add("bhhhk");
item2.add("b1212");
LinkedList<String> item3 = new LinkedList<>();
item3.add("cnbnb");
item3.add("chhhk");
item3.add("c1212");
LinkedList<String> item4 = new LinkedList<>();
item4.add("dnbnb");
item4.add("dhhhk");
item4.add("d1212");
list.add(item1);
list.add(item2);
list.add(item3);
list.add(item4);
return list;
}
}
Book 类
/**
* @author bilahepan
* @version $Id: Book.java, v 0.1 2017年11月3日 下午12:24:40 bilahepan Exp $
*/
public class Book {
/** name */
private String name;
/** price */
private int price;
/**
*
*/
public Book() {
}
/**
* @param name
* @param price
*/
public Book(String name, int price) {
this.name = name;
this.price = price;
}
/**
* Getter method for property <tt>name</tt>.
*
* @return property value of name
*/
public String getName() {
return name;
}
/**
* Setter method for property <tt>name</tt>.
*
* @param name value to be assigned to property name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter method for property <tt>price</tt>.
*
* @return property value of price
*/
public int getPrice() {
return price;
}
/**
* Setter method for property <tt>price</tt>.
*
* @param price value to be assigned to property price
*/
public void setPrice(int price) {
this.price = price;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Book [name=" + name + ", price=" + price + "]";
}
}