Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Java 8
Code

Java 8 Cho Phát Triển Android: Các Phương Thức Static và Mặc Định

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Java 8 for Android Development.
Java 8 for Android: Cleaner Code With Lambda Expressions
Java 8 for Android Development: Stream API and Date & Time Libraries

Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)

Java 8 là một bước tiến lớn đối với ngôn ngữ lập trình và bây giờ, với việc phát hành Android Studio 3.0, các nhà phát triển Android cuối cùng đã có quyền truy cập vào hỗ trợ tích hợp sẵn cho một số tính năng quan trọng nhất của Java 8.

Trong loạt bài gồm ba phần này, chúng tôi đã khám phá các tính năng của Java 8 mà bạn có thể bắt đầu sử dụng trong dự án Android của mình ngay hôm nay. Trong Cleaner Code With Lambda Expressions, chúng tôi đã thiết lập việc phát triển của mình để sử dụng hỗ trợ của Java 8 được cung cấp bởi bộ công cụ mặc định của Android, trước khi xem xét kỹ lưỡng lambda expressions.

Trong bài đăng này, chúng ta sẽ xem xét hai cách khác nhau để bạn có thể khai báo các phương thức non-abstract trong các Interfaces của bạn (điều không thực hiện được ở các phiên bản trước của Java). Chúng ta cũng sẽ trả lời câu hỏi, bây giờ các giao diện có thể triển khai các phương thức, sự khác biệt chính xác giữa các abstract class và interface là gì?

Chúng tôi cũng sẽ giới thiệu một tính năng của Java 8 cho phép bạn tự do sử dụng cùng một chú thích nhiều lần theo bạn muốn trong cùng một vị trí, đồng thời vẫn tương thích ngược với các phiên bản cũ của Android.

Nhưng trước tiên, chúng ta hãy xem một tính năng của Java 8 được thiết kế để sử dụng kết hợp với các lambda expression mà chúng ta đã thấy trong bài viết trước.

Viết Lambda Expression gọn đẹp hơn, với phương thức tham chiếu

Trong bài trước, bạn đã thấy cách bạn có thể sử dụng lambda expressions để xóa nhiều đoạn code boilerplate khỏi các ứng dụng Android của bạn. Tuy nhiên, khi một lambda expression chỉ gọi một phương thức duy nhất đã đặt tên, bạn có thể cắt giảm nhiều đoạn code hơn từ dự án của bạn bằng cách sử dụng một phương thức tham chiếu.

Ví dụ, lambda expression này thực sự chỉ chuyển hướng công việc sang một phương thức handleViewClick đang tồn tại:

Trong tình huống này, chúng ta có thể tham chiếu phương thức này bằng tên gọi, sử dụng :: làm toán tử tham chiếu phương thức. Bạn tạo kiểu tham chiếu phương thức này, sử dụng định dạng dưới đây:

Trong ví dụ về Floating Action Button, chúng ta có thể sử dụng phương thức tham chiếu như phần code chính của lambda expression của chúng ta:

Lưu ý rằng phương thức được tham chiếu phải có cùng tham số với Interface-trong trường hợp này, đó là View.

Bạn có thể sử dụng toán tử phương thức tham chiếu (::) để gọi tham chiếu bất sau đây:

Một phương thức Static

Nếu bạn có một lambda expression gọi một phương thức static:

Sau đó bạn có thể biến nó thành một phương thức tham chiếu:

Ví dụ, nếu bạn đã có một phương thức static PrintMessage trong một class MyClass, thì phương thức tham chiếu của bạn sẽ giống như sau:

Một Instance của phương thức của một Object cụ thể

Đây là một instance cho phương thức của một đối tượng được biết trước, cho phép bạn thay thế:

Với:

Vì thế, nếu bạn có lambda expression dưới đây:

Sau đó việc giới thiệu một phương thức tham chiếu sẽ đem đến cho bạn điều sau đây:

Một Instance của phương thức của một Object (đối tượng) tùy ý của Type đặc biệt

Đây là một phương thức dụ của một đối tượng được biết đến trước, cho phép bạn thay thế:

Các tham chiếu Constructor

Constructor tham chiếu tương tự như các tham chiếu phương thức, ngoại trừ việc bạn sử dụng từ khóa new để gọi constructor. Ví dụ, Button :: new là một constructor tham chiếu cho Class Button, mặc dù constructor chính xác được đã được gọi đến phụ thuộc vào tình huống.

Bằng việc sử dụng các tham chiếu cho constructor, bạn có thể biến:

Thành:

Ví dụ: nếu bạn có interface MyInterface sau đây:

Sau đó, bạn có thể sử dụng các tham chiếu constructor để tạo các Instances mới của class Student:

Nó cũng có thể tạo ra constructor tham chiếu cho các kiểu của array. Ví dụ, một tham chiếu constructor cho một array của ints int [] :: new.

Bổ sung phương thức mặc định cho Interface của bạn

Trước khi có Java 8, bạn chỉ có các phương thức abstract trong các Interface của mình (nghĩa là các phương thức không có phần code thực sự), điều này làm cho việc phát triển Interface trở nên khó khăn, sau khi xuất bản.

Mỗi lần bạn bổ sung một phương thức vào một định nghĩa Interface, bất kỳ class nào triển khai Interface này đột nhiên sẽ bị thiếu một phần triển khai. Ví dụ, nếu bạn có một Interface (MyInterface) đã được sử dụng bởi MyClass, sau đó thêm một phương thức để MyInterface sẽ phá vỡ tính tương thích với MyClass.

Trong tình huống tốt nhất, khi bạn chịu trách nhiệm cho một số lượng ít các Class đã sử dụng MyInterface, hành động này sẽ gây phiền phức nhưng có thể quản lý được - bạn chỉ cần dành thời gian để cập nhật các class của mình với quá trình triển khai mới. Tuy nhiên, mọi thứ có thể trở nên rất phức tạp nếu một số lượng lớn các Class triển khai từ MyInterface, hoặc nếu Interface đã được sử dụng trong các Classes mà bạn không chịu trách nhiệm.

Mặc dù có một số cách giải quyết vấn đề này, nhưng không có cái nào lý tưởng hết. Ví dụ, bạn có thể bao gồm các phương thức mới trong một Abstract Class, nhưng điều này vẫn yêu cầu tất cả mọi người cập nhật code của họ để mở rộng Abstract Class này; và trong khi bạn có thể mở rộng Interface ban đầu bằng một Interface mới, bất kỳ ai muốn sử dụng các phương pháp mới này cũng sẽ cần phải viết lại tất cả các tham chiếu của Interface họ đang có.

Bằng việc giới thiệu các phương thức mặc định trong Java 8, bây giờ có thể khai báo các phương thức non-abstract (ví dụ các phương thức có phần code thực sự) bên trong các Interface của bạn, vì vậy cuối cùng bạn có thể thực hiện các triển khai mặc định cho các phương thức của mình.

Khi bạn thêm một phương thức vào interface của bạn như một interface mặc định, bất kỳ Class nào triển khai interface này không nhất thiết phải cung cấp việc triển khai của chính nó, cho phép bạn cập nhật Interface mà không phá vỡ sự tương thích. Nếu bạn thêm một phương thức mới vào Interface làm phương thức mặc định, thì mọi Class sử dụng Interface này nhưng không cung cấp cho việc triển khai của riêng nó sẽ kế thừa kế thừa việc triển khai mặc định của phương thức. Khi Class không thiếu một phần triển khai, nó sẽ tiếp tục hoạt động như bình thường.

Trên thực tế, việc giới thiệu các phương thức mặc định là lý do khiến Oracle có thể tạo ra một số lượng lớn bổ sung cho Collections API trong Java 8.

Collection là một Interface chung được sử dụng trong nhiều Class khác nhau, do đó thêm các phương thức mới vào interface này có khả năng làm hỏng vô số dòng mã lệnh. Thay vì thêm các phương thức mới vào Collection Interface và phá vỡ những Class bắt nguồn từ Interface này, Oracle đã tạo ra tính năng phương thức mặc định và sau đó thêm các phương thức mới này làm phương thức mặc định. Nếu bạn xem xét phương thức Collection.Stream() mà chúng ta sẽ khám phá chi tiết trong phần ba, bạn sẽ thấy rằng nó đã được bổ sung như một phương thức mặc định:

Tạo một phương thức mặc định rất đơn giản - chỉ cần thêm các modifier mặc định vào signature của phương thức của bạn:

Bây giờ, nếu MyClass sử dụng MyInterface nhưng không cung cấp việc triển khai defaultMethod riêng của nó, nó sẽ chỉ kế thừa việc triển khai mặc định được cung cấp bởi MyInterface. Ví dụ, các Class sau đây vẫn sẽ biên dịch:

Một Class đang triển khai có thể override triển khai mặc định được cung cấp bởi một Interface, vì vậy các Class vẫn đang kiểm soát hoàn toàn việc triển khai của chúng.

Mặc dù các phương thức mặc định là sự bổ sung đáng hoan nghênh dành cho các nhà thiết kế API, đôi khi chúng có thể gây ra vấn đề cho các nhà phát triển đang cố gắng sử dụng nhiều Interface trong cùng một Class.

Tưởng tượng khi bổ sung vào MyInterface, bạn có kết quả sau:

Cả hai MyInterfaceSecondInterface đều chứa một phương thức mặc định có chính xác cùng một signature (defaultMethod). Bây giờ hãy tưởng tượng bạn cố gắng sử dụng cả hai Interface này trong cùng một Class:

Tại thời điểm này bạn có hai triển khai bị xung đột defaultMethod, và trình biên dịch không có biết phương thức nào nó nên sử dụng, vì vậy bạn sẽ gặp phải một lỗi trình biên dịch.

Một cách để giải quyết vấn đề này là ghi đè phương thức xung đột với phần triển khai của riêng bạn:

Một giải pháp khác là xác định phiên bản của defaultMethod bạn muốn triển khai, sử dụng định dạng sau đây:

Vì vậy nếu bạn muốn gọi phần khai triển MyInterface#defaultMethod(), bạn sử dụng đoạn code sau đây:

Việc sử dụng các phương thức Static trong Interface Java 8

Tương tự với các phương thức mặc định, các phương thức static của interface mang đến cho bạn một cách để định nghĩa các phương thức, một class đang được thiết lập không thể override một interface của các phương thức static.

Nếu bạn có các phương thức static cụ thể đến một interface, thì các phương thức static của Java 8 mang lại cho bạn một cách thức để đặt những phương thức này vào trong các interface tương ứng, hơn là chỉ lưu chúng lại trong một class độc lập.

Bạn tạo một phương thức static bằng cách đặt từ khoá static ở phía trước của method signature, ví dụ như:

Khi bạn triển khai một interface gồm có phương thức static của interface, phương thức static đó vẫn là một phần của interface và không được kết thừa bởi class triển khai nó, vì thế bạn sẽ cần phải thêm tiếp đầu ngữ vào phương thức với tên của interface, ví dụ như:

Điều này cũng có nghĩa là một class và một interface có phương thức static với cùng signature. Ví dụ, sử dụng MyClass.staticMethodMyInterface.staticMethod trong cùng một class sẽ không gây ra lỗi compile-error.

Vậy, có phải Interfaces chỉ là các Abstract Classes không?

Việc bổ sung các phương thức static cho Interface và các phương thức mặc định đã khiến một số nhà phát triển đặt câu hỏi liệu các Interface của Java có đang trở nên giống các Abstract Classes hơn hay không. Tuy nhiên, ngay cả với việc bổ sung các phương thức mặc định và static cho Interface, vẫn có một số khác biệt đáng chú ý giữa các Interface và các Abstract Class:

  • Các Abstract Class có thể có các biến số final, non-final, static và non-static, trong khi một Interface chỉ có thể có các biến số static và final.
  • Các Abstract Class cho phép bạn khai báo các trường không phải static và final, trong khi các trường của Interface vốn có static và final.
  • Trong Interface, tất cả các phương thức mà bạn khai báo hoặc định nghĩa là các phương thức mặc định là public, trong khi trong Abstract Class bạn có thể xác định các phương thức cụ thể rõ ràng public, protected, và private.
  • Abstract class là class, và do đó cần có các trạng thái (state), interface không thể có state tương tác với nó.
  • Bạn có thể định nghĩa các constructor bên trong abstract class, một cái gì đó không thể có trong các Java interfaces.
  • Java chỉ cho phép bạn mở rộng một class (bất kể liệu nó có phải là abstract hay không), nhưng bạn có quyền triển khai bao nhiêu interface mà bạn cần. Có nghĩa là về cơ bản interfaces có biên độ khi bạn yêu cầu đa kết thừa, mặc dù bạn cần cảnh giác về lỗi deadly diamond of death!

Áp dụng chú thích tương tự bao nhiêu lần cũng được.

Theo truyền thống, một trong những hạn chế của các chú thích Java là bạn không thể áp dụng cùng một chú thích nhiều lần ở cùng một vị trí. Hãy thử sử dụng cùng một chú thích nhiều lần, và bạn sẽ gặp một lỗi compile-time.

Tuy nhiên, việc giới thiệu các chú thích lặp lại của Java 8, bây giờ bạn đã tự do sử dụng cùng một chú thích nhiều lần như bạn muốn ở cùng một vị trí.

Để đảm bảo mã của bạn vẫn tương thích với các phiên bản trước đó của Java, bạn cần lưu trữ các chú thích lặp lại của mình trong chú thích container.

Bạn có thể cho trình biên dịch tạo ra vùng chứa này, bằng cách hoàn thành các bước sau:

  • Đánh dấu chú thích trong câu hỏi với chú thích meta @Repeatable (chú thích được sử dụng để trích dẫn một chú thích). Ví dụ: nếu bạn muốn thực hiện chú thích @ToDo có thể lặp lại được, bạn cần sử dụng: @Repeatable (ToDos.class). Giá trị trong ngoặc đơn là loại chú thích container mà trình biên dịch cuối cùng sẽ tạo ra.
  • Khai báo kiểu chú thích đang có Điều này phải có thuộc tính đó là một mảng của loại chú thích lặp lại, ví dụ:

Cố áp dụng cùng một chú thích nhiều lần mà không phải khai báo rằng nó lặp lại sẽ dẫn đến lỗi tại thời điểm biên dịch. Tuy nhiên, một khi bạn đã xác định đây là một chú thích có thể được lặp lại, bạn có thể sử dụng chú thích này nhiều lần ở bất kỳ vị trí nào mà bạn muốn sử dụng một chú thích tiêu chuẩn.

Tổng kết

Trong phần hai của loạt bài về Java 8, chúng tôi đã thấy cách bạn giảm đi những template có sẵn từ dự án Android của bạn bằng cách kết hợp những lambda expressions với phương thức tham chiếu, và làm sao cải thiện interface của bạn với các phương thức static và mặc định.

Trong phần ba và phần cuối cùng, chúng tôi sẽ xem xét một Java 8 API mới, cho phép bạn xử lý số lượng lớn dữ liệu một cách hiệu quả hơn và khai báo hơn mà không phải lo lắng về concurrency và thread managment (quản lý luồng). Chúng tôi cũng sẽ kết hợp một vài tính năng khác nhau mà chúng tôi đã thảo luận trong loạt bài này bằng cách khám phá vai trò mà Functional Interfaces phải thực hiện trong lambda expression, các phương thức static interface, các phương thức mặc định và hơn thế nữa.

Và cuối cùng, mặc dù chúng tôi vẫn đang đợi API mới cho Date và Time của Java 8 chính thức xuất hiện trên Android, tôi sẽ chỉ ra cách bạn có thể bắt đầu sử dụng API mới này trong các dự án Android của mình ngay hôm nay với sự giúp đỡ của một số thư viện của bên thứ ba.

Trong thời điểm hiện giờ, hãy xem qua những bài viết khác về Java và phát triển ứng dụng Android!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.