8 điểm khác nhau giữa Scala và Java 8
1. Scala – ngôn ngữ lập trình hàm (Functional Language) “write less, do more” Ví dụ , nhu cầu ứng dụng của bạn cần lựa chọn một số từ (string) thích hợp thỏa mãn tiêu chí điều kiện của bạn từ một danh sách các từ – List(). Để thực hiện logic này trong Java (7, ...
1. Scala – ngôn ngữ lập trình hàm (Functional Language)
“write less, do more”
Ví dụ, nhu cầu ứng dụng của bạn cần lựa chọn một số từ (string) thích hợp thỏa mãn tiêu chí điều kiện của bạn từ một danh sách các từ – List(). Để thực hiện logic này trong Java (7, 6), cú pháp sẽ có dạng:
1 2 3 4 5 6 7 8 9 |
List<String> filteredList = LinkeList<String>(); // giả sử chúng ta có danh sách các từ trong không gian mẫu là stringToFilterList for(stringToFilter : stringToFilterList){ if(StringUtils.isNotBlank(stringToFilter) && stringToFilter.startsWith("A")){ filteredList.add(stringToFilter); } } |
Mình vừa thực hiện 3 bước để lấy kết quả mong muốn. Cùng trả lại kết quả như vậy nhưng trong Scala, các bước sẽ ngắn gọn hơn với lambda expressions:
1 2 3 4 |
// giả sử chúng ta có danh sách các từ trong không gian mẫu là listStringToFilter val filteredList = stringToFilterList.filter(s => s.nonEmplty()n && s.startsWith("A")) |
Trong ví dụ trên, cách mà Java phát biểu là : “cần tạo ra một không gian lưu trữ dữ liệu lọc, lọc từng từ trong không gian mẫu và nếu từ nào thỏa mãn điều kiện bắt đầu bằng chữ A thì sẽ lưu lại”. Còn Scala phát biểu rằng : “lọc tất cả các từ mà từ đó thỏa mãn điều kiện bắt đầu bằng chữ A thì sẽ lưu lại”.
Theo ý kiến cá nhân thì không thể phủ nhận rằng Java là ngôn ngữ giải thích nghĩa đen một vấn đề rất trong sáng. Nhưng hãy xem cách mà Scala diễn giải cũng khá dễ hiểu và có phần ngắn gọn hơn.
Trong Java 8 hỗ trỡ lambda, code có thể viết lại như sau :
1 2 3 4 |
List<String> filteredList = stringToFilterList.stream().filter(s => StringUtils.isNotBlank(s) && s.startsWith("A")).collect(Collectors.toList()) |
Dù việc viết các dòng code so với Java (7, 6) đã ít hơn, xong so với Scala nó dường như vẫn quá dài.
2. Monads
Mình tạm dịch là ‘đơn nguyên’ hay ‘đơn tử’. Đây là một khái niệm để lập trình hàm từ toán học. Khái niệm này dường như khó hiểu, nhưng nó lại không khó để học, mời các bạn cùng xem tiếp phần dưới đây.
Chúng ta sẽ nói về lợi ích của monads trpng lập trình, và đối với newbie mới học sẽ gặp các monads phổ biến nhất trong Scala như:
2.1 “Maybe”M monad.
Maybe monad thường được gọi như là một “Optional types”, nó implemented một Option[T]. Option[T] này xác định rõ 2 trạng thái: một là Some[T] (khi giá trị thực của T tồn tại), hai là None nếu Option rỗng.
Trong Scala chúng ta sẽ gặp cách khai báo :
1 2 3 |
val maybeString = Option(someOrtherString) |
Trong Java bạn sẽ cần kiểm tra nếu(if) tồn tại thì gắn giá trị T không(else) thì gắn bằng None. Nhưng Option đã thay bạn làm việc kiểm tra này.
Scala cũng cung cấp một số phương pháp khác:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// matching of state, cách hoạt động gần giống như switch trong java maybeString match { case Some(s) => { // xử lý code với string được matching } case None => { // xử lý code khi None } } // lấy giá trị lưu trong monads, hoặc sử dụng giá trị mặc định nếu monad rỗng maybeString.getOrElse("defaultValueOfThisString") // lấy giá trị lưu trong monads, hoặc thực thi một function nếu monad rỗng maybeString.orElse({ // xử lý logic của bạn log.warn("Using default value!!!") Some("defaultValueOfThisString") }).get // và orElse sẽ không bao giờ nhận một giá trị None.get ở đây // Bạn muốn kiểm tra trước lấy giá trị của maybeString..... if (maybeString.isEmpty) { ... } // Bạn muốn kiểm tra với một số điều kiện khác if (maybeString.exists(_.startsWith("somePredicate"))) { ... } |
2.2 Try
Cách thức hoạt động của Try trong Scala về ý nghĩa là giống Java.
1 2 3 4 5 6 7 8 9 |
Try({ //...code của bạn }) ... // có thể gọi các class trong Try Try(SomeClass.someMethod()) ... |
Try cũng có hai trạng thái: Success, khi khối code của bạn không sinh ra ngoại lệ; Failure khi trong khối code trong Try có lỗi xảy ra. Chúng ta hãy cùng xem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// matching of state Try(SomeClass.someMethod()) match { case Success(s) => { // xử lý code với kết quả } case Failure(e) => { // xử lý code với exception } } // Khi muốn xử lý khi Try nếu Failure Try(SomeClass.someMethod()).recover({ // xử lý }) // Thêm một số điều kiện trước khi Try trả về kết quả Success Try(SomeClass.someMethod()).filter({ // điều kiện của bạn }) // Khi mà chỉ quan tâm tâm kết quả trả về Try(SomeClass.someMethod()).toOption // Lấy giá trị nếu khối Try Success hoặc giá trị mặc định Try(SomeClass.someMethod()).getOrElse("somethingDefault") // Nếu cần làm gì đó nhiều hơn việc chỉ đơn giản trả về kết quả Try(SomeClass.someMethod()).orElse({ log.error("We couldn't get that thing for you, enjoy your default value") Success("somethingDefault") }).get // Nếu chỉ muốn biết kết quả khối Try Try(SomeClass.someMethod()).isSuccess Try(SomeClass.someMethod()).isFailure |
2.3 Future
Đây là một trong những điều rất thú vị, nó mở ra mộ không gian mới trong việc lập trình không đồng bộ – asynchronous programming. Đây cũng là một trong số rất nhiều lý do làm cho Scala trở nên là một ngôn ngữ mạnh. Ví dụ :
1 2 3 4 5 6 |
scala> val future = concurrent.Future{println("A"); println("B")} A B future: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@1725dc0f |
Việc thực thi từng lệnh code trong Future lần lượt, kết quả là backgound task in ra chữ A, sau đó là chữ B rồi mới đưa ra kết quả của Future. Nhưng hãy xem chuyện gì xảy ra khi mình thêm một Thread bên trong Future:
1 2 3 4 5 6 7 |
scala> val future = concurrent.Future{println("A");Thread.sleep(5000);println("B")} A future: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@6989da5e scala> B |
Khi đó backgound task in ra chữ A, trả về kết quả của Future và sau đó chờ 5s mới in ra chữ C. Đáng nhẽ phải in ra chữ C rồi mới in ra kết quả thực thi của Future. Điều này chứng tỏ sự không đồng bộ đã xảy ra, tức là main Thread tiếp tục hoạt động mà không chờ sub Thread trong Future thực hiện xong. (Bài này mang tính chất so sánh nên mình không đi vào chi tiếp, các bạn có thể tìm hiểu thêm về Future tại đây)
Như đã thấy Scala tuân thủ một tập quy tắc, dễ dàng chuyển đổi. Điều này cho phép chúng ta tập trung vào việc thực hiện logic của ứng dụng.
3. Immutability
Tính bất biến trong ngôn ngữ lập trình làm là cách tiếp cận làm cho mọi thứ trở nên khác biệt trong kinh nghiêm phát triển phần mềm.
Tính bất biến là mô hình mà đối tượng sau khi được tạo ra sẽ không thể thay đổi trạng thái của nó. Nếu bạn muốn thay đổi trạng thái của đối tượng bất biến thì bạn cần tạo ra bản sao của nó và thay đổi các thuộc tính cần thiết.
Trong Java, ví dụ về tính bất biến đơn giản nhất là đối tượng String. Mỗi khi đối tượng String thay đổi là một bản sao của nó được tạo ra.
Tính bất biến có thể coi là chìa khóa của ngôn ngữ lập trình hàm (lập trình chức năng). Bởi vì nó phù hợp với mục tiêu giảm tải sự thay đổi các đối tượng trong chương trình, điều này giúp ta quản lý dễ dàng suy luận và quản lý các thành phần trong chương trình.
4. Traits trong Scala và Virtual extension method trong Java
Scala cung cấp một cơ chế tuyệt vời để mở rộng các class. Về mặt kỹ thuật thì Trait cung cấp giao diện(interface), bao gồm các tùy chọn (phương thức hoặc thuộc tính). Một class có thể gọi nhiều Trait.
Lưu ý rằng Scala giống với Java là đều không hỗ trợ đa kế thừa, một subClass chỉ có thể kế thừa từ một superClass. Nhưng với Trait thì khác, một class có thể gọi nhiều Trait với từ khóa “with”.
Với Trait, bạn có thể cô lập các tính năng chung chung theo kiểu moduls và gọi lại chúng ở bất cứ class nào khi cần sử dụng.
Bên cạnh đó, trong Java 8 mới đây có giới thiệu Virtual Extension Methods (hay còn được gọi là Default Methods)
5. Kiểu dữ liệu và thư viện đa dạng
Không giống như Virtual Extension Methods trong Java 8, Scala cung cấp các implicit class giúp bạn có thể định nghĩa những hành vi mới và sử dụng chúng ở bất kỳ class nào với cách import đơn giản. Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 |
// định nghĩa Implicit class object IntExtensions { implicit class IntPredicates(i: Int) { def isEven = i % 2 == 0 def isOdd = !isEven } } import IntExtensions._ // import ở nơi cần dùng 4.isEven // sử dụng function trong implicit class đã import |
Một trong những điều đáng chú ý nhất trong cách tiếp cận này là có một bộ thư viện chuẩn là cầu nối giữa bộ thư viện của Java và bộ thư viện của Scala. Nhờ đó trong mã Scala ta có thể gọi phương thức “.asJava()” và trong mã Java có thể gọi “.asScala()” làm cho cách xử lý phong phú đa dạng hơn.
6. Việc sử dụng các mẫu thiết kế (design patern)
Ghé qua một diễn đàn nào đó có thể bạn bắt gặp một số bài viết như “A factory pattern in Scala”, “a Command pattern in Scala”. Mình từng đọc được một bài viết nói về việc sử dụng các mấu thiết kế, tác giả của bài viết là một người nhiều năm kinh nghiệm làm việc trên cả 2 ngôn ngữ Java và Scala. Ông nói rằng việc sử dụng các mẫu thiết kế trong scala không thật sự cần thiết như trong Java. Bởi vì cách tiếp cận vấn đề theo hướng kiểu function mở ra nhiều hướng giải quyết hơn và khi đó cách giải quyết phụ thuộc vào logic của người lập trình nên không còn phụ thuộc vào các mẫu thiết kế.
7. Minimal codebase.
Có nghĩa là tối thiểu code. Đây là một tính năng trong Scala mà Java chưa được thông qua.
-Ví dụ điển hình nhất là khai báo một class. Trong Java bạn sẽ khải báo như thế nào???
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Person { private final Integer id; private String name; public Person(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() {..} public String getName() {..} public void setName(String newName) {..} @ Override public boolean equals(Object obj) {..} @ Override public int hashCode() {..} } |
Dưới đây là cách bạn khai báo nó tương tự trong Scala:
1 2 3 4 5 |
case class Person(@BeanProperty val id: Int,@BeanProperty var name: String) /*@BeanProperty là annotation mà khi compiler sẽ tự động tạo ra getters and setters giống java-style, như là getId(), setName(..),...., thay cho các getter-setters như mã code java. */ |
-Đê khởi tạo một biến trong Java: