Java8函数式编程入门

版权声明:需要引用、发表的朋友请与本人联系 https://blog.csdn.net/pbrlovejava/article/details/85226974


在这里插入图片描述

Java 8发布于2014年,距现在已经有4个年头了,被誉为继Java 5以来最具革命性的版本:新增Lambda表达式、引入流式API(Stream API)使Java具有函数式编程的风格、引入Optional类来防止空指针异常、新增接口的默认方法实现关键字default,让实现类默认拥有这个接口的钩子、新增::方法引用等等,在本文中都将一一介绍。

一、Lambda表达式

1.1、用Lambda表达式代替匿名内部类

Lambda是希腊字母λ的英文,Lambda表达式允许你通过表达式来代替匿名功能接口实现方法的这一系列繁琐的代码,起到精简结构的作用,
Lambda表达式的语法为:

(parameters) -> expression

(parameters) ->{ statements; }

以下是使用Runnable接口并且重写run方法的一个例子:

		//创建带缓冲池的Executor
        ExecutorService executorService = Executors.newCachedThreadPool();
        
        //原始写法
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("创建一个Runnable接口并且重写run方法");
            }
        });

        //使用Lambda表达式
        executorService.execute(()-> System.out.println("创建一个Runnable接口并且重写run方法"));

原本我们需要在execute()方法中新建一个Runnable接口并且重写run方法,在使用了Lambda表达式之后,可使用()-> {}代替Runnable接口的重写run方法,达到简化代码的作用。

1.2、Lambda表达式的参数类型判断

并且在Java 8中,Lambda表达式具有参数类型自动判断:

		 //等于return x + y
        (int x,int y)->x + y

        //也可以这样写,自动参数类型判断
        (x,y)->x + y
		
		//--------------------------------------------
			
			// 使用匿名内部类  
		 setOnAction(new EventHandler<ActionEvent>() {  
          @Override  
          public void handle(ActionEvent event) {  
              System.out.println("Hello World!");   
          }  
	    });  
   
		//使用Lambda表达式,自动将event判断为ActionEvent类型  
		btn.setOnAction(event -> System.out.println("HelloWorld!"));  

注意:在使用Lambda表达式去代替匿名接口重写方法时,这个接口有且只有唯一的一个抽象方法,不然Lambda表达式无法判断需要重写哪个方法。

二、Stream API

流式API是Java 8后引入的重要特性之一,它提供了一套完整的API,配合Lambda表达式使用可以简化代码,使Java代码拥有函数式编程的风格。

2.1、Stream API 和 Lambda Expression实现遍历的Demo

现在假设我们需要获取List< String >中首字母为a的数据,可以有以下两种写法,我们可以发现使用流式编程和Lambda表达式后会更加地凝练:
获取流-过滤流-遍历流。

		 String[] arrays = {"a1","a2","b","c"};
        //将arrays转化为List<String>
        List<String> stringList = Arrays.asList(arrays);

        //原始的处理方法
        for (String s : stringList) {
            if(s.charAt(0) == 'a'){
                System.out.println(s);
            }
        }
        
        //Stream API 结合 Lambda Expression
        stringList.stream()
                     .filter(s->s.charAt(0) == 'a')
                     .forEach(s-> System.out.println(s));


2.2、Stream常用方法

Stream是在Java 8之后更新的一种流,它不同于io流中的InputStream、OutputStream等,准确地说,这个位于java.util下的Stream和io中的流毫无关系。这里的Stream是数据流和对象流。

2.2.1、 of(T… values)

要把List、Set转化为数据流可以使用xxxList.stream()或者使用Stream.of(T…values),T…values代表着数组或者是不定数量的数据,它们会按顺序转换成数据流。

  • 获得Stream的三种方式
		//1
        Stream<String> stringStream1 = Stream.of(new String[]{"a","b","c"});
        //2
        Stream<String> stringStream2 = Stream.of("a","b","c");
        //3
        Stream<String> stringStream3 = stringList.stream();

2.2.2、filter(Predicate<? super T> predicate)

filter用以将流按需过滤成新的流,需要传入的参数为一个位于java.util.function下的Predicate接口并重写test方法去进行校验:

Stream newStream = stringList.stream().filter(new Predicate() {
            @Override
            public boolean test(Object s) {
                if (s.toString().charAt(0) == 'a') {
                    return true;
                }
                return false;
            }
        });

利用Lambda表达式,我们可以将上述代码简化为:

Stream newStream = stringList.stream().filter(s->s.charAt(0) == 'a');

2.2.3、 forEach(Consumer<? super T> action)

对此流的每一个元素进行操作,需要传入的参数为Consumer接口并且实现其accept方法:

stringStream.forEach(new Consumer(){
            @Override
            public void accept(Object s) {
                System.out.println(s);
            }
        });

结合Lambda表达式:

stringStream.forEach(s->System.out.println(s));

2.2.4、map(Function<? super T,? extends R> mapper)

map方法的作用是对Stream进行处理并且返回一个其他对象充当原Stream。

  • 将数据转换为大写
 String[] arrays = {"a1","a2","b","c"};
        //将arrays转化为List<String>
        List<String> stringList = Arrays.asList(arrays);
        List<String> collect = stringList.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        System.out.println(collect);

需要说明的是,map方法需要传入的参数是一个函数式方法,可以使用lambda表达式也可直接使用双冒号表达式(现在可以将双冒号表达式::理解为对象通过::调用方法并且传入当前的数据作为参数);而collect方法则是将经过map处理的流“收集”起来形成新的流,传递参数Collectors.toList()表示以List的形式转化流。

  • 删除末尾的数字
List<String> collect1 = Stream.of(arrays)
                .filter(v -> v != null)
                .map(v -> {
                    if (v.length() == 2) {
                        //删除尾部
                        v = v.substring(0, v.length()-1);
                    }
                    //返回最终结果
                    return v;
                })
                .collect(Collectors.toList());
        System.out.println(collect1);
  • 将Person对象转化为Student对象
    class Person implements Serializable {
            //编号
            private int id;
            //姓名
            private String name;
            //年龄
            private int age;
            //getter setter...
        }

        class Student implements Serializable {
            //学号
            private int schoolId;
            //姓名
            private String name;
            //年龄
            private int age;
            //getter setter...
        }


        public static void main(String[] args) {
            List<Person> personList = Arrays.asList(new Person(1, "lily", 18)
                , new Person(2, "arong", 19)
                , new Person(3, "joke", 20));
        //将PersonList转化为Studentlist
        List<Student> studentList = personList.stream().map(p -> {
            Student student = new Student();
            student.setSchoolId(3111000 + p.getId());
            student.setName(p.getName());
            student.setAge(p.getAge());
            //将转化好的student作为结果返回
            return student;
        }).collect(Collectors.toList());
        
        System.out.println(studentList.toString());

        }
    }

三、default关键字

在Java 8之前,接口中只能定义方法标签,而无法定义方法体,所有的方法体都由实现类去实现,如果需要在实现类中 使用父类的“钩子函数”,那么则需要使用抽象类去代替接口:

/**
 * @Author: arong
 * @Description: 抽象类Demo
 * @Date: 2018/12/23 13:05
 */
public abstract class AbstractTestClass {
    //钩子方法
    public void hookMethod(){
        System.out.println("抽象类默认实现的钩子方法");
    }

    //需要子类重写的方法
    public abstract void abstractMethod();
}

而在Java 8中引入了default关键字,能够让接口提供钩子方法:

/**
 * @Author: arong
 * @Description: Interface Demo
 * @Date: 2018/12/23 13:09
 */
public interface TestInterface {
    //钩子方法
    default void hookMethod(){
        System.out.println("接口默认实现的钩子方法");
    }

    //需要实现类重写的方法
    void abstractMethod();
}

四、optional类

Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的NPE(NullPointerException)。
以下就是一个经典的例子:

			User user = new User();
            user.setUserName("arong");
            User user1 = new User();
            user1.setUserPassword("arong");
            CopyOnWriteArrayList<User> userList = new CopyOnWriteArrayList<User>();
            ConcurrentHashMap map = new ConcurrentHashMap<String, List<User>>();
            map.put("userList",userList);

            //判断map是否为null
            if (map != null) {
                List userListTemp = (List<User>) map.get("userList");
                //判断userListTemp是否为null且大小大于0
                if (userListTemp != null && userListTemp.size() > 0) {
                    User userTemp = (User) userListTemp.get(0);
                    String userName = userTemp.getUserName();
                    //判断userName是否为null
                    if (userName != null) {
                        System.out.println(userName);
                    } else {
                        System.out.println("null");
                    }
                }

            }
            //使用Optional类可以很好地解决NPE问题
            String userName = Optional.ofNullable(map)
                    .map(n -> (List<User>) n.get("userList"))
                    .filter(n->n.size() > 0)
                    .map(n -> (User) n.get(0))
                    .map(n -> n.getUserName())
                    //.orElseThrow(() -> new RuntimeException("不存在"));
                    .orElse(null);

            System.out.println(userName);

猜你喜欢

转载自blog.csdn.net/pbrlovejava/article/details/85226974