java8的新特性主要是Lambda表达式和流式编程,前提都是需要一个函数式接口。
---------------------函数式接口------------------
1、函数式接口的定义
函数式接口在java中是指有且只有一个抽象方法的接口。
java中函数式编程的体现就是Lambda表达式。
语法糖:是指使用更加方便,但是原理不变的代码语法。Lambda可以被当做是匿名内部类的“语法糖”。
2、函数式接口的使用
可以做为方法的参数或者返回值使用
接口:
package functionalInterface;
/*
* 1、有且只有一个抽象方法,但可以包含其他非抽象方法
* 2、@FunctionalInterface注解用于检查是否有且只有一个抽象方法
* 如果没有抽象方法或者抽象方法多于一个,都会编译不通过
* */
@FunctionalInterface
public interface MyFunctionalInterface {
//没有用abstract指定的方法也不行
//public void method1();
//定义一个抽象方法
public abstract void method2();
}
实现类:
package functionalInterface;
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
@Override
public void method2() {
System.out.println("函数式接口的实现类MyFunctionalInterfaceImpl");
}
}
使用1:做为方法参数package functionalInterface;
/*
* 函数式接口的使用:可以做为方法的参数或者返回值
* */
public class Demo {
//函数式接口做为方法参数
public void show(MyFunctionalInterface myfi){
myfi.method2();
}
public static void main(String[] args) {
Demo demo = new Demo();
//1、调用方法时,传递函数式接口的具体实现类
demo.show(new MyFunctionalInterfaceImpl());
//2、调用方法时,传递函数式接口的匿名内部类
demo.show(new MyFunctionalInterface() {
@Override
public void method2() {
System.out.println("使用匿名内部类实现");
}
});
//3、调用方法时,使用Lambda表达式
demo.show(()->{
System.out.println("使用Lambda表达式实现");
});
//4、调用方法时,使用简化的Lambda表达式
//由于方法实现只有一行代码,所以可以省略代表方法体的大括号和这行代码后的分号
demo.show(()->System.out.println("使用简化的Lambda表达式实现"));
}
}
使用2:做为方法的返回值
package functionalInterface;
/*
* 函数式接口做为方法的返回值使用的用法
* */
public class Demo2 {
public MyFunctionalInterface show1(){
return new MyFunctionalInterfaceImpl();
}
public MyFunctionalInterface show2(){
return new MyFunctionalInterface() {
@Override
public void method2() {
System.out.println("使用匿名内部类做为返回值");
}
};
}
public MyFunctionalInterface show3(){
return ()->{
System.out.println("使用Lambda表达式做为返回值");
};
}
public MyFunctionalInterface show4(){
return ()->System.out.println("使用简化的Lambda表达式做为返回值");
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
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、案例
效率低的写法:
package lazyExcute;
public class Demo1 {
public void showInfo(boolean printFlag,String str){
if(printFlag == true){
System.out.println(str);
}
}
//这种调用,会先拼接字符串,即使最后不满足条件,没有打印拼接后的字符串,也是进行了拼接
//造成了性能浪费
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
String a = "Hello";
String b = "World";
demo1.showInfo(false,a+b);
}
}
效率高的写法(使用Lambda的延迟执行的特性):
package lazyExcute;
@FunctionalInterface
public interface InfoBuilder {
public String buildInfo();
}
package lazyExcute;
public class Demo2Lambda {
public void showInfo(boolean printFlag,InfoBuilder infoBuilder){
if(printFlag == true){
System.out.println(infoBuilder.buildInfo());
}
}
//此种方法,只有满足了打印条件,才会进行字符串拼接,避免了不必要代码的执行,提高了性能
public static void main(String[] args) {
Demo2Lambda demo2Lambda = new Demo2Lambda();
String a = "Hello";
String b = "World";
demo2Lambda.showInfo(false,()->{
System.out.println("满足条件才会执行该方法");
return a+b;
});
}
}
函数式接口做为方法返回值的另一个参考案例:
package lazyExcute;
import java.util.Arrays;
import java.util.Comparator;
public class ComparatorLambda {
public static Comparator<String> getComparator(){
//使用匿名内部类
/*return new Comparator<String>() {
@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>,说明兑现类型是String,所以o1和o2前的String类型可省略
//由于方法体中只有一行代码,所以大括号可省略
//由于只有一行代码,方法体中的return也省略
return (o1,o2)->o1.length()-o2.length();
}
public static void main(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>{T get()}
即泛型定义的什么类型,get()就返回什么类型
需求:Supplier做为方法的参数,通过Lambda表达式求int型数组的最大值
package program;
import java.util.function.Supplier;
public class getMaxLambda {
public static int getMax(Supplier<Integer> function){
return function.get();
}
public static void main(String[] args) {
int[] arr= {25,3,98,0,-1,196,77,285,4};
//里边变量名为max,则外边变量名不能也为max
int resultMax = getMax(()->{
int max = arr[0];
for(int i=1;i<arr.length;i++){
if(max<arr[i]){
max = arr[i];
}
}
return max;
});
System.out.println(resultMax);
}
}
小总结:写Lambda表达式的时候都是先写()->{},然后再去填写小阔靠中的参数和大括号中的方法实现。
2)Consumer<T>接口
会做为stream流的forEach方法的参数
public interface Consumer<T> {void accept(T t); }
即泛型定义什么类型的数据,方法就接收什么类型的参数做为数据来处理
案例:使用Consumer函数式接口通过Lambda表达式对字符串实现反转输出
package program;
import java.util.function.Consumer;
public class ConsumerLambda {
public static void consumeString(String str, Consumer<String> consumer){
consumer.accept(str);
}
public static void main(String[] args) {
ConsumerLambda.consumeString("我爱你",(name)->{
String newStr = new StringBuilder(name).reverse().toString();
System.out.println(newStr);
});
}
}
Consumer接口中的默认方法andThen
andThen是连接两个接口的意思
例如:
Consumer<String> con1
Consumer<String> con2
String s = “hello world”
con1.accept(s)
con2.accept(s)
等价于con1.andThen(con2).accept(s);意思是连接两个接口再进行消费,写在前边的先消费,即con1先消费、con2再消费。
代码如下:
package program;
import java.util.function.Consumer;
public class ConsumerAndThen {
public static void consumeString(String str, Consumer<String> con1,Consumer<String > con2){
con1.accept(str);
con2.accept(str);
//此处的一行代码等价于上边的两行代码,先调用con1的accept方法,再调用con2的accept方法
con1.andThen(con2).accept(str);
}
public static void main(String[] args) {
ConsumerAndThen.consumeString("Hello",
(str1)->{
System.out.println(str1.toUpperCase());
},
(str2)->{
System.out.println(str2.toLowerCase());});
}
}
3)Predicate函数式接口
会做为Stream类的filter方法的参数使用
public interface Predicate<T> {
boolean test(T t);
}
4)Function函数式接口
会做为Stream类中的map方法的参数使用
public interface Function<T, R> {
R apply(T t);
}
-----------------------流式思想-----------------------
java8引入全新的流式概念stream,用于解决已有集合类库的弊端。
stream是Lambda的衍生物。
package program;
import java.util.ArrayList;
import java.util.List;
public class StreamTest {
//普通的遍历集合
public static void test1(){
List<String> list = new ArrayList<>();
list.add("王炸");
list.add("王武云");
list.add("孙猴子");
list.add("猪八戒");
list.add("王大");
//过滤集合,只保留“王”开头的名字
List<String> list1 = new ArrayList<>();
for(String str: list){
if(str.startsWith("王")){
list1.add(str);
}
}
//过滤集合,只保留长度为2的名字
List<String> list2 = new ArrayList<>();
for(String str:list1){
if(str.length() == 2){
list2.add(str);
}
}
//统计集合的长度并输出
int count = list2.size();
System.out.println(count);
}
//使用流的方式实现test1方法实现的功能
public static void test2(){
List<String> list = new ArrayList<>();
list.add("王炸");
list.add("王武云");
list.add("孙猴子");
list.add("猪八戒");
list.add("王大");
//过滤集合,只保留“王”开头的名字
//过滤集合,只保留长度为2的名字
//统计集合的长度并输出
//1、将集合转换为stream流
//2、过滤符合条件的数据,filter方法的参数为函数式接口Predict,该接口需要实现一个根据参数返回boolean值的方法
//3、最后通过count()方法统计集合大小,由于Lambda的延迟特性,只有真正调用count()方法时前边的过滤条件才被执行,性能提高
int count = (int)list.stream().filter(str->str.startsWith("王"))
.filter(str -> str.length() == 2)
.count();
System.out.println(count);
}
public static void main(String[] args) {
test1();
test2();
}
}
获取流Stream的两种方式:
1) 通过集合的stream()方法获取
2) 通过Stream类的静态方法of()获取
Stream方法又主要分为两类:
1) 延迟方法:返回类型仍然为Stream的子类
2) 终结方法:返回类型不再是Stream,只有count()和forEach()这两个方法
Stream的forEach方法的参数是Consumer函数式接口
案例代码如下:
package program;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamForEach {
public static void forEachTest(){
List<String> list = new ArrayList();
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 void main(String[] args) {
forEachTest();
}
}
stream流的特点:stream流属于管道流,只能被消费一次,第一个stream执行完毕后,数据就会流转到下一个stream,第一个stream就会关闭,如果再调用第一个stream的方法就会报错。
filter方法:进行过滤,参数为Predicate函数式接口
map方法:
map方法定义如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
将当前流中T类型的数据转换为B类型的数据存放到新的stream中返回,利用的是参数Function实现的,Function就是一个函数式接口,其抽象方法就是接收一种类型的数据转换为另一种类型的数据返回
案例:将字符串数组转换为数字数组后,元素依次乘以2后输出
package program;
import java.util.List;
import java.util.stream.Stream;
public class StreamMap {
public static void mapTest(){
//获取String类型的stream
Stream<String> stream1 = Stream.of("1","2","3");
//将String类型的stream转换为map类型的Stream
Stream<Integer> stream2 = stream1.map((str)->{
return Integer.parseInt(str);
});
//对stream2中的数据进行遍历
stream2.forEach((Integer num)->{
System.out.println(num*2);
});
}
public static void main(String[] args) {
mapTest();
}
}
limit方法:可以对Stream流中的元素进行截取,只取前n个
skip方法:跳过stream流中的前n个元素,将剩余的元素以新的流的形式返回
concat方法:如果有两个流,希望合并为一个流,可以使用Stream接口的静态方法concat
package program;
import java.util.stream.Stream;
public class StreamConcat {
public static void testConcat() {
Stream<String> stream1 = Stream.of("老虎", "熊猫");
Stream<Integer> stream2 = Stream.of(1, 2);
//由于stream1和stream2流中的存放的数据类型不一致,所以新定义的stream没有用泛型指定是任何一种类型
Stream newStream = Stream.concat(stream1, stream2);
newStream.forEach((value) -> {
System.out.println(value);
});
}
public static void main(String[] args) {
testConcat();
}
}
---------------Lambda表达式的优化:方法的引用--------------
函数式接口代码:
package reference;
@FunctionalInterface
public interface Printable {
public void printInfo(String s);
}
package reference;
public class Demo1Printable {
public static void printString(Printable printable){
printable.printInfo("helloWorld");
}
public static void main(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来应用本类的成员方法
原文:https://www.cnblogs.com/xiao1572662/p/12173604.html