1- Kiểu nguyên thủy và kiểu tham chiếu trong Java.
Trước hết chúng ta cần phân biệt kiểu nguyên thủy (Primitive type) và kiểu tham chiếu (reference type) trong java.
Trong Java chúng ta có 8 kiểu nguyên thủy.
- Kiểu nguyên thủy (Primitive type)
Type | Bit/Bytes | Range |
---|---|---|
boolean | 1 bit | True or False |
char | 16 bit/ 2 bytes | 0 to 65535 |
byte | 8 bit/ 1 byte | -128 to 127 |
short | 16 bit/ 2 bytes | -32768 to 32767 |
int | 32 bits/ 4 bytes | -2147483648 to 2147483647 |
long | 64 bits/ 8 bytes | -9,223,372,036,854,775,808 to -9,223,372,036,854,775,808 (-2^63 to -2^63) |
float | 32 bits/ 4 bytes | -3.4028235 x 10^38 to 3.4028235 x 10^38 |
double | 64 bits/ 8 bytes | -1.7976931348623157 x 10^308 to 1.7976931348623157 x 10^308 |
Tất cả các kiểu khác đều mở rộng từ Object, chúng là các kiểu tham chiếu.
2- Stack và Heap là gì?
Biến của bạn được lưu trữ trên Stack (Ngăn xếp) hoặc Heap, vậy Stack và Heap là gì?
- TODO
3- Kiểu nguyên thủy lưu trữ thế nào trên bộ nhớ
Trước hết bạn phải hiểu rằng, Java không đảm bảo rằng mỗi biến sẽ tương ứng với một vị trí trên bộ nhớ. chẳng hạn Java sẽ tối ưu theo cách biến 'i' sẽ được lưu trữ trên bộ đăng ký (register), hoặc thậm trí không được lưu trữ ở đâu cả, nếu trình biên dịch nhận thấy rằng bạn không bao giờ sử dụng giá trị của nó, hoặc nó có thể được dõi theo thông qua code và sử dụng các giá trị phù hợp một cách trực tiếp.
Xem một đoạn code:
1
2
3
4
5
6
7
8
9
| // Create a variable 'a', and assin valud to it. int a = 100 ; // Assign new value a = 200 ; // Create variable b, assign value a int b = a; |
Khi đó Java thực hiện các thao tác sau:
- TODO
4- Kiểu tham chiếu lưu trữ thế nào trên bộ nhớ
Khi bạn sử dụng toán tử new (Ví dụ new Object()), Java sẽ tạo ra một thực thể mới trên bộ nhớ. Bạn khai báo một biến và khởi tạo giá trị của nó thông qua toán tử new, chẳng hạn Object a = new Object(); Java sẽ tạo ra một thực thể mới trong bộ nhớ, và một tham chiếu 'a' trỏ tới vị trí bộ nhớ của thực thể vừa được tạo ra.
Khi bạn khai báo một biến b Object b = a; không có thực thể nào được tạo ra trong bộ nhớ, Java chỉ tạo ra một tham chiếu 'b', trỏ tới vị cùng vị trí mà 'a' đang trỏ tới.
Khi bạn khai báo một biến b Object b = a; không có thực thể nào được tạo ra trong bộ nhớ, Java chỉ tạo ra một tham chiếu 'b', trỏ tới vị cùng vị trí mà 'a' đang trỏ tới.
1
2
3
4
5
6
7
8
| // Khai báo và khởi tạo đối tượng. Object a = new Object(); // Khởi tạo lại đối tượng a = new String( "Text" ); // Khai báo đối tượng 'b' và gán nó bằng đối tượng 'a'. Object b = a; |
- TODO
5- Các kiểu so sánh trong Java
Trong Java có 2 kiểu so sánh:
Toán tử equals(..) là method chỉ dùng cho các kiểu tham chiếu.
- Sử dụng toán tử ==
- Sử dụng phương thức (method) equals(..)
Toán tử equals(..) là method chỉ dùng cho các kiểu tham chiếu.
6- So sánh các kiểu nguyên thủy
Với kiểu nguyên thủy chúng ta chỉ có duy nhất một cách so sánh bằng toán tử ==, các kiểu nguyên thủy so sánh với nhau thông qua giá trị của chúng.
1
2
3
4
5
6
7
8
9
10
11
12
| // Tạo ra một biến a, gán bởi giá trị 200 // Một vùng bộ nhớ (1) được tạo ra chứa giá trị 200. int a = 200 ; // Tạo ra một biến b, gán giá trị 200. // Một vùng bộ nhớ (2) được tạo ra chứa giá trị 200. int b = 200 ; // Mặc dù 'a' và 'b' trỏ tới 2 vùng bộ nhớ khác nhau. // So sánh a == b sẽ cho kết quả true. // Vì kiểu nguyên thủy so sánh với nhau là giá trị. boolean c = (a == b); |
7- So sánh các kiểu tham chiếu
7.1- Sử dụng toán tử == so sánh các kiểu tham chiếu
- Khi bạn so sánh 2 đối tượng tham chiếu theo toán tử ==, có nghĩa là so sánh vị trí mà 2 đối tượng tham chiếu này trỏ tới. Về bản chất là kiểm tra xem 2 tham chiếu đó có cùng trỏ tới một thực thể trên bộ nhớ hay không.
- Hãy xem ví dụ:
- ReferenceEeDemo.java123456789101112131415161718192021222324252627282930313233343536373839404142
package
org.o7planning.tutorial.comparation;
public
class
ReferenceEeDemo {
public
static
void
main(String[] args) {
// Chú ý: Với String 2 cách khởi tạo đối tượng sau là không giống nhau:
// Bạn có thể xem trong tài liệu nói về String.
String str1 =
"String 1"
;
String str2 =
new
String(
"String 1"
);
// Toán tử new tạo ra vùng bộ nhớ (1)
// Chứa String "This is text"
// Và s1 là một tham chiếu trỏ đến (1)
String s1 =
new
String(
"This is text"
);
// Toán tử new tạo ra vùng bộ nhớ (2)
// Chứa String "This is text"
// Và s2 là một tham chiếu trỏ đến (2)
String s2 =
new
String(
"This is text"
);
// Sử dụng toán tử == so sánh s1 và s2.
// Kết quả ra false.
// Nó rõ ràng khác với suy nghĩ của bạn.
// Lý do là với kiểu tham chiếu
// toán tử == so sánh vị trí mà chúng trỏ tới.
boolean
e1 = (s1 == s2);
// false
System.out.println(
"s1 == s2 ? "
+ e1);
// Không có toán tử new nào.
// Java tạo ra một tham chiếu có tên 'obj'
// Và trỏ tới vùng bộ nhớ mà s1 trỏ tới.
Object obj = s1;
// 2 tham chiếu 'obj' và 's1' đang cùng trỏ tới 1 vùng bộ nhớ.
// Kết quả trả về true
boolean
e2 = (obj == s1);
// true
System.out.println(
"obj == s1 ? "
+ e2);
}
}
- Kết quả chạy chương trình
7.2- Sử dụng equals(..) so sánh các kiểu tham chiếu
- StringComparationDemo.java1234567891011121314151617181920212223242526
package
org.o7planning.tutorial.comparation;
public
class
StringComparationDemo {
public
static
void
main(String[] args) {
String s1 =
new
String(
"This is text"
);
String s2 =
new
String(
"This is text"
);
// So sánh s1 và s2 thông qua method equals(..)
boolean
e1 = s1.equals(s2);
// Kết quả sẽ là true
System.out.println(
"first comparation: s1 equals s2 ? "
+ e1);
s2 =
new
String(
"New s2 text"
);
boolean
e2 = s1.equals(s2);
// Kết quả sẽ là false
System.out.println(
"second comparation: s1 equals s2 ? "
+ e2);
}
}
- Kết quả chạy chương trình:
7.3- Ghi đè method equals(Object)
- Phương thức equals(Object) là phương thức có sẵn của class Object, mọi class con đều được thừa kế method này. Trong một số tình huống bạn có thể ghi đè method này tại class con.
- NumberOfMedals.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
package
org.o7planning.tutorial.comparation.equals;
// Số lượng Huy chương
public
class
NumberOfMedals {
// Số huy chương vàng
private
int
goldCount;
// Số huy chương bạc.
private
int
silverCount;
// Số huy chương đồng
private
int
bronzeCount;
public
NumberOfMedals(
int
goldCount,
int
silverCount,
int
bronzeCount) {
this
.goldCount = goldCount;
this
.silverCount = silverCount;
this
.bronzeCount = bronzeCount;
}
public
int
getGoldCount() {
return
goldCount;
}
public
int
getSilverCount() {
return
silverCount;
}
public
int
getBronzeCount() {
return
bronzeCount;
}
// Ghi đè method equals(Object) của class Object.
@Override
public
boolean
equals(Object other) {
// Nếu đối tượng so sánh null trả về false.
if
(other ==
null
) {
return
false
;
}
// Nếu đối tượng cần so sánh không phải kiểu NumberOfMedals
// thì trả về false.
if
(!(other
instanceof
NumberOfMedals)) {
return
false
;
}
// Ép kiểu
NumberOfMedals otherNoM = (NumberOfMedals) other;
// Nếu số lượng huy chương vàng, bạc, đồng bằng nhau
// trả về true.
if
(
this
.goldCount == otherNoM.goldCount
&&
this
.silverCount == otherNoM.silverCount
&&
this
.bronzeCount == otherNoM.bronzeCount) {
return
true
;
}
return
false
;
}
}
- NumberOfMedalsComparationDemo.java1234567891011121314151617181920212223
package
org.o7planning.tutorial.comparation.equals;
public
class
NumberOfMedalsComparationDemo {
public
static
void
main(String[] args) {
// Thành tích của đội Mỹ
NumberOfMedals american =
new
NumberOfMedals(
40
,
15
,
15
);
// Thành tích của đội Nhật
NumberOfMedals japan =
new
NumberOfMedals(
10
,
5
,
20
);
// Thành tích của đội Hàn Quốc
NumberOfMedals korea =
new
NumberOfMedals(
10
,
5
,
20
);
System.out.println(
"Medals of American equals Japan ? "
+ american.equals(japan));
System.out.println(
"Medals of Korea equals Japan ? "
+ korea.equals(japan));
}
}
- Kết quả chạy ví dụ
Vấn đề:
– Với các kiểu dữ liệu có sẵn, mình không bàn tới, nếu nó là int, char… thì có thể dùng dấu == để so sánh. Còn với các đối tượng khác, như String hay Date thì gọi hàm equals(), hai cách đem lại cùng một kết quả.– Nhưng với các đối tượng bạn tự tạo ra thì sao? Xét đoạn chương trình sau://vd1import java.util.Comparator;
import java.util.HashSet;public class HelloWorld {public static void main(String[] agrs) {Person a = new Person();
Person b = new Person();a.name=new String(“hiepnq”);
b.name=”hiepnq”;a.age=22;
b.age=22;HashSet set = new HashSet();
set.add(a);
set.add(b);System.out.println(set.size());
}
}class Person {
public String name;
public int age;
}-> mặc dù 2 instance a và b có các cặp thuộc tính tương ứng bằng nhau (a.name.equals(b.name) ==true và a.age==b.age), nhưng khi in ra màn hình, HashSet set lại coi a và b là 2 đối tượng khác nhau, nên set.size() in ra lại là 2.– Vấn đề này thoạt nhìn thì tưởng như chẳng có gì, nhưng nếu sử dụng trong kĩ thuật lập trình hiện đại, thì lại là một điều vô cùng tai hại: bạn sẽ khó có thể kiểm soát được việc so sánh 2 đối tượng, điều này khiến cho một số chức năng khác có liên quan tới so sánh sẽ không thực hiện được (vd: sắp xếp – tìm kiếm).Vấn đề: Tại sao lại có hiện tượng như trên? Cơ chế nào khiến cho 2 đối tượng được gọi là bằng nhau?– Trả lời cho vấn đề này, chúng ta tìm hiểu kĩ một chút về java nói riêng và các ngôn ngữ lập trình hiện đại nói chung (C# cũng tuân theo nguyên tắc này…). Trong bộ nhớ, khác với C/C++, Java quản lí đối tượng theo mã băm (hashCode), có nghĩa là: địa chỉ bộ nhớ các đối tượng sẽ được “băm” (hash) theo một công thức nào đó, trở thành một số int duy nhất, không trùng lặp khi trên cùng 1 máy tính.– Tất cả các đối tượng trong Java đều có 1 gốc đối tượng cha duy nhất là Object, bản thân đối tượng Object có 2 phương thức: equals() và hashCode(). equals() sẽ dùng để so sánh các đối tượng, các lớp dẫn xuất từ Object có thể định nghĩa thế nào là bằng nhau nhờ Override hàm này (vd: String, Date …) và nếu chúng ta sử dụng thông thường, chúng ta cũng vẫn có thể sử dụng hàm này để so sánh, tham khảo đoạn code sau://vd2import java.util.Comparator;
import java.util.HashSet;public class HelloWorld {public static void main(String[] agrs) {Person a = new Person();
Person b = new Person();a.name=new String(“hiepnq”);
b.name=”hiepnq”;a.age=22;
b.age=22;System.out.println(a.equals(b));
}
}class Person {
public String name;
public int age;@Override
public boolean equals(Object obj){
if(obj instanceof Person){
if(((Person)obj).name.equals(this.name)){
return true;
}
}
return false;
}
}-> kết quả trả về là true, tốt rồi…– Vấn đề kế tiếp được đặt ra: trong vd1 thì sao? nếu ta override hàm equals()? Lớp Person sẽ có dạng:class Person {
public String name;
public int age;@Override
public boolean equals(Object obj){
if(obj instanceof Person){
if(((Person)obj).name.equals(this.name)){
return true;
}
}
return false;
}
}-> nhưng nếu thay vào vd1, thì set.size() vẫn là 2 – có nghĩa là: khi ta sử dụng, thì ta có thể coi là bằng nhau khi Perso.name bằng nhau, nhưng với các collection Set và Map (cả HashTable) thì chúng không cho rằng 2 đối tượng trên đã bằng nhau? tại sao vậy?– Như đã nói ở trên, Object còn có hàm hashCode() trả về mã băm của đối tượng, mặc định, hai đối tượng được coi là bằng nhau, nếu chúng có hashCode() bằng nhau, có nghĩa là, chúng trỏ tới cùng 1 đối tượng trên vùng nhớ HEAP… Vậy, để khắc phục điều này, ta cần phải override cả hàm hashCode() nữa…import java.util.Comparator;
import java.util.HashSet;public class HelloWorld {public static void main(String[] agrs) {Person a = new Person();
Person b = new Person();a.name=new String(“hiepnq”);
b.name=”hiepnq”;a.age=22;
b.age=22;HashSet set = new HashSet();
set.add(a);
set.add(b);System.out.println(set.size());
}
}class Person {
public String name;
public int age;@Override
public boolean equals(Object obj){
if(obj instanceof Person){
if(((Person)obj).name.equals(this.name)){
return true;
}
}
return false;
}@Override
public int hashCode(){
return age;
}
}-> giờ thì kết quả là 1 rồi– Vậy, muốn đối tượng bằng nhau trọn vẹn khi và chỉ khi: hàm equals() trả về true, và hàm hashCode() trả về cùng một giá trị.Kết luận:
– Định nghĩa 2 đối tượng bằng nhau được coi là 1 trong những vấn đề cốt lõi và then chốt nhất trong Core Java và cả C# căn bản nữa, nắm bắt được yếu tố này, các bạn có thể tự định nghĩa các đối tượng bằng nhau một các rất mềm dẻo
Nhận xét
Đăng nhận xét