java8的新特性主要是Lambda表达式和流式编程,前提都是需要一个函数式接口。
---------------------函数式接口------------------
1、函数式接口的定义
函数式接口在java中是指有且只有一个抽象方法的接口。
java中函数式编程的体现就是Lambda表达式。
语法糖:是指使用更加方便,但是原理不变的代码语法。Lambda可以被当做是匿名内部类的“语法糖”。
2、函数式接口的使用
可以做为方法的参数或者返回值使用
接口:
packagefunctionalInterface;
/*
* 1、有且只有一个抽象方法,但可以包含其他非抽象方法* 2、@FunctionalInterface注解用于检查是否有且只有一个抽象方法*如果没有抽象方法或者抽象方法多于一个,都会编译不通过* */@FunctionalInterface
public interfaceMyFunctionalInterface {
//没有用abstract指定的方法也不行//public void method1();
//定义一个抽象方法public abstract voidmethod2();
}
实现类:
packagefunctionalInterface;
public classMyFunctionalInterfaceImpl implementsMyFunctionalInterface {
@Override
public voidmethod2() {
System.out.println("函数式接口的实现类MyFunctionalInterfaceImpl");
}
}
使用1:做为方法参数packagefunctionalInterface;
/*
*函数式接口的使用:可以做为方法的参数或者返回值* */public classDemo {
//函数式接口做为方法参数public voidshow(MyFunctionalInterface myfi){
myfi.method2();
}
public static voidmain(String[] args) {
Demo demo = newDemo();
//1、调用方法时,传递函数式接口的具体实现类demo.show(newMyFunctionalInterfaceImpl());
//2、调用方法时,传递函数式接口的匿名内部类demo.show(newMyFunctionalInterface() {
@Override
public voidmethod2() {
System.out.println("使用匿名内部类实现");
}
});
//3、调用方法时,使用Lambda表达式demo.show(()->{
System.out.println("使用Lambda表达式实现");
});
//4、调用方法时,使用简化的Lambda表达式//由于方法实现只有一行代码,所以可以省略代表方法体的大括号和这行代码后的分号demo.show(()->System.out.println("使用简化的Lambda表达式实现"));
}
}
使用2:做为方法的返回值
packagefunctionalInterface;
/*
*函数式接口做为方法的返回值使用的用法* */public classDemo2 {
publicMyFunctionalInterface show1(){
return newMyFunctionalInterfaceImpl();
}
publicMyFunctionalInterface show2(){
return newMyFunctionalInterface() {
@Override
public voidmethod2() {
System.out.println("使用匿名内部类做为返回值");
}
};
}
publicMyFunctionalInterface show3(){
return()->{
System.out.println("使用Lambda表达式做为返回值");
};
}
publicMyFunctionalInterface show4(){
return()->System.out.println("使用简化的Lambda表达式做为返回值");
}
public static voidmain(String[] args) {
Demo2 demo2 = newDemo2();
MyFunctionalInterface myfi = demo2.show1();
myfi.method2();
myfi = demo2.show2();
myfi.method2();
myfi = demo2.show3();
myfi.method2();
myfi = demo2.show4();
myfi.method2();
}
}
小总结:在步骤2的基础上打开class对应的文件夹,发现会包含对应的匿名内部类,所以Lambda虽然可以被当做是匿名内部类的“语法糖”,但是由于Lambda表达式不会生成匿名内部类,所以不占用空间,性能上是由于匿名内部类的。
-----------------------函数式编程-----------------
1、Lambda表达式的延迟特性
在程序执行过程中,有些代码的执行结果不一定被使用,从而造成性能浪费而Lambda表达式是延迟执行的,正好可以做为这种场景的解决方案提升性能。
2、案例
效率低的写法:
packagelazyExcute;
public classDemo1 {
public voidshowInfo(booleanprintFlag,String str){
if(printFlag == true){
System.out.println(str);
}
}
//这种调用,会先拼接字符串,即使最后不满足条件,没有打印拼接后的字符串,也是进行了拼接//造成了性能浪费public static voidmain(String[] args) {
Demo1 demo1 = newDemo1();
String a = "Hello";
String b = "World";
demo1.showInfo(false,a+b);
}
}
效率高的写法(使用Lambda的延迟执行的特性):
packagelazyExcute;
@FunctionalInterface
public interfaceInfoBuilder {
publicString buildInfo();
}
packagelazyExcute;
public classDemo2Lambda {
public voidshowInfo(booleanprintFlag,InfoBuilder infoBuilder){
if(printFlag == true){
System.out.println(infoBuilder.buildInfo());
}
}
//此种方法,只有满足了打印条件,才会进行字符串拼接,避免了不必要代码的执行,提高了性能public static voidmain(String[] args) {
Demo2Lambda demo2Lambda = newDemo2Lambda();
String a = "Hello";
String b = "World";
demo2Lambda.showInfo(false,()->{
System.out.println("满足条件才会执行该方法");
returna+b;
});
}
}
函数式接口做为方法返回值的另一个参考案例:
packagelazyExcute;
importjava.util.Arrays;
importjava.util.Comparator;
public classComparatorLambda {
public staticComparator getComparator(){
//使用匿名内部类/*return new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};*/
//使用Lambda表达式/*return (String o1,String o2)->{
return o1.length() - o2.length();
};*/
//使用简化的Lambda表达式//由于返回类型是Comparator,说明兑现类型是String,所以o1和o2前的String类型可省略//由于方法体中只有一行代码,所以大括号可省略//由于只有一行代码,方法体中的return也省略return(o1,o2)->o1.length()-o2.length();
}
public static voidmain(String[] args) {
String[] arr = {"aaa","b","cc","ddddddddd"};
Arrays.sort(arr,getComparator());
String str = Arrays.toString(arr);
System.out.println(str);
}
}
---------------常用函数式接口-----------------
jdk提供了大量的函数式接口,主要在java.util.function包中
1)Supplier函数式接口
Supplier{T get()}
即泛型定义的什么类型,get()就返回什么类型
需求:Supplier做为方法的参数,通过Lambda表达式求int型数组的最大值
packageprogram;
importjava.util.function.Supplier;
public classgetMaxLambda {
public static intgetMax(Supplier function){
returnfunction.get();
}
public static voidmain(String[] args) {
int[] arr= {25,3,98,0,-1,196,77,285,4};
//里边变量名为max,则外边变量名不能也为maxintresultMax = getMax(()->{
intmax = arr[0];
for(inti=1;i
if(max
max = arr[i];
}
}
returnmax;
});
System.out.println(resultMax);
}
}
小总结:写Lambda表达式的时候都是先写()->{},然后再去填写小阔靠中的参数和大括号中的方法实现。
2)Consumer接口
会做为stream流的forEach方法的参数
public interfaceConsumer {voidaccept(T t); }
即泛型定义什么类型的数据,方法就接收什么类型的参数做为数据来处理
案例:使用Consumer函数式接口通过Lambda表达式对字符串实现反转输出
packageprogram;
importjava.util.function.Consumer;
public classConsumerLambda {
public static voidconsumeString(String str, Consumer consumer){
consumer.accept(str);
}
public static voidmain(String[] args) {
ConsumerLambda.consumeString("我爱你",(name)->{
String newStr = newStringBuilder(name).reverse().toString();
System.out.println(newStr);
});
}
}
Consumer接口中的默认方法andThen
andThen是连接两个接口的意思
例如:
Consumer con1
Consumer con2
String s = “hello world”
con1.accept(s)
con2.accept(s)
等价于con1.andThen(con2).accept(s);意思是连接两个接口再进行消费,写在前边的先消费,即con1先消费、con2再消费。
代码如下:
packageprogram;
importjava.util.function.Consumer;
public classConsumerAndThen {
public static voidconsumeString(String str, Consumer con1,Consumer con2){
con1.accept(str);
con2.accept(str);
//此处的一行代码等价于上边的两行代码,先调用con1的accept方法,再调用con2的accept方法con1.andThen(con2).accept(str);
}
public static voidmain(String[] args) {
ConsumerAndThen.consumeString("Hello",
(str1)->{
System.out.println(str1.toUpperCase());
},
(str2)->{
System.out.println(str2.toLowerCase());});
}
}
3)Predicate函数式接口
会做为Stream类的filter方法的参数使用
public interfacePredicate {
booleantest(T t);
}
4)Function函数式接口
会做为Stream类中的map方法的参数使用
public interfaceFunction {
R apply(T t);
}
-----------------------流式思想-----------------------
java8引入全新的流式概念stream,用于解决已有集合类库的弊端。
stream是Lambda的衍生物。
packageprogram;
importjava.util.ArrayList;
importjava.util.List;
public classStreamTest {
//普通的遍历集合public static voidtest1(){
List list = newArrayList<>();
list.add("王炸");
list.add("王武云");
list.add("孙猴子");
list.add("猪八戒");
list.add("王大");
//过滤集合,只保留“王”开头的名字List list1 = newArrayList<>();
for(String str: list){
if(str.startsWith("王")){
list1.add(str);
}
}
//过滤集合,只保留长度为2的名字List list2 = newArrayList<>();
for(String str:list1){
if(str.length() == 2){
list2.add(str);
}
}
//统计集合的长度并输出intcount = list2.size();
System.out.println(count);
}
//使用流的方式实现test1方法实现的功能public static voidtest2(){
List list = newArrayList<>();
list.add("王炸");
list.add("王武云");
list.add("孙猴子");
list.add("猪八戒");
list.add("王大");
//过滤集合,只保留“王”开头的名字//过滤集合,只保留长度为2的名字//统计集合的长度并输出//1、将集合转换为stream流//2、过滤符合条件的数据,filter方法的参数为函数式接口Predict,该接口需要实现一个根据参数返回boolean值的方法//3、最后通过count()方法统计集合大小,由于Lambda的延迟特性,只有真正调用count()方法时前边的过滤条件才被执行,性能提高intcount = (int)list.stream().filter(str->str.startsWith("王"))
.filter(str -> str.length() == 2)
.count();
System.out.println(count);
}
public static voidmain(String[] args) {
test1();
test2();
}
}
获取流Stream的两种方式:
1) 通过集合的stream()方法获取
2) 通过Stream类的静态方法of()获取
Stream方法又主要分为两类:
1) 延迟方法:返回类型仍然为Stream的子类
2) 终结方法:返回类型不再是Stream,只有count()和forEach()这两个方法
Stream的forEach方法的参数是Consumer函数式接口
案例代码如下:
packageprogram;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.stream.Stream;
public classStreamForEach {
public static voidforEachTest(){
List list = newArrayList();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
//forEach的参数为Consumer函数式接口//如果是别人写的代码,反向解析含义:// name没有类型,说明类型与forEach遍历的类型相同//没有方法体,说明方法体中只有一行代码//至于这一行代码是普通的逻辑代码,还是返回值,需要看该函数式接口中定义的方法的返回类型是void还是有返回类型// list.stream().forEach(name ->System.out.println(name));Stream stream = list.stream();
stream.forEach(name ->System.out.println(name));
//stream流只能被消费一次,如果下边的代码也打开就会报错//System.out.println(stream.count());}
public static voidmain(String[] args) {
forEachTest();
}
}
stream流的特点:stream流属于管道流,只能被消费一次,第一个stream执行完毕后,数据就会流转到下一个stream,第一个stream就会关闭,如果再调用第一个stream的方法就会报错。
filter方法:进行过滤,参数为Predicate函数式接口
map方法:
map方法定义如下:
Stream map(Function superT, ? extendsR> mapper);
将当前流中T类型的数据转换为B类型的数据存放到新的stream中返回,利用的是参数Function实现的,Function就是一个函数式接口,其抽象方法就是接收一种类型的数据转换为另一种类型的数据返回
案例:将字符串数组转换为数字数组后,元素依次乘以2后输出
packageprogram;
importjava.util.List;
importjava.util.stream.Stream;
public classStreamMap {
public static voidmapTest(){
//获取String类型的streamStream stream1 = Stream.of("1","2","3");
//将String类型的stream转换为map类型的StreamStream stream2 = stream1.map((str)->{
returnInteger.parseInt(str);
});
//对stream2中的数据进行遍历stream2.forEach((Integer num)->{
System.out.println(num*2);
});
}
public static voidmain(String[] args) {
mapTest();
}
}
limit方法:可以对Stream流中的元素进行截取,只取前n个
skip方法:跳过stream流中的前n个元素,将剩余的元素以新的流的形式返回
concat方法:如果有两个流,希望合并为一个流,可以使用Stream接口的静态方法concat
packageprogram;
importjava.util.stream.Stream;
public classStreamConcat {
public static voidtestConcat() {
Stream stream1 = Stream.of("老虎", "熊猫");
Stream stream2 = Stream.of(1, 2);
//由于stream1和stream2流中的存放的数据类型不一致,所以新定义的stream没有用泛型指定是任何一种类型Stream newStream = Stream.concat(stream1, stream2);
newStream.forEach((value) -> {
System.out.println(value);
});
}
public static voidmain(String[] args) {
testConcat();
}
}
---------------Lambda表达式的优化:方法的引用--------------
函数式接口代码:
packagereference;
@FunctionalInterface
public interfacePrintable {
public voidprintInfo(String s);
}
packagereference;
public classDemo1Printable {
public static voidprintString(Printable printable){
printable.printInfo("helloWorld");
}
public static voidmain(String[] args) {
//Lambda表达式printString((String str)->{
System.out.println(str);
});
//方法的引用:应用System.out对象的println方法,需要这个对象和这个方法已经存在//传递给printString方法的参数就会做为println方法引用的参数,//需要该参数类型可以直接做为方法引用需要的参数类型才可以,否则会抛出异常printString(System.out::println);
}
}
方法的引用
1) 通过类名引用静态方法 System.out::println
2) 通过对象引用成员方法 obj::printInfo obj是创建出的对象
3) 使用构造方法引用 Person::new 等价于Lambda表达式实现函数式接口中的方法时的写法为:return new Person();
并且可以通过super来引用父类的成员方法,通过this来应用本类的成员方法