Author Archives: Phở Code

Java 8 – Thừa kế

Trong các bài trước, chúng ta đã làm việc sơ qua với tính năng thừa kế trong Java. Trong Java thì các lớp có thể thừa kế lại các lớp khác, tức là có chứa các thuộc tính và phương thức của các lớp khác.

Một lớp kế thừa từ lớp khác còn được gọi là subclass (hoặc lớp con). Lớp được các lớp khác kế thừa lại thì được gọi là superclass (hay lớp cha hoặc lớp cơ cở).

Mặc định trong Java thì toàn bộ các lớp, kể cả lớp được định nghĩa bởi lập trình viên, nếu không khai báo kế thừa từ lớp nào thì đều được kế thừa từ một lớp gốc có tên là Object.

Các lớp cũng có thể được kế thừa từ một lớp đã được kế thừa, cứ như vậy, và tất nhiên lớp cha cao nhất là lớp Object.

Tính năng thừa kế là một tính năng rất mạnh mẽ, nó cho phép chúng ta tạo thêm lớp mới nhưng không cần viết các đoạn code đã có sẵn trong các lớp khác, chúng ta chỉ cần “kế thừa” lại và viết thêm các đoạn code mới.

Một lớp con kế thừa toàn bộ các thuộc tính, phương thức và các lớp lồng nhau của lớp cha. Tuy nhiên không kế thừa phương thức khởi tạo (constructor), nhưng chúng ta có thể gọi phương thức khởi tạo của lớp cha trong lớp con.

Cây phân lớp Java

Lớp Object được định nghĩa trong gói java.lang định nghĩa tất cả các hành vi của toàn bộ các lớp, kể cả các lớp mà chúng ta tự viết. Trong Java thì có một số lớp kế thừa trực tiếp từ lớp Object, và một số lớp khác lại kế thừa từ các lớp này, tạo thành một cây phân cấp các lớp.

Ví dụ

Chẳng hạn chúng ta có đoạn code định nghĩa lớp Bicycle như sau:

public class Bicycle {
    private int gear;
    private int speed;

    public Bicycle(int gear, int speed) {
        this.gear = gear;
        this.speed = speed;
    }

    public int getGear() {
        return gear;
    }

    public void setGear(int newValue) {
        gear = newValue;
    }

    public int getSpeed() {
        return speed;
    }

    public void speedUp(int increment) {
        speed += increment;
    } 
}

Lớp Bicycle có 2 thuộc tính và 4 phương thức. Trong đó phương thức Bicycle() là phương thức khởi tạo, phương thức này nhận vào 2 tham số là speedgear dùng để gán cho 2 thuộc tính speedgear.

Sau đó chúng ta định nghĩa lớp MountainBike kế thừa lớp Bicycle như sau:

public class MountainBike extends Bicycle {
        
    public int seatHeight;

    public MountainBike(int startHeight,                        
                        int speed,
                        int gear) {
        super(seatHeight, speed, gear);
        seatHeight = startHeight;
    }   
        
    public void setHeight(int newValue) {
        seatHeight = newValue;
    }   
}

Chúng ta cho lớp MountainBike kế thừa từ lớp Bicycle bằng cách thêm từ khóa extends Bicycle vào sau tên MountainBike. Bên trong chúng ta định nghĩa thêm thuộc tính seatHeight, 2 phương thức MountainBike()setHeight(). Phương thức khởi tạo MountainBike()nhận vào các tham số seatHeight, speedgear, chúng ta không gán giá trị trực tiếp cho các thuộc tính speedgear của lớp MountainBike, mà thay vào đó chúng ta gọi phương thức khởi tạo của lớp cha là super() và truyền vào các giá trị speedgear. Phương thức super() sẽ gọi phương thức khởi tạo của lớp Bicycle(), và như thế thì các giá trị speedgear của lớp MountainBike sẽ được gán luôn trong đó

Quyền thừa kế của lớp con

  • Lớp con kế thừa toàn bộ các phương thức và thuộc tính có phạm vi publicprotected của lớp cha. Nếu lớp con nằm chung gói (package) với lớp cha thì sẽ kế thừa cả các thuộc tính và phương thức private.
  • Có thể định nhĩa thuộc tính và phương thức mới.
  • Các thuộc tính và phương thức được thừa kế được sử dụng trực tiếp như các thuộc tính và phương thức bình thường.
  • Có thể định nghĩa thuộc tính trong lớp con trùng tên với thuộc tính trong lớp cha, tuy nhiên thông thường chúng ta không nên làm thế.
  • Có thể định nghĩa phương thức trong lớp con trùng tên với phương thức trong lớp cha, việc này còn được gọi là ghi đè (overriding).
  • Có thể định nghĩa phương thức static trùng tên.

 

Các thành phần private

Một lớp con không được thừa kế các thành phần private của lớp cha. Tuy nhiên nếu lớp cha có phương thức public hoặc protected mà có sử dụng đến các thành phần private thì lớp con cũng có thể gọi các phương thức này.

 

Ép kiểu đối tượng

Các đối tượng thuộc lớp cha có thể sử dụng phương thức khởi tạo của các lớp con. Tức là chúng ta có thể tạo đối tượng Bicycle như sau:

Bicycle bike = new MountainBike();
Object obj = new Bicycle();
...

Trường hợp ngược lại thì không được phép trong Java. Nếu chúng ta viết:

MoutainBike bike = new Bibycle();

Thì trình biên dịch sẽ báo lỗi. Tuy nhiên chúng ta có thể ép kiểu:

MountainBike bike = (Bicycle)new Bicycle();

Java 8 – Phương thức default và static trong Interface

Trong phần trước chúng ta đã tìm hiểu về interface, chúng ta đã biết là khi viết thêm các phương thức và interface thì chúng ta phải code các phương thức đó cho toàn bộ các lớp đã implement interface đó, nếu không thì sẽ bị lỗi biên dịch.

Phương thức default

Phương thức default (mặc định) cho phép chúng ta viết thêm phương thức mới vào interface mà không lo bị lỗi biên dịch.

Giả sử chúng ta có đoạn code interface Blog như sau:

 

public interface Blog {
    void new_post();
    void delete_post();
}

Và chúng ta định nghĩa lớp PhoCode implement lại interface này như sau:

public class PhoCode implements Blog {
    String title = "phocode.com";

    void new_post() {
        // ...
    }

    void delete_post() {
        // ...
    }
}

Interface Blog có 2 phương thức là new_post()delete_post(). Giả sử chúng ta muốn thêm một phương thức mới vào interface này là update_post() như sau:

public interface Blog {
    void new_post();
    void delete_post();
    void update_post();
}

Chúng ta sẽ phải viết thêm phần code cho phương thức update_post() trong lớp PhoCode.

public class PhoCode implements Blog {
    String title = "phocode.com";
   
    void new_post() {
        // ...
    }

    void delete_post() {
        // ... 
    }

    void update_post() {
        // ...
    }
}

Việc viết lại các phương thức mới của interface vào toàn bộ các lớp có implement rất mệt mỏi. Do đó chúng ta có thể khai báo phương thức mặc định trong interface như sau:

public interface Blog {
    void new_post();
    void delete_post();
    default void update_post() {
        // ...
    }
}

Chúng ta khai báo một phương thức mà mặc định bằng cách đưa từ khóa default ra trước tên phương thức, tất cả các phương thức trong interface đều có phạm vi hoạt động là public. Đối với phương thức mặc định thì chúng ta viết phần code cho phương thức đó luôn, tức là có phần ngoặc nhọn {} thay vì dấu chấm phẩy phía sau tên phương thức.

Và khi khai báo một phương thức là mặc định thì các lớp implement lại interface đó không cần phải viết lại phần code cho phương thức mặc định và có thể gọi đến các phương thức mặc định như bình thường.

Kế thừa interface có phương thức mặc định

Các interface kế thừa lại interface có chứa phương thức mặc định có thể thực hiện các việc sau:

  • Không khai báo lại các phương thức mặc định, Java sẽ tự hiểu là kế thừa lại 100% phương thức mặc định
  • Kế thừa lại phương thức mặc định nhưng không khai báo phần thân phương thức {}, lúc này phương thức đó sẽ trở lại là phương thức trừu tượng.
  • Kế thừa lại và có khai báo phần thân phương thức, tức là override lại phương thức mặc định

Giả sử chúng ta có interface BlogV2 kế thừa Blog như sau:

public interface BlogV2 extends Blog {

}

Phương thức BlogV2 không khai báo phương thức update_post(), tức là mặc định sẽ kế thừa lại hoàn toàn phương thức này.

Nếu khai báo như sau:

public interface BlogV2 extends Blog {
    public void update_post();
}

Thì lúc này phương thức update_post() trong BlogV2 trở lại là phương thức trừu tượng, và tất cả các lớp implement interface BlogV2 sẽ phải code phương thức update_post().

Và cuối cùng chúng ta cũng có thể khai báo lại phương thức này là phương thức default, tức là ghi đè phương thức:

public interface BlogV2 extends Blog {
    default void update_post() {
        // ...
    }
}

Phương thức static

Cũng tương tự như các class, chúng ta có thể khai báo phương thức static (tĩnh) bên trong một interface. Ví dụ:

public interface Blog {
    String title = "phocode.com";

    static String getTitle() {
        return this.title;
    }
}

Chúng ta khai báo một phương thức static bằng cách ghi từ khóa static ra trước tên phương thức. Sau tên phương thức chúng ta khai báo phần thân phương thức trong cặp dấu ngoặc nhọn {}.

Phương thức static là phương thức dùng chung cho toàn bộ interface giống như với class, tức là tất cả các đối tượng implement interface có phương thức static đều kế thừa luôn 100% phương thức static đó, không thể override lại được.

Java 8 – Interface

Trong Java thì interface (giao diện) là một kiểu dữ liệu tham chiếu tương tự như class, nhưng chỉ có thể chứa hằng số và tên các phương thức, không chứa phần thân phương thức, tuy nhiên chúng ta vẫn có thể viết phần thân phương thức cho các phương thức tĩnh và phương thức mặc định. Interface không thể được khởi tạo như lớp mà chỉ có thể được mở rộng từ các lớp khác hoặc được kế thừa từ các interface khác.

Để định nghĩa một interface thì cũng tương tự như định nghĩa một lớp:

public interface Blog {
   const String title = "phocode.com";

   void newPost();
   void deletePost();
   void updatePost();
}

Bên trong định nghĩa interface chỉ có hằng số hoặc tên phương thức, phương thức không chứa phần thân, tức là không có phần ngoặc nhọn {}, mà kết thúc bằng dấu chấm phẩy ;.

Để sử dụng interface thì phải có một lớp code lại interface đó bằng từ khóa implements, và lớp đó phải code lại toàn bộ phương thức có trong interface. Ví dụ:

public class PhoCode implements Blog {
    void newPost() {     
    }
    void deletePost {
    }
    void updatePost {
    }   
}

Trong đoạn code trên, lớp PhoCode kế thừa interface Blog, và bên trong lớp PhoCode phải code lại các phương thức có trong interface này, tức là phải có phần ngoặc nhọn {} phía sau tên phương thức.

Định nghĩa một interface

Chúng ta định nghĩa một interface bằng cách ghi từ khóa phạm vi hoạt động, rồi đến từ khóa interface, sau đó là tên của interface mà chúng ta muốn đặt, tiếp theo là danh sách các interface kế thừa nếu có, cuối cùng là phần ngoặc nhọn {} khai báo các phương thức và hằng số. Ví dụ:

public interface Blog extends Http, Database {
    String title = "phocode.com";

    void newPost();
    void deletePost();
    void updatePost();
}

Từ khóa public cho biết interface này có thể sử dụng ở bất kỳ gói nào, nếu chúng ta không khai báo từ khóa phạm vi nào thì mặc định interface này chỉ có thể được sử dụng trong cùng một gói.

Một interface có thể được kế thừa từ nhiều interface khác. Khác với class là mỗi class chỉ có thể kế thừa từ một class thì interface có thể kế thừa từ nhiều interface khác, danh sách các interface kế thừa cách nhau bởi dấu phẩy.

Các phương thức của interface có thể là phương thức trừu tượng, phương thức mặc định và phương thức tĩnh. Phương thức ảo chính là phương thức mà bình thường mà chúng ta viết, không có gì đặc biệt, phương thức mặc định thì có từ khóa default (chúng ta sẽ tìm hiểu phương thức mặc định sau), phương thức tĩnh thì có từ khóa staticTất cả các phương thức trong interface đều mặc định là public nên chúng ta không cần khai báo public.

Tất cả các hằng số trong interface là public, staticfinal.

Implement interface

Chúng ta khai báo từ khóa implements sau tên lớp rồi ghi tên danh sách các interface ra (nếu implement nhiều interface).

Lưu ý là nếu lớp này có kế thừa từ lớp khác thì từ khóa extends sẽ luôn đứng trước từ khóa implements.

Chẳng hạn như chúng ta có interface chứa phương thức so sánh xem đối tượng nào “lớn hơn” đối tượng nào như sau:

public interface Comparison {        
    public int isGreaterThan(Comparison other);
}

Sau này giả sử dụng ta có lớp Blog, và chúng ta muốn so sánh 2 đối tượng Blog với nhau thì chúng ta cho lớp Blog implements interface trên, bên trong lớp Blog chúng ta code lại phương thức isLargerThan() là xong. Tất nhiên là với điều kiện chúng ta phải có cách để so sánh 2 đối tượng, bởi vì ở đây chúng ta không so sánh 2 giá trị thô như so sánh 2 số nguyên, chẳng hạn như so sánh 2 đối tượng Blog thì so sánh xem đối tượng nào có nhiều bài viết hơn, nhiều comment hơn… v.v Ví dụ:

public class Blog implements Comparison {
    private int post_count = 0;

    int getPostCount() {
        return this.post_count;
    }

    public int isLargerThan(Comparison otherBlog) {
        if (this.getPostCount() < otherRect.getPostCount())
            return -1;
        else if (this.getPostCount() > otherRect.getPostCount())
            return 1;
        else
            return 0;               
    }
}

Trong đoạn code trên chúng ta viết lớp Blog có phương thức getPostCount() là phương thức lấy về số lượng bài viết của Blog, rồi implements interface Comparison, trong đó chúng ta code lại phương thức isLargerThan(), phương thức này nhận vào tham số là một đối tượng Blog khác, chúng ta kiểm tra xem đối tượng nào có phương thức getPostCount() lớn hơn thì trả về 1, bé hơn là -1 và bằng nhau là 0. Trên đây chỉ là một cách để chúng ta so sánh, trên thực tế chúng ta có nhiều cách khác để so sánh 2 đối tượng.

Dùng Interface làm kiểu dữ liệu

Việc định nghĩa một interface giống như định nghĩa một kiểu dữ liệu tham chiếu mới vậy. Hầu như chỗ nào chúng ta đặt tên lớp thì chúng ta cũng có thể thay bằng tên interface.

Nếu chúng ta khai báo một đối tượng có kiểu dữ liệu của một interface thì trong đoạn code khởi tạo đối tượng đó phải là khởi tạo từ một lớp có implement interface đó. Ví dụ:

public interface Blog {
    //  ...
}

public class PhoCode implements Blog {
    public static void main(String[] args) {
        Blog phocode_blog = new PhoCode();
    }
}

Mở rộng interface

Giả sử chúng ta có interface Blog với 2 phương thức mẫu như sau:

public interface Blog {
    void newPost();
    void deletePost();
}

Giả sử interface này đã được rất nhiều class implement lại, nhưng bây giờ chúng ta lại muốn thêm vào interface này một phương thức như sau:

public interface Blog {
    void newPost();
    void deletePost();
    void updatePost();
}

Và kết quả là toàn bộ các lớp có implement interface này sẽ sụp đổ vì chúng chưa code lại phương thức updatePost() mới.

Để tránh tình trạng này thì chúng ta nên đoán trước các phương thức mà chúng ta sẽ code và khai báo toàn bộ trong interface ngay từ đầu. Một cách khác là chúng ta viết một interface mới kế thừa từ interface cũ, chẳng hạn như:

public interface BlogPlus extends Blog {
    void updatePost();   
}

Và các coder sẽ có 2 lựa chọn là dùng interface cũ của bạn hoặc chuyển qua interface mới có đầy đủ các tính năng hơn.

Hoặc chúng ta có một cách khác là khai báo phương thức mặc định với từ khóa default như sau:

public interface Blog {
    void newPost();
    void deletePost();
    default updatePost() {
        // ...
    }     
}

Phương thức mặc định sẽ được code ngay bên trong interface chứ không được kế thừa nữa. Chúng ta sẽ tìm hiểu phương thức mặc định trong bài sau.

Java 8 – Annotation – Phần 2

Các annotation có sẵn trong Java

Java có sẵn một số annotation được định nghĩa sẵn, thông thường được dùng cho trình biên dịch hoặc dùng cho các annotation khác.

Có 3 annotation được định nghĩa sẵn trong gói java.lang@Deprecated, @Override@SuppressWarnings.

@Deprecated cho biết một phần tử nào đó không còn được sử dụng nữa. Trình biên dịch sẽ in các câu cảnh báo khi chương trình sử dụng các thuộc tính, lớp hoặc phương thức có gắn với @Deprecated.

public class MyClass {
    @Deprecated
    static void deprecated-method() {
        // ...
    }
}

@Override cho biết phần tử đó ghi đè lên một phần tử khác đã được định nghĩa trong lớp cha. Chúng ta sẽ làm việc với các phương thức ghi đè khá nhiều.

public class MyClass extends MySuperClass {
    @Override
    public int override-method() {
        // ...
    }
}

Chúng ta không nhất thiết phải sử dụng @Override khi ghi đè phương thức, nhưng annotation này sẽ giúp chúng ta tránh lỗi dễ dàng hơn.

@SupressWarnings thông báo cho trình biên dịch biết là không được in các câu cảnh báo nào đó. Chẳng hạn như chặn câu cảnh báo deprecated ở trên.

public class MyClass {
    @Deprecated
    static void deprecated-method() {
        //  ...
    }

    @SupressWarnings("deprecation")
    public void supress-method() {
        this.deprecated-method();
    }
}

Tất cả các loại câu cảnh báo trong Java đều thuộc về một trong 2 loại là deprecation hoặc unchecked. Chúng ta có thể loại bỏ cả 2 loại cảnh báo bằng cách truyền vào @SupressWarnings như sau:

@SuppressWarnings({"unchecked", "deprecation"})

Ngoài ra còn có 2 annotation là @SafeVarargs và @FunctionalInterface. 

@SafeVarargs được đặt trước phương thức. có tác dụng kiểm tra xem đoạn code phía sau nó có tạo ra bất cứ một hành động nguy hiểm nào đó hay không, chẳng hạn như kiểm tra xem biến sắp được sử dụng có thể giải phóng lỗi exeption hay không… khi dùng @SafeVarargs thì các câu cảnh báo không được hiện ra.

@FunctionalInterface là annotation có trong Java 8, được đặt trước các giao diện hàm và thông báo cho trình biên dịch biết sau đó là phần khai báo một giao diện của một hàm, chúng ta sẽ tìm hiểu sau.

Lớp annotation

Như đã giới thiệu trong phần trước, đối với phiên bản Java 8 thì các annotation còn có thể được dùng giống như một lớp, tức là có thể dùng trong việc tạo đối tượng, ép kiểu, kế thừa…v.v

Lớp annotation được sử dụng với mục đích hỗ trợ cho việc phân tích chương trình, nhằm tăng tính type-checking, tức là đảm bảo cho biến hoặc đối tượng luôn lưu trữ một dữ liệu trong một ngữ cảnh nào đó. Chẳng hạn như chúng ta có thể quy định một biến không bao giờ được gán giá trị null, nhờ đó mà không bao giờ xảy ra lỗi NullPointerException, bằng cách sử dụng một annotation nào đó mà chúng ta tự định nghĩa, chẳng hạn như:

@NonNull String str;

Khi chúng ta biên dịch thì trình biên dịch sẽ in những câu thông báo lỗi nếu các biến có thể gây ra một lỗi nào đó, nhờ đó mà chúng ta sửa lại kịp thời trước khi chạy.

Đây là một tính năng khá mới, bạn có thể tìm hiểu thêm trên mạng, ở đây chúng ta không đi sâu.

Khai báo nhiều annotation trùng tên

Sẽ có trường hợp chúng ta muốn sử dụng một annotation nhiều lần tại một vị trí, Java 8 hỗ trợ tính năng này.

Ví dụ như chúng ta muốn viết một chương trình tự động thực hiện một công việc nào đó tại một thời điểm nào đó tương tự như dịch vụ cron. Giả sử chúng ta muốn đặt thời gian thực hiện một việc vào cuối tuần, rồi lại muốn đặt thời gian thực hiện một việc khác vào đầu tuần, chúng ta có thể định định nghĩa và khai báo 2 annotation dạng như sau:

@Schedule(date="Mon")
@Schedule(date="Sun")
public void someCron() {
    // ...
}

Để có thể sử dụng một annotation nhiều lần thì chúng ta phải khai báo một annotation khác dùng để chứa annotation lặp theo 2 bước dưới đây.

Bước 1: Khai báo kiểu annotation có thể lặp

Để khai báo một annotation có thể lặp thì chúng ta khai báo một annotation khác cho nó là @Repeatable. Ví dụ:

import java.lang.annotation.Repeatable;

@Repeatable(Schedules.class)
public @interface Schedule {
     String date() default "Mon";
}

Bên trong @Repeatable chúng ta phải truyền vào class của một annotation khác dùng để chứa @Schedule, ở đây là Schedules.class (lưu ý khác với Schedule). Tức là @Schedules sẽ chứa các @Schedule.

Bước 2: Khai báo annotation dùng để chứa

Kiểu annotation dùng để chứa annotation khác phải có một thuộc tính có tên là value. Kiểu dữ liệu được chứa phải là kiểu mảng. Ví dụ:

 

public @interface Schedules {
    Schedule[] value();
}

Java 8 – Annotation – Phần 1

Annotations là một kiểu metadata (hay siêu dữ liệu) cung cấp thông tin thêm cho một chương trình, nhưng lại không đóng một vai trò nào trong quá trình chạy của chương trình cả. Annotation không ảnh hưởng hay điều chỉnh các đoạn code mà chương trình chạy.

Một số mục đích của annotation là:

  • Cung cấp thông tin cho trình biên dịch, trình biên dịch có thể dùng annotation để phát hiện lỗi.
  • Xử lý một số công việc trong quá trình biên dịch và khởi chạy, chẳng hạn như có thể dùng để tạo ra các file code, XML…v.v
  • Xử lý một số công việc trong quá trình chạy.

Cơ bản về annotation trong Java

Annotation có dạng như sau:

@Entity

Kí tự @ cho trình biên dịch biết theo sau là một annotation, ở trên annotation có tên Entity. Annotation mà chúng ta thường dùng nhất là @Override.

@Override
void mySuperMethod() { ... }

Annotation có thể mang theo các thuộc tính và giá trị của riêng nó, chẳng hạn như:

@Blog(
   name = "Pho Code",
   site = "phocode.com"
)
class MyClass() { ... }

Hay một kiểu khai báo khác là:

@Blog(name = "Pho Code")
void myMethod() { ... }

Nếu annotation chỉ có một thuộc tính thì không cần ghi tên thuộc tính ra cũng được, ví dụ:

@Blog("Pho Code")
void myMethod() { ... }

Nếu không có thuộc tính thì không cần đến cặp dấu () như trong @Override.

Có thể khai báo nhiều annotation tại một vị trí:

@Blog
@Site
class MyClass { ... }

Và cũng có thể khai báo nhiều annotation cùng tên tại một vị trí, đây là tính năng chỉ được hỗ trợ trong Java 8, chúng ta sẽ tìm hiểu thêm sau, ví dụ:

@Blog(name = "Pho Code")
@Blog(name = "Java 8")
class MyClass { ... }

Annotation có thể đứng trước khai báo lớp, thuộc tính, phương thức và các thành phần khác của chương trình. Khi khai báo thì annotation được đặt trên dòng riêng của nó.

Đặc biệt trong Java 8 thì annotation có thể dùng giống như một lớp, tức là có thể tạo thành đối tượng, ép kiểu dữ liệu, kế thừa giao diện, giải phóng lỗi ngoại lệ…v.v

//  Tạo đối tượng
new @Blog MyObject();

//  Ép kiểu
str1 = (@Name String) str2;

//  Kế thừa giao diện
class MyClass implements @Blog AnotherClass { ... }

//  Giải phóng lỗi exception
void myMethod() throws @Blog Exception { ... }

Gần như annotation được dùng để thay thế cho tính năng comment trong các đoạn code.

Ví dụ như chúng ta có đoạn code khai báo lớp như sau:

public class Generation3List extends Generation2List {

   // Author: J.K. Rowling
   // Date: 3/17/2002
   // Current revision: 6
   // Last modified: 4/12/2004
   // By: phocode.com
}

Chúng ta có thể thay đoạn code comment ở trên bằng đoạn annotation như sau:

@ClassPreamble (
   author = "J.K. Rowling",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "phocode.com",
)
public class Generation3List extends Generation2List {

}

Định nghĩa annotation

Ví dụ về một đoạn code định nghĩa annotation:

public @interface AnnotationExample {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
}

Chúng ta định nghĩa annotation bằng cách ghi từ khóa @interface, rồi đến tên annotation, sau đó là cặp dấu ngoặc nhọn {}, rồi đến danh sách các thuộc tính.

Ở đây các thuộc tính được khai báo bằng cách khai báo kiểu dữ liệu, rồi đến tên thuộc tính và cặp dấu ngoặc tròn (). Nếu muốn chúng ta có thể đưa giá trị mặc định cho thuộc tính bằng cách ghi từ khóa default, theo sau là giá trị mặc định đó.

Java 8 – Lớp cục bộ

Lớp cục bộ (local class) là các lớp được định nghĩa trong một khối lệnh.

Thường thì chúng ta hay định nghĩa lớp cục bộ trong khối lệnh của một phương thức, một câu lệnh for hoặc một câu lệnh if. Ví dụ:

public class OuterClass {

    public void printMessage(String msg) {
        final String parent = "Outer Class";        

        class LocalClass {
            String msg;
            LocalClass(String msg) {
                this.msg = msg;
            }

            public void print() {
                System.out.println(this.msg + " from " + parent);
            }
        }
  
        LocalClass lc = new LocalClass(msg);
        lc.print();
    }

    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.printMessage("Hello World");
    }
}

Trong đoạn code trên thì chúng ta có lớp OuterClass là lớp cha ngoài cùng, bên trong có phương thức printMessage(), trong phương thức này chúng ta định nghĩa lớp LocalClass có phương thức print(). Ở đây lớp LocalClass chính là một lớp cục bộ, vì được định nghĩa trong một khối lệnh.

Hello World from Outer Class

Các lớp cục bộ có thể truy xuất đến các phương thức và thuộc tính của lớp cha của nó, tuy nhiên với điều kiện là thuộc tính đó phải được khai báo với từ khóa final. Trong đoạn code trên phương thức print() của lớp LocalClass có thể đọc thuộc tính parent của lớp OuterClass là vì thuộc tính parent được khai báo là final.

Tuy nhiên, kể từ phiên bản Java 8 trở đi thì lớp cục bộ cũng có thể truy xuất đến các thuộc tính “gần như final” nữa, ở đây Java quy định thuộc tính “gần như final” là các thuộc tính không khai báo từ khóa final nhưng không bao giờ thay đổi giá trị sau khi đã được khai báo và khởi tạo.

Do đó trong đoạn code trên chúng ta có thể bỏ từ khóa final ở thuộc tính parent. Nhưng nếu chúng ta gán lại giá trị mới cho thuộc tính parent thì Java sẽ báo lỗi khi biên dịch.

public class OuterClass {

    public void printMessage(String msg) {
        String parent = "Outer Class";        

        class LocalClass {
            String msg;
            LocalClass(String msg) {
                parent = "Changed";
                this.msg = msg;
            }

            public void print() {
                System.out.println(this.msg + " from " + parent);
            }
        }
  
        LocalClass lc = new LocalClass(msg);
        lc.print();
    }

    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.printMessage("Hello World");
    }
}

Trong đoạn code trên chúng ta bỏ từ khóa final cho biến parent, sau đó thay đổi giá trị của biến này trong phương thức khởi tạo của lớp LocalClass. Khi biên dịch thì Java sẽ thông báo lỗi:

local variables referenced from an inner class must be final or effectively final
     parent = "Changed";
     ^

Lớp cục bộ và lớp nội khá giống nhau vì chúng đều không thể khai báo và định nghĩa các thuộc tính và phương thức static. Lớp cục bộ được định nghĩa trong một phương thức static thì chỉ có thể truy xuất các thuộc tính static của lớp chứa phương thức đó. Nói chung là việc truy xuất các thuộc tính static của lớp cục bộ khá hạn chế.

Chúng ta cũng không thể định nghĩa interface trong một khối lệnh bởi vì vốn dĩ interface cũng là static rồi.

public class OuterClass {

    public void printMessage(String msg) {
        String parent = "Outer Class"; 

        class LocalClass {
            String msg;
            LocalClass(String msg) {
                parent = "Changed";
                this.msg = msg;
            }

            public void print() {
                System.out.println(this.msg + " from " + parent);
            }
 
            public void greetInEnglish() {
                interface HelloThere {
                    public void greet();
                }
                class EnglishHelloThere implements HelloThere {
                    public void greet() {
                        System.out.println("Hello ");
                    }
                }
                HelloThere myGreeting = new EnglishHelloThere();
                myGreeting.greet();
            }
        }
 
        LocalClass lc = new LocalClass(msg);
        lc.print();
    }

    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.printMessage("Hello World");
    }
}

Đoạn code trên sẽ báo lỗi.

interface not allowed here
     interface HelloThere {
     ^

Ngoài ra chúng ta cũng không thể khai báo phương thức static trong một lớp cục bộ:

public class OuterClass {
    public void outerClassMethod() {
        class LocalClass {
            public static void doNothing() {
                //...
            }
 
            LocalClass() {
                //...   
            }
        }
    }

    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.outerClassMethod();
    }
}

Đoạn code trên sẽ báo lỗi:

Illegal static declaration in inner class LocalClass
     public static void doNothing() {
                        ^

Tuy nhiên có một điều kì dị là Java cho phép chúng ta khai báo một thuộc tính final static trong một lớp cục bộ:

public class OuterClass {

    public void outerClassMethod() {
        class LocalClass {
            public static final float PI = 3.14159f;
            LocalClass() {
                //...
            }
        }
    }

    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.outerClassMethod();
    }
}

Java 8 – Lớp lồng nhau

Java cho phép chúng ta định nghĩa một lớp trong một lớp khác, những lớp như thế được gọi là lớp lồng nhau (nested class).

class OuterClass {
    ...
    class NestedClass {
        ...    
    }
}

Lớp lồng nhau mà không phải là static thì còn được gọi là lớp nội (inner class).

Lớp lồng không static (tức lớp nội) có thể truy xuất đến tất cả các thuộc tính và phương thức của lớp lồng bên ngoài, kể cả các thành phần private, nhưng lớp lồng static thì lại không thể.

Lớp lồng có các công dụng như:

  • Gom nhóm các lớp có chung mục đích lại với nhau
  • Tăng tính đóng gói
  • Tăng tính dễ đọc và dễ bảo trì cho code

Lớp lồng static – Static Nested Class

Giống như phương thức và thuộc tính, lớp lồng static được gắn kết với đối tượng thuộc lớp bao bọc lấy nó nhưng không thể truy xuất tới các thuộc tính và phương thức của đối tượng đó.

Chúng ta truy xuất tới lớp lồng static bằng cách thêm dấu chấm và tên lớp:

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

Lớp nội – Inner Class

Lớp nội được gắn với đối tượng thuộc lớp bao bọc lấy nó và có thể truy xuất tới các phương thức và thuộc tính thuộc đối tượng đó, nhưng không thể định nghĩa các thuộc tính và đối tượng static.

Đối tượng của lớp nội chỉ tồn tại trong đối tượng của lớp bao bọc lấy nó.

class OuterClass {
    ...
    class InnerClass {
        ...
    }
}

Để tạo một đối tượng lớp nội thì chúng ta phải tạo lớp ngoài trước. Sau đó tạo đối tượng lớp nội theo cú pháp như sau:

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

Lớp nội còn có 2 loại nữa là lớp cục bộ và lớp ẩn, chúng ta sẽ tìm hiểu sau.

Biến trùng tên

Khi chúng ta khai báo các thuộc tính nằm trong các khu vực con mà có tên giống nhau, thì các biến nằm trong cùng sẽ được dùng trước tiên. Ví dụ:

public class LevelOne {
    public int x = 0;

    class LevelTwo {
        public int x = 1;
 
        void methodLevelTwo(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("LevelOne.this.x = " + LevelOne.this.x);
        }
    }

    public static void main(String[] args) {
        LevelOne lo = new LevelOne();
        LevelOne.LevelTwo lt = lo.new LevelTwo();
        lt.methodLevelTwo(23);
    }
}

Trong đoạn code trên, chúng ta có 3 biến tên là x, một biến là thuộc tính của lớp LevelOne, một biến là thuộc tính của lớp LevelTwo, một biến là tham số của phương thức methodLevelTwo(). Khi chúng ta sử dụng đến biến trong phương thức methodLevelTwo() thì Java sẽ tự động hiểu là dùng biến trong tham số, muốn truy xuất đến biến x của lớp LevelTwo thì chúng ta phải dùng từ khóa this, muốn truy xuất đến biến của lớp LevelOne thì chúng ta phải ghi rõ tên lớp rồi tới từ khóa this.

Đoạn code trên sẽ in ra các kết quả sau:

x = 23
this.x = 1
LevelOne.this.x = 0

Java 8 – Thao tác trên lớp

Phần này sẽ tìm hiểu thêm một số đặc điểm của lớp.

Trả về giá trị trong phương thức

Một phương thức quay trở lại nơi đã gọi nó khi:

  • Hoàn thành mọi câu lệnh, hoặc
  • Có câu lệnh return, hoặc
  • Giải phóng lỗi ngoại lệ (sẽ đề cập sau)

Khi chúng ta khai báo kiểu dữ liệu trả về trong phần khai báo phương thức thì chúng ta phải có câu lệnh return để “trả về” một giá trị nào đó dạng như thế này:

return value;

Nếu kiểu dữ liệu là void thì không cần, hoặc có thể ghi return không cũng được.

Kiểu dữ liệu của giá trị được trả về phải trùng với kiểu dữ liệu đã khai báo ở tên phương thức.

Ví dụ:

public class Rectangle {
    private int width = 1280;
    private int height = 720;
    public int area() {
        return width * height;
    }
}

Trong đoạn code trên thì phương thức area() có kiểu dữ liệu trả về là int, do đó trong câu lệnh return chúng ta phải ghi một biểu thức hoặc một biến nào đó có kiểu dữ liệu là int, ở đây là width * height.

Từ khóa this

Trong phần định nghĩa các phương thức của một lớp, chúng ta có thể sử dụng từ khóa this để tham chiếu đến đối tượng hiện tại – tức là đối tượng sẽ được gọi các phương thức của nó, để đọc/ghi tới tất cả các thuộc tính và phương thức của đối tượng đó.

Thông thường công dụng của từ khóa this là để phân biệt thuộc tính của lớp và tham số trong các phương thức. Ví dụ:

public class Point {
    public int x = 0;
    public int y = 0;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

Trong phương thức khởi tạo trên thì tên các biến tham số cũng trùng với tên các thuộc tính, do đó để phân biệt rõ biến nào là tham số, biến nào là phương thức thì chúng ta dùng từ khóa this.

Từ khóa phạm vi

Có tất cả 4 loại phạm vi, chúng ta đã biết 2 loại là publicprivate, các từ khóa này quy định việc các lớp khác có được truy xuất vào các biến và phương thức của lớp này hay không.

Bảng dưới đây mô tả các loại phạm vi của lớp:

TÊN

CÙNG LỚP

CÙNG PACKAGE

LỚP CON

MỌI NƠI KHÁC

public
protected Không
không có từ khóa Không Không
private Không Không Không

Theo như bảng trên thì các biến và phương thức của một lớp lúc nào cũng có thể được truy xuất trong chính lớp đó, các biến và phương thức có phạm vi là private thì chỉ có thể truy xuất được ở bên trong lớp đó, ở các nơi khác thì không.

Biến class

Biến class có tên khác là biến static, biến tĩnh…v.v là biến được dùng chung trong toàn bộ đối tượng, tức là khi một đối tượng thuộc một lớp thay đổi giá trị của biến này thì giá trị đó cũng sẽ được thay đổi trong các đối tượng cùng lớp khác.

Để khai báo biến static thì chúng ta chỉ cần thêm từ khóa static vào trước kiểu dữ liệu:

class Point {
    private static int width = 1280;
    private static int height = 720;
    
    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }

    public void changeWidth(int width) {
        this.width = width;
    }

    public void changeHeight(int height) {
        this.height = height;
    }
}
class StaticVar {
    public static void main(String[] args) {
        Point p1 = new Point();
        System.out.println(p1.getWidth());
        p1.changeWidth(1600);
        Point p2 = new Point();
        System.out.println(p2.getWidth());
    }
}
1280
1600

Và tương tự với thuộc tính, chúng ta cũng có thể có phương thức static, tuy nhiên phương thức static phải được gọi bằng tên lớp chứ không được gọi bằng tên đối tượng. Thông thường phương thức static được dùng để đọc/ghi các thuộc tính static là chính.

Ngoài ra trong Java còn có từ khóa final, biến nào có từ khóa final sẽ không thể chỉnh sửa được nữa, do đó từ khóa final thường được dùng để định nghĩa hằng số kết hợp với từ khóa static.

static final double PI = 3.141592653589793;

Java 8 – Đối tượng

Một chương trình viết bằng Java sẽ tạo ra các đối tượng có thể gọi các phương thức. Thông qua việc thao tác với các đối tượng này, chương trình có thể thực hiện nhiều công việc khác nhau như hiển thị giao diện, chạy hoạt hình, gửi và nhận thông tin qua mạng. Khi đã xong việc thì các tài nguyên này sẽ được tái chế để sử dụng bởi các đối tượng khác.

Tạo đối tượng

Chúng ta đã biết lớp chính là một bản thiết kế để tạo ra các đối tượng, để tạo mới một đối tượng thì chúng ta sử dụng toán tử new:

Point center = new Point(23, 94);
Rectangle rect1 = new Rectangle(center, 100, 200);

Một câu lệnh tạo đối tượng gồm có 2 phần:

  • Khai báo: là tên lớp/kiểu dữ liệu và tên đối tượng, ở đoạn code trên thì phần khai báo là Point centerRectangle rect1.
  • Khởi tạo: là toán tử new, theo sau là phương thức khởi tạo của lớp đó.

Việc khai báo đối tượng cũng giống như việc khai báo biến thông thường vậy, chúng ta cũng có thể gọi một đối tượng là một biến có kiểu dữ liệu là lớp của nó. Tuy vậy việc khai báo biến với kiểu dữ liệu thông thường và khai báo đối tượng cũng có hơi khác nhau, biến thông thường khi được khai báo nhưng không được khởi tạo (không có phần sau dấu =) thì vẫn có một giá trị mặc định nào đó, chẳng hạn như int thì là 0, String thì là ""… nhưng đối tượng đối tượng thì chúng lại không có một giá trị mặc định nào cả. Việc khai báo một đối tượng mà không khởi tạo sẽ không thực sự tạo ra một đối tượng, chúng ta phải khởi tạo cho chúng thì mới có thể sử dụng được.

Khi chúng ta sử dụng toán tử new để khởi tạo một đối tượng, thì hệ điều hành sẽ cấp phát bộ nhớ cho đối tượng đó và gửi về địa chỉ bộ nhớ cho đối tượng đó.

Theo sau toán tử new là phương thức khởi tạo của lớp đó, và địa chỉ bộ nhớ của đối tượng đó, nếu muốn biết chúng ta có thể in ra như thường:

Point center = new Point(50, 50);
System.out.println(center);

Và kết quả sẽ tương tự như thế này:

Point@15db9742

Sử dụng đối tượng

Khi đã tạo đối tượng thì chúng ta có thể làm một số thứ với nó, chúng ta có thể sử dụng giá trị trong thuộc tính của chúng, hoặc gọi các phương thức để thực hiện một công việc nào đó.

Chúng ta có thể trích xuất (tức đọc) dữ liệu từ các trường trong đối tượng thông qua tên của chúng:

class Rectangle {
    private int width = 1280;
    private int height = 720;
    public void display() {
        System.out.println("Width and height are: " + width + ", " + height);
    }
}
Width and height are: 1280, 720

Nếu chúng ta truy xuất các trường của đối tượng ở bên ngoài thì chúng ta phải ghi rõ ra tên đối tượng, theo sau là dấu chấm rồi tới tên trường:

class Rectangle {
    public int width = 1280;
    public int height = 720;
}

class DisplayRectangle {
    void display() {
        Rectangle rect = new Rectangle();
        System.out.println("Width and height are: " + rect.width + ", " + rect.height);
    }
}

Và tất nhiên là các trường phải có phạm vi truy xuất là public.

Để gọi một phương thức của đối tượng thì chúng ta cũng làm giống như truy xuất trường, là ghi tên đối tượng, tiếp theo là dấu chấm rồi tới tên phương thức, và danh sách các tham số nếu có:

class Rectangle {
    private int width = 1280;
    private int height = 720;
    public void area() {
      return width * height;
    }
}

class RectangleArea {
    private Rectangle rect1;
    public static void main(String[] args) {
        System.out.println("Rectangle area: " + rect1.area());
    }
}
Rectangle are: 921600

Trình dọn rác – Garbage collector

Một số ngôn ngữ lập trình hướng đối tượng yêu cầu chúng ta phải theo dõi quá trình tồn tại của các đối tượng, phải xóa các đối tượng sau khi dùng xong vì sẽ có trường hợp chúng ta thoát chương trình nhưng các đối tượng vẫn còn tồn tại và sẽ gây ra lãng phí bộ nhớ.

Tuy nhiên việc theo dõi thủ công này rất mệt mỏi và dễ sinh lỗi, do đó Java có sẵn trình thu dọn tài nguyên, và sẽ xóa toàn bộ các đối tượng khi chương trình kết thúc, chúng ta không cần phải quan tâm đến việc thu dọn rác đó nữa.

Java 8 – Lớp

Chúng ta đã tìm hiểu qua về lớp trong các phần trước, trong phần này chúng ta sẽ tìm hiểu kĩ hơn.

Khai báo lớp

Chúng ta đã biết cú pháp khai báo lớp như sau:

class MyClass {
    // thuộc tính và phương thức
}

Cú pháp trên là cú pháp khai báo lớp. Phần thân lớp nằm giữa cặp dấu ngoặc nhọn sẽ chứa đoạn code mô tả vòng đời của các đối tượng được tạo ra từ lớp đó, chẳng hạn như phương thức khởi tạo để khởi tạo các đối tượng, khai báo các thuộc tính để lưu giữ trạng thái của lớp và các đối tượng, khai báo phương thức để mô tả các hành vi của lớp.

Cú pháp trên là cú pháp cơ bản, chúng ta có thể khai báo lớp này kế thừa từ một lớp khác hay code một giao diện nào đó với cú pháp đầy đủ như sau:

class MyClass extends MySuperClass implements YourInterface {
    // thuộc tính và phương thức
}

Cú pháp trên có ý nghĩa là lớp MyClass kế thừa từ lớp MySuperClass, và code giao diện YourInterface.

Ngoài ra chúng ta có thể thêm các từ khóa phạm vi hoạt động như public hay private và trước từ khóa class, chúng ta sẽ tìm hiểu sau về phạm vi hoạt động.

Khai báo biến

Biến có các loại sau đây:

  • Biến thành viên của một lớp – có tên khác là trường hoặc thuộc tính
  • Biến nằm trong một phương thức hoặc khối lệnh – có tên khác là biến cục bộ
  • Biến nằm trong phần khai báo tên phương thức – có tên khác là tham số

Chẳng hạn như:

class Bicycle {
    public int speed;
    public int gear;
}

Trong đoạn code trên thì speedgear là các trường (hoặc thuộc tính), cú pháp khai báo trường gồm có:

  • Có thể có từ khóa khai báo phạm vi như public hoặc private
  • Kiểu dữ liệu (bắt buộc)
  • Tên trường (bắt buộc)

Từ khóa phạm vi hoạt động cho biết lớp nào có quyền được đọc/ghi trực tiếp lên thuộc tính. Bây giờ thì chúng ta chỉ xem qua 2 từ khóa chính là publicprivate thôi:

  • public – lớp nào cũng có thể đọc/ghi trực tiếp vào thuộc tính
  • private – chỉ có thể đọc/ghi từ chính lớp đó

Thông thường thì tất cả các thuộc tính nên được để private, tức là chỉ có thể truy xuất từ chính lớp chủ của nó, việc truy xuất từ các lớp khác nên được làm thông qua phương thức. Ví dụ:

public class Bicycle {
    private int gear;
    private int speed;

    public int getGear() {
        return gear;
    }

    public void setGear(int newValue) {
        gear = newValue;
    }

    public int getSpeed() {
        return speed;
    }

    public void speedUp(int increment) {
        speed += increment;
    }    
}

Kiểu dữ liệu của thuộc tính có thể là bất cứ kiểu dữ liệu nào, từ các kiểu dữ liệu cơ bản như int, float, boolean… cho đến các kiểu lớp và phức tạp hơn như String, mảng, đối tượng…v.v

Định nghĩa phương thức

Ví dụ về cú pháp của một phương thức như sau:

public double calculate(double first, double second) {
    // tính toán
}

Một phương thức cần ít nhất là kiểu dữ liệu trả về, cặp dấu ngoặc tròn () và khối lệnh trong cặp dấu ngoặc nhọn {}.

Ngoài ra phương thức còn có thể có từ khóa phạm vi (public, private) tên phương thức, danh sách các tham số trong cặp dấu ngoặc nhọn, tên phương thức, danh sách các lỗi ngoại lệ (sẽ tìm hiểu sau), phần thân phương thức trong cặp dấu ngoặc nhọn {}.

Chúng ta có thể có nhiều phương thức cùng tên nhưng khác danh sách tham số hoặc kiểu dữ liệu trả về, và được gọi là ghi đè phương thức, ví dụ:

public class Data {
    public void calculate(String s) {
        ...
    }

    public void calculate(int i) {
        ...
    }

    public void calculate(double f) {
        ...
    }    
}

Chúng ta không thể khai báo 2 phương thức có cùng tên, cùng kiểu dữ liệu trả về và cùng danh sách các phương thức.

Phương thức khởi tạo

Phương thức khởi tạo là các phương thức được gọi khi đối tượng được tạo ra, dùng để khai báo giá trị cho các thuộc tính, ví dụ:

public Bicycle(int startGear, int startSpeed) {
    gear = startGear;
    speed = startSpeed;
}

Để khởi tạo một đối tượng thì chúng ta dùng toán tử new như thường:

Bicycle myBike = new Bicycle(0, 8);

Toán tử new sẽ gọi phương thức khởi tạo Bicycle() ở trên.

Phương thức khởi tạo cũng giống như phương thức bình thường, chỉ khác là tên phương thức chính là tên lớp, và không có kiểu dữ liệu trả về. Chúng ta có thể có nhiều phương thức khởi tạo, miễn là chúng khác nhau danh sách tham số.

Phương thức khởi tạo không bắt buộc phải có, khi biên dịch thì trình biên dịch sẽ tự tạo ra một phương thức khởi tạo rỗng nếu chúng ta không khai báo.

Truyền tham số

Các phương thức khi được khai báo có thể nhận vào các biến, chúng ta gọi chúng là các tham số.  Ví dụ:

public double calculate(double a, double b) {
    ...
}

Trong đoạn code trên thì phương thức là calculate, có 2 biến tham số là ab có kiểu dữ liệu là double.

Cũng giống như các biến bình thường, kiểu dữ liệu của tham số cũng có thể là bất kì kiểu nào. Từ các kiểu cơ bản cho đến các kiểu phức tạp hơn.

Thông thường chúng ta hay khai báo số lượng các biến tham số một cách rõ ràng, tức là phương thức nhận vào 3 tham số thì chính xác là 3 tham số. Tuy nhiên Java có một cú pháp cho phép chúng ta truyền vào số lượng tham số bất kì. Ví dụ:

public double sum(double... values) {
    double result = 0;
    int n = values.length;
    for(int i = 0 ; i < n ; i++) {
        result += values[i];
    }
    return result;
}

Cú pháp này thực ra cũng chỉ là một dạng khác của mảng thôi, để khai báo thì chúng ta thêm 3 dấu chấm vào sau kiểu dữ liệu, và tham số lúc này sẽ là một mảng, chúng ta có thể thao tác như mảng bình thường. Khi truyền tham số thì chúng ta truyền từng giá trị đơn lẻ:

System.out.println(sum(2.3, 4.6, 1.0, 3.2, 9.8));

Các biến tham số cũng có phạm vi tương tự như các biến cục bộ, tức là chỉ có hiệu lực trong phương thức đó, do đó tên biến tham số không được trùng với tên các biến cục bộ khác. Tuy nhiên biến tham số có thể trùng tên với tên của thuộc tính, để có thể sử dụng 2 biến này với nhau chúng ta phải dùng đến con trỏ this, tuy nhiên con trỏ this sẽ được bàn đến trong các bài sau, và tốt hơn hết là không nên đặt tên 2 biến đó giống nhau.