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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章