Seri lập trình Java – Cùng dev

Class và object

Một chút về định nghĩa

Trong Java, object là các đối tượng có các thuộc tính (properties) và hành động (method).

Ví dụ: 1 con mèo là 1 đối tượng (object) có thuộc tính color = yellow (lông màu vàng) và phương thức eat() – hành động ăn.

Class là thứ đặc trưng cho các đối tượng cùng kiểu. Ví dụ class Cat đặc trưng cho các object mèo (là những đối tượng có các thuộc tính color và phương thức eat.

Khai báo class

Ta sử dụng từ khóa class để khai báo class:

public class Cat {

	String color;
	String name;

	public Cat(String name, String color) {
		this.color = color;
		this.name = name;
	}

	public void eat() {
		System.out.println(name + " is eating");
	}
}

Ở trong class Cat này ta đã khai báo các thuộc tính color và name cho các đối tượng Cat.

Ta cũng khai báo 1 phương thức eat cho các đối tượng Cat.

Đồng thời ta cũng xây dựng 1 phương thức cho phép tạo ra các đối tượng Cat – phương thức này gọi là constructor – hàm khởi tạo (sẽ nói ở phần sau):

public Cat(String name, String color) {
     this.color = color;
     this.name = name;
}

Như các bạn thấy hàm khởi tạo này cho phép tạo ra đối tượng Cat có thuộc tính name và color.

Khởi tạo đối tượng

Ta sẽ tạo ra 1 đối tượng Cat như sau:

Cat cat = new Cat(“Lyly”, “yellow”);

Giờ đây ta đã có thể sử dụng các phương thức trong class Cat:

cat.eat(); //in ra Lily is eating

Nạp chồng phương thức – Overloading methods

Trong 1 class, đôi khi với 1 phương thức ta có thể truyền các đầu vào khác nhau. Java cho phép ta tạo ra nhiều phương thức có cùng tên, nhưng khác đầu vào.

Ví dụ class Cat trên đã có sẵn phương thức eat – ko truyền giá trị đầu vào. Giả sử ta muốn khai báo thêm 1 phương thức eat(String food) truyền thêm đầu vào là food. Ta sẽ làm như sau:

public void eat(String food){
    System.out.println(name + " is eating "+food);
}

Như vậy 1 phương thức eat khác đã được tạo ra và cho phép ta truyền giá trị đầu vào là food – tên thức ăn. Ta sẽ sử dụng phương thức này:

Cat secondCat = new Cat("Miumiu", "Black");
secondCat.eat("Carrot"); //in ra: Miumiu is eating Carrot

Tương tự với hàm khởi tạo, ta cũng có thể tạo ra nhiều hàm khởi tạo với các giá trị truyền vào khác nhau. Ta sẽ thêm 1 hàm khởi tạo mới cho lớp Cat:

public Cat(String name) {
    this.name = name;
}

Hàm này chỉ cho phép truyền vào name mà ko cho truyền thêm color như ban đầu. Ta sẽ thử tạo 1 đối tượng bằng hàm khởi tạo mới:

Cat thirdCat = new Cat("Kiki");
thirdCat.eat("candy"); // in ra Kiki is eating candy

Khi nào thì được nạp chồng hàm

Khi số lượng các tham số truyền vào hàm là khác nhau

Ví dụ:

method();
method(String param1);
method(String param1, String param2);

Khi thứ tự các tham số truyền vào là khác nhau

Ví dụ:

method(int i, float f);
method(float f, int i);
method(int i, float f, int i2);
method(int i2, float f, int i)// Không được, vì kiểu và thứ tự của các tham số trùng với hàm trên, cùng là (int, float, int)

Ghi đè phương thức – override method

Ghi đè phương thức là việc định nghĩa lại 1 phương thức đã có từ trước. Ví dụ thế này, ta có 1 class Shape:

public class Shape {
    public float getArea(){
	return 1f;
    }
}

Shape có phương thức tính diện tích getArea trả về giá trị float = 1. Giờ ta sẽ ghi đè (override) phương thức getArea này.

Đầu tiên ta khai báo 1 lớp Square kế thừa Shape:

public class Square extends Shape {

    float size;
    public Square(float size) {
        super();
	this.size = size;
    }


}

Nói qua cho các bạn nào chưa rõ, việc 1 classA kế thừa 1 classB có nghĩa là classA được thừa hưởng các phương thức public và thuộc tính public của classB. Hay nói cách khác đối tượng classA sẽ có các phương thức và thuộc tính public đã định nghĩa trong classB.

Ở trường hợp này Square kế thừa Shape. Vì vậy 1 đối tượng Square sẽ có thể sử dụng phương thức getArea (tính diện tích) của Shape. Ví dụ:

Square square = new Square(4);
System.out.println("Area = "+square.getArea()); // in ra :Area = 1.0

Nhưng tất nhiên, Square (hình vuông) sẽ có 1 cách tính diện tích (getArea) riêng. Và ta cần ghi đè (viết lại) phương thức getArea() trong class Square như thế này:

@Override
public float getArea() {
    return size * size;
}

Ta đã sử dụng annotation @Override để chỉ ra phương thức getArea đã được ghi đè. 

Khi này ta sẽ có:

Square square = new Square(4);
System.out.println("Area = "+square.getArea()); // in ra :Area = 16.0

Nếu ta tạo 1 class Circle (hình tròn), và kế thừa Shape, ta cũng có thể ghi đè override lại phương thức getArea():

public class Circle extends Shape {
	float r;

	public Circle(float r) {
		super();
		this.r = r;
	}

	@Override
	public float getArea() {
		return (float) Math.PI * r * r;
	}
}

Và ta thử:

Circle circle = new Circle(2);
System.out.println("Area = "+circle.getArea());// in ra Area = 12.566371

Hàm khởi tạo

Hàm khởi tạo là 1 phương thức đặc biệt để tạo ra 1 đối tượng. Cũng giống như các phương thức khác, hàm khởi tạo có thể nhận các giá trị đầu vào. Chỉ có điều hàm khởi tạo không trả về giá trị nào.

Như ví dụ ở trên:

public class Circle extends Shape {
	float r;

	public Circle(float r) {
		super();
		this.r = r;
	}

	@Override
	public float getArea() {
		return (float) Math.PI * r * r;
	}
}

Ở đây ta thấy:

public Circle(float r) {
    super();
    this.r = r;
}

chính là hàm khởi tạo của lớp Circle.

Những điều cần lưu ý về hàm khởi tạo:

  • Hàm khởi tạo có thể mang bất kỳ modifier nào (public, private, protected), nhưng không được khai báo dưới dạng abstract, static, hay synchronized.

  • Hàm khởi tạo ko trả về giá trị.

  • Hàm khởi tạo bắt buộc phải có tên trùng với tên class

  • từ khóa this sử dụng trong hàm khởi tạo dùng để chỉ đối tượng hiện tại (đối tượng mà hàm khởi tạo đó tạo ra)

Thuộc tính static

Java cho phép ta sử dụng 1 biến static làm thuộc tính của class. Vậy thuộc tính static này khác với thuộc tính thông thường mà ta đã biết ở chỗ nào?

Mình tạo 1 class:

class ObjectMemberVsStaticMember {
	static int staticCounter = 0;
	int memberCounter = 0;

	void increment() {
		staticCounter++;
		memberCounter++;
	}
}

Ở đây ta tạo 1 class chứa 1 thuộc tính kiểu int là memberCounter và thuộc tính static int staticCounter

Giờ mình sẽ chỉ ra sự khác nhau giữa 2 thuộc tính này:

final ObjectMemberVsStaticMember o1 = new ObjectMemberVsStaticMember(); 
final ObjectMemberVsStaticMember o2 = new ObjectMemberVsStaticMember(); 
o1.increment(); 
o2.increment(); 
o2.increment(); 
System.out.println("o1 static counter =" + o1.staticCounter); 
System.out.println("o1 member counter =" + o1.memberCounter);

System.out.println("o2 static counter =" + o2.staticCounter); 
System.out.println("o2 member counter =" + o2.memberCounter); 
System.out.println(); 
System.out.println("ObjectMemberVsStaticMember.staticCounter = " + ObjectMemberVsStaticMember.staticCounter);

Ta tạo ra 2 đối tượng o1o2, 2 đối tượng này cùng gọi đến phương thức increment. Tổng cộng là 3 lời gọi đến phương thức increment.Ta sẽ xem log kết quả để thấy sự khác biệt giữa memberCounter  staticCounter. Kết quả là:

o1 static counter =3 
o1 member counter =1 
o2 static counter =3 
o2 member counter =2 
ObjectMemberVsStaticMember.staticCounter = 3

Thuộc tính static là thành phần của class và chỉ tồn tại duy nhất trong class đó. Tức là bạn có thể tạo ra hàng trăm đối tượng, nhưng các thuộc tính static thì chỉ được tạo ra duy nhất 1 lần.

Thuộc tính thường (non static) thì được tạo ra với từng đối tượng của class.

Như ở ví dụ trên: thuộc tính staticCounter được tạo ra duy nhất 1 lần, và mỗi lần gọi hàm increment, giá trị của nó đều tăng lên 1.

Thuộc tính memberCounter thì khác, mỗi đối tượng của lớp ObjectMemberVsStaticMember được sinh ra, thì sẽ có 1 biến memberCounter mới được sinh ra. Như bạn thấy giá trị memberCounter của o1  memberCounter của o2 là khác nhau.