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

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.

Java Core

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:
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)

2. Tính kế thừa (Inheritance)

3. Tính đa hình (polymorphism)

4. Tính trừu tượng (abstraction)

Java Core

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 Core

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):

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 Core

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.

java-object.png

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 Core

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 Không Không Không
default Không Không
protected Không
public
Java Core

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
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 Core

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
5. Đặc điểm của Abstract Method
Java Core

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:

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ị:

Java Core

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.

variable.png

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

types-of-variables1.png

1. Biến local - Local variable

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

class A {
	int data = 50; //instance variable  
	void method() {
		System.out.println(data);
	}
} //end of class

3. Biến static - Static variable

class A {
	static int m = 100; //static variable  
	void method() {
		System.out.println(m);
	}
} //end of class
Java Core

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 đó:

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:

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:

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:

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 Core

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:

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 Core

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 + " ");
        }
 
    }
}

arrays-in-java-.jpg

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 Core

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      
  1. abstract - Chỉ ra rằng class hoặc phương thức sẽ phải thực thi sau đó trong một subclass.
  2. 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.
  3. boolean - Kiểu dữ liệu chỉ có giá trị True và False
  4. break – A control statement for breaking out of loops
  5. byte – A data type that can hold 8-bit data values
  6. case – Used in switch statements to mark blocks of text
  7. catch – Catches exceptions generated by try statements
  8. char – A data type that can hold unsigned 16-bit Unicode characters
  9. class -Declares a new class
  10. continue -Sends control back outside a loop
  11. default -Specifies the default block of code in a switch statement
  12. do -Starts a do-while loop
  13. double – A data type that can hold 64-bit floating-point numbers
  14. else – Indicates alternative branches in an if statement
  15. enum – A Java keyword used to declare an enumerated type. Enumerations extend the base class.
  16. extends -Indicates that a class is derived from another class or interface
  17. final -Indicates that a variable holds a constant value or that a method will not be overridden
  18. finally -Indicates a block of code in a try-catch structure that will always be executed
  19. float -A data type that holds a 32-bit floating-point number
  20. for -Used to start a for loop
  21. if -Tests a true/false expression and branches accordingly
  22. implements -Specifies that a class implements an interface
  23. import -References other classes
  24. instanceof -Indicates whether an object is an instance of a specific class or implements an interface
  25. int – A data type that can hold a 32-bit signed integer
  26. interface – Declares an interface
  27. long – A data type that holds a 64-bit integer
  28. native -Specifies that a method is implemented with native (platform-specific) code
  29. new – Creates new objects
  30. null -Indicates that a reference does not refer to anything
  31. package – Declares a Java package
  32. private -An access specifier indicating that a method or variable may be accessed only in the class it’s declared in
  33. 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)
  34. 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)
  35. return -Sends control and possibly a return value back from a called method
  36. short – A data type that can hold a 16-bit integer
  37. static -Indicates that a variable or method is a class method (rather than being limited to one particular object)
  38. 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.
  39. super – Refers to a class’s base class (used in a method or class constructor)
  40. switch -A statement that executes code based on a test value
  41. synchronized -Specifies critical sections or methods in multithreaded code
  42. this -Refers to the current object in a method or constructor
  43. throw – Creates an exception
  44. throws -Indicates what exceptions may be thrown by a method
  45. 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)
  46. try -Starts a block of code that will be tested for exceptions
  47. void -Specifies that a method does not have a return value
  48. volatile -Indicates that a variable may change asynchronously
  49. while -Starts a while loop

** The keywords const and goto are reserved, even they are not currently in 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 Core

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.

if_statement_flow_chart_diagram.png

Cú pháp:

if (conditon) {
	action 1 statements;
}
else {
	action 2 statements;
}

Trong đó:

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 Core

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.

if_else_if_statement_diagram.png

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 Core

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.

if_statement_flow_chart_diagram.png

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 Core

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 đó:

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 Core

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.

while_loop_flow_chart_diagram.png

Cú pháp:

while(condition)
{
	action statement;
	:
	:
}

Trong đó:

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 Core

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.

do_while_loop_flowchart_diagram.png

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 Core

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.

for_loop_flow_chart_diagram.png

1. For loop basic

Cú pháp:

for (statement 1; statement 2; statement 3) {
  // code block to be executed
}

Trong đó:

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 Core

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 đó.

break_statement_flowchart_diagram.png

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 Core

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 đó.

continue_statement_flowchart_diagram.png

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 Core

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 

ExceptionHierarchy.png

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ệ:

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 Core

Java Try-catch exception - Khối Try-catch trong Java

Khối try-catch được sử dụng để thi hành mô hình catchthrow 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 Core

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 Core

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:

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 Core

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 Core

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:

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 throwthrows 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 Core

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 Core

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 Core

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.classB.class

Java Core

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 Core

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 Core

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).

java-streams.png

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 Core

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 InputStreamOutputStream:

java-streams.png

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 Core

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:

java-streams.png

# 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 Core

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 Class

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.

string-hierachy.png

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 dayweekday đượ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 Class

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

StringBuffer-in-Java.png

2. Khởi tạo đối tượng lớp StringBuffer

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()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

+ 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:

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 Class

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

StringBuilder-Class-Diagram.png

2. Khởi tạo đối tượng lớp StringBuilder 

3. Các phương thức của lớp StringBuilder

+ 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 Class

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 Class

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()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 Class

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

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 Class

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 Class

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ử

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 Class

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 Class

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 Class

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

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.

java-design-patterns.png

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:

2. Lợi ích của Design Pattern

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.

Java-design-patterns-types-edureka-1.png

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:

design-patterns-relationship.jpg

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.

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.

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.

Java Design Pattern

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.

singleton.png

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.

structure-en.png

Sử dụng Singleton khi chúng ta muốn:

Một số trường hợp sử dụng của Singleton Pattern thường gặp:

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:

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

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:

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:

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 ý:

3. Tổng kết

  1. 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.
  2. 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.
  3. 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.
  4. 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 Design Pattern

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.

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:

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ể.

AbstractFactoryPattern-2.png

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 Design Pattern

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:

- Lợi ích của Factory Pattern:

2. Thực thi Factory Design Pattern

Một Factory Pattern bao gồm các thành phần cơ bản sau:

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-pattern-java-450x327.png

- 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 Design Pattern

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).

image-1628927539847.png

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.

image-1628927659777.png

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.

image-1628927744859.png

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.

image-1628927904159.png

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.

image-1628927997578.png

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 đó.

image-1628928120267.png

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

image-1628928268424.png

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ả

image-1628928621014.png

// 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

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

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-features2.png

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.

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.

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.

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 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:

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:

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:

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:

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:

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:

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:

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ư:

Java Features

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 ) );

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ả

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 Features

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 Features

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:

Những đặc điểm quan trọng về phương thức default trong interface:

  1. 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ó.
  2. 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ó
  3. 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
  4. 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.
  5. 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.
  6. 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ú ý:

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:

  1. 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ó.
  2. 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 …
  3. 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
  4. 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
  5. 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 Features

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 Features

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 Guideline

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

Heap_memory_space.png

2. Java Heap memory

Java-Memory-Model-450x186.png

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 đó:

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 Guideline

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.

Java-heap.png

- 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.

java-heap-eden.png

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.

java-gc-promotion.png

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.

gc-marking.png

2. Normal deleting: Trình Garbage Collector sẽ xóa các Object không còn sử dụng.

gc-deleting.png

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.

gc-compacting.png

 

Java Guideline

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:

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

Java-JVM-e1512883356815.png

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:

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 đó.

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:

- 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:

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.

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.

Mo-ta-qua-trinh-bien-dich-ma-java-1.jpg

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.

Mo-ta-qua-trinh-bien-dich-ma-java-2.jpg

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.

Mo-ta-qua-trinh-bien-dich-ma-java-3.jpg

Java Guideline

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
3. Giải thích cách hoạt động của Java Stack

stack_memory_space.png

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 Guideline

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).

Heap_memory_space.png

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.

stack_memory_space.png

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().

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()

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.

heap_stack.png

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.
Java Guideline

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();
            }
        }
    }
}