0048java8的新特性

Posted 1572662

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0048java8的新特性相关的知识,希望对你有一定的参考价值。

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,所以o1o2前的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);
        //此处的一行代码等价于上边的两行代码,先调用con1accept方法,再调用con2accept方法
       
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);
        //由于stream1stream2流中的存放的数据类型不一致,所以新定义的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来应用本类的成员方法

以上是关于0048java8的新特性的主要内容,如果未能解决你的问题,请参考以下文章

java8的新特性

Java8-11的新特性和理解的误区

深度分析:java8的新特性lambda和stream流,看完你学会了吗?

深度分析:java8的新特性lambda和stream流,看完你学会了吗?

Java 8的新特性

Java8的新特性