- [Java Core] B1: Tại sao nên học Java, syntax cơ bản
- [Java Core] B2: Class và Object (Lớp và đối tượng)
- [Java Core] B3: Cách Java quản lý dữ liệu
- [Java Core] B4: Tính chất đóng gói, kế thừa và đa hình trong Java
- [Java Core] B5: Tính chất trừu tượng, Interface và Abstract Class trong Java
- [Java Core] B6: Design Pattern Iterator. Iterable và Collection trong Java
- [Java Core] B7: Exception trong Java
Tổng quan về Class và Object
![[Java Core] B2: Class và Object (Lớp và đối tượng) 12 f0035 01 - quochung.cyou PTIT](https://learning.oreilly.com/api/v2/epubs/urn:orm:book:9781492091646/files/assets/f0035-01.png)
Class và Object (Lớp và Đối tượng) là hai trong những khái niệm quan trọng nhất của ngôn ngữ lập trình hướng đối tượng (OOP).
Sự khác biệt chính giữa một Class và một Object trong Java là:
- Class là một mô hình chi tiết để bạn sử dụng tạo ra các Object. Class định nghĩa tất cả các thuộc tính và các phương thức cần thiết của một Object.
- Mỗi Object phải thuộc một Class nào đó. Và một Object là một thể hiện của Class. Tất cả các Object thuộc về cùng một Class có cùng các thuộc tính và các phương thức.
Syntac :
ClassAobj=newClassA();
Vậy thực chất Java đã làm những gì?
- Khi ta sử dụng
new Example()
, Java sẽ dành ra một số lượng vừa đủ ô nhớ trong bộ nhớ để lưu trữ các giá trị của một đối tượngExample
và sau đó khởi tạo đối tượng đó. - Sau khi khởi tạo, Java sẽ lấy địa chỉ của vùng nhớ đó, gán vào cho “biến”
obj
. Biếnobj
được định nghĩa là kiểuExample
, hay tức là nếu ta truy cập vào địa chỉ mà biếnobj
lưu thì vùng nhớ đó sẽ là vùng nhớ của một đối tượngExample
.
Điều cần nói
- Hiểu đơn giản, một object trong Java thực chất như 1 cái điều khiển, đang được cài đặt cho 1 cái tivi thực chất ở trong đó. Khi ta bấm nút trên điều khiển, thực chất ta đang gửi tín hiệu đến tivi, và tivi sẽ thực hiện hành động tương ứng. Vậy nên, khi ta gọi hàm
obj.printInfo()
, thực chất ta đang gửi tín hiệu đến đối tượngobj
, và đối tượngobj
sẽ thực hiện hành động tương ứng. - Lúc này, cứ tạm nhớ nó chỉ là cái điều khiển, chứ không phải cái tivi thật sự, sâu hơn ta sẽ tìm hiểu tiếp ở dưới. (Phần equal và hashcode)
Từ khóa this
- Từ khoá this trong Java được sử dụng để tham chiếu đến đối tượng hiện tại. Từ khoá this có thể được sử dụng để tham chiếu bất kỳ biến nào của đối tượng hiện tại.
Ví dụ:
classPerson {
String name;
Person(String name) {
this.name = name;
}
}
- Trong ví dụ trên, ta có một class Person, trong đó có thuộc tính name. Ta muốn gán giá trị cho thuộc tính name, ta sẽ tạo một hàm khởi tạo, và trong hàm khởi tạo đó, ta sẽ gán giá trị cho thuộc tính name. Vì tên của tham số trùng với tên của thuộc tính, nên ta sẽ dùng từ khóa this để tham chiếu đến thuộc tính name.
- Từ khóa this cũng có thể được sử dụng để tham chiếu đến các phương thức của đối tượng hiện tại.
classPerson {
String name;
Person(String name) {
this.name = name;
}
voidprintInfo() {
System.out.println(this.name);
}
}
Access modifier
![[Java Core] B2: Class và Object (Lớp và đối tượng) 14 access modifier trong java - quochung.cyou PTIT](https://viettuts.vn/images/java/access-modifier-trong-java.png)
Access modifier là gì
- Access modifier là một từ khóa trong Java, nó được dùng để chỉ định quyền truy cập của một thuộc tính hoặc một phương thức. Có 4 loại access modifier trong Java, đó là public, private, protected, default.
- Bài này chỉ cần nói default, private, public
Public
- Khi ta dùng từ khóa public với một thuộc tính hoặc một phương thức, thì thuộc tính hoặc phương thức đó có thể được truy cập từ bên ngoài class.
publicclassPerson {
public String name;
publicvoidsetName(String name) {
this.name = name;
}
}
Private
- Khi ta dùng từ khóa private với một thuộc tính hoặc một phương thức, thì thuộc tính hoặc phương thức đó chỉ có thể được truy cập từ bên trong class.
publicclassPerson {
private String name;
privatevoidsetName(String name) {
this.name = name;
}
}
Protected
- Khi ta dùng từ khóa protected với một thuộc tính hoặc một phương thức, thì thuộc tính hoặc phương thức đó chỉ có thể được truy cập từ bên trong class, hoặc từ bên trong các class con của class đó.
publicclassPerson {
protected String name;
protectedvoidsetName(String name) {
this.name = name;
}
}
Default
- Khi ta không dùng từ khóa nào với một thuộc tính hoặc một phương thức, thì thuộc tính hoặc phương thức đó chỉ có thể được truy cập từ bên trong class, hoặc từ bên trong các class cùng package với class đó.
publicclassPerson {
String name;
voidsetName(String name) {
this.name = name;
}
}
Tại sao cần dùng access modifier
- Khi ta dùng access modifier, ta có thể kiểm soát được quyền truy cập của các thuộc tính và phương thức. Ví dụ, ta có một class Person, trong đó có thuộc tính name. Ta muốn thuộc tính name này chỉ có thể được truy cập từ bên trong class Person, và không thể được truy cập từ bên ngoài class Person. Vì vậy, ta sẽ dùng từ khóa private với thuộc tính name.
publicclassPerson {
private String name;
}
Điều cần nói
- Trong thực tế, ví dụ như 1 cái ô tô, bên trong có rất nhiều cấu kiện tinh vi. Khi ta khởi động xe, các cấu kiện hoạt động với nhau, ta không biết chúng làm gì, làm như nào, chỉ biết là khi bật xe, thì xe chạy.
- Các cấu kiện đó là những thứ “private”, chỉ bên trong một class “Oto” với nhau mới biết, bên ngoài không biết. Điều này giúp cho việc sử dụng xe dễ dàng hơn, thứ bên ngoài không cần biết quá nhiều về xe bên trong.
Getter và Setter
Getter
- Getter là một hàm dùng để lấy giá trị của một thuộc tính. Ví dụ, ta có một class Person, trong đó có thuộc tính name. Để lấy giá trị của thuộc tính name, ta sẽ tạo một hàm getName().
classPerson {
String name;
String getName() {
return name;
}
}
Setter
- Setter là một hàm dùng để gán giá trị cho một thuộc tính. Ví dụ, ta có một class Person, trong đó có thuộc tính name. Để gán giá trị cho thuộc tính name, ta sẽ tạo một hàm setName().
classPerson {
String name;
voidsetName(String name) {
this.name = name;
}
}
Tại sao cần dùng Getter và Setter
- Khi ta dùng Getter và Setter, ta có thể kiểm soát được quyền truy cập của các thuộc tính. Ví dụ, ta có một class Person, trong đó có thuộc tính name. Ta muốn thuộc tính name này chỉ có thể được truy cập từ bên trong class Person, và không thể được truy cập từ bên ngoài class Person. Vì vậy, ta sẽ dùng từ khóa private với thuộc tính name, và tạo một hàm getName() và setName() để lấy và gán giá trị cho thuộc tính name.
classPerson {
private String name;
String getName() {
return name;
}
voidsetName(String name) {
this.name = name;
}
}
Điều cần nói
- Hiểu đơn giản hơn, khi để tất cả thuộc tính private và chỉ getter setter public, vô hình chung ta sẽ dẫn mọi truy cập vào 1 class chỉ còn 1 con đường, đó là getter và setter
- Điều này giúp ta có được “quyền kiểm soát”, “control” ở 1 class đó, ta có thể kiểm soát được các giá trị được gán vào thuộc tính, và các giá trị được lấy ra từ thuộc tính.
- Ví dụ, ta có một class là “NhaHang”, nó có một phương thức lấy số điện thoại, tuy nhiên ta muốn khi lấy số điện thoại, ta sẽ chuẩn hoá lại giá trị đó, ví dụ như thêm dấu +84 vào đầu, hoặc thêm dấu – vào giữa, hoặc thêm dấu cách vào giữa, hoặc thêm dấu ngoặc vào đầu cuối, … Vậy nên, ta sẽ tạo một hàm getter, và trong hàm getter đó, ta sẽ chuẩn hoá lại giá trị trước khi trả về.
class NhaHang {
private String soDienThoai;
String getSoDienThoai() {
// Chuẩn hoá lại số điện thoạireturn soDienThoai;
}
void setSoDienThoai(String soDienThoai) {
this.soDienThoai = soDienThoai;
}
}
- Như này, ta đảm bảo số điện thoại được lấy ra dùng cho class khác luôn là số điện thoại chuẩn hoá, không cần phải chuẩn hoá lại nữa. Vì cách duy nhất để lấy số điện thoại ra là dùng hàm getter, và hàm getter đã chuẩn hoá rồi.
- Tương tự với setter, giả sử ta có một class ConNguoi với thuộc tính chiều cao. Chiều cao thì không thể âm được, vậy nên ta sẽ tạo một hàm setter, trong đó ta sẽ kiểm tra giá trị trước khi gán vào thuộc tính.
class ConNguoi {
private int chieuCao;
void setChieuCao(int chieuCao) {
if (chieuCao < 0) {
this.chieuCao = 0;
} else {
this.chieuCao = chieuCao;
}
}
}
- Điều này đảm bảo tính toàn vẹn của dữ liệu, giúp cho mọi thứ chính xác và chặt chẽ
- Ngoài ra, nó cũng giúp tăng tính bảo mật, không lộ quá nhiều thông tin bên trong class ra bên ngoài, mà mọi thứ chỉ có thể truy cập, sử dụng qua các phương thức ta đã định nghĩa sẵn. (Tính chất này còn được gọi là encapsulation, đóng gói trong OOP)
Từ khoá Static
Static là gì
- Static là một từ khóa trong Java, nó có thể được dùng với biến, hàm, class. Static có nghĩa là tĩnh, tức là nó chỉ tồn tại ở một vị trí duy nhất, tồn tại ngay cả khi chưa tạo ra đối tượng. Vì vậy, khi ta dùng static với biến, hàm, class, ta có thể gọi chúng mà không cần tạo ra đối tượng.
- Ví dụ, trong các thư viện Java, ta thường thấy các hàm, biến, class static. Vì vậy, ta có thể gọi chúng mà không cần tạo ra đối tượng.
Math.sqrt(2);
Từ khoá static với biến
- Khi ta dùng static với biến, thì biến đó sẽ được tạo ra ngay cả khi chưa tạo ra đối tượng. Vì vậy, ta có thể gọi biến đó mà không cần tạo ra đối tượng. Biến static này là tồn tại duy nhất, tức là nó chỉ có một giá trị duy nhất, và nó sẽ được sử dụng chung cho tất cả các đối tượng.
class Person {
static int count=0;
Person() {
count++;
}
}
Hàm main static
- Hàm main() là một hàm static, nó được gọi khi chương trình bắt đầu chạy. Ví dụ:
public classHelloWorld {
public staticvoidmain(String[] args) {
System.out.println("Hello World");
}
}
Lí do hàm main là static
- Như ta biết, để gọi 1 hàm từ 1 đối tượng, ta phải tạo ra 1 đối tượng đó trước.
- Vậy nếu hàm main không phải là static, thì ta phải tạo ra 1 đối tượng của class HelloWorld trước, rồi gọi hàm main từ đối tượng đó.
- Vì vậy, hàm main static thường xuất hiện ở class khởi nguồn. Như vậy, Java có thể gọi thẳng hàm main luôn mà không cần khởi tạo class trước đó.
Lí do hàm static chỉ có thể gọi hàm static khác
- Tuy nhiên, 1 hàm static chỉ có thể gọi các hàm static khác, và chỉ có thể truy cập các biến static khác. Lí do là vì, khi ta gọi hàm static, ta không cần tạo ra 1 đối tượng, vậy nên ta không thể truy cập các biến không phải static của đối tượng đó được. Và vì ta không tạo ra 1 đối tượng, nên ta không thể gọi các hàm không phải static của đối tượng đó được.
- Cứ tưởng tượng đơn giản, các hàm, biến, class static luôn chỉ tồn tại độc nhất, tồn tại ngay cả khi chưa tạo ra đối tượng. Vì vậy, các hàm, biến, class static luôn có thể được gọi mà không cần tạo ra đối tượng. Nếu mà 1 hàm static gọi 1 hàm không static, thì lỡ đâu hàm không static đó lại cần phải truy cập đến các biến không static, thì làm sao mà truy cập được, vì chưa tạo ra đối tượng mà.
Về equal và hashcode, ==
- Trong Java, có 2 cách để so sánh 2 đối tượng với nhau, đó là so sánh bằng toán tử
==
và so sánh bằng hàmequal()
. Tuy nhiên, 2 cách này lại có 2 cách hoạt động khác nhau. - Khi ta so sánh 2 đối tượng bằng toán tử
==
, ta đang so sánh 2 địa chỉ của 2 đối tượng đó. Nếu 2 đối tượng đó có cùng địa chỉ, thì toán tử==
sẽ trả vềtrue
, ngược lại thì trả vềfalse
. - Khi ta so sánh 2 đối tượng bằng hàm
equal()
, ta đang so sánh 2 nội dung của 2 đối tượng đó. Nếu 2 đối tượng đó có cùng nội dung, thì hàmequal()
sẽ trả vềtrue
, ngược lại thì trả vềfalse
. equal()
dùng để so sánh nội dung của 2 đối tượng. (Lưu ý: toán tử==
sẽ là so sánh giá trị của 2 biến đó, hay chính xác hơn là so sánh địa chỉ, chứ không phải so sánh nội dung)hashcode()
dùng để xác định vị trí của đối tượng đó trong một bảng băm (cấu trúc dữ liệu mà đa số sẽ sử dụng)
![[Java Core] B2: Class và Object (Lớp và đối tượng) 15 - quochung.cyou PTIT](https://miro.medium.com/v2/resize:fit:500/1*x5UK5p0mqcr3EBKwlKNRwg.png)
![[Java Core] B2: Class và Object (Lớp và đối tượng) 16 f0055 06 - quochung.cyou PTIT](https://learning.oreilly.com/api/v2/epubs/urn:orm:book:9781492091646/files/assets/f0055-06.png)
![[Java Core] B2: Class và Object (Lớp và đối tượng) 17 f0057 01 - quochung.cyou PTIT](https://learning.oreilly.com/api/v2/epubs/urn:orm:book:9781492091646/files/assets/f0057-01.png)
- Vì như đã dạy ở trên, ta khi tạo 1 biến, thì thực chất nó chỉ là 1 tham chiếu
- Vì vậy, các String có cùng 1 giá trị, dùng == để so sánh sẽ trả về false, vì chúng có các tham chiếu khác nhau
- – Ta có thể thấy, khi gán 1 String khác, nếu không dùng new gán vào string cũ, chúng sẽ bằng nhau vì chúng cùng tham chiếu vào 1 địa chỉ
String str1=newString("Hello");
String str2=newString("Hello");
System.out.println(str1 == str2); // false
```java
String str1 = new String("Hello");
String str2 = str1;
System.out.println(str1 == str2); // true
- Để giải quyết vấn đề này, ta dùng hàm
equal()
để so sánh nội dung của 2 String
String str1=newString("Hello");
String str2=newString("Hello");
System.out.println(str1.equal(str2)); // true
int a=5;
int b=5;
Integer a1=5;
Integer b1=5;
Integer a2=100;
Integer b2=100;
Integer a3=500;
Integer b3=500;
System.out.println(a == b); // true
System.out.println(a1 == b1); // true vì đây là tham chiếu, nhưng mà Java có cơ chế cache Integer từ -128 đến 127, nên khi gán 2 số từ -128 đến 127, chúng sẽ cùng tham chiếu đến 1 địa chỉ, nên == sẽ trả về true
System.out.println(a2 == b2); // true vì đây là tham chiếu, nhưng mà Java có cơ chế cache Integer từ -128 đến 127, nên khi gán 2 số từ -128 đến 127, chúng sẽ cùng tham chiếu đến 1 địa chỉ, nên == sẽ trả về true
System.out.println(a3 == b3); // false
System.out.println(a3.equal(b3)); // true vì đây là so sánh nội dung, không phải so sánh tham chiếu
String và String Builder trong Java
- String là một class trong Java, nó được sử dụng để lưu trữ một chuỗi các ký tự. Một đối tượng String được tạo ra bằng cách sử dụng từ khóa new và có thể được khởi tạo bằng một chuỗi ký tự hoặc một đối tượng String khác.
- Lí do là vì, khi ta tạo biến String mới, ta chỉ đang tạo 1 tham chiếu đến vùng nhớ của String đó.
- String là một dạng immutable, tức là nó không thể thay đổi được. Vì vậy, khi ta thay đổi giá trị của String, ta đang tạo ra một String mới, và tham chiếu đến nó.
String str="Hello";
str = "World";
- Để nhập vào String từ bàn phím, ta sử dụng hàm
nextLine()
của Scanner
Scanner sc=newScanner(System.in);
String str= sc.nextLine();
- Ngoài ra String có rất nhiều hàm hỗ trợ, dạy chính có thể đảo qua vài hàm chính
String str="Hello";
str = str.toLowerCase(); // "hello"
str = str.toUpperCase(); // "HELLO"
str = str.replace("l", "L"); // "HeLLo"Stringstr1="Hello "
str1 = str1.trim(); // "Hello"
- String Builder là một class trong Java, nó được sử dụng để lưu trữ một chuỗi các ký tự. Một đối tượng String Builder được tạo ra bằng cách sử dụng từ khóa new và có thể được khởi tạo bằng một chuỗi ký tự hoặc một đối tượng String khác.
- String Builder là một dạng mutable, tức là nó có thể thay đổi được. Vì vậy, khi ta thay đổi giá trị của String Builder, ta không tạo ra một String Builder mới, mà chỉ thay đổi giá trị của String Builder đó.
StringBuilderstr=newStringBuilder("Hello");
str.append(" World"); // "Hello World"
str.deleteCharAt(0); // "ello World"
str.delete(0, 4); // " World"
str.insert(0, "Hello"); // "Hello World"
str.reverse(); // "dlroW olleH"
- Để nhập vào String Builder từ bàn phím, ta sử dụng hàm
nextLine()
của Scanner như String - Vậy StringBuilder khác String ở chỗ nào
Stringstr="Hello";
str = str + " World";
- Khi ta thực hiện phép cộng chuỗi, ta đang tạo ra một String mới, và tham chiếu đến nó. Vậy nên khi ta thực hiện phép cộng chuỗi nhiều lần, ta sẽ tạo ra rất nhiều String mới, và tham chiếu đến chúng. Điều này sẽ làm tốn rất nhiều bộ nhớ, và làm chậm chương trình.
- Vì vậy, khi ta cần thay đổi giá trị của chuỗi nhiều lần, ta nên sử dụng StringBuilder, để tránh tạo ra quá nhiều String mới.
- Bảng so sánh
String | StringBuilder |
---|---|
String là immutable | StringBuilder là mutable |
Khi thay đổi ít lần, String tốn ít bộ nhớ hơn | Khi thay đổi ít lần, StringBuilder không được hết khả năng và tốn bộ nhớ nhiều hơn (lí do bên dưới) |
Khi thay đổi nhiều lần, String tốn nhiều bộ nhớ hơn và thời gian hơn | Khi thay đổi nhiều lần, StringBuilder tốn ít bộ nhớ hơn và thời gian hơn |
inttotal=50000;
Strings="";
for (inti=0; i < total; i++) { s += String.valueOf(i); }
// 4828msStringBuildersb=newStringBuilder();
for (inti=0; i < total; i++) { sb.append(String.valueOf(i)); }
// 4ms
- Lí do StringBuilder có thể động như vậy vì nó sử dụng cơ chế mảng động, tức là khi khai báo 1 String độ dài 10, thực chất Java dành ra nhiều ô nhớ hơn để khi thêm độ dài mới vào tốc độ sẽ nhanh hơn (do không phải di chuyển toàn bộ vùng nhớ sang phần khác). Trong khi đó, String thực chất là một mảng tĩnh, khi khai báo 1 String độ dài 10, Java chỉ dành ra đúng 10 ô nhớ, khi thêm độ dài mới vào, Java sẽ phải di chuyển toàn bộ vùng nhớ sang phần khác, điều này sẽ làm chậm tốc độ xử lí.
- Tuy nhiên, do dùng ô nhớ ít hơn, String tốn ít bộ nhớ hơn StringBuilder. Vì vậy, khi ta cần thay đổi giá trị của chuỗi ít lần, ta nên sử dụng String, để tránh tốn bộ nhớ.
Kĩ thuật mảng động: Nhân đôi mảng
- Hiểu đơn giản, mảng luôn được khởi tạo số lượng phần từ nhiều hơn thực tế, để khi thêm phần tử vào mảng, ta không cần phải di chuyển toàn bộ mảng sang phần khác.
- Điều này thực chất đang hi sinh bộ nhớ nhiều hơn để có hiệu quả thời gian tốt hơn trong đa số trường hợp.
![[Java Core] B2: Class và Object (Lớp và đối tượng) 18 image 4 - quochung.cyou PTIT](http://213.35.113.17:9002/wp-content/uploads/2023/11/image-4-1024x411.png)
![[Java Core] B2: Class và Object (Lớp và đối tượng) 19 image 5 - quochung.cyou PTIT](http://213.35.113.17:9002/wp-content/uploads/2023/11/image-5.png)
7. Inner class
Inner class là gì
- Inner class là một class được khai báo bên trong một class khác. Inner class có thể được khai báo là static hoặc non-static. Inner class có thể truy cập tất cả các biến và phương thức của class bên ngoài nó.
classOuterClass {
intx=5;
classInnerClass {
inty=10;
}
}
Inner class static
- Inner class static là một class được khai báo bên trong một class khác, và được khai báo là static. Inner class static có thể truy cập tất cả các biến và phương thức của class bên ngoài nó.
classOuterClass {
intx=5;
staticclassInnerClass {
inty=10;
}
}
Inner class non-static
- Inner class non-static là một class được khai báo bên trong một class khác, và không được khai báo là static. Inner class non-static có thể truy cập tất cả các biến và phương thức của class bên ngoài nó.
classOuterClass {
intx=5;
classInnerClass {
inty=10;
}
}
Tại sao cần dùng Inner class
- Inner class có thể được dùng để tạo ra một class chỉ được sử dụng bởi một class khác. Ví dụ, ta có một class Person, trong đó có một class Address. Class Address này chỉ được sử dụng bởi class Person, nên ta có thể tạo ra một inner class Address.
classPerson {
String name;
Address address;
classAddress {
String street;
String city;
String state;
}
}
9. Sử dụng class từ file khác trong Java
- từ việc import Scanner, import ArrayList, import String, … có thể import bất kì class nào trong Java, và sử dụng nó trong class của mình.
import java.util.Scanner;
classPerson {
String name;
int age;
voidinput() {
Scannersc=newScanner(System.in);
name = sc.nextLine();
age = sc.nextInt();
}
}
- Tuy nhiên, có thể tự tạo ra các class của riêng mình, và sử dụng nó trong class của mình. Ví dụ có các Class như QuanLySinhVien, SinhVien, MonHoc, … thì có thể tạo ra các file QuanLySinhVien.java, SinhVien.java, MonHoc.java, … và sử dụng chúng trong class của mình.
import java.util.Scanner;
classSinhVien {
String name;
int age;
voidinput() {
Scannersc=newScanner(System.in);
name = sc.nextLine();
age = sc.nextInt();
}
}
classQuanLySinhVien {
SinhVien[] danhSachSinhVien;
voidinput() {
Scannersc=newScanner(System.in);
intn= sc.nextInt();
danhSachSinhVien = newSinhVien[n];
for (inti=0; i < n; i++) {
danhSachSinhVien[i] = newSinhVien();
danhSachSinhVien[i].input();
}
}
}
- Lưu ý, trong 1 file thì chỉ có thể có 1 class public, và tên của class đó phải trùng với tên của file. Ví dụ, trong file QuanLySinhVien.java, ta chỉ có thể có 1 class public, và tên của class đó phải là QuanLySinhVien.
// QuanLySinhVien.java
public classQuanLySinhVien {
...
}