一、Lamda表达式
在JAVA8新特性中主要一点就是支持了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表达式后
package cn.com.java;
@FunctionalInterface
interface Message {// 这适宜个一个接口
public void print(String str);// 里面只有一个抽象方法
}
public class TestDemo {
public static void main(String[] args) {
Message msg = (s) -> System.out.println(s);
msg.print("Hello word");
}
}
其中(s)要与接口中print方法的参数数量相同,这种使用的话就只允许接口中只有一个抽象方法,所以用到了@FunctionalInterface注解。
1、在使用Lamda表达式中有三种语法:
1.1、使用单行语句就是上面的例子;
(params) -> 单行语句;
1.2、使用表达式
(params) -> 表达式;
package cn.com.java;
@FunctionalInterface
interface Message {// 这适宜个一个接口
public int print(int a, int b);// 里面只有一个抽象方法
}
public class TestDemo {
public static void main(String[] args) {
Message msg = (a, b) -> a + b; // 单行语句中可以不用使用return声明返回值,如果只有一个参数的话(a,b)中括号也可以不需要
msg.print(1, 2);
}
}
1.3、多行语句
(params) -> {多行语句};
package cn.com.java;
@FunctionalInterface
interface Message {// 这适宜个一个接口
public int print(int a, int b);// 里面只有一个抽象方法
}
public class TestDemo {
public static void main(String[] args) {
Message msg = (a, b) ->{
int sum = a+b;
return sum;
};
msg.print(1, 2);
}
}
2、JAVA8中接口可以扩充普通方法和静态方法
增加普通方法的话使用default声明
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");//静态方法由类名称调用
}
}
二、方法引用
所谓的方法引用实际上指的是将一个特定类的方法功能映射过来,而后通过接口中的方法,利用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) {
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"));
}
}
3、类中普通方法的引用:实例化对象名称:: 普通方法
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"));
}
}
4、特定类型的任意方法引用:类名称:: 方法名称。
在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 = 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 !");
}
}
3、供给型接口 Supplier
@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());
}
}
4、·断言型接口 Predicate
@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包之中定义的函数式接口,所以掌握这四个接口就是掌握了整个包的使用。
四、数据流
类集自从JDK 1.2开始引入之后,一开始给予我们的特征是动态对象数组,后来有了泛型(JDK 1.5),让类集操作更加的安全,再后来到了JDK 1.8的时代,类集又变为了重要的批量数据处理的容器,而这个容器的操作实现就是利用数据流完成的。
所以在JDK 1.8版本之后专门提供了一个数据流的工具包:java.util.stream。而在这个包里面最需要关注的是Stream接口,此接口是BaseStream的子接口。
在BaseStream接口里面会包含有:DoubleStream、IntStream、LongStream、Stream<T>。
范例:操作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());// 取得数据流的长度}}
}
}
通过本程序应该清楚一点:Collection集合里面已经支持了Stream接口对象的取得。这一点可以为随后要讲解的集合的数据分析带来帮助。除了Collection集合可以为Stream接口实例化之外,那么实际上也可以利用Stream接口里面提供的方法完成:
·取得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); // 方法引用
}
}
现在可以利用Collection接口实例化Stream接口,也可以利用Stream接口里面的of()方法,利用static方法取得Stream接口对象,但是折腾半天,感觉它就是一个集合个数(Collection接口的size())取得以及集合的输出(Iterator)。
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);
幸运的是在Java之中,数据流的处理里面所有的并发操作完全不需要用户来关心,都可以自己处理。并且最有意思的是可以并行和串行互相切换。
通过以上的讲解,实际上就应该清楚一点:所有的操作像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);
Stream接口可以保存各种类型,而IntStream里面只能够保存int型数据。
从JDK 1.8开始一些类里面也增加了改变,例如:java.util.Random类,在这个类里面增加了新的方法:
·返回一个整型数据流:public IntStream ints()。
newRandom().ints().limit(10).forEach(System.out::println);
随着大数据的发展,基本上Java的开发也向大数据靠拢,所有的概念也都是相通的。
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()方法的功能就是针对于集合的每个数据进行了处理。
如果说使用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接口里面提供了相应的操作:
·处理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());