Java
Java là một ngôn ngữ lập trình hướng đối tượng (OOP) và dựa trên các lớp (class). Khác với phần lớn ngôn ngữ lập trình thông thường, thay vì biên dịch mã nguồn thành mã máy hoặc thông dịch mã nguồn khi chạy, Java được thiết kế để biên dịch mã nguồn thành bytecode, bytecode sau đó sẽ được môi trường thực thi (runtime environment) chạy.
- Java Core
- Overview
- Java Hello world - Chương trình Java Hello world
- Java Class - Lớp trong Java
- Java Object - Đối tượng trong Java
- Java Package - Gói trong Java
- Java Interface - Giao diện trong Java
- Java Abstract - Lớp và phương thức trừu tượng trong Java
- Java Data Types - Kiểu dữ liệu trong Java
- Java Variables - Kiểu biến trong Java
- Java Method - Phương thức trong Java
- Java Operators - Toán tử trong Java
- Java Arrays - Mảng trong Java
- Java Keywords - Các từ khóa trong Java
- Java If-else statement - Cấu trúc If-else trong Java
- Java If-else-if statement - Cấu trúc If-else-if trong Java
- Java Short If-else statement - Cấu trúc If-else rút gọn trong Java
- Java Switch-case statement - Cấu trúc Switch-case trong Java
- Java While loop - Vòng lặp While trong Java
- Java Do-while loop - Vòng lặp Do-while trong Java
- Java For loop - Vòng lặp For trong Java
- Java Break statement - Lệnh Break trong Java
- Java Continue statement - Lệnh Continue trong Java
- Java Exceptions - Ngoại lệ trong Java
- Java Try-catch exception - Khối Try-catch trong Java
- Java Multiple-catch block exception - Khối Multi-catch trong Java
- Java Finally block exception - Khối Finally trong Java
- Java Throw exception - Ném ngoại lệ trong Java
- Java Throws exception - Khai bao ngoại lệ trong Java
- Java User-defined exception - Tự định nghĩa ngoại lệ trong Java
- Java Constructor - Phương thức khởi tạo trong Java
- Java Inner class - Lớp nội trong Java
- Java Final class - Lớp "vô sinh" trong Java
- Java Finalize method - Phương thức finalize trong Java
- Java Streams I/O - Luồng I/O trong Java
- Java Byte I/O - Luồng Byte trong Java
- Java Character I/O - Luồng ký tự trong Java
- Java this keyword - Biến this trong Java
- Java Class
- Java String class - Lớp String trong Java
- Java StringBuffer class - Lớp StringBuffer trong Java
- Java StringBuilder class - Lớp StringBuilder trong Java
- Java Math class - Lớp java.lang.Math trong Java
- Java Runtime class - Lớp Runtime trong Java
- Java System class - Lớp System trong Java
- Java Class class - Lớp Class trong Java
- Java Object class - Lớp Object trong Java
- Java Hashtable class - Lớp Hashtable trong Java
- Java Random class - Lớp Random trong Java
- Java Vector class - Lớp Vector trong Java
- Java StringTokenizer class - Lớp StringTokenizer trong Java
- Java Design Pattern
- Java Design Pattern overview - Mẫu thiết kế trong Java
- Java Singleton Pattern - Mẫu thiết kế Singleton trong Java
- Java Abstract Factory Pattern - Mẫu thiết kế Abstract Factory trong Java
- Java Factory Design Pattern - Mẫu thiết kế Factory Design trong Java
- Java Builder Pattern - Mẫu thiết kế Builder trong Java
- Java Features
- Java Features Overview
- Java 8 - Lambda expression
- Java 8 - Functional Interface
- Java 8 - Default method & Static method
- Java 8 - Method reference
- Java 8 - Repeating annotation
- Java Guideline
Java Core
Java Core hay còn gọi là Java SE (Standard Edition) là một trong những thành phần quan trọng nhất của Java.
Overview
Java là một ngôn ngữ lập trình hướng đối tượng (OOP) và dựa trên các lớp (class). Khác với phần lớn ngôn ngữ lập trình thông thường, thay vì biên dịch mã nguồn thành mã máy hoặc thông dịch mã nguồn khi chạy, Java được thiết kế để biên dịch mã nguồn thành bytecode, bytecode sau đó sẽ được môi trường thực thi (runtime environment) chạy.
Java được khởi đầu bởi James Gosling và bạn đồng nghiệp ở Sun Microsystems năm 1991. Ban đầu ngôn ngữ này được gọi là Oak (có nghĩa là cây sồi; do bên ngoài cơ quan của ông Gosling có trồng nhiều loại cây này), họ dự định ngôn ngữ đó thay cho C++, nhưng các tính năng giống Objective C. Không nên lẫn lộn Java với JavaScript, hai ngôn ngữ đó chỉ giống tên và loại cú pháp như C. Công ty Sun Microsystems đang giữ bản quyền và phát triển Java thường xuyên. Tháng 04/2011, công ty Sun Microsystems tiếp tục cho ra bản JDK 1.6.24.
Java được tạo ra với tiêu chí "Viết (code) một lần, thực thi khắp nơi" ("Write Once, Run Anywhere" (WORA)). Chương trình phần mềm viết bằng Java có thể chạy trên mọi nền tảng (platform) khác nhau thông qua một môi trường thực thi với điều kiện có môi trường thực thi thích hợp hỗ trợ nền tảng đó. Môi trường thực thi của Sun Microsystems hiện hỗ trợ Sun Solaris, Linux, Mac OS, FreeBSD & Windows. Ngoài ra, một số công ty, tổ chức cũng như cá nhân khác cũng phát triển môi trường thực thi Java cho những hệ điều hành khác như BEA, IBM, HP.... Trong đó đáng nói đến nhất là IBM Java Platform hỗ trợ Windows, Linux, AIX & z/OS.
Điểm mạnh của Java:
- Object Oriented: Là một ngôn ngữ lập trình hướng đối tượng
- Platform Independent: Không giống như nhiều ngôn ngữ lập trình khác bao gồm C và C++, khi Java được biên dịch, nó không được biên dịch thành mã máy cụ thể của nền tảng, thay vào đó là mã byte độc lập với nền tảng. Mã byte này được Máy ảo (JVM) biên dịch trên bất kỳ nền tảng nào nó đang được chạy.
- Simple: Java được thiết kế để dễ học. Nếu bạn hiểu khái niệm cơ bản về OOP Java, nó sẽ dễ dàng thành thạo.
- Secure: Với tính năng bảo mật của Java, nó cho phép phát triển các hệ thống chống virus, không có virus. Kỹ thuật xác thực dựa trên mã hóa khóa công khai.
- Architecture-neutral: Trình biên dịch Java tạo ra một định dạng đối tượng trung lập kiến trúc, làm cho mã được biên dịch có thể thực thi được trên nhiều bộ xử lý
- Portable: Trung lập về kiến trúc và không có các khía cạnh phụ thuộc triển khai của đặc tả làm cho Java có thể di động. Trình biên dịch trong Java được viết bằng ANSI C với ranh giới khả năng di chuyển sạch, là tập hợp con POSIX.
- Robust: Java nỗ lực để loại bỏ các tình huống dễ xảy ra lỗi bằng cách tập trung vào kiểm tra lỗi khi biên dịch và kiểm tra runtime.
- Multithreaded: Với tính năng đa luồng của Java, có thể viết các chương trình thực hiện đồng thời nhiều tác vụ. Tính năng này cho phép các nhà phát triển xây dựng các ứng dụng tương tác có thể chạy trơn tru.
- Interpreted: Mã byte Java được dịch nhanh chóng theo hướng dẫn của máy gốc và không được lưu trữ ở bất cứ đâu
- High Performance: Java hỗ trợ và xử lý tốt các bài toán yêu cầu hiệu suất cao
- Distributed: Java được thiết kế cho môi trường phân tán của internet.
- Dynamic: Java được coi là năng động hơn C hoặc C ++ vì nó được thiết kế để thích ứng với môi trường phát triển. Các chương trình Java có thể mang lượng thông tin lớn về thời gian chạy có thể được sử dụng để xác minh và giải quyết các truy cập vào các đối tượng trong thời gian chạy
4 tính chất của lập trình hướng đối tượng trong Java
1. Tính đóng gói (encapsulation) và che giấu thông tin (information hiding)
- Trạng thái của đối tượng được bảo vệ không cho các truy cập từ code bên ngoài như thay đổi trong thái hay nhìn trực tiếp. Việc cho phép môi trường bên ngoài tác động lên các dữ liệu nội tại của một đối tượng theo cách nào là hoàn toàn tùy thuộc vào người viết mã.
- Đây là tính chất đảm bảo sự toàn vẹn, bảo mật của đối tượng Trong Java, tính đóng gói được thể hiện thông qua phạm vi truy cập (access modifier). Ngoài ra, các lớp liên quan đến nhau có thể được gom chung lại thành package.
2. Tính kế thừa (Inheritance)
- Tính kế thừa là khả năng cho phép ta xây dựng một lớp mới dựa trên các định nghĩa của một lớp đã có. Lớp đã có gọi là lớp Cha, lớp mới phát sinh gọi là lớp Con và đương nhiên kế thừa tất cả các thành phần của lớp Cha, có thể chia sẻ hay mở rộng các đặc tính sẵn có mà không phải tiến hành định nghĩa lại.
3. Tính đa hình (polymorphism)
- Khi một tác vụ được thực hiện theo nhiều cách khác nhau được gọi là tính đa hình.
Đối với tính chất này, nó được thể hiện rõ nhất qua việc gọi phương thức của đối tượng. Các phương thức hoàn toàn có thể giống nhau, nhưng việc xử lý luồng có thể khác nhau. Nói cách khác: Tính đa hình cung cấp khả năng cho phép người lập trình gọi trước một phương thức của đối tượng, tuy chưa xác định đối tượng có phương thức muốn gọi hay không. Đến khi thực hiện (run-time), chương trình mới xác định được đối tượng và gọi phương thức tương ứng của đối tượng đó. Kết nối trễ giúp chương trình được uyển chuyển hơn, chỉ yêu cầu đối tượng cung cấp đúng phương thức cần thiết là đủ. - Trong Java, chúng ta sử dụng nạp chồng phương thức (method overloading) và ghi đè phương thức (method overriding) để có tính đa hình.
- Nạp chồng (Overloading): Đây là khả năng cho phép một lớp có nhiều thuộc tính, phương thức cùng tên nhưng với các tham số khác nhau về loại cũng như về số lượng. Khi được gọi, dựa vào tham số truyền vào, phương thức tương ứng sẽ được thực hiện.
- Ghi đè (Overriding): là hai phương thức cùng tên, cùng tham số, cùng kiểu trả về nhưng thằng con viết lại và dùng theo cách của nó, và xuất hiện ở lớp cha và tiếp tục xuất hiện ở lớp con. Khi dùng override, lúc thực thi, nếu lớp Con không có phương thức riêng, phương thức của lớp Cha sẽ được gọi, ngược lại nếu có, phương thức của lớp Con được gọi.
4. Tính trừu tượng (abstraction)
- Tính trừu tượng là một tiến trình ẩn các chi tiết trình triển khai và chỉ hiển thị tính năng tới người dùng. Tính trừu tượng cho phép bạn loại bỏ tính chất phức tạp của đối tượng bằng cách chỉ đưa ra các thuộc tính và phương thức cần thiết của đối tượng trong lập trình.
- Tính trừu tượng giúp bạn tập trung vào những cốt lõi cần thiết của đối tượng thay vì quan tâm đến cách nó thực hiện.
- Trong Java, chúng là sử dụng abstract class và abstract interface để có tính trừu tượng.
Java Hello world - Chương trình Java Hello world
Chúng ta hãy bắt đầu từ chương trình Java cổ điển nhất với một ứng dụng đơn giản. Chương trình sau đây cho phép hiển thị một thông điệp:
// This is a simple program called “HelloWorld.java”
class First {
public static void main(String args[]) {
System.out.println(“Hello World”);
}
}
Tên file đóng vai trò rất quan trọng trong Java. Chương trình biên dịch Java chấp nhận phần mở rộng .java. Trong Java các mã cần phải gom thành các lớp. Bởi vậy tên lớp và tên file có thể trùng nhau. Do đó Java phân biệt rạch ròi chữ in hoa và chữ in thường (case-sensitive). Nói chung tên lớp và tên file nên khác nhau. Ví dụ tên file 'First' và 'first' là hai file khác nhau.
Để biên dịch mã nguồn, ta xử dụng trình biên dịch java. Trình biên dịch xác định tên của file nguồn tại dòng lệnh như mô tả dưới đây:
javac First.Java
Trình dịch java tạo ra file First.class chứa các mã “bytecodes”. Những mã này chưa thể thực thi được. Để chương trình thực thi được ta cần dùng trình thông dịch “java interpreter”
Lệnh được thực hiện như sau:
java HelloWorld
Kết quả sẽ hiển thị trên màn hình như sau:
Hello World
Java Class - Lớp trong Java
Khi định nghĩa một lớp, bạn chỉ ra thuộc tính mà nó chứa được thể hiện bằng biến (Member Variable) và hành vi được thể hiện bởi hàm (Method):
- Các biến định nghĩa bên trong một lớp gọi là các biến thành viên (Member Variables).
- Mã lệnh chứa trong các phương thức (Method). Các phương thức và biến định nghĩa trong lớp gọi chung là thành phần của lớp.
Trong hầu hết các lớp, các biến thể hiện được truy cập bởi các phương thức định nghĩa trong lớp đó. Vì vậy, chính các phương thức quyết định dữ liệu của lớp có thể dùng như thế nào. Lớp định nghĩa một kiểu dữ liệu mới, dùng để tạo các đối tượng thuộc kiểu đó.
Dạng đầy đủ của một định nghĩa lớp như sau:
[public] |
Lớp được truy xuất chung cho các Package khác, mặc định chỉ có các đoạn mã trong cùng một gói mới có quyền truy xuất nó |
[abstract] |
Lớp trừu tượng, không thể khởi tạo |
[final] |
Lớp hằng không có lớp con, không kế thừa |
class ClassName |
Tên lớp |
[extends SuperClass] |
Kế thừa lớp cha SuperClass |
[implements Interfaces] |
Giao diện được cài đặt bởi Class |
{ //Member Variables Declarations |
Khai báo các biến |
// Methods Declarations |
Khai báo các phương thức |
} |
|
Ví dụ: Tạo một lớp Box đơn giản với ba biến: width, height, depth
/* Định nghĩa lớp*/
class Box {
double width;
double height;
double depth;
public double getWidth() {
return this.width;
}
}
Java Object - Đối tượng trong Java
Object Class
Mặc định lớp Object là lớp cha của tất cả các lớp trong java. Nói cách khác nó là một lớp cáo nhất trong java.
Sử dụng lớp Object là hữu ích nếu bạn muốn tham chiếu bất kỳ đối tượng nào mà bạn chưa biết kiểu dữ liệu của đối tượng đó. Chú ý rằng biến tham chiếu của lớp cha có thể tham chiếu đến đối tượng của lớp con được gọi là upcasting.
Ví dụ: giả sử phương thức getObject() trả về một đối tượng nhưng nó có thể là bất kỳ kiểu nào như Employee, Student… chúng ta có thể sử dụng biến tham chiếu của lớp Object để tham chiếu tới đối tượng đó.
Object obj = getObject();//Chung ta khong biet doi tuong nao se duoc tra ve tu phuong thuc nay
Lớp Object cung cấp một vài cách xử lý chung cho tất cả các đối tượng như đối tượng có thể được so sánh, đối tượng có thể được cloned, đối tượng có thể được notified…
Các phương thức của lớp Object
Lớp Object cung cấp các phương thức như trong bảng sau:
Phương thức | Mô tả |
---|---|
public final Class getClass() | trả về đối tượng lớp Class của đối tượng hiện tại. Từ lớp Class đó có thể lấy được các thông tin metadata của class hiện tại. |
public int hashCode() | trả về số hashcode cho đối tượng hiện tại. |
public boolean equals(Object obj) | so sánh đối tượng đã cho với đối tượng hiện tại. |
protected Object clone() throws CloneNotSupportedException | tạo và trả về bản sao chép (clone) của đối tượng hiện tại. |
public String toString() | trả về chuỗi ký tự đại diện của đối tượng hiện tại. |
public final void notify() | đánh thức một luồng, đợi trình giám sát của đối tượng hiện tại. |
public final void notifyAll() | đánh thức tất cả các luồng. đợi trình giám sát của đối tượng hiện tại. |
public final void wait(long timeout)throws InterruptedException | làm cho Thread hiện tại đợi trong khoảng thời gian là số mili giây cụ thể, tới khi Thread khác thông báo (gọi phương thức notify() hoặc notifyAll()). |
public final void wait(long timeout,int nanos)throws InterruptedException | làm cho Thread hiện tại đợi trong khoảng thời gian là số mili giây và nano giây cụ thể, tới khi Thread khác thông báo (gọi phương thức notify() hoặc notifyAll()). |
public final void wait()throws InterruptedException | làm Thread hiện tại đợi, tới khi Thread khác thông báo (gọi phương thức notify() hoặc notifyAll()). |
protected void finalize()throws Throwable | Được gọi bởi Garbage Collector trước khi đối tượng bị dọn rác. |
Khai báo Object
Một Object (đối tượng) nó chứa trong đó bao gồm các method (phương thức) và properties (thuộc tính) để tạo ra một kiểu dữ liệu hữu ích.
Object xác định hành vi của class. Khi bạn gửi một thông điệp vào một object, có nghĩa là bạn đang yêu cầu gọi các object hoặc thực hiện một trong những phương thức của nó.
Từ một quan điểm của lập trình hướng đối tượng, một đối tượng có thể là một cấu trúc dữ liệu (data structure), một biến (variable) hoặc một chức năng (function).
Object được phân bổ vị trí bộ nhớ. Các Object được thiết kế như class phân cấp.
ClassName ReferenceVariable = new ClassName();
Điểm khác biệt giữa Class và Object trong Java
Một Class là một Blueprint (kế hoạch) hay Prototype (nguyên mẫu) xác định biến và các phương thức (hay function) chung với tất cả các đối tượng cùng loại.
Một Object là một cụ thể của một Class. Các đối tượng thường được dùng để mô tả đối tượng trong thế giới thực mà bạn thấy hàng ngày.
Java Package - Gói trong Java
Package trong java được tạo bởi sự kết hợp của nhiều class hay interface. và trong package có thể chứa các package khác. Package thường chứa các class, interface hay sub-package có liên quan với nhau.
//Khai báo package
package vn.laptrinh;
public class Student {
private int mark = 0;
public Student(int m) {
mark = m;
}
...
}
Truy cập các thành phần trong package
Các class mà dự định sẽ được sử dụng bên ngoài package sẽ được khai báo là public.
Các package khác nhau có thể có các class trùng tên với nhau.
Nếu các package khác nhau mà có các class có tên trùng nhau thì khi sử dụng bắt buộc phải import đầy đủ tên package và tên class.
Có 4 kiểu truy cập vào package là private, protected, public và default:
Từ khóa | Trong cùng class | Trong cùng package | Trong sub-package | Package khác |
private | Có | Không | Không | Không |
default | Có | Có | Không | Không |
protected | Có | Có | Có | Không |
public | Có | Có | Có | Có |
- private: Chỉ có thể được truy cập bởi chính class đó.
- protected: Được truy cập bởi các class cùng trong package và các class là sub-class của class này.
- public: Được truy cập bởi tất cả các class ở cùng package hay khác package.
- default: Được truy cập bởi các class cùng package.
Java Interface - Giao diện trong Java
Khái niệm
Trong Java, Interface (giao diện) là một kiểu dữ liệu tham chiếu tương tự như Class (lớp) nhưng chỉ có thể chứa hằng số và tên các phương thức, không có phần thân phương thức (phương thức trừu tượng). Một lớp mô tả các thuộc tính và hành động của đối tượng còn Interface thì mô tả các hành động của lớp đó. 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.
Trong Interface, chúng ta không thể khai báo hàm tạo và Interface không thể kế thừa từ một lớp mà chỉ có thể được mở rộng từ lớp và một Interface có thể được kế thừa lại từ nhiều Interface khác.
Ngoại trừ lớp trừu tượng thì tất cả các lớp mở rộng Interface phải định nghĩa lại tất cả các phương thức của Interface.
Các đặc điểm của interface
- Interface luôn luôn có modifier là: public interface, cho dù bạn có khai báo rõ hay không.
- Nếu có các trường (field) thì chúng đều là: public static final, cho dù bạn có khai báo rõ hay không.
- Các method của nó đều là method trừu tượng, nghĩa là không có thân hàm, và đều có modifier là: public abstract, cho dù bạn có khai báo hay không.
- Interface không có Constructor (cấu tử).
Khai báo Interface
Để khai báo một Interface, sử dụng từ khóa interface
/* File name : Animal.java */
interface Animal {
public void eat();
public void travel();
}
Thực thi Interface
/* File name : MammalInt.java */
public class MammalInt implements Animal {
public void eat() {
System.out.println("Mammal eats");
}
public void travel() {
System.out.println("Mammal travels");
}
public int noOfLegs() {
return 0;
}
public static void main(String args[]) {
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
Kế thừa Interface
Một Interface có thể kế thừa từ Interface khác tương tự như cách mà một lớp kế thừa từ một lớp khác đó là chúng ta cũng sử dụng từ khóa extends. Một lớp mở rộng từ Interface con sẽ kế thừa tất cả các phương thức có trong Interface đó (tức là phương thức của Interface con và Interface cha của Interface mà lớp đó implement). Trong Java, 1 Interface có thể kế thừa từ nhiều Interface và các Interface này được ngăn cách nhau bởi dấu phẩy.
// Filename: Sports.java
public interface Sports {
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
// Filename: Football.java
public interface Football extends Sports {
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}
// Filename: Hockey.java
public interface Hockey extends Sports {
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
Đa kế thừa Interface
Khác với Class, Interface có thể kế thừa (extends) từ nhiều Interface khác
public interface Hockey extends Sports, Event
Java Abstract - Lớp và phương thức trừu tượng trong Java
1. Khái niệm
Tính trừu tượng là một tiến trình ẩn các chi tiết triển khai và chỉ hiển thị tính năng tới người dùng. Nói cách khác, nó chỉ hiển thị các thứ quan trọng tới người dùng và ẩn các chi tiết nội tại, ví dụ: để gửi tin nhắn, người dùng chỉ cần soạn text và gửi tin. Bạn không biết tiến trình xử lý nội tại về phân phối tin nhắn. Tính trừu tượng giúp bạn trọng tâm hơn vào đối tượng thay vì quan tâm đến cách nó thực hiện.
Trong trường hợp chúng ta muốn định nghĩa một lớp cha theo một cấu trúc trừu tượng cho trước mà không cần hiện thực đầy đủ các phương thức. Tức là ta muốn tạo một lớp cha có dạng chung cho tất cả các lớp con và để các lớp con hiện thực chi tiết. Khi đó, bạn muốn chắc chắn lớp con có chồng lắp phương thức.
Như vậy, những phương thức phải được chồng lắp trong lớp con gọi là phương thức trừu tượng, được khai báo abstract và không có phần thân phương thức.
Ví dụ: Trong các ứng dụng, bạn có thể vẽ đường tròn, hình chữ nhật, đoạn thẳng, đường cong… Mỗi một đối tượng đồ hoạ này đều chứa các thuộc tính (vị trí, nét viền) và hành vi (di chuyển, thay kích thước, vẽ). Bạn có thể khai báo chúng kế thừa lớp Graphic. Tuy nhiên vẽ một đường tròn là hoàn toàn khác với vẽ một hình chữ nhật, nên lớp Graphic được khai báo là lớp trừu tường, chứa các phương thức đã được hiện thực như moveTo, và phương thức trừu tượng như draw:
abstract class GraphicObject {
int x, y;
void moveTo(int newX, int newY) {
// process
}
abstract void draw();
}
Mỗi một lớp con không trừu tượng của lớp Graphic như Circle, Rectangle sẽ phải cài đặt đầy đủ cho phương thức draw:
class Circle extends GraphicObject {
void draw() {
// process
}
}
class Rectangle extends GraphicObject {
void draw() {
// process
}
}
2. Phương thức trừu tượng trong Java
Một phương thức được khai báo là abstract và không có chi tiết triển khai thì đó là phương thức trừu tượng.
Nếu bạn muốn một lớp chứa một phương thức cụ thể nhưng bạn muốn triển khai thực sự phương thức đó để được quyết định bởi các lớp con, thì bạn có thể khai báo phương thức đó trong lớp cha ở dạng abstract.
Từ khóa abstract được sử dụng để khai báo một phương thức dạng abstract. Một phương thức abstract không có thân phương thức.
Phương thức abstract sẽ không có định nghĩa, chỉ gồm tên phương thức:
abstract void draw();
3. Kế thừa lớp Abstract trong Java
Trong ví dụ này, Shape là lớp trừu tượng, trình triển khai của nó được cung cấp bởi lớp Rectangle và lớp Circle. Hai lớp này kế thừa lớp trừu tượng Shape.
// lop truu tuong Shape
abstract class Shape {
abstract void draw();
}
//Trong tinh huong nay, trinh trien khai duoc cung cap boi ai do, vi du: nguoi su dung cuoi cung nao do
class Rectangle extends Shape {
void draw() {
System.out.println("Ve hinh chu nhat");
}
}
class Circle1 extends Shape {
void draw() {
System.out.println("Ve hinh tron");
}
}
//Trong tinh huong nay, phuong thuc duoc goi boi lap trinh vien hoac nguoi dung
class TestAbstraction1 {
public static void main(String args[]) {
Shape s = new Circle1(); //Trong tinh huong nay, doi tuong duoc cung cap thong qua phuong thuc, chang han nhu getShape()
s.draw();
}
}
4. Đặc điểm của Abstract Class
-
Một lớp được khai báo với từ khóa abstract là lớp trừu tượng (abstract class).
-
Lớp trừu tượng có thể có các phương thức abstract hoặc non-abtract.
-
Lớp trừu tượng có thể khai báo 0, 1 hoặc nhiều method trừu tượng bên trong.
-
Không thể khởi tạo 1 đối tượng trực tiếp từ một class trừu tượng.
-
Một lớp kế thừa từ lớp trừu tượng (subclass – lớp con) không cần phải implement non-abstract methods, nhưng những method nào có abstract thì bắt buộc phải override. Trừ khi subclass cũng là abstract.
- Lớp trừu tượng có thể có thành viên dữ liệu, phương thức trừu tượng, constructor, và có thể cả phương thức main().
5. Đặc điểm của Abstract Method
- Một phương thức được khai báo là abstract và không có trình triển khai thì đó là phương thức trừu tượng (abstract method).
- Nếu bạn muốn một lớp chứa một phương thức cụ thể nhưng bạn muốn triển khai thực sự phương thức đó để được quyết định bởi các lớp con, thì bạn có thể khai báo phương thức đó trong lớp cha ở dạng abstract.
- Từ khóa abstract được sử dụng để khai báo một phương thức dạng abstract. Phương thức abstract sẽ không có định nghĩa, được theo sau bởi dấu chấm phẩy, không có dấu ngoặc nhọn theo sau.
Java Data Types - Kiểu dữ liệu trong Java
Java cung cấp một các kiểu dữ liệu. Chúng được hỗ trợ trên tất cả các nền tảng. Ví dụ, dữ liệu loại int (integer) của Java được thể hiện bằng 4 bytes trong bộ nhớ của tất cả các loại máy bất luận ở đâu chạy chương trình Java. Bởi vậy các chương trình Java không cần phải thay đổi khi chạy trên các nền tảng khác nhau.
Trong Java kiểu dữ liệu được chia thành hai loại:
- Các kiểu dữ liệu nguyên thủy (primitive)
- Các kiểu dữ liệu tham chiếu (reference)
Kiểu dữ liệu nguyên thủy
Java cung cấp tám kiểu dữ liệu nguyên thuỷ:
Kiểu dữ liệu |
Độ dài theo số bit |
Phạm vi |
Mô tả |
byte |
8 |
-128 đến 127 |
Số liệu kiểu byte là một loại điển hình dùng để lưu trữ một giá tri bằng một byte. Chúng được sử dụng rộng rãi khi xử lý một file văn bản |
Char |
16 |
‘\uoooo’ to ’u\ffff ’ |
Kiểu Char sử dụng để lưu tên hoặc các dữ liệu ký tự .Ví dụ tên ngườI lao động |
Boolean |
1 |
“True” hoặc “False” |
Dữ liệu boolean dùng để lưu các giá trị “Đúng” hoặc “sai” Ví dụ : Người lao đông có đáp ứng được yêu cầu của công ty hay không ? |
short |
16 |
-32768 đến 32767 |
Kiểu short dùng để lưu các số có giá trị nhỏ dưới 32767.Ví dụ số lượng người lao động. |
Int |
32 |
-2,147,483,648 đến +2,147,483,648 |
Kiểu int dùng để lưu một số có giá trị lớn đến 2,147,483,648.Ví dụ tổng lương mà công ty phải trả cho nhân viên. |
Long |
64 |
-9,223,372,036’854,775,808 đến +9,223,372,036’854,775,808 |
Kiểu long được sử dụng để lưu một số cố giá trị rất lớn đến 9,223,372,036’854,775,808 .Ví dụ dân số của một nước |
Float |
32 |
-3.40292347E+38 đến +3.40292347E+38 |
Kiểu float dùng để lưu các số thập phân đến 3.40292347E+38 Ví dụ : giá thành sản phẩm |
double |
64 |
-1,79769313486231570E+308 đến +1,79769313486231570E+308 |
Kiểu double dùng để lưu các số thập phân có giá trị lớn đến 1,79769313486231570E+308 Ví dụ giá trị tín dụng của ngân hàng nhà nước. |
Kiểu dữ liệu tham chiếu
Trong Java có 3 kiểu dữ liệu tham chiếu
Kiểu dữ liệu |
Mô tả |
Mảng (Array) |
Tập hợp các dữ liệu cùng loại.Ví dụ : tên sinh viên |
Lớp (Class) |
Tập hợp các biến và các phương thức.Ví dụ : lớp “Sinhviên” chứa toàn bộ các chi tiết của một sinh viên và các phương thức thực thi trên các chi tiết đó. |
Giao diện (Interface) |
Là một lớp trừu tượng được tạo ra để bổ sung cho các kế thừa đa lớp trong Java. |
Ép kiểu (Type casting)
Có thể bạn sẽ gặp tình huống khi cộng một biến có dạng integer với một biến có dạng float. Để xử lý tình huống này, Java sử dụng tính năng ép kiểu (type casting) của các phần mềm trước đó C, C++. Lúc này một kiểu dữ liệu sẽ chuyển đổi sang kiểu khác. Khi sử dụng tính chất này, bạn cần thận trọng vì khi điều chỉnh dữ liệu có thể bị mất.
Đoạn mã sau đây thực hiện phép cộng một giá trị dấu phẩy động (float) với một giá trị nguyên (integer).
Float c=34.896751F;
Int b = (int)c +10;
Đầu tiên giá trị dấu phảy động c được đổi thành giá trị nguyên 34. Sau đó nó được cộng với 10 và kết quả là giá trị 44 được lưu vào b.
Sự nới rộng (widening) – quá trình làm tròn số theo hướng nới rộng không làm mất thông tin về độ lớn của mỗi giá trị:
- Biến đổi theo hướng nới rộng chuyển một giá trị sang một dạng khác có độ rộng phù hợp hơn so với nguyên bản.
- Biến đổi theo hướng thu nhỏ lại (narrowwing) làm mất thông tin về độ lớn của giá trị được chuyển đổi. Chúng không được thực hiện khi thực hiện phép gán. Ở ví dụ trên giá trị thập phân sau dấu phảy sẽ bị mất.
Java Variables - Kiểu biến trong Java
Khái niệm
Các ứng dụng sử dụng các biến để lưu trữ các dữ liệu cần thiết hoặc các dữ liệu được tạo ra trong quá trình thực thi chương trình. Các biến được xác định bởi một tên biến và có một phạm vi tác động. Phạm vi tác động của biến được xác định một cách rõ ràng trong chương trình. Mỗi biến được khai báo trong một khối chương trình chỉ có tác động trong phạm vi khối đó, không có ý nghĩa và không được phép truy nhập từ bên ngoài khối.
int data=50;//Here data is variable
Việc khai báo một biến bao gồm 3 thành phần: kiểu biến, tên của nó và giá trị ban đầu được gán cho biến (không bắt buộc). Để khai báo nhiều biến ta sử dụng dấu phẩy để phân cách các biến. Khi khai báo biến, luôn nhớ rằng Java phân biệt chữ thường và chữ in hoa (case-sensitive).
Cú pháp
Datatype indentifier [=value] [, indentifier[=value]... ];
Để khai báo một biến nguyên (int) có tên là counter dùng để lưu giá trị ban đầu là 1, ta có thể thực hiện phát biểu sau đây:
int counter = 1;
Java có những yêu cầu hạn chế đặt tên biến mà bạn có thể gán giá trị vào. Những hạn chế này cũng giống các hạn chế khi đặt tên cho các định danh mà ta đã thảo luận ở các phần trước của chương này.
Các loại biến
1. Biến local - Local variable
- Biến local được khai báo trong các phương thức, hàm contructor hoặc trong các block.
- Biến local được tạo bên trong các phương thức, contructor, block và sẽ bị phá hủy khi kết thúc các phương thức, contructor và block.
- Không được sử dụng "access modifier" khi khai báo biến local.
- Các biến local được lưu trên vùng nhớ stack của bộ nhớ.
- Bạn cần khởi tạo giá trị mặc định cho biến local trước khi có thể sử dụng.
class Simple {
public static void main(String[] args) {
int a = 10; // local variable
float f = a; // local variable
System.out.println(a);
System.out.println(f);
}
}
2. Biến toàn cục - Instance variable
- Biến instance được khai báo trong một lớp(class), bên ngoài các phương thức, constructor và các block.
- Biến instance được lưu trong bộ nhớ heap.
- Biến instance được tạo khi một đối tượng được tạo bằng việc sử dụng từ khóa "new" và sẽ bị phá hủy khi đối tượng bị phá hủy.
- Biến instance có thể được sử dụng bởi các phương thức, constructor, block… Nhưng nó phải được sử dụng thông qua một đối tượng cụ thể.
- Bạn được phép sử dụng “access modifier” khi khai báo biến instance, mặc định là "default".
- Biến instance có giá trị mặc định phụ thuộc vào kiểu dữ liệu của nó. Ví dụ nếu là kiểu int, short, byte thì giá trị mặc định là 0, kiểu double thì là 0.0d... Vì vậy, bạn sẽ không cần khởi tạo giá trị cho biến instance trước khi sử dụng.
- Bên trong class mà bạn khai báo biến instance, bạn có thể gọi nó trực tiếp bằng tên khi sử dụng ở khắp nới bên trong class đó.
class A {
int data = 50; //instance variable
void method() {
System.out.println(data);
}
} //end of class
3. Biến static - Static variable
- Biến static được khai báo trong một class với từ khóa "static", phía bên ngoài các phương thức, constructor và block.
- Sẽ chỉ có duy nhất một bản sao của các biến static được tạo ra, dù bạn tạo bao nhiêu đối tượng từ lớp tương ứng.
- Biến static được lưu trữ trong bộ nhớ static riêng.
- Biến static được tạo khi chương trình bắt đầu chạy và chỉ bị phá hủy khi chương trình dừng.
- Giá trị mặc định của biến static phụ thuộc vào kiểu dữ liệu bạn khai báo tương tự biến instance.
- Biến static được truy cập thông qua tên của class chứa nó, với cú pháp: TenClass.tenBien.
- Trong class, các phương thức sử dụng biến static bằng cách gọi tên của nó khi phương thức đó cũng được khai báo với từ khóa "static".
class A {
static int m = 100; //static variable
void method() {
System.out.println(m);
}
} //end of class
Java Method - Phương thức trong Java
Khái niệm
Phương thức xác định giao diện cho phần lớn các lớp. Trong khi đó Java cho phép bạn định nghĩa các lớp mà không cần phương thức. Bạn cần định nghĩa phương thức truy cập dữ liệu mà bạn đã lưu trong một lớp.
Phương thức được định nghĩa như một hành động hoặc một tác vụ thật sự của đối tượng. Nó còn được định nghĩa như một hành vi mà trên đó các thao tác cần thiết được thực thi.
Cú pháp
access_specifier modifier datatype method_name(parameter_list)
{
//body of method
}
Trong đó:
- access_specifier: Chỉ định truy cập vào phương thức.
- modifier: Cho phép bạn gán các thuộc tính cho phương thức.
- datatype: Kiểu dữ liệu mà giá trị của nó được phương thức trả về. Nếu không có một giá trị nào được trả về, kiểu dữ liệu có thể là void.
- method_name: Tên của phương thức
- parameter_list: Chứa tên của tham số được sử dụng trong phương thức và kiểu dữ liệu. Dấu phẩy được dùng để phân cách các tham số.
class Temp {
static int x = 10; //variable
public static void show() //method
{
System.out.println(x);
}
public static void main(String args[]) {
Temp t = new Temp(); // object 1
t.show(); //method call
Temp t1 = new Temp(); // object 2
t1.x = 20;
t1.show();
}
}
Chỉ định truy cập phương thức - Access specifier method
Các chỉ định truy xuất dùng để giới hạn khả năng truy nhập vào một phương thức. Java cung cấp các chỉ định truy xuất sau đây:
- Public: Phương thức có chỉ định truy xuất public có thể được nhìn thấy từ mọi gói hoặc mọi lớp.
- Protected: Các lớp mở rộng từ lớp hiện hành trong cùng một gói, hoặc tại các gói khác nhau có thể truy cập các phương thức sử dụng chỉ định truy xuất này.
- Private: Phương thức riêng tư có thể được truy cập nhờ phương thức công cộng trên cùng một lớp.
Loại phương thức - Modifier method
Các bổ nghĩa loại phương thức cho phép ta thiết lập các thuộc tính của phương thức. Java cung cấp các bổ nghĩa sau:
- Tĩnh (static): Các trạng thái mà phương thức có thể được thay đổi mà không cần đến đối tượng. Nó chỉ được sử dụng đối với các dữ liệu và các phương thức tĩnh.
- Trừu tượng (abstract): Ngụ ý rằng phương thức không có một mã cụ thể (code) và nó sẽ được bổ sung ở các lớp con (subclass). Loại phương thức này được sử dụng trong các lớp kế thừa.
- Kết thúc (final): Phương thức không thể được thừa kế hoặc ghi đè (Overridden).
- Tự nhiên (native): Chỉ ra rằng phần thân của phương thức được viết trên các ngôn ngữ khác Java ví dụ C, hoặc C++.
- Đồng bộ (synchronized): Sử dụng với phương thức trong quá trình thực thi threads. Nó cho phép chỉ một thread được truy cập vào khối mã vào một thời điểm.
- Linh hoạt (volatile): Được sử dụng với các biến để thông báo rằng giá trị của biến có thể được thay đổi vài lần khi thực thi chương trình và giá trị của nó không được ghi vào thanh ghi.
Nạp chồng - Overloading method
Phương thức được nạp chồng (overload) là phương thức trong cùng một lớp, có cùng một tên song có danh sách các tham số khác nhau. Sử dụng việc nạp chồng phương thức để thực thi các phương thức giống nhau đối với các kiểu dữ liệu khác nhau. Ví dụ phương thức swap() có thể bị nạp chồng (overload) bởi các tham số của kiểu dữ liệu khác như integer, double và float.
Phương thức nạp chồng là một hình thức đa hình (polymorphism) trong quá trình biên dịch (compile).
Đoạn chương trình sau mô tả nạp chồng phương thức được thực hiện như thế nào:
//defined once
protected void perfomTask(double salary) {
System.out.prinln("Salary is: "+salary);….
}
//overloaded –defined the second time with different parameters
protected void performTask(double salary, int bonus) {
System.out.println("Total Salary is: " + salary + bonus);
}
Ghi đè - Overriding method
Phương thức được ghi đè (overriden) là phương thức có mặt ở lớp cha (superclasss) cũng như ở các lớp kế thừa. Phương thức này cho phép một lớp tổng quát chỉ định các phương thức sẽ là phương thức chung trong các lớp con. Ví dụ lớp xác định phương thức tổng quát 'area()'. Phương thức này có thể được hiện thực trong một lớp con để tìm diện tích một hình cụ thể như hình chữ nhật, hình vuông...
Phương thức ghi đè là một hình thức đa hình trong quá trình thực thi (runtime).
Các đoạn mã sau đây mô tả việc thực thi ghi đè phương thức trong Java:
class SupperClass // Tạo lớp cơ bản
{
int a;
SuperClass() // constuctor
{}
SuperClass(int b) //overloaded constructor
{
a = b;
}
class Subclass Extends SuperClass { // derriving a class
int a;
SubClass(int a) { //subclass constructor
This.a;
}
public void message() { // overiding the base class message()
System.out.prinln("In the sub class");
}
}
}
Bây giờ chúng ta sẽ tạo ra một đối tượng lớp cha và gán một lớp nhỏ tham chiếu đến nó như sau:
SuperClasss spObj = new Subclass(22);
Câu lệnh 'spObj.message' thuộc phương thức nhóm con. Ở đây kiểu đối tượng được gán cho 'spObj' sẽ chỉ được xác định khi chương trình thực thi. Điều này được biết dưới khái niệm 'liên kết động' (dinamic binding).
Phương thức khởi tạo lớp - Constructor method
Phương thức khởi tạo lớp là một loại phương thức đặc biệt rất khác với các kiểu khởi tạo cơ bản. Nó không có kiểu trả về. Nó có tên trùng với tên của lớp. Hàm khởi tạo lớp thực thi như một phương thức hoặc một chức năng bình thường song nó không trả về bất cứ một giá trị nào. Nói chung chúng được dùng để khởi tạo các biến thành viên của một lớp và nó được gọi bất cứ lúc nào bạn tạo ra đối tượng của lớp đó.
Phương thức khởi tạo lớp có hai loại:
- Tường minh (explicit): Bạn có thể lập trình những phương thức khởi tạo lớp khi định nghĩa lớp. Khi tạo một đối tượng của một lớp, những giá trị mà bạn truyền vào phải khớp với những tham số của phương thức khởi tạo (số lượng, thứ tự và kiểu dữ liệu của các tham số)
- Ngầm định (Implicit): Khi bạn không định nghĩa một hàm khởi tạo cho một lớp, JVM cung cấp một giá trị mặc định hay một phương thức khởi tạo ngầm định.
Bạn có thể định nghĩa nhiều phương thức khởi tạo cho một lớp. Giống như các phương thức khác, phương thức khởi tạo lớp có thể bị nạp chồng (overload)
Ví dụ một phương thức khởi tạo:
Đoạn mã sau đây định nghĩa một phương thức khởi tạo tường minh (explicit) cho một lớp Employee. Phương thức khởi tạo bao gồm tên và tuổi. Chúng được coi như các tham số và gán các giá trị của chúng vào các biến của lớp. Chú ý rằng từ khoá 'this' được sử dụng để tham chiếu đến đối tượng hiện hành của lớp.
Class Employee {
String name;
int age;
Employee(String varname, int varage) {
this.name = varname;
this.age = varage;
}
public static void main(String arg[]) {
Employee e = new Employee("Allen", 30);
}
}
Java Operators - Toán tử trong Java
Một chương trình thực tế bao hàm việc tạo ra các biến. Các toán tử kết hợp các giá trị đơn giản hoặc các biểu thức con thành những biểu thức mới, phức tạp hơn và có thể trả về các giá trị. Điều này có hàm ý tạo ra các toán tử luận lý, số học, quan hệ và so sánh trên các biểu thức.
Java cung cấp nhiều dạng toán tử. Chúng bao gồm:
- Toán tử số học
- Toán tử dạng bit
- Toán tử quan hệ
- Toán tử luận lý
- Toán tử điều kiện
- Toán tử gán
1. Các toán tử số học
Các toán hạng của các toán tử số học phải ở dạng số. Các toán hạng kiểu Boolean không sử dụng được, song các toán hạng ký tự cho phép sử dụng loại toán tử này. Một vài kiểu toán tử được liệt kê trong bảng dưới đây.
Toán tử |
Mô tả |
+ |
Cộng.Trả về giá trị tổng hai toán hạng Ví dụ 5+3 trả về kết quả là 8 |
- |
Trừ Trả về giá trị khác nhau giữa hai toán hạng hoặc giá trị phủ định của toán hạng. Ví dụ 5-3 kết quả là 2 và –10 trả về giá trị âm của 10 |
* |
Nhân Trả về giá trị là tích hai toán hạng. Ví dụ 5*3 kết quả là 15 |
/ |
Chia Trả về giá trị là thương của phép chia Ví dụ 6/3 kết quả là 2 |
% |
Phép lấy modulo Giá trị trả về là phần dư của toán tử chia Ví dụ 10%3 giá trị trả về là 1 |
++ |
Tăng dần Tăng giá trị của biến lên 1. Ví dụ a++ tương đương với a= a+1 |
-- |
Giảm dần Giảm giá trị của biến 1 đơn vị. Ví dụ a-- tương đương với a=a-1 |
+= |
Cộng và gán giá trị Cộng các giá trị của toán hạng bên trái vào toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c+=a tương đương c=c+a |
-= |
Trừ và gán giá trị Trừ các giá trị của toán hạng bên trái vào toán toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c-= a tương đương vớI c=c-a |
*= |
Nhân và gán Nhân các giá trị của toán hạng bên trái với toán toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c *= a tương đương với c=c*a |
/= |
Chia và gán Chia giá trị của toán hạng bên trái cho toán toán hạng bên phải và gán giá trị trả về vào toán hạng bên trái. Ví dụ c /= a tương đương với c=c/a |
%= |
Lấy số dư và gán Chia giá trị của toán hạng bên trái cho toán toán hạng bên phải và gán giá trị số dư vào toán hạng bên trái. Ví dụ c%=a tương đương với c=c%a |
Ví dụ:
class ArithmeticOp {
public static void main(String args[]) {
int p = 5, q = 12, r = 20, s;
s = p + q;
System.out.println(“p + q is” + s);
s = p % q;
System.out.println(“p % q is” + s);
s *= r;
System.out.println(“s *= r is” + s);
System.out.println(“Value of p before operation is” + p);
p++;
System.out.println(“Value of p after operation is” + p);
double x = 25.75, y = 14.25, z;
z = x - y;
System.out.println(“x - y is” + z);
z -= 2.50;
System.out.println(“z -= 2.50 is“ + z);
System.out.println(“Value of z before operation is” + z);
z--;
System.out.println(“Value of z after operation is” + z);
Z = x / y;
System.out.println(“x / y is” + z);
}
}
Output:
p+q is 17
p%q is 5
s*=r is 100
Value of p before operation is 9.0
Value of z after operation is 8.0
x/y is 1.8070175438596429
2. Toán tử Bit
Các toán tử dang Bit cho phép ta tạo những Bit riêng biệt trong các kiểu dữ liệu nguyên thuỷ. Toán tử Bit dựa trên cơ sở đại số Boolean. Nó thực hiện phép tính trên hai đối số là các bit để tạo ra một kết qủa mới. Một vài dạng toán tử kiểu này được liệt kê dưới đây
Toán tử |
Mô tả |
~ |
Phủ định (NOT) Trả về giá trị phủ định của một số. Ví dụ a=10 thì ~a=-10 |
& |
Toán tử AND Trả về giá trị là 1 nếu các toán hạng là 1 và 0 trong các trường hợp khác. Ví dụ nếu a=1và b=0 thì a&b trả về giá trị 0 |
I |
Toán tử OR Trả về giá trị là 1 nếu một trong các toán hạng là 1 và 0 trong các trường hợp khác. Ví dụ nếu a=1và b=0 thì aIb trả về giá trị 1 |
^ |
Exclusive OR Trả về giá trị là 1 nếu chỉ một trong các toán hạng là 1 và trả về 0 trong các trường hợp khác. Ví dụ nếu a=1và b=1 thì a^b trả về giá trị 0 |
>> |
Dịch sang phải Chuyển toàn bộ các bít cuả một số sang phải một vị trí, giữ nguyên dấu của số âm. Toán hạng bên trái là số bị dịch còn số bên phải chỉ số vị trí mà các bít cần dịch. Ví dụ x=37 tức là 00011111 vậy x>>2 sẽ là 00000111. |
<< |
Dịch sang trái Chuyển toàn bộ các bít cuả một số sang trái một vị trí, giữ nguyên dấu cuả số âm. Toán hạng bên trái là số bị dịch còn số bên phải chỉ số vị trí mà các bít cần dịch. |
3. Các toán tử quan hệ
Các toán tử quan hệ kiểm tra mối quan hệ giữa hai toán hạng. Kết quả của một biểu thức có dùng các toán tử quan hệ là những giá trị Boolean (logic "đúng" hoặc "sai"). Các toán tử quan hệ được sử dụng trong các cấu trúc điều khiển.
Toán tử |
Mô tả |
= = |
So sánh bằng Toán tử này kiểm tra sự tương đương của hai toán hạng Ví dụ if (a= =b) trả về giá tri “True” nếu giá trị của a và b như nhau |
!= |
So sánh khác Kiểm tra sự khác nhau của hai toán hạng Ví dụ if(a!=b) Trả về giá trị “true” nếu a khác b |
> |
Lớn hơn Kiểm tra giá trị của toán hạng bên phải lớn hơn toán hạng bên trái hay không Ví du if(a>b) . Trả về giá trị “true” nếu a lớn hơn b,ngựơc lai (nhỏ hơn hoặc bằng ), trả về ‘False’ |
< |
Nhỏ hơn Kiểm tra giá trị của toán hạng bên phải có nhỏ hơn toán hạng bên trái hay không Ví du if(a<b) . Trả về giá trị “true” nếu a nhỏ hơn b, ngựơc lại (lớn hơn hoặc bằng trả về ‘False’ |
>= |
Lớn hơn hoặc bằng Kiểm tra giá trị của toán hạng bên phải có lớn hơn hoặc bằng toán hạng bên trái hay không Ví du if(a>=b) . Trả về giá trị "true" nếu a lớn hơn hoặc bằng b, ngựơc lại (nhỏ hơn trả về ‘False’ |
<= |
Nhỏ hơn hoặc bằng Kiểm tra giá trị của toán hạng bên phải có nhỏ hơn hoặc bằng toán hạng bên trái hay không Ví du if(a<=b) . Trả về giá trị “true” nếu a nhỏ hơn hoặc bằng b , ngựơc lại (lớn hơn trả về 'False' |
Ví dụ:
class RelationalOp {
public static void main(String args[]) {
float a = 10.0 F;
double b = 10.0;
if (a = = b)
System.out.println(a and b are equal”);
else
System.out.println("a and b are not equal");
}
}
Output:
a and b are not equal
Trong chương trình trên cả a và b là những số có dấu phẩy động, dạng dữ liệu có khác nhau, a là kiểu float còn b là kiểu double. Tuy vậy chúng không phải là cùng một kiểu. Bởi vậy khi kiểm tra giá trị của các toán hạng, kiểu dữ liệu cần phải được kiểm tra.
4. Các toán tử logic
Các toán tử logic làm việc với các toán hạng Boolean. Một vài toán tử kiểu này được chỉ ra dưới đây:
Toán tử |
Mô tả |
& |
Và (AND) Trả về một giá trị “Đúng” (True) nếu chỉ khi cả hai toán tử có giá trị “True” Ví dụ: if(sciencemarks>90) AND (mathmarks>75) thì gán “Y” cho biến “được nhận học bổng” |
I |
Hoặc (OR) Trả về giá trị “True” nếu một giá trị là True hoặc cả hai đều là True Ví dụ Nếu age_category is ‘Senior_citizen’ OR special_category is ‘handicapped’ thì giảm giá tua lữ hành. Giá cũng sẽ được giảm nếu cả hai điều kiện đều được thỏa mãn |
^ |
XOR Trả về giá trị True nếu chỉ một trong các giá trị là True, các trường hợp còn lại cho giá trị False (sai) |
! |
Toán hạng đơn tử NOT. Chuyển giá trị từ True sang False và ngược lại. Ví dụ: Quá trình thực thi các dòng lệnh tiếp tục cho đến khi kết thúc chương trình. |
5. Các toán tử điều kiện
Toán tử điều kiện là một loại toán tử đặc biệt vì nó gồm ba thành phần cấu thành biểu thức điều kiện:
Cú pháp:
biểu thức 1?biểu thức 2: biểu thức 3;
biểu thức 1
Điều kiện luận lý (Boolean) mà nó trả về giá trị True hoặc False
biểu thức 2
Giá trị trả về nếu biểu thức 1 xác định là True
biểu thức 3
Giá trị trả về nếu biểu thức 1 xác định là False
Câu lệnh sau đây kiểm tra có những người đi làm bằng vé tháng có tuổi lớn hơn 65 không và gán một tiêu chuẩn cho họ. Nếu những người này có tuổi là 55, tiêu chuẩn gán là "Regular"
CommuterCategory=(CommuterAge>65)?"Senior Citizen": "Regular"
6. Toán tử gán
Toán tử gán (=) dùng để gán một giá trị vào một biến. Bạn nên gán nhiều giá trị đến nhiều biến cùng một lúc.
Ví dụ đoạn lệnh sau gán một giá trị cho biến num. Thì giá trị trong biến num được gán cho nhiều biến trên một dòng lệnh đơn.
int num = 20000;
int p,q,r,s;
p=q=r=s=num;
Dòng lệnh cuối cùng được thực hiện từ phải qua trái. Đầu tiên giá trị ở biến num được gán cho 's', sau đó giá trị của 's' được gán cho 'r' và cứ tiếp như vậy.
7. Thứ tự ưu tiên của các toán tử
Các biểu thức được viết ra nói chung gồm nhiều toán tử. Thứ tự ưu tiên quyết định trật tự thực hiện các toán tử trên các biểu thức. Bảng dưới đây liệt kê thứ tự thực hiện các toán tử trong Java
Thứ tự |
Toán tử |
1. |
Các toán tử đơn như +,-,++,-- |
2. |
Các toán tử số học và các toán tử dịch như *,/,+,-,<<,>> |
3. |
Các toán tử quan hệ như >,<,>=,<=,= =,!= |
4. |
Các toán tử logic và Bit như &&,II,&,I,^ |
5. |
Các toán tử gán như =,*=,/=,+=,-= |
Để thay đổi thứ tự ưu tiên trên một biểu thức, bạn có thể sử dụng dấu ngoặc đơn (). Từng phần của biểu thức được giới hạn trong ngoặc đơn được thực hiện trước tiên. Nếu bạn sử dùng nhiều ngoặc đơn lồng nhau thì toán tử nằm trong ngoặc đơn phía trong sẽ thực thi trước, sau đó đến các vòng phía ngoài. Nhưng trong phạm vi một ngoặc đơn thì quy tắc thứ tự ưu tiên vẫn giữ nguyên tác dụng.
Java Arrays - Mảng trong Java
Khái niệm
Java cung cấp cấu trúc dữ liệu, gọi là mảng (array), để lưu trữ các phần tử có cùng kiểu dữ liệu. Mảng được sử dụng để lưu trữ bộ sưu tập dữ liệu, nhưng người ta thường biết đến mảng lưu trữ các phần tử có cùng kiểu dữ liệu nhiều hơn.
Thay vì khai báo từng biến một, chẳng hạn như số 0, số 1... và số 99, bạn có thể khai báo một biến mảng như: số và sau đó sử dụng các số [0], số [1]... và số [99] để biểu diễn từng biến một.
Khai báo mảng trong Java
Để sử dụng một mảng trong chương trình, trước hết bạn phải khai báo biến để tham chiếu mảng, và phải chỉ định loại mảng mà biến có thể tham chiếu. Dưới đây là cú pháp khai báo biến mảng trong Java:
Cú pháp:
dataType[] arrayRefVar = new dataType[arraySize];
Hoặc:
dataType arrayRefVar[] = new dataType[arraySize];
Hoặc ngoài ra bạn có thể tạo các mảng trong Java bằng cách sử dụng cú pháp dưới đây:
dataType[] arrayRefVar = {value0, value1, ..., valuek};
Lưu ý:
Cú pháp dataType[] arrayRefVar được ưu tiên hơn. Còn cú pháp dataType arrayRefVar[] có nguồn gốc từ ngôn ngữ C/C++ và được chấp nhận trong Java.
Phương thức hoạt động của mảng
Một mảng sẽ có chỉ số index từ 0 đến n - 1 (n là số lượng phần tử của mảng).
Ví dụ: Khởi tạo mảng số nguyên 10 phần tử, có giá trị tuần tự từ 1 đến 10. Và xuất các giá trị của mảng ra màn hình console.
public class Main {
public static void main(String[] args) {
int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i + 1;
}
for(int item : arr) {
System.out.print(item + " ");
}
}
}
Truy xuất các phần tử của mảng
Chúng ta có 2 cách để truy xuất các phần tử của mảng:
Truy xuất trực tiếp
public class Main {
public static void main(String[] args) {
// Khoi tao mang
int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i + 1;
}
System.out.println(arr[5]);
}
}
Chú ý nếu index vượt ra khoảng chưá của mảng thì chúng ta sẽ nhận một exception. Khoảng hợp lệ 0 đến n – 1 (n số lượng phần tử của mảng).
Khi mình truy xuất arr[10] là vị trí không hợp lệ mình sẽ được kết quả sau:
Exception in thread “main” ava.lang.ArrayIndexOutOfBoundsException: 10
at Main.main(Main.java:11)
Truy xuất tuần tự
- Sử dụng vòng lặp for:
for(int i = 0; i < 10; i++) {
System.out.println(i);
}
- Sử dụng foreach:
for(int item : arr) {
System.out.println(item);
}
Passing by reference
Khi bạn truyền java vào một method, các thay đổi trên mảng sẽ được cập sau khi kết thúc method.
Ví dụ: Tăng mỗi phần tử trong mảng lên mảng và in ra màn hình console.
public class Main {
public static void main(String[] args) {
// Khoi tao mang
int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i + 1;
}
increment(arr);
for(int item : arr) {
System.out.print(item + " ");
}
}
public static void increment(int[] arr) {
for (int i = 0; i < 10; i++) {
arr[i] += 1;
}
}
}
Output: 2 3 4 5 6 7 8 9 10 11
Chúng ta thấy hàm increment()
đã thay đổi các giá trị trong mảng. Vì vậy ở hàm main truy xuất được mảng đã được update bởi increment()
.
Thao tác cơ bản trên array
Java cung cấp cho chúng ta một số thao tác cơ bản mà chúng ta thường xuyên sử dụng như sắp xếp, so sánh etc, đựơc implement trong class java.util.Arrays
.
STT | Method |
1 |
public static int binarySeach(Object[] a, Object key) Tìm kiếm phần tử key trong mảng, điều kiện mảng đã được sắp xếp |
2 |
public static boolean equals(long[] a, long[] a2) So sánh 2 mảng, trả về true nếu bằng nhau(index, value), ngược lại false |
3 |
public static void fill(int[] a. int val) Khởi tạo mảng với giá trị được gán sẵn val |
4 |
public static void sort(Object[] a) Sắp xếp mảng tăng dần |
Java Keywords - Các từ khóa trong Java
Keywords (Từ khóa) là những từ đã được định nghĩa cho trình biên dịch Java. Chúng có ý nghĩa đặc biệt cho trình biên dịch. Từ khóa Java phải có trong thông tin của bạn vì bạn không thể sử dụng chúng làm biến, lớp hoặc tên phương thức.
Bạn không thể sử dụng từ khóa làm định danh trong các chương trình Java của mình, các từ dành riêng trong thư viện Java và được sử dụng để thực hiện thao tác nội bộ.
abstract | assert | boolean | break |
byte | case | catch | char |
class | const | continue | default |
do | double | else | enum |
extends | final | finally | float |
for | goto | if | implements |
import | instanceof | int | interface |
long | native | new | package |
private | protected | public | return |
short | static | strictfp | super |
switch | synchronized | this | throw |
throws | transient | try | void |
volatile | while | true | false |
null |
- abstract - Chỉ ra rằng class hoặc phương thức sẽ phải thực thi sau đó trong một subclass.
- assert - Là keyword có từ Java 1.4 sử dụng để kiểm tra một biểu thức có đúng hay không, thường sử dụng cho việc viết code unit test. Trong Java sử dụng assert có 2 cách viết.
- boolean - Kiểu dữ liệu chỉ có giá trị True và False
- break – A control statement for breaking out of loops
- byte – A data type that can hold 8-bit data values
- case – Used in switch statements to mark blocks of text
- catch – Catches exceptions generated by try statements
- char – A data type that can hold unsigned 16-bit Unicode characters
- class -Declares a new class
- continue -Sends control back outside a loop
- default -Specifies the default block of code in a switch statement
- do -Starts a do-while loop
- double – A data type that can hold 64-bit floating-point numbers
- else – Indicates alternative branches in an if statement
- enum – A Java keyword used to declare an enumerated type. Enumerations extend the base class.
- extends -Indicates that a class is derived from another class or interface
- final -Indicates that a variable holds a constant value or that a method will not be overridden
- finally -Indicates a block of code in a try-catch structure that will always be executed
- float -A data type that holds a 32-bit floating-point number
- for -Used to start a for loop
- if -Tests a true/false expression and branches accordingly
- implements -Specifies that a class implements an interface
- import -References other classes
- instanceof -Indicates whether an object is an instance of a specific class or implements an interface
- int – A data type that can hold a 32-bit signed integer
- interface – Declares an interface
- long – A data type that holds a 64-bit integer
- native -Specifies that a method is implemented with native (platform-specific) code
- new – Creates new objects
- null -Indicates that a reference does not refer to anything
- package – Declares a Java package
- private -An access specifier indicating that a method or variable may be accessed only in the class it’s declared in
- protected – An access specifier indicating that a method or variable may only be accessed in the class it’s declared in (or a subclass of the class it’s declared in or other classes in the same package)
- public – An access specifier used for classes, interfaces, methods, and variables indicating that an item is accessible throughout the application (or where the class that defines it is accessible)
- return -Sends control and possibly a return value back from a called method
- short – A data type that can hold a 16-bit integer
- static -Indicates that a variable or method is a class method (rather than being limited to one particular object)
- strictfp - Là keyword đảm bảo cho bạn sẽ nhận một kết quả như nhau khi thực hiện các toán tử với số thập phân trên các platform’s hardware khác nhau. Keyword này có thể được sử dụng cho classes, interfaces and methods.
- super – Refers to a class’s base class (used in a method or class constructor)
- switch -A statement that executes code based on a test value
- synchronized -Specifies critical sections or methods in multithreaded code
- this -Refers to the current object in a method or constructor
- throw – Creates an exception
- throws -Indicates what exceptions may be thrown by a method
- transient - Là keyword sẽ thông báo cho JVM biết đối tượng được transient sẽ không được serialization khi truyền qua IO (cũng có nghĩa là đối tượng đó sẽ không được chuyển qua mạng)
- try -Starts a block of code that will be tested for exceptions
- void -Specifies that a method does not have a return value
- volatile -Indicates that a variable may change asynchronously
- while -Starts a while loop
** The keywords const and goto are reserved, even they are not currently in use.
- const -Reserved for future use
- goto – Reserved for future use
** true, false and null không phải là các từ dành riêng nhưng không thể được sử dụng làm định danh, bởi vì nó là giá trị của các kiểu dữ liệu.
Java If-else statement - Cấu trúc If-else trong Java
Câu lệnh if-else
kiểm tra kết quả của một điều kiện và thực thi một thao tác phù hợp trên cơ sở kết quả đó. Dạng của câu lệnh if-else
rất đơn giản.
Cú pháp:
if (conditon) {
action 1 statements;
}
else {
action 2 statements;
}
Trong đó:
- condition: Biểu thức Boolean như toán tử so sánh. Biểu thức này trả về giá trị True hoặc False
- action 1: Các dòng lệnh được thực thi khi giá trị trả về là True
- else: Từ khoá xác định các câu lệnh tiếp sau được thực hiện nếu điều kiện trả về giá trị False
- action 2: Các câu lệnh được thực thi nếu điều kiện trả về giá trị False
Ví dụ:
Đoạn chương trình sau kiểm tra xem các số là chẵn hay lẻ và hiển thị thông báo phù hợp:
class CheckNumber {
public static void main(String args[] {
int num = 10;
if (num % 2 == 0)
System.out.println(num + " is an even number");
else
System.out.println(num + " is an odd number");
}
}
Ở đoạn chương trình trên num
được gán giá trị nguyên là 10
. Trong câu lệnh if-else
điều kiện num %2
trả về giá trị 0 và điều kiện thực hiện là True. Thông báo 10 is an even number được in ra. Lưu ý rằng cho đến giờ chỉ có một câu lệnh tác động được viết trong đoạn if và else, bởi vậy không cần phải được đưa vào dấu ngoặc móc.
Java If-else-if statement - Cấu trúc If-else-if trong Java
Câu lệnh if-else-if
cũng kiểm tra điều kiện của các giá trị để thực thi khối lệnh. Nếu giá trị điều kiện if là True thì chỉ có khối lệnh sau if sẽ được thực hiện. Nếu giá trị điều kiện if else nào là True thì chỉ có khối lệnh sau else if đó sẽ được thực hiện… Nếu tất cả điều kiện của if và else if là False thì chỉ có khối lệnh sau else sẽ được thực hiện.
Cú pháp:
if (condition1) {
// khối lệnh này được thực thi
// nếu condition1 là true
} else if (condition2) {
// khối lệnh này được thực thi
// nếu condition2 là true
} else if (condition3) {
// khối lệnh này được thực thi
// nếu condition3 là true
}
...
else {
// khối lệnh này được thực thi
// nếu tất cả những điều kiện trên là false
}
Ví dụ:
public class MyClass {
public static void main(String[] args) {
int time = 22;
if (time < 10) {
System.out.println("Good morning.");
} else if (time < 20) {
System.out.println("Good day.");
} else {
System.out.println("Good evening.");
}
}
}
Kết quả:
Good evening.
Java Short If-else statement - Cấu trúc If-else rút gọn trong Java
Câu lệnh Short If-else
được sử dụng để rút gọn lệnh if-else
, giúp chương trình ngắn gọn hơn.
Cú pháp:
variable = (condition) ? expressionTrue : expressionFalse;
Ví dụ:
Thay vì sử dụng if-else như ví dụ dưới đây:
public class MyClass {
public static void main(String[] args) {
int time = 20;
if (time < 18) {
System.out.println("Good day.");
} else {
System.out.println("Good evening.");
}
}
}
Có thể viết lại ngắn gọn như sau mà vẫn cho kết quả đúng:
public class MyClass {
public static void main(String[] args) {
int time = 20;
String result;
result = (time < 18) ? "Good day." : "Good evening.";
System.out.println(result);
}
}
Java Switch-case statement - Cấu trúc Switch-case trong Java
Câu lệnh switch-case
có thể được xem như một dạng khác của câu lệnh if-else. Nó được sử dụng trong tình huống một biểu thức cho ra nhiều kết quả. Việc sử dụng câu lệnh switch-case cho phép việc lập trình dễ dàng và đơn giản hơn.
Cú pháp:
switch (expression)
{
case 'value':
action 1 statement;
break;
case 'value':
action 2 statement;
break;
:
:
case 'valueN':
actionN statement (s);
break;
default:
action default;
}
Trong đó:
- expession - Biến chứa một giá trị xác định
- value1,value 2,….valueN: Các giá trị hằng số phù hợp với giá trị trên biến expression .
- action1,action2…actionN: Các phát biểu được thực thi khi một trường hợp tương ứng có giá trị True
- break: Từ khoá được sử dụng để bỏ qua tất cả các câu lệnh sau đó và giành quyền điều khiển cho cấu trúc bên ngoài switch
- default: Từ khóa tuỳ chọn được sử dụng để chỉ rõ các câu lệnh nào được thực hiện chỉ khi tất cả các trường hợp nhận giá trị False
- default - action: Các câu lệnh được thực hiện chỉ khi tất cả các trường hợp nhận giá trị False
Ví dụ:
Đoạn chương trình sau xác định giá trị trong một biến nguyên và hiển thị ngày trong tuần được thể hiện dưới dạng chuỗi. Để kiểm tra các giá trị nằm trong khoảng 0 đến 6, chương trình sẽ thông báo lỗi nếu nằm ngoài phạm vi trên.
class SwitchDemo {
public static void main(String agrs[]) {
int day = 4;
switch (day) {
case 0:
system.out.println("Sunday");
break;
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
case 6:
System.out.println("Satuday");
break;
case 7:
System.out.println("Saturday");
break;
default:
System.out.println("Invalid day of week");
}
}
}
Java While loop - Vòng lặp While trong Java
Vòng lặp while
được sử dụng khi vòng lặp được thực hiện mãi cho đến khi điều kiện thực thi vẫn là True. Số lượng vòng lặp không đựơc xác định trước song nó sẽ phụ thuộc vào từng điều kiện.
Cú pháp:
while(condition)
{
action statement;
:
:
}
Trong đó:
- condition: Biểu thức Boolean, nó trả về giá trị True hoặc False. Vòng lặp sẽ tiếp tục cho đến khi nào giá trị True được trả về.
- action statement: Các câu lệnh được thực hiện nếu condition nhận giá trị True
Ví dụ 1:
Đoạn chương trình sau tính giai thừa của số 5.Giai thừa được tính như tích 5*4*3*2*1
class WhileDemo {
public static void main(String args[]) {
int a = 5, fact = 1;
while (a. >= 1) {
fact *= a;
a--;
}
System.out.println("The Factorial of 5 is " + fact);
}
}
Ở ví dụ trên, vòng lặp được thực thi cho đến khi điều kiện a>=1
là True. Biến a được khai báo bên ngoài vòng lặp và được gán giá trị là 5. Cuối mỗi vòng lặp, giá tri của a giảm đi 1. Sau năm vòng giá trị của a bằng 0. Điều kiện trả về giá trị False và vòng lặp kết thúc. Kết quả sẽ được hiển thị The factorial of 5 is 120
Ví dụ 2:
Đoạn chương trình sau hiển thi tổng của 5 số lẻ đầu tiên
class ForDemo {
public static void main(String args[]) {
int i = 1, sum = 0;
for (i = 1; i <= 10; i += 2)
sum += i;
System.out.println("sum of first five old numbers is " + sum);
}
}
Ở ví dụ trên, i và sum là hai biến được gán các giá trị đầu là 1 và 0 tương ứng. Điều kiện được kiểm tra và khi nó còn nhận giá trị True, câu lệnh tác động trong vòng lặp được thực hiện. Tiếp theo giá trị của i được tăng lên 2 để tạo ra số lẻ tiếp theo. Một lần nữa, điều kiện lại được kiểm tra và câu lệnh tác động lại được thực hiện. Sau năm vòng, i tăng lên 11, điều kiện trả về giá trị False và vòng lặp kết thúc. Thông báo: Sum of first five odd numbers is 25
được hiển thị.
Java Do-while loop - Vòng lặp Do-while trong Java
Vòng lặp do-while
trong java được sử dụng để lặp một phần của chương trình một vài lần. Tương tự như vòng lặp while, ngoại trừ do-while
thực hiện lệnh ít nhất một lần ngay cả khi điều kiện là False.
Cú pháp:
do {
// Khối lệnh được thực thi
} while(condition);
Ví dụ:
Ví dụ sau tính tổng của 5 số tự nhiên đầu tiên dùng cấu trúc do-while
public class DoWhileExample1 {
public static void main(String[] args) {
int a = 1, sum = 0;
do {
sum += a;
a++;
} while (a <= 5);
System.out.println("Sum of 1 to 5 is " + sum);
}
}
Java For loop - Vòng lặp For trong Java
Vòng lặp for
trong java được sử dụng để lặp một phần của chương trình nhiều lần. Nếu số lần lặp là cố định thì vòng lặp for
được khuyến khích sử dụng, còn nếu số lần lặp không cố định thì nên sử dụng vòng lặp while
hoặc do while
.
1. For loop basic
Cú pháp:
for (statement 1; statement 2; statement 3) {
// code block to be executed
}
Trong đó:
- Statement 1: Khởi tạo biến.
- Statement 2: Khai báo điều kiện để thực thi khối lệnh.
- Statement 3: Tăng/giảm biến.
Ví dụ:
public class MyClass {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
}
}
2. For-each loop
Vòng lặp for-each
được sử dụng để lặp mảng (array) hoặc tập hợp (collection) trong java. Bạn có thể sử dụng nó dễ dàng, dễ hơn cả vòng lặp for đơn giản. Bởi vì bạn không cần phải tăng hay giảm giá trị của biến rồi check điều kiện, bạn chỉ cần sử dụng ký hiệu hai chấm ":"
Cú pháp:
for (type variableName : arrayName) {
// code block to be executed
}
Ví dụ:
public class MyClass {
public static void main(String[] args) {
String[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
for (String i : cars) {
System.out.println(i);
}
}
}
Java Break statement - Lệnh Break trong Java
Từ khóa break
trong java được sử dụng để dừng và thoát thực thi lệnh trong vòng lặp (for, while, do-while) hoặc trong mệnh đề switch tại điều kiện đã được chỉ định. Đối với vòng lặp bên trong vòng lặp khác, thì nó chỉ dừng vòng lặp bên trong đó.
Cú pháp:
break;
Ví dụ:
public class MyClass {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 4) {
break;
}
System.out.println(i);
}
}
}
Kết quả:
0
1
2
3
Java Continue statement - Lệnh Continue trong Java
Từ khóa continue
trong java được sử dụng để tiếp tục vòng lặp tại điều kiện đã được xác định, và khối lệnh phía sau từ khóa continue sẽ không được thực thi. Đối với vòng lặp bên trong một vòng lặp khác, continue
chỉ có tác dụng với vòng lặp bên trong đó.
Cú pháp:
continue;
Ví dụ:
public class MyClass {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 4) {
continue;
}
System.out.println(i);
}
}
}
Kết quả:
0
1
2
3
5
6
7
8
9
Java Exceptions - Ngoại lệ trong Java
1. Giới thiệu
Exception là một lỗi đặc biệt. Lỗi này xuất hiện vào lúc thực thi chương trình. Các trạng thái không bình thường xảy ra trong khi thi hành chương trình tạo ra các exception. Những trạng thái này không được biết trước trong khi ta đang xây dựng chương trình. Nếu bạn không phân phối các trạng thái này thì exception có thể bị kết thúc đột ngột. Ví dụ, việc chia cho 0 sẽ tạo một lỗi trong chương trình. Ngôn ngữ Java cung cấp bộ máy dùng để xử lý ngoại lệ rất tuyệt vời. Việc xử lý này làm hạn chế tối đa trường hợp hệ thống bị phá vỡ (crash) hay hệ thống bị ngắt đột ngột. Tính năng này làm cho Java là một ngôn ngữ lập trình mạnh.
2. Mục đích của việc xử lý ngoại lệ
Một chương trình nên có cơ chế xử lý ngoại lệ thích hợp. Nếu không, chương trình sẽ bị ngắt khi một exception xảy ra. Trong trường hợp đó, tất cả các nguồn tài nguyên mà hệ thống trước kia phân phối sẽ được di dời trong cùng trạng thái. Điều này gây lãng phí tài nguyên. Để tránh trường hợp này, tất cả các nguồn tài nguyên mà hệ thống phân phối nên được thu hồi lại. Tiến trình này đòi hỏi cơ chế xử lý ngoại lệ thích hợp.
Cho ví dụ, xét thao tác nhập xuất (I/O) trong một tập tin. Nếu việc chuyển đổi kiểu dữ liệu không thực hiện đúng, một ngoại lệ sẽ xảy ra và chương trình bị hủy mà không đóng lại tập tin. Lúc đó tập tin dễ bị hư hại và các nguồn tài nguyên được cấp phát cho tập tin không được thu hồi lại cho hệ thống.
3. Xử lý ngoại lệ
Khi một ngoại lệ xảy ra, đối tượng tương ứng với ngoại lệ đó được tạo ra. Đối tượng này sau đó được truyền cho phương thức là nơi mà ngoại lệ xảy ra. Đối tượng này chứa thông tin chi tiết về ngoại lệ. Thông tin này có thể được nhận về và được xử lý. Các môi trường runtime như 'IllegalAccessException', 'EmptyStackException' v.v… có thể chặn được các ngoại lệ. Đoạn mã trong chương trình đôi khi có thể tạo ra các ngoại lệ. Lớp 'throwable' được Java cung cấp là lớp trên nhất của lớp Exception, lớp này là lớp cha của các ngoại lệ khác nhau.
4. Hệ thống cấp bậc của các lớp ngoại lệ trong Java
5. Xử lý ngoại lệ
Trong Java, mô hình xử lý ngoại lệ kiểm tra việc xử lý những hiệu ứng lề (lỗi), được biết đến là mô hình ‘catch và throw’. Trong mô hình này, khi một lỗi xảy ra, một ngoại lệ sẽ bị chặn và được đưa vào trong một khối. Người lập trình viên nên xét các trạng thái ngoại lệ độc lập nhau từ việc điều khiển thông thường trong chương trình. Các ngoại lệ phải được bắt giữ nếu không chương trình sẽ bị ngắt.
Ngôn ngữ Java cung cấp 5 từ khoá sau để xử lý các ngoại lệ:
- try
- catch
- throw
- throws
- finally
6. Một số class ngoại lệ
Ngoại lệ |
Mô tả |
RuntimeException |
Lớp cơ sở cho nhiều ngoại lệ java.lang |
ArthmeticException |
Trạng thái lỗi về số, ví dụ như ‘chia cho 0’ |
IllegalAccessException |
Lớp không thể truy cập |
IllegalArgumentException |
Phương thức nhận một đối số không hợp lệ |
ArrayIndexOutOfBoundsExeption |
Kích thước của mảng lớn hơn 0 hay lớn hơn kích thước thật sự của mảng |
NullPointerException |
Khi muốn truy cập đối tượng null |
SecurityException |
Việc thiết lập cơ chế bảo mật không được hoạt động |
ClassNotFoundException |
Không thể nạp lớp yêu cầu |
NumberFormatException |
Việc chuyển đối không thành công từ chuỗi sang số thực |
AWTException |
Ngoại lệ về AWT |
IOException |
Lớp cha của các ngoại lệ I/O |
FileNotFoundException |
Không thể định vị tập tin |
EOFException |
Kết thúc một tập tin |
NoSuchMethodException |
Phương thức yêu cầu không tồn tại |
InterruptedException |
Khi một luồng bị ngắt |
Java Try-catch exception - Khối Try-catch trong Java
Khối try-catch
được sử dụng để thi hành mô hình catch
và throw
của việc xử lý ngoại lệ. Khối try
chứa một bộ các lệnh có thể thi hành được. Các ngoại lệ có thể bị chặn khi thi hành những câu lệnh này. Phương thức dùng để chặn ngoại lệ có thể được khai báo trong khối try
. Một hay nhiều khối catch
có thể theo sau khối try
. Các khối catch này bắt các ngoại lệ bị chặn trong khối try
.
Cú pháp:
try {
// Block of code to try
}
catch(Exception e) {
// Block of code to handle errors
}
Ví dụ:
- Chương trình thông thường không sử dụng try-catch:
public class MyClass {
public static void main(String[] args) {
int[] myNumbers = {
1,
2,
3
};
System.out.println(myNumbers[10]); // error!
}
}
Kết quả lỗi:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at MyClass.main(MyClass.java:4)
- Chương trình sử dụng try-catch:
public class MyClass {
public static void main(String[] args) {
try {
int[] myNumbers = {
1,
2,
3
};
System.out.println(myNumbers[10]);
} catch (Exception e) {
System.out.println("Something went wrong.");
}
}
}
Kết quả:
Something went wrong.
Java Multiple-catch block exception - Khối Multi-catch trong Java
Các khối chứa nhiều catch
xử lý các kiểu ngoại lệ khác nhau một cách độc lập.
Khi sử dụng các try
lồng nhau, khối try
bên trong được thi hành đầu tiên. Bất kỳ ngoại lệ nào bị chặn trong khối try
sẽ bị bắt giữ trong các khối catch
theo sau. Nếu khối ‘catch’ thích hợp không được tìm thấy thì các khối catch
của các khối try
bên ngoài sẽ được xem xét. Nếu không, Java Runtime Environment xử lý các ngoại lệ.
Cú pháp:
try {
// Protected code
} catch (ExceptionType1 e1) {
// Catch block
} catch (ExceptionType2 e2) {
// Catch block
} catch (ExceptionType3 e3) {
// Catch block
}
Ví dụ 1:
public class TestMultipleCatchBlock {
public static void main(String args[]) {
try {
int a[] = new int[5];
a[5] = 30 / 0;
} catch (ArithmeticException e) {
System.out.println("task1 is completed");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("task 2 completed");
} catch (Exception e) {
System.out.println("common task completed");
}
System.out.println("rest of the code...");
}
}
Kết quả:
task1 is completed
rest of the code...
Ví dụ 2:
public class TestMultipleCatchBlock1 {
public static void main(String args[]) {
try {
int a[] = new int[5];
a[5] = 30 / 0;
} catch (Exception e) {
System.out.println("common task completed");
} catch (ArithmeticException e) {
System.out.println("task1 is completed");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("task2 is completed");
}
System.out.println("rest of the code...");
}
}
Kết quả:
Compile-time error
Chương trình trên bị lỗi tại compile-time là vì khi có ngoại lệ xảy ra thì các khối lệnh catch (ArithmeticException e) và catch (ArrayIndexOutOfBoundsException e) không bao giờ được thực thi, do khối catch (Exception e) đã bắt tất cả các ngoại lệ rồi.
Java Finally block exception - Khối Finally trong Java
Khi một ngoại lệ xuất hiện, phương thức đang được thực thi có thể bị dừng mà không được thi hành toàn vẹn. Nếu điều này xảy ra, thì các đoạn mã (ví dụ như đoạn mã với chức năng thu hồi tài nguyên có các lệnh đóng lại tập tin khai báo cuối phương thức) sẽ không bao giờ được gọi. Java cung cấp khối finally
để giải quyết việc này. Khối finally
thực hiện tất cả các việc thu dọn khi một ngoại lệ xảy ra. Khối này được sử dụng kết hợp với khối try
, khối finally
chứa các câu lệnh thu hồi tài nguyên về cho hệ thống hay lệnh in ra các câu thông báo.
Các lệnh này bao gồm:
- Đóng tập tin.
- Đóng lại bộ kết quả (được sử dụng trong chương trình cơ sở dữ liệu).
- Đóng lại các kết nối được tạo trong cơ sở dữ liệu.
Cú pháp:
try {
// Protected code
} catch (ExceptionType1 e1) {
// Catch block
} catch (ExceptionType2 e2) {
// Catch block
} catch (ExceptionType3 e3) {
// Catch block
}finally {
// The finally block always executes.
}
Ví dụ:
public class MyClass {
public static void main(String[] args) {
try {
int[] myNumbers = {
1,
2,
3
};
System.out.println(myNumbers[10]);
} catch (Exception e) {
System.out.println("Something went wrong.");
} finally {
System.out.println("The 'try catch' is finished.");
}
}
}
Kết quả:
Something went wrong.
The 'try catch' is finished.
Java Throw exception - Ném ngoại lệ trong Java
Các ngoại lệ bị chặn với sự trợ giúp của từ khoá throw
. Từ khóa throw
chỉ ra một ngoại lệ vừa xảy ra. Toán tử của throw
là một đối tượng của lớp, lớp này được dẫn xuất từ Throwable.
Tóm lại, mục đích của thow
để ném ngoại lệ cụ thể.
Cú pháp:
throw exception;
Ví dụ:
public class MyClass {
static void checkAge(int age) {
if (age < 18) {
throw new ArithmeticException("Access denied - You must be at least 18 years old.");
} else {
System.out.println("Access granted - You are old enough!");
}
}
public static void main(String[] args) {
checkAge(15); // Set age to 15 (which is below 18...)
}
}
Kết quả:
Exception in thread "main" java.lang.ArithmeticException: Access denied - You must be at least 18 years old.
at MyClass.checkAge(MyClass.java:4)
at MyClass.main(MyClass.java:12)
Java Throws exception - Khai bao ngoại lệ trong Java
Từ khóa throws
trong java được sử dụng để khai báo một ngoại lệ. Nó thể hiện thông tin cho lập trình viên rằng có thể xảy ra một ngoại lệ, vì vậy nó là tốt hơn cho các lập trình viên để cung cấp các mã xử lý ngoại lệ để duy trì luồng bình thường của chương trình.
Exception Handling chủ yếu được sử dụng để xử lý ngoại lệ checked. Nếu xảy ra bất kỳ ngoại lệ unchecked như NullPointerException, đó là lỗi của lập trình viên mà anh ta không thực hiện kiểm tra trước khi code được sử dụng.
Mục đích của throws:
- Ngoại lệ checked có thể được ném ra ngoài và được xử lý ở một hàm khác.
- Cung cấp thông tin cho caller của phương thức về các ngoại lệ.
Cú pháp:
ReturnType methodName() throws ExceptionClassName {
// method code
}
Ví dụ:
import java.io.IOException;
class Testthrows1 {
void m() throws IOException {
throw new IOException("device error"); //checked exception
}
void n() throws IOException {
m();
}
void p() {
try {
n();
} catch (Exception e) {
System.out.println("exception handled");
}
}
public static void main(String args[]) {
Testthrows1 obj = new Testthrows1();
obj.p();
System.out.println("normal flow...");
}
}
Kết quả:
exception handled
normal flow...
Sự khác nhau giữa Throw và Throws trong java
Có một số điểm khác nhau giữa từ khóa throw
và throws
trong java được mô tả trong bảng dưới đây:
No. | throw | throws |
---|---|---|
1) | Từ khóa throw trong java được sử dụng để ném ra một ngoại lệ rõ ràng. | Từ khóa throws trong java được sử dụng để khai báo một ngoại lệ. |
2) | Ngoại lệ checked không được truyền ra nếu chỉ sử dụng từ khóa throw. | Ngoại lệ checked được truyền ra ngay cả khi chỉ sử dụng từ khóa throws. |
3) | Sau throw là một instance. | Sau throws là một hoặc nhiều class. |
4) | Throw được sử dụng trong phương thức. | Throws được khai báo ngay sau dấu đóng ngoặc đơn của phương thức. |
5) | Bạn không thể throw nhiều exceptions. | Bạn có thể khai báo nhiều exceptions, Ví dụ: public void method()throws IOException,SQLException. |
Java User-defined exception - Tự định nghĩa ngoại lệ trong Java
Lớp Exception
thực thi giao diện Throwable
và cung cấp các tính năng hữu dụng để phân phối các ngoại lệ. Ưu điểm của nó là tạo các lớp ngoại lệ được định nghĩa bởi người dùng. Để làm điều này, một lớp con của lớp Exception được tạo ra. Ưu điểm của lớp con là một kiểu ngoại lệ mới có thể bị bắt giữ độc lập từ các loại Throwable khác.
Nếu bạn tạo ngoại lệ riêng của mình được biết đến như ngoại lệ tùy chỉnh (exception tùy chỉnh) hoặc ngoại lệ do người dùng định nghĩa. Các ngoại lệ tùy chỉnh trong Java được sử dụng để tùy chỉnh ngoại lệ theo nhu cầu của người dùng.
Cú pháp:
class MyException extends Exception {
}
Ví dụ:
Chương trình sau minh họa ngoại lệ được định nghĩa bởi người dùng ArraySizeException
public class ThrowDemo {
int size, array[];
ThrowDemo(int s) {
size = s;
try {
checkSize();
} catch (ArraySizeException e) {
System.out.println(e);
}
}
void checkSize() throws ArraySizeException {
if (size < 0)
throw new ArraySizeException();
else
System.out.println("The array size is ok.");
array = new int[3];
for (int i = 0; i < 3; i++)
array[i] = i + 1;
}
public static void main(String arg[]) {
new ThrowDemo(-1);
}
}
class ArraySizeException extends NegativeArraySizeException {
ArraySizeException() // constructor
{
super("You have passed an illegal array size");
}
}
Kết quả:
$javac ThrowDemo.java
$java -Xmx128M -Xms16M ThrowDemo
ArraySizeException: You have passed an illegal array size
Java Constructor - Phương thức khởi tạo trong Java
Contructor thật ra là một loại phương thức đặc biệt của lớp. Constructor dùng gọi tự động khi khởi tạo một thể hiện của lớp, có thể dùng để khởi gán những giá trị măc định. Các constructor không có giá trị trả về, và có thể có tham số hoặc không có tham số.
Constructor phải có cùng tên với lớp và được gọi đến dùng từ khóa new.
Nếu một lớp không có constructor thì java sẽ cung cấp cho lớp một constructor mặc định (default constructor). Những thuộc tính, biến của lớp sẽ được khởi tạo bởi các giá trị mặc định (số: thường là giá trị 0, kiểu luận lý là giá trị false, kiểu đối tượng giá trị null…).
Lưu ý: thông thường để an toàn, dễ kiểm soát và làm chủ mã nguồn chương trình chúng ta nên khai báo một constructor cho lớp.
Ví dụ:
public class xemay {
// …
public xemay() {}
public xemay(String s_nhasx, String s_model,
f_chiphisx, int i_thoigiansx, int i_so); {
nhasx = s_nhasx;
model = s_model;
chiphisx = f_chiphisx;
thoigiansx = i_thoigiansx;
so = i_so;
// hoặc
// this.nhasx = s_nhasx;
// this.model = s_model;
// this.chiphisx = f_chiphisx;
// this.thoigiansx = i_thoigiansx;
// this.so = i_so;
}
}
Java Inner class - Lớp nội trong Java
Lớp nội là lớp được khai báo bên trong 1 lớp khác. Lớp nội thể hiện tính đóng gói cao và có thể truy xuất trực tiếp biến của lớp cha.
Ví dụ:
public class A {
// …
int < field_1 >
static class B {
// …
int < field_2 >
public B(int par_1) {
field_2 = par_1 + field_1;
}
}
}
Trong ví dụ trên thì chương trình dịch sẽ tạo ra hai lớp với hai files khác nhau: A.class và B.class
Java Final class - Lớp "vô sinh" trong Java
Lớp không thể có lớp dẫn xuất từ nó (không có lớp con) gọi là lớp "vô sinh", hay nói cách khác không thể kế thừa được từ một lớp "vô sinh". Lớp "vô sinh" dùng để hạn chế, ngăn ngừa các lớp khác dẫn xuất từ nó.
Để khai báo một lớp là lớp "vô sinh", chúng ta dùng từ khóa final class
.
Tất cả các phương thức của lớp vô sinh đều vô sinh, nhưng các thuộc tính của lớp vô sinh thì có thể không vô sinh.
Ví dụ:
public final class A {
public final int x;
private int y;
public final void method_1() {
// …
}
public final void method_2() {
// …
}
}
Java Finalize method - Phương thức finalize trong Java
Trong Java không có kiểu dữ liệu con trỏ như trong C, người lập trình không cần phải quá bận tâm về việc cấp phát và giải phóng vùng nhớ, sẽ có một trình dọn dẹp hệ thống đảm trách việc này. Trình dọn dẹp hệ thống sẽ dọn dẹp vùng nhớ cấp phát cho các đối tượng trước khi hủy một đối tượng.
Phương thức finalize()
là một phương thức đặc biệt được cài đặt sẵn cho các lớp. Trình dọn dẹp hệ thống sẽ gọi phương thức này trước khi hủy một đối tượng. Vì vậy việc cài đặt một số thao tác giải phóng, dọn dẹp vùng nhớ đã cấp phát cho các đối tượng dữ liệu trong phương thức finalize()
sẽ giúp cho người lập trình chủ động kiểm soát tốt quá trình hủy đối tượng thay vị giao cho trình dọn dẹp hệ thống tự động. Đồng thời việc cài đặt trong phương thức finalize()
sẽ giúp cho bộ nhớ được giải phóng tốt hơn, góp phần cải tiến tốc độ chương trình.
Ví dụ:
class A {
// Khai báo các thuộc tính
public void method_1() {
// …
}
protected void finalize() {
// Có thể dùng để đóng tất cả các kết nối
// vào cơ sở dữ liệu trước khi hủy đối tượng.
// …
}
}
Java Streams I/O - Luồng I/O trong Java
Tất cả những hoạt động nhập/xuất dữ liệu (nhập dữ liệu từ bàn phím, lấy dữ liệu từ mạng về, ghi dữ liệu ra đĩa, xuất dữ liệu ra màn hình, máy in...) đều được quy về một khái niệm gọi là luồng (stream).
Luồng là nơi có thể "sản xuất" và "tiêu thụ" thông tin. Luồng thường được hệ thống xuất nhập trong java gắn kết với một thiết bị vật lý. Tất cả các luồng đều có chung một nguyên tắc hoạt động ngay cả khi chúng được gắn kết với các thiết bị vật lý khác nhau. Vì vậy cùng một lớp, phương thức xuất nhập có thể dùng chung cho các thiết bị vật lý khác nhau. Chẳng hạn cùng một phương thức có thể dùng để ghi dữ liệu ra console, đồng thời cũng có thể dùng để ghi dữ liệu xuống một file trên đĩa. Java hiện thực luồng bằng tập hợp các lớp phân cấp trong gói java.io
.
Java định nghĩa hai kiểu luồng: byte và ký tự (phiên bản gốc chỉ định nghĩa kiểu luồng byte, và sau đó luồng ký tự được thêm vào trong các phiên bản về sau).
Luồng byte
(hay luồng dựa trên byte) hỗ trợ việc xuất nhập dữ liệu trên byte, thường được dùng khi đọc ghi dữ liệu nhị phân.
Luồng ký tự
được thiết kế hỗ trợ việc xuất nhập dữ liệu kiểu ký tự (Unicode). Trong một vài trường hợp luồng ký tự sử dụng hiệu quả hơn luồng byte, nhưng ở mức hệ thống thì tất cả những xuất nhập đều phải qui về byte. Luồng ký tự hỗ trợ hiệu quả chỉ đối với việc quản lý, xử lý các ký tự
Java Byte I/O - Luồng Byte trong Java
Các luồng byte được định nghĩa dùng hai lớp phân cấp. Mức trên cùng là hai lớp trừu tượng InputStream
và OutputStream
:
- InputStream định nghĩa những đặc điểm chung cho những luồng nhập byte.
- OutputStream mô tả cách xử lý của các luồng xuất byte.
Các lớp con dẫn xuất từ hai lớp InputStream và OutputStream sẽ hỗ trợ chi tiết tương ứng với việc đọc ghi dữ liệu trên những thiết bị khác nhau. Đừng choáng ngợp với hàng loạt rất nhiều các lớp khác nhau. Đừng quá lo lắng, mỗi khi bạn nắm vững, sử dụng thành thạo một luồng byte nào đó thì bạn dễ dàng làm việc với những luồng còn lại.
# |
Lớp luồng byte | Ý nghĩa |
1 | BufferedInputStream | Buffered input stream |
2 | BufferedOutputStream | Buffered output stream |
3 | ByteArrayInputStream | Input stream đọc dữ liệu từ một mảng byte |
4 | ByteArrayOutputStream | Output stream ghi dữ liệu đến một mảng byte |
5 | DataInputStream | Luồng nhập có những phương thức đọc những kiểu dữ liệu chuẩn trong java |
6 | DataOutputStream | Luồng xuất có những phương thức ghi những kiểu dữ liệu chuẩn trong java |
7 | FileInputStream | Luồng nhập cho phép đọc dữ liệu từ file |
8 | FileOutputStream | Luồng xuất cho phép ghi dữ liệu xuống file |
9 | FilterInputStream | Hiện thực lớp trừu tượng InputStream |
10 | FilterOutputStream | Hiện thực lớp trừu tượng OutputStream |
11 | InputStream | Lớp trừu tượng, là lớp cha của tất cả các lớp luồng nhập kiểu Byte |
12 | OutputStream | Lớp trừu tượng, là lớp cha của tất cả các lớp xuất nhập kiểu Byte |
13 | PipedInputStream | Luồng nhập byte kiểu ống (piped) thường phải được gắn với một luồng xuất kiểu ống. |
14 | PipedOutputStream | Luồng nhập byte kiểu ống (piped) thường phải được gắn với một luồng nhập kiểu ống để tạo nên một kết nối trao đổi dữ liệu kiểu ống. |
15 | PrintStream | Luồng xuất có chứa phương thức print() và println() |
16 | PushbackInputStream | Là một luồng nhập kiểu Byte mà hỗ trợ thao tác trả lại (push back) và phục hồi thao tác đọc một byte (unread) |
17 | RandomAccessFile | Hỗ trợ các thao tác đọc, ghi đối với file truy cập ngẫu nhiên. |
18 | SequenceInputStream | Là một luồng nhập được tạo nên bằng cách nối kết logic các luồng nhập khác. |
Java Character I/O - Luồng ký tự trong Java
Các luồng ký tự được định nghĩa dùng hai lớp phân cấp. Mức trên cùng là hai lớp trừu tượng Reader và Writer:
- Lớp Reader dùng cho việc nhập dữ liệu của luồng
- Lớp Writer dùng cho việc xuất dữ liệu cua luồng. Những lớp dẫn xuất từ Reader và Writer thao tác trên các luồng ký tự Unicode.
# | Lớp luồng ký tự | Ý nghĩa |
1 | BufferedReader | Luồng nhập ký tự đọc dữ liệu vào một vùng đệm. |
2 | BufferedWriter | Luồng xuất ký tự ghi dữ liệu tới một vùng đệm. |
3 | CharArrayReader | Luồng nhập đọc dữ liệu từ một mảng ký tự |
4 | CharArrayWriter | Luồng xuất ghi dữ liệu tời một mảng ký tự |
5 | FileReader | Luồng nhập ký tự đọc dữ liệu từ file |
6 | FileWriter | Luồng xuất ký tự ghi dữ liệu đến file |
7 | FilterReader | Lớp đọc dữ liệu trung gian (lớp trừu tượng) |
8 | FilterWriter | Lớp xuất trung gian trừu tượng |
9 | InputStreamReader | Luồng nhập chuyển bytes thành các ký tự |
10 | LineNumberReader | Luồng nhập đếm dòng |
11 | OutputStreamWriter | Luồng xuất chuyển những ký tự thành các bytes |
12 | PipedReader | Luồng đọc dữ liệu bằng cơ chế đường ống |
13 | PipedWriter | Luồng ghi dữ liệu bằng cơ chế đường ống |
14 | PrintWriter | Luồng ghi văn bản ra thiết bị xuất (chứa phương thức print() và println()) |
15 | PushbackReader | Luồng nhập cho phép đọc và khôi phục lại dữ liệu |
16 | Reader | Lớp nhập dữ liệu trừu tượng |
17 | StringReader | Luồng nhập đọc dữ liệu từ chuỗi |
18 | StringWriter | Luồng xuất ghi dữ liệu ra chuỗi |
19 | Writer | Lớp ghi dữ liệu trừu tượng |
Java this keyword - Biến this trong Java
Biến this
là một biến ẩn tồn tại trong tất cả các lớp trong ngông ngữ java. Một class trong Java luôn tồn tại một biến this, biến this được sử dụng trong khi chạy và tham khảo đến bản thân lớp chứa nó.
Ví dụ:
< tiền tố > class A {
< tiền tố > int < field_1 > ;
< tiền tố > String < field_2 > ;
// Contructor của lớp A
public A(int par_1, String par_2) {
this.field_1 = par_1;
this.field_2 = par_2;
}
< tiền tố > <kiểu trảvề > <method_1 > () {
// ...
}
< tiền tố > <kiểu trảvề > <method_2 > () {
this.method_1()
// ...
}
}
Java Class
Kiến thức về các Class trong Java
Java String class - Lớp String trong Java
1. Khái niệm
String là một chuỗi các ký tự. Lớp String cung cấp hàng loạt các phương thức để thao tác với các chuỗi. Nó cung cấp các phương thức khởi tạo (constructor) khác nhau.
Package: java.lang
new String(byte[] byte_arr);
new String(byte[] byte_arr, Charset char_set)
new String(byte[] byte_arr, String char_set_name)
new String(byte[] byte_arr, int start_index, int length)
new String(byte[] byte_arr, int start_index, int length, Charset char_set)
new String(byte[] byte_arr, int start_index, int length, String char_set_name)
new String(char[] char_arr)
new String(char[] char_array, int start_index, int count)
new String(int[] uni_code_points, int offset, int count)
new String(StringBuffer s_buffer)
new String(StringBuilder s_builder)
Lớp String thuộc gói java.lang
Lớp java.lang.String được implements từ các interface Serializable, Comparable and CharSequence.
Ví dụ:
String str1 = new String( );
//str1 chứa một dòng trống.
String str2 = new String("Hello World");
//str2 chứa dòng "Hello World"
char ch[] = {'A','B','C','D','E'};
String str3 = new String(ch);
//str3 chứa "ABCDE"
String str4 = new String(ch,0,2);
//str4 chứa "AB" vì 0- tính từ ký tự bắt đầu, 2- là số lượng ký tự kể từ ký tự bắt đầu.
2. String pool
Một chương trình Java có thể chứa nhiều chuỗi bằng chữ. "String Pool" đại diện cho tất cả các chữ được tạo trong chương trình. Mỗi khi một chuỗi bằng chữ được tạo, String Pool tìm kiếm để nhìn thấy nếu chuỗi bằng chữ tồn tại. Nếu nó tồn tại, một thể hiện mới được gán đến một chuỗi mới.
Ví dụ:
String day = "Monday";
String weekday = "Monday";
Ở đây, một thể hiện cho biến "day", biến đó có giá trị là "Monday", được tạo trong String Pool. Khi chuỗi bằng chữ "weekday" được tạo, việc lưu giữ các giá trị giống nhau như của biến "day", một thể hiện đang tồn tại được gán đến biến "weekday". Vì cả hai biến "day" và "weekday" cũng đều nhằm chỉ vào chuỗi tương tự trong String Pool.
Hình ảnh sau minh hoạ khái niệm của "String Pool".
Theo ví dụ trên, chỉ có một đối tượng chuỗi “Monday” được tạo ra. Biến day
và weekday
được tham chiếu đến đối tượng chuỗi “Monday”.
3. Các phương thức lớp String
Trong phần này, chúng ta sẽ xem xét các phương thức của lớp String.
+ charAt()
Phương thức này trả về một ký tự tại một vị trí đặc biệt trong một chuỗi.
Ví dụ:
String name = new String("Java Language");
char ch = name.charAt(5);
Biến "ch" chứa giá trị "L", từ đó vị trí các số bắt đầu từ 0.
+ startsWith()
Phương thức này trả về giá trị kiểu logic (Boolean), phụ thuộc vào chuỗi có bắt đầu với một giá trị đặc biệt không.
Ví dụ:
String strname = "Java Language";
boolean flag = strname.startsWith("Java");
Biến "flag" chứa giá trị true.
+ endsWith()
Phương thức này trả về một giá trị kiểu logic (boolean), có chăng phụ thuộc vào chuỗi kết thúc với một giá trị đặc biệt.
Ví dụ:
String strname = "Java Language";
boolean flag = strname.endsWith("Java");
Biến "flag" chứa giá trị false.
+ copyValueOf()
Phương thức này trả về một chuỗi được rút ra từ một mảng ký tự được truyền như một đối số. Phương thức này cũng lấy hai tham số nguyên. Tham số đầu tiên chỉ định vị trí từ nơi các ký tự phải được rút ra, và tham số thứ hai chỉ định số ký tự được rút ra từ mảng.
Ví dụ:
char name[] = {'L','a','n','g','u','a','g','e'};
String subname = String .copyValueOf(name,5,2);
Bây giờ biến "subname" chứa chuỗi "ag".
+ toCharArray()
Phương thức này lấy một chuỗi, và chuyển nó vào một mảng ký tự. Ví dụ:
String text = new String("Hello World");
Char textArray[] = text.toCharArray();
+ indexOf()
Phương thức này trả về thứ tự của một ký tự đặc biệt, hoặc một chuỗi trong phạm vi một chuỗi. Các câu lệnh sau biểu diễn các cách khác nhau của việc sử dụng hàm.
String day = new String("Sunday");
int index1 = day.indexOf('n');
//chứa 2
int index2 = day.indexOf('z',2);
//chứa –1 nếu "z" không tìm thấy tại vị trí 2.
int index3 = day.indexOf("Sun");
//chứa mục 0 của mẫu
+ toUpperCase()
Phương thức này trả về chữ hoa của chuỗi thông qua hàm.
String lower = new String("good morning");
System.out.println("Uppercase: "+lower.toUpperCase( ));
+ toLowerCase()
Phương thức này trả về chữ thường của chuỗi thông qua hàm.
String upper = new String("LAPTRINH.VN");
System.out.println("Lowercase: "+upper.toLowerCase( ));
+ trim()
Phương thức này cắt bỏ khoảng trắng trong đối tượng String. Hãy thử đoạn mã sau để thấy sự khác nhau trước và sau khi cắt bỏ khoảng trắng.
String space = new String(" Spaces ");
System.ut.println(spaces);
System.out.println(spaces.trim()); //Sau khi cắt bỏ khoảng trắng
+ equals()
Phương thức này so sánh nội dung của hai đối tượng chuỗi.
String name1 = "Laptrinh.vn", name2 = "LAPTRINH.VN";
boolean flag = name1.equals(name2);
Biến "flag" chứa giá trị false.
4. Các phương thức của lớp String trong Java
SN | Methods | Description |
---|---|---|
1 | char charAt(int index) | Trả về một ký tự tại vị trí có chỉ số được chỉ định. |
2 | int compareTo(Object o) | So sánh một String với một Object khác. |
3 | int compareTo(String anotherString) | So sánh hai chuỗi theo từ điển. (Phân biệt chữ hoa chữ thường) |
4 | int compareToIgnoreCase(String str) | So sánh hai chuỗi theo từ điển. (Không phân biệt chữ hoa chữ thường) |
5 | String concat(String str) | Nối chuỗi được chỉ định đến cuối của chuỗi này. |
6 | boolean contentEquals(StringBuffer sb) | Trả về true nếu và chỉ nếu chuỗi này đại diện cho cùng một chuỗi ký tự như là StringBuffer quy định. |
7 | static String copyValueOf(char[] data) | Trả về một chuỗi đại diện cho chuỗi ký tự trong mảng quy định. |
8 | static String copyValueOf(char[] data, int offset, int count) | Trả về một chuỗi đại diện cho chuỗi ký tự trong mảng quy định. |
9 | boolean endsWith(String suffix) | Kiểm tra nếu chuỗi này kết thúc với hậu tố quy định. |
10 | boolean equals(Object anObject) | So sánh với một đối tượng |
11 | boolean equalsIgnoreCase(String anotherString) | So sánh với một String khác, không phân biệt chữ hoa chữ thường. |
12 | byte[] getBytes() | Mã hóa chuỗi này thành một chuỗi các byte bằng cách sử dụng bảng mã mặc định của flatform (nền tảng), lưu trữ kết quả vào một mảng byte mới. |
13 | byte[] getBytes(String charsetName) | Mã hóa chuỗi này thành một chuỗi các byte bằng cách sử dụng bảng mã cho trước, lưu trữ kết quả vào một mảng byte mới. |
14 | void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) | Copy các ký tự từ chuỗi này vào mảng ký tự đích. |
15 | int hashCode() | Trả về một mã "hash code" cho chuỗi này. |
16 | int indexOf(int ch) | Trả về chỉ số trong chuỗi này xuất hiện đầu tiên của ký tự cụ thể. |
17 | int indexOf(int ch, int fromIndex) | Trả về chỉ số trong chuỗi này xuất hiện đầu tiên của ký tự được chỉ định, bắt đầu tìm kiếm từ chỉ số cụ thể đến cuối. |
18 | int indexOf(String str) | Trả về chỉ số trong chuỗi này xuất hiện đầu tiên của chuỗi quy định. |
19 | int indexOf(String str, int fromIndex) |
Trả về chỉ số trong chuỗi này xuất hiện đầu tiên của chuỗi quy định, bắt đầu từ chỉ số xác định.
|
20 | String intern() | Returns a canonical representation for the string object. |
21 | int lastIndexOf(int ch) | Trả về chỉ số trong chuỗi này về sự xuất hiện cuối cùng của ký tự cụ thể. |
22 | int lastIndexOf(int ch, int fromIndex) | Trả về chỉ số trong chuỗi này về sự xuất hiện cuối cùng của ký tự được chỉ định, tìm kiếm lùi lại bắt đầu từ chỉ số xác định. |
23 | int lastIndexOf(String str) | Trả về chỉ số trong chuỗi này xảy ra cuối cùng bên phải của chuỗi quy định. |
24 | int lastIndexOf(String str, int fromIndex) |
Trả về chỉ số trong chuỗi này về sự xuất hiện cuối cùng của chuỗi xác định, tìm kiếm lùi lại bắt đầu từ chỉ số xác định.
|
25 | int length() | Trả về độ dài chuỗi. |
26 | boolean matches(String regex) | Kiểm tra chuỗi này khớp với biểu thức chính quy chỉ định hay không. |
27 | boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) | Kiểm tra chuỗi có một phần giống nhau. |
28 | boolean regionMatches(int toffset, String other, int ooffset, int len) | Kiểm tra chuỗi có một phần giống nhau. |
29 | String replace(char oldChar, char newChar) | Trả về một chuỗi mới từ thay thế tất cả các lần xuất hiện của ký tự oldChar trong chuỗi này với ký tự newChar. |
30 | String replaceAll(String regex, String replacement) |
Thay thế tất cả các chuỗi con của chuỗi này khớp với biểu thức chính quy bởi String mới replacement
|
31 | String replaceFirst(String regex, String replacement) | Thay thế chuỗi con đầu tiên của chuỗi này khớp với biểu thức chính quy bởi một String mới replacement |
32 | String[] split(String regex) | Tách chuỗi này thành các chuỗi con, tại các chỗ khớp với biểu thức chính quy cho trước. |
33 | String[] split(String regex, int limit) | Tách chuỗi này thành các chuỗi con, tại các chỗ khớp với biểu thức chính quy cho trước. Tối đa limit chuỗi con. |
34 | boolean startsWith(String prefix) | Kiểm tra nếu chuỗi này bắt đầu với tiền tố quy định. |
35 | boolean startsWith(String prefix, int toffset) |
Kiểm tra nếu chuỗi này bắt đầu với tiền tố quy định bắt đầu một chỉ số xác định.
|
36 | CharSequence subSequence(int beginIndex, int endIndex) | Trả về một chuỗi ký tự mới là một dãy con của dãy này. |
37 | String substring(int beginIndex) | Trả về một chuỗi ký tự mới là một dãy con của dãy này. Từ chỉ số cho trước tới cuối |
38 | String substring(int beginIndex, int endIndex) | Trả về một chuỗi ký tự mới là một dãy con của dãy này. Từ chỉ số bắt đầu cho tới chỉ số cuối. |
39 | char[] toCharArray() |
Chuyển chuỗi này thành mảng ký tự. |
40 | String toLowerCase() |
Chuyển tất cả các ký tự của chuỗi này sang chữ thường, sử dụng miền địa phương mặc định (default locale) |
41 | String toLowerCase(Locale locale) |
Chuyển tất cả các ký tự của chuỗi này sang chữ thường, sử dụng miền địa phương (locale) cho trước. |
42 | String toString() | Trả về String này. |
43 | String toUpperCase() | Chuyển tất cả các ký tự của chuỗi này sang chữ hoa, sử dụng miền địa phương mặc định (default locale) |
44 | String toUpperCase(Locale locale) | Chuyển tất cả các ký tự của chuỗi này sang chữ hoa, sử dụng miền địa phương (locale) cho trước. |
45 | String trim() | Trả về một String mới, sau khi loại bỏ các ký tự trắng (whitespace) bên trái và bên phải. |
46 | static String valueOf(primitive data type x) | Returns the string representation of the passed data type argument. |
5. Tính đối tượng và vừa có tính nguyên thủy của String
- Tính nguyên thủy:
Bạn có thể tạo một string literal (chuỗi chữ), string literal được lưu trữ trong ngăn xếp (stack), đòi hỏi không gian lưu trữ ít hơn khi thao tác.
String literal = "Hello World";
Bạn có thể sử dụng toán tử + để nối 2 string, toán tử này vốn quen thuộc và sử dụng cho các kiểu dữ liệu nguyên thủy int, float, double.
Các string literal được chứa trong một bể chứa (String pool). Như vậy hai string literal có nội dung giống nhau sử dụng chung một vùng bộ nhớ trên stack, điều này giúp tiết kiệm bộ nhớ.
- Tính đối tượng:
Vì String là một class, vì vậy nó có thể được tạo ra thông qua toán tử new.
String object = new String("Hello World");
Các đối tượng String được lưu trữ trên Heap, yêu cầu quản lý bộ nhớ phức tạp và tốn không gian lưu trữ. Hai đối tượng String có nội dung giống nhau lưu trữ trên 2 vùng bộ nhớ khác nhau của Heap.
Java StringBuffer class - Lớp StringBuffer trong Java
1. Khái niệm
Lớp StringBuffer cung cấp các phương thức khác nhau để thao tác một đối tượng dạng chuỗi. Lớp StringBuffer được sử dụng để tạo chuỗi có thể thay đổi (mutable). Lớp StringBuffer trong java tương tự như lớp String ngoại trừ nó có thể thay đổi.
Package: java.lang
2. Khởi tạo đối tượng lớp StringBuffer
- StringBuffer(): Tạo ra một bộ đệm chuỗi với dung lượng ban đầu là 16.
- StringBuffer(String str): Tạo ra một bộ đệm chuỗi với chuỗi cụ thể.
- StringBuffer(int capacity): Tạo ra một bộ đệm chuỗi với dung lượng được chỉ định như độ dài chuỗi.
Ví dụ:
class StringBufferCons {
public static void main(String args[]) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer(20);
StringBuffer s3 = new StringBuffer(“StringBuffer”);
System.out.println("s3 = " + s3);
System.out.println(s2.length()); //chứa 0
System.out.println(s3.length()); //chứa 12
System.out.println(s1.capacity()); //chứa 16
System.out.println(s2.capacity()); //chứa 20
System.out.println(s3.capacity()); //chứa 28
}
}
length()
và capacity()
của đối tượng StringBuffer là hoàn toàn khác nhau. Phương thức length()
đề cập đến số các ký tự mà đối tượng đưa ra, trong khi capacity()
trả về tổng dung lượng mặc định của một đối tượng (16), và số các ký tự trong đối tượng StringBuffer.
3. Các phương thức lớp StringBuffer
- public synchronized StringBuffer append(String s): được sử dụng để nối thêm các chuỗi được chỉ định với chuỗi này. Các phương thức append() được nạp chồng như append(char), append(boolean), append(int), append(float), append(double), …
- public synchronized StringBuffer insert(int offset, String s): được sử dụng để chèn chuỗi chỉ định với chuỗi này tại vị trí quy định. Các phương thức insert() được nạp chồng như insert(int, char), insert(int, boolean), insert(int, int), insert(int, float), insert(int, double), …
- public synchronized StringBuffer replace(int startIndex, int endIndex, String str): được sử dụng để thay thế chuỗi từ vị trị startIndex đến endIndex bằng chuỗi str.
- public synchronized StringBuffer delete(int startIndex, int endIndex): được sử dụng để xóa chuỗi từ vị trí startIndex đến endIndex.
- public synchronized StringBuffer reverse(): được sử dụng để đảo ngược chuỗi.
- public int capacity(): được sử dụng để trả về dung lượng hiện tại.
- public void ensureCapacity(int minimumCapacity): được sử dụng để đảm bảo dung lượng ít nhất bằng mức tối thiểu nhất định.
- public char charAt(int index): được sử dụng trả về ký tự tại vị trí quy định.
- public int length(): được sử dụng trả về chiều dài của chuỗi nghĩa là tổng số ký tự.
- public String substring(int beginIndex): được sử dụng trả về chuỗi con bắt đầu từ vị trí được chỉ định.
- public String substring(int beginIndex, int endIndex): được sử dụng trả về chuỗi con với vị trí bắt đầu và vị trí kết thúc được chỉ định.
+ append()
Phương thức này nối thêm một chuỗi hoặc một mảng ký tự tại vị trí cuối cùng của một đối tượng StringBuffer. Ví dụ:
StringBuffer s1 = new StringBuffer("Good");
s1.append("evening");
Giá trị trong s1 bây giờ là "goodevening".
+ insert()
Phương thức này lấy hai tham số. Tham số đầu tiên là vị trí chèn. Tham số thứ hai có thể là một chuỗi, một ký tự (char), một giá trị nguyên (int), hay một giá trị số thực (float) được chèn vào. Vị trí chèn sẽ lớn hơn hay bằng đến 0, và nhỏ hơn hay bằng chiều dài của đối tượng Stringbuffer. Bất kỳ đối số nào, trừ ký tự hoặc chuỗi, được chuyển vào biểu mẫu chuỗi, và sau đó được chèn vào. Ví dụ:
StringBuffer str = new StringBuffer("Java sion");
str.insert(1,'b');
Biến "str" chứa chuỗi "Java sion".
+ charAt()
Phương thức này trả về một giá trị ký tự trong đối tượng StringBuffer tại vị trí được chỉ định. Ví dụ:
StringBuffer str = new StringBuffer("James Gosling");
char letter = str.charAt(6); //chứa "G"
+ setCharAt()
Phương thức này được sử dụng để thay thế ký tự trong một StringBuffer với những cái khác tại một vị trí được chỉ định.
StringBuffer name = new StringBuffer("Java");
name.setCharAt(2,’v’);
Biến "name" chứa "Java".
+ setLength()
Phương thức này thiết lập chiều dài của đối tượng StringBuffer. Nếu chiều dài được chỉ định nhỏ hơn chiều dài nguyên thuỷ của bộ nhớ trung gian, thì các ký tự thừa sẽ bị cắt bớt. Nếu chiểu dài chỉ định nhiều hơn chiều dài nguyên thủy của bộ nhớ đệm, các ký tự null được thêm vào tại vị trí cuối cùng của bộ nhớ đệm.
StringBuffer str = new StringBuffer(10);
str.setLength(str.legth() +10);
+ getChars()
Phương thức này được sử dụng để trích ra các ký tự từ đối tượng StringBuffer, và sao chép chúng vào một mảng. Phương thức getChars() lấy bốn tham số sau:
- Mục bắt đầu: vị trí bắt đầu, từ nơi mà ký tự được lấy vào.
- Mục kết thúc: vị trí kết thúc
- Mảng: Mảng đích, nơi mà các ký tự được sao chép.
- Nơi gởi tới mục bắt đầu: Các ký tự được sao chép trong mảng đích từ vị trí này.
Ví dụ:
StringBuffer str = new StringBuffer("Leopard");
char ch[] = new char[10];
str.getChars(3,6,ch,0);
Bây giờ biến "ch" chứa "par"
+ reverse()
Phương thức này đảo ngược nội dung của một đối tượng StringBuffer, và trả về một đối tượng StringBuffer. Ví dụ:
StringBuffer str = new StringBuffer("devil");
StringBuffer strrev = str.reverse();
Biến "strrev" chứa "lived".
+ replace()
Phương thức replace() của lớp StringBuffer thay thế chuỗi bằng chuỗi khác từ vị trị bắt đầu và kết thúc được quy định.
public class StringBufferExam3 {
public static void main(String args[]) {
StringBuffer sb = new StringBuffer("Hello");
sb.replace(1, 3, "Java");
System.out.println(sb); //in HJavalo
}
}
+ delete()
Phương thức replace() của lớp StringBuffer xóa chuỗi từ vị trị bắt đầu và kết thúc được quy định.
public class StringBufferExam4 {
public static void main(String args[]) {
StringBuffer sb = new StringBuffer("Hello");
sb.delete(1, 3);
System.out.println(sb); //in Hlo
}
}
+ capacity()
Phương thức capacity() của lớp StringBuffer trả về dung lượng của bộ nhớ đệm. Dung lượng mặc định của bộ nhớ đệm là 16. Nếu số lượng ký tự của chuỗi tăng lên thì dung lượng được tính theo công thức (dung lượng cũ*2)+2. Ví dụ: Nếu dung lượng hiện tại là 16, nó sẽ tăng lên (16*2)+2=34.
public class StringBufferExam6 {
public static void main(String args[]) {
StringBuffer sb = new StringBuffer();
System.out.println(sb.capacity()); //mặc định là 16
sb.append("Hello");
System.out.println(sb.capacity()); //đến đây vẫn là 16
sb.append("java is my favourite language");
System.out.println(sb.capacity()); //đến đây là (16*2)+2=34 i.e (dung lượng cũ*2)+2
}
}
+ ensureCapacity()
Phương thức ensureCapacity() của lớp StringBuffer đảm bảo rằng dung lượng đã cho là tối thiểu với dung lượng hiện tại. Nếu nó lớn hơn dung lượng hiện tại, dung lượng hiện tại được tăng theo công thức (dung lượng cũ*2)+2. Ví dụ, dung lượng hiện tại là 16, nó sẽ tăng lên là (16*2)+2=34.
public class StringBufferExam7 {
public static void main(String args[]) {
StringBuffer sb = new StringBuffer();
System.out.println(sb.capacity()); //mặc định là 16
sb.append("Hello");
System.out.println(sb.capacity()); //đến đây là 16
sb.append("java is my favourite language");
System.out.println(sb.capacity()); //đến đây là (16*2)+2=34 i.e (dung lượng cũ*2)+2
sb.ensureCapacity(10); //đến đây không có sự thay đổi
System.out.println(sb.capacity()); //đến đây là 34
sb.ensureCapacity(50); //đến đây là (34*2)+2
System.out.println(sb.capacity()); //đến đây là 70
}
}
Java StringBuilder class - Lớp StringBuilder trong Java
1. Khái niệm
Trong java, lớp StringBuilder được sử dụng để tạo chuỗi có thể thay đổi (mutable). Lớp StringBuilder trong java tương tự như lớp StringBuffer ngoại trừ nó có các phương thức không đồng bộ (non-synchronized).
Package: java.lang
2. Khởi tạo đối tượng lớp StringBuilder
- StringBuilder(): Tạo ra một Builder chuỗi với dung lượng ban đầu là 16.
- StringBuilder(String str): Tạo ra một Builder chuỗi với chuỗi cụ thể.
- StringBuilder(int capacity): Tạo ra một Builder chuỗi với dung lượng được chỉ định như độ dài chuỗi.
- StringBuilder(CharSequence cs): Tạo ra một Builder chuỗi với chuỗi cụ thể.
3. Các phương thức của lớp StringBuilder
- public StringBuilder append(String s): được sử dụng để nối thêm các chuỗi được chỉ định với chuỗi này. Các phương thức append() được nạp chồng như append(char), append(boolean), append(int), append(float), append(double), …
- public StringBuilder insert(int offset, String s): được sử dụng để chèn chuỗi chỉ định với chuỗi này tại vị trí quy định. Các phương thức insert() được nạp chồng như insert(int, char), insert(int, boolean), insert(int, int), insert(int, float), insert(int, double), …
- public StringBuilder replace(int startIndex, int endIndex, String str): được sử dụng để thay thế chuỗi từ vị trị startIndex đến endIndex bằng chuỗi str.
- public StringBuilder delete(int startIndex, int endIndex): được sử dụng để xóa chuỗi từ vị trí startIndex đến endIndex.
- public StringBuilder reverse(): được sử dụng để đảo ngược chuỗi.
- public int capacity(): được sử dụng để trả về dung lượng hiện tại.
- public void ensureCapacity(int minimumCapacity): được sử dụng để đảm bảo dung lượng ít nhất bằng mức tối thiểu nhất định.
- public char charAt(int index): được sử dụng trả về ký tự tại vị trí quy định.
- public int length(): được sử dụng trả về chiều dài của chuỗi nghĩa là tổng số ký tự.
- public String substring(int beginIndex): được sử dụng trả về chuỗi con bắt đầu từ vị trí được chỉ định.
- public String substring(int beginIndex, int endIndex): được sử dụng trả về chuỗi con với vị trí bắt đầu và vị trí kết thúc được chỉ định.
+ append()
Phương thức append() của lớp StringBuilder nối thêm tham số vào cuối chuỗi.
public class StringBuilderExam1 {
public static void main(String args[]) {
StringBuilder sb = new StringBuilder("Hello ");
sb.append("Java");
System.out.println(sb);
}
}
+ insert()
Phương thức insert() của lớp StringBuilder chèn chuỗi vào chuỗi này từ vị trí quy định.
public class StringBuilderExam2 {
public static void main(String args[]) {
StringBuilder sb = new StringBuilder("Hello ");
sb.insert(1, "Java");
System.out.println(sb);
}
}
+ replace()
Phương thức replace() của lớp StringBuilder thay thế chuỗi bằng chuỗi khác từ vị trị bắt đầu và kết thúc được quy định.
public class StringBuilderExam3 {
public static void main(String args[]) {
StringBuilder sb = new StringBuilder("Hello");
sb.replace(1, 3, "Java");
System.out.println(sb);
}
}
+ delete()
StringBuilder delete() được sử dụng để xóa chuỗi từ vị trí startIndex đến endIndex
StringBuilder sb = new StringBuilder("JournalABC");
sb.delete(7,14);
System.out.println(sb);// prints Journal
+ reverse()
Phương thức reverse() được sử dụng để đảo ngược chuỗi.
StringBuilder sb = new StringBuilder("lived");
sb.reverse();
System.out.println(sb);// prints devil
+ capacity()
Phương thức capacity() của lớp StringBuilder trả về dung lượng của bộ nhớ đệm. Dung lượng mặc định của bộ nhớ đệm là 16. Nếu số lượng ký tự của chuỗi tăng lên thì dung lượng được tính theo công thức (dung lượng cũ*2)+2. Ví dụ: Nếu dung lượng hiện tại là 16, nó sẽ tăng lên (16*2)+2=34.
StringBuilder sb=new StringBuilder();
System.out.println(sb.capacity()); // default value 16
sb.append("Java");
System.out.println(sb.capacity()); // still 16
sb.append("Hello StringBuilder Class!");
System.out.println(sb.capacity()); // (16*2)+2
+ ensureCapacity()
Phương thức ensureCapacity() của lớp StringBuilder đảm bảo rằng dung lượng đã cho là tối thiểu với dung lượng hiện tại. Nếu nó lớn hơn dung lượng hiện tại, dung lượng hiện tại được tăng theo công thức (dung lượng cũ*2)+2. Ví dụ, dung lượng hiện tại là 16, nó sẽ tăng lên là (16*2)+2=34.
public class StringBuilderExample {
public static void main(String[] args) {
StringBuilder sbObj = new StringBuilder();
System.out.println(sbObj.capacity()); //default 16
sbObj.append("Java StringBuilder Class!");
System.out.println(sbObj.capacity()); // capacity 34
sbObj.ensureCapacity(12); // no change
System.out.println(sbObj.capacity()); //still 34
sbObj.ensureCapacity(60); // (34*2)+2 = 70
System.out.println(sbObj.capacity()); //70
}
}
Java Math class - Lớp java.lang.Math trong Java
Lớp này chứa các phương thức tĩnh để thực hiện các thao tác toán học.
Package: java.lang
Các phương thức của Math class như sau:
abs()
Phương thức này trả về giá trị tuyệt đối của một số. Đối số được truyền đến nó có thể là kiểu int, float, double, hoặc long. Kiểu dữ kiệu byte và short được chuyển thành kiểu int nếu chúng được truyền tới như là một đối số. Ví dụ:
int num = -1;
Math.abs(num) //trả về 1.
ceil()
Phương thức này tìm thấy số nguyên lớn hơn hoặc bằng đối số được truyền đến ngay tức thời.
floor()
Phương thức này trả về số nguyên nhỏ hơn hoặc bằng đối số được truyền vào ngay tức thời.
System.out.println(Math.ceil(8.02)); //trả về 0.9
System.out.println(Math.ceil(-1.3)); //trả về -1.0
System.out.println(Math.ceil(100)); //trả về 100.0
System.out.println(Math.floor(-5.6)); //trả về -6.0
System.out.println(Math.floor(201.1)); //trả về 201
System.out.println(Math.floor(100)); //trả về 100
max()
Phương thức này tìm giá trị lớn nhất trong hai giá trị được truyền vào. Các đối số được
truyền vào có thể là kiểu int, long, double, và float.
min()
Phương thức này tìm giá trị nhỏ nhất trong hai giá trị được truyền vào. Các đối số được truyền vào có thể là kiểu int, long, double và float.
round()
Phương thức này làm tròn đối số có dấu phẩy động. Ví dụ, câu lệnh Math.round(34.5) trả về 35.
random()
Phương thức này trả về một số ngẫu nhiên giữa 0.0 và 1.0 của kiểu double.
sqrt()
Phương thức này trả về bình phương của một số. Ví dụ, câu lệnh Math.sqrt(144) trả về 12.0.
sin()
Phương thức này trả về sine của một số, nếu góc được truyền đến bằng radian. Ví dụ: Math.sin(Math.PI/2) trả về 1.0, giá trị của sin 45.
Pi/2 radians = 90 độ. Giá trị của "pi" bắt nguồn từ hằng số được định nghĩa trong lớp "Math.PI".
cos()
Phương thức này trả về cos của một số, nếu góc được truyền đến bằng radian.
tan()
Phương thức này trả về tan của một số, nếu góc được truyền đến bằng radian.
Java Runtime class - Lớp Runtime trong Java
Lớp Runtime được gói gọn trong môi trường Runtime. Lớp này được sử dụng cho việc quản lý bộ nhớ, và việc thực thi của các quá trình xử lý gia tăng. Mỗi chương trình Java có một thể hiện đơn của lớp này, để cho phép ứng dụng giao tiếp với môi trường. Nó không thể được khởi tạo, khi mà một ứng dụng không thể tạo ra một minh dụ của riêng mình thuộc lớp này. Tuy nhiên, chúng ta có thể tạo ra một minh dụ hiện hành trong lúc thực hiện chương trình từ việc dùng phương thức Runtime().garbage
Package: java.lang
Bây giờ, chúng ta biết rằng việc thu gom các dữ liệu không thích hợp trong Java là một tiến trình tự động, và chạy một cách định kỳ. Để kích hoạt một cách thủ công bộ thu thập dữ liệu không thích hợp, ta gọi phương thức gc() trên minh dụ thời gian thời gian thực hiện hành. Để quyết định chi tiết cấp phát bộ nhớ, sử dụng các phương thức totalMemory()
và freeMemory()
.
Runtime r = Runtime.getRunTime(); .....
.....
long freemem = r.freeMemory();
long totalmem = r.totalMemory(); r.gc();
Bảng sau biểu diễn một vài phương thức được sử dụng chung của lớp này:
STT |
Method |
Purpose |
1 |
exit(int) |
Dừng việc thực thi, và trả về giá trị của đoạn mã đến hệ điều hành. Việc dừng thông thường với giá trị 0; giá trị khác 0 cho biết việc dừng khác thường. |
2 |
freeMemory() |
Quyết định số lượng sẵn có của bộ nhớ trống đến hệ thống thời gian chạy của Java trong giới hạn của các byte |
3 |
getRuntime() |
Trả về thể hiện thời gian chạy hiện hành. |
4 |
gc() |
Gọi những bộ phận thu thập dữ liệu vô nghĩa. |
5 |
totalMemory() |
Để quyết định tổng số lượng bộ nhớ sẵn có của chương trình. |
6 | exec(String) |
Thực thi một chương trình phân cách của tên được gọi. |
Ví dụ:
class RuntimeDemo {
public static void main(String args[]) {
Runtime r = Runtime.getRuntime();
Process p = null;
try {
p = r.exec(“calc.exe”);
}
catch(Exception e) {
System.out.println(“Error executing calculator”);
}
}
}
Bạn có thể đạt được minh dụ thời Runtime hiện hành thông qua phương thức Runtime.getRuntime().
Sau đó, bạn có thể tham chiếu đến chương trình thi hành calc.exe, và lưu trữ trong một đối tượng của tiến trình.
Java System class - Lớp System trong Java
Lớp System cung cấp các điều kiện thuận lợi như là, xuất, nhập chuẩn và các luồng lỗi. Nó cũng cung cấp một giá trị trung bình để các thuộc tính truy cập được kết hợp với hệ thống thời gian chạy của Java, và các thuộc tính môi trường khác nhau như là, phiên bản, đường dẫn, hay các dịch vụ... Các trường của lớp này là in, out, và err, các trường này tiêu biểu cho xuất, nhập và lỗi chuẩn tương ứng.
Package: java.lang
STT | Method | Desc |
1 |
exit(int) |
Dừng việc thực thi, và trả về giá trị của đoạn mã. 0 cho biết có thể thoát ra một cách bình thường. |
2 |
gc() |
Thực hiện trình thu gom rác Garbage Collection |
3 |
getProperties() |
Trả về thuộc tính được kết hợp với hệ thống thời gian chạy của Java. |
4 |
setProperties() |
Thiết lập các đặc tính hệ thống hiện hành. |
5 |
currentTimeMillis() |
Trả về thời gian hiện tại trong mili giây (ms), được đo lường lúc nửa đêm vào tháng giêng năm 1970. |
6 |
arraycopy(Object, int, Object, int, int) |
Sao chép một mảng. |
Lớp System không thể khai báo để tạo các đối tượng.
Đoạn mã trong chương trình sau truy lục và hiển thị một vài các thuộc tính môi trường liên quan đến Java.
class SystemDemo {
public static void main(String args[]) {
System.out.println(System.getProperty(“java.class.path”));
System.out.println(System.getProperty(“java.home”));
System.out.println(System.getProperty(“java.class.version”));
System.out.println(System.getProperty(“java.specification.vendor”));
System.out.println(System.getProperty(“java.specification.version”));
System.out.println(System.getProperty(“java.vendor”));
System.out.println(System.getProperty(“java.vendor.url”));
System.out.println(System.getProperty(“java.version”));
System.out.println(System.getProperty(“java.vm.name”));
}
}
Mỗi thuộc tính mà được yêu cầu để được in, được cung cấp như một tham số chuỗi đến phương thức System.getProperty()
. Phương thức này lần lượt sẽ trả về thông tin có liên quan đến phương thức System.out.println()
.
Java Class class - Lớp Class trong Java
Các thể hiện của lớp Class đại diện cho các lớp và giao diện (interface) trong một ứng dụng Java đang chạy. Điều này cho phép chúng ta truy cập thông tin về đối tượng trong suốt thời gian chạy.
Package: java.lang
Chúng ta có thể lấy một đối tượng của lớp này, hoặc một minh dụ bằng một trong ba cách sau:
Sử dụng phương thức getChar() trong một đối tượng.
interface A {
final int id = 1;
final String name = "Diana";
}
class B implements A {
int deptno;
}
class ClassDemo {
public static void main(String args[]) {
A a = new B();
B b = new B();
Class x;
x = a.getClass();
System.out.println("a is object of type: " + x.getName());
x = b.getClass();
System.out.println("b is object of type: " + x.getName());
x = x.getSuperclass();
System.out.println(x.getName() + " is the superclass of b.");
}
}
Java Object class - Lớp Object trong Java
Lớp Object là một lớp cha của tất cả các lớp. Dù là một lớp do người dùng định nghĩa không mở rộng bất kỳ một lớp nào khác, theo mặc định nó mở rộng lớp đối tượng.
Package: java.lang
STT | Method | Desc |
equals(Object) | So sánh thể hiện đối tượng hiện tại với đối tượng đã cho, và kiểm tra nếu chúng bằng nhau. | |
finalize() | Mặc định hình thức của phương thức cuối cùng. Thông thường bị phủ bởi lớp con. | |
notify() | Thông báo dòng (thread) mà hiện thời trong trạng thái đang chờ trên màn hình của đối tượng này. | |
notifyAll() | Thông báo tất cả các dòng (thread) hiện hành trong trạng thái chờ trên màn hình của đối tượng này. | |
toString() | Trả về một chuỗi đại diện cho đối tượng. | |
wait() | Tạo ra dòng hiện hành để nhập vào trạng thái đang chờ. |
Trong chương trình sau, chúng ta không khai báo bất kỳ lớp hoặc gói nào. Bây giờ, chúng ta có thể tạo bằng cách sử dụng phương thức equals(). Bởi vì, theo mặc định lớp ObjectDemo mở rộng lớp Object.
class ObjectDemo {
public static void main(String args[]) {
if (args[0].equals(“Aptech”));
System.out.println(“Yes, Aptech is the right choice ! ”);
}
}
Java Hashtable class - Lớp Hashtable trong Java
Lớp Hashtable mở rộng lớp trừu tượng Dictionary, lớp này cũng được định nghĩa trong gói java.util. Hashtable được sử dụng để ánh xạ các khoá đến các giá trị. Ví dụ, nó có thể được sử dụng để ánh xạ các tên đến tuổi, những người lập trình đến những dự án, các tiêu đề công việc đến các lương, và cứ tiếp tục như vậy.
Packge: java.util
Hashtable mở rộng kích thước khi các phần tử được thêm vào. Khi đó việc tạo một bảng băm mới, bạn có thể chỉ định một dung lượng ban đầu và các yếu tố nạp vào. Điều này sẽ làm cho hashtable tăng kích thước lên, bất cứ lúc nào việc thêm vào một phần tử mới sẽ làm thay đổi giới hạn của hashtable cũ. Giới hạn của bảng băm là dung lượng được nhân lên bởi các yếu tố được nạp vào.
Ví dụ: một bảng băm với dung lượng 100, và một yếu tố nạp vào là 0.75 sẽ có một giới hạn là 75 mục.
Các phương thức xây dựng cho bảng băm được biểu diễn trong bảng sau:
STT | Constructor | Purpose |
1 | Hashtable(int) | Xây dựng một bảng mới với dung lượng ban đầu được chỉ định. |
2 | Hashtable(int, float) | Xây dựng một lớp mới với dung lượng ban đầu được chỉ định và yếu tố nạp vào. |
3 | Hashtable() | Xây dựng một lớp mới bằng cách sử dụng giá trị mặc định cho dung lượng ban đầu và yếu tố nạp vào. |
Hashtable hash1 = new Hashtable(500,0,80);
Trong trường hợp này, Bảng băm "hash1" sẽ lưu trữ 500 phần tử. Khi bảng băm lưu trữ vừa đầy 80% (một yếu tố nạp vào của .80), kích thước tối đa của nó sẽ được tăng lên.
Mỗi phần tử trong một hashtable bao gồm một khoá và một giá trị. Các phần tử được thêm vào bảng băm bằng cách sử dụng phương thức put(), và được truy lục bằng cách sử dụng phương thức get(). Các phần tử có thể được xoá từ một bảng băm với phương thức remove(). Các phương thức contains() và containsKey() có thể được sử dụng để tra cứu một giá trị hoặc một khoá trong bảng băm.
Một vài phương thức của Hashtable được tóm tắt trong bảng sau:
STT | Method | Desc |
1 |
clear() |
Xoá tất cả các phần tử từ bảng băm. |
2 |
clone() |
Tạo một bảng sao của Hashtable. |
3 |
contains(Object) |
Trả về True nếu bảng băm chứa các đối tượng được chỉ định. |
4 |
containsKey(Object) |
Trả về True nếu bảng băm chứa khoá được chỉ định. |
5 |
elements() |
Trả về một bảng liệt kê các yếu tố trong bảng băm. |
6 |
get(Object key) |
Truy xuất phần tử với khoá được chỉ định. |
7 |
isEmpty() |
Trả về true nếu bảng băm trống. |
8 |
keys() |
Trả về một bảng liệt kê các khoá trong bảng băm. |
9 |
put(Object, Object) |
Thêm một phần tử mới vào bảng băm bằng cách sử dụng khoá và giá trị được chỉ định. |
10 | rehash() |
Thay đổi bảng băm thành một bảng băm lớn hơn. |
11 | remove(Object key) | Xoá một đối tượng được cho bởi khoá được chỉ định. |
12 | size() | Trả về số phần tử trong bảng băm. |
13 | toString() | Trả về đại diện chuỗi được định dạng cho bảng băm. |
Chương trình sau sử dụng lớp Hashtable. Trong chương trình này, tên của các tập ảnh là các khoá, và các năm là các phần tử
-
contains
được sử dụng để tra cứu phần tử nguyên 1969, để thấy có danh sách chứa bất kỳ các tập ảnh từ 1969. -
containsKey
được sử dụng để tìm kiếm cho khoá "Animals", để nhìn thấy nếu tập ảnh đó tạo nên danh sách. -
get()
được sử dụng để truy lục tập ảnh "Wish You Were Here" có trong bảng băm không. Phương thức get() trả về phần tử kết hợp với khoá, cả hai tên và năm được hiển thị tại điểm này.
import java.util. * ;
public class HashTableImplementer {
public static void main(String args[]) {
//tạo một bảng băm mới
Hashtable ht = new Hashtable();
//thêm các tập ảnh tốt nhất của Pink Floyd
ht.put("Pulse", new Integer(1995));
ht.put("Dark Side of the Moon", new Integer(1973));
ht.put("Wish You Were Here", new Integer(1975));
ht.put("Animals", new Integer(1997));
ht.put("Ummagumma", new Integer(1969));
//Hiển thị bảng băm
System.out.println("Initailly: " + ht.toString());
//kiểm tra cho bất kỳ tập ảnh nào từ 1969
if (ht.contains(new Integer(1969)))
System.out.println("An album from 1969 exists");
//kiểm tra cho tập ảnh các con thú
if (ht.containsKey("Animals"));
System.out.println("Animals was found");
//Tìm ra
Integer year = (Integer) ht.get("Wish You Were Here");
System.out.println("Wish you Were Here was released in " + year.toString());
//Xoá một tập ảnh
System.out.println("Removing Ummagumma\r\n");
ht.remove(“Ummagumma”);
//Di chuyển thông qua một bảng liệt kê của tất cả các khoá trong bảng.
System.out.println("Remaining: \r\n");
for (Enumeration enum = ht.keys(); enum.hasMoreElements();)
System.out.println((String) enum.nextElement());
}
}
Java Random class - Lớp Random trong Java
Lớp này đại diện một bộ tạo số ngẫu nhiên (pseudo-random).
Package: java.util
STT | Method | Desc |
1 |
random() |
Tạo ra một bộ tạo số ngẫu nhiên mới |
2 |
random(long) |
Tạo ra một bộ tạo số ngẫu nhiên mới dựa trên giá trị khởi tạo được chỉ định. |
3 |
nextDouble() |
Trả về một giá trị kiểu double kế tiếp giữa 0.0D đến 1.0D từ bộ tạo số ngẫu nhiên. |
4 |
nextFloat() |
Trả về một giá trị kiểu float kế tiếp giữa 0.0F và 1.0F từ bộ tạo số ngẫu nhiên. |
5 |
nextGaussian() |
Trả về kiểu double được phân phối Gaussian kế tiếp từ bộ tạo số ngẫu nhiên. Tạo ra các giá trị Gaussian sẽ có một giá trị trung bình của 0, và một độ lệch tiêu chuẩn của 1.0. |
6 |
nextInt() |
Trả về giá trị kiểu Integer kế tiếp từ một bộ tạo số ngẫu nhiên. |
7 |
nextLong() |
Trả về giá trị kiểu long kế tiếp từ một bộ tạo số ngẫu nhiên. |
8 |
setSeed(long) |
Thiết lập giá trị khởi tạo từ bộ tạo số ngẫu nhiên. |
Java Vector class - Lớp Vector trong Java
Một trong các vấn đề với một mảng là chúng ta phải biết nó lớn như thế nào khi chúng ta tạo nó. Nó thì không thể xác định kích thước của mảng trước khi tạo nó.
Lớp Vector của Java giải quyết vấn đề này. Nó cung cấp một dạng mảng với kích thước ban đầu, mảng này có thể tăng thêm khi nhiều phần tử được thêm vào. Một lớp Vector lưu trữ các item của kiểu Object, nó có thể dùng để lưu trữ các thể hiện của bất kỳ lớp Java nào. Một lớp Vector đơn lẻ có thể lưu trữ các phần tử khác nhau, các phần tử khác nhau này là thể hiện của các lớp khác nhau.
Package: java.util
Tại bất kỳ thời điểm, một lớp Vector có dung lượng để lưu trữ một số nào đó của các phần tử. Khi một lớp Vector biết về dung lượng của nó, thì dung lượng của nó được gia tăng bởi một số lượng riêng cho Vector đó. Lớp Vector cung cấp ba phương thức xây dựng khác nhau mà có thể chúng ta chỉ định dung lượng khởi tạo, và tăng số lượng của một Vector, khi nó được tạo ra.
Các phương thức khởi tạo của lớp Vector được tóm tắt trong bảng sau:
STT |
Constructor |
Desc |
1 |
Vector(int) |
Tạo ra một lớp Vector mới với dung lượng ban đẩu được chỉ định. |
2 |
Vector(int, int) |
Tạo ra một lớp Vector mới với dung lượng ban đầu được chỉ định, và tăng số lượng. |
3 |
Vector() |
Tạo ra một lớp Vector mới với dung lượng khởi tạo mặc định, và tăng số lượng. |
Một mục (item) được thêm vào một lớp Vector bằng cách sử dụng hàm addElement(). Tương tự, một phần tử có thể được thay thế bằng cách sử dụng hàm setElementAt(). Một lớp Vector có thể tìm kiếm bằng cách sử dụng phương thức contains(), phương thức này trông có vẻ dễ dàng cho một lần xuất hiện của một đối tượng (Object). Phương thức elements() thì hữu dụng bởi vì nó trả về một bảng liệt kê của các đối tượng được lưu trữ trong lớp Vector.
Các phương thức này và các phương thức thành viên khác của lớp Vector được tóm tắt trong bảng dưới đây:
STT | Method | Desc |
1 |
addElement(Object) |
Chèn các phần tử được chỉ định vào lớp Vector. |
2 |
capacity() |
Trả về số phần tử mà sẽ vừa đủ cho phần được cấp phát hiện thời của lớp Vector. |
3 |
clone() |
Tạo bản sao của vector, nhưng không phải là các phần tử của nó. |
4 |
contains(Object) |
Trả về True nếu lớp Vector chứa đối tượng được chỉ định. |
5 |
copyInto(Object []) |
Sao chép các phần tử của lớp Vector vào mảng được chỉ định. |
6 |
elementAt(int) |
Truy lục phần tử được cấp phát tại chỉ mục được chỉ định. |
7 |
elements() |
Trả về một bảng liệt kê của các phần tử trong lớp Vector. |
8 |
ensureCapacity(int) |
Chắc chắn rằng lớp Vector có thể lưu trữ ít nhất dunglượng tối thiểu được chỉ định. |
9 |
firstElement() |
Trả về phần tử đầu tiên trong lớp Vector. |
10 |
indexOf(Object) |
Tìm kiếm lớp Vector, và trả về chỉ mục zero cơ bản cho khớp với đối tượng đầu tiên. |
11 |
indexOf(Object, int) |
Tìm kiếm lớp Vector đang bắt đầu tại số chỉ mục được chỉ định, và trả về chỉ mục zero cơ bản cho khớp với đối tượng kế tiếp. |
12 |
insertElementAt(Object, int) |
Thêm các đối tượng được chỉ định tại chỉ mục được chỉ định. |
13 |
isEmpty() |
Trả về True nếu lớp Vector không có phần tử. |
14 |
lastElement() |
Trả về phần tử cuối cùng trong lớp Vector. |
15 |
lastIndexOf(Object) |
Tìm kiếm lóp Vector, và trả về chỉ mục zero cơ bản cho khớp với đối tượng cuối cùng. |
16 |
lastIndexOf(Object, int) |
Tìm kiếm lớp Vector đang bắt đầu tại số chỉ mục được chỉ định, và trả về chỉ mục zero cơ bản cho khớp với đối tượng trước. |
17 |
removeAllElements() |
Xoá tất cả các phần tử từ lớp Vector. |
18 |
removeElement(Object) |
Xoá đối tượng được chỉ định từ lớp Vector. |
19 |
removeElementAt(int) |
Xoá đối tượng tại chỉ mục được chỉ định. |
20 |
setElementAt(Object, int) |
Thay thế đối tượng tại chỉ mục được chỉ định với đối tượng được chỉ định. |
21 |
setSize(int) |
Thiết lập kích thước của lớp Vector thành kích thước mới được chỉ định. |
22 |
setSize(int) |
Thiết lập kích thước của lớp Vector thành kích thước mới được chỉ định. |
23 |
size() |
Trả về số của các phần tử hiện thời trong lớp Vector. |
24 |
toString() |
Trả về một đại diện chuỗi được định dạng nội dung của lớp Vector. |
25 |
trimToSize() |
Định lại kích thước của lớp Vector để di chuyển dung lượng thừa trong nó. |
Chương trình sau tạo ra một lớp Vector "vect". Nó chứa 6 phần tử: "Numbers In Words", "One", "Two", "Three", "Four", "Five". Phương thức removeElement() được sử dụng để xoá các phần tử từ "vect".
import java.util. * ;
public class VectorImplementation {
public static void main(String args[]) {
Vector vect = new Vector();
vect.addElement("One");
vect.addElement("Two");
vect.addElement("Three");
vect.addElement("Four");
vect.addElement("Five");
vect.insertElementAt("Numbers In Words", 0);
vect.insertElementAt("Four", 4);
System.out.println("Size: " + vect.size());
System.out.println("Vector");
for (int i = 0; i < vect.size(); i++) {
System.out.println(vect.elementAt(i) + ", ");
}
vect.removeElement("Five");
System.out.println("");
System.out.println("Size: " + vect.size());
System.out.println("Vector");
for (int i = 0; i < vect.size(); i++) {
System.out.print(vect.elementAt(i) + ", ");
}
}
}
Java StringTokenizer class - Lớp StringTokenizer trong Java
Lớp StringTokenizer
được sử dụng để tách một chuỗi thành các phần tử token của nó.
Package: java.util
Ví dụ: Mỗi từ trong một câu có thể coi như là một token.
Lớp StringTokenizer có thể chỉ định một bộ dấu phân tách token. Dấu phân cách (khoảng trắng) là ký tự phân tách mặc định, tuy nhiên, chúng ta có thể sử dụng tập các toán tử toán học (+, *, /, và -) trong khi phân tách một biểu thức. Các ký tự phân tách có thể chỉ định khi một đối tượng StringTokenizer mới được khởi tạo.
Các phương thức khởi tạo
# |
Phương thức |
Mục đích |
1 |
StringTokenizer(String) |
Tạo ra một lớp StringTokenizer mới dựa trên chuỗi chỉ định được thông báo. |
2 |
StringTokenizer |
Tạo ra một lớp StringTokenizer mới dựa trên (String, String) chuỗi chỉ định được thông báo, và một tập các dấu phân cách. |
3 |
StringTokenizer(String, String, Boolean) |
Tạo ra một lớp StringTokenizer dựa trên chuỗi chỉ định được thông báo, một tập các dấu phân cách, và một cờ hiệu cho biết nếu các dấu phân cách sẽ được trả về như các token. |
Các phương thức của lớp StringTokenizer
# |
Phương thức |
Mục đích |
1 |
countTokens() |
Trả về số các token còn lại. |
2 |
hasMoreElements() |
Trả về True nếu nhiều phần tử đang được đánh dấu trong chuỗi. Nó thì giống hệt như hasMoreTokens. |
3 |
hasMoreTokens() |
Trả về True nếu nhiều tokens đang được đánh dấu trong chuỗi. Nó thì giống hệt như hasMoreElements. |
4 |
nextElement() |
Trả về phần tử kế tiếp trong chuỗi. Nó thì giống như nextToken. |
5 |
nextToken() |
Trả về Token kế tiếp trong chuỗi. Nó thì giống như nextElement. |
6 |
nextToken() |
Thay đổi bộ dấu phân cách đến chuỗi được chỉ định, và sau đó trả về token kế tiếp trong chuỗi. |
Ví dụ:
import java.util. * ;
public class StringTokenizerImplementer {
public static void main(String args[]) {
// đặt một biểu thức toán học trong một chuỗi và tạo một tokenizer cho chuỗi đó. String mathExpr = “4*3+2/4”;
StringTokenizer st1 = new StringTokenizer(mathExpr, ” * +/-“, true);
/ / trong khi cócác token trái, hãy hiển thịmỗi token System.out.println(“Tokens f mathExpr: “);
while (st1.hasMoreTokens()) System.out.println(st1.nextToken());
//tạo một chuỗi của các trường được phân cách bởi dấu phẩy và tạo một tokenizer cho chuỗi.
String commas = “field1, field2, field3, and field4”;
StringTokenizer st2 = new StringTokenizer(commas, ”, ”, false); //trong khi có các token trái, hãy hiển thị mỗi token. System.out.println(“Comma-delimited tokens : “);
while (st2.hasMoreTokens()) System.out.println(st2.nextToken());
}
}
Java Design Pattern
Design Pattern là giải pháp kỹ thuật thiết kế chương trình để giải quyết tối ưu các vấn đề chung, thường gặp trong lập trình.
Java Design Pattern overview - Mẫu thiết kế trong Java
Design Pattern là giải pháp kỹ thuật thiết kế (hay còn gọi là mẫu thiết kế) chương trình để giải quyết tối ưu các vấn đề chung, thường gặp trong lập trình.
1. Design Pattern là gì
Design patterns là các giải pháp đã được tối ưu hóa, được tái sử dụng cho các vấn đề lập trình mà chúng ta gặp phải hàng ngày. Nó là một khuôn mẫu có thể được áp dụng vào mỗi trường hợp cụ thể.
Các vấn đề mà bạn gặp phải có thể bạn sẽ tự nghĩ ra cách giải quyết nhưng có thể nó chưa phải là tối ưu. Design Pattern giúp bạn giải quyết vấn đề một cách tối ưu nhất, cung cấp cho bạn các giải pháp trong lập trình OOP. Nó không phải là ngôn ngữ cụ thể nào cả. Design patterns có thể thực hiện được ở phần lớn các ngôn ngữ lập trình. Ta thường gặp nó nhất trong lập trình OOP.
Để xây dựng và áp dụng Design Pattern, cần nắm rõ các kiến thức sau:
- Bốn đặc tính của OOP: Thừa kế, Đa hình, Trừu tượng, Bao đóng.
- Khái niệm interface và abstract. Cái này cực kỳ quan trọng, để hiểu và áp dụng 2 khái niệm này có thể sẽ mất một thời gian, nhưng khi bạn nắm chắc nó bạn sẽ thấy nó thực sự cần thiết.
2. Lợi ích của Design Pattern
- Các mẫu thiết kế đã được xác định và cung cấp cách tiếp cận chuẩn theo ngành để giải quyết vấn đề lặp lại, vì vậy sẽ tiết kiệm được thời gian nếu chúng ta sử dụng mẫu thiết kế hợp lý.
- Sử dụng các mẫu thiết kế thúc đẩy khả năng tái sử dụng dẫn đến phát triển phần mềm nhanh hơn. Nó giúp giảm tổng chi phí của sản phẩm phần mềm.
- Khi áp dụng 1 mẫu thiết kế, nó làm cho code dễ hiểu và gỡ lỗi.
- Design Pattern giúp bạn tái sử dụng mã lệnh và dẽ dàng mở rộng.
- Nó là tập hơn những giải pháp đã được tối ưu hóa, đã được kiểm chứng để giải quyết các vấn đề trong software engineering. Vậy khi bạn gặp bất kỳ khó khăn gì, design patterns là kim chỉ nam giúp bạn giải quyết vấn đề thay vì tự tìm kiếm giải pháp cho một vấn đề đã được chứng minh.
- Design pattern cung cấp giải pháp ở dạng tổng quát, giúp tăng tốc độ phát triển phần mềm bằng cách đưa ra các mô hình test, mô hình phát triển đã qua kiểm nghiệm.
- Dùng lại các design pattern giúp tránh được các vấn đề tiềm ẩn có thể gây ra những lỗi lớn, dễ dàng nâng cấp, bảo trì về sau.
- Giúp cho các lập trình viên có thể hiểu code của người khác 1 cách nhanh chóng (có thể hiểu là tính communicate).
- Mọi thành viên trong team có thể dễ dàng trao đổi với nhau để cùng xây dựng dự án mà k mất quá nhiều thời gian.
3. Các loại Design Pattern
Năm 1994, bốn tác giả Erich Gamma, Richard Helm, Ralph Johnson và John Vlissides đã cho xuất bản một cuốn sách với tiêu đề Design Patterns – Elements of Reusable Object-Oriented Software, đây là khởi nguồn của khái niệm design pattern trong lập trình phần mềm.
Bốn tác giả trên được biết đến rộng rãi dưới tên Gang of Four (bộ tứ). Theo quan điểm của bốn người, design pattern chủ yếu được dựa theo những quy tắc sau đây về thiết kế hướng đối tượng.
Có 4 loại Design Pattern sau:
TT | Design Pattern | Mô tả | Các loại |
---|---|---|---|
1 | Creational Patterns | Nhóm này cung cấp phương pháp tạo ra các đối tượng một cách linh hoạt hơn. Nghĩa là quyết định đối tượng nào được tạo ra tuỳ thuộc vào trường hợp sử dụng nhất định. | 5 mẫu: Factory Method, Abstract Factory, Builder, Prototype, Singleton |
2 | Structural Pattern | Nhóm này liên quan đến sự kết hợp giữa các đối tượng với nhau | 7 mẫu: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy |
3 | Behavioral Patterns | Mẫu thiết kế này trình bày phương pháp thiết kế liên quan đến hành vi của các đối tượng. | 11 mẫu: Interpreter, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Visitor |
4 | J2EE Pattern | Nhóm này cung cấp phương pháp thiết kế chương trình theo mô hình nhiều tầng (multiple tier) |
Hình dưới là mối quan hệ giữa 23 Design Pattern cơ bản:
3.1. Nhóm Creational (nhóm khởi tạo)
Nhóm này cung cấp phương pháp tạo ra các đối tượng một cách linh hoạt hơn. Nghĩa là quyết định đối tượng nào được tạo ra tuỳ thuộc vào trường hợp sử dụng nhất định.
-
Singleton:
- Đảm bảo 1 class chỉ có 1 instance và cung cấp 1 điểm truy xuất toàn cục đến nó.
- Tần suất sử dụng: cao trung bình.
-
Abstract Factory:
- Cung cấp một interface cho việc tạo lập các đối tượng (có liên hệ với nhau) mà không cần qui định lớp khi hay xác định lớp cụ thể (concrete) tạo mỗi đối tượng.
- Tần suất sử dụng: cao.
-
Factory Method:
- Định nghĩa Interface để sinh ra đối tượng nhưng để cho lớp con quyết định lớp nào được dùng để sinh ra đối tượng Factory method cho phép một lớp chuyển quá trình khởi tạo đối tượng cho lớp con.
- Tần suất sử dụng: cao.
-
Builder:
- Tách rời việc xây dựng (construction) một đối tượng phức tạp khỏi biểu diễn của nó sao cho cùng một tiến trình xây dựng có thể tạo được các biểu diễn khác nhau.
- Tần suất sử dụng: trung bình thấp.
-
Prototype:
- Qui định loại của các đối tượng cần tạo bằng cách dùng một đối tượng mẫu, tạo mới nhờ vào sao chép đối tượng mẫu này.
- Tần suất sử dụng: trung bình.
3.2. Nhóm Structural (nhóm cấu trúc)
Nhóm này liên quan đến sự kết hợp giữa các đối tượng với nhau.
-
Adapter:
- Do vấn đề tương thích, thay đổi interface của một lớp thành một interface khác phù hợp với yêu cầu người sử dụng lớp.
- Tần suất sử dụng: cao trung bình.
-
Bridge:
- Tách rời ngữ nghĩa của một vấn đề khỏi việc cài đặt, mục đích để cả hai bộ phận (ngữ nghĩa và cài đặt) có thể thay đổi độc lập nhau.
- Tần suất sử dụng: trung bình.
-
Composite:
- Tổ chức các đối tượng theo cấu trúc phân cấp dạng cây. Tất cả các đối tượng trong cấu trúc được thao tác theo một cách thuần nhất như nhau.
Tạo quan hệ thứ bậc bao gộp giữa các đối tượng. Client có thể xem đối tượng bao gộp và bị bao gộp như nhau -> khả năng tổng quát hoá trong code của client -> dễ phát triển, nâng cấp, bảo trì. - Tần suất sử dụng: cao trung bình.
- Tổ chức các đối tượng theo cấu trúc phân cấp dạng cây. Tất cả các đối tượng trong cấu trúc được thao tác theo một cách thuần nhất như nhau.
-
Decorator:
- Gán thêm trách nhiệm cho đối tượng (mở rộng chức năng) vào lúc chạy (dynamically).
- Tần suất sử dụng:trung bình.
-
Facade:
- Cung cấp một interface thuần nhất cho một tập hợp các interface trong một “hệ thống con” (subsystem). Nó định nghĩa 1 interface cao hơn các interface có sẵn để làm cho hệ thống con dễ sử dụng hơn.
- Tần suất sử dụng: cao.
-
Flyweight:
- Sử dụng việc chia sẻ để thao tác hiệu quả trên một số lượng lớn đối tượng “cở nhỏ” (chẳng hạn paragraph, dòng, cột, ký tự…).
- Tần suất sử dụng: thấp.
-
Proxy:
- Cung cấp đối tượng đại diện cho một đối tượng khác để hỗ trợ hoặc kiểm soát quá trình truy xuất đối tượng đó. Đối tượng thay thế gọi là proxy.
- Tần suất sử dụng: cao trung bình.
3.3. Nhóm Behavioral (nhóm hành vi/ tương tác)
Mẫu thiết kế này trình bày phương pháp thiết kế liên quan đến hành vi của các đối tượng.
-
Chain of Responsibility:
- Khắc phục việc ghép cặp giữa bộ gởi và bộ nhận thông điệp. Các đối tượng nhận thông điệp được kết nối thành một chuỗi và thông điệp được chuyển dọc theo chuỗi nầy đến khi gặp được đối tượng xử lý nó. Tránh việc gắn kết cứng giữa phần tử gởi request với phần tử nhận và xử lý request bằng cách cho phép hơn 1 đối tượng có có cơ hội xử lý request. Liên kết các đối tượng nhận request thành 1 dây chuyền rồi gửi request xuyên qua từng đối tượng xử lý đến khi gặp đối tượng xử lý cụ thể.
- Tần suất sử dụng: trung bình thấp.
-
Command:
- Mỗi yêu cầu (thực hiện một thao tác nào đó) được bao bọc thành một đối tượng. Các yêu cầu sẽ được lưu trữ và gởi đi như các đối tượng.Đóng gói request vào trong một Object, nhờ đó có thể nthông số hoá chương trình nhận request và thực hiện các thao tác trên request: sắp xếp, log, undo…
- Tần suất sử dụng: cao trung bình.
-
Interpreter:
- Hỗ trợ việc định nghĩa biểu diễn văn phạm và bộ thông dịch cho một ngôn ngữ.
- Tần suất sử dụng: thấp.
-
Iterator:
- Truy xuất các phần tử của đối tượng dạng tập hợp tuần tự (list, array, …) mà không phụ thuộc vào biểu diễn bên trong của các phần tử.
- Tần suất sử dụng: cao.
-
Mediator:
- Định nghĩa một đối tượng để bao bọc việc giao tiếp giữa một số đối tượng với nhau.
- Tần suất sử dụng: trung bình thấp.
-
Memento:
- Hiệu chỉnh và trả lại như cũ trạng thái bên trong của đối tượng mà vẫn không vi phạm việc bao bọc dữ liệu.
- Tần suất sử dụng: thấp.
-
Observer:
- Định nghĩa sự phụ thuộc một-nhiều giữa các đối tượng sao cho khi một đối tượng thay đổi trạng thái thì tất cả các đối tượng phụ thuộc nó cũng thay đổi theo.
- Tần suất sử dụng: cao.
-
State:
- Cho phép một đối tượng thay đổi hành vi khi trạng thái bên trong của nó thay đổi, ta có cảm giác như class của đối tượng bị thay đổi.
- Tần suất sử dụng: trung bình.
-
Strategy:
- Bao bọc một họ các thuật toán bằng các lớp đối tượng để thuật toán có thể thay đổi độc lập đối với chương trình sử dụng thuật toán.Cung cấp một họ giải thuật cho phép client chọn lựa linh động một giải thuật cụ thể khi sử dụng.
- Tần suất sử dụng: cao trung bình.
-
Template method:
- Định nghĩa phần khung của một thuật toán, tức là một thuật toán tổng quát gọi đến một số phương thức chưa được cài đặt trong lớp cơ sở; việc cài đặt các phương thức được ủy nhiệm cho các lớp kế thừa.
- Tần suất sử dụng: trung bình.
-
Visitor:
- Cho phép định nghĩa thêm phép toán mới tác động lên các phần tử của một cấu trúc đối tượng mà không cần thay đổi các lớp định nghĩa cấu trúc đó.
- Tần suất sử dụng: thấp.
Java Singleton Pattern - Mẫu thiết kế Singleton trong Java
Singleton là một Design Pattern cho phép bạn đảm bảo rằng một lớp chỉ có một thể hiện, và cung cấp một truy xuất toàn cục cho thể hiện này.
1. Tại sao cần có Singleton Pattern
Đôi khi, trong quá trình phân tích thiết kế một hệ thống, chúng ta mong muốn có những đối tượng cần tồn tại duy nhất và có thể truy xuất mọi lúc mọi nơi. Làm thế nào để hiện thực được một đối tượng như thế khi xây dựng mã nguồn? Chúng ta có thể nghĩ tới việc sử dụng một biến toàn cục (global variable: public static final). Tuy nhiên, việc sử dụng biến toàn cục nó phá vỡ quy tắc của OOP (encapsulation). Để giải bài toán trên, người ta hướng đến một giải pháp là sử dụng Singleton pattern.
Singleton là 1 trong 5 design pattern của nhóm Creational Design Pattern.
Singleton đảm bảo chỉ duy nhất một thể hiện (instance) được tạo ra và nó sẽ cung cấp cho bạn một method để có thể truy xuất được thể hiện duy nhất đó mọi lúc mọi nơi trong chương trình.
Sử dụng Singleton khi chúng ta muốn:
- Đảm bảo rằng chỉ có một instance của lớp.
- Việc quản lý việc truy cập tốt hơn vì chỉ có một thể hiện duy nhất.
- Có thể quản lý số lượng thể hiện của một lớp trong giớn hạn chỉ định.
Một số trường hợp sử dụng của Singleton Pattern thường gặp:
- Vì class dùng Singleton chỉ tồn tại 1 Instance (thể hiện) nên nó thường được dùng cho các trường hợp giải quyết các bài toán cần truy cập vào các ứng dụng như: Database, Shared resource, Logger, Configuration, Caching, Thread pool…
- Một số design pattern khác cũng sử dụng Singleton để triển khai: Abstract Factory, Builder, Prototype, Facade,…
- Đã được sử dụng trong một số class của core java như: java.lang.Runtime, java.awt.Desktop.
2. Thực thi Singleton Pattern
Có rất nhiều cách để implement Singleton Pattern bằng các dựa trên nguyên tắc dưới đây cơ bản dưới đây:
- Khai báo private constructor để hạn chế truy cập từ class bên ngoài.
- Khai báo private static final variable đảm bảo biến chỉ được khởi tạo trong class.
- Khai báo một method public static để return instance được khởi tạo ở trên.
2.1. Eager initialization
Singleton Class được khởi tạo ngay khi được gọi đến. Đây là cách dễ nhất nhưng nó có một nhược điểm mặc dù instance đã được khởi tạo mà có thể sẽ không dùng tới.
public class SingleObject {
//create an object of SingleObject
private static SingleObject instance = new SingleObject();
//make the constructor private so that this class cannot be
//instantiated
private SingleObject(){}
//Get the only object available
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
public class SingletonPatternDemo {
public static void main(String[] args) {
//illegal construct
//Compile Time Error: The constructor SingleObject() is not visible
//SingleObject object = new SingleObject();
//Get the only object available
SingleObject object = SingleObject.getInstance();
//show the message
object.showMessage();
}
}
2.2. Static block initialization
Vì static block
sẽ chỉ được gọi một lần, chúng ta có thể sử dụng static block
để phát triển lớp singleton. Dưới đây ví dụ cho thấy cách tạo các lớp singleton bằng cách sử dụng static block
:
- Để tạo lớp singleton, hãy tạo constructor thành private, để bạn không thể tạo đối tượng bên ngoài lớp.
- Tạo một biến private static có cùng loại class, để đối tượng được tạo sẽ được trỏ đến tham chiếu này. Bây giờ tạo
static block
và tạo đối tượng bên trongstatic block
. Vìstatic block
sẽ chỉ được gọi một lần, nên đối tượng sẽ chỉ được tạo một lần.
public class MyStaticSingleton {
public static void main(String a[]){
MySingleton ms = MySingleton.getInstance();
ms.testSingleton();
}
}
class MySingleton{
private static MySingleton instance;
static{
instance = new MySingleton();
}
private MySingleton(){
System.out.println("Creating MySingleton object...");
}
public static MySingleton getInstance(){
return instance;
}
public void testSingleton(){
System.out.println("Hey.... Instance got created...");
}
}
Output:
Creating MySingleton object...
Hey.... Instance got created...
2.3. Lazy Initialization
Là một cách làm mang tính mở rộng hơn so với 2 cách làm trên và hoạt động tốt trong môi trường đơn luồng (single-thread).
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton() {
}
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
}
- Ưu điểm: Cách này đã khắc phục được nhược điểm của cách Eager initialization, chỉ khi nào getInstance() được gọi thì instance mới được khởi tạo.
- Nhược điểm:
- Tuy nhiên, cách này chỉ sử dụng tốt trong trường hợp đơn luồng (single-thread), trường hợp nếu có nhiều luồng (multi-thread) cùng chạy và cùng gọi hàm getInstance() tại cùng một thời điểm thì có thể có nhiều hơn 1 thể hiện của instance. Để khắc phục nhược điểm này chúng ta sử dụng Thread Safe Singleton.
- Một nhược điểm nữa của Lazy Initialization cần quan tâm là: đối với thao tác create instance quá chậm thì người dùng có phải chờ lâu cho lần sử dụng đầu tiên.
2.4. Thread Safe Singleton
Để đảm bảo khi khởi tạo Singletong trong Thread, chúng ta có thể sử dụng phương thức synchronized trong hàm getInstance() và như vậy hệ thống đảm bảo rằng tại cùng một thời điểm chỉ có thể có 1 luồng có thể truy cập vào hàm getInstance() và đảm bảo rằng chỉ có duy nhất 1 thể hiện của class.
// Java program to create Thread Safe
// Singleton class
public class GFG {
// private instance, so that it can be
// accessed by only by getInstance() method
private static GFG instance;
private GFG() {
// private constructor
}
//synchronized method to control simultaneous access
synchronized public static GFG getInstance() {
if (instance == null) {
// if instance is null, initialize
instance = new GFG();
}
return instance;
}
}
Biến
volatile
trong Java có tác dụng thông báo sự thay đổi giá trị của biến tới các thread khác nhau nếu biến này đang được sử dụng trong nhiều thread.
Cách này có nhược điểm là một phương thức synchronized sẽ chạy rất chậm và tốn hiệu năng, bất kỳ Thread nào gọi đến đều phải chờ nếu có một Thread khác đang sử dụng. Có những tác vụ xử lý trước và sau khi tạo thể hiện không cần thiết phải block. Vì vậy chúng ta cần cải tiến nó đi 1 chút với Lazy initialization with Double check locking.
2.5. Lazy initialization with Double check locking
Để implement theo cách này, chúng ta sẽ kiểm tra sự tồn tại thể hiện của lớp, với sự hổ trợ của đồng bộ hóa, hai lần trước khi khởi tạo. Phải khai báo volatile cho instance để tránh lớp làm việc không chính xác do quá trình tối ưu hóa của trình biên dịch.
// Java code to explain double check locking
public class GFG {
// private instance, so that it can be
// accessed by only by getInstance() method
private static volatile GFG instance;
private GFG() {
// private constructor
}
public static GFG getInstance() {
if (instance == null) {
//synchronized block to remove overhead
synchronized(GFG.class) {
if (instance == null) {
// if instance is null, initialize
instance = new GFG();
}
}
}
return instance;
}
}
2.6. Bill Pugh Singleton Implementation
Trước Java5, mô hình bộ nhớ có rất nhiều vấn đề và các phương thức trên gây ra lỗi trong các kịch bản nhất định trong môi trường đa luồng. Vì vậy, Bill Pugh đã đề xuất một khái niệm về các lớp inner static để sử dụng cho singleton.
// Java code for Bill Pugh Singleton Implementaion
public class GFG {
private GFG() {
// private constructor
}
// Inner class to provide instance of class
private static class BillPughSingleton {
private static final GFG INSTANCE = new GFG();
}
public static GFG getInstance() {
return BillPughSingleton.INSTANCE;
}
}
Khi lớp singleton được load, lớp bên trong không được load và do đó không tạo đối tượng khi load class. Inner class chỉ được tạo khi phương thức getInstance() được gọi. Vì vậy, nó có vẻ giống như eager initialization nhưng đó là lazy initialization.
Đây là cách tiếp cận được sử dụng rộng rãi nhất vì nó không sử dụng đồng bộ hóa.
2.7. Enum Singleton
Khi dùng enum thì các params chỉ được khởi tạo 1 lần duy nhất, đây cũng là cách giúp bạn tạo ra Singleton instance.
/**
* Singleton implementation using enum initialization
*/
public enum EnumSingleton {
INSTANCE;
}
Lưu ý:
- Enum có thể sử dụng như một Singleton, nhưng nó có nhược điểm là không thể extends từ một lớp được, nên khi sử dụng cần xem xét vấn đề này.
- Hàm constructor của enum là lazy, nghĩa là khi được sử dụng mới chạy hàm khởi tạo và nó chỉ chạy duy nhất một lần. Nếu muốn sử dụng như một eager singleton thì cần gọi thực thi trong một static block khi start chương trình.
- So sánh giữa 2 cách sử dụng enum initialization và static block initialization method, enum có một điểm rất mạnh khi giải quyết về vấn đề Serialization/ Deserialization.
3. Tổng kết
-
Eager initialization
là cách dễ dàng nhất để thực thi Singleton nhưng nó có nhược điểm khi instance được khởi tạo mà có thể không được sử dụng. - Sử dụng
Static block
trong Eager initialization, chúng ta có thể cung cấp xử lý ngoại lệ và kiểm soát được instance. - Sử dụng
synchronized
chúng ta có thể tạo Singleton class trong môi trường đa luồng multi-threading nhưng nó có thể gây chậm chương trình, một cách tối ưu hơn là sử dụng cơ chế Double check locking. -
Bill Pugh
là phương pháp được sử dụng rộng rãi nhất trong thực thi Singleton do việc khai báo đơn giản và có nhiều ưu điểm.
Java Abstract Factory Pattern - Mẫu thiết kế Abstract Factory trong Java
Abstract Factory cung cấp một interface cho việc tạo lập các đối tượng (có liên hệ với nhau) mà không cần qui định lớp khi hay xác định lớp cụ thể (concrete) tạo mỗi đối tượng.
1. Tại sao cần có Abstract Factory Pattern
Hãy tưởng tượng, Abstract factory như là một nhà máy lớn chứa nhiều nhà máy nhỏ, trong các nhà máy đó có những xưởng sản xuất, các xưởng đó tạo ra những sản phẩm khác nhau.
- Cung cấp hướng tiếp cận với Interface thay thì các implement, che giấu sự phức tạp của việc khởi tạo các đối tượng với người dùng (client), độc lập giữa việc khởi tạo đối tượng và hệ thống sử dụng, …
- Giúp tránh được việc sử dụng điều kiện logic bên trong Factory Pattern. Khi một Factory Method lớn (có quá nhiều sử lý if-else hay switch-case), chúng ta nên sử dụng theo mô hình Abstract Factory để dễ quản lý hơn (cách phân chia có thể là gom nhóm các sub-class cùng loại vào một Factory).
- Abstract Factory Pattern là factory của các factory, có thể dễ dạng mở rộng để chứa thêm các factory và các sub-class khác.
- Dễ dàng xây dựng một hệ thống đóng gói (encapsulate): sử dụng được với nhiều nhóm đối tượng (factory) và tạo nhiều product khác nhau.
2. Thực thi Abstract Factory Pattern
Một Abstract Factory Pattern bao gồm các thành phần cơ bản sau:
- AbstractFactory: Khai báo dạng interface hoặc abstract class chứa các phương thức để tạo ra các đối tượng abstract.
- ConcreteFactory: Xây dựng, cài đặt các phương thức tạo các đối tượng cụ thể.
- AbstractProduct: Khai báo dạng interface hoặc abstract class để định nghĩa đối tượng abstract.
- Product: Cài đặt của các đối tượng cụ thể, cài đặt các phương thức được quy định tại AbstractProduct.
- Client: là đối tượng sử dụng AbstractFactory và các AbstractProduct.
Ví du: Chúng ta có các nhà máy sản xuất Ô tô được đặt tại IndiaCarFactory, USACarFactory và DefaultCarFactory. Và chương trình của chúng ta cần đủ thông minh để xác định được vị trí mà nhà máy được đặt, vì vậy chúng ta sẽ có thể sử dụng nhà máy ô tô phù hợp mà không cần biết việc thực hiện nhà máy ô tô nào sẽ được sử dụng trong nội bộ. Điều này cũng tiết kiệm cho chúng ta từ một người gọi nhầm nhà máy cho một vị trí cụ thể.
CarType.class
// Java Program to demonstrate the working of Abstract Factory Pattern
enum CarType {
MICRO,
MINI,
LUXURY
}
Car.class
abstract class Car {
Car(CarType model, Location location) {
this.model = model;
this.location = location;
}
abstract void construct();
CarType model = null;
Location location = null;
CarType getModel() {
return model;
}
void setModel(CarType model) {
this.model = model;
}
Location getLocation() {
return location;
}
void setLocation(Location location) {
this.location = location;
}
@Override
public String toString() {
return "CarModel - " + model + " located in " + location;
}
}
LuxuryCar.class
class LuxuryCar extends Car {
LuxuryCar(Location location) {
super(CarType.LUXURY, location);
construct();
}
@Override
protected void construct() {
System.out.println("Connecting to luxury car");
}
}
MicroCar.class
class MicroCar extends Car {
MicroCar(Location location) {
super(CarType.MICRO, location);
construct();
}
@Override
protected void construct() {
System.out.println("Connecting to Micro Car ");
}
}
Minicar.class
class MiniCar extends Car {
MiniCar(Location location) {
super(CarType.MINI, location);
construct();
}
@Override
void construct() {
System.out.println("Connecting to Mini car");
}
}
Location.class
enum Location {
DEFAULT,
USA,
INDIA
}
INDIACarFactory.class
class INDIACarFactory {
static Car buildCar(CarType model) {
Car car = null;
switch (model) {
case MICRO:
car = new MicroCar(Location.INDIA);
break;
case MINI:
car = new MiniCar(Location.INDIA);
break;
case LUXURY:
car = new LuxuryCar(Location.INDIA);
break;
default:
break;
}
return car;
}
}
DefaultCarFactory.class
class DefaultCarFactory {
public static Car buildCar(CarType model) {
Car car = null;
switch (model) {
case MICRO:
car = new MicroCar(Location.DEFAULT);
break;
case MINI:
car = new MiniCar(Location.DEFAULT);
break;
case LUXURY:
car = new LuxuryCar(Location.DEFAULT);
break;
default:
break;
}
return car;
}
}
USACarFactory.class
class USACarFactory {
public static Car buildCar(CarType model) {
Car car = null;
switch (model) {
case MICRO:
car = new MicroCar(Location.USA);
break;
case MINI:
car = new MiniCar(Location.USA);
break;
case LUXURY:
car = new LuxuryCar(Location.USA);
break;
default:
break;
}
return car;
}
}
CarFactory.class
class CarFactory {
private CarFactory() {
}
public static Car buildCar(CarType type) {
Car car = null;
// We can add any GPS Function here which
// read location property somewhere from configuration
// and use location specific car factory
// Currently I'm just using INDIA as Location
Location location = Location.INDIA;
switch (location) {
case USA:
car = USACarFactory.buildCar(type);
break;
case INDIA:
car = INDIACarFactory.buildCar(type);
break;
default:
car = DefaultCarFactory.buildCar(type);
}
return car;
}
}
AbstractDesign.class
class AbstractDesign {
public static void main(String[] args) {
System.out.println(CarFactory.buildCar(CarType.MICRO));
System.out.println(CarFactory.buildCar(CarType.MINI));
System.out.println(CarFactory.buildCar(CarType.LUXURY));
}
}
Output:
Connecting to Micro Car
CarModel - MICRO located in INDIA
Connecting to Mini car
CarModel - MINI located in INDIA
Connecting to luxury car
CarModel - LUXURY located in INDIA
Java Factory Design Pattern - Mẫu thiết kế Factory Design trong Java
Factory Design Pattern được sử dụng khi chúng ta có một super-class với nhiều sub-class và dựa trên đầu vào, chúng ta cần trả về một trong các sub-class. Design Pattern này nhận trách nhiệm khởi tạo một lớp từ chương trình client đến lớp factory.
1. Tại sao cần Factory Design Pattern
- Factory Pattern được sử dụng khi:
- Chúng ta có một super class với nhiều sub-class và dựa trên đầu vào, chúng ta cần trả về một sub-class. Mô hình này giúp chúng ta đưa trách nhiệm của việc khởi tạo một lớp từ phía người dùng (client) sang lớp Factory.
- Chúng ta không biết sau này sẽ cần đến những sub-class nào nữa. Khi cần mở rộng, hãy tạo ra sub class và implement thêm vào factory method cho việc khởi tạo sub class này.
- Lợi ích của Factory Pattern:
- Factory Pattern giúp giảm sự phụ thuộc giữa các module (loose coupling): cung cấp 1 hướng tiếp cận với Interface thay thì các implement. Giúp chuơng trình độc lập với những lớp cụ thể mà chúng ta cần tạo 1 đối tượng, code ở phía client không bị ảnh hưởng khi thay đổi logic ở factory hay sub class.
- Mở rộng code dễ dàng hơn: khi cần mở rộng, chỉ việc tạo ra sub class và implement thêm vào factory method.
- Khởi tạo các Objects mà che giấu đi xử lí logic của việc khởi tạo đấy. Người dùng không biết logic thực sực được khởi tạo bên dưới phương thức factory.
- Dễ dạng quản lý life cycle của các Object được tạo bởi Factory Pattern.
- Thống nhất về naming convention: giúp cho các developer có thể hiểu về cấu trúc source code.
2. Thực thi Factory Design Pattern
Một Factory Pattern bao gồm các thành phần cơ bản sau:
- Super Class: môt supper class trong Factory Pattern có thể là một interface, abstract class hay một class thông thường.
- Sub Classes: các sub class sẽ implement các phương thức của supper class theo nghiệp vụ riêng của nó.
- Factory Class: một class chịu tránh nhiệm khởi tạo các đối tượng sub class dựa theo tham số đầu vào. Lưu ý: lớp này là Singleton hoặc cung cấp một public static method cho việc truy xuất và khởi tạo đối tượng. Factory class sử dụng if-else hoặc switch-case để xác định class con đầu ra.
Ví dụ: Chương trình sau thể hiện cách chúng ta xây dựng mô hình quan hệ giữa các computer: PC, server
- Factory Design Pattern Super Class
public abstract class Computer {
public abstract String getRAM();
public abstract String getHDD();
public abstract String getCPU();
@Override
public String toString(){
return "RAM= "+this.getRAM()+", HDD="+this.getHDD()+", CPU="+this.getCPU();
}
}
- Factory Design Pattern Sub Classes
public class PC extends Computer {
private String ram;
private String hdd;
private String cpu;
public PC(String ram, String hdd, String cpu){
this.ram=ram;
this.hdd=hdd;
this.cpu=cpu;
}
@Override
public String getRAM() {
return this.ram;
}
@Override
public String getHDD() {
return this.hdd;
}
@Override
public String getCPU() {
return this.cpu;
}
}
public class Server extends Computer {
private String ram;
private String hdd;
private String cpu;
public Server(String ram, String hdd, String cpu){
this.ram=ram;
this.hdd=hdd;
this.cpu=cpu;
}
@Override
public String getRAM() {
return this.ram;
}
@Override
public String getHDD() {
return this.hdd;
}
@Override
public String getCPU() {
return this.cpu;
}
}
- Factory Class
import design.model.Computer;
import design.model.PC;
import design.model.Server;
public class ComputerFactory {
public static Computer getComputer(String type, String ram, String hdd, String cpu){
if("PC".equalsIgnoreCase(type)) return new PC(ram, hdd, cpu);
else if("Server".equalsIgnoreCase(type)) return new Server(ram, hdd, cpu);
return null;
}
}
- Test Class
import design.factory.ComputerFactory;
import design.model.Computer;
public class TestFactory {
public static void main(String[] args) {
Computer pc = ComputerFactory.getComputer("pc","2 GB","500 GB","2.4 GHz");
Computer server = ComputerFactory.getComputer("server","16 GB","1 TB","2.9 GHz");
System.out.println("Factory PC Config::"+pc);
System.out.println("Factory Server Config::"+server);
}
}
- Output:
Factory PC Config::RAM= 2 GB, HDD=500 GB, CPU=2.4 GHz
Factory Server Config::RAM= 16 GB, HDD=1 TB, CPU=2.9 GHz
Java Builder Pattern - Mẫu thiết kế Builder trong Java
Builder là một mẫu thiết kế sáng tạo cho phép bạn xây dựng các đối tượng phức tạp theo từng bước. Mẫu cho phép bạn tạo ra các kiểu và biểu diễn khác nhau của một đối tượng bằng cách sử dụng cùng một hàm khởi tạo (construction).
1. Vấn đề
Hãy tưởng tượng một đối tượng phức tạp đòi hỏi nhiều công sức, khởi tạo từng bước của nhiều trường và các đối tượng lồng nhau. Hàm khởi tạo như vậy thường được tạo bên trong một hàm tạo khổng lồ với rất nhiều tham số. Hoặc thậm chí tệ hơn: nằm rải rác trên toàn bộ mã code.
Bạn có thể làm cho chương trình trở nên quá phức tạp bằng cách tạo một lớp con cho mọi cấu hình có thể có của một đối tượng.
Ví dụ: Hãy nghĩ về cách tạo đối tượng Ngôi nhà. Để xây dựng một ngôi nhà đơn giản, bạn cần xây dựng bốn bức tường và một tầng, lắp cửa ra vào, lắp một cặp cửa sổ và xây dựng một mái nhà. Nhưng nếu bạn muốn một ngôi nhà lớn hơn, sáng sủa hơn, có sân sau và các tiện ích khác (như hệ thống sưởi, hệ thống ống nước và hệ thống dây điện)?
Giải pháp đơn giản nhất là mở rộng lớp House cơ sở và tạo một tập hợp các lớp con để bao gồm tất cả các tổ hợp của các tham số. Nhưng cuối cùng bạn sẽ có một số lượng đáng kể các lớp con. Bất kỳ thông số mới nào, chẳng hạn như kiểu hiên nhà, sẽ yêu cầu phát triển hệ thống phân cấp này nhiều hơn nữa.
Có một cách tiếp cận khác không liên quan đến việc lai tạo các lớp con. Bạn có thể tạo một phương thức khởi tạo khổng lồ ngay trong lớp House cơ sở với tất cả các tham số có thể có để điều khiển đối tượng house. Mặc dù cách tiếp cận này thực sự loại bỏ sự cần thiết của các lớp con, nhưng nó lại tạo ra một vấn đề khác.
Hàm tạo với nhiều tham số có nhược điểm của nó: không phải lúc nào cũng cần có tất cả các tham số.
Trong hầu hết các trường hợp, hầu hết các tham số sẽ không được sử dụng, làm cho các cuộc gọi hàm tạo khá xấu xí. Ví dụ: Chỉ một phần nhỏ các ngôi nhà có bể bơi, vì vậy các thông số liên quan đến bể bơi sẽ vô dụng.
2. Giải pháp
Mẫu Builder gợi ý rằng bạn trích xuất mã xây dựng đối tượng ra khỏi lớp của chính nó và di chuyển nó đến các đối tượng riêng biệt được gọi là trình xây dựng.
Mẫu Builder cho phép bạn xây dựng các đối tượng phức tạp theo từng bước. Builder không cho phép các đối tượng khác truy cập vào sản phẩm khi nó đang được xây dựng.
Mẫu sắp xếp việc xây dựng đối tượng thành một tập hợp các bước (buildWalls, buildDoor, v.v.). Để tạo một đối tượng, bạn thực hiện một loạt các bước này trên một đối tượng trình tạo. Phần quan trọng là bạn không cần phải gọi tất cả các bước. Bạn chỉ cần gọi những bước cần thiết để tạo ra một cấu hình cụ thể của một đối tượng.
Một số bước xây dựng có thể yêu cầu thực hiện khác nhau khi bạn cần xây dựng các hình ảnh đại diện khác nhau của sản phẩm. Ví dụ: Các bức tường của một cabin có thể được xây dựng bằng gỗ, nhưng các bức tường của lâu đài phải được xây dựng bằng đá.
Trong trường hợp này, bạn có thể tạo một số lớp trình xây dựng khác nhau triển khai cùng một tập hợp các bước xây dựng, nhưng theo một cách khác. Sau đó, bạn có thể sử dụng các trình xây dựng này trong quá trình xây dựng (tức là một tập hợp các lệnh gọi có thứ tự đến các bước xây dựng) để tạo ra các loại đối tượng khác nhau.
Các trình xây dựng khác nhau thực hiện cùng một nhiệm vụ theo nhiều cách khác nhau.
Ví dụ: Hãy tưởng tượng một người thợ xây dựng mọi thứ từ gỗ và kính, người thứ hai xây dựng mọi thứ bằng đá và sắt và người thứ ba sử dụng vàng và kim cương. Bằng cách gọi cùng một nhóm các bước, bạn sẽ có được một ngôi nhà bình thường từ người xây dựng đầu tiên, một lâu đài nhỏ từ người thứ hai và một cung điện từ người thứ ba. Tuy nhiên, điều này sẽ chỉ hoạt động nếu client code gọi các bước xây dựng có thể tương tác với các nhà xây dựng bằng giao diện chung.
3. Class Director
Bạn có thể đi xa hơn và trích xuất một loạt các lệnh gọi đến các bước của trình tạo mà bạn sử dụng để xây dựng một sản phẩm thành một lớp riêng biệt có tên là director. Lớp director xác định thứ tự thực hiện các bước xây dựng, trong khi trình xây dựng cung cấp việc triển khai cho các bước đó.
Director biết các bước xây dựng cần thực hiện để có được một sản phẩm hoạt động. Việc có một lớp director trong chương trình của bạn là không hoàn toàn cần thiết. Bạn luôn có thể gọi các bước xây dựng theo thứ tự cụ thể trực tiếp từ client code. Tuy nhiên, lớp director có thể là một nơi tốt để đưa các quy trình xây dựng khác nhau để bạn có thể sử dụng lại chúng trong chương trình của mình.
Ngoài ra, director hoàn toàn giấu kín các chi tiết cấu tạo sản phẩm với client code. Client chỉ cần liên kết builder với director, khởi tạo với director và nhận kết quả từ builder.
4. Structure
1. Giao diện Builder
khai báo các bước xây dựng sản phẩm chung cho tất cả các loại trình xây dựng.
2. Concrete Builders
cung cấp các cách triển khai khác nhau của các bước xây dựng. Concrete Builders có thể tạo ra các sản phẩm không tuân theo giao diện chung.
3. Product
là đối tượng kết quả. Các product do các builder khác nhau tạo ra không nhất thiết phải thuộc cùng một hệ thống phân cấp hoặc giao diện lớp.
4. Lớp Director
xác định thứ tự gọi các bước xây dựng, vì vậy bạn có thể tạo và sử dụng lại các cấu hình cụ thể của sản phẩm.
5. Client phải liên kết một trong các đối tượng builder với director. Thông thường, nó chỉ được thực hiện một lần, thông qua các tham số của hàm tạo của director. Sau đó, director sử dụng đối tượng xây dựng đó cho tất cả các construction xây dựng tiếp theo. Tuy nhiên, có một cách tiếp cận thay thế khi client chuyển đối tượng builder sang method của director. Trong trường hợp này, bạn có thể sử dụng một trình builder khác mỗi khi bạn thực thi với director.
5. Mã giả
// Using the Builder pattern makes sense only when your products
// are quite complex and require extensive configuration. The
// following two products are related, although they don't have
// a common interface.
class Car is
// A car can have a GPS, trip computer and some number of
// seats. Different models of cars (sports car, SUV,
// cabriolet) might have different features installed or
// enabled.
class Manual is
// Each car should have a user manual that corresponds to
// the car's configuration and describes all its features.
// The builder interface specifies methods for creating the
// different parts of the product objects.
interface Builder is
method reset()
method setSeats(...)
method setEngine(...)
method setTripComputer(...)
method setGPS(...)
// The concrete builder classes follow the builder interface and
// provide specific implementations of the building steps. Your
// program may have several variations of builders, each
// implemented differently.
class CarBuilder implements Builder is
private field car:Car
// A fresh builder instance should contain a blank product
// object which it uses in further assembly.
constructor CarBuilder() is
this.reset()
// The reset method clears the object being built.
method reset() is
this.car = new Car()
// All production steps work with the same product instance.
method setSeats(...) is
// Set the number of seats in the car.
method setEngine(...) is
// Install a given engine.
method setTripComputer(...) is
// Install a trip computer.
method setGPS(...) is
// Install a global positioning system.
// Concrete builders are supposed to provide their own
// methods for retrieving results. That's because various
// types of builders may create entirely different products
// that don't all follow the same interface. Therefore such
// methods can't be declared in the builder interface (at
// least not in a statically-typed programming language).
//
// Usually, after returning the end result to the client, a
// builder instance is expected to be ready to start
// producing another product. That's why it's a usual
// practice to call the reset method at the end of the
// `getProduct` method body. However, this behavior isn't
// mandatory, and you can make your builder wait for an
// explicit reset call from the client code before disposing
// of the previous result.
method getProduct():Car is
product = this.car
this.reset()
return product
// Unlike other creational patterns, builder lets you construct
// products that don't follow the common interface.
class CarManualBuilder implements Builder is
private field manual:Manual
constructor CarManualBuilder() is
this.reset()
method reset() is
this.manual = new Manual()
method setSeats(...) is
// Document car seat features.
method setEngine(...) is
// Add engine instructions.
method setTripComputer(...) is
// Add trip computer instructions.
method setGPS(...) is
// Add GPS instructions.
method getProduct():Manual is
// Return the manual and reset the builder.
// The director is only responsible for executing the building
// steps in a particular sequence. It's helpful when producing
// products according to a specific order or configuration.
// Strictly speaking, the director class is optional, since the
// client can control builders directly.
class Director is
private field builder:Builder
// The director works with any builder instance that the
// client code passes to it. This way, the client code may
// alter the final type of the newly assembled product.
method setBuilder(builder:Builder)
this.builder = builder
// The director can construct several product variations
// using the same building steps.
method constructSportsCar(builder: Builder) is
builder.reset()
builder.setSeats(2)
builder.setEngine(new SportEngine())
builder.setTripComputer(true)
builder.setGPS(true)
method constructSUV(builder: Builder) is
// ...
// The client code creates a builder object, passes it to the
// director and then initiates the construction process. The end
// result is retrieved from the builder object.
class Application is
method makeCar() is
director = new Director()
CarBuilder builder = new CarBuilder()
director.constructSportsCar(builder)
Car car = builder.getProduct()
CarManualBuilder builder = new CarManualBuilder()
director.constructSportsCar(builder)
// The final product is often retrieved from a builder
// object since the director isn't aware of and not
// dependent on concrete builders and products.
Manual manual = builder.getProduct()
6. Mối quan hệ với các Pattern khác
- Nhiều thiết kế bắt đầu bằng cách sử dụng Factory Method (ít phức tạp hơn và có thể tùy chỉnh nhiều hơn thông qua các lớp con) và phát triển theo hướng Abstract Factory, Prototype hoặc Builder (linh hoạt hơn nhưng phức tạp hơn).
- Builder tập trung vào việc xây dựng các đối tượng phức tạp theo từng bước. Abstract Factory chuyên tạo các family đối tượng liên quan. Abstract Factory trả lại sản phẩm ngay lập tức, trong khi Builder cho phép bạn chạy một số bước xây dựng bổ sung trước khi tìm nạp sản phẩm.
- Bạn có thể sử dụng Builder khi tạo các cây Composite phức tạp vì bạn có thể lập trình các bước xây dựng của nó để hoạt động một cách đệ quy.
- Bạn có thể kết hợp Builder với Bridge: lớp director đóng vai trò trừu tượng, trong khi các trình xây dựng khác nhau đóng vai trò triển khai.
- Abstract Factories, Builders và Prototypes đều có thể được thực hiện dưới dạng các Singleton.
Java Features
Ngôn ngữ lập trình Java và những tính năng mới qua từng phiên bản Java
Java Features Overview
Dưới đây là lịch sử nâng cấp và các tính năng nổi bật qua từng phiên bản Java
Java 12 Features
Release Date: 19/03/2019
Java 12 là phiên bản mới nhất của JDK. Cùng tìm hiểu những tính năng và nâng cấp mới nhất.
- Collectors.teeing() in Stream API
- String API Changes
- Files.mismatch(Path, Path)
- Compact Number Formatting
- Support for Unicode 11
- Switch Expressions (Preview)
Java 11 Features
Release Date: 09/2018
Java 11 bao gồm rất nhiều cập nhật quan trọng và hữu ích.
- HTTP Client API
- Launch Single-File Programs Without Compilation
- String API Changes
- Collection.toArray(IntFunction)
- Files.readString() and Files.writeString()
- Optional.isEmpty()
Java 10 Features
Sau khi phát hành phiên bản Java 9, Java 10 được phát hành ngay sau đó. Không giống như các lần cập nhật phiên bản trước đây, Java 10 không có nhiều thay đổi tính năng nổi bật mà nó có các cập nhật thay đổi các bạn viết code và với các phiên bản java trong tương lai.
- JEP 286: Local Variable Type Inference
- JEP 322: Time-Based Release Versioning
- JEP 304: Garbage-Collector Interface
- JEP 307: Parallel Full GC for G1
- JEP 316: Heap Allocation on Alternative Memory Devices
- JEP 296: Consolidate the JDK Forest into a Single Repository
- JEP 310: Application Class-Data Sharing
- JEP 314: Additional Unicode Language-Tag Extensions
- JEP 319: Root Certificates
- JEP 317: Experimental Java-Based JIT Compiler
- JEP 312: Thread-Local Handshakes
- JEP 313: Remove the Native-Header Generation Tool
- New Added APIs and Options
- Removed APIs and Options
Java 9 Features
Release Date: 09/2017. Sự thay đổi lớn nhất là về module hóa (Java modules).
Some important features/changes in Java 9 are:
- Java platform module system
- Interface Private Methods
- HTTP 2 Client
- JShell – REPL Tool
- Platform and JVM Logging
- Process API Updates
- Collection API Updates
- Improvements in Stream API
- Multi-Release JAR Files
- @Deprecated Tag Changes
- Stack Walking
- Java Docs Updates
- Miscellaneous Other Features
Java 8 Features
Release Date: 18/03/2014
Phiên bản này bao gồm nâng cấp và bổ sung các tính năng mới:
- Lambda expression support in APIs
- Stream API
- Functional interface
- Default method & static method
- Method reference
- Optionals
- Nashorn – JavaScript runtime which allows developers to embed JavaScript code within applications
- Annotation on Java Types
- Unsigned Integer Arithmetic
- Repeating annotations
- New Date and Time API
- Statically-linked JNI libraries
- Launch JavaFX applications from jar files
- Remove the permanent generation from GC
Java SE 7 Features
Release Date: 28/07/2011
Phiên bản này được gọi là "Dolphin". Bao gồm các tính năng:
- JVM support for dynamic languages
- Compressed 64-bit pointers
- Strings in switch
- Automatic resource management in try-statement
- The diamond operator
- Simplified varargs method declaration
- Binary integer literals
- Underscores in numeric literals
- Improved exception handling
- ForkJoin Framework
- NIO 2.0 having support for multiple file systems, file metadata and symbolic links
- WatchService
- Timsort is used to sort collections and arrays of objects instead of merge sort
- APIs for the graphics features
- Support for new network protocols, including SCTP and Sockets Direct Protocol
Java SE 6 Features
Release Date: 11/12/2006
Phiên bản này được gọi là “Mustang”. Sun đã loại bỏ ".0" trong số version và đã trở thành tên gọi Java SE 6. Bao gồm các tính năng:
- Scripting Language Support
- Performance improvements
- JAX-WS
- JDBC 4.0
- Java Compiler API
- JAXB 2.0 and StAX parser
- Pluggable annotations
- New GC algorithms
J2SE 5.0 Features
Release Date: 30/09/2004
Phiên bản này được gọi là "Tiger".
Phiên bản này được gọi là 5.0 thay vì 1.5. Bao gồm các tính năng:
- Generics
- Annotations
- Autoboxing/unboxing
- Enumerations
- Varargs
- Enhanced for each loop
- Static imports
- New concurrency utilities in java.util.concurrent
- Scanner class for parsing data from various input streams and buffers.
J2SE 1.4 Features
Release Date: 06/02/2002
Phiên bản này được gọi là "Merlin". Bao gồm các tính năng:
- assert keyword
- Regular expressions
- Exception chaining
- Internet Protocol version 6 (IPv6) support
- New I/O; NIO
- Logging API
- Image I/O API
- Integrated XML parser and XSLT processor (JAXP)
- Integrated security and cryptography extensions (JCE, JSSE, JAAS)
- Java Web Start
- Preferences API (java.util.prefs)
J2SE 1.3 Features
Release Date: 08/05/2000
Phiên bản này được gọi là "Kestrel". Bao gồm các tính năng:
- HotSpot JVM
- Java Naming and Directory Interface (JNDI)
- Java Platform Debugger Architecture (JPDA)
- JavaSound
- Synthetic proxy classes
J2SE 1.2 Features
Release Date: 08/12/1998
Phiên bản này được gọi là "Playground". Đây là một bản phát quan trọng về số lượng các lớp được thêm vào (gần gấp ba lần kích thước). Thuật ngữ J2 J2SE được giới thiệu để phân biệt nền tảng với J2EE và J2ME. Các tính năng bao gồm:
- strictfp keyword
- Swing graphical API
- Sun’s JVM was equipped with a JIT compiler for the first time
- Java plug-in
- Collections framework
JDK 1 Features
Release Date: 23/01/1996
Đây là phiên bản phát hành đầu tiên với tên gọi ban đầu là Oak (Cây sồi). Đây là phiên bản có các API rất không ổn định và một trình duyệt web java có tên WebRunner.
Phiên bản ổn định (stable) đầu tiên là JDK 1.0.2, được gọi là Java 1.
JDK 1.1 được phát hành vào February 19, 1997 gồm các tính năng quan trọng như:
- AWT event model
- Inner classes
- JavaBeans
- JDBC
- RMI
- Reflection which supported Introspection only, no modification at runtime was possible.
- JIT (Just In Time) compiler for Windows
Java 8 - Lambda expression
1. Giới thiệu Lambda
Lamda là một hàm không có tên (unamed function) với các tham số (parameters) và nội dung thực thi (body). Nội dung thực thi của Lamda expression có thể là 1 khối lệnh hoặc 1 biểu thức. Dấu “->” tách biệt các tham số và nội dung thực thi.
Lambda là một trong những thay đổi lớn nhất và được mong đợi nhất của ngôn ngữ Java trong phiên bản phát hành này. Nó cho phép chúng ta xử lý chức năng (functionality) như một đối của phương thức (thông qua function around), hoặc xử lý code như dữ liệu (data): những khái niệm mà mỗi nhà phát triển chức năng rất quen thuộc.
Có rất nhiều ngôn ngữ trên nền tảng JVM (Groovy, Scala…) đã có lambdas từ những ngày đầu tiên, tuy nhiên Java developer thi không có sự lựa chọn cho lambdas với anonymous classes.
Việc thảo luận và thiết kế Lambdas được thực hiện với rất nhiều thời gian và nỗ lực của cộng đồng. Cuối cùng, bản thương mại đã được đề ra, một cấu trúc ngôn ngữ ngắn gọn và súc tích. Trong dạng đơn giản nhất, lambda có thể được biểu diễn như một danh sách các dấu phẩy của các thông số.
2. Cách khai báo và ví dụ
Ví dụ:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
Chú ý rằng, kiểu đối số e được suy ra từ trình biên dịch compiler. Ngoài ra, bạn cũng có thể cung cấp kiểu đối số một cách tường minh, trong dấu ngoặc đơn.
Ví dụ:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Trong trường hợp body của lambda phức tạp hơn, nó có thể được đặt trong dấu ngoặc nhọn – như một hàm thông thường trong Java.
Ví dụ:
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.print( e );
System.out.print( e );
} );
Lambda có thể reference tới thành viên của lớp và các biến cục bộ (chắc chắn rằng các biến này là kiểu final)
Ví dụ: Hai đoạn mã sau là tương đương
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator )
);
và:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator )
);
Lambda có thể trả về kết quả giá trị. Kiểu của return value sẽ được suy ra từ trình biên dịch compiler. Câu lệnh return không yêu cầu bắt buộc nếu lambda body chỉ chứa 1 dòng code.
Ví dụ: Hai đoạn mã sau là tương đương
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
và
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
3. Biểu thức lambda minh bạch và không minh bạch (Explicit and implicit lambda expression)
Mô tả
- Một biểu thức lambda không khai báo kiểu (type) của các tham số (parameter) của nó được gọi là biểu thức lambda không minh bạch (implicit)
- Một biểu thức lambda minh bạch (explicit) là biểu thức có khai báo kiểu của các tham số của nó một cách rõ ràng.
Trình biên dịch sẽ suy ra các kiểu tham số đối với biểu thức lambda không minh bạch.
Ví dụ 1:
Đoạn code sau tạo một interface với một phương thức duy nhất và sử dụng nó như một loại biểu thức lambda. Khi tạo ra biểu thức lambda chúng ta khai báo tham số s1 có kiểu là Integer
public class Main {
public static void main(String[] args) {
MyIntegerCalculator myIntegerCalculator = (Integer s1) -> s1 * 2;
System.out.println("Result: " + myIntegerCalculator.calcIt(5));
}
}
interface MyIntegerCalculator {
public Integer calcIt(Integer s1);
}
Đoạn code trên sẽ tạo ra kết quả: Result: 10
Ví dụ 2:
Dưới đây là bản demo mà không sử dụng khai báo kiểu cho các tham số. Khi bỏ qua các kiểu, trình biên dịch sẽ tự nhận ra nó.
public class Main {
public static void main(String[] args) {
MyIntegerCalculator myIntegerCalculator = (s1) -> s1 * 2;
System.out.println("Result: " + myIntegerCalculator.calcIt(5));
}
}
interface MyIntegerCalculator {
public Integer calcIt(Integer s1);
}
Đoạn code trên sẽ tạo ra kết quả: Result: 10
4. Tại sao nên sử dụng Lambda
- Giảm số dòng code
Một trong những tiện ích dễ thấy nhất khi sử dụng Lambda Expression là số lượng dòng code được giảm. Chúng ta có thể dễ dàng tạo ra một thể hiện của một functional interface bằng cách sử dụng Lambda Expression hơn là sử dụng một anonymous class.
- Hỗ trợ thực hiện tuần tự (sequential) và song song (parallel)
Một tiện ích khác của Lambda là việc hưởng lợi từ sự hỗ trợ của Stream API cho các tiến trình tuần tự và song song.
Để cho dễ hiểu, chúng ta cùng làm 1 ví dụ đơn giản, chúng ta sẽ viết một phương thức để kiểm tra xem số nhập vào có phải là số nguyên tố hay không.
Theo cách thông thường, ta sẽ viết code như sau, có thể đoạn code chưa tối ưu nhưng thể hiện được điều mình muốn làm:
private static boolean isPrime(int number) {
if(number < 2) return false;
for(int i=2; i<number; i++){
if(number % i == 0) return false;
}
return true;
}
Vấn đề ở đoạn code trên là nó sẽ chạy tuần tự một cách tự nhiên và nếu chúng ta có 1 số cực lớn thì chúng ta sẽ mất nhiều thời gian. Ngoài ra trong đoạn code này có nhiều điểm trả về mà nó sẽ không chạy qua.
Bây giờ chúng ta sẽ xem Lambda giải quyết vấn đề này như thế nào.
private static boolean isPrime(int number) {
return number > 1
&& IntStream.range(2, number).noneMatch(
index -> number % index == 0);
}
IntStream là một chuỗi các phần tử có giá trị kiểu int hỗ trợ các tiến trình tổng hợp tuần tự và song song.
Để dễ đọc hơn, chúng ta cũng có thể viết theo cách sau:
private static boolean isPrime(int number) {
IntPredicate isDivisible = index -> number % index == 0;
return number > 1
&& IntStream.range(2, number).noneMatch(
isDivisible);
}
- Truyền hành động (behavior) vào phương thức
Bây giờ chúng ta sẽ truyền hành động vào một phương thức thông qua một ví dụ đơn giản.
public static int sumWithCondition(List numbers, Predicate predicate) {
return numbers.parallelStream()
.filter(predicate)
.mapToInt(i -> i)
.sum();
}
Cách sử dụng:
//Tổng các số
sumWithCondition(numbers, n -> true)
//Tổng các số chẵn
sumWithCondition(numbers, i -> i%2==0)
//Tổng các số lớn hơn 5
sumWithCondition(numbers, i -> i>5)
- Lười biếng nhưng hiệu quả
Một lợi thế của việc sử dụng Lambda là sự lười biếng. Ví dụ, ta sẽ viết một phương thức tìm ra số lẻ lớn nhất trong dãy từ 3 đến 11 và trả về bình phương của nó.
Thông thường, đoạn code sẽ được viết như sau:
private static int findSquareOfMaxOdd(List numbers) {
int max = 0;
for (int i : numbers) {
if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
max = i;
}
}
return max * max;
}
Chương trình trên sẽ luôn chạy tuần tự nhưng chúng ta có thể sử dụng Stream API để đạt được điều này với cách lười biếng nhất. Hãy xem cách chúng ta viết lại khi sử dụng Stream API và Lambda Expression.
public static int findSquareOfMaxOdd(List numbers) {
return numbers.stream()
.filter(NumberTest::isOdd)
.filter(NumberTest::isGreaterThan3)
.filter(NumberTest::isLessThan11)
.max(Comparator.naturalOrder())
.map(i -> i * i)
.get();
}
public static boolean isOdd(int i) {
return i % 2 != 0;
}
public static boolean isGreaterThan3(int i){
return i > 3;
}
public static boolean isLessThan11(int i){
return i < 11;
}
Java 8 - Functional Interface
Functional Interface là một interface có một phương thức abstract, nó cũng có thể được gọi là Single Abstract Interface (SAM) một cụm từ đôi khi chúng ta bắt gặp.
@FunctionalInterface
annotation được thêm vào để chúng ta đánh dấu interface đó là functional interface, điều này không bắt buộc nhưng có thể là cách tốt nhất trong việc sử dụng functional interface để tránh vô tình thêm các phương thức khác. Nếu một interface đã có annotation @FunctionalInterface
và chúng ta cố gắng thêm vào các phương thức khác thì trình biên dịch sẽ ném ra lỗi.
Lợi ích chính của functional interface là chúng ta có thể sử dụng Lambda Expression để tạo ra thể hiện (instance) cho interface đó.
Ví dụ:
@FunctionalInterface
public interface Functional {
void method();
}
Một điều cần lưu ý: phương thức default and static không phá vỡ quy tắc của functional interface.
Ví dụ:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
Ví dụ: Trong ví dụ sau, chúng ta sẽ gán biểu thức lambda cho functional interface. Sau đó chúng ta có thể chạy biểu thức lambda bằng cách gọi phương thức được định nghĩa bên trong functional interface và truyền tham số vào.
public class Main {
public static void main(String[] argv) {
Processor stringProcessor = (String str) -> str.length();
String name = "Java Lambda";
int length = stringProcessor.getStringLength(name);
System.out.println(length);
}
}
@FunctionalInterface
interface Processor {
int getStringLength(String str);
}
Đoạn code trên trả về kết quả như sau: 11
Java 8 - Default method & Static method
1. Giới thiệu
Một trong những thay đổi lớn nhất trong Java 8 là khái niệm về interface. Như chúng ta đã biết từ Java 7 trở về trước, interface chỉ cho phép chúng ta khai báo các phương thức bên trong nó. Nhưng trong Java 8 chúng ta sẽ có thêm 2 khái niệm mới đối với interface là phương thức default (default methods) và phương thức static (static methods).
Thiết kế interface luôn là một công việc rất khó khăn, bởi vì khi chúng ta thay đổi các phương thức bên trong interface nó đòi hỏi phải thay đổi tất cả các class được implements từ nó. Một khi số lượng các class được implements từ interface phát triển nhiều lên thì đến mức độ nào đó interface có thể không mở rộng được nữa. Đây là lý do tại sao khi thiết kế một ứng dụng, hầu hết các framework cung cấp một class cơ sở (base class), sau đó chúng ta sẽ mở rộng (extends) và ghi đè (override) lên các phương thức phù hợp với ứng dụng đang thực hiện.
2. Phương thức default
Để tạo một phương thức default trong interface, chúng ta sẽ sử dụng từ khóa "default".
Ví dụ: Interface1.java
private interface Defaulable {
void method1(String str);
// Interfaces now allow default methods, the implementer may or may not implement (override) them.
default String notRequired() {
return "Default implementation";
}
}
private static class DefaultableImpl implements Defaulable {
@Override
public void method1(String str) {
}
}
private static class OverridableImpl implements Defaulable {
@Override
public void method1(String str) {
}
@Override
public String notRequired() {
return "Overridden implementation";
}
}
Giải thích: Chúng ta xây dựng một interface là Defaulable với hai phương thức:
- void method1
- default String notRequired
Khi xây dựng các lớp implements tới lớp Defaulable, chúng ta có thể không cần thực hiện Override phương thức notRequired (lớp DefaultableImpl) hoặc Override phương thức notRequired (lớp OverridableImpl)
Những đặc điểm quan trọng về phương thức default trong interface:
- Phương thức default giúp chúng ta mở rộng interface mà không phải lo ngại phá vỡ các class được implements từ nó.
- Phương thức default giúp chúng ta tránh dùng các class tiện ích, ví dụ như tất cả phương thức của class Collections có thể được cung cấp ngay bên trong interface của nó
- Phương thức default giúp chúng ta tháo gỡ các class cơ sở (base class), chúng ta có thể tạo phương thức default và trong class được implement có thể chọn phương thức để override
- Một trong những lý do xuất hiện của phương thức default là để nâng cấp Collection API trong Java 8 hỗ trợ cho Lambda Expression.
- Nếu bất kỳ class nào kế thừa những phương thức default giống nhau, thì nó sẽ không còn hiệu lực. Một điều tương tự, một phương thức default sẽ không thể override một phương thức từ java.lang.Object. Lý do rất đơn giản là bởi vì Object là base class của tất cả các class trong Java. Vì vậy nếu chúng ta có các phương thức của class Object được định nghĩa là phương thức default trong interface, nó sẽ không dùng được bởi vì các phương thức của Object luôn luôn được sử dụng. Đây lý do tại sao chúng ta sẽ không có bất cứ phương thức default nào override các phương thức của class Object.
- Phương thức default cũng có thể được gọi là phương thức Defender (Defender Methods) hay là phương thức Virtual mở rộng (Virtual extension methods)
Chú ý:
- Phương thức default được thực hiện trên JVM mang lại hiệu quả và được hỗ trợ các chỉ dẫn byte code cho phương pháp gọi. Phương thức default cho phép các Java interface đã tồn tại phát triển thêm mà không gây lỗi trong quá trình biên dịch. Ví dụ như bổ sung các phương thức vào interface java.util.Collection: stream(), parallelStream(), forEach(), removeIf()…
- Mặc dù vậy, phương thức default cũng cần được sử dụng một cách cần thận bởi nguyên nhân: Trước khi khai báo phương thức là default, cần chắc chắn là nó là cần thiết bị nó có thể gây ra sự nhập nhằng và biên dịch lỗi trong hệ thống phân cấp phức tạp.
3. Phương thức static
Phương thức static cũng giống như phương thức default ngoại trừ việc nó không thể được override chúng trong class được implements.
Một tính năng thú vị cung cấp bởi Java 8 là interface có thể khai báo (và cung cấp implementation) các phương thức tĩnh. Dưới đây là một số ví dụ.
Ví dụ 1:
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
Main class:
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}
Kết quả:
Default implementation
Overridden implementation
Ví dụ 2:
MyData.java
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
Bây giờ sẽ xem class được implements có phương thức isNull()
MyDataImpl.java
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
Phương thức isNull(String str)
là một phương thức đơn giản, nó không override phương thức của interface. Ví dụ nếu chúng ta thêm annotation @Override
cho phương thức isNull()
, trình biên dịch sẽ báo lỗi.
Bây giờ chúng ta sẽ chạy ứng dụng và xem kết quả:
Interface Null Check
Impl Null Check
Nếu chúng ta chuyển static
thành default
, thì kết quả như sau:
Impl Null Check
MyData Print::
Impl Null Check
Phương thức static chỉ hiển thị trong phương thức của interface, nếu chúng ta xóa phương thức isNull() trong class MyDataImpl
, chúng ta sẽ không thể sử dụng nó cho đối tượng (object) của MyDataImpl
. Tuy nhiên, giống như các phương thức static khác, chúng ta có thể sử dụng phương thức static của interface thông qua tên của class. Ví dụ sau đây là cách sử dụng hợp lệ:
boolean result = MyData.isNull("abc");
Những đặc điểm quan trọng về phương thức static trong interface:
- Phương thức static là một thành phần của interface, chúng ta có thể sử dụng nó trong class được implements từ nó.
- Phương thức static rất hữu ích trong việc cung cấp các phương thức tiện ích, ví dụ như là kiểm tra null, sắp xếp tập hợp …
- Phương thức static giúp chúng ta bảo mật, không cho phép class implements từ nó có thể override
- Chúng ta không thể định nghĩa phương thức static của các phương thức thuộc class Object, chúng ta sẽ gặp lỗi "This static method cannot hide the instance method from Object". Điều này không cho phép trong Java, khi Object là base class cho tất cả các class và chúng ta không thể có một phương thức static và một phương thức khác cùng định dạng
- Chúng ta có thể sử dụng phương thức static để bỏ đi những những phương thức dạng tiện ích như là Collections và làm cho tất cả các phương thức có thể liên lạc với interface, chúng ta sẽ dễ dàng tìm thấy và sử dụng những phương thức đó.
Java 8 - Method reference
Phương thức tham chiếu cung cấp các cú pháp (syntax) hữu ích để truy cập trực tiếp tới các phương thức hoặc hàm dựng đã tồn tại của các lớp hoặc đối tượng trong Java. Với sự kết hợp của Lambda expression, phương thức tham chiếu làm cho cấu trúc ngôn ngữ trông nhỏ gọn và súc tích.
Ví dụ: Car class với các định nghĩa phương thức khác nhau
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
Giải thích:
1. Tham chiếu hàm dựng
Loại thứ nhất của phương thức tham chiếu là hàm dựng tham chiếu với cú pháp Class::new
hoặc cách tổng quát, Class<T>::new
final Car car = Car.create(Car::new);
final List<Car> cars = Arrays.asList(car);
Chú ý rằng, hàm dựng này không có đối
2. Tham chiếu phương thức static
Loại thứ hai là tham chiếu tới phương thức static với cú pháp Class::staticMethod
cars.forEach(Car::collide);
Chú ý rằng, phương thức này chấp nhận chính xác tham số của Car
3. Tham chiếu phương thức instance của đối tượng tùy ý
Loại thức ba là tham chiếu tới phương thức instance của đối tượng tùy ý của loại cụ thể với cú pháp Class::method
cars.forEach(Car::repair);
Chú ý rằng, phương thức này có thể chấp nhận không có đối số
4. Tham chiếu phương thức instance của lớp instance
Loại thứ tư là tham chiếu tới phương thức instance của lớp instance cụ thể với cú pháp instance::method
final Car police = Car.create(Car::new);
cars.forEach(police::follow);
Chú ý rằng, phương thức này chấp nhận chính xác tham số của Car
Kết quả:
Collided vn.laptrinh.Car@36baf30c
Repaired vn.laptrinh.Car@36baf30c
Following the vn.laptrinh.Car@36baf30c
Java 8 - Repeating annotation
Annotations support từ khi được giới thiệu trong phiên bản Java 5, tính năng này đã trở thành một tính năng hữu ích và được sử dụng rộng rãi.
Tuy nhiên, hạn chế của việc sử dụng annotatioin này là các annotation có thể không được khai báo nhiều hơn một lần cùng một vị trí. Java 8 đã giới thiệu tính năng repeating annotation. Nó cho phép các annotation giống nhau có thể được khai báo nhiều lần cùng một vị trí.
Repeating annotation sử dụng chú thích @Repeatable
.
Ví dụ:
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatingAnnotations {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Filters {
Filter[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Filters.class)
public @interface Filter {
String value();
};
@Filter("filter1")
@Filter("filter2")
public interface Filterable {
}
public static void main(String[] args) {
for (Filter filter : Filterable.class
.getAnnotationsByType(Filter.class)) {
System.out.println(filter.value());
}
}
}
Giải thích: Đây là lớp sử dụng Filter annotation với @Repeatable (Filters.class)
. Lớp Filters chỉ nắm giữ Filter annotation nhưng trình biên dịch Java compiler cố gắng để ch giấu sự hiện diện của nó từ developer. Như vậy Filterable interface có chú thích Filter được định nghĩa hai lần (không đề cập tới Filters).
Ngoài ra, Reflection API cũng cung cấp phương thức mới là getAnnotationsByType()
để trả về các repeating annotation. Chú ý rằng Filterable.class.getAnnotation(Filters.class)
sẽ trả về instance của Filters bởi trình biên dịch.
Kết quả khi chạy chương trình:
filter1
filter2
Java Guideline
Kiến thức Java
Java Heap memory
1. Java Heap là gì
Khi JVM start up, nó được cấp phát bộ nhớ Memory từ Hệ điều hành. JVM sử dụng memory cho tất cả nhu cầu của nó và 1 phần bộ nhớ này được gọi là Heap memory. Heap trong java được sinh ra ở cuối cùng của vùng địa chỉ và di chuyển lên trên, bất cứ khi nào chúng ta sử dụng toán tử new
hoặc khai báo đối tượng sẽ được cấp phát bộ nhớ từ Heap, còn khi object chết hoặc GC, memory sẽ quay trở lại Heap.
Bộ nhớ Heap trong Java được dùng để cấp phát bộ nhớ cho các đối tượng, các lớp JRE lúc thực thi. Bất cứ khi nào, chúng ta tạo đối tượng, nó sẽ được tạo trong bộ nhớ Heap.
Với những đối tượng không còn được tham chiếu nữa thì trình thu thập rác (Garbage Collection) sẽ giải phóng bộ nhớ mà các đối tượng đó sử dụng.
Đối tượng được tạo trong bộ nhớ Heap có phạm vị truy cập toàn cục, tức là chúng ta có thể truy cập đối tượng đó ở bất kỳ đâu trong ứng dụng
2. Java Heap memory
Heap đôi khi được chia làm 2 vùng (hay thế hệ) gọi là Nursey (hay young space
) và vùng old space
.
Nursery là một phần của Heap để dành cho việc cấp phát cho các đối tượng mới. Khi nursery bắt đầu đầy, rác sẽ được thu gom bằng một process thu gom rác đặc biệt gọi là young collection
, nơi mà các đối tượng sống đủ lâu trong nursery được promoted
và di chuyển lên vùng old space
, do đó giải phóng nursery để cấp phát các đối tượng khác.
Khi old space
trở nên đầy, rác sẽ được thu gom bởi process khác được gọi là old collection
. Lý do đằng sau 1 nursery
là hầu hết các đối tượng đều là tạm thời và ngắn hạn. Một process young collection
được thiết kế để nhanh chóng tìm các đối tượng mới được cấp phát mà vẫn tồn tại (alive) và di chuyển chúng khỏi nursery. Thông thường, young collection
process sẽ giải phóng một số lượng bộ nhớ nhất định nhanh hơn old collection
process hay garbage collection
process của một heap mà không có vùng nursery (còn gọi là single-generational heap
)
Từ các version release mới của JVM, có một phần của nursery được giữ lại gọi là keep area
. Vùng này sẽ chứa các đối tượng được cấp phát gần đây nhất và chưa được thu gom cho đến khi young collection
process tiếp theo được chạy. Điều này sẽ ngăn cản việc đối tượng được di chuyển lên old space
vì các đối tượng chỉ vừa được cấp phát ngay trước khi young collection
chạy.
Permanent generation
của heap được sử dụng để lưu trữ String pool và siêu dữ liệu (metadata) khác nhau theo yêu cầu của JVM liên quan đến Class, phương thức và các java primitives khác.
3. Phân loại lưu trữ Object trong Heap
Trong quá trình cấp phát đối tượng, JVM
sẽ phân biệt giữa các đối tượng nhỏ (small object) và lớn (large object). Giới hạn khi một đối tượng được xem là lớn phụ thuộc vào các yếu tố như JVM version, độ lớn heap size, chiến lược GC và platform
được sử dụng, nhưng thường sự khác nhau giữa 2 loại đối tượng là khoảng 128kB.
Các đối tượng nhỏ sẽ được cấp phát trong vùng gọi là thread local areas
(TLAs), TLAs là các khối rỗng (free chunks) từ heap và được giao cho một Java thread
sử dụng độc quyền. Thread này có thể cấp phát các đối tượng trong TLA của nó mà không cần phải đồng bộ với các thread khác. Khi TLA bắt đầu đầy, thread chỉ đơn giản yêu cầu một TLA mới.
Các đối tượng lớn sẽ không vừa bên trong một TLA được cấp phát trực tiếp trên heap. Khi một Nursery được dùng, các đối tượng lớn sẽ được phân bố trực tiếp trên old space
. Sự phân bố các đối tượng lớn yêu cầu sự đồng bộ giữa các Java thread
, mặc dù JVM dùng một hệ thống caches
của các khối rỗng khác kích thước để giảm nhu cầu đồng bộ và tăng tốc độ cấp phát.
4. Tăng dung lượng Heap như thế nào?
Bằng cách thêm tham số khi chạy ứng dụng, chúng ta có thể tăng/giảm dung lượng bộ nhớ Heap:
java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
Trong đó:
- Xms: Dung lượng bộ nhớ được cấp phát khi JVM được start up
- Xmx: Dung lượng bộ nhớ tối đa mà JVM được cấp phát
- Xmn: Kích thước của không gian heap Young generation.
- XX:PermSize, XX:MaxPermSize: Dung lượng bộ nhớ cấp phát và bộ nhớ tối đa của Permanent generation
Kích thước mặc định của không gian Heap trong Java là 128MB trên hầu hết JVM của 32 bit nhưng nó rất khác nhau từ JVM đến JVM.
Ví dụ: Mặc định tối đa và kích thước heap bắt đầu cho Hệ điều hành Solaris 32 bit (Phiên bản nền tảng SPARC) là -Xms = 3670K và -Xmx = 64M và giá trị mặc định của các tham số kích thước heap trên hệ thống 64 bit đã tăng lên khoảng 30%.
Ngoài ra, nếu bạn đang sử dụng trình thu gom rác thông lượng trong Java 1.5 kích thước heap tối đa mặc định của JVM sẽ là bộ nhớ vật lý / 4 và kích thước vùng heap ban đầu mặc định sẽ là bộ nhớ vật lý / 16. Một cách khác để tìm kích thước heap mặc định của JVM là khởi động một ứng dụng với các tham số heap mặc định và theo dõi bằng cách sử dụng JConsole có sẵn trên JDK 1.5 trở đi, trên tab VMSummary, bạn sẽ có thể thấy kích thước heap tối đa.
Nhân tiện, có thể tăng kích thước không gian heap java dựa trên nhu cầu ứng dụng của bạn và tôi luôn khuyến nghị điều này để tránh sử dụng các giá trị heap JVM mặc định, nếu ứng dụng của bạn lớn và nhiều đối tượng được tạo, bạn có thể thay đổi kích thước của vùng heap bằng cách sử dụng các tùy chọn JVM -Xms và -Xmx. Xms biểu thị kích thước bắt đầu của Heap trong khi -Xmx biểu thị kích thước tối đa của Heap trong Java.
Có một tham số khác gọi là -Xmn biểu thị Kích thước của thế hệ Heap Java mới. Chỉ có điều là bạn không thể thay đổi kích thước của Heap trong Java một cách linh hoạt, bạn chỉ có thể cung cấp tham số Kích thước Heap Java trong khi bắt đầu JVM. Tôi đã chia sẻ một số tùy chọn JVM hữu ích hơn liên quan đến không gian Heap Java và bộ sưu tập Rác trên bài đăng của tôi 10 tùy chọn JVM mà lập trình viên Java phải biết, bạn có thể thấy hữu ích.
10 điểm về không gian heap Java
1. Bộ nhớ Heap Java là một phần của bộ nhớ được hệ điều hành cấp cho JVM.
2. Bất cứ khi nào chúng ta tạo các đối tượng, chúng được tạo bên trong Heap của Java.
3. Không gian Heap Java được chia thành ba vùng hoặc thế hệ cho mục đích thu gom rác được gọi là Thế hệ mới (New Generation), Thế hệ cũ (Old Generation) hoặc Thế hệ cho thuê(Rental Generation) hoặc Không gian Perm (Perm Space). Tạo vĩnh viễn là rác được thu thập trong toàn bộ máy chủ trong điểm nóng JVM.
4. Bạn có thể tăng hoặc thay đổi kích thước của không gian Heap Java bằng cách sử dụng các tùy chọn dòng lệnh JVM -Xms, -Xmx và -Xmn. Đừng quên thêm từ "M" hoặc "G" sau khi chỉ định kích thước để biểu thị Mega hoặc Gig. Ví dụ: bạn có thể đặt kích thước java heap thành 258MB bằng cách thực hiện theo lệnh java -Xmx256m HelloWord.
5. Bạn có thể sử dụng JConsole hoặc Runtime.maxMemory (), Runtime.totalMemory (), Runtime.freeMemory () để truy vấn các kích thước lập trình Heap trong Java.
6. Bạn có thể sử dụng lệnh "jmap" để lấy kết xuất Heap trong Java và "jhat" để phân tích kết xuất heap đó.
7. Không gian Heap Java khác với Stack được sử dụng để lưu trữ phân cấp cuộc gọi và các biến cục bộ.
8. Trình thu gom rác Java chịu trách nhiệm khôi phục bộ nhớ từ các đối tượng chết và trở về không gian Heap Java.
9. Nếu bạn gặp lỗi java.lang.OutOfMemoryError, đôi khi đó chỉ là vấn đề tăng kích thước heap, nhưng nếu nó bị lặp lại, hãy tìm nguyên nhân gây rò rỉ bộ nhớ trong Java.
10. Sử dụng các công cụ Phân tích kết xuất Profiler và Heap để hiểu không gian Heap Java và dung lượng bộ nhớ được phân bổ cho từng đối tượng.
Java Garbage collection - GC
1. Garbage collection là gì?
Garbage collection là quá trình xác định và loại bỏ các Object không được sử dụng (unreferenced) để giải phóng không gian của Heap để cấp phát cho các đối tượng mới.
Garbage collector là chương trình chạy nền, nó theo dõi toàn bộ các Object trong bộ nhớ (Heap) và tìm ra những Object nào không được dùng nữa (không có Object nảo reference đến nó). Toàn bộ những Object không có reference sẽ bị xóa.
2. Các thành phần của bộ nhớ Heap
Bộ nhớ Heap được chia thành các phần nhỏ như hình dưới đây.
- Young Generation Là nơi chứa toàn bộ Object mới được khởi tạo. Khi vùng nhớ Young generation đầy thì garbage collectior là Minor GC hoạt động. Vùng Young generation lại được chia thành 3 vùng nhỏ hơn là Eden và 2 vùng Survivor là S0, S1.
Ban đầu mọi Object mới tạo được chứa ở vùng Eden, khi Eden đầy thì Minor GC chuyển chúng sang vùng S0, S1.
Minor GC liên tục theo dõi các Object ở S0, S1. Sau "nhiều" chu kỳ quét mà Object vẫn còn được sử dùng thì chúng mới được chuyển sang vùng nhớ Old generation. Old generation được quản lý bởi garbage collectior khác là Major GC.
Hình trên mô phòng 2 Object được chuyển từ vùng Young generation sang Old generation sau 9 chu kỳ quét của Minor GC. Những ô màu vàng tượng chưng cho những Object đã không còn được sử dụng (unreferenced). Chúng sẽ được xóa khi Minor GC hay Majo GC clear vùng nhớ nó quản lý.
- Permanent Generation: Mô hình vùng nhớ Heap có vùng Perm (Permanent Generation), Perm không phải một phần của Heap. Perm không chứa Object, nó chứa metadata của JVM như các thư viện Java SE, mô tả các class và các method của ứng dụng.
3. Garbage Collector của Java làm việc như thế nào?
Trong Java, bộ garbage collector làm việc hoàn toàn tự động. Đồng nghĩa với việc lập trình viên không cần gọi các lệnh "dọn" bộ nhớ như trong C/C++.
Phần Implementation của garbage collector nằm trong JVM. Mỗi JVM lại có một cách implement garbage collector khác nhau, phù hợp với những đặc tính của JVM đó (JVM Hotspot vs JRockit...)
Quá trình thu gom rác cơ bản thông qua 3 bước sau:
1. Marking: Là bước đánh dấu những Object còn sử dụng và những Object không còn sử dụng.
2. Normal deleting: Trình Garbage Collector sẽ xóa các Object không còn sử dụng.
3. Deletion with Compacting: Sau khi những Object không còn được sử dụng bị xóa, những Object còn được sử dụng sẽ được "gom" lại gần nhau. Điều đó làm tăng hiệu xuất sử dụng bộ nhớ trống để cấp phát cho những Object mới.
Java Virtual Machine - JVM
1. Java Virtual Machine là gì?
Java Virtual Machine (Viết tắt là JVM) là môi trường dùng để chạy ứng dụng được viết bằng ngôn ngữ lập trình Java:
- Một Engine cung cấp Runtime Environment (môi trường chạy mã) để điều khiển mã Java hoặc các ứng dụng.
- Nó chuyển đổi Java Bytecode thành ngôn ngữ máy tính hiểu được.
- JVM là một phần của JRE (Môi trường chạy Java - Java Run Environment). Nó là viết tắt của Java Virtual Machine
Nhờ có JVM mà Java có thể chạy trên nhiều Platform khác nhau. JVM giống như một cái máy ảo, muốn khởi chạy Java thì bắt buộc phải chạy trên cái máy ảo này. Cứ với mỗi Platform ta sẽ có một JVM tương ứng, ví dụ như Ubuntu thì sẽ có bản JVM cho Ubuntu, Windows thì có JVM cho Windows. Và cơ chế hoạt động của JVM ở mọi nền tảng là hoàn toàn như nhau cho nên ứng dụng Java viết trên Window chạy được trên JVM của Window, khi đem cái ứng dụng đó qua Ubuntu thì chỉ cần cài JVM lên Ubuntu là ứng dụng được.
2. Các thành phần chính của Java Virtual Machine
- Class Loader: là một hệ thống con của JVM, làm nhiệm vụ tải các lớp được định nghĩa. Nó thực hiện ba chức năng chính: Loading, Linking và Initialization (Khởi tạo)
- Class Area: lưu trữ cấu trúc của các lớp, thuộc tính, phương thức của lớp, và code của các phương thức.
- Heap: là vùng nhớ lưu trử các đối tượng được khởi tạo trong quá trình thực thi.
- Stack: chứa các frame. Mỗi frame chứa các biến cục bộ và thực thi một hàm gọi và trả kết quả về. Mỗi tiến trình có một Stack riêng, được khởi tạo cùng lúc với tiến trình. Mỗi frame sẽ được tạo khi một hàm được gọi và hủy khi việc gọi hàm kết thúc.
- Programming Counter Register chứa địa chỉ của máy chủ ảo đang thực thi
- Native Method Stack: chứa các hàm của hệ thống được sữ dụng trong chương trình
- Execution Engine: là một hệ thống bao gồm: bộ xử lý ảo, trình thông dịch (đọc Java byte code và thực thi các chỉ thị), JIT compiler biên dịch mã byte code sang mã máy. Các nhiệm vụ chính của JVM bao gồm: tải code, kiểm tra code, thực thi code, cung cấp môi trường runtime.
3. Bộ nhớ trong Java JVM
Khi thực hiện cấp phát một bộ nhớ hoặc một đối tượng mới có thể được tạo và đặt vào vùng nhớ Heap. Khi ứng dụng của bạn không còn tham chiếu tới đối tượng này nữa thì Java garbage collector cho phép xóa đối tượng này đi để sử dụng lại vùng nhớ đó.
- Java Heap: JVM giúp lưu tất cả đối tượng đã được tạo ra bởi toán tử “new” trong ứng dụng Java vào trong vùng nhớ Heap ngay tại thời điểm chạy.
- Java Stack: Các phương thức và tham chiếu tới đối tượng địa phương được lưu trữ trong Stack. Mỗi Thread sẽ được quản lý một stack. Khi phương thức được gọi, nó được đưa vào đỉnh của Stack. Stack lưu trữ trạng thái của phương thức bao gồm: dòng code thực thi, tham chiếu tới đối tượng địa phương. Khi phương thức chạy xong, vùng nhớ (dòng code thực thi, tham chiếu tới đối tượng địa phương) được đẩy ra khỏi stack và tự động giải phóng.
- Java Perm: Lưu trữ thông tin của Class được nạp vào và một vài tính năng khác như StringPool (vùng nhớ của biến String) thường được tạo bởi phương thức String.interm(). Khi ứng dụng của bạn chạy, Perm space được lấp đầy nhanh chóng.
4. Cơ chế làm việc của Java Virtual Machine
JVM được chia thành 3 module chính:
- Class-Loader Subsytem: chuyên tìm kiếm và load các file .class vào vùng nhớ của Java.
- Runtime Data Area: vùng nhớ hệ thống cấp phát cho Java Virtual Machine.
- Execution Engine: chuyển các lệnh của JVM trong file .class thành các lệnh của máy, hệ điều hành tương ứng và thực thi chúng.
4.1. Class Loader Subsystem
Chịu trách nhiện load, liên kết và khởi tạo file .class khí nó refer đến một class lần đầu tiên trong thời gian chạy (không phải thời gian biên dịch).
- Loading:
Các class sẽ được load bởi thành phần này. BootStrap class Loader, Extension class Loader, và Application class Loader là 3 trình nạp class sẽ giúp thực hiện được điều đó.
- Boot Strap ClassLoader : chịu trách nhiệm load các class từ classpath, đó là các file .jar . Ưu tiên cao nhất sẽ được trao cho trình nạp class này.
- Extension ClassLoader : Chịu trách nhiệm load các class nằm trong thư mục jre\lib
- Application ClassLoader : Chịu trách nhiệm load các class từ được cấu hình ở các đường dẫn (path) được đề cập trong biến môi trường.
Các trình nạp class bên trên tuân theo thuật toán phân cấp trong khi load các class.
- Linking:
- Verify : Bytecode verifier (trình xác minh bytecode) sẽ kiểm tra xem byte code có được tạo ra phù hợp hay không. Nếu xác minh là không thành công, sẽ thông báo lỗi verify.
- Prepare : Đối với tất cả các static variables memory sẽ được phân bổ và gnas với các giá trị mặc định.
- Resolve : All symbolic memory references are replaced with the original references from Method Area.
- Initialization:
Đây là giai đoạn cuối của Class Loading. Trong giai đoạn này các biến tĩnh (static variables) sẽ được gán với các giá trị ban đầu và static block sẽ được thực thi.
4.2. Runtime Data Area
Runtime Data Area được chia thành 5 thành phần chính:
- Method area : Tất cả dữ liệu level class được lưu trữ ở đây, bao gồm static variables
- Heap area : Tất cả các đối tượng và các biến, arrays sẽ được lưu trữ ở đây. Method area và Heap area chia sẻ memory cho nhiều luồng, do đó dữ liệu được lưu trữ trở nên không an toàn.
- Stack area : Đối với mỗi luồng, một seperate runtime stack sẽ được tạo. Khi mỗi phương thức được gọi, mỗi lời gọi sẽ được thực hiện trong stack memory gọi là Stack Frame. Tất cả local variables sẽ được tạo trong stack memory. Stack area là thread-safe, vì nó không chia sẻ resource. Stack Frame được chia làm 3 phần nhỏ:
- PC Registers : Với mỗi luồng sẽ được chia vào PC Register riêng để giữ địa chỉ của lệnh thực thi hiện tại sau khi lệnh được thực thi. Sau đó PC Register sẽ cập nhậ lệnh tiếp theo.
- Native Method stacks : Đối với mỗi luồng, một ngăn xếp phương thức riêng sẽ được tạo.
4.3. Execution Engine (Công cụ thực thi)
Bytecode sẽ được assign cho Runtime Data Area, và sẽ được thực thi bới Execution Engine. Execution Engine đọc bytecode từng mảng một.
- Interpreter : Trình thông dịch dịch bytecode nhanh, nhưng thực thi chậm. Nhược điểm của trình thông dịch là khi một phương thức được gọi nhiều lần, mỗi lần cần một thông dịch mới.
- JIT Compilier : Trình biên dịch JIT vô hiệu hóa nhược điểm của trình thông dịch (interpreter). Execution Engine sẽ sử dụng trợ giúp của trình thông dịch trong việc chuyển đổi bytecode, nhưng khi thấy mã lặp lại , nó sẽ sử dụng trình biên dịch JIT. Nó biên dịch toàn bộ bytecode và thay đổi nó thành mã gốc. Mã này sẽ được sử dụng khi các phương thức bị gọi lặp lại nhiều lần, điều này giúp cảu thiện hiệu năng của hệ thống.
- Garbage Collector : Thu thập và loại bỏ các đối tượng được khởi tạo nhưng không sử dụng.
5. Biên dịch và thực thi mã Java trong Java VM
Hãy xem xét quá trình này cho JAVA. Trong main của bạn, bạn có hai method f1 và f2.
- Phương thức main được lưu trữ trong tệp a1.java
- f1 được lưu trữ trong tệp dưới dạng a2.java
- f2 được lưu trữ trong tệp dưới dạng a3.java
Trình biên dịch sẽ biên dịch ba tệp và tạo ra 3 tệp .class tương ứng chứa BYTE code. Không giống như C, không có liên kết được thực hiện.
Java VM hoặc Máy ảo Java nằm trên RAM. Trong quá trình thực thi, sử dụng class loader, class files được đưa vào RAM. Tại đây BYTE code được xác minh cho tính bảo mật.
Tiếp theo, Execution Engine sẽ chuyển đổi bytecode thành mã máy gốc. Đây chỉ là trong thời gian biên dịch. Đây là một trong những lý do chính tại sao Java tương đối chậm.
Java Stack memory
1. Java Stack là gì?
Stack
là một vùng nhớ được sử dụng để lưu trữ các tham số và các biến local của phương thức mỗi khi một phương thức được gọi ra. Các tham số và các biến local của một phương thức tạo thành một bản ghi kích hoạt, còn được gọi là một stack frame. Các bản ghi kích hoạt được đẩy vào một stack khi phương thức được gọi và đẩy ra khỏi stack khi phương thức trả về. Sự tồn tại tạm thời của các biến này quyết định thời gian sống của các biến.
2. Đặc điểm của của Java Stack memory
- Stack memory được sử dụng cho quá trình thực thi của mỗi thread.
- Bất cứ khi nào gọi 1 hàm, một khối bộ nhớ mới sẽ được tạo trong Stack cho hàm đó để lưu các biến local. Khi hàm thực hiện xong, khối bộ nhớ cho hàm sẽ bị xoá, và giải phóng bộ nhớ trong stack.
- Stack memory bao gồm các giá trị cụ thể của method: các biến local/nguyên thủy và các tham chiếu tới các đối tượng chứa ở trong heap memory được tham chiếu bởi method.
- Stack memory được tham chiếu theo thứ tự LIFO (Last In First Out – vào cuối cùng thì ra đầu tiên). Tức là lưu trữ kiểu ngăn xếp (stack). Khi có một method được thực thi, một block được tạo ra trong stack memory để chứa các biến nguyên thủy local và các tham chiếu tới các object. Khi method kết thúc, block đó sẽ không còn được sử dụng và được phục vụ cho method tiếp theo.
- Stack memory có kích thước rất nhỏ so với Heap memory.
3. Giải thích cách hoạt động của Java Stack
Step 1. Khi chạy chương trình, một thread sẽ khởi tạo và sẽ gọi hàm main ở dòng 1. Một khối bộ nhớ được tạo trong stack cho hàm main().
- int i=1
: Một biến local được tạo, loại primitive được lưu trong cùng khối bộ nhớ của hàm main()
- int j=2
: Một biến local được tạo, loại primitive được lưu trong vùng nhớ tiếp theo của stack main()
- Stack_Test() reff = new Stack_Test()
: một đối tượng được tạo loại Object sẽ được lưu trong bộ nhớ Heap và biến tham chiếu reff
được lưu trong Stack của hàm main()
Step 2. Hàm foo()
thì được gọi, vì vậy nó sẽ tạo một khối mới bộ nhớ trong stack cho hàm foo()
- int param
: Một biến local được tạo (Giá trị được truyền vào), loại primitive được lưu trong cùng khối bộ nhớ của hàm foo()
- int k=3
: Một biến local được tạo, loại primitive được lưu trong vùng nhớ tiếp theo của stack foo()
- Hàm foo() sẽ kết thúc sau khi hàm System.out.println()
được thực thi, vì vậy bộ nhớ trong stack cho hàm foo() sẽ được giải phóng.
Step 3. Theo quy luật LIFO, foo() vào sau chết trước, và sau đó hàm main() cũng kết thúc, bộ nhớ trong stack cho hàm main() cũng được giải phóng.
Step 4. Chương trình kết thúc.
4. Chương trình gây lỗi Stack Overflow Error
Chương trình sau là ví dụ gây ra lỗi Stack Overflow Error trong Java, do thực hiện hàm đệ quy dẫn tới không thể giải phóng được bộ nhớ Stack gây tràn bộ nhớ.
public class Show_StackOverFlowError {
public static void main(String[] args) {
methodOne();
}
public static void methodOne(){
System.out.println("Method One");
methodTwo();
}
public static void methodTwo(){
System.out.println("Method Two");
methodOne();
}
}
Output:
Method One
Method Two
Method One
Method Two
Method One
Method Two
.
.
.
.
.
.
Exception in thread "main" java.lang.StackOverflowError
at java.base/sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:695)
at java.base/java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:578)
at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:292)
at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:281)
at java.base/sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
at java.base/java.io.OutputStreamWriter.write(OutputStreamWriter.java:211)
at java.base/java.io.BufferedWriter.flushBuffer(BufferedWriter.java:120)
at java.base/java.io.PrintStream.write(PrintStream.java:526)
at java.base/java.io.PrintStream.print(PrintStream.java:666)
at java.base/java.io.PrintStream.println(PrintStream.java:803)
at Show_StackOverFlowError.methodOne(Show_StackOverFlowError.java:16)
at Show_StackOverFlowError.methodTwo(Show_StackOverFlowError.java:22)
at Show_StackOverFlowError.methodOne(Show_StackOverFlowError.java:17)
at Show_StackOverFlowError.methodTwo(Show_StackOverFlowError.java:22)
at Show_StackOverFlowError.methodOne(Show_StackOverFlowError.java:17)
Java Heap - Stack memory
Phân biệt cách hoạt động và cấp phát của Heap và Stack memory trong Java
Xem lại:
1. Heap memory
Heap là một vùng nhớ trong bộ nhớ được sử dụng để lưu trữ các đối tượng khi từ khóa new được gọi ra, các biến static và các biến toàn cục (biến instance).
2. Stack memory
Stack là một vùng nhớ được sử dụng để lưu trữ các tham số và các biến local của phương thức mỗi khi một phương thức được gọi ra. Các tham số và các biến local của một phương thức tạo thành một bản ghi kích hoạt, còn được gọi là một stack frame. Các bản ghi kích hoạt được đẩy vào một stack khi phương thức được gọi và đẩy ra khỏi stack khi phương thức trả về. Sự tồn tại tạm thời của các biến này quyết định thời gian sống của các biến.
3. Heap and Stack memory
Chương trình sau là ví dụ cách quản lý và cấp phát bộ nhớ Heap và Stack trong Java
public class Heap_Stack {
//main() method thread creates space in stack memory
public static void main(String[] args) {
// primitive datatype created inside main() method space in stack memory
int i=1;
// Object created in heap memory and its refference obj in stack memory
Object obj = new Object();
// Heap_Stack Object created in heap memory and its refference objnew in stack memory
Heap_Stack objnew = new Heap_Stack();
// New space for foo() method created in the top of the stack memory
objnew.foo(obj);
}
private void foo(Object p) {
// String for p.toString() is created in String Pool and refference str created in stack memory
String str = p.toString();
System.out.println(str);
}
}
Step 1. Khi chạy chương trình, một thread sẽ khởi tạo và sẽ gọi hàm main ở dòng 1. Một khối bộ nhớ được tạo trong stack cho hàm main().
-
int i=1
: Một biến local được tạo, loại primitive được lưu trong cùng khối bộ nhớ của hàm main() -
Object obj = new Object()
: một đối tượng được tạo loại Object sẽ được lưu trong bộ nhớ Heap và biến tham chiếuobj
được lưu trong Stack của hàm main() -
Heap_Stack objnew = new Heap_Stack()
: một đối tượng được tạo loại Object sẽ được lưu trong bộ nhớ Heap và biến tham chiếuobjnew
được lưu trong Stack của hàm main()
Step 2. Hàm foo()
thì được gọi, vì vậy nó sẽ tạo một khối mới bộ nhớ trong stack cho hàm foo()
-
Object p
: Một biến localp
được tạo (Giá trị được truyền vào), loại tham chiếu được lưu trong cùng khối bộ nhớ của hàm foo() -
String str=p.toString()
: Một biến String được tạo được lưu trong String Pool, biên tham chiếustr
được lưu trong vùng nhớ tiếp theo của stack foo() - Hàm foo() sẽ kết thúc sau khi hàm
System.out.println()
được thực thi, vì vậy bộ nhớ trong stack cho hàm foo() sẽ được giải phóng.
Step 3. Theo quy luật LIFO, foo() vào sau chết trước, và sau đó hàm main() cũng kết thúc, bộ nhớ trong stack cho hàm main() cũng được giải phóng.
Step 4. Chương trình kết thúc.
4. Heap vs Stack memory
Sự khác nhau Java Heap và Stack memory
# |
Heap Memory |
Stack Memory |
1 | Java Heap Memory là bộ nhớ được sử dụng ở runtime để lưu các Objects. Bất cứ khi nào ở đâu trong chương trình của bạn khi bạn tạo Object thì nó sẽ được lưu trong Heap (thực thi toán tử new). | Stack Memory là bộ nhớ để lưu các biến local trong hàm và lời gọi hàm ở runtime trong một Thread java. Các biến local bao gồm: loại nguyên thuỷ (primitive), loại tham chiếu tới đối tượng trong heap (reference), khai báo trong hàm, hoặc đối số được truyền vào hàm. |
2 | Thời gian sống của bộ nhớ Heap dài hơn so với Stack. Thời gian sống của object phụ thuộc vào Garbage Collection của java. Garbage Collection sẽ chạy trên bộ nhớ Heap để xoá các Object không được sử dụng nữa, nghĩa là object không được referece trong chương trình. |
Thường có thời gian sống ngắn. |
3 | Các objects trong Heap đều được truy cập bởi tất cả các các nơi trong ứng dụng, bởi các threads khác nhau. | Stack chỉ được sử dụng cho một Thread duy nhất. Thread ngoài không thể truy cập vào được. |
4 | Cơ chế quản lý của Heap thì phức tạp hơn. Heap đuơc phân làm 2 loại Young-Generation, Old-Generation. Đọc thêm về Garbage Collection để hiểu rõ hơn. | Cơ chế hoạt động là LIFO (Last-In-First-Out), chạy sau chết trước. |
5 | Dung lượng Heap thường lớn hơn Stack. | Bộ nhớ stack thường nhỏ. |
6 | Sử dụng -Xms và -Xmx để định nghĩa dung lượng bắt đầu và dung lượng tối đa của bộ nhớ heap. | Dùng -Xss để định nghĩa dung lượng bộ nhớ stack. |
7 | Khi Heap bị đầy chương trình hiện lỗi java.lang.OutOfMemoryError : Java Heap Space |
Khi stack bị đầy bộ nhớ, chương trình phát sinh lỗi: java.lang.StackOverFlowError
|
8 | Truy cập vùng nhớ Heap chậm hơn Stack. | Truy cập stack nhanh hơn Heap |
9 | Dung lượng sử dụng của Heap sẽ tăng giảm phụ thuộc vào Objects sử dụng. | Bất cứ khi nào gọi 1 hàm, một khối bộ nhớ mới sẽ được tạo trong Stack cho hàm đó để lưu các biến local. Khi hàm thực hiện xong, khối bộ nhớ cho hàm sẽ bị xoá, và giải phóng bộ nhớ trong stack. |
Catch an SQL exception with the error code ORA-00942 in Java
Để bắt ngoại lệ SQL bằng mã lỗi ORA-00942 trong Java, bạn có thể sử dụng lớp SQLException cùng với cách xử lý ngoại lệ dành riêng cho Oracle. Lỗi ORA-00942 chỉ ra rằng bảng hoặc dạng xem bạn đang cố truy cập không tồn tại hoặc bạn không có các quyền cần thiết để truy cập vào nó.
Đây là ví dụ xử lý ngoại lệ cụ thể này trong Java:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class OracleSQLExceptionExample {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// Initialize the database connection
connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:yourdb", "username", "password");
statement = connection.createStatement();
// Attempt to execute an SQL statement that may cause ORA-00942
// Replace "YourTable" with the actual table name you are trying to access
String sql = "SELECT * FROM YourTable";
statement.executeQuery(sql);
} catch (SQLException e) {
if (e.getErrorCode() == 942) {
// ORA-00942: Table or view does not exist
System.out.println("Caught ORA-00942: Table or view does not exist");
// You can handle this exception here or log it as needed.
} else {
// Handle other SQL exceptions
e.printStackTrace();
}
} finally {
try {
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}