java8学习:重构和调试

简介:

内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。
书籍购买地址:java8实战

  • 这一篇主要讲使用Lambda表达式来重构之前的代码,并且应用到重用的设计模式之上,然后会介绍如何测试和使用Lambda表达式和StreamAPI

从匿名类到Lambda表达式的转换

  • 下面是传统方式实现的匿名类

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("run");
        }
    };
  • 使用lambda重构上面的代码

    Runnable runnable = () -> {
        System.out.println("run");
    };
  • 代码明显变少,但是需要注意的是匿名类和lambda中对this和super的定义是不一样的,在匿名类中,this代表的是匿名类本身,而在lambda中,他代表的包含类,并且匿名类可以定义与类变量重名的变量,而lambda不可以,如

    int a = 2;
    Runnable runnable = new Runnable() {
        int a = 3;  //正确
    
        @Override
        public void run() {
            System.out.println(a);
        }
    };
    Runnable runnable1 = () -> {
        int a = 3;   //编译错误
        System.out.println(a);
    };
  • 在重载的时候,匿名类和lambda的表现也不一样,比如

    @FunctionalInterface
    public interface People {
        void print();
    }
    @Test
    public void test() throws IOException {
        doSome(new People() {
            @Override
            public void print() {
                System.out.println("people");
            }
        });
        doSome((People) () -> System.out.println("run"));
    }
    public static void doSome(People people){people.print();}
    public static void doSome(Runnable runnable){runnable.run();}
    • 如上重载了doSome方法,当我们用匿名类来传递实现的时候一点问题都没有,但是在使用lambda的时候,由于people的方法和runnable方法都不需要参数,那么就会产生模糊的调用所以会导致编译出错,所以我们只能手动的去指定一个被调用的方法,原因是匿名类传递实现的时候其传递的类型在初始化的时候就能确定比如new people.而lambda只能是通过上下文去判断传入的参数到底是什么类型的,所以需要手动去指定

#lambda表达式到方法引用的转换

  • 方法引用往往比实例点方法名更容易读,更能只管的表达代码意图,代码之前的文章中都有,并且这个概念比较容易理解,就不贴代码了

从命令时的数据处理切换到Stream

  • Stream API帮助我们去遍历数据,只需要告诉他怎么遍历,而不需要写具体的遍历步骤,并且通过短路和延迟加载等可以对我们的操作进行优化
  • 如下是一个命令式编程,过滤条件加收集数据到List

    List<Dish> dishes = new ArrayList<>();
    for (Dish dish:menu){
        if (dish.getCalories() > 200){
            dishes.add(dish);
        }
    }
  • 使用Stream 应该这样

    List<Dish> collect = menu.stream()
            .filter(dish -> dish.getCalories() > 200)
            .collect(Collectors.toList());
  • 但是需要说的是:从复杂的命令式循环切换到Stream遍历是不容易的,因为涉及到break之类的控制语句,对于这些我在阅读本书的时候,书中给出的网址已经无法访问,所以对于复杂的控制流程目前自己还是使用命令式编程,如果以后在学习中知道了,会及时贴出来的

使用Lambda重构面向对象的设计模式

策略模式

  • 自己理解的策略模式就是:根据不同的情形使用不同的实现
  • 原来代码可能这样写

    @FunctionalInterface
    public interface People {
        void doSome(String s);
    }
    public class Eat implements People {
        @Override
        public void doSome(String s) {
            System.out.println("s = " + s);
            System.out.println("Eat");
        }
    }
    public class Drink implements People {
        @Override
        public void doSome(String s) {
            System.out.println("s = " + s);
            System.out.println("Drink");
        }
    }
    public class MS {
        private final People people;
    
        public MS(People people) {
            this.people = people;
        }
        public void msDoSome(String s){
            people.doSome(s);
        }
    }
    @Test
    public void test() {
        MS people1 = new MS(new Eat());
        people1.msDoSome("eat...");
        MS people2 = new MS(new Drink());
        people2.msDoSome("drink...");
    }
    • 如上实现一个策略模式需要一个接口模板,然后分别去实现不同的策略,然后用一个"策略转换类"(自己瞎定义的,也是自己的理解)去实现不同的策略,但是这太长了,并且模板接口类已经是一个函数式接口,那么我们就无需这么麻烦了
    @FunctionalInterface
    public interface People {
        void doSome(String s);
    }
    public class MS {
      private final People people;
    
      public MS(People people) {
          this.people = people;
      }
      public void msDoSome(String s){
          people.doSome(s);
      }
    }
    @Test
    public void test() {
        MS ms = new MS((s) -> System.out.println("s = " + s));
        ms.msDoSome("eat...");
    }
    • lambda比普通实现少了很多模板实现代码

模板方法

  • 自己理解的模板方法就是:有固定部分,也有自实现部分,比如取钱,人人都需要到ATM面前,但是操作不一样,有的存钱有的取钱,如下

    @ToString
    public class Customer {
        private int id;
        public Customer(int id) {
            this.id = id;
        }
    }
    abstract class Bank {
        public void process(int id){
            System.out.println("欢迎"); //模板方法
            Customer customer = BankInnerDatabase.getCustomerById(id); //模板方法
            doSome(customer);//自定义步骤
        }
        public abstract void doSome(Customer customer);
        static class BankInnerDatabase{
            static Customer getCustomerById(int id){
                return new Customer(id);
            }
        }
    }
    //如上是抽象类,如果我们要实现doSome就只能是继承然后实现抽象方法
    public class MyDoSome extends Bank {
        @Override
        public void doSome(Customer customer) {
            System.out.println(customer + ":存钱");
        }
    }
    //使用
    public void test() {
        Bank bank = new MyDoSome();
        bank.process(1);
    }
  • Lambda应该这样:由于抽象类中的抽象方法符合函数式接口Consumer,那么我们就可以用一个方法来代替实现类,比如

    //修改Bank类
    class Bank {
        public void process(int id, Consumer<Customer> consumer){
            System.out.println("欢迎"); //模板方法
            Customer customer = BankInnerDatabase.getCustomerById(id); //模板方法
            consumer.accept(customer);//自定义步骤
        }
        static class BankInnerDatabase{
            static Customer getCustomerById(int id){
                return new Customer(id);
            }
        }
    }
    //删除Bank实现类
    //测试
    public void test() {
        new Bank().process(1,(c)-> System.out.println( c + ":取钱"));
    }

观察者模式

  • 自己理解的观察者模式:就是如果A发生变化,B就会观测到A的变化,然后针对变化做出反应

    
    public interface Observer {
        void notify(String tweet);
    }
    class A implements Observer{
        @Override
        public void notify(String mes) {
            System.out.println("A:"+mes);
        }
    }
    class B implements Observer{
        @Override
        public void notify(String mes) {
            System.out.println("B:"+mes);
        }
    }
    class C implements Observer{
        @Override
        public void notify(String mes) {
            System.out.println("C:"+mes);
        }
    }
    public interface MyActionListener {
        //注册服务
        void registerServer(Observer observer);
        //通知方法
        void notifyServer(String s);
    }
    class MyActionListenerImpl implements MyActionListener{
        //注册服务列表
        List<Observer> observers = new ArrayList<>();
        @Override
        public void registerServer(Observer observer) {
            observers.add(observer);
        }
        @Override
        public void notifyServer(String s) {
            observers.forEach(observer -> observer.notify(s));
        }
    }
    public class MyTest {
        @Test
        public void test() {
            MyActionListenerImpl im = new MyActionListenerImpl();
            im.registerServer(new A());
            im.registerServer(new C());
            im.registerServer(new B());
            //ABC服务都订阅了,如果通知那么ABC将都会受到通知
            im.notifyServer("s");
        }
    }
  • lambda实现

    MyActionListenerImpl im = new MyActionListenerImpl();
    im.registerServer((a)-> System.out.println(a));
    im.registerServer((b)-> System.out.println(b));
    im.registerServer((c)-> System.out.println(c));
    im.notifyServer("tongzhi");
    • 如上就不需要实现Observer接口了,但是并不代表什么时候lambda都可以用,在逻辑比较复杂的时候,应该还是首选类实现的方式

##责任链模式

  • 自己理解的责任链模式:就是类似一个链条,但是其实实现就是指向而已,责任链的每一部分负责一个功能或者处理一个事情,然后达成最后的目标
  • LABDA IN JAVA 8 ACTION字符串修正为lambda in java 8 action

    public abstract class ObjectProcess<T> {
        private ObjectProcess<T> objectProcess;
        public T handle(T t){
            T t1 = nextWork(t);
            if (null != objectProcess){
                return objectProcess.handle(t1);
            }
            return t1;
        }
        abstract T nextWork(T t);
        public void setObjectProcess(ObjectProcess objectProcess) {
            this.objectProcess = objectProcess;
        }
    }
    public class LowerCaseProcess extends ObjectProcess<String> {
        @Override
        String nextWork(String s) {
            return s.toLowerCase();
        }
    }
    public class ReplaceProcess extends ObjectProcess<String> {
        @Override
        String nextWork(String s) {
            return s.replace("labda","lambda");
        }
    }
    //测试
    public void test() {
        ObjectProcess<String> o1 = new LowerCaseProcess();
        ObjectProcess<String> o2 = new ReplaceProcess();
        o1.setObjectProcess(o2);
        String handle = o1.handle("LABDA IN JAVA 8 ACTION");
        System.out.println("handle = " + handle); //handle = lambda in java 8 action
    }
  • 如上就完成了对String字符串的处理,上面中有两部分,一是先将字母lower处理,然后在替换
  • 我们用lambda替换如上的操作,如上代码是一种先..然后...关系,那么我们之前有一个函数就是andThen

    UnaryOperator<String> lower = (s -> s.toLowerCase());
    UnaryOperator<String> replace = (s -> s.replace("labda","lambda"));
    Function<String, String> then = lower.andThen(replace);
    String apply = then.apply("LABDA IN JAVA 8 ACTION");
    System.out.println("apply = " + apply);//apply = lambda in java 8 action
  • 如上我们就省去了很多不必要的代码,UnaryOperator类继承了Function,可以看做是一个Function的实现

工厂模式

  • 自己理解的工厂模式:就是方便调用的static方法,并且隐藏了创建对象的方法细节

    class Fruits{}
    class Banana extends Fruits{}   //香蕉是黄色
    class Watermelon extends Fruits{}  //西瓜是绿色
    public class MyFactory {
        public static Fruits createFruit(String color){
            switch (color) {
                case "yellow" : return new Banana();
                case "green" : return new Watermelon();
                default: return new Fruits();
            }
        }
    }
    //测试
    public void test() {
        Fruits green = MyFactory.createFruit("green");
        System.out.println(green.getClass());//class com.qidai.demotest.Watermelon
    }
  • 使用lambda解决:之前的文章里提到了构造方法的引用,我们可以使用这种办法完成工厂模式的编写

    class Fruits{}
    class Banana extends Fruits{}
    class Watermelon extends Fruits{}
    public class MyFactory {
        private static final Map<String, Supplier<Fruits>> map = new HashMap<>();
        static {
            map.put("green",Watermelon::new);
            map.put("yellow",Banana::new);
        }
        public static Fruits createFruit(String color){
            Supplier<Fruits> fruitsSupplier = map.get(color);
            if (fruitsSupplier != null){
                return fruitsSupplier.get();
            }
            return null;
        }
    }
    //测试方法是跟上面的代码是一样的

Lambda的调试

  • 下面是一个错误代码

    List<String> lists = Arrays.asList("S", null);
    lists.stream().map(String::toString).collect(Collectors.toList());
    • 出现的错误是
    java.lang.NullPointerException
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)                    //这行是个什么鬼
        at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
      ....
    • 如上的打注释的堆栈跟踪信息代表了lambda内部发生了错误,由于lambda没有名字,所以编译器只能为它指定一个名字,如果很多地方的lambda同时出错那么会崩溃的,但是在java8中事实就是这样..
  • 需要注意的是:如果方法引用指向的是同一个类中声明的方法,那么他的名称是可以被打印的

    public class MyTest {
        @Test
        public void test() {
            List<String> lists = Arrays.asList("S", null);
            lists.stream().map(MyTest::method).collect(Collectors.toList());
        }
        public static String method(String s){
            return s.toLowerCase();
        }
    }
    • 错误信息
    java.lang.NullPointerException
        at com.qidai.demotest.MyTest.method(MyTest.java:17)    //出现了
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
        at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
  • 对于lambda的自定义名称是没有办法的...

使用日志调试

  • peek:最初实际就是为了在流的每个操作之间插入一个动作,它只会将操作顺承到下一个操作,而不对流产生影响,他的定义如下

    Stream<T> peek(Consumer<? super T> action);
  • 实例

    public void test() {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
        List<Integer> collect = integers.stream()
                                        .peek(integer -> System.out.println("from stream " + integer))
                                        .map(integer -> integer + 1)
                                        .peek(integer -> System.out.println("from map " + integer))
                                        .filter(integer -> integer %2 == 0)
                                        .peek(integer -> System.out.println("from filter " + integer))
                                        .collect(Collectors.toList());
        System.out.println(collect);
    }
    • 结果
    from stream 1
    from map 2
    from filter 2
    from stream 2
    from map 3
    from stream 3
    from map 4
    from filter 4
    from stream 4
    from map 5
    from stream 5
    from map 6
    from filter 6
    from stream 6
    from map 7
    from stream 7
    from map 8
    from filter 8
    from stream 8
    from map 9
    [2, 4, 6, 8]
目录
相关文章
|
11天前
|
消息中间件 前端开发 Java
java学习路径
【4月更文挑战第9天】java学习路径
17 1
|
1月前
|
安全 Java 程序员
学习Java类加载机制
在Java的世界里,每一个类或者接口,在经历编译器后,都会生成一个个.class文件。
18 0
|
1月前
|
Java 关系型数据库 MySQL
37、一篇文章学习 Java 中的日期相关类(Date 和 Calendar),非常常用
37、一篇文章学习 Java 中的日期相关类(Date 和 Calendar),非常常用
27 0
|
2月前
|
Java 数据库连接 开发工具
正式开始JAVA学习之旅
正式开始JAVA学习之旅
39 0
|
1月前
|
存储 安全 Java
24、使用 Java 官方教程学习:① 类变量和类方法详解;② 深入介绍 main() 方法
24、使用 Java 官方教程学习:① 类变量和类方法详解;② 深入介绍 main() 方法
37 1
|
11天前
|
设计模式 前端开发 安全
Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
【4月更文挑战第9天】Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
15 1
|
1月前
|
Java 索引
Java中String方法学习总结_kaic
Java中String方法学习总结_kaic
|
2月前
|
监控 IDE Java
Java项目调试实战:如何高效调试Spring Boot项目中的GET请求,并通过equalsIgnoreCase()解决大小写不一致问题
Java项目调试实战:如何高效调试Spring Boot项目中的GET请求,并通过equalsIgnoreCase()解决大小写不一致问题
40 0
|
1天前
|
JavaScript Java 测试技术
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
21 1
|
1天前
|
JavaScript Java 测试技术
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
19 0