顯示具有 JAVA 標籤的文章。 顯示所有文章
顯示具有 JAVA 標籤的文章。 顯示所有文章

Eclipse 啟動發生 libjvm.dylib does not contain the JNI_CreateJavaVM symbol

Eclipse/SpringToolSuites4 啟動發生 libjvm.dylib does not contain the JNI_CreateJavaVM symbol

更新 openjdk 後,無法開啟 Eclipse/SpringToolSuites4,錯誤訊息如下 :

The JVM shared library "/Users/foo/Library/Java/JavaVirtualMachines/adopt-openjdk-11.0.12/Contents/Home/bin/../lib/server/libjvm.dylib" does not contain the JNI_CreateJavaVM symbol.

看起來應該是更新的 openjdk(adopt-openjdk-11.0.12)有問題,編輯 Eclipse/SpringToolSuites4 的 Info.plist,指定要執行的 Java 路徑(原先正常的 adopt-openjdk-11.0.11)即可。

<!-- Info.plist 搜尋 -vm 字串 --> 

<string>-vm</string><string>/Users/foo/Library/Java/JavaVirtualMachines/adopt-openjdk-11.0.11/Contents/Home/bin/java</string>

為什麼要使用 Java Optional 以及 Optional 常用的方法有哪些

你用過 Java Optional 嗎?

本文假設讀者你已經用過 Optional 或是介接過回傳 Optional 的 API,知道 Optional 主要是解決 java.lang.NullPointerException(剛好是本站用的網址 XD),但還是不太知道為什麼要用 Optional!

換個角度看 Java Optional

我覺得站在開發 API 的角度來看這個問題,就很清楚為何要使用 Optional!

  • 如果回傳的結果可能為 null 就該用 Optional<T>。
  • 反之,不可能回傳 null 就跟以前一樣就用 T。

使用你 API 的人,就可以根據這個資訊,知道呼叫這個方法需不需要處理 null 的問題,而不是每個方法都要多寫處理 null 的邏輯,或是沒處裡就會發生 java.lang.NullPointerException。

// 回傳 People 代表這方法絕對會回傳一個我爸
public People getMyFather()

// 回傳 <Optional>People 代表可能回傳空物件
public <Optional>People getMyFather()

如果今天你寫的系統,每個人都必定會有個爸爸,那回傳 People 的 getMyFather() 符合你的意圖,而回傳 <Optional>People 的 getMyFather() 因為有可能回傳空物件的語意,所以就不適合用此方法。

若你寫的系統允許父不詳的狀況,那回傳 <Optional>People 的 getMyFather() 就是比較好的寫法。

我看到第一個回傳 People 的 getMyFather(),我很清楚我不用處理 null 的狀況,也就不用多寫處理 null 的邏輯。而第二個方法我就需要用 isPresent() 檢查回傳的 Optional<People>是否為一個空物件。

Java Optional 常用的方法整理

下面程式碼說明 of、ofNullable 這兩個方法:

Foo foo = null;

// of()不接受傳入 null,此處會發生 NullPointerException
Optional<Foo> optionalFoo1 = Optional.of(foo);

// ofNullable()可以傳入 null,此處會得到一個空物件 
Optional<Foo> optionalFoo2 = Optional.ofNullable(foo);

下面程式碼說明 ifPresent、get、getElse、orElseGet、orElseThrow:

// 從 getBar 取得一個 Optional<Bar>
// 由回傳 Optional<Bar> 來看
// 我們預期有可能收到空物件
Optional<Bar> optionalBar = getBar();

// 用 ifPresent + get 的寫法跟原本檢查 null 的寫法一樣差
if (optionalBar.ifPresent()) // 檢查是否為空物件
{
  Bar bar = optionalBar.get(); // 取得包在裏頭的 bar
  bar.doSometing();
}

// 當 optionalBar 裏頭包的是 null,會建立一個新的 Bar 物件並回傳
Bar bar = optionalBar.getElse(new Bar());

// 當 optionalBar 裏頭包的是 null,會建立一個新的 Bar 物件並回傳
Bar bar = optionalBar.orElseGet(() -> { 
    // 這邊可以多做一些事,比如印 Log
    return new Bar();
}));

// 當 optionalBar 裏頭包的是 null,會丟出一個自訂的例外
Bar bar = optionalBar.orElseThrow(() -> new MyException("WTF")));

結語

開發 API 的人使用 Optional 來說明回傳的物件是不是有可能為 null,讓使用 API 的人看到介面就知道是否要處理 null。

而 getElse() 能改善以往檢查是否為空,如果不為空就...的寫法,讓程式碼更簡潔更容易閱讀。

Java 常用的 Functional Interface:Consumer、Function、Predicate、 Supplier

在看 Functional Interface 之前,先看看以下例子,如果我們只能寫一個方法,要在一個字串集合內,找到預期的字串,同時要能支援完全比對/忽略大小寫的話,寫出來的程式碼大概長的是以下這個樣子:


/*
* expect:是要比對的字串。
* ignoreCase:true 代表比對可以忽略大小寫。
*/
public boolean findAnyMatch(List<String> list, String expect, boolean ignoreCase)
{
  if (ignoreCase) // 若任一個字串符合傳入的字串(忽略大小寫),就回傳 true。
  {
    for (String string : list)
    {
      if (expect.equalsIgnoreCase(string))
      {
        return true;
      }
    }
  }
  else // 若任一個字串符合傳入的字串(要完全一樣),就回傳 true。 
  {
    for (String string : list)
    {
      if (expect.equals(string))
      {
        return true;
      }
    }
  }
		
  return false;
}

...

System.out.println(foo.findAnyMatch(list, "Moe", false)); // true
System.out.println(foo.findAnyMatch(list, "Jack", false)); // false
System.out.println(foo.findAnyMatch(list, "moe", true)); // true

原本的寫法除了又臭又長之外,還有多重巢狀結構,不容易閱讀。

同樣的方法改用 lamda 表達式作為 findAnyMatch 傳入參數的寫法,程式碼如下,是不是短了很多?甚至還可以在不更動 findAnyMatch 的情況下,找出是否有包含 "o" 的字串。


/*
* predicate:是一個只定義一個抽象方法 interface,告訴傳入的 Lambda 表達式會收到一個參數,然後要回傳一個 boolean。
*/
public boolean findAnyMatch(List<String> list, Predicate<String> predicate)
{
  // 若任一個字串符合傳入的 predicate 檢查的條件,就回傳 true。
  return list.stream()
    .filter(predicate) // 傳入的 lambda 會收到一個字串,回傳 boolean
    .findFirst()
    .isPresent();
}

...

System.out.println(foo.findAnyMatch(list, (s) -> s.equals("Moe"))); // true
System.out.println(foo.findAnyMatch(list, (s) -> s.equals("Jack"))); // false
System.out.println(foo.findAnyMatch(list, (s) -> s.equalsIgnoreCase("moe"))); // true
System.out.println(foo.findAnyMatch(list, (s) -> s.contains("o"))); // true

所以大致的演進流程是:用傳入的參數來影響方法內的行為 > 直接把行為(lambda)當作參數。同樣的效果用傳入匿名類別當作參數也可以做到,但傳入 lambda 的寫法看起來比較短、比較好閱讀。

Java 把 lambda 寫法常用到的 Interface 先定義出來,我們在導入 lamda 寫法改寫原有方法時,就不用重複定義這些常出現的介面。比如上面用到的 Predicate 介面,它預期接收一個字串類型物件,然後返回一個 boolean 值。

以下整理出程式碼常見到的 Functional Interface:

Functional Interface輸入回傳
PredicateT 類型物件boolean
ConsumerT 類型物件不用回傳
FuctionT 類型物件R 類型物件
Supplier無T 類型物件
BiPredicateT 類型物件 + U 類型物件boolean
BiConsumerT 類型物件 + U 類型物件不用回傳
BiFuctionT 類型物件 + U 類型物件R 類型物件

Intellij IDEA 啟動跳出 Unsupported Java Version

升級完 Intellij IDEA 後,要開啟 Intellij IDEA 就跳出了 Unsupported Java Version 這個警告,在 Console 下查看 Java 版本是 openjdk version "11.0.2" 2019-01-15,版本大於 Java 11,應該不是環境的問題。

執行以下指令排除此問題:

rm ~/Library/Application\ Support/JetBrains/IntelliJIdea2020.3/idea.jdk

特殊註解:TODO、FIXME、XXX

最早接觸到這些特殊註解是在寫 Eclipse 的時候,有些自動產生的程式碼就會自己補上 //TODO,在 Tasks 分頁就能顯示出這些備註,點擊就直接跳到程式碼上相當方便。

實際上在 Preferences > Java > Compiler > Task Tags 下可以看到預設只支援 TODO、FIXME 和 XXX 這三種特殊註釋。

除了 TODO、FIXME 和 XXX 外,我也列出了我比較少用,但在其他地方有看到的特殊註釋:

  • TODO:知道要實作什麼功能,但還沒開始寫的程式碼。比如://TODO 接上 LDAP 認證
  • FIXME:發現比較少發生的 bug,但當下沒時間改。比如://FIXME 對方回傳結果缺乏 foo 欄位,會造成 NullPointerException。
  • XXX:很醜但可以正常運作的程式碼,我會先用這個註釋標註,未來有空回來優化。比如://XXX 目前用 iterator 的方式掃描集合,可改用 Stream 的方式。
  • 少用
    • NOTE:說明這段程式碼怎麼運作。因為等於在寫一般的單行註解,所以我沒用過。
    • HACK:這裡用非正規的方法實作功能(Android 開發常用 reflection 機制呼叫沒開放的功能)。因為跟 XXX 類似,所以我很少用。
    • BUG:這裡有 bug。因為跟 FIXME 類似,所以我很少用。

Java Stream API Cheat Sheet

在 Java 8 之後,可以用 Stream API 來處理資料集合,最大的好處就是巢狀結構消失了,比較容易閱讀、看出商業邏輯,以下紀錄比較常用的操作:

建立 Stream

  • of
  • empty
  • concat
  • generate:產生無限串流 的方法之一。
  • iterate:產生無限串流的方法之二。
// 示範 of
Stream<String> fooBarBazStream = Stream.of("foo", "bar", "baz");

// 示範 empty
Stream<String> emptyStream = Stream.empty(); 

// 示範 concat
Stream<String> stream1 = Stream.of("foo", "bar");
Stream<String> stream2 = Stream.of("baz");
Stream<String> stream3 = Stream.concat(stream1, stream2);

// 示範 generate
Stream<String> helloStream = Stream.generate(() -> "hello"); // 總是回傳 "hello"
String<Double> randomStream = Stream.generate(Math::random); // 每次回傳一個亂數

// 示範 iterate
Integer seed = 1;
Stream<Integer> oneTwoFourEghitStream = Stream.iterate(seed, n -> n*2); // 1, 2, 4, 8...

中間操作

  • peek:常用來 debug,印出經過元素。
  • limit
  • skip
  • distinct:去除重複
  • sorted
  • filter
  • map:將經過的元素類型轉換成別種元素類型,比如:員工物件轉成姓名字串。
    • mapToInt
    • mapToLong
    • mapToDouble
  • flatMap:將經過元素轉換成該元素內資料集合的元素,比如:一個員工有兩個電話(手機和住家電話),將傳入的三位員工轉成六個電話字串。
    • flatMapToInt
    • flatMapToLong
    • flatMapToDouble
// 示範 distinct
Stream.of("a", "b", "a", "b")
  .distinct()
  .forEach(e -> System.out.print(e)); // 印出 "ab"

// 示範 map
employees.stream()
  .map(e -> e.getName())
  .forEach(name -> System.out.println(name)); // 印出所有員工的姓名

// 示範 flatMap
employees.stream()
  .flatMap(e -> e.getPhoneList().stream()) // 傳入一位員工,發送出多筆電話
  .forEach(phone -> System.out.println(phone)); // 印出所有員工的電話

終止操作

終止操作完了以後,Stream 就不能再使用了。

  • max
  • min
  • findFirst
  • findAny
  • anyMatch:有符合條件的元素回傳 true。
  • allMatch
  • noneMatch
  • orElse
  • count
  • forEach:很像 peek,但 peek 是中間操作,需要一個終止操作才能執行。
  • collect:返回的是集合。
  • toArray:返回的會是陣列。
  • reduce:可以去看一下 Google 提出的 MapReduce 概念
// 示範 collect
List<String> nameList = 
  employees.stream()
    .map(e -> e.getName())
    .collect(Collectors.toList());

Java 安裝在 Docker 容器的記憶體問題

Docker 容器佔用大量記憶體空間

Docker VM 的記憶體使用量很高,進去看16G 的記憶體的機器,才裝12個 Docker 容器,一個一個檢查才發現幾個是跑 Java APP 的容器吃的記憶體特別高(1G~3G 之間),但有些容器都是 Serverless 類型的服務,不應該佔用那麼多記憶體。

Java 支援 Docker 的記憶體限制

在容器內部執行 JVM,預設 Max Heap Size 會是主機記憶體的1/4,而非容器記憶體的1/4(所以我 docker run 設定 memory 參數也沒用)。

在Java 8u131 之後支援了 Docker CPU 和記憶體的限制,在 Java 8 上要使用以下參數:

  • UnlockExperimentalVMOptions:開啟實驗性質的選項。
  • UseCGroupMemoryLimitForHeap:使用 cgroup(control group) 記憶體大小來計算 Max Heap Size ,白話就是偵測到 Docker 容器的記憶體。
  • MaxRAMFraction:將偵測到的記憶體除以這個數字,就是 Max Heap Size。這邊設為1,搭配 UseCGroupMemoryLimitForHeap 的效果就是 Docker 容器的記憶體大小除以1就是 Max Heap Size,白話就是不會超過 Docker 容器設定的記憶體。

# 建立一個記憶體500 MB 有 JRE 8 的容器,執行 Java 指令印出 Max. Heap Size
# 調整 docker run 的 memory 參數,可以影響容器內 JVM 使用的記憶體大小。
docker run --memory 500MB openjdk:8-jre java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=1 \
-XshowSettings:vm \
-version

參考:

修正 Dockerfile 以及 docker run

了解上一章節的參數後,首先記得 docker run 一定要加上 memory 參數來限制容器的記憶體。

再來就是將原本執行 Java 程式的命令列(看是寫在 docker run 指令還是寫在 Dockerfile 裡頭)加上參數。這樣就能解決 Docker 容器跑 Java 程式佔用太多記憶體的問題。


# 執行 app.jar
docker run --memory 500MB openjdk:8-jre java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=1 \
-jar path/to/your/app.jar

# Dockerfile
# 省略
CMD ["java", "-Dspring.profiles.active=test", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-XX:MaxRAMFraction=1", "-XshowSettings:vm",  "-jar", "path/to/your/app.jar"]

Java 送出的時間與 MySQL 時間相差13個小時或差14個小時

MySQL 時間相差13或14個小時

Java 送出的時間有設定為台灣時區,但 MySQL 上看到的時間就是差13個小時,確認過 MySQL 主機時區是台灣的時區+8也沒有錯。
-- 在 MySQL 執行以下 SQL 指令,顯示 MySQL 時區以及 MySQL 主機時區
show variables like "%time_zone%";

-- 發現 system_time_zone 是 CST,代表 MySQL 主機時區是 CST
-- 發現 time_zone 是 SYSTEM,代表 MySQL 時區參考 MySQL 主機時區

發生原因在於 CST 可能是中國標準時間(China Standard Time)或是美國中部時間(Central Standard Time),造成 Java 認為 MySQL 時區是美國中部時間。

明確指定 MySQL 時區

MySQL 時區不要參考 MySQL 主機時區,明確指定 MySQL 時區為+8,讓 Java 明確知道要用+8的時區。

# 修改 my.cnf,在 [mysqld] 下增加:
default-time-zone = '+08:00'

# 需要重新啟動 MySQL
-- 重起後,重新在 MySQL 執行以下 SQL 指令,顯示 MySQL 時區以及 MySQL 主機時區
show variables like "%time_zone%";

-- system_time_zone 是 CST
-- time_zone 是 +08:00

用 RxPermissions 處理 Android 6.0 Permission

Android 6(API level 23) 之後,開發者會被要求者處理 APP 權限問題(針對 Run),程式碼要撰寫相關邏輯:檢查有沒有權限、處理取得權限後的行為...等。官方範例碼大概就是畫面跳轉類似用 Listener Pattern 的方式來處理,另外還要處理當使用者選取"不要再跳出權限對話框"的特殊狀況。總覺得應該就是一個 yes 或 no 就該解決的事情,但每次寫出來都不一樣,各別提不同人寫出來的流程都是天差地遠了。
這次改用 RxPermissions 來重寫,RxPermissions 封裝邏輯後寫出來的程式碼順序很直覺:

  • 路徑1:要求權限 > 都有權限 > 執行需要權限的程式碼。
  • 路徑2:要求權限 > 有權限被拒絕會自動跳出對話框 > 還是有權限被拒絕(對話框沒勾選不再詢問) > 再重新要求權限 或 執行當沒有權限還是可以執行的程式碼。
  • 路徑3:要求權限 > 有權限被拒絕而且曾在對話框勾選不再訊問 > 提示有用訊息並引導使用者到設定頁面打開權限 > onActivityResult 處理設定頁面跳轉回來的事件再重新要求權限。
廢話不多說,上扣!

JCCONF 2016 筆記


會場有提供幾個黑板讓大家回答問題,還蠻有趣的。

  • IDE:以 Eclipse 和 Intellij 為大宗,Intellij 還不是寫成 Android Studio。
  • 第二語言:以 Python 最多,Groovy 也不少。
  • Server:什麼都有看到但就是沒有 JBoss Server。
這次參加心得是
1) 一個團隊就是要重頭包到尾 // 不分開發測試DB維運
2) 自動化要做的好,才有時間精力從頭包到尾
3) Spring Boot 看來能快速開發後台,直接包好 Web Server 一個指令執行,不再走傳統部屬至 Server 的流程 // 我想要繼續走後台的話要來學一下
4) 覺得現場人頭廠商徵才的方式蠻鳥的,覺得最好的方法就是好好把內部的 API 開源出來,派開發者出來演講,想用的人就會上去貢獻程式碼,這些人就是最有潛力的人才。 // 我絕對沒說是東森
5) 新竹台北通勤好累,來回大概就4小時去了,回來安全帽還被幹走。 // 幹
6) 回來想說隨便寫,沒想到大概花了又4小時才整理出來。 // 一場鬥陣都還沒開始玩
7) 沒帶筆電去是對的,沒事做只能認真寫筆記。

下面是準備回公司分享的投影片, 礙於內部分享不寫太多字太深入,所以一個議題都只有一張投影片,眼睛掃過去有興趣的話大會有提供 hackpad 可以看 ,會後也應該會放上影片,到時我也會更新至這份投影片中。

用程式碼介紹 RxJava

前言

RxJava 也強迫自己用兩個月了,除了自己的專案外,工作的專案也拿來改寫原本很難看懂的邏輯。這篇建議就當作一篇引導文或是心得文看看,有興趣繼續研究的朋友,網路上已經有很多中/英文資源,不怕沒地方學只怕你不學。

安裝

要導入 RxJava 很簡單,gradle 下一行即可,沒用 gradle 也只要抓一個 rxjava.jar 檔案,再引入專案就可以了。
compile 'io.reactivex:rxjava:x.y.z'

不用綁定 JVM 版本,不限定是 Android 專案還是純 Java 專案,只要一個 jar 檔案就能使用 RxJava。

什麼是 RxJava

直接舉幾個例子,來說明什麼是 RxJava,又為什麼我要用 RxJava,看了程式碼應該比用文字描述來的有感覺。這些例子也是我在專案中反覆用到的程式碼來改寫的。

Android 中需要更新 GUI 的耗時工作

取代 AsyncTask 在 Android 上呼叫 耗時工作,呼叫前更新 GUI,呼叫完再更新 GUI。

對於耗時工作回傳的結果,在 RxJava 中還能做進一步處理,就像工廠的流水線,從頭到尾能看到做哪些處理。想想如果要用第一個耗時工作的結果,去呼叫第二的耗時工作,在 RxJava 就是單純再多一個站點(下面程式碼中的 map()),如果沒有 RxJava 這段程式碼要怎麼寫?寫出來的程式碼會不會很難看?
Observable.just("input")
    .doOnSubscribe(new Action0()
    {
        @Override
        public void call()
        {
            // 在觸發耗時工作前更新 GUI
            // 比如顯示進度條
        }
    })
    .subscribeOn(Schedulers.newThread()) // 設定耗時工作在新的 Thread 上執行
    .map(new Func1<String, String>()
    {
        @Override
        public String call(String input) 
        {
            // 執行耗時工作
            // 比如是一個 Web API 呼叫
            return new WebApi(input);
       }
    })
    .map(new Func1<String, User>()
    {
        @Override
        public User call(String input) 
        {
            // 處理耗時工作回傳的結果
            // 比如 Web API 回傳的 Json 字串轉成 POJO
            return new Gson().fromJson(input, User.class);
       }
    })
    .observeOn(AndroidSchedulers.mainThread()) // 設定非同步工作完成後回到 Android GUI Thread 上更新 GUI
    .subscribe(new Observer<User>() 
    {
        @Override
        public void onNext(User user) 
        {
            // 更新 GUI
            // 比如在 TextView 上顯示 User 的名稱
        }

        @Override
        public void onCompleted()
        {
           // 如果沒有發生例外
           // 比如關掉進度條
        }

        @Override
        public void onError(Throwable error)
        {
            // 如果發生例外
            // 比如關掉進度條後跳出警告視窗
        }
    }); 

移除會造成 Callback Hell 的程式風格

延續上個範例提到的,如果要用第一個耗時工作的結果(用帳號密碼呼叫 Web API 得到 token),去呼叫第二的耗時工作(用 token 呼叫得到使用者的訊息清單),最後印出訊息清單。

在 Java 中考慮到呼叫 Web API 是一個耗時工作,所以大部分設計的呼叫參數常會多帶一個 Handler(Observer Pattern),被呼叫端做完耗時工再後,再利用 Handler 讓呼叫端知道該做什麼,程式碼大致會長成這樣:
webApi.getToken("user", "password", new DefaultHandler()
{
    // 第一個 Web API 呼叫成功
    @Override
    public void onSuccess(String response)
    {
        webApi.getMessageList(response, new DefaultHandler()
        {   
            // 第二個 Web API 呼叫成功
            @Override
            public void onSuccess(List<String> response)
            {
                // 呼叫成功
                for (String message : response)
                {
                    // 印出每個訊息
                }
            }

            // 第二個 Web API 呼叫失敗
            @Override
            public void onFailure(Exception exception)
            {
                // 印出失敗訊息
            }
        });
    }

    // 第一個 Web API 呼叫失敗
    @Override
    public void onFailure(Exception exception)
    {
        // 印出失敗訊息
    }
});

有沒有看到程式碼一直往內凹,這個就是 Callback Hell,雖然程式碼還是可以動,但就是不夠簡單不夠整齊。如果今天採用 RxJava 來設計,RxJava 本來就是 Observer Pattern,前一個方法呼叫完成,才會進入下一個方法,程式碼大致會長成這樣:
Observable.just(webApi.getToken("user", "password"))
    .flatMap(new Func1<String, Observable<String>>()
    {
        // 第一個 Web API 呼叫成功
        @Override
        public Observable<String> call(String token)
        {
            try
            {
                // 第二個 Web API 呼叫成功
                return Observable.from(webApi.getMessageList(token));
            }
            catch (Exception e)
            {
                // 第二個 Web API 呼叫失敗
                // 將錯誤丟出來
                throw Exceptions.propagate(e);
            }
        }
    })
    .subscribe(new Observer<String>() 
    {
        @Override
        public void onNext(String message) 
        {
            // 印出每個訊息
        }

        @Override
        public void onCompleted()
        {
            // 如果沒有發生例外
        }

        @Override
        public void onError(Throwable error)
        {
            // 第一個 Web API 呼叫失敗
            // 第二個 Web API 呼叫失敗
        }
    });

當要串的 Web API 越多的時候,帶 Handler 產生的巢狀架構,會讓程式碼越來越複雜不容易看懂,而用 RxJava 寫法的程式碼只會變長。

執行固定次數的動作

有時想要執行某個動作10次,一般都是要寫個 for 迴圈,現在不用羨慕 Ruby/Python 有很簡單的寫法,現在我們也有了。
Observable.range(0, 10).subscribe(new Action1<Integer>() // 0, 1, 2, 3...9
{
    @Override
    public void call(Integer item)
    {
        // 執行動作
        System.out.println(string);
    }
});

幹,好像也沒有比 for 迴圈少打幾個字,這個就必須要你有學好 lamda 了。
Observable.range(0, 10).subscribe(item -> System.out.println(item)); // 0, 1, 2, 3...9

資料集合處理

寫後台寫邏輯常常會需要處理資料集合,今天如果我們有個字串集合,要做以下處理(依序):
  • 只取出有帶 "boy" 的字串
  • 取出字串中的數字
  • 將數字轉成英文(1變成 "one")
  • 只取最前面兩個符合條件的英文
廢話不多說,直接上程式碼最有感覺。
public class HelloRxJava
{
    public static void main(String[] args)
    {
        List<String> list = new ArrayList<String>(Arrays.asList("boy_4", "boy_1", "girl_1", "boy_2", "boy_3", "girl_2"));


        // 傳統寫法:
        int take = 2;
        int i = 1;
        for (String string : list)
        {
            if (isBoy(string))
            {
                if (take < i)
                {
                    return; // Jump!
                }

                System.out.println(numberToEnglishNumber(getNumber(string)));
                i++;
            }
        }


        // RxJava 沒有 lamda 的寫法:
        Observable.from(list)
            .filter(new Func1<String, Boolean>()
            {
                @Override
                public Boolean call(String string) 
                {
                    return isBoy(string);
                }
            })
            .map(new Func1<String, String>()
            {
                @Override
                public String call(String string) 
                {
                    return getNumber(string);
                }
            })
            .map(new Func1<String, String>()
            {
                @Override
                public String call(String string) 
                {
                    return numberToEnglishNumber(string);
                }
            })
            .take(2)
            .subscribe(new Action1<String>()
            {
                @Override
                public void call(String string) 
                {
                    System.out.println(string);
                }
            });


        // RxJava 有 lamda 的寫法:
        Observable.from(list)
            .filter(string -> isBoy(string) == true)
            .map(string -> getNumber(string))
            .map(string -> numberToEnglishNumber(string))
            .take(2)
            .subscribe(string -> System.out.println(string));
    }




    /**
     * 將阿拉伯數字轉為英文字
     */
    private static String numberToEnglishNumber(String string)
    {
        if (string.equals("1")) return "one";

        if (string.equals("2")) return "two";

        if (string.equals("3")) return "three";

        return "oops!";
    }

    /**
     * 取得字串裡的數字
     */
    private static String getNumber(String string)
    {
        return string.substring(string.indexOf("_") + 1);
    }

    /**
     * 字串是否包含 boy
     */
    private static boolean isBoy(String string)
    {
        return string.startsWith("boy");
    }
}

為求範例簡潔,一些邏輯都抽取成 private 方法。可以發現傳統寫法雖然不長,但有巢狀 if 的關係,需要花一點時間才能了解程式碼的意圖,而用 RxJava 的寫法,基本上根本就是把剛剛條列的動作依序執行,而用 lamda 的方式寫的程式碼更是很容易就能看出程式碼的意圖。

小結

本文說明了:
  • 無論是單純的 Java 環境,還是在 Android 上,只要一個 jar 就能開始用 RxJava。
  • RxJava 雖然讓程式碼變長了,但讓程式碼的意圖變得比較好理解。
  • 如果你會用 lamda,你的程式碼就不會那麼長。
  • RxJava 提供的多種運算子,讓我們很容易就能做出"取出最前面兩個元素"、"Retry 10次"之類的邏輯。
我有把我學習過程中寫的範例碼都放上 github,有興趣的人可以拉下來跑,基本上都是一些釐清觀念的 Code。另外推薦這篇 RxJava 文檔中文版,除了介紹 RxJava 的概念外,也針對各種運算子有很詳細的說明。

Blogger 自動發文

需求很簡單,希望餵個標題跟內容,就自動產生文章貼到 Blogger 上,但弄的跌跌撞撞的,紀錄一下,留給麵包屑給未來的自己和有需要的人。

先來看一下官方作法 - 新增 Post 的做法,可以透過 Restful Web API 或是官方提供的 Java Library 來完成。理想上是這樣,看起來很間單,那為什麼我會跌的狗吃屎?

Restful Web API 的方式,參考指南這一篇,才發現 HTTP Header 還要填入 Authorization: /* OAuth 2.0 token here */,基本上就還是要透過 OAuth 2.0 的方式得到 token 才走的下去。那換成官方提供的 Java Library,反正連 Sample Code 都有了應該很簡單,貼上去才發現範例中的 OAuth2Native 根本沒有,需要自己實作。

做到這邊我心已死,成功之路沒有捷徑,只好去翻這兩份文件來看:

大概的問題就是想辦法通過 OAuth 2.0 取得 Token,再來拿這個 Token,看是要用 Restful Web API 還是 Java Library 來新增 Post 都可。步驟如下:
  1. 到 https://console.developers.google.com/ 下的 API 和驗證>API 把 Blogger API 啟用。
  2. 到 https://console.developers.google.com/ 下的 API 和驗證>同意畫面 把該填的資訊填一填。
  3. 到 https://console.developers.google.com/ 下的 API 和驗證>憑證>OAuth 建立用戶端 ID,類型選取已安裝的應用程式(我的 APP 是一個桌面執行程式)。
  4. 此時會得到用戶端 ID,用戶端密碼。
  5. 修改官方範例碼如下:
當執行這段範例碼時,在 Po 文之前,瀏覽器會自動打開,告知使用者有應用程式嘗試要存取你的 Blogger ,使用者同意之後就會自動 Po 文。

但實際上這段程式碼還是需要人為介入(在瀏覽器上同意),其實在 https://console.developers.google.com/ 下的 API 和驗證>憑證>OAuth 建立用戶端 ID,有一個類型叫做服務帳戶(Service Account)可以自動通過 OAuth 認證,但因為 Blogger 平台不支援用服務帳戶來發文,所以我們會得到 HTTP 403 授權失敗的錯誤。

PS: Google 關於通過 OAuth 的範例程式碼

Spark - Java 輕量級的 Web 框架

這跟處理巨量資料的 apache spark 無關!

如果有時候只是要寫個沒幾個人用的 Restful API,或只是想要有個 HTTP 接口(透過 get/post)來呼叫我們寫好的 Java SE 程式,如果對架設 tomcat / Java EE Server 又不熟悉,那可以考慮用 Spark!

Hello World

Spark 2 以後需要用到 lambda 語法,需要安裝 Java 8。如果只有 Java 7,請找 Spark 1 或是去玩 play。

以 Eclipse 為例,安裝 Spark 方法有兩種:

第一種比較簡單,直接建立 Maven 專案,在 pom.xml 加入對 spark 的 dependency 就好了。

第二種就到 github clone 下來,不過實際建立時,我還是得把這專案轉成 maven 專案,才能利用 pom.xml 把其他要用的 jar 檔抓回來...也可能我太笨沒抓到官方給的提示...

寫一個 HelloWorld.java 包含一個 main 如下:

import static spark.Spark.*;

public class HelloWorld {
    public static void main(String[] args) {
        get("/hello", (req, res) -> "Hello World");
    }
}

直接執行 HelloWorld.java,無需安裝另外的 Web Container,無需任何設定,在瀏覽器打開 http://localhost:4567/hello 就可以看到結果。

螢幕快照 2015-03-26 下午1.49.01

Spark 提供的功能

大部份 Restful API 需要的功能都有:
  • 指定 URL 路徑。
  • 存取 request 標頭/參數/...。
  • 回寫 response。
  • 處理 session。
  • 處理每個 request 前後插入 filter 做前置/後置處理。
除了單純回傳資料,也可以介接 Freemarker 這類的 HTML Template Engine,將要顯示的值以類似 JSP EL 的方式,填到預先寫好的 HTML Template 上。

也可以介接 AngularJS 做出類似 Gmail 這種只有一頁的 Web 網站。

部署

官網上沒有特別寫部屬的方式,試了一下。就直接在 Eclipse 上匯出一個可執行的 jar,執行的目標就指向有 main 的那支 java。

執行匯出的 jar 就會在本機上開啟 Web Server 了(參考下圖)!開發到使用完全就像執行一般 jar 一樣!

螢幕快照 2015-03-26 下午1.51.17

參考

[讀書心得] 例外處理設計的逆襲



書中幾乎所有的程式碼都是以 Java 作為例子,所以寫 Java,C# 的人看起來會比較輕鬆。太深入的內容就不寫了,簡單寫幾句看完自己覺得的有印象的(寫程式可以先當作身體反應的準則),有興趣的還是買書去支持一下 Teddy 大大。

  • HTC One X 被婊很大。 XD
  • 接到 Exception 的程式碼,若沒有能力去處理,就先往上丟,最後讓 main 發生例外,或是 GUI 顯示比較白話的訊息。
  • Java 7 後新增的功能
    • Multi-catch exceptions:若收到不同的例外都是做同樣的處理(ex:印 log),就可以合併成一個 catch,減少重複的程式碼。
    • try-with-resource:從 C# 的 using 借過來的概念(最近剛好也碰到),直接把產生的資源寫在 try(...這裡頭...){},執行完就會自動釋放資源,可以少寫 finally,也不會忘記釋放資源。
  • Checked Exception v.s. Unchecked Exception
    • Checked Exception:Java 設計概念認為 Checked Exception 代表可以回復的錯誤,Compiler 會強迫開發者去處理 Checked Exception(catch 或是再丟出去)。不過我好像都沒有回復過 Checked Exception,要碼不是印 log,再往上丟,了不起就寫個 re-try 機制來想辦法衝過去。
    • Unchecked Exception:程式碼內不應該出現的錯誤,就使用 Unchecked Exception,若別人收到此錯誤,代表要去檢查為什麼發生,然後去修正程式碼。
    • C# 都是 Unchecked Exception,所以一開始寫發現都沒有 try catch,但最後又被 exception 炸到,很不習慣。
  • 若實在對例外處理沒有想法,那就是一直往上丟就對了,至少炸掉會知道,而不是被 catch 吃掉,提早在設計階段就炸掉趕快處理,比在上線才炸掉來的風險小。

最後壞味道和重構壞味道的部分,實用度很高,去買來翻看看吧。

Tomcat7 使用 Logging

專案有需要產生 Log 檔案的需求,以便未來服務發生錯誤有個記錄,所以又要看看 Tomcat7 是如何產生 Log。Tomcat7 預設使用 java.util.logging,目前開發的功能很小,就不打算要另外安裝 log4j,才不會每台機器要裝一次 log4j,PG 兼 SA 就只好挑重點做。

1) 程式碼取得 Logger

Logger logger = Logger.getLogger("com.xyz.foo");

2) 程式碼中使用 Logger

logger.info("Hi!");

3) 替專案設定 Handler

到 ${專案路徑}/WebContent/WEB-INF/classes/ 下,新增或編輯 logging.properties,加入 Handler 並設定相關資訊,是要用 Console 還是要 File,等級多少要印出來。

logging.properties

4) 替專案設定 Logger

通過 Logger Level 的訊息才傳給上一步設定的 Handler。

經過測試,有點討厭的是,Logger 要到負責啟動專案的 JRE 上來設定才有作用。以我使用 macbook 舉例,編輯  Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home/jre/lib/logging.properties,拉到最下面可以替 Logger 設定等級。

比如你專案中取得 Logger 是用

Logger.getLogger("com.xyz.foo");

那你可以在此新增

com.xyz.foo.level = FINE

這樣專案中透過 com.xyz.foo Logger,訊息的等級是 FINE 以上才會送給 Handler。而在 logging.properties 可以發現預設的 Logger Level 是 INFO。

.level = INFO

補充

JAVA Logging Level

SEVERE > WARNING > INFO > CONFIG > FINE > FINER > FINEST

Log 輸出

一條 Log 會
  • 第一關,先檢查 Logger Level 後才送到 Handler。
  • 第二關,再檢查 Handler Level 才決定是否要印在螢幕或是檔案上。
我個人習慣是
  • Handler Level 開到 FINEST(第二關永遠放行)。
  • 然後用 Logger Level 來決定是否要印在螢幕或是檔案上(第一關卡控)。
  • 如此,只要檢查 Logger Level 就能判斷會不會被印出來。

Tomcat7 使用 DataSource(MySQL)

之前比較好命有 JBoss 可以用,現在只有 Tomcat ,有些預設沒開的功能就要想辦法整合進來了,這陣子弄了些有的沒的,趕快記錄一下,免得忘記。

1) 在 Tomcat 下設定

到 $tomcat安裝路徑/conf/context.xml 新增 DataSource 的設定。

context.xml

2) 記得下載 mysql-connector.jar 放到 $tomcat安裝路徑/lib 底下。

3) 重新啟動 Tomcat 就應該可以看到有無連上 MySQL 。

4) 程式碼就可以透過 JNDI 用 jdbc/TestDB (上面設定檔替 DataSource 取的名稱)拿到 DataSource。

Eclipse 整合 Tomcat 遇到問題

但是從 Eclipse 下直接啟動 Tomcat 就會發現一直拿不到 DaraSource,是因為 Eclipse 開啓 Tomcat 時,會用預設的設定檔去覆蓋 Tomcat 的設定檔造成的。

其中一個解法是,直接修改 $eclipse使用的workspace/Servers/Tomcat/context.xml,把上一段做的變動改寫過來即可。

但這樣 Tomcat 和 Eclipse 會有兩個 context.xml (Don't Repeat Yourself),而用 Eclipse 啟動又會覆寫 Tomcat 原本的 context.xml (容易讓人搞不清楚到底以哪個為準)。所以就用了下面的做法。

綁定專案設定

直接修改 $project路徑/WebContent/META-INF/context.xml,把上一段做的變動改寫過來即可。Tomcat 再載入專案時就會加上專案裡新增的 DataSource。

這樣做之後接手的人,只需要部署 war 檔,不用特別到 Tomcat 下做設定,比較符合我個人開發專案的習慣。

AllJoyn Peer Group Manager Module

關於 AllJoyn 請參考這篇。

本篇簡單說明 AllJoyn 傳輸檔案的 Peer Group Manager Module,架構在原本 AllJoyn 之上,大大簡化了 AllJoyn 的概念和所需要寫的程式碼。

網路上中文的技術文件比較少,在此分享一下跟工作無關的部分。

AllJoyn File Transfer Module

關於 AllJoyn 請參考這篇。

本篇簡單說明 AllJoyn 傳輸檔案的 File Transfer Module,架構在原本 AllJoyn 之上,目前支援三種不同的傳檔情境。

網路上中文的技術文件比較少,在此分享一下跟工作無關的部分。

AllJoyn 開發筆記

AllJoyn 是高通的開源專案,AllJoyn 會自己處理底層的連線問題(支援 WiFi、藍芽),讓不同作業系統的設備透過 AllJoyn 來連線傳輸資料。

網路上中文的技術文件比較少,在此分享一下跟工作無關的部分。

2013 Java Developer Day 筆記心得

趁頭腦還清楚記錄今日的心得,有些比較不熟悉或是沒聽懂的部分就省略了,免得誤導看到本文的人。最下方有放 陳奕孝 baterme@gmail.com 整理出來這次會議的懶人包(本人沒有影分身術無法同時參加四個議題 XD),回家要交作業的人可以連去複習!


55個Java SE 8的新功能(Stephen Chin)

  • 主要最大的更新就是 Lamda,然後其他54個功能我都…

心得

  • 只有60分鐘要介紹55個功能,怎麼可能都記得。


Java EE7全新企業平台:提高生產力與支援HTML5(Reza Rahman

)

  • 新的 API 或是更新的 API(JMS、WebSocket、JSon、JAX-RS、JPA、JTA、JSF、Batch)。
    • 介紹了很多但不外乎就是以下兩個好處。
    • 簡化舊版本的寫法。
    • 再也不用使用第三方的 API。
  • BeanValidation
    • 在變數前面加上 annotation(ex: @NotNull、@Max(10)) 判斷變數的合法性。

心得

  • 講師完全是火力全開,完全把大家當成美國人的速度在講課。
  • Manning: EJB 3 in Action 這本書惠我良多,但都買不到實體書,只好含淚放棄簽書的計畫。如果你有這本書要賣,請 mail 給我,持續徵求實體書中(簡中原文皆可)。
  • 解開了從講師手上拿到獎品的成就...
1095089_615306678492287_1882015555_n


淺談 Lambda 運算式(Stephen Chin)


雖然知道 Lambda 基本上就是能將方法本身當作參數傳遞,也在 Ruby / C 上實作過,但 Java 8 的 lamda 實作我目前還無法融會貫通(有點當初 Java 5 導入 Generic 昏頭的狀況 XD ),這邊就不亂寫筆記以免誤導大家。
這堂課主要就先說明 Java 中 Lambda 的語法。透過一個類似貪食蛇的遊戲,說明 Collection 在支援了 Lambda 後(集合內的元素,篩選、根據條件修改、轉換成不同的元素…),可以省去很多寫程式碼的時間。

心得

  • 連聽三堂英文,已經昏昏欲睡。


整合SOA、 jBPM、 Rule 引擎建置次世代的企業應用雲端系統(林瑋玫 Christina)

  • JBoss AS 7(JBoss 的 Java EE Server 社群版) 更名為 Wild Fly。
  • Rule
    • 條件判斷(一堆 if else)抽取成 Rule。
    • 方便修改。
    • 不用重新 compile code。
  • BPM
    • 商業邏輯(jBPM)可以去串這些 Rule 來用。
    • Rule 可以設計成可獨立的最小單位,而非為了商業邏輯將多個 Rule 合併成一個大的 Rule。方便重用 Rule。
    • 可視化的畫面讓業務人員能參與流程設計。
  • Gurvor
    • 可以作為 Rule 和 BPM 文件的儲存庫。想成是程式碼的 SVN 或是 Git 版本控管系統。
  • Camel
    • 資料流的處理,輸入資料(FTP、JMS、HTTP…)、處理資料(修改格式、物件轉換、路由…)、輸出資料。
  • SwitchYard
    • 我最想聽的部分,結果沒有時間就草草代過了。這邊就我導入的心得作補充。
    • JBoss ESB 的後繼者,目前剛進入1.0版。
    • SwitchYard 是一個可以實踐 SOA 的平台(採用 SCA 的架構來設計 SOA)。
    • 第一,它是一個 ESB,可以把所有的外部的服務掛上來。
    • 第二,它其中可以建立 Java Bean 元件、Camel 元件、BPM 元件…,而這些元件又能去使用作為 ESB 掛上來的外部服務。
    • 第三,將完成的功能(利用元件 + 外部服務)再以 SOAP、FTP、Queue…等方式再發佈出去。
    • 綜合以上三點,SwitchYard 把散落在不同系統的功能收集起來,串成新的功能再重新發佈成新的功能。

心得

  • BPM 實作太長了太深入了,沒有把 SwitchYard 講完很可惜,聽眾不容易了解 JBoss SOA 整個架構。
  • 比較扯的是C大的同事聽到疑似打瞌睡有點…


從 Web Services 到 Mobile Services(蘇國鈞)

  • 過去 Client/Server 架構之於現在 WebApp,MobileApp/Cloud。
  • Service Invocation
    • Client 端怎麼去呼叫到 Web Service(簡化定義就是透過 HTTP 取得的資料)。
    • Restful 的 Web Service 透過 HTTP 的 Post/Delete/Get/Post 就可以完成呼叫。
    • SOAP 的 Web Service 必須要先 parse WSDL,產生對應的 SOAP 才能完成呼叫。
    • 前端(JavaScript)沒法透過 HTTP 的方式(比較簡單),就必須透過其他工具呼叫 SOAP 的 Web Service(比較麻煩)。
  • Data Format
    • 討論 JSON 格式定義的正確性,實作中要注意符合定義。
  • 認證
    • OAUTH
      • 授權除了確認身分外,還會分享個人資料(如果使用者也同意授權這些資料的話)。
    • OpenID
      • 只有確認身分,並沒有任何個人資料可以分享。
    • OAUTH / OpenID 本質上並非是一樣的概念,請視開發需求決定要使用哪種 solution。
    • 如果只是要確認身分,就不一定要導入OAUTH。

心得

  • 超級賽亞人(升級)、火影忍者(擴充!? 我有點忘了)梗 + 10 分!
  • Live Demo 就是會出問題,然後只是老師故意鋪的梗也 + 10 分!


排隊應用開發(王建興 Qing)

  • Why Queue
    • 演算法實作。
    • 分配有限的資源。
    • 跨行程(不同 VM、不同主機…)有收送訊息的需求。
  • Queue 的本質
    • 非同步的傳送方式。在大家都在忙的情況下,有人的需求不會馬上被滿足。
    • Buffer。作不完的事情都先被記錄下來等等在做。
    • 標準化的溝通介面。大家都照著一樣的規則,把需求丟進 Queue、把需求從 Queue 拿出來,不同的程式語言之間也可以透過 Queue 做溝通、合作(想像成 C# 和 Java 之間可以用 XML 作資料交換)。
  • In Process Queue
    • 訊息在同一個 VM 上。
    • 不用序列化,效能快,但擴展性差(升級CPU、RAM…)。
  • NetWork-based Queue
    • 訊息在不同 VM 傳輸(ex: JMS)。
    • 需要續列化,擴展性佳(加入新的主機)。
    • 可加入備援、容錯機制。
  • 沒有完美的實作,視需求來選擇要導入的 Queue。

心得

  • 很多 Queue 基本的觀念,然後投影片熱騰騰的,Q 大平常應該就保留很多梗可以寫(有 Mr. 6 的風格)。
  • 臨時加入上廁所的 Queue 實例照片加10分。
  • 中間口頭置入式行銷 Cubie(約三秒)。


 JAVA Day 2013 非官方懶人包