Chương 9: Đối tượng có thể biến đổi

Trở về Mục lục cuốn sách

String là các đối tượng, song chúng là đối tượng không điển hình bởi lẽ

  • Chúng không thể biến đổi.
  • Chúng không có thuộc tính.
  • Bạn không bắt buộc phải dùng new để tạo nên một chuỗi mới.

Trong chương này, ta dùng hai đối tượng thuộc thư viện Java, là đối tượng Point và Rectangle (điểm và hình chữ nhật). Song trước hết, tôi muốn nói rõ rằng những điểm và hình chữ nhật này không phải là những đối tượng đồ hoạ xuất hiện trên mà hình. Chúng chỉ là những giá trị có chứa số liệu, cũng như các int và double. Giống những giá trị khác, chúng được sử dụng bên trong chương trình để thực hiện tính toán.

9.1  Các gói chương trình

Các thư viện Java được chia thành các gói, trong đó có java.lang là gói chứa hầu hết các lớp mà ta dùng cho đến giờ, và java.awt, tên đầy đủ Abstract Window Toolkit (AWT), là gói chứa các lớp gồm cửa sổ, nút bấm, đồ hoạ, v.v.

Để dùng một lớp được định nghĩa trong gói khác, bạn phải nhập nó. Point và Rectangle nằm trong gói java.awt, bởi vậy để nhập chúng ta làm như sau:

    import java.awt.Point; 
    import java.awt.Rectangle;

Tất cả câu lệnh import đều xuất hiện ở điểm đầu chương trình, bên ngoài lời định nghĩa lớp.

Các lớp trong java.lang, như Math và String, được nhập một cách tự động, bởi vậy từ trước đến giờ ta chưa cần dùng đến câu lệnh import nào.

9.2  Đối tượng Point

Một điểm là hai con số (toạ độ) mà ta coi chúng hợp nhất như một đối tượng đơn lẻ. Theo kí hiệu toán học, điểm thường được viết trong cặp ngoặc tròn, với dấu phẩy phân cách giữa các toạ độ. Chẳng hạn, (0, 0) chỉ định gốc toạ độ, còn (xy) chỉ định điểm cách điểm gốc x đơn vị về bên tay phải và y đơn vị lên trên.

Trong Java, một điểm được biểu diễn bởi một đối tượng Point. Để tạo nên một điểm mới, bạn phải dùng đến new:

    Point blank; 
    blank = new Point(3, 4);

Dòng thứ nhất là một lời khai báo biến thông dụng: blank có kiểu Point. Dòng thứ hai kích hoạt new, quy định kiểu của đối tượng mới, và cung cấp các đối số. Ở đây các đối số là toạ độ của điểm mới, (3, 4).

Kết quả của new là một tham chiếu đến điểm mới,  vì vậy blank chứa một tham chiếu đến đối tượng mới tạo nên. Có một cách tiêu chuẩn để sơ đồ hoá phép gán này, xem trên hình vẽ.

Như thường lệ, tên biến, blank, được ghi bên ngoài ô và giá trị của nó ở trong ô. Với trường hợp này, giá trị là một tham chiếu, và được biểu diễn bởi một mũi tên. Mũi tên này chỉ đến đối tượng mà ta tham chiếu tới.

Ô lớn biểu diễn đối tượng mới tạo lập cùng với hai giá trị bên trong. Các tên gọi x và y là các tên của biến thực thể.

Xét tổng thể, tất cả các biến, giá trị, và đối tượng trong một chương trình thì được gọi là trạng thái. Những biểu đồ như thế này, dùng để biểu diễn trạng thái chương trình, được gọi là biểu đồ trạng thái. Khi chương trình chạy, trạng thái của nó thay đổi; bởi vậy bạn nên coi biểu đồ trạng thái như một ảnh chụp tại một thời điểm cụ thể trong quá trình thực thi.

9.3  Các biến thực thể

Những đơn vị thông tin hợp thành đối tượng được gọi là các biến thực thể vì từng đối tượng, vốn là một thực thể cho kiểu của nó, có một bản sao riêng của biến thực thể này.

Cũng giống như ngăn trước [nơi để CD, giấy tờ ở ghế ngồi phía trước] của một chiếc xe hơi. Mỗi cái xe là thực thể của kiểu “xe hơi,” và từng chiếc xe có ngăn riêng của nó. Nếu bạn yêu cầu tôi lấy đồ từ ngăn trước của xe hơi bạn đang dùng, thì hãy cho tôi biết xe bạn đang dùng là xe nào.

Tương tự như vậy, nếu bạn muốn đọc một giá trị từ biến thực thể, bạn phải chỉ định đối tượng mà bạn cần lấy giá trị từ đó. Ở Java, điều này được thực hiện bằng cách dùng “kí pháp dấu chấm.”

    int x = blank.x;

Biểu thức blank.x nghĩa là “đến đối tượng mà blank chỉ tới, rồi lấy giá trị của x.” Trong trường hợp này ta gán giá trị đó vào một biến địa phương có tên là x. Không hề có xung đột gì giữa biến địa phương tên x này và biến thực thể mang tên x. Mục đích của kí pháp dấu chấm là để quy định rõ ràng xem biến nào mà bạn đang tham chiếu tới.

Bạn có thể dùng kí pháp dấu chấm làm một thành phần trong bất kì biểu thức Java nào, bởi vậy các biểu thức sau đều hợp lệ.

    System.out.println(blank.x + ", " + blank.y); 
    int distance = blank.x * blank.x + blank.y * blank.y;

Dòng thứ nhất in ra 3, 4; dòng thứ hai tính giá trị của 25.

9.4  Đối tượng trong vai trò của tham số

Bạn có thể truyền đối tượng như những tham số theo cách thông thường. Chẳng hạn:

  public static void printPoint(Point p) { 
    System.out.println("(" + p.x + ", " + p.y + ")"); 
  }

Phương thức này nhận một điểm làm đối số rồi in nó ra dưới định dạng tiêu chuẩn. Nếu bạn kích hoạt  printPoint(blank), nó sẽ in ra (3, 4). Thực tế là Java đã có sẵn một phương thức để in ra các Point. Nếu kích hoạt System.out.println(blank), bạn sẽ nhận được

java.awt.Point[x=3,y=4]

Đây là định dạng tiêu chuẩn mà Java dùng để in các đối tượng. Nó in ra tên của kiểu dữ liệu, tiếp theo là các tên và giá trị của những biến thực thể.

Một ví dụ thứ hai là ta có thể viết lại phương thức distance ở Mục 6.2 để nó nhận hai Point làm tham số thay vì bốn double.

  public static double distance(Point p1, Point p2) { 
    double dx = (double)(p2.x - p1.x); 
    double dy = (double)(p2.y - p1.y); 
    return Math.sqrt(dx*dx + dy*dy); 
  }

Các phép đổi kiểu dữ liệu ở đây đều không thật sự cần thiết. Tôi chỉ viết vào để tự nhắc rằng các biến thực thể trong một Point đều là các số nguyên.

9.5  Hình chữ nhật

Rectangle (hình chữ nhật) cũng giống như các điểm, chỉ khác rằng chúng có bốn biến thực thể: xywidth (bề rộng) và height (chiều cao). Ngoài điều này ra thì những thứ còn lại vẫn y nguyên.

Ví dụ sau đây tạo nên một đối tượng Rectangle rồi khiến box tham chiếu đến nó.

    Rectangle box = new Rectangle(0, 0, 100, 200);

Hình vẽ này mô tả hiệu ứng của lệnh gán nêu trên.

Nếu in box ra, bạn nhận được

java.awt.Rectangle[x=0,y=0,width=100,height=200]

Một lần nữa, đây là kết quả của một phương thức Java vốn biết cách in những đối tượng Rectangle.

9.6  Đối tượng với vai trò là kiểu được trả lại

Bạn có thể viết những phương thức trả lại đối tượng. Chẳng hạn, findCenter lấy một Rectangle làm đối số rồi trả lại một Point có chứa toạ độ của tâm Rectangle:

  public static Point findCenter(Rectangle box) { 
    int x = box.x + box.width/2; 
    int y = box.y + box.height/2; 
    return new Point(x, y); 
}

Lưu ý rằng bạn có thể dùng new để tạo nên một đối tượng mới, và rồi lập tức dùng kết quả này làm giá trị trả lại.

9.7  Đối tượng có tính thay đổi

Bạn có thể thay đổi nội dung của một đối tượng bằng cách viết lệnh gán cho một trong số những biến thực thể của nó. Chẳng hạn, để “dịch chuyển” một hình chữ nhật mà không làm thay đổi kích thước của nó, bạn có thể chỉnh sửa các giá trị x và y:

    box.x = box.x + 50; 
    box.y = box.y + 100;

Kết quả được biểu diễn trên hình:

Ta có thể bao bọc đoạn mã lệnh trên vào một phương thức rồi khái quát hoá nó để dịch chuyển hình chữ nhật đi một khoảng cách bất kì:

  public static void moveRect(Rectangle box, int dx, int dy) { 
    box.x = box.x + dx; 
    box.y = box.y + dy; 
  }

Các biến dx và dy chỉ định khoảng cách dịch chuyển hình theo từng hướng riêng. Việc kích hoạt phương thức này có ảnh hưởng làm thay đổi Rectangle được truyền vào dưới dạng tham số.

    Rectangle box = new Rectangle(0, 0, 100, 200); 
    moveRect(box, 50, 100); 
    System.out.println(box);

sẽ in ra java.awt.Rectangle[x=50,y=100,width=100,height=200].

Việc thay đổi các đối tượng bằng cách truyền chúng làm tham số cho các phương thức mặc dù có thể hữu ích, song nó cũng có thể gây khó khăn cho việc gỡ lỗi vì không phải lúc nào cũng dễ thấy là việc kích hoạt một phương thức có thay đổi các đối số của nó hay không. Về sau, tôi sẽ thảo luận những ưu nhược điểm của phong cách lập trình này.

Java có các phương thức thao tác với Point và Rectangle. Bạn có thể đọc tài liệu ở http://download.oracle.com/javase/6/docs/api/java/awt/Point.html và http://download.oracle.com/javase/6/docs/api/java/awt/Rectangle.html.

Chẳng hạn, translate có hiệu ứng tựa như moveRect, song thay vì phải truyền Rectangle làm đối số, bạn lại dùng kí pháp dấu chấm:

    box.translate(50, 100);

9.8  Aliasing

Hãy nhớ rằng khi bạn gán một đối tượng vào cho một biến, bạn đang gán một tham chiếu đến đối tượng. Hoàn toàn có thể có nhiều biến cùng tham chiếu tới một đối tượng. Chẳng hạn, đoạn mã sau:

    Rectangle box1 = new Rectangle(0, 0, 100, 200); 
    Rectangle box2 = box1;

tạo nên một biểu đồ trạng thái trông như sau:

box1 và box2 cùng chỉ đến một đối tượng. Nói cách khác, đối tượng này có hai tên gọi, box1 và box2. Việc người nào đó dùng hai tên được gọi là aliasing (dùng bí danh). Với đối tượng cũng như vậy.

Khi hai biến được dùng bí danh, bất kì sự thay đổi nào ảnh hưởng tới biến này thì cũng ảnh hưởng tới biến kia. Chẳng hạn:

    System.out.println(box2.width); 
    box1.grow(50, 50); 
    System.out.println(box2.width);

Dòng lệnh thứ nhất in ra 100, vốn là bề rộng của Rectangle được tham chiếu qua biến box2. Dòng thứ hai kích hoạt phương thức grow lên box1, để mở rộng Rectangle thêm 50 điểm ảnh theo mỗi chiều (hãy đọc tài liệu để biết thêm thông tin). Hiệu ứng được cho thấy ở hình vẽ dưới đây:

Bất kể thay đổi nào thực hiện đối với box1 thì cũng ảnh hưởng đến box2. Do vậy, giá trị được in ra bởi dòng lệnh thứ ba là 200, bề rộng của hình chữ nhật sau khi mở rộng. (Nói thêm, việc các toạ độ của một Rectangle nhận giá trị âm là hoàn toàn hợp lệ.)

Từ ví dụ đơn giản này bạn có thể thấy rằng mã lệnh có chứa bí dạng nhanh chóng khiến ta nhầm lần và có thể khó gỡ lỗi. Nói chung, nên tránh dùng bí danh hoặc dùng thật cẩn thận.

9.9  null

Khi bạn tạo nên một biến đối tượng, hãy nhớ rằng bạn đang tạo nên một tham chiếu đến đối tượng. Trước khi bạn khiến cho một biến chỉ tới đối tượng, thì giá trị của biến vẫn là nullnull là một giá trị đặc biệt (và cũng là một từ khoá trong Java) có nghĩa là “không có đối tượng.”

Lời khai báo Point blank; thì tương đương với lệnh khởi tạo sau

     Point blank = null;

và được biểu diễn bởi biểu đồ trạng thái sau:

Giá trị null được biểu thị bằng một hình vuông nhỏ không kèm theo mũi tên.

Nếu bạn cố thử dùng một đối tượng null, qua việc truy cập một biến thực thể hay kích hoạt một phương thức, thì Java sẽ phát một biệt lệ có tên NullPointerException, in một thông báo lỗi và kết thúc chương trình.

    Point blank = null; 
    int x = blank.x; // NullPointerException 
    blank.translate(50, 50); // NullPointerException

Mặt khác, sẽ hoàn toàn hợp lệ nếu ta truyền một đối tượng null làm đối số hoặc nhận một null làm giá trị trả về. Thực ra, điều này rất thông dụng, với mục đích chẳng hạn là biểu diễn một tập hợp rỗng hay để chỉ một điều kiện có lỗi.

9.10  Thu dọn rác

Ở Mục 9.8 ta đã nói về những gì đã xảy ra khi nhiều biến cùng tham chiếu tới một đối tượng. Thế còn điều gì sẽ xảy ra khi không có biến nào tham chiếu đến đối tượng? Chẳng hạn:

    Point blank = new Point(3, 4); 
    blank = null;

Dòng thứ nhất tạo ra một đối tượng Point mới rồi khiến cho blank tham chiếu đến nó. Dòng thứ hai sửa chữa blank để cho, thay vì tham chiếu đến đối tượng, nó không tham chiếu đến gì cả (hay tham chiếu đến đối tượng null).

Nếu không có ai tham chiếu đến đối tượng, thì cũng chảng ai có thể đọc hay ghi giá trị bất kì nào từ nó, hay kích hoạt một phương thức lên nó. Hệ quả là, nó sẽ ngừng tồn tại. Ta có thể vẫn giữ đối tượng này trong bộ nhớ, song làm như vậy chỉ tốn dung lượng; bởi vậy khi chương trình chạy, theo định kì hệ thống sẽ tìm kiếm các đối tượng lang thang rồi thu hồi lại nó, theo một quá trình mang tên thu dọn rác. Sau này, dung lượng nhớ bị chiếm bởi đối tượng sẽ về tay người dùng để phục vụ đối tượng mới.

Bạn không cần phải làm bất cứ điều gì để tiến hành thu dọn rác, và nói chung bạn sẽ không nhận thức được quá trình này. Song bạn cần biết rằng quá trình luôn được ngầm chạy một cách định kì.

9.11  Các đối tượng và kiểu nguyên thủy

Trong Java có hai loại kiểu dữ liệu, kiểu nguyên thủy và kiểu đối tượng. Kiểu nguyên thủ, như int và boolean đều bắt đầu bằng chữ viết thường; kiểu đối tượng bắt đầu bằng chữ viết in. Sự phân biệt này rất có ích vì chúng nhắc ta một số điểm khác nhau giữa chúng:

  • Khi khai báo một biến nguyên thủy, bạn được một dung lượng lưu trữ dành cho giá trị nguyên thủy. Khi bạn khai báo một biến đối tượng, bạn nhận được một dung lượng chứa tham chiếu tới đối tượng. Để giành được dung lượng cho bản thân đối tượng đó, bạn phải dùng đến new.
  • Nếu bạn không khởi tạo một kiểu nguyên thủy, thì nó sẽ được điền giá trị mặc định tùy theo kiểu đó là gì. Chẳng hạn, 0 với trường hợp int và false với boolean. Giá trị mặc định của kiểu đối tượng là null, nghĩa là không có đối tượng nào.
  • Các biến nguyên thủy tách biệt hoàn toàn, theo nghĩa bất cứ bạn làm gì trong một phương thức này sẽ không ảnh hưởng đến một biến ở phương thức khác. Các biến đối tượng thì lại cần phải khéo léo khi thao tác vì chúng không được biệt lập như vậy. Nếu bạn truyền một tham chiếu đến đối tượng để làm đối số, thì phương thức mà bạn kích hoạt có thể sẽ thay đổi đối tượng, và trong trường hợp này bạn sẽ thấy hiệu ứng. Dĩ nhiên, đó có thể là điều hay, song bạn cần nhận thức được việc này.

Còn một điểm khác biệt giữa kiểu nguyên thủy và kiểu đối tượng. Bạn không thể bổ sung kiểu nguyên thủy mới nào vào Java (trứ khi bạn là thanh viên trong hội đồng tiêu chuẩn), nhưng bạn có thể tạo nên kiểu đối tượng mới! Bạn sẽ biết cách làm như vậy trong chương sau.

9.12  Thuật ngữ

gói:
Một tập hợp các lớp. Các lớp Java được tổ chức thành các gói.
AWT:
Abstract Window Toolkit, một trong các gói Java lớn nhất và thông dụng nhất.
thực thể:
Ví dụ lấy từ một thể loại nào đó. Con mèo nhà tôi là một thực thể thuộc thể loại “động vật họ miêu.” Mỗi đối tượng đều là thực thể của một lớp nào đó.
biến thực thể:
Một trong số các đơn vị dữ liệu được đặt tên để cấu thành một đối tượng. Từng đối tượng (thực thể) đều có bản sao riêng các biến thực thể trong lớp mà nó thuộc vào.
tham chiếu:
Một giá trị để chỉ định một đối tượng. Trên sơ đồ trạng thái, một tham chiếu xuất hiện dưới dạng hình mũi tên.
aliasing (bí danh):
Tình trạng khi có nhiều biến cùng tham chiếu tới một đối tượng.
thu dọn rác:
Quá trình tìm các đối tượng không có tham chiếu và thu hồi dung lượng bộ nhớ mà chúng chiếm giữ.
trạng thái:
Một hình thức diễn tả đầy đủ tất cả các biến và đối tượng cùng những giá trị của chúng tại một thời điểm trong khi chương trình được thực thi.
sơ đồ trạng thái:
Một hình ảnh “chụp lại” trạng thái của chương trình.

9.13  Bài tập

Bài tập 1

  1. Với chương trình sau đây, hãy vẽ một sơ đồ ngăn xếp cho thấy các biến địa phương và các đối số của main và riddle, rồi cho thấy mọi đối tượng mà hai biến này chỉ đến.
  2. Kết quả của chương trình này là gì?
  public static void main(String[] args) { 
    int x = 5; 
    Point blank = new Point(1, 2); 
    System.out.println(riddle(x, blank)); 
    System.out.println(x); System.out.println(blank.x); 
    System.out.println(blank.y); 
  } 
  public static int riddle(int x, Point p) { 
    x = x + 7; 
    return x + p.x + p.y; 
  }

Mục đích của bài tập này là để đảm bảo rằng bạn hiểu cơ chế truyền đối tượng làm tham số.

Bài tập 2

  1. Với chương trình sau, hãy vẽ một biểu đồ ngăn xếp thể hiện trạng thái của chương trình ngay trước khi distance trả về. Hãy kèm theo tất cả các biến số và tham số cùng với những đối tượng mà các biến này tham chiếu tới.
  2. Kết quả của chương trình này là gì?
  public static double distance(Point p1, Point p2) { 
    int dx = p1.x - p2.x; 
    int dy = p1.y - p2.y; 
    return Math.sqrt(dx*dx + dy*dy); 
  } 
  public static Point findCenter(Rectangle box) { 
    int x = box.x + box.width/2; 
    int y = box.y + box.height/2; 
    return new Point(x, y); 
  } 
  public static void main(String[] args) { 
    Point blank = new Point(5, 8); 
    Rectangle rect = new Rectangle(0, 2, 4, 4); 
    Point center = findCenter(rect); 
    double dist = distance(center, blank); 
    System.out.println(dist); 
  }

Bài tập 3   Phương thức grow thuộc về lớp Rectangle. Hãy đọc tài liệu ở http://download.oracle.com/javase/6/docs/api/java/awt/Rectangle.html#grow(int, int).

  1. Kết quả của chương trình sau là gì?
  2. Hãy vẽ một sơ đồ trạng thái chỉ ra trạng thái của chương trình ngay trước khi main kết thúc, trong đó bao gồm tất cả những biến địa phương cùng các đối tượng mà những biến này tham chiếu tới.
  3. Ở điểm cuối của main, liệu p1 và p2 có cùng là bí danh không? Tại sao (không)?
  public static void printPoint(Point p) { 
    System.out.println("(" + p.x + ", " + p.y + ")"); 
  } 
  public static Point findCenter(Rectangle box) { 
    int x = box.x + box.width/2; 
    int y = box.y + box.height/2; 
    return new Point(x, y); 
  } 
  public static void main(String[] args) { 
    Rectangle box1 = new Rectangle(2, 4, 7, 9); 
    Point p1 = findCenter(box1); 
    printPoint(p1); 
    box1.grow(1, 1); 
    Point p2 = findCenter(box1); 
    printPoint(p2); 
  }

Bài tập 4   Đến giờ có thể bạn đang tương tư về phương thức giai thừa, nhưng ta sẽ viết thêm một dạng mới.

  1. Hãy tạo một chương trình mới có tên Big.java rồi viết một dạng lặp cho factorial.
  2. In ra một bảng các số nguyên chạy từ 0 đến 30 cùng với giai thừa của chúng. Ở tầm khoảng 15, có thể bạn sẽ thấy kết quả không còn đúng nữa. Tại sao vậy?
  3. BigIntegers là các đối tượng Java với khả năng biểu diễn những số nguyên lớn tùy ý. Không có giới hạn trên nào trừ giới hạn kích thước bộ nhớ và tốc độ xử lý. Hãy đọc tài liệu về BigIntegers athttp://download.oracle.com/javase/6/docs/api/java/math/BigInteger.html.
  4. Để dùng được BigIntegers, bạn phải thêm dòng import java.math.BigInteger vào đầu chương trình của bạn.
  5. Có vài cách tạo nên một BigInteger, nhưng tôi khuyên bạn cách dùng valueOf. Đoạn mã sau chuyển đổi một số nguyên thành BigInteger:
    int x = 17; 
    BigInteger big = BigInteger.valueOf(x);

    Hãy gõ đoạn mã lệnh này rồi chạy thử. Cố gắng in ra một BigInteger.

  6. Vì BigIntegers không phải là kiểu nguyên thủy nên các toán tử toán học thông thường không thể thao tác với chúng. Thay vào đó, ta phải dùng những phương thức như add. Để cộng hai BigInteger, hãy kích hoạt add lên một số rồi truyền số kia làm đối số. Chẳng hạn:
    BigInteger small = BigInteger.valueOf(17); 
    BigInteger big = BigInteger.valueOf(1700000000); 
    BigInteger total = small.add(big);

    Hãy thử một số phương thức khác, như multiply và pow.

  7. Chuyển đổi factorial sao cho nó tính toán với BigInteger rồi trả lại kết quả cũng là một BigInteger. Bạn có thể mặc kệ tham số—nó vẫn sẽ là một số nguyên.
  8. Hãy thử in lại bảng bằng phương thức giai thừa mà bạn vừa sửa đổi. Liệu nó có đúng đến 30 không? Bạn có thể làm cho nó lớn đến bao nhiêu? Tôi đã tính giai thừa của tất cả các số từ 0 đến 999, nhưng vì máy tính của tôi khá chậm nên mất một lúc. Số cuối cùng, 999!, có tới 2565 chữ số.

Bài tập 5   Nhiều kĩ thuật mã hóa phụ thuộc vào khả năng nâng các số nguyên lớn lên những lũy thừa nguyên. Sau đây là một phương thức thực hiện một kĩ thuật (tương đối) nhanh để tính lũy thừa số nguyên:

  public static int pow(int x, int n) { 
    if (n == 0) return 1; 

    // tính x mũ n/2 bằng cách đệ quy 
    int t = pow(x, n/2); 

    // nếu n chẵn, kết quả là t bình phương
    // nếu n lẻ, kết quả là t bình phương nhân với x
    if (n%2 == 0) { 
      return t*t; 
    } else { 
      return t*t*x; 
    } 
  }

Vấn đề với phương thức này là ở chỗ nó chỉ hoạt động được nếu kết quả nhỏ hơn 2 tỷ. Hãy viết lại phương thức để kết quả là một BigInteger. Tuy vậy các tham số vẫn là số nguyên.

Bạn có thể dùng các phương thức cho BigInteger là add và multiply, song đừng dùng pow, như vậy sẽ chẳng còn gì để làm.

Bài tập 6   Nếu bạn thích đồ họa, bây giờ đúng là lúc đọc đến Phụ lục A rồi làm các bài tập ở đó.

5 bình luận

Filed under Think Java

5 responses to “Chương 9: Đối tượng có thể biến đổi

  1. Pingback: Think Java: Cách suy nghĩ như nhà khoa học máy tính | Blog của Chiến

  2. mình ko hiểu tại sao hcn lại có chiều cao và có đối số x y

  3. hu hu hu ko hiểu cái hình chữ nhật

Bình luận về bài viết này