一、Lamda表达式
传统写法
- package cn.com;
- interface Message {// 这适宜个一个接口
- public void print(String str) ;// 里面只有一个抽象方法
- }
- public class TestDemo {
- public static void main(String[] args) {
- Message msg= new Message() {
- @Override
- public void print(String str) {
- System.out.println(str);
- }
- } ;// 匿名内部类定义完成了
- msg.print("Hello word");
- }
- }
使用Lamda后的写法
Message msg = (x) -> System.out.println(x);这里只有一个参数(x)括号可以不要
Lamda表达式的用法:
.单行语句如上示例(params) -> 单行语句;
.单行语句如上示例(params) -> 表达式;
.多行语句(a, b) -> {多条语句};
.单行语句带返回值 (a, b) -> a + b; 单行语句返回不用写return
二、接口可以扩充默认的方法实现和静态方法
- package cn.com.java;
- @FunctionalInterface
- interface Message {
- public int print(int a, int b);
- default String getInfo(String str){
- return str;
- }
- static String fun(String str){
- return str;
- }
- }
- public class TestDemo {
- public static void main(String[] args) {
- Message msg = (a, b) ->{
- int sum = a+b;
- return sum;
- };
- msg.print(1, 2);
- msg.getInfo("Hello Word");//普通方法由对象调用
- Message.fun("Hello Word");//静态方法由类名称调用
- }
- }
san、方法引用
所谓的方法引用实际上指的是将一个特定类的方法功能映射过来,而后通过接口中的方法,利用lamda表达式实现方法体的定义,当然这种定义的形式一共分为四种
1、类之中构造方法的引用:类名称:: new
- package cn.com.java;
- class Book{
- private String name;
- private int price;
- public Book(String name,int price){
- this.name = name;
- this.price = price;
- }
- @Override
- public String toString() {
- // TODO Auto-generated method stub
- return "书名:"+this.name+";价格:"+this.price;
- }
- }
- @FunctionalInterface
- interface Message<T extends Book> {
- public T print(String name,int price);// 如果想引用Book类之中的构造方法,需参数向Book中的保持一致
- }
- public class TestDemo {
- public static void main(String[] args) {
- // 实现为接口方法的参数传给Book类的构造函数创建一个book实例
- Message<Book> mes = Book::new; //实际就是接口中那个独一无二的方法的实现,同时定义了接口的实现类
- System.out.println(mes.print("Java开发", 30));
- }
- }
2、类中静态方法的引用:类名称:: 静态方法名称
- package cn.com.java;
- interface Message {
- public Integer print(String msg);
- }
- public class TestDemo {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Message mes = Integer::parseInt;
- System.out.println(mes.print("123456"));
- }
- }
- package cn.com.java;
- interface Message {
- public Integer print(String msg);
- }
- public class TestDemo {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- String a = "a";
- Message mes = a::compareTo;
- System.out.println(mes.print("b"));
- }
- }
在String类里面有一个不区分大小写判断内容大小的方法:public int compareToIgnoreCase(String str);
- package cn.com.java;
- import java.util.Arrays;
- import java.util.Comparator;
- public class TestDemo {
- public static void main(String[] args) {
- String[] data = new String[] { "test", "hello", "eclipse", "java",
- "Oracle" };
- //Comparator<String> cmp = (a, b) -> b.compareToIgnoreCase(a);
- Comparator<String> cmp = String::compareToIgnoreCase;// 特定的类型的方法
- Arrays.sort(data, cmp);
- System.out.println(Arrays.toString(data));
- }
- }
三、系统提供的函数式接口
从JDK 1.8开始为了方便用户开发专门提供了一个新的包:java.util.function,在这个包里面针对于用户有可能出现的函数式接口做了一个公共定义。
在java.util.function包之中最为核心的只有四个接口:
·功能型接口:Function;
·消费型接口:Consumer;
·供给型接口:Supplier;
·断言型接口:Predicate。
1、功能型接口 Function
@FunctionalInterface
public interface Function<T, R> {
publicR apply(T t) ;// 接收数据而后返回处理结果
}
范例:实现功能型接口的引用
·本次引用Integer类的parseInt()方法,这个方法要求接收String型数据,而后返回int型数据。
- package cn.com.java;
- import java.util.function.Function;
- public class TestDemo {
- public static void main(String[] args) {
- Function<String, Integer> fun= Integer::parseInt; // parseInt()方法为static型
- int num= fun.apply("100");
- System.out.println(num* 2);
- }
- }
也就是说这种既能接收数据,又能返回结果的方法,都用Function接口定义
2、消费型接口 Consumer
@FunctionalInterface
public interface Consumer<T> {
publicvoidaccept(T t); // 只是接收数据,并没有返回值存在
}
范例:使用消费型接口,引用System.out.println()这个方法,只接收数据但是没有返回值
- package cn.com.java;
- import java.util.function.Consumer;
- public class TestDemo {
- public static void main(String[] args) {
- Consumer<String> con= System.out::println;
- con.accept("Hello World !");
- }
- }
@FunctionalInterface
public interface Supplier<T> {
publicT get();
}
本接口的方法没有参数,但是却可以返回数据。
范例:设置供给型方法引用,本次引用System.currentTimeMillis();
- package cn.com.java;
- import java.util.function.Supplier;
- public class TestDemo {
- public static void main(String[] args) {
- Supplier<Long> sup= System :: currentTimeMillis;
- System.out.println(sup.get());
- }
- }
@FunctionalInterface
public interfacePredicate<T> {
public boolean test(T t);
}
现在是一个判断操作,那么如果是判断操作,就使用正则验证。
范例:引用String类中的matches()方法
- package cn.com.java;
- import java.util.function.Predicate;
- public class TestDemo {
- public static void main(String[] args) {
- String str= "100";// String类对象
- Predicate<String> pre= str::matches;
- System.out.println(pre.test("\\d+"));
- }
- }
以上是四个核心接口,实际上这四个接口会了,那么整个java.util.function包之中的接口就明白怎么使了。
例如,随便找一个BiFunction接口,这个接口与Function相似的,此接口定义如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {
publicR apply(T t, U u);
}
虽然与Function接口不同,但是这里面可以设置两个参数。
范例:利用BiFunction接口引用一个方法
·引用方法,String类的replaceAll()方法。
- package cn.com.java;
- import java.util.function.BiFunction;
- public class TestDemo {
- public static void main(String[] args) {
- String str= "hello";
- BiFunction<String, String, String> bf= str::replaceAll;
- System.out.println(bf.apply("l", "_"));
- }
- }
整个包之中的接口的功能都是类似的,实际上四个会了,所有的也就都会了。
之所以系统会提供内建的函数式接口,那么就会在大量的系统类库之中使用它。在Collection接口里面新定义了一个forEach()方法:default void forEach(Consumer<? super T> action)
此方法是一个default方法可以直接利用接口对象调用,同时这个方法里面接收有一个消费型接口。
范例:List遍历输出
- package cn.com.java;
- import java.util.ArrayList;
- import java.util.List;
- public class TestDemo {
- public static void main(String[] args) {
- List<String> pro= new ArrayList<String>();
- pro.add("java");
- pro.add("android");
- pro.add("pl/sql");
- pro.add("ios");
- pro.add("python");
- pro.add("node.js");
- // pro.forEach((s) -> System.out.println(s));// 如果只有一个参数,直接编写也可以,不用写()了
- pro.forEach(s-> System.out.println(s));
- }
- }
在以后学习系统类的时候会大量的使用到在java.util.function包之中定义的函数式接口,所以掌握这四个接口就是掌握了整个包的使用。
四、数据流
范例:操作Stream
- import java.util.ArrayList;
- import java.util.List;
- import java.util.stream.Stream;
- public class TestDemo3 {
- public static void main(String[] args) {
- List<String> all= new ArrayList<String>();
- all.add("hello");
- all.add("word");
- all.add("test");
- Stream<String> stream= all.stream(); // 将集合变为了数据流的形式
- System.out.println(stream.count());// 取得数据流的长度}}
- }
- }
·取得Stream集合:static <T> Stream<T> of(T... values);
·支持forEach输出:public void forEach(Consumer<? super T> action)。
- import java.util.stream.Stream;
- public class TestDemo3 {
- public static void main(String[] args) {
- Stream<String> stream= Stream.of("yootk", "mldn", "mldnjava"); // 准备好数据
- stream.forEach(System.out::println); // 方法引用
- }
- }
Java8之后最大的特征是支持了数据的分析操作,所以有了Stream接口对象最大的好处是在于可以进行集合的处理。在函数式接口里面有一个Predicate,这个接口可以负责断言操作。
范例:新的判断模式
- public static void main(String[] args) {
- List<String> pros= new ArrayList<String>();
- pros.add("java");
- pros.add("android");
- pros.add("ios");
- pros.add("python");
- pros.add("node.js");
- filter(pros, (str) -> str.contains("a"));
- }
- public static void filter(List<String> temp, Predicate<String> pre) {
- temp.forEach((s) -> {if(pre.test(s)) {
- System.out.println("data = "+ s);
- }
- });
- }
感觉上和输出没有任何的区别,但是在这个时候是属于Java8的输出模式。但是现在有人觉得,这种代码看起来很乱,相当于结合两个函数式接口。
范例:执行集合数据过滤
·集合过滤操作:public Stream<T> filter(Predicate<? super T> predicate);
- import java.util.ArrayList;
- import java.util.List;
- public class TestDemo3 {
- public static void main(String[] args) {
- List<String> pros= new ArrayList<String>();
- pros.add("java");
- pros.add("android");
- pros.add("ios");
- pros.add("python");
- pros.add("node.js");
- pros.stream().filter((x) -> x.contains("a")).forEach(System.out::println);
- }
- }
此时虽然简化了过滤的操作,但是感觉到,这种操作还是完全可以利用Iterator输出实现。还是觉得没用。
例:取得过滤后的子集合
在Stream接口里面有一个收集器:public <R,A> R collect(Collector<? super T,A,R> collector);
- import java.util.ArrayList;
- import java.util.List;
- import java.util.stream.Collectors;
- public class TestDemo3 {
- public static void main(String[] args) {
- List<String> pros= new ArrayList<String>();
- pros.add("java");
- pros.add("android");
- pros.add("ios");
- pros.add("python");
- pros.add("node.js");
- List<String> subList= pros.stream().filter((x) -> x.contains("a")).collect(Collectors.toList()); // 将满足条件的集合变为了一个新的集合
- subList.forEach(System.out::println);
- }
- }
除了可以进行数据的判断之外,那么也可以进行数据的处理,逐个进行处理。
范例:将包含的字符串数据进行小写变大写
·如果要想针对于每个数据处理:public <R> Stream<R> map(Function<? super T,? extends R> mapper);
- import java.util.ArrayList;
- import java.util.List;
- import java.util.stream.Collectors;
- public class TestDemo3 {
- public static void main(String[] args) {
- List<String> pros= new ArrayList<String>();
- pros.add("java");
- pros.add("android");
- pros.add("android");
- pros.add("ios");
- pros.add("ios");
- pros.add("python");
- pros.add("python");
- pros.add("python");
- pros.add("node.js");
- List<String> subList= pros.stream().map((x) -> x.toUpperCase()).collect(Collectors.toList()); // 将满足条件的集合变为了一个新的集合
- subList.forEach(System.out::println);
- }
- }
也就是说这个时候可以发现,map()方法可以将每一条数据分别进行处理,而后里面会包含原始的内容。
范例:消除重复数据
·在Stream接口里面提供了重复数据清除的方法:public Stream<T> distinct()。
- import java.util.ArrayList;
- import java.util.List;
- import java.util.stream.Collectors;
- public class TestDemo3 {
- public static void main(String[] args) {
- List<String> pros= new ArrayList<String>();
- pros.add("java");
- pros.add("android");
- pros.add("android");
- pros.add("ios");
- pros.add("ios");
- pros.add("python");
- pros.add("python");
- pros.add("python");
- pros.add("node.js");
- List<String> subList= pros.stream().map((x) -> x.toUpperCase()).distinct().collect(Collectors.toList()); // 将满足条件的集合变为了一个新的集合
- subList.forEach(System.out::println);
- }
- }
这个时候已经可以成功的消除掉了重复的数据内容。
在Stream接口里面也提供了一些数据的处理方法:
·判断集合中的全部数据:public boolean allMatch(Predicate<? super T> predicate);
·判断集合中的任意一个数据:public boolean anyMatch(Predicate<? super T> predicate);
·不匹配:public boolean noneMatch(Predicate<? super T> predicate)。
if(pros.stream().allMatch((s) -> s.contains("a"))) { System.out.println("集合中的全部内容都包含有字母a!"); } if(pros.stream().anyMatch((s) -> s.contains("a"))) { System.out.println("集合中的全部内容都包含有字母a!"); }在JDK 1.8之前,如果匿名内部类要想访问方法中的参数,则参数前必须加上final关键字,但是从JDK 1.8开始提供的新特性,匿名内部类访问方法参数的时候可以不加上final关键字了。
以上的判断你判断的全部都是单个条件,如果现在有多个条件呢?如果是判断,则一定使用断言式函数接口:Predicate,在这个接口里面提供有一些连接的操作方法:
·与操作:default Predicate<T> and(Predicate<? super T> other);
·或操作:default Predicate<T> or(Predicate<? super T> other);
- Predicate<String> condA= (str) -> str.contains("a");
- Predicate<String> condB= (str) -> str.length() == 3;
- pros.stream().filter(condA.or(condB)).forEach(System.out::println);
·设置为并行处理:public S parallel();
·设置为串行处理:public S sequential()。
范例:采用并行处理
- pros.stream().filter(condA.or(condB)).parallel().forEach(System.out::println);
通过以上的讲解,实际上就应该清楚一点:所有的操作像map()、filter()这样操作都属于中间操作,而像collectors()、forEach()一定都属于结尾操作。
在BaseStream接口里面一共定义了四个子接口,以IntStream为例。
范例:使用IntStream接口
·生成整型的数据流:static IntStream range(int startInclusive, int endExclusive)。
- IntStream stream= IntStream.range(0, 30);stream.forEach(System.out::println);
从JDK 1.8开始一些类里面也增加了改变,例如:java.util.Random类,在这个类里面增加了新的方法:
·返回一个整型数据流:public IntStream ints()。
- newRandom().ints().limit(10).forEach(System.out::println);
map和reduce函数的使用
范例:定义一个购物车的类
- import java.util.ArrayList;
- import java.util.List;
- class Car {
- private String pname;
- private Integer amount;
- private Double price;
- public Car() {
- super();
- }
- public Car(String pname, Integer amount, Double price) {
- super();
- this.pname= pname;
- this.amount= amount;
- this.price= price;
- }
- public String getPname() {
- return pname;
- }
- public void setPname(String pname) {
- this.pname= pname;
- }
- public Integer getAmount() {
- return amount;
- }
- public void setAmount(Integer amount) {
- this.amount= amount;
- }
- public Double getPrice() {
- return price;
- }
- public void setPrice(Double price) {
- this.price= price;
- }
- }
- public class TestDemo3 {
- public static void main(String[] args) {
- List<Car> all= new ArrayList<Car>();
- all.add(new Car("Java开发", 200, 79.8));
- all.add(new Car("Java WEB开发", 500, 69.8));
- all.add(new Car("Android开发", 700, 89.8));
- all.add(new Car("Oracle开发", 300, 88.8));
- all.add(new Car("MongoDB开发", 610, 98.8));
- all.stream().map((myCar) -> {
- System.out.print("书名:"+ myCar.getPname() + ",购买总价:");
- return myCar.getAmount() * myCar.getPrice();
- }).forEachOrdered(System.out::println); // 统计每本书的各自花费
- }
- }
如果说使用map()方法实现了数据的重新组合,那么reduce()就是将集合中的所有的数据变为一个结果,例如:类似于SQL中的sum()、avg()、count()函数的功能。
·reduce()方法:public T reduce(T identity, BinaryOperator<T> accumulator)。
- double result= all.stream().map((myCar) -> {
- // System.out.print("书名:" + myCar.getPname() + ",购买总价:");
- return myCar.getAmount() * myCar.getPrice();
- }).reduce((sum, carPrice) -> sum+ carPrice).get();
- System.out.println("购买总金额:"+ result);
如果要进行统计,可能会包含:总和、最大值、最小值、平均值、数量。
Stream.of("AAA","BBB","CCC").parallel().forEach(s->System.out.println("Output:"+s));
//区别于forEach的是并行的时候有序
Stream.of("AAA","BBB","CCC").parallel().forEachOrdered(s->System.out.println("Output:"+s));
在Stream接口里面提供了相应的操作:
·处理double数据:public DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
·处理int数据:public IntStream mapToInt(ToIntFunction<? super T> mapper);
·处理long数据:public LongStream mapToLong(ToLongFunction<? super T> mapper)。
在每个返回的接口里面都提供了如下的统计操作方法:
·double数据统计(DoubleStream):public Double SummaryStatistics summaryStatistics()·
int数据的统计(IntStream):public IntSummaryStatistics summaryStatistics()
·long数据的统计(LongStream):public LongSummaryStatistics summaryStatistics()
这些类里面都提供有一系列的getXxx()方法用于统计相关信息。
范例:进行reduce功能实现
- DoubleSummaryStatistics result= all.stream().mapToDouble((myCar) -> {
- return myCar.getAmount() * myCar.getPrice();
- }).summaryStatistics();
- System.out.println("统计量:"+ result.getCount());
- System.out.println("最大值:"+ result.getMax());
- System.out.println("最小值:"+ result.getMin());
- System.out.println("总和:"+ result.getSum());
- System.out.println("平均值:"+ result.getAverage());