[Java Core] B4: Tính chất đóng gói, kế thừa và đa hình trong Java

This entry is part 4 of 8 in the series Java Core

Đóng gói

  • Đóng gói là một trong những tính chất quan trọng của lập trình hướng đối tượng
  • Đóng gói giúp che dấu thông tin bên trong của một đối tượng, chỉ cho phép các đối tượng khác tương tác thông qua các phương thức được cung cấp
  • Đóng gói giúp giảm sự phức tạp của chương trình, giúp dễ dàng bảo trì và mở rộng chương trình
Encapsulation
  • Ví dụ, trong một chiếc xe ô tô, ta có rất nhiều cấu phận và thông số. Nó có động cơ, vô lăng, bình xăng, đánh lửa, …
  • Nhưng ta thường không tương tác trực tiếp được vào chúng, mà thông qua các phương thức như bấm ga, bấm còi, đạp phanh, …
  • Khi bấm ga, có thể cả một quy trình rất dài đang xảy ra, như động cơ chạy, bình xăng bơm xăng, đánh lửa đốt xăng, …
  • Nhưng ta không cần quan tâm đến những thứ đó, ta chỉ cần bấm ga, và xe sẽ chạy
  • Ngoài ra, đóng gói giúp ta đảm bảo việc dữ liệu sẽ được giấu khỏi các đối tượng khác, và chỉ có các phương thức được cung cấp mới có thể tương tác với dữ liệu đó. Giúp đảm bảo tính toàn vẹn của dữ liệu.

Ví dụ:

  • getter, setter là một ví dụ về đóng gói. Nhờ getter, setter, ta có thể đảm bảo dữ liệu được cập nhật từ đối tượng khác sẽ luôn theo ý của mình, hay dữ liệu được lấy ra từ đối tượng khác sẽ luôn đúng theo ý của mình
  • Ví dụ trong các thư viện của Java, ta có thể thấy rất nhiều ví dụ về đóng gói. Ví dụ như trong class Scanner, ta có thể thấy rằng các biến được khai báo là private, và ta không thể truy cập trực tiếp vào chúng. Nhưng ta có thể sử dụng các phương thức như nextInt(), nextDouble(), nextLine(), … để lấy dữ liệu từ bàn phím. (Có thể ctrl + click vào các phương thức đó để xem code, sẽ thấy Scanner sử dụng các biến private để lấy dữ liệu từ bàn phím)
image 26 - quochung.cyou PTIT
  • Đóng gói xuất hiện ở mọi nơi: Từ cả cái máy tính của bạn chỉ cần 1 nút bật, một game với cả trăm nghìn class chỉ cần 1 lần nhấn Play sẽ bắt đầu chạy, các thư viện Java với vô vàn chức năng đã được đóng gói và cho phép ta sử dụng qua những hàm rất đơn giản.
  • Trong lập trình phần mềm, yêu cầu của một chương trình thay đổi liên tục đáp ứng nhu cầu của khách hàng. Việc đóng gói và phân tách công việc của những bộ phận khác nhau là cực kì quan trọng, khi đó, ta sẽ cố gắng giới hạn những thay đổi chỉ bên trong 1 bộ phận nhỏ, độc lập nhất có thể và ít ảnh hưởng tới nhiều chỗ khác.
image 28 - quochung.cyou PTIT

Kế thừa

  • Kế thừa là một trong những tính chất quan trọng của lập trình hướng đối tượng
  • Kế thừa giúp ta có thể sử dụng lại các đặc tính của một đối tượng khác, giúp giảm sự lặp lại của code, giúp dễ dàng bảo trì và mở rộng chương trình
  • Kế thừa giúp ta có thể tạo ra các đối tượng mới từ các đối tượng đã có, và có thể thêm các đặc tính mới vào các đối tượng mới đó
Inheritance
image 27 - quochung.cyou PTIT
  • Ta nhận thấy kế thừa, hay sơ đồ cây xuất hiện ở mọi nơi trong cuộc sống. Như một chiếc ô tô có thể gọi là xe bốn bánh, xe bốn bánh có thể coi là phương tiện giao thông, …. Con người có thể coi là động vật. trong sinh học ta có các giống loài, trong vật lý có nhiều loại sóng khác nhau cùng là sóng,có chung tính chất là sóng, …
image 29 - quochung.cyou PTIT

Ví dụ:

  • Ví dụ ta có nhiều class cần tạo như Chó, Mèo, Gà, Vịt, … Ta nhận thấy chúng đều có chung 1 đặc điểm là chúng đều là động vật, và có chung 1 số đặc điểm như là có thể ăn, có thể ngủ, có thể chạy, có thể bơi, … Ta có thể tạo ra một class cha là Động vật, và các class trên sẽ kế thừa từ class Động vật. Như vậy, ta có thể sử dụng lại các đặc điểm của class Động vật cho các class con, và có thể thêm các đặc điểm mới vào các class con đó, ví dụ Chó thì có thêm đặc điểm là sủa, Mèo thì có thêm đặc điểm là kêu meo meo, …
  • Các phương thức, thuộc tính của class cha sẽ được các class con kế thừa, và có thể sử dụng lại. Ngoài ra, các class con có thể thêm các phương thức, thuộc tính mới vào các class con đó. Điều này giúp giảm bớt các code dư thừa, giúp dễ dàng bảo trì và mở rộng chương trình
  • Có thể ở các class nhỏ hiện tại các bạn chưa thấy kế thừa hữu ích. Tuy nhiên khi bài toán ngày càng lớn lên, đặc biệt thiên về domain chuyên ngành, 1 class sẽ có rất nhiều thuộc tính và phương thức. Ví dụ: một cái bánh bình thường ở tạp hoá, có thể có lên tới 10-15 thuộc tính như màu sắc, hình dạng, giá, hạn sử dụng, ngày sản xuất, thành phần sản phẩm, các chứng chỉ an toàn thực phẩm, mã barcode, hay có các biến như “có thể cho động vật ăn”, … sau đó lại có nhiều loại bánh khác nhau nữa
image 17 - quochung.cyou PTIT

Class Object

Class Object
  • Trong Java, mọi class đều kế thừa từ class Object
  • Class Object là class cha của tất cả các class khác
  • Class Object có các phương thức như equals()toString()hashCode()clone()finalize()wait()notify()notifyAll(), …
  • Các phương thức này được các class khác kế thừa, và có thể sử dụng lại
  • Có nhiều phương thức các bạn chưa cần đào sâu. Tuy nhiên tạm thời có thể để ý nó có các phương thức quan trọng như equals, toString, hashCode. Đây là các phương thức thường được dùng.

Đa hình

image 30 - quochung.cyou PTIT
  • Đa hình là một trong những tính chất quan trọng của lập trình hướng đối tượng
  • Đa hình giúp ta có thể sử dụng các đối tượng khác nhau, nhưng có cùng một kiểu dữ liệu, giúp giảm sự lặp lại của code, giúp dễ dàng bảo trì và mở rộng chương trình
Polymorphism

Override

  • Override là một kĩ thuật trong đa hình, giúp ta có thể thay đổi cách thức hoạt động của một phương thức đã có sẵn trong class cha
  • Override giúp ta có thể sử dụng các đối tượng khác nhau, nhưng có cùng một class cha, và khi gọi các phương thức, ta sẽ thấy mỗi đối tượng sẽ thực hiện theo cách của nó, chứ không phải cách của đối tượng khác

Cú pháp:

publicclassClassCha {
    @OverridepublicvoidmethodName() {
        // code
    }
}

classClassConextendsClassCon {
    @OverridepublicvoidmethodName() {
        // code
    }
}

Overload

  • Overload là một kĩ thuật trong đa hình, giúp ta có thể tạo ra nhiều phương thức cùng tên, nhưng khác nhau về tham số truyền vào
  • Overload giúp ta có thể sử dụng các phương thức khác nhau, nhưng có cùng một tên, và khi gọi các phương thức, ta sẽ thấy mỗi phương thức sẽ thực hiện theo cách của nó, chứ không phải cách của phương thức khác

Cú pháp:

publicclassClassCha {
    publicvoidmethodName(int a) {
        // code
    }

    publicvoidmethodName(int a, int b) {
        // code
    }
}

  • Dễ thấy overload trong các phương thức của class Math, như Math.max()Math.min()Math.abs()Math.pow()Math.sqrt()Math.round()Math.floor()Math.ceil()Math.random(), … Chúng đều có cùng một tên là Math, nhưng khác nhau về tham số truyền vào. Ví dụ nếu truyền vào số nguyên, thì xử lý khác, truyền số thực thì xử lý khác, …

Phân biệt Overload và Override

OverloadOverride
Cùng tên, khác tham sốCùng tên, cùng tham số
Cùng classKhác class
Overload giúp ta có thể tạo ra nhiều phương thức cùng tên, nhưng khác nhau về tham số truyền vàoOverride là một kĩ thuật trong đa hình, giúp ta có thể thay đổi cách thức hoạt động của một phương thức đã có sẵn trong class cha
Overload chạy trong compile timeOverride chạy trong run time

Đa hình compile time và runtime

  • Đa hình compile time là đa hình mà chúng ta có thể thấy được ngay trong quá trình code, ví dụ như overload. Đa hình compile time sẽ được thực hiện trong compile time, tức là khi chúng ta build chương trình, chúng ta sẽ thấy các phương thức được overload sẽ được thực hiện theo cách của nó, chứ không phải cách của phương thức khác
  • Đa hình runtime là đa hình mà chúng ta không thể thấy được ngay trong quá trình code, ví dụ như override. Đa hình runtime sẽ được thực hiện trong runtime, tức là khi chúng ta chạy chương trình, chúng ta sẽ thấy các phương thức được override sẽ được thực hiện theo cách của nó, chứ không phải cách của phương thức khác

Ví dụ: Khi viết code lúc compile ta đã thấy ngay phương thức nào được gọi, nhưng khi chạy chương trình, ta mới thấy phương thức nào được thực hiện của override

Ví dụ:

  • Ta có các class Chó, Mèo, Bò. Chúng đều kế thừa từ class Động vật. Ta có thể tạo ra một mảng các đối tượng Động vật, và có thể thêm vào đó các đối tượng Chó, Mèo, Bò. Như vậy, ta có thể sử dụng các đối tượng khác nhau, nhưng có cùng một kiểu dữ liệu, là Động vật.
  • Chúng có thể có chung 1 hàm là keu(), nhưng khi gọi hàm keu() trên mảng các đối tượng Động vật, ta sẽ thấy mỗi đối tượng sẽ keu() theo cách của nó, chứ không phải cách của đối tượng khác. Như vậy, ta có thể sử dụng các đối tượng khác nhau, nhưng có cùng một kiểu dữ liệu, và khi gọi các phương thức, ta sẽ thấy mỗi đối tượng sẽ thực hiện theo cách của nó, chứ không phải cách của đối tượng khác.
publicclassDongVat {
    publicvoidkeu() {
        System.out.println("Keu cua dong vat");
    }
}

publicclassChoextendsDongVat {
    @Overridepublicvoidkeu() {
        System.out.println("Gau gau");
    }
}

publicclassMeoextendsDongVat {
    @Overridepublicvoidkeu() {
        System.out.println("Meo meo");
    }
}

publicclassBoextendsDongVat {
    @Overridepublicvoidkeu() {
        System.out.println("Um bo");
    }
}

Bài tập kế thừa và đa hình

  • Tạo ra một class ConNguoi, có các thuộc tính là tên, tuổi, giới tính, …
  • Có các class con là SinhVien, NhanVien, HocSinhCap3 kế thừa từ class ConNguoi
  • Các class con có thể có thêm các thuộc tính riêng của nó, ví dụ SinhVien có thuộc tính là mã sinh viên, NhanVien có thuộc tính là mã nhân viên, …
  • Các class con có thể có thêm các phương thức riêng của nó, ví dụ SinhVien có phương thức là học, NhanVien có phương thức là làm việc, …
  • Có 1 phương thức chung của 3 class con là lamViecBuoiSang(), nhưng mỗi class con sẽ thực hiện theo cách của nó, chứ không phải cách của class con khác. Ví dụ SinhVien sẽ đi học đại học, NhanVien sẽ đi làm, HocSinhCap3 sẽ đi học ở trường cấp 3

[Java Core] B3: Cách Java quản lý dữ liệu

This entry is part 3 of 8 in the series Java Core

Mục lục:

  • Cách Java lưu trữ dữ liệu (Kiểu dữ liệu nguyên thuỷ, Object, Wrapper class, Auto-boxing, Auto-unboxing)
  • Constructor, Super, This
  • Pass by value
  • Garbage Collector

I. Cách Java lưu trữ dữ liệu (Kiểu dữ liệu nguyên thuỷ, Object, Wrapper class, Auto-boxing, Auto-unboxing)

1. Kiểu dữ liệu nguyên thuỷ (Primitive data type)

  • Trong Java, có 8 kiểu dữ liệu nguyên thuỷ (Primitive data type): byteshortintlongfloatdoublebooleanchar.
  • Kiểu dữ liệu nguyên thuỷ là các kiểu dữ liệu mà không phải là đối tượng, không có các phương thức, thuộc tính, … như các đối tượng.
  • Tức là, bạn không thấy các phương thức toString()equals()hashCode(), … trong các kiểu dữ liệu nguyên thuỷ.
inta=1;

// a không có hàm hay biến như các object// a.toString() không tồn tại// a.equals() không tồn tại// a.(gì đó) không tồn tại
Primitive data type
  • Ví dụ, khi khai báo 1 biến int a = 1;, thì Java sẽ cấp phát 1 vùng nhớ có kích thước 4 byte để lưu giá trị 1 vào biến a. Thực chất trong bộ nhớ nó được lưu theo binary là 00000000 00000000 00000000 00000001. (1 byte = 8 bit)
  • Do có các 32 bit, nên int có thể lưu giá trị từ -2^31 đến 2^31 – 1. (1 bit để lưu dấu)
  • Tương tự, các kiểu dữ liệu khác cũng được lưu theo binary, và có kích thước khác nhau.

2. Object

2.1 Object là gì?

image 6 - quochung.cyou PTIT
  • Trong Java, mọi thứ đều là đối tượng (Object). Vậy nên các kiểu dữ liệu nguyên thuỷ cũng là đối tượng.
  • Hiện tại các em chưa học về kế thừa, nhưng cứ tạm hiểu, khi ta nói về 1 tập đối tượng, ví dụ: Xe Ô Tô, hay class Car, có các thuộc tính, phương thức gì đó. Xe máy, hay class Motorbike, cũng có các thuộc tính, phương thức gì đó. Vậy thì Xe Ô Tô và Xe Máy đều là đối tượng, và chúng có các thuộc tính, phương thức giống nhau.
  • Vậy ta gọi tập đối tượng có cả xe máy và xe ô tô, không chứa các phương thức đặc thù như chạydừng, … là gì? Đó là tập đối tượng Xe, hay class Vehicle. Vậy nên, Xe Ô Tô và Xe Máy đều là Xe, hay Vehicle. Tập hợp Vehicle có thể nằm trong tập hợp to hơn là Phương Tiện Giao Thông, hay class Transportation.
  • Trong Java cũng như vậy, mọi class các em tạo ra mặc định đều thuộc 1 tập hợp chung là Object. Và class này luôn cung cấp cho mọi object tạo ra từ các class khác các phương thức, thuộc tính như toString()equals()hashCode(), … như đã nói ở trên.

2.2 Object được lưu thế nào trong Java

image 7 - quochung.cyou PTIT
  • Hiểu đơn giản, giá trị cách lưu object thực sự rất phức tạp, điều đó đều do Java quản lý, và ta không cần quan tâm. Ta chỉ cần biết, khi tạo ra 1 object, ta chỉ lưu 1 địa chỉ tới object đó. Và từ địa chỉ này, ta có thể ra lệnh từ xa để lấy các thuộc tính, phương thức của object đó.
  • Vậy nên, khi ta gán 1 object cho 1 biến, thực chất ta đang gán 1 địa chỉ tới object đó cho biến đó.
Studentobj=newStudent();

// obj là 1 địa chỉ tới object Student
  • Vậy nên ta không thể so sánh 2 object với nhau bằng toán tử ==, mà phải dùng phương thức equals().
Studentobj1=newStudent();

Studentobj2=newStudent();

// obj1 != obj2// obj1.equals(obj2) == false

2.3 Wrapper class

  • Wrapper class là các class được tạo ra để bao bọc các kiểu dữ liệu nguyên thuỷ. Ví dụ: IntegerFloatDoubleBooleanCharacter, …
  • Wrapper class cung cấp các phương thức để thao tác với các kiểu dữ liệu nguyên thuỷ. Ví dụ: Integer.parseInt()Integer.toString()Integer.valueOf(), …
Primitive data typeWrapper class
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter
  • Các kiểu dữ liệu lưu dạng object này tuân theo cách Java lưu trữ object, tức là không lưu trực tiếp object, mà chỉ lưu địa chỉ tới object đó.
Ưu điểmNhược điểm
Có thể sử dụng các phương thức của objectTốn bộ nhớ hơn
  • Ví dụ, ở Java ta có thể chuyển String thành số, từ các số hay từ 1 object bất kì sang String rất nhanh bằng toString(), ngoài ra khi nó là Object, ta có thể sử dụng các kĩ thuật OOP như kế thừa, đa hình, … (sẽ nói ở các buổi sau).
inta=1;

// a không có hàm hay biến như các object// a.toString() không tồn tạiIntegerb=1;

// b có hàm toString()// b.toString() tồn tại

2.4 Auto-boxing và Auto-unboxing

  • Auto-boxing là quá trình chuyển đổi từ kiểu dữ liệu nguyên thuỷ sang kiểu dữ liệu lưu dạng object.
  • Auto-unboxing là quá trình chuyển đổi từ kiểu dữ liệu lưu dạng object sang kiểu dữ liệu nguyên thuỷ.
  • Ví dụ:
inta=1;

Integerb=1;

// Auto-boxingIntegerc= a;

// Auto-unboxingintd= b;

  • Auto-boxing và Auto-unboxing là 2 quá trình tự động, do Java tự động thực hiện. Vậy nên các em không cần phải quan tâm đến nó.
  • Tuy nhiên, khi sử dụng Auto-boxing và Auto-unboxing, các em cần phải cẩn thận, vì nó có thể gây ra lỗi.
Integera=null;

intb= a; // NullPointerException
  • Vậy nên, khi sử dụng Auto-boxing và Auto-unboxing, các em cần phải kiểm tra giá trị null trước khi sử dụng.
Integera=null;

intb= a == null ? 0 : a;

  • Auto-boxing và Auto-unboxing có thể gây ra lỗi khi sử dụng toán tử ==.
Integera=1;

Integerb=1;

Integerc=128;

Integerd=128;

System.out.println(a == b); // true

System.out.println(c == d); // false
  • Autoboxing và Auto-unboxing giúp cho việc sử dụng các kiểu dữ liệu nguyên thuỷ và object trở nên dễ dàng hơn. Ví dụ, khi ta muốn sử dụng HashMap, thì ta không thể sử dụng kiểu dữ liệu nguyên thuỷ là int, mà phải sử dụng Integer.

HashMap<Integer, String> map = newHashMap<>();

map.put(1, "Hello");

map.put(2, "World");

System.out.println(map.get(1)); // Hello

System.out.println(map.get(2)); // World
  • Nếu không có Auto-boxing và Auto-unboxing, thì ta phải làm như sau:

HashMap<Integer, String> map = newHashMap<>();

map.put(Integer.valueOf(1), "Hello");

map.put(Integer.valueOf(2), "World");

System.out.println(map.get(Integer.valueOf(1))); // Hello

System.out.println(map.get(Integer.valueOf(2))); // World
  • Tuy nhiên, trong nhiều trường hợp nên hạn chế dùng các kiểu Object do chúng tốn bộ nhớ
for (Integeri=0; i < 1000000; i++) {

  // ...

}

// Tốn bộ nhớ hơn// Vì i là object, nên phải tạo ra 1 object mới mỗi lần tăng i// Còn nếu dùng int, thì chỉ cần tạo ra 1 biến int i, và tăng i mỗi lần

3. Các phương thức khởi tạo (Constructor) trong Java

  • Constructor là một phương thức đặc biệt, nó được gọi khi ta khởi tạo một đối tượng. Constructor có tên giống với tên class, và không có kiểu trả về.
  • Constructor được sử dụng để khởi tạo các giá trị ban đầu cho đối tượng. Ví dụ như ta có một class Student, trong đó có các thuộc tính nameageaddressmathliteratureenglish. Ta có thể tạo một constructor như sau:
classStudent {
  String name;
  int age;
  String address;
  float math;
  float literature;
  float english;

  Student(String name, int age, String address, float math, float literature, float english) {
    this.name = name;
    this.age = age;
    this.address = address;
    this.math = math;
    this.literature = literature;
    this.english = english;
  }
}

  • Constructor có thể có hoặc không có tham số. Nếu không có tham số, thì constructor đó được gọi là constructor mặc định. Nếu có tham số, thì constructor đó được gọi là constructor có tham số.
  • Constructor có thể có nhiều tham số, và các tham số đó có thể là bất kỳ kiểu dữ liệu nào.
  • Constructor có thể có nhiều hơn một, và các constructor đó có thể có số lượng tham số khác nhau.
  • Constructor có thể gọi constructor khác của cùng một class bằng từ khóa this(). Ví dụ:
classStudent {
  String name;
  int age;
  String address;
  float math;
  float literature;
  float english;

  Student(String name, int age, String address, float math, float literature, float english) {
    this.name = name;
    this.age = age;
    this.address = address;
    this.math = math;
    this.literature = literature;
    this.english = english;
  }

  Student(String name, int age, String address) {
    this(name, age, address, 0, 0, 0);
  }
}

4. Các khái niệm cơ bản về Garbage Collector (GC)

image 8 - quochung.cyou PTIT
image 9 - quochung.cyou PTIT
  • Trong quá trình thực thi chương trình, Java sẽ liên tục tìm các Object không được tham chiếu tới Heap Memory (mang giá trị null hoặc một vài trường hợp khác). Sau đó Garbage Collector sẽ tự động giải phóng bộ nhớ cho các ô nhớ chứa tham chiếu đó.
  • Điều này kiến lập trình viên không cần phải giải phóng bộ nhớ thủ công như C/C++. Thay vào đó, để giải phóng bộ nhớ, lập trình viên chỉ cần loại bỏ tham chiếu của Object.

5. Cách Java truyền tham số | Pass-by-value

Cách Java truyền tham số

5.1. Pass-by-value

  • Trong Java, khi ta truyền tham số vào một hàm, thì tham số đó sẽ được copy ra một vùng nhớ khác, và hàm sẽ thao tác với tham số ở vùng nhớ mới này.
  • Vậy nên, khi ta thay đổi giá trị của tham số trong hàm, thì giá trị của tham số bên ngoài hàm không bị thay đổi.
publicclassMain {
  publicstaticvoidmain(String[] args) {
    inta=1;
    System.out.println(a); // 1
    change(a);
    System.out.println(a); // 1
  }

  publicstaticvoidchange(int a) {
    a = 2;
  }
}

  • Tuy nhiên, nó sẽ lại thay đổi được các thuộc tính của object.
publicclassMain {
  publicstaticvoidmain(String[] args) {
    Studentstudent=newStudent();
    student.name = "A";
    System.out.println(student.name); // A
    change(student);
    System.out.println(student.name); // B
  }

  publicstaticvoidchange(Student student) {
    student.name = "B";
  }
}

classStudent {
  String name;
}

5.2. Tại sao pass-by-value mà String lại thay đổi được?

image 13 - quochung.cyou PTIT
  • Ta hãy quay trở lại khái niệm vừa nhắc tới Khi ta truyền một biến nguyên thuỷ, hay trong trường hợp trên là age, ta đang như tạo một bản sao của một tờ giấy bình thường, tờ giấy đó ghi số là 20, ta đưa cho người khác. Họ sửa chúng thành 90, 100 hay gì đi chăng nữa, thì tờ giấy ban đầu của ta vẫn là 20
  • Tuy nhiên, khi ta truyền một biến reference (tham chiếu) của Object person, ta đang truyền bản sao của “tham chiếu” của nó. Hay đơn giản là, ta đang tạo ra bản sao của một cái điều khiển, vậy thì cái điều khiển bản sao này khi đưa cho người khác, vẫn sẽ bật tắt được tivi ban đầu của mình.

[Java Core] B2: Class và Object (Lớp và đối tượng)

This entry is part 2 of 8 in the series Java Core

Tổng quan về Class và Object

f0035 01 - quochung.cyou PTIT

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ượng Example 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ến obj được định nghĩa là kiểu Example, hay tức là nếu ta truy cập vào địa chỉ mà biến obj lưu thì vùng nhớ đó sẽ là vùng nhớ của một đối tượng Example.
blog12 - quochung.cyou PTIT

Đ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ượng obj, và đối tượng obj 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

access modifier trong java - quochung.cyou PTIT

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àm equal(). 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àm equal() 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)
- quochung.cyou PTIT
f0055 06 - quochung.cyou PTIT
f0057 01 - quochung.cyou PTIT
  • 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
StringStringBuilder
String là immutableStringBuilder là mutable
Khi thay đổi ít lần, String tốn ít bộ nhớ hơnKhi 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ơnKhi 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.
image 4 - quochung.cyou PTIT
image 5 - quochung.cyou PTIT

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


[Java Core] B1: Tại sao nên học Java, syntax cơ bản

This entry is part 1 of 8 in the series Java Core

Giới thiêu về Java

Ngôn ngữ lập trình Java được thiết kế để trở thành một ngôn ngữ không phụ thuộc vào nền tảng (machine-independent). Java có thể chạy trên bất kỳ nền tảng nào miễn là có máy ảo Java (Java Virtual Machine – JVM). Máy ảo Java là một chương trình có thể chạy trên nhiều nền tảng khác nhau mà không cần phải biên dịch lại. Máy ảo Java có thể chạy trên các máy tính, điện thoại, máy tính bảng, máy chủ, … Máy ảo Java có thể được cài đặt trên các hệ điều hành khác nhau như Windows, Linux, Mac OS, …

Java vừa đủ mạnh với nhiều thư viện, tính năng, bảo đảm các sự chặt chẽ, nhưng cũng đồng thời chạy rất nhanh. Java có thể được sử dụng để phát triển các ứng dụng desktop, web, mobile, game, … Java cũng là một trong những ngôn ngữ lập trình được sử dụng nhiều nhất hiện nay.

Điều cần nói

  • Q: Tại sao Java ra đời, tại sao cần hướng đối tượng chứ không code hết vào 1 file
  • A: Trước khi Java ra đời, các ngôn ngữ lập trình khác như C/C++ đã ra đời. Tuy nhiên, các ngôn ngữ này có một số hạn chế như sau:
    • Các ngôn ngữ này không thể chạy trên nhiều nền tảng khác nhau, mà phải biên dịch lại cho từng nền tảng khác nhau. Ví dụ như C++ trên Windows và C++ trên Linux là 2 ngôn ngữ khác nhau, nên phải biên dịch lại.
    • Các ngôn ngữ này không có khái niệm về hướng đối tượng, nên không thể phát triển các ứng dụng lớn, phức tạp.
    • Cơ chế hướng đối tượng của Java giúp chương trình trở thành các đối tượng làm việc với nhau, giúp cho việc phát triển các ứng dụng lớn, phức tạp trở nên dễ dàng hơn. (Sẽ nói sâu hơn vào phần sau)

Ba thành phần nền tảng Java không thể thiếu và cách chúng hoạt động cùng nhau trong các ứng dụng Java của bạn. Cụ thể:

  • JDK (Java Development Kit – Bộ công cụ phát triển Java)
  • JRE (Java Runtime Environment – Môi trường thực thi Java)
  • JVM (Java Virtual Machine – Máy ảo Java)
JDK JRE JVM - quochung.cyou PTIT

Cách Java thực hiện một đoạn code như sau:

  • Với các file code ví dụ là Party.java, nó sẽ được biên dịch bởi trình biên dịch javac trong JDK để tạo ra file Party.class chứa các bytecode.
  • Các file .class và các thư viện cần có cho Java sẽ được tổng hợp bởi JRE
  • Từ các đoạn mã máy trên, JRE sẽ đưa cho JVM để thực thi, và chạy một “máy ảo” độc lập để thực thi chương trình của chúng ta.

Điều đó nghĩa là, chương trình chúng ta đang chạy “tách biệt” với hệ thống (chạy trong JVM). Vậy nên với Java ta chỉ cần viết một lần, biên dịch một lần và nó có thể chạy ở mọi nền tảng mà JVM hỗ trợ. Khả năng đó được lý giải vì khác với các ngôn ngữ như C hay C++, Java được thiết kế theo nhiều lớp khác nhau để tách biệt giữa chương trình Java và hệ thống.

Java Virtual Machine (JVM)

Không như C/C++ khi mà code được biên dịch thì sẽ tạo thành các mã lệnh được làm cho riêng các vi xử lý khác nhau. Code java đầu tiên được biên dịch thành một dạng tổng quát – bytecode, là ngôn ngữ cho JVM chạy. Sau đó JVM mới chạy thành các ngôn ngữ máy cho nền tảng đó.

image 3 - quochung.cyou PTIT

Trình tự hoạt động:

  • Bạn tạo ra một đoạn code (source) với đuôi .java
  • javac compiler biến nó thành 1 file .class
  • File .class sẽ được đọc bởi JVM, và chuyển thành bytecode để chạy trên các nền tảng khác nhau.
image 2 - quochung.cyou PTIT

Điều cần nói

  • Tóm lại, Java sẽ không compile code ra thẳng mã máy, mà sẽ compile ra dạng .class đặc biệt, gọi là bytecode. Sau đó, JVM sẽ đọc bytecode này, và chuyển nó thành mã máy để chạy trên nền tảng đó.

Ưu điểm:

  • Khả năng độc lập với nền tảng, chỉ cần có JVM là được. Người dùng cuối, thiết bị cuối không cần cài cả bộ mingw như C++, họ chỉ cần JVM (là một phần nhỏ trong bộ JDK để lập trình), tức là bộ chỉ dùng để chạy thôi, và bộ thư viện này có sẵn trên hầu hết các thiết bị hiện nay.
  • Do chạy trên máy ảo, nên có tính bảo mật cao hơn, do mỗi chương trình là một máy ảo khác nhau

Nhược điểm:

  • Chậm hơn C++, vì phải chạy trên máy ảo. Code không được ra thẳng mã máy để chạy cho nhanh, và phải qua 1 tầng nữa để JVM biên dịch thành mã máy. Ví dụ: các thiết bị Android theo cơ chế này nên chúng thường mở ứng dụng chậm hơn IOS, và dùng tài nguyên nhiều hơn. Tuy nhiên những lần mở sau thì nhanh hơn, vì JVM đã biên dịch thành mã máy rồi, nên chỉ cần chạy mã máy thôi.
  • Có thể nói sâu hơn nếu muốn: JVM ngày xưa thì chỉ có cơ chế JIT (Just In Time – vừa đúng lúc), tức là khi nào cần mở ứng dụng thì nó mới biên dịch .class thành mã máy, khi biên dịch nó sẽ tự động tối ưu code, tối giản file, … để lần sau nhanh hơn. Sau này nó có thêm cơ chế AOT (Ahead Of Time – trước thời gian), tức là khi cài đặt ứng dụng, nó sẽ biên dịch luôn thành mã máy, để lần sau mở ứng dụng sẽ nhanh hơn. Tuy nhiên, cơ chế này sẽ làm tăng dung lượng file cài đặt, và cũng làm tăng thời gian cài đặt.

Cấu trúc chương trình Java

  • Trong file source code, chứa “class” (lớp)
  • Mỗi “class” chứa nhiều “method” (hàm) khác nhau.
  • Mỗi “method” chứa nhiều “statements” (dòng lệnh) khác nhau.

Ví dụ 1 file class:

image 1 - quochung.cyou PTIT
``` 

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

```
  • Khi một dự án Java chạy, JVM sẽ tìm class bạn để là class đầu tiên khởi chạy, rồi sau đó tìm đến method main để chạy.
image - quochung.cyou PTIT
public static void main(String[] args) {
   // đây là hàm đầu tiên được chạy
}

2. Bắt đầu với Java

2.0 Sắp xếp các element trong 1 chương trình Java

ElementMô tảBắt buộc cóĐể ở đâu
Packagepackage abc;KhôngĐầu file
Importimport abc;KhôngSau package
khai báo classpublic class abc {Sau import
khai báo biếnint a;KhôngBất cứ đâu
khai báo hàmpublic void abc() {KhôngBất cứ đâu

Package là gì:

  • Tưởng tượng package như cây thư mục thôi, nó giúp chúng ta phân loại các file code của chúng ta, để dễ quản lý hơn. Ví dụ như chúng ta có 1 project lớn, có 1000 file code, thì chúng ta sẽ phân chúng thành các package nhỏ, ví dụ như package com.company.project1com.company.project2, … để dễ quản lý hơn.
JVM
  • Giả dụ ta có cấu trúc thư mục như sau

anhclb
└── anhfirstmeet
    ├── 2020
    │   ├── QuocHung.png
    │   └── PMA.png
    └── 2021
    │   ├── QuocHung.png
    │   └── PMA.png

  • Thì chỉ cần nhìn vào đây, có 2 file ảnh QuocHung, nhưng người ta biết ngay là 2 file ảnh khác nhau, vì nó nằm trong 2 thư mục khác nhau. Tương tự, package cũng giúp chúng ta phân loại các file code của chúng ta, để dễ quản lý hơn.
  • Ví dụ package thư mục chuyên chứa các class xử lí tác vụ (như java.util), các thư mục chuyên chứa code xử lí thuật toán (như algorithm trong C++)

2.1. Nhập, xuất

Ví dụ đơn giản về nhật xuất trong Java:

import java.util.Scanner;

publicclassSayHelloExample {
    publicstaticvoidmain(String[] args) {
        Scannerscanner=newScanner(System.in);

        System.out.print("Enter your name: ");
        Stringname= scanner.nextLine();

        System.out.println("Hello " + name + "!");
    }
}

Để bắt đầu ta sẽ đến với các hàm xuất:

  • System.in.println() để in ra và xuống dòng
  • System.in.print() để in nội dung thông thường

Với việc nhập ta sẽ cần khai báo một đối tượng Scanner thuộc package java.util. Sau đó, ta có thể sử dụng các hàm .next().nextInt(),… để lấy dữ liệu theo dạng token hoặc .nextLine() để lấy cả dòng.

Bài tập

  • Cho số bộ test, mỗi bộ test nhập vào 2 số a, b. In ra a/b làm tròn đến 2 dấu phẩy thập phân theo format như sau:
  • Bo test 0001: 1/2 = 0.50
  • Bo test 0002: 1/3 = 0.33
  • Yêu cầu sử dụng printf

Điều cần nói

  • Một chương trình nhập xuất cơ bản như trên cho ta mường tượng về sự hướng đối tượng chặt chẽ của Java
  • Ví dụ trong C++, để nhập ta có cin, để xuất có cout, trong Python để xuất có printf, trong C cũng là printf, sao Java lại phải dài tận System.out.printf như vậy ?
  • Đó là vì trong Java, mọi thứ đều là đối tượng, nên để xuất ra màn hình, ta phải gọi đến hàm printf của đối tượng System.out. Đây là một trong những điểm khác biệt giữa Java và các ngôn ngữ khác. Điều này thể hiện sự tường minh tuyệt đối
  • Ví dụ trong C++ hay Python như trên, cái gì đang in ra ? Hàm in ra được gọi từ hư không, chẳng có gì khởi nguồn cả, còn Java thì không, ta phải gọi đến hàm printf của đối tượng System.out, nó là một đối tượng thực sự, nó có một hàm printf, và ta gọi đến hàm printf của nó. Điều này thể hiện sự tường minh tuyệt đối của Java, mọi thứ đều là đối tượng, không có gì hư không cả.
  • Rõ ràng, chương trình bắt đầu với đối tượng là class Example, sau đó nó tự động chạy hàm main và đang giao tiếp với các đối tượng khác, ở đây là Scanner để nhập vào, và System.out để xuất ra. Điều này thể hiện sự hướng đối tượng chặt chẽ của Java, mọi thứ đều là đối tượng, và chúng đang nói chuyện, giao tiếp với nhau.

2.2. Khai báo biến, câu lệnh rẽ nhánh, vòng lặp

2.2.1. Biến

  • Biến trong Java chia làm 3 loại
    • Local Variables
    • Instance Variables
    • Static Variables
  • Ngoài ra biến trong Java còn dùng để chứa 2 kiểu dữ liệu primitives và references
  • Local Variable là biến được khai báo trong một phương thức, constructor hoặc một khối lệnh. Biến này chỉ có giá trị trong phương thức, constructor hoặc khối lệnh đó. Khi phương thức, constructor hoặc khối lệnh kết thúc, biến này sẽ bị hủy. (Ví dụ trong hàm main ở trên, biến name là một local variable, và chỉ dùng được trong hàm main đó)
  • Instance Variable là biến được khai báo trong một class, nhưng bên ngoài các phương thức, constructor hoặc khối lệnh. Biến này có thể được truy cập bởi bất kỳ phương thức, constructor hoặc khối lệnh nào của class đó.

Ví dụ:

publicclassExample {
    public String name; // instance variablepublicvoidsayHello() {
        Stringmessage="Hello " + name; // local variable
        System.out.println(message);
    }
}

  • Static Variable là biến được khai báo trong một class, nhưng bên ngoài các phương thức, constructor hoặc khối lệnh. Biến này có thể được truy cập bởi bất kỳ phương thức, constructor hoặc khối lệnh nào của class đó. Tuy nhiên, biến này chỉ có một bản thể duy nhất, không phải mỗi đối tượng sẽ có một bản thể riêng.

Ví dụ:

publicclassExample {
    publicstaticintcount=0; // static variablepublicExample() {
        count++;
    }
}

  • Như trên, nếu không có static thì mỗi class example tạo ra nhiều object con thì count là khác nhau, còn hiện tại, count là độc nhất, gắn chặt với class Example, không phải với object con của nó.

2.2.2. Câu lệnh rẽ nhánh

  • Nhìn chung, câu lệnh rẽ nhánh trong Java cũng giống với C và C++
if (condition1) {
    //do something
} elseif (condition2) {
    
} else {
    
}

Câu hỏi

  • Q: Có dùng được kiểu while(1) trong Java như C++ không?
  • A: Không, trong Java thì boolean và integer là 2 kiểu dữ liệu khác nhau, không thể dùng chung được. Trong Java, để tạo vòng lặp vô hạn, ta sẽ dùng while(true) thay cho while(1).
  • Lí do cho điều này là vì trong C++, 0 là false, 1 là true, 2 cũng là true, nói chung số dương là true. Điều này làm rối loạn cho người mới lập trình, không đủ tường minh, nên trong Java, boolean và integer là 2 kiểu dữ liệu khác nhau, không thể dùng chung được.

Câu hỏi

  • Q: Liệu viết nhiều class có phải viết hàm main hết cho chúng không
  • A: Không, chỉ cần viết một class có hàm main là được. Các class khác có thể không có hàm main, hoặc có hàm main nhưng không được gọi đến. Class chính có hàm main sẽ khởi động lên ,rồi gọi vào các class khác.
  • Một comment bắt đầu bằng hai dấu gạch //
// đây là comment

2.2.3. Vòng lặp

Nhìn chung, vòng lặp trong Java cũng giống với C và C++

  • Câu lệnh lặp for
for (int i = 0; i < 10; i++) {
    // thực hiện câu lệnh này 10 lần
}
  • Câu lệnh lặp while
while (true) {
    // thực hiện câu lệnh này mãi mãi
}
  • Câu lệnh lặp do-while
do {
    // thực hiện câu lệnh này ít nhất 1 lần
} while (true);

Lệnh lặp for gồm 3 phần trong ngoặc tròn:

  • Phần init dùng khởi tạo biến đếm
  • Phần condition chỉ định điều kiện lặp tiếp
  • Phần increment dùng tăng, giảm biến đếm, để tới lúc nào đó điều kiện trở thành false.

Java có một vòng lặp khác, gọi là foreach nhưng vẫn dùng từ khóa for, nhưng theo cú pháp khác.

int[] a = { 1, 2, 3 };
for (int e: a)

Các kiểu dữ liệu nguyên thủy

Kiểu dữ liệuKích thướcGiá trị tối thiểuGiá trị tối đaMô tả
byte1 byte-128127Lưu trữ các số nguyên có dấu
short2 bytes-32,76832,767Lưu trữ các số nguyên có dấu
int4 bytes-2,147,483,6482,147,483,647Lưu trữ các số nguyên có dấu
long8 bytes-9,223,372,036,854,775,8089,223,372,036,854,775,807Lưu trữ các số nguyên có dấu
float4 bytes1.40129846432481707e-453.40282346638528860e+38Lưu trữ các số thực
double8 bytes4.94065645841246544e-324d1.79769313486231570e+308dLưu trữ các số thực
boolean1 bittruefalseLưu trữ các giá trị logic
char2 bytes‘\u0000’ (or 0)‘\uffff’ (or 65,535 inclusive)Lưu trữ các ký tự/ chữ cái Unicode

Mảng trong Java

  • Mảng trong Java là một tập hợp các phần tử có cùng kiểu dữ liệu. Mảng trong Java có thể chứa các kiểu dữ liệu nguyên thủy như int, float, double, char, … hoặc các đối tượng như String, … Mảng trong Java có độ dài cố định, nghĩa là khi khai báo mảng, ta phải xác định được số lượng phần tử của mảng đó. Mảng trong Java có thể là mảng một chiều, mảng hai chiều, mảng ba chiều, … Mảng trong Java có thể được khai báo như sau:
// Khai báo mảng một chiềuint[] a = newint[10];

int[] b = {1, 2, 3, 4, 5};

// Khai báo mảng hai chiềuint[][] c = newint[10][10];

int[][] d = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

Điều cần nói

  • Một lần nữa ta thấy được sự chặt chẽ của Java. Ở đây, như ta nhớ, để khai báo 1 biến ta phải theo syntax sau:
  • <kiểu dữ liệu> <tên biến> = <giá trị>;
  • Vậy ở đây, int[], double[] coi cả cục này là kiểu dữ liệu “mảng” (array), và a, b là tên biến, và new int[10] là giá trị của biến đó. Vậy, ta có thể hiểu là a, b là 2 biến kiểu mảng, và giá trị của nó là 2 mảng có 10 phần tử, và các phần tử đó có kiểu int.
  • Điều này khác với C++, vì kiểu dữ liệu là kiểu dữ liệu, chứ không có chuyện trong kiểu dữ liệu lại có cả số lượng phần tử như c++

Trong C++ trông nó như sau:

int a[10];

Các phương thức so sánh

Phương thứcMô tả
<Nhỏ hơn
<=Nhỏ hơn hoặc bằng
>Lớn hơn
>=Lớn hơn hoặc bằng
==Bằng
!=Không bằng
&&
||Hoặc
!Phủ định
? :Toán tử 3 ngôi

Ôn lại cuối buổi

3. Tại sao Java lại ra đời, tại sao cần hướng đối tượng chứ không code hết vào 1 file

  • A: Trước khi Java ra đời, các ngôn ngữ lập trình khác như C/C++ đã ra đời. Tuy nhiên, các ngôn ngữ này có một số hạn chế như sau:
    • Các ngôn ngữ này không thể chạy trên nhiều nền tảng khác nhau, mà phải biên dịch lại cho từng nền tảng khác nhau. Ví dụ như C++ trên Windows và C++ trên Linux là 2 ngôn ngữ khác nhau, nên phải biên dịch lại.
    • Các ngôn ngữ này không có khái niệm về hướng đối tượng, nên không thể phát triển các ứng dụng lớn, phức tạp.
    • Cơ chế hướng đối tượng của Java giúp chương trình trở thành các đối tượng làm việc với nhau, giúp cho việc phát triển các ứng dụng lớn, phức tạp trở nên dễ dàng hơn. (Sẽ nói sâu hơn vào phần sau)
  • Ví dụ: thay vì code hết code vào 1 file HopThu, chuyên làm mọi việc xử lí email, … ta chia làm 3 class GuiThu, NhanThu, ThungRacThu, làm chỉ nhiệm vụ như tên của nó
  • Khi có lỗi xảy ra, ví dụ gửi thư, ta chỉ cần sửa class GuiThu, không cần sửa cả class NhanThu, ThungRacThu, … như vậy sẽ dễ dàng hơn rất nhiều. Giusp debug 1 luồng đơn giản hơn, dễ maintain, dễ scale hơn.
  • Nhược điểm là về lâu về lại, số lượng class sẽ tăng lên cao rất nhiều, và sẽ khó quản lý hơn.
  • Điều đó buộc chúng ta đặt tên file, đặt package sao cho chuẩn, cho tường minh là điều vô cùng quan trọng, để sau này dễ quản lý hơn.

4. Có thể có 2 hàm main trong 1 chương trình không

  • A: Có, nhưng khi chạy, JVM sẽ chỉ chạy hàm main đầu tiên, và bỏ qua các hàm main khác.

5. Tại sao cần chia JDK và JRE và JVM ra làm 3 phần

  • A: Để cho việc phát triển và chạy ứng dụng trở nên dễ dàng hơn. Ví dụ, khi ta phát triển ứng dụng, ta chỉ cần cài đặt JDK, và khi ta cần chạy ứng dụng, ta chỉ cần cài đặt JRE. Nếu ta cần chạy ứng dụng trên nhiều nền tảng khác nhau, ta chỉ cần cài đặt JVM cho từng nền tảng đó. Thiết bị cuối không cần cài toàn bộ thư viện để code, nó chỉ cần chạy thôi

6. Tại sao Java lại chạy trên máy ảo

  • A: Vì máy ảo có các tiện ích là:
    • Có thể chạy trên nhiều nền tảng khác nhau
    • Có thể chạy nhiều ứng dụng khác nhau cùng lúc

[Web-Buổi 3] Tìm hiểu về Responsive

Mục lục

  • Grid
  • FlexBox
  • Media Query
  • Break point
  • Viewport
  • GridView
  • Flexible Media
  • CSS Sass

I. Grid

  • Grid là một hệ thống layout mạnh mẽ, cho phép bạn tạo ra các layout phức tạp và đẹp mắt mà không cần phải sử dụng các thẻ div lồng nhau.
  • Grid được chia thành 2 phần chính là Grid Container và Grid Item.
  • Grid Container là phần bao bọc các Grid Item.
  • Grid Item là các phần tử con của Grid Container.

Example Code in MD:

    <div class="grid-container">
        <div class="grid-item">1</div>
        <div class="grid-item">2</div>
        <div class="grid-item">3</div>
        <div class="grid-item">4</div>
        <div class="grid-item">5</div>
        <div class="grid-item">6</div>
        <div class="grid-item">7</div>
        <div class="grid-item">8</div>
        <div class="grid-item">9</div>
    </div>
Grid

Display Grid:

    .grid-container {
        display: grid;
    }

Grid Template Columns:

    .grid-container {
        display: grid;
        grid-template-columns: 100px 100px 100px;
    }

Grid Template Rows:

    .grid-container {
        display: grid;
        grid-template-columns: 100px 100px 100px;
        grid-template-rows: 100px 100px 100px;
    }

Grid Gap:

    .grid-container {
        display: grid;
        grid-template-columns: 100px 100px 100px;
        grid-template-rows: 100px 100px 100px;
        grid-gap: 10px;
    }

Grid tự động cân bằng

    .grid-container {
        display: grid;
        grid-template-columns: auto auto auto;
        grid-template-rows: auto auto auto;
        grid-gap: 10px;
    }
Grid

Grid cân bằng theo tỉ lệ

    .grid-container {
        display: grid;
        grid-template-columns: 20% 30% 50%
        grid-gap: 10px;
    }
Grid

Tuỳ chỉnh vùng theo thuộc tính

    .item8 {
    grid-area: 1 / 2 / 5 / 6;
    }
Grid
Grid

II. FlexBox

  • FlexBox là một hệ thống layout mạnh mẽ, cho phép bạn tạo ra các layout phức tạp và đẹp mắt mà không cần phải sử dụng các thẻ div lồng nhau.
  • FlexBox được chia thành 2 phần chính là Flex Container và Flex Item.
  • Flex Container là phần bao bọc các Flex Item.
  • Flex Item là các phần tử con của Flex Container.
FlexBox

Example Code in MD:

<div class="flex-container">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>
FlexBox

III. Media Query

  • Media Query là một công cụ cho phép bạn thay đổi style của một trang web dựa trên các điều kiện nhất định.
  • Media Query được sử dụng để tạo ra các trang web đáp ứng (responsive web).
  • Media Query được sử dụng để tạo ra các trang web có thể hiển thị đẹp trên các thiết bị khác nhau, với các kích thước màn hình khác nhau.

Example Code in MD:

@media only screen and (max-width: 600px) {
  body {
    background-color: lightblue;
  }
}
Media Query

IV. Break point

  • Breakpoint, là những điểm (chiều rộng màn hình của thiết bị) mà ở đó giao diện được chuyển đổi cho phù hợp với thiết bị hiện tại, ví dụ như màn hình rộng hơn 1024px, thì có background-color màu đỏ, nhỏ hơn 1024px thì background-color màu xanh, khi này ta gọi 1024 là breakpoint.
  • Tùy vào chiều rộng hiển thị của thiết bị mà breakpoint sẽ khác nhau, hiện nay có rất nhiều thiết bị, tương ứng sẽ có nhiều chiều rộng khác nhau, nên sẽ có nhiều breakpoint khác nhau, do đó ta không thể thiết lập beakpoint cho từng loại thiết bị được.

Điểm breakpoint

  • 320 px Màn hình chiều dọc cho smartphone nhỏ (VD iPhone 5)
  • 480 px Màn hình chiều ngang cho smartphone nhỏ
  • 640 px Màn hình chiều ngang cho smartphone vừa
  • 768 px Màn hình chiều dọc cho tablet (VD: iPad)
  • 1024 px Màn hình chiều ngang cho tablet (VD: iPad), hoặc chiều dọc cho tablet lớn (VD iPad Pro)
@media only screen and (min-width: 320px) {
 body{
   background-color: orange;
  }
}
Break point

V. Viewport

  • Viewport là khu vực hiển thị nội dung trên trình duyệt.
  • Viewport thay đổi theo thiết bị, và sẽ nhỏ hơn trên điện thoại di động so với màn hình máy tính.
  • Trước khi có máy tính bảng và điện thoại di động, các trang web được thiết kế chỉ cho màn hình máy tính, và việc thiết kế tĩnh và kích thước cố định là phổ biến.
  • Sau đó, khi chúng ta bắt đầu lướt web bằng máy tính bảng và điện thoại di động, các trang web có kích thước cố định quá lớn để vừa với viewport. Để khắc phục điều này, các trình duyệt trên các thiết bị đó thu nhỏ toàn bộ trang web để vừa với màn hình.
  • Khác biệt giữa có viewport và không có viewport:
  • Một trang web không có thẻ meta viewport sẽ được hiển thị với độ rộng của trang web, không phụ thuộc vào thiết bị.
  • Một trang web với thẻ meta viewport sẽ được hiển thị với độ rộng của thiết bị, và với tỷ lệ khung hình cố định.
<meta name="viewport" content="width=device-width, initial-scale=1.0">
  • width=device-width phù hợp với chiều rộng của thiết bị (đừng quên để thẻ meta viewport trong thẻ <head>)
  • initial-scale=1.0 thiết lập tỷ lệ khung hình của trang web
  • user-scalable=no ngăn người dùng phóng to hoặc thu nhỏ trang web
  • minimum-scale=1.0 ngăn người dùng thu nhỏ trang web
  • maximum-scale=1.0 ngăn người dùng phóng to trang web

VI. GridView

!!!

VII. Flexible Media

Flexible Media
img {
  max-width: 100%;

  /* just in case, to force correct aspect ratio */
  height: auto !important;
}

VIII. CSS Sass

  • Sass là một ngôn ngữ mở rộng của CSS, nó có thể giúp việc viết CSS trở nên dễ dàng và nhanh hơn.
  • Sass là chữ viết tắt của Syntactically Awesome Style Sheets, chương trình tiền xử lý bằng ngôn ngữ kịch bản (Preprocessor Scripting Language ), sẽ được biên dịch thành CSS. Nghĩa là, mình sẽ làm style bằng SASS, rồi SASS sẽ render việc mình làm thành file CSS.
  • Sass có 2 phiên bản là Sass và SCSS, 2 phiên bản này khác nhau ở cú pháp, nhưng cùng có chức năng tương tự nhau.

Package và Exception trong Java

  1. Package trong Java
  1. Định Nghĩa
  • Một Package (gói) trong Java là một nhóm các class, interface và các package con tương tự, liên quan đến nhau. Chúng ta có thể coi package giống như là một folder.
  • Các package trong Java được sử dụng để tránh việc xung đột trong khi đặt tên, để kiểm soát truy cập, giúp bạn tìm kiếm và sử dụng các class, interface… một các dễ dàng hơn.
irlr2uR493p oRCD 6d3UkhGz9Spnw7GqXq c1VOYu5hCjcZAieWIm0qIsFU7VDLqVibAJq5RXdyTP3 MAV22 - quochung.cyou PTIT
  1. Phân loại

a. Các package có sẵn trong Java API:

  • java.lang: Chứa các lớp hỗ trợ ngôn ngữ (ví dụ: lớp được định nghĩa các kiểu dữ liệu nguyên thủy, các phép toán). Package này được import tự động.
  • java.io: Chứa lớp để hỗ trợ input / output (I/O)
  • java.util: Chứa các lớp tiện ích thực hiện các cấu trúc dữ liệu như danh sách liên kết, dictionary và hỗ trợ cho các hoạt động date / time.
  • java.applet: Chứa các lớp để tạo Applet.
  • java.awt: Chứa các class để triển khai các thành phần cho giao diện người dùng đồ họa (ví dụ như button, menu,…).
  • java.net: Chứa các lớp để hỗ trợ các thao tác trong mạng (network).

b. Package do người dùng tự định nghĩa

  1. Lợi thế của việc sử dụng Package
  • Package được sử dụng để phân loại lớp và interface giúp dễ dàng bảo trì.
  • Package cung cấp bảo vể truy cập
  • Package khắc phục được việc đặt trùng tên.
  1. Triển khai Package
  • Nếu tên package là traidat.vietnam.hanoi, khi đó có 3 thư mục là: traidat, vietnam, hanoi. Package traidat chứa subpackage – package con vietnam, sau đó package vietnam lại chứa subpackage là hanoi
  • Tên package thường được viết thường hết các chữ cái, và ghi rõ nghĩa của các class nằm trong package. VD package “math” chứa các class toán học, …
- quochung.cyou PTIT
  • Để có thể sử dụng các phương thức, … của class TenClass này. Ta có thể gọi 
3 cmeFmK s1jNykYSFaox2P7S - quochung.cyou PTIT
  1. Exception trong Java
  1. Định Nghĩa
  • Exception (ngoại lệ) là một tình trạng bất thường. Trong java, ngoại lệ là một sự kiện làm gián đoạn luồng bình thường của chương trình. Nó là một đối tượng được ném ra tại runtime.
  • VD: Một chiếc ô tô đang chạy bình thường trên đường, thì bỗng nhiên dừng lại – chương trình dừng lại (RTE – Run Time Error), lúc này do ô tô hết xăng không chạy được nữa, vậy việc “hết xăng” là một ngoại lệ – sự bất thường bỗng xảy ra trong chương trình
mX1rXgYmcRtZrm57u22DkPoi28zX0gqRLNW9a6IZqq5QAR9EBL06tp0MR1bVE1N0pnlSDsNB1vxKotdJE3pfG1pH3Mg0 u6Vi7gw8tFq0C4BBooniVYJp4YftOsVs wrk8Akn2PF58SE - quochung.cyou PTIT
  1. Các loại Exception
  • Checked Exceptions: Các Exceptions là lớp extends từ lớp Throwable ngoại trừ RuntimeException và Error. Các Checked Exception được kiểm tra tại compile-time (Lúc chương trình được biên dịch)
  • Unchecked Exception: Các lớp extends từ RuntimeException được gọi là Unchecked Exception, ví dụ: ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException,… Các Exception này thường xảy ra khi lỗi đó không lường trước được.
  • Error: Đây là các lỗi không thể cứu chữa được, không thể làm gì khi nó xảy ra được nữa (VD: Hết ram, …)

VD: Khi ta chuẩn bị đi ra ngoài, ta sợ trời có thể mưa nên đã phòng sẵn một cái ô.

Việc trời mưa ta đã lường trước được, nên gọi là Checked Exception, và ta đã khai báo “cái ô” để chuẩn bị trước.

Tuy nhiên có một số vấn đề khác ta không lường được, ví dụ có một vũng nước trên đường, có một cái ô tô chắn không ra khỏi nhà (Các vấn đề phải thực thi chương trình, đi ra ngoài mới có thể gặp)

  • Checked Exceptions
    • IOException: Ngoại lệ liên quan đến file input / output
    • SQLException: Ngoại lệ liên quan đến cú pháp SQL
    • DataAccessException: Ngoại lệ liên quan đến việc truy cập CSDL
    • ClassNotFoundException: Bị ném khi JVM không thể tìm thấy một lớp mà nó cần, do lỗi dòng lệnh, sự cố đường dẫn hoặc tệp, class bị thiếu…
    • InstantiationException: Ngoại lệ khi cố gắng tạo đối tượng của một abstract class hoặc interface
  • UnChecked Exceptions
    • NullPointerException: Ngoại lệ bị ném ra khi cố gắng truy cập một đối tượng có biến tham chiếu có giá trị hiện tại là null
    • ArrayIndexOutOfBound: Ngoại lệ khi cố gắng truy cập một phần tử vượt quá độ dài của mảng
    • IllegalArgumentException: Ngoại lệ bị ném ra khi một phương thức nhận được một đối số được định dạng khác với phương thức mong đợi.
    • IllegalStateException: Ngoại lệ bị ném ra khi trạng thái của môi trường không phù hợp với hoạt động cố gắng thực hiện, ví dụ: Sử dụng Scanner đã bị đóng.
    • NumberFormatException: Ngoại lệ bị ném khi một phương thức chuyển đổi một Chuỗi thành số nhưng không thể chuyển đổi.
    • ArithmeticException: Lỗi số học, chẳng hạn như chia cho 0.
  1. Xử lí Exception

Khối lệnh try-catch:

bZCO49o6XpA3LRRSjXycB2rrPgmItCcrV GJxgZgBlwqALoWqaHhpujkQwaSCA hIF5J6akks2GFZjumQ8GaVh9Zz nEfDnmK60q9OcuS3lkq6EjI 4TsHkkQhanspMRDLdSibXp v1t - quochung.cyou PTIT

Lưu ý: Bạn có thể sử dụng nhiều khối catch với một khối try duy nhất. (Xử lí nhiều lỗi khi thử làm gì đó)

fW5YcizqGIJL4U624ct2EOmk S1LFAOuq9pUExLLycf u WxnhCDfAWHRr66uJPmXG EERUK1lppMX6IKBSpHTu3EGVlUFUl6ylvs5ToI0LFVLZLXYhLVFU 49Nk5 CV9 zguRSaoKnr - quochung.cyou PTIT
Ảnh

Khối lệnh finally:

  • Khối lệnh finally trong java được sử dụng để thực thi các lệnh quan trọng như đóng kết nối, đóng cả stream,…
  • Khối lệnh finally trong java luôn được thực thi cho dù có ngoại lệ xảy ra hay không hoặc gặp lệnh return trong khối try.
  • Quy tắc:
  • Đối với mỗi khối try, có thể có không hoặc nhiều khối catch, nhưng chỉ có một khối finally.
  • Khối finally sẽ không được thực thi nếu chương trình bị thoát (bằng cách gọi System.exit() hoặc xảy ra một lỗi không thể tránh khiến chương trình bị chết).
  1. Custom Exception

Chúng ta có thể khai báo 1 class mới và extends Exception để trở thành 1 ngoại lệ checked

U ffy4g3NSN4W S6bA64JsgRkDn wCU sp E7rGC4IE3CN61jJSqNrd19zVhl9pL2QSsuM3VjSTehyj1dFMahKL68oC vWLwpMNnmiyIYIEvRbmoP5 Ayjiurl9UwnipTGlBMeJGWsu - quochung.cyou PTIT

Chúng ta có thể khai báo 1 class mới và extends RuntimeException để trở thành 1 ngoại lệ unchecked

EUZ4AZSIHP7tvEZ yf15ysT2LSD y18QIJ98He6BeIF1221MkXN6Fux8R29w4AhQGslU482o 8nYwaLr4EwWvbzxan2PUKCS0EFDDNkGAoC8IQIURXZ6onTbv rwayA9E4RiLYRSFLy - quochung.cyou PTIT

Tính đa hình – Polymorphism trong C++ OOP

Đa hình

C++ Polymorphism with Example

Tính đa hình (Polymorphism) là một trong bốn tính chất cơ bản của OOP. Trong lập trình, thuật ngữ này dùng để ám chỉ đến việc một biến, một hàm hoặc một phương thức có thể tồn tại ở nhiều dạng khác nhau. Đa hình là hiện tượng mà các đối tượng thuộc các class khác nhau có thể biểu diễn cùng một thông điệp theo các cách khác nhau.

Ví dụ:

Một người A có thể là một học sinh, đồng thời là thành viên câu lạc bộ, đồng thời là nhân viên bán thời gian quán café.

Một hành động “làm việc” nếu cho học sinh sẽ là học tập, cho kế toán sẽ là “tính toán”, …

Lập trình viên có thể tận dụng Tính đa hình trong OOP vào những trường hợp sau:

  • Khi các lớp con cần dùng phương thức của lớp cha để bổ sung cho một phương thức khác.
  • Khi một lớp cần có nhiều phương thức cùng tên nhưng khác tham số (parameter).

Tính đa hình trong OOP gồm có 2 loại: 

  • Runtime Polymorphism (Đa hình thời gian chạy) 
  • Compile Time Polymorphism (Đa hình thời gian biên dịch) 

Theo đó , tính đa hình cho phép một phương thức thực thi những hành vi khác nhau theo hai hướng: sử dụng phương thức ghi đè (method overriding) hoặc phương thức nạp chồng (method overloading).

  • Đa hình thời gian chạy là trường hợp một đối tượng bị ràng buộc với chức năng của chúng ngay tại thời gian chương trình đang chạy. Đa hình thời gian chạy sử dụng phương thức ghi đè.
  • Đa hình thời gian biên dịch là trường hợp một đối tượng bị ràng buộc với chức năng của chúng ngay tại thời gian chương trình đang biên dịch. Đa hình thời gian biên dịch sử dụng phương thức nạp chồng.

Phương thức ảo, từ khóa Virtual

Một số tài liệu có viết công dụng của Virtual Function như sau:

“Virtual Function là để khai báo một function ở class cha (base class) mà sau đó các class kế thừa (derived class) có thể override function đó”

Các hàm ảo sẽ cho phép chúng ta tạo một danh sách các con trỏ lớp cơ sở và các phương thức của bất kỳ lớp dẫn xuất nào mà không cần biết loại đối tượng của lớp dẫn xuất.

Khi khai báo hàm ảo với từ khóa virtual nghĩa là hàm này sẽ được gọi theo loại đối tượng được trỏ (hoặc tham chiếu), chứ không phải theo loại của con trỏ (hoặc tham chiếu). Và điều này dẫn đến kết quả khác nhau:

  • Nếu không khai báo hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp cở sở base (lớp cha)
  • Nếu dùng hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp dẫn xuất derived (lớp kế thừa)

Phương thức thuần ảo, lớp ảo

Hàm thuần ảo (pure virtual function) được sử dụng khi:

  • Không cần sử dụng hàm này trong lớp cơ sở, chỉ phục vụ cho lớp dẫn xuất
  • Lớp dẫn xuất bắt buộc phải định nghĩa lại hàm thuần ảo (Nạp chồng)

Ví dụ: Chúng ta có một lớp cơ sở là Shape. Các lớp dẫn xuất của lớp Shape là Triangle, Square và Circle. Chúng ta muốn tính diện tích của các hình này.

Chúng ta có thể tạo ra một hàm thuần ảo có tên là calculateArea() trong lớp Shape. Các lớp Triangle, Square và Circle phải định nghĩa lại hàm calculateArea() với công thức tính diện tích khác nhau cho mỗi hình.

5aqTqiCz9G4Xm6Uaz UnhFT3FFBkS90tg1hwvHqeYduCSDl5bOpxqiTLxwqxx4EbE2vveyk tfw2kZGOLfDwlJA8SUFw3a8rOZHJ6SbM7jUjoubXhah94az MJ4IizDzt4zAHqYB7VNstPKvRs 6pw - quochung.cyou PTIT

Lớp ảo

Có một vấn đề khi một lớp cơ sở được kế thừa bởi nhiều lớp dẫn xuất. Ví dụ, xét tình huống các lớp kế thừa theo sơ đồ như sau:

https://media.geeksforgeeks.org/wp-content/uploads/20190312133527/Need-of-Containership-in-C.jpg

Lớp B và Lớp C kế thừa từ lớp A. Chúng trở thành A (B) và A (C). Lúc này, nếu ta lại cho D kế thừa từ B và C, ta vô tình tạo ra tới 2 con đường kế thừa về 1 điểm đích.

  • D kế thừa thông qua B kế thừa A
  • D kế thừa thông qua C kế thừa A

Điều này sẽ khiến trình biên dịch mơ hồ và không biết nên đi theo con đường nào và tạo ra lỗi.

Thay vì kế thừa thông thường, ta sẽ định nghĩa B và C là các lớp ảo, việc sử dụng lớp ảo sẽ tiết kiệm không gian và tránh việc trùng lặp kế thừa.

  • Nếu kế thừa thông thường, khi tạo class D kế thừa đồng thời B và C, ta thực chất tạo ra 2 bản sao class A
  • Nếu cho class B và class C thành virtual, thì khi tạo class C chúng ta sẽ dùng chung 1 bản class A.
YilbwpP RhhZw3Zu6GAQyVLIUIhvGX 6KaIg13I2EQG8CpgaorXvQFB0eY GEOS7Bz W4hU1hwcttV1CufHhY8kzKzAMhEUsGUXD5YX8KoAYNEEcx1LaO - quochung.cyou PTIT

Diagram UML và C++ OOP

Diagram UML + Practicing

Hệ thống UML Class Diagram (Biểu đồ Class) là một cách dùng hình ảnh để khởi tạo và biểu diễn một hệ thống hướng đối tượng.

Tìm hiểu về Diagram UML: Access Modifier, Composition, Aggregation, Cardinality.

Access Modifier:

Trong C++ chúng ta có 3 nhãn phạm vi (access modifier) thì uml sẽ kí hiệu như sau

Dấu + : public

Dấu – : private

Dấu # : protected

Composition, Aggregation

Wevt9VIWixwXXqZChL595AM2tmBIItZfBe8pguSnvZep4R8d - quochung.cyou PTIT

Ví dụ:

  • Người đang tiếp xúc với pet, pet cũng tiếp xúc với người (Sự kết hợp – association)
  • Cái đuôi là một thành phần của con chó hoặc con mèo (aggregation composition – Thành phần)
  • Mèo là một kiểu con của pet (Kế thừa)

Aggregation là kiểu khi mà các lớp con được độc lập và không phụ thuộc vào lớp cha. Khi lớp cha bị xóa thì lớp con vẫn tồn tại.

Composition bị phụ thuộc vào lớp cha.

Ví dụ:

hkxJP7s9WMyUHH7feURQTXTOEhBlGB9rvilfx3i5 wM1V147HRCaA7KWv4L5k0TVCQ2u01P0ujuByHCj pWMoP L15HQiSNLrfshipIX2NvBU A74Hev1Tv 5cN8CaCJV3kpAUKIE7TjgHQG0Zdjqw - quochung.cyou PTIT

Class ô tô và Class bánh xe. Class bánh xe vẫn là một sự tồn tại “có ý nghĩa” kể cả không có Class  ô tô, class bánh xe vẫn có thể dùng cho class xe đạp, class xe máy, …

=> Aggregation là kiểu khi mà các lớp con được độc lập và không phụ thuộc vào lớp cha. Khi lớp cha bị xóa thì lớp con vẫn tồn tại.

OrRJ8oN57LWRsOqjwK6LO5K915Y6olyTiFXoOXz8XqHdxi L9m SoisWF0wcGm2vecjOcT9tEvBQIeRUvUbL6SERIdzO91Kf9A25Jmy5VIUUmNIa6b1qazQHTiYnSg7 6E EbgSIRUT2TNM3XYJTHA - quochung.cyou PTIT

Class người và class tay, chân, đầu. Nếu thiếu class người thì gần như những class còn lại đều tồn tại “không có ý nghĩa”, vì chúng không thể sử dụng vào đâu khác cả.

Cardinality

Sử dụng để thể hiện quan hệ về số lượng giữa các đối tượng được tạo từ các class trong class diagram

0…1: 0 hoặc 1

n : Bắt buộc có n

0…* : 0 hoặc nhiều

1…* : 1 hoặc nhiều

m…n: có tối thiểu là m và tối đa là n

B3hsekvGyamXUNgY 5v9biPnYAZKVqtKylsQQobRPQ7385gB SvuQp4u3qsdOppomEHptEBfQ2ykwt2Qfhxn0frNOsyQ U3rSS1WoEGKs8KX5yaGC9vsyCWjJ18srjYuF 11sWQ0qfB - quochung.cyou PTIT

-> VD ảnh: Công ty chỉ có một, còn nhân viên công ty thì là 1 hoặc * (nhiều – không giới hạn)

Tìm hiểu về lập trình hướng đối tượng (OOP) trong C++

  1. Khái niệm

Ở những chương trình code bình thường, code của chúng ta như một vector vậy, chúng chỉ là một dãy các dòng lệnh sẽ được chạy tuần tự từ đầu đến cuối. Ngoại lệ là các hàm và dữ liệu sẽ được tách khỏi vector đó và có thể gọi chúng giữa vector để tạo nên kết quả. 

Vậy OOP – Lập trình hướng đối tượng là gì? Nếu nhìn vào cuộc sống thực tế, tất cả thứ bạn thấy xung quanh đều là các đối tượng (Object) , object sẽ luôn có 2 thành phần chính

  • Các đặc tính của chúng. (VD: cân nặng, chiều cao, kích cỡ, hình dáng, màu sắc, …)
  • Các “hành động” chúng có thể bị tương tác hoặc có thể tương tác (Quyển sách bị “mở”, người “đi”, …) (Hàm)

Lập trình hướng đối tượng cho chúng ta khả năng tạo ra các đối tượng liên kết chặt chẽ với nhau và hoạt động như những khối hộp có thể sử dụng lại

VD: Bạn cần tạo một con game thế giới mở, người chơi có thể đi lại trong thế giới.

VD bạn cần tạo ra một ngôi nhà, ngôi nhà được cấu thành từ : bức tường, vật gia dụng, sơn, …. Bạn không thể mỗi nơi lại gọi hết các bức tường, vật gia dụng, sơn, … khác nhau ra, mà chúng ta sẽ có một Lớp – Ngôi nhà chứa tất cả đặc tính chúng ta cần như màu sắc, kích cỡ, và các thuộc tính này sẽ khác nhau mỗi nơi, để có thể sử dụng lại

  1. Class và Object
QnfLA2cHVptrlxRXBNmlIRvhDPfa9g3LJ7Icf52JGEbiWeNTssv5re3wrUvRYHv128YolvasaDupKZIpM2dawUJRL4BYZhjRDOPfsm8W6o K8mCChFTmvwVXFAkAW9kM3wbZ00WKSs44kmxx Z2MQA - quochung.cyou PTIT

Class là một lớp chứa các thuộc tính, chức năng. và Object – đối tượng là một vật thể được tạo ra từ một lớp Class đó.

struct NgayThang { int nam {}; int thang {}; int ngay {}; };

Đây là một lớp ngày tháng có 3 thuộc tính : ngày tháng năm, các lớp bây giờ sẽ là những thời gian khác nhau trong năm

NgayThang tet = 1/1/2022

NgayThang valentine = 4/2/2022

Con trỏ this?

YfOfxemnNCJM U7b Yge4QXCQUUUCabvTcImwuSqgTkCWfCyJLuGjJIhPkvAO48o9371OKjEEfuJ2Hrf 2EhREa5riQ8tLi1bYv5CM2pn2SKkMz8qlUiLHMbM6pKc7G8f3tP5rjSKqSElqP VnQmgw - quochung.cyou PTIT

This là một con trỏ cho phép chúng ta truy cập các thành phần, đặc tính của class

Ví dụ ta đang xây dựng một mạng xã hội với Class, và có một lớp NguoiDung

struct NguoiDung { int taikhoan {}; int matkhau {};};

Ta không thể cho phép người sử dụng khác in ra mật khẩu, tài khoản của một người dùng bất kì

cout << NguoiDung.matkhau;

=> Không cho phép truy cập các phần tử bí mật.

Nhưng ta vẫn cần cho phép người dùng có thể lấy ra mật khẩu khi đăng nhập, đăng xuất, quên mật khẩu, … nhưng mà lại không thể truy cập các phần tử bí mật.

NguoiDung endcontest;

cout << endcontest.matkhau; -> Không được

endcontest.dangnhap(); 

// nhưng 

void NguoiDung::dangnhap() const {

string matkhau;

cin >> matkhau;

if (matkhau == this.matkhau) cout << “dang nhap”;

  }

Ta đã ứng dụng con trỏ this để gọi ra mật khẩu, trong khi không thể truy cập trực tiếp.

  1. Các đặc tính của OOP
zFX7J35Q1qtmDc7ENbJQ sljjPTf OdxuVcBYz txJxSapajdZTKxd zjMqbk eSITIhTkXkPzTddedDEJYjwOqY3L7MzAC0EzYVR K pu5gs 6WB6kFp VY6 pUqJTN6e1SHgy9nSpfSlG B f 3w - quochung.cyou PTIT

Tính đóng gói (Encapsulation):

(Điều này giống ví dụ về this bên trên)

uDfzeGc8RtonzgEmfpW8wSiPti960aXK8MqMW20 JdUpkgY8bd6wKWH5pAZvstXzAO0MSw5eI88n0f4Tmp4I0oCcRL1LuVw1zcFkZRgp0erj9uCbactkaeHUdgT5F zugvRBjYkJomiB9iVW8O0CFw - quochung.cyou PTIT

Là cách để che dấu những tính chất xử lý bên trong của đối tượng, những đối tượng khác không thể tác động trực tiếp làm thay đổi trạng thái  chỉ có thể tác động thông qua các method public của đối tượng đó. 

xem cách thể hiện bằng code dưới đây : class hinhchunhat

WDgadreoPMjdoiA fJXqTXumUn n4THY3sxy9bO9OLDk02OUQeVzS R3F5ok9fgfIzO91h13jtpKWutcI Twqy o0oi4q tTjHlTOGK8ZZ4vzPr8FTthuHkPOi1Z3blw2lOzd iEyIqt - quochung.cyou PTIT
  • height và width ở đây chính là các tính chất (properties) của đối  tượng class hinhchunhat
  • tinhdientich() là method được public nhằm mục đích tương tác với các đối tượng khác. Tạo một class Program với method static để run, xem cách tương tác và thay đổi tính chất  của đối tượng thông qua các method public như nào:

Ta thấy ta sẽ không thể truy cập các thuộc tính height, width. Nhưng vẫn có thể sử dụng các hàm/method để truy cập chúng. 

Tính kế thừa (Inheritance):

Là kỹ thuật cho phép kế thừa lại những tính năng mà một đối tượng khác đã có, giúp tránh việc code lặp dư thừa mà chỉ xử lý công việc tương tự.

  • Kế thừa một cấp (Single level Inheritance): Với một class cha và một class con
Fk5F2xvm2TUt9lsJVVZawNnHWfP1nBPCS2XP5df9VDBlNUJ5VPt8N9z0CABzluYOHGCjczlJyq0KDQ7CE89dqLMZZfFdtrPHYYV2W7 55zGaF38cfyDwX2o72xjXZbxYVk2U - quochung.cyou PTIT

Ví dụ ta có một class đồ điện tử, chúng có các thuộc tính như: số vi mạch, lượng điện sử dụng, hiệu điện thế, cường độ dòng điện ,…. Các thiết bị như điện thoại, tai nghe, … vẫn dùng chúng, nhưng lại có thêm một số thuộc tính riêng khác, nên thay vì phải tạo lại tất cả thuộc tính đó cho từng class điện thoại, tai nghe mới. Ta sẽ gọi Điện thoạilớp kế thừa của đồ điện tử, và thừa hưởng mọi thuộc tính, chức năng của đồ điện tử.

Tính đa hình (Polymorphism):

Là một đối tượng thuộc các lớp khác nhau có thể hiểu cùng một thông điệp theo cách khác nhau.

Ví dụ đa hình trong thực tế: Mình có 2 con vật: chó, mèo hai con vật này khi nhận được mệnh lệnh  là “hãy kêu” thì chó kêu “gâu gâu”,  mèo kêu “meo meo”.

Ví dụ trên cả 2 con vật đều hiểu chung một thông điệp “hãy kêu” và thực hiện theo cách riêng của chúng.

Tính trừu tượng (Abstraction):

Abstraction (Tính trừu tượng) là một khả năng mà chương trình có thể bỏ qua sự phức tạp bằng cách tập trung vào cốt lõi của thông tin cần xử lý.

Điều đó có nghĩa, bạn có thể xử lý một đối tượng bằng cách gọi tên một phương thức và thu về kết quả xử lý, mà không cần biết làm cách nào đối tượng đó được các thao tác trong class.

Ví dụ: bạn có thể nấu cơm bằng nồi cơm điện bằng cách rất đơn giản là ấn công tắc nấu, mà không cần biết là bên trong cái nồi cơm điện đó đã làm thế nào mà gạo có thể nấu thành cơm.

  1.  Nhãn phạm vi

Nhãn public

  • Từ khóa public được dùng để tạo các thành phần “public – công khai” (Cho dữ liệu hoặc hàm) 
  • Các thành phần được tạo public có thể truy cập từ bất cứ chỗ nào trong phần mềm

Nhãn private

  • Từ khóa public được dùng để tạo các thành phần “private – bí mật” (Cho dữ liệu hoặc hàm) 
  • Các phần tử private chỉ có thể truy cập trong class hoặc class friend

Nhãn protected

  • Từ khóa public được dùng để tạo các thành phần “protected – bảo mật” (Cho dữ liệu hoặc hàm) 
  • Các thành phần protected chỉ có thể truy cập trong class và các class kế thừa của nó.

Tổng quát

KiểuTrong chính classClass kế thừaBên ngoài
publicYesYesYes
privateYesNoNo
protectedYesYesNo
  1. Hàm khởi tạo và hàm hủy

Hàm khởi tạo là một hàm đặc biệt trong lớp. Hàm này được gọi tự động khi một đối tượng được tạo ra. Hàm khởi tạo sẽ khởi tạo giá trị cho các thuộc tính của đối tượng. Trong C++, một hàm khởi tạo có đặc điểm sau:

− Tên hàm khởi tạo trùng với tên của lớp.

− Hàm khởi tạo không có kiểu dữ liệu trả về (kể cả void).

− Hàm khởi tạo có thể là public, private hoặc protected. Hàm private có thể truy cập từ lớp friend, protected có thể gọi từ lớp kế thừa.

− Hàm khởi tạo có thể có đối số hoặc không có đối số.

− Trong một lớp có thể có nhiều hàm khởi tạo (cùng tên nhưng khác đối số).

XQUeJ5tuUCmdfPiZuaa7l4 kfr99 uKNPT fcYAS1yyZGs1gHLFohu7YaTZ22NRxhP 3HEHMdZ7yetmE 5LLmmjoAcKb aLhgyBZiijchDiwwwpzziaN fz8j RbMV2t 94 Jw32 52ouEgRmLYGQw - quochung.cyou PTIT

 Hàm khởi tạo mặc định (default constructor)

Hàm khởi tạo mà không có đối số nào thì được gọi là hàm khởi tạo mặc định. Hàm này thường dùng gán giá trị mặc định cho các thuộc tính trong lớp.

Trong ví dụ bên dưới, hàm Circle() là một hàm khởi tạo mặc định và tự đặt r = 1 nếu chưa cài r.

- quochung.cyou PTIT

Lưu ý: Nếu lớp không có hàm khởi tạo nào, trình biên dịch sẽ cung cấp một hàm khởi tạo mặc định không đối số và không code trong nó.

Nếu trong lớp đã có ít nhất một hàm khởi tạo thì hàm khởi tạo mặc định do trình biên dịch cung cấp sẽ không còn nữa. Khi đó, bạn phải sử dụng các hàm khởi tạo được định nghĩa trong lớp nếu không sẽ gây ra lỗi.

Vnaot vBBOcs CkvdX 6a17fiR4RmGEtkjP vqjE8Fzan7uMRcFliLBLZTgZhutiDq5VfvdLoNHP88 rLNpZVcoAaqCp XeDTyGozJle4RsNoQ6NWDIDAIitR44mnftAo1H8HCYqstLm2i TF54qIw - quochung.cyou PTIT

Hàm huỷ sẽ tự động được gọi trước khi giải phóng một đối tượng để giải phóng vùng nhớ trước khi đối tượng được hủy bỏ. Hàm hủy có các đặc điểm sau:

− Mỗi lớp chỉ có một hàm hủy.

− Hàm hủy không có kiểu, không có giá trị trả về và không có đối số.

− Tên hàm huỷ cùng tên với tên lớp và có một dấu ngã (~) ngay trước tên.

w29IEuvUbhgSWYiwv0XNRZsUs5pnhlO8U7a9Jm2SbNGotBSRFJxcd1Pc9FBdNfLLNsLUR3Enp6l1ZT4n1jO ho AnqGW5wklXX6SsGS6WfU6uZabfCYByMwO4dUffsvujTYosAV22R97ZuAV35y8ew - quochung.cyou PTIT
  1. Getter và SetterbZtWeYVc93RF9jxumWG2W UscRZzz x90pjaS88rewCNSpIz6Xh7JDliDnvisncvatxhKOzUEt5yGQ - quochung.cyou PTIT

Là các hàm dùng để set (đặt) hoặc get (lấy) dữ liệu

  1. Static

Static trong C++ là một toán tử có tác dụng chỉ định một biến hoặc hàm thành viên trong class tồn tại ở dạng tĩnh.

Dạng tĩnh ở đây có ý nghĩa, vùng bộ nhớ dùng để lưu trữ các dữ liệu này sẽ là bất biến, không thay đổi.

Bằng việc sử dụng static trong class C++, chúng ta sẽ có được biến static và hàm static, là các thành viên được lưu tại các vùng bộ nhớ bất biến. Vì ở dạng tĩnh nên chúng sẽ tồn tại duy nhất trong class. Chúng có thể sử dụng kể cả không có đối tượng trong class

O9FVSTRGZXdoSY11q IDpeTUYejM2BoQsIYHvQflsDxzyt0W8flmXIe7VzRm4OHcMGWrCA9kp4uN3p7PJ 1vGRDONJ068lqnrrALyw AeitPeMb0mXDKa q Y cXAFN6xE 6eCAQggCQ - quochung.cyou PTIT

6.1. Biến static

Để tạo biến static trong class C++, chúng ta thêm toán tử static vào đằng trước tên biến khi khai báo biến trong class như sau. Lưu ý là access modifier của biến static phải ở dạng public để cho phép truy cập nó từ ngoài phạm vi class.

Ưu điểm lớn nhất của biến static đó chính là chúng ta có thể sử dụng trực tiếp nó mà không cần phải tạo ra đối tượng (instance) từ class. Tuy nhiên do không tạo ra instance dẫn đến việc giá trị ban đầu của biến static cũng không được khởi tạo, nên khi dùng biến static mà không tạo ra đối tượng, chúng ta cần phải khởi tạo giá trị cho nó ở bên ngoài phạm vi class.

Ví dụ:

VjdwUs zBdsnMUzInd5E ohUzrsk5z3qzjrCxDuK3iw2rQHX 1llE1pjPiSvIr8BWe9O4gBlSIeu58kIvJdK9CXQRTDG yWlsQSm6W9J866Z 0ndY5chkjXRo7OeBpgcyEGQ CFHQJ13bbYsHwz0Q - quochung.cyou PTIT

Giống như ví dụ trên, do sNum là biến static nên chúng ta có thể trực tiếp sử dụng giá trị của nó mà không cần phải tạo ra đối tượng từ class. Tuy nhiên biến num thì chỉ là biến thành viên bình thường nên chúng ta không thể sử dụng trực tiếp nó vì lỗi sẽ xảy ra.

6.2. Hàm static

Để tạo hàm static trong class C++, chúng ta thêm toán tử static vào đằng trước tên hàm khi khai báo hàm trong class như sau. Lưu ý là access modifier của hàm static phải ở dạng public để cho phép truy cập nó từ ngoài phạm vi class.

Lưu ý: đối với các hàm static thì các biến sử dụng bên trong phải là biến static

Ví dụ:

9LO5wMGm97Qb291ve6J AvXkWn8VTs l yllM5W - quochung.cyou PTIT
  1. Kỹ thuật chia tách file trong C++

Headers file

Khi các chương trình phát triển ngày càng lớn (và sử dụng nhiều file hơn), Việc khai báo các hàm bạn muốn sử dụng được định nghĩa trong cùng một file sẽ trở nên ngày càng tẻ nhạt và làm cho file đó trở lên nhiều dòng code, khó kiểm soát. Sẽ tốt hơn nếu bạn có thể đặt tất cả các khai báo của mình ở một nơi và sau đó định nghĩa chúng khi bạn cần nó.

Viết một file header

Ví dụ chúng ta có hai tệp, add.cpp và main.cpp, trông giống như thế này:

LNlR7gRsreZomKD6JOtH8Unnc9isFedY8yTE0S3Bu6HwYVX6x7N2E r2uZ6e3FBIzDwctFkyk4VGkdEeFH1Nwd hGD7Jq7yvAF6tbrvNhAYX1 PP GOTc0sGhuiQllSrpL7CIb4W8tvwjvd4wb64yw - quochung.cyou PTIT

Trong ví dụ này, chúng ta đã sử dụng một khai báo để trình biên dịch sẽ biết định danh add là gì khi biên dịch main.cpp. Chúng ta có thể thấy việc thêm thủ công cho mọi hàm bạn muốn sử dụng trong một file khác khá nhiều và phức tạp.

Hãy viết một file header để giảm bớt gánh nặng này. Viết một file header rất dễ dàng, vì các file header chỉ bao gồm hai phần:

Các tiền chỉ thị trong file header.

Nội dung thực tế của file header, phải là khai báo cho tất cả các định danh mà chúng ta muốn các file khác có thể nhìn thấy.

Sử dụng hậu tố .h khi đặt tên file header

Các file header thường được ghép nối với các file code, với file header cung cấp các khai báo cho file code tương ứng. Vì file header của chúng ta sẽ chứa một khai báo cho các hàm được định nghĩa trong add.cpp, nên chúng ta sẽ gọi file header mới là add.h.

Nếu một file header được ghép nối với một file code (ví dụ: add.h với add.cpp), cả hai sẽ có cùng tên (add).

File add.h

doyig TXI0hIRsuVPmObj7bZanklmCVvX2JlvwD - quochung.cyou PTIT

Để sử dụng file header này trong main.cpp, chúng ta phải #include nó (sử dụng dấu ngoặc kép, không phải dấu ngoặc nhọn).

main.cpp

ArtV1BocMhzCaB KwzB2ZjkXZIcCRAWFwK9WMiHbJ3dLd CDJTnN5nXTlctRy3Ny8yyh3oNFcqVOlsx2F5cDQhnZnh64Cx 4NSHdjEFTzVLZEjmiITMWm4m8WA1hTbd2FffRTS1PsxSTxRlVu O5CQ - quochung.cyou PTIT

Quy tắc đặt tên biến trong OOP C++

Luôn sử dụng tiếng Anh để đặt tên hàm và biến.

Ví dụ:

/* Bad */

const hoTen = “Trạng Tí”

const banBe = [“Sửu Ẹo”, “Dần Béo”, “Cả Mẹo”]

/* Good */

const fullName = “Trạng Tí”

const friends = [“Sửu Ẹo”, “Dần Béo”, “Cả Mẹo”]

Quy ước đặt tên

Nếu team của bạn chọn quy ước đặt tên là camelCase, hãy sử dụng camelCase cho toàn bộ dự án, nếu bạn qua một team khác chuộng snake_case hơn, hãy tuân thủ nghiêm ngặt. Cho dù là quy ước nào thì điều quan trọng nhất chính là tính nhất quán.

/* Bad */

const page_count = 5

const isUser = true

/* Good */

const pageCount = 5

const isUser = true

Nguyên tắc S-I-D

Short (ngắn gọn): tên không được dài, không phải mất thời gian để gõ và nhớ.

Intuitive (tự nhiên): tên khi đọc lên phải cho cảm giác xuôi tai, gần gũi với văn nói.

Descriptive (súc tích): tên phải mô tả được ý nghĩa, tác dụng của nó, bằng cách hiệu quả nhất.

/* Bad */

const totalNumberOfPublishedArticles = 10 // tên quá dài

const a = 5 // “a” không mô tả được số 5 để làm gì

const isDisplayable = a > 5 // “isDisplayable” nghe không tự nhiên lắm

/* Good */

const totalArticles = 10

const postCount = 5

const shouldDisplay = postCount > 5

Tính kế thừa – Inheritance [C++ OOP]

Kế thừa là gì?

  • Tính kế thừa (Inheritance) là một trong bốn tính chất cơ bản của Lập trình hướng đối tượng (OOP).
  • Kế thừa có thể được định nghĩa là quá trình mà một lớp (class) có được các thuộc tính của một lớp khác. Các thuộc tính đó có thể là một phương thức (method) hoặc một trường (field) nào đó. Có nghĩa là nếu lớp A kế thừa lớp B thì lớp A sẽ có những thuộc tính và phương thức của lớp B.
  • Lớp được thừa hưởng những thuộc tính và phương thức từ lớp khác được gọi là dẫn xuất (Derived Class) hay lớp Con (Subclass) và các lớp bị lớp khác kế thừa được gọi là lớp cơ sở (Base Class) hoặc lớp cha (Parent Class).

Cú pháp kế thừa

6Em9crRKpncLAstWY2Z2bQZfwt ERW0Sck jghXSFbpcKJxvyDbN94we3Yf8c0kLHreuGstaVmMTX3cptUJnE1vGYRS3wA - quochung.cyou PTIT

Đơn kế thừa, đa kế thừa

Đơn kế thừa

  • Đơn kế thừa (single inheritance) là một class con kế thừa duy nhất từ một class cha. Ví dụ như class sinh viên chỉ kế thừa duy nhất từ class con người.

Khai báo:

w8VoTdRWgs3vjKMRb973p jC ZkXSiEhdOBSacDB8MS9VCMKTRoQgb 3w4ggLcaWfAiUFewo8I5kMtt3kAA DoUTHSqXjtDpWRgWTIBhLLew bVryXWGIh8iEhsiF0IDApB U WLVE - quochung.cyou PTIT

Tính chất dẫn xuất trong đơn kế thừa

Lớp dẫn xuấtLớp dẫn xuấtLớp dẫn xuất
Lớp cơ sởPublicPrivateProtected
PrivateKhông kế thừaKhông kế thừaKhông kế thừa
ProtectedProtectedPrivateProtected
PublicPublicPrivateProtected
  • public: Nếu kế thừa ở dạng này, sau khi kế thừa, tất cả các thành viên dạng public lớp cha sẽ public ở lớp con, dạng protected ở lớp cha vẫn sẽ là protected ở lớp con.
  • protected: Nếu dùng protected thì sau khi kế thừa, tất cả các thành viên dạng public lớp cha sẽ trở thành protected tại lớp con.
  • private: Trường hợp ta sử dụng private, thì sau khi kế thừa, tất cả các thành viên dạng public và protected ở lớp cha sẽ thành private tại lớp con.

Đa kế thừa

  • Đa kế thừa (multiple inheritance) nghĩa là thay vì một class dẫn xuất kế thừa từ một class cơ sở, class dẫn xuất này có thể có kế thừa từ nhiều class cơ sở khác nhau. Ví dụ như con cái thì sẽ kế thừa đặc điểm từ cả bố và mẹ của chúng.

Khai báo:

p7jnwzKx8OTXleC1sNzeAfsA41V19Q4Lo GOHqrzDGN9kbth2XZyeMQDyhWrSkaqO zTLzh0Wc NFEcKg HWhMqr9jsI r9geXyEOjP0FnYhHZ MDYPXaXGsST7UytG7oU1b8W5yNlXV - quochung.cyou PTIT
  • Khi thừa kế từ nhiều class cơ sở, bạn thấy sẽ có một vấn đề phát sinh chính là khi lớp cơ sở có phương thức trùng tên, khi gọi phương thức sẽ xảy ra sự mơ hồ. Sự mơ hồ nghĩa là trình biên dịch không biết bạn đang gọi phương thức từ class dẫn xuất nào. Để giải quyết vấn đề này, ta có thể sử dụng toán tử phạm vi :: để chỉ rõ phương thức ở class nào. Ví dụ:
6LY VpYtPOiVZa5X62k5a21IgZF0z RbLRqRYW nD4H7nHvSPDvVieoemMZtnmVkKhKqv7dNyMf5AZOXhysn - quochung.cyou PTIT

Function Overloading & Operator Overloading

  • Ngôn ngữ C++ cho phép xác định nhiều hơn một định nghĩa cho một tên hàm hoặc một toán tử trong cùng phạm vi, được gọi tương ứng là Nạp chồng hàm và Nạp chồng toán tử.
  • Một khai báo nạp chồng là một khai báo mà đã được khai báo cùng tên như một khai báo được khai báo trước đó trong cùng phạm vi, ngoại trừ rằng: cả hai khai báo có các tham số khác nhau và định nghĩa khác nhau

Nạp chồng hàm

  • Kỹ thuật này cho phép sử dụng cùng một tên gọi cho các hàm “giống nhau” (có cùng mục đích). Nhưng khác nhau về kiểu dữ liệu tham số hoặc số lượng tham số.
  • Ví dụ: Nạp chồng hàm cho phép ta khai báo và định nghĩa các hàm sau với cùng một tên gọi.
tYUerjOrMqnd4LwQawL7weM2TMV1 efVoirK6BCawbTGbNbrDFFX7u0WcxYT NcQCnn0G1VHHXn7CuZU4P8ykm3WL TBGd ESMMmlmN61dEKqbpE6GbmU2VNrl4ZJ kM7o3IlRn ToZ2 - quochung.cyou PTIT

Nạp chồng toán tử

  • .Nạp chồng toán tử (overload operator) là bạn định nghĩa lại toán tử đã có trên kiểu dữ liệu người dùng tự định nghĩa để dể dàng thể hiện các câu lệnh trong chương trình.
  • Ví dụ như bạn định nghĩa phép cộng cho kiểu dữ liệu phân số thì sẽ thực hiện cộng hai phân số rồi trả về một phân số mới. So với việc thực hiện gọi hàm, việc overload toán tử sẽ làm cho câu lệnh ngắn gọn, dễ hiểu hơn.
XjTsX IbJE9IzgP5NGytA763YIqjQffZljq53jqA znKHiJloMouE1UsO4nfhSHOnWc3q2MIyzYoWHBVkgZCavfVoSp Aofx6bbHdlzfSTA1VGuM0M8V2ig6X89ArKN zBd5My8BmKpw - quochung.cyou PTIT

Cú pháp:

=> Như đã giới thiệu, bản chất việc dùng toán tử là lời gọi hàm, do đó chúng ta overload toán tử cũng giống overload hàm, vậy chúng ta sẽ overload hàm nào? Chúng ta sẽ overload hàm có tên là operator @, với @ là toán tử cần overload (+, -, *, /…), kiểu trả về của hàm chính là lớp đó.

Nạp chồng toán tử 2 ngôi:

BQ1NHTILv 0aXq0Ha9tsVX88OBxh70XHLdYkO3sBFS8w7FRJ 0oD4jFagxDUI1336PbZF88rPajtgOGMYanSItSKRFtOz1ghoLz9mtCaB2YzwO1GROk7qh4D8 Nbj2SunNMe oQZDIDD - quochung.cyou PTIT

Function Overriding

  • Trong kế thừa, khi lớp con khai báo phương thức có tên trùng với phương thức ở lớp cha thì phương thức của lớp cha sẽ bị thay thế bởi phương thức ở lớp con.
- quochung.cyou PTIT

Hàm Friend

  • Hàm bạn của một lớp không phải là hàm thành viên nên nó không phụ thuộc vào lớp và có thể định nghĩa ở trong hoặc ngoài lớp. 
  • Hàm bạn có thể truy cập trực tiếp các thành viên private và một lớp có thể có nhiều hàm bạn.
  • Cú pháp khai báo hàm bạn đơn giản là thêm từ khóa friend trước hàm bình thường ta vẫn sử dụng.