Generics trong Java

[toc]

Định nghĩa

Generic programming trong Java là việc ta cho phép 1 phương thức hay 1 class xử lý các đối tượng thuộc 1 kiểu dữ liệu tuỳ ý (generic). Kiểu dữ liệu tuỳ ý này có dạng  compile-time type safety. Tức là 1 khi nó đã được khai báo, trình biên dịch sẽ không chấp nhận việc phương thức hay class chứa xử lý các kiểu dữ liệu khác.

Để dễ hiểu hơn mình sẽ lấy 1 ví dụ đơn giản thế này. Ta khai báo 1 ArrayList như sau:

ArrayList<String> listName = new ArrayList<>();

Bạn thấy listName là 1 ArrayList và nó chỉ chấp nhận các giá trị String. Ở đây ArrayListclass chứa và String chính là kiểu dữ liệu “tuỳ ý” mà mình nói ở trên.

Mình lại lấý 1 ví dụ khác:

ArrayList<Integer> listNumber = new ArrayList<>();

Lần này thì ArrayList lại chỉ chấp nhận xử lý kiểu dữ liệu Integer, bởi vì Integer đã được khai báo với ArrayList.

Tạo 1 generic class (lớp chứa)

Giống như mọi class khác, generic class được khai báo với từ khoá class. Điểm khác biệt ở đây là nó còn có thêm cặp <T> để tượng trưng cho kiểu dữ liệu mà nó sẽ xử lý. Ví dụ:

public class Param<T> {
	T value;

	public T getValue() {
		return value;
	}

	public void setValue(T value) {
		this.value = value;
	}
}

Như các bạn thấy <T> tượng trưng cho kiểu dữ liệu sẽ được xử lý bởi Param. Nó có thể là bất cứ kiểu gì: Integer, String, Boolean, Animal, Human, ….. Nhưng 1 khi đã được khai báo, lớp chứa của nó (generic class) sẽ chỉ chấp nhận xử lý kiểu dữ liệu đã được khai báo.

Giờ mình sẽ tạo ra 1 đối tượng Param chuyên xử lý kiểu dữ liệu Integer:

Param<Integer> paramInteger = new Param<>();
paramInteger.setValue(10); // lúc này hàm setValue chỉ chấp nhận giá trị có kiểu Integer
System.out.println(paramInteger.value); //in ra 10

Mình sẽ tạo 1 đối tượng Param khác chuyên xử lý kiểu String:

Param<String> paramString= new Param<>();
paramString.setValue("cungdev.com"); //// lúc này hàm setValue chỉ chấp nhận giá trị có kiểu String
System.out.println(paramString.value); //in ra cungdev.com

Tương tự, bạn có thể khai báo bất cứ kiểu dữ liệu nào để generic class Param xử lý.

Kế thừa generic class

Việc kế thừa generic class thực chất chẳng khác gì kế thừa 1 class bình thường

Ví dụ mình khai báo 1 generic class AbstractParam như sau (lớp này sẽ được kế thừa bởi các lớp khác):

public abstract class AbstractParam<T> {
	protected T value;
	protected abstract void printValue();

}

Không khác gì 1 lớp bình thường ngoài việc khai báo thêm kiểu dữ liệu generic <T> phải không?

Giờ ta sẽ tạo 1 lớp Email kế thừa AbstractParam:

public class Email extends AbstractParam<String>{

	@Override
	protected void printValue() {
		System.out.println("My email is:"+value);
		
	}	

}

Ta đã tạo 1 lớp Email kế thừa AbstracParam<String>, điều này có nghĩa là:

  • Ta đã chỉ định generic type là String
  • Thuộc tính value trong lớp Email sẽ có kiểu là String.

Ta sẽ test 2 điều trên bằng câu lệnh sau:

Email email = new Email();
email.value = "duongtuanvu1111@gmail.com";
email.printValue();//in ra My email is:duongtuanvu1111@gmail.com
email.value = 10; //Khong duoc, vi value bat buoc phai mang kieu String

Giờ mính sẽ khai báo 1 lớp khác là Age:

public class Age extends AbstractParam<Integer>{

	@Override
	protected void printValue() {
		System.out.println("My age is "+value);
		
	}
	
}

Tương tự như class Email, nhưng lần này Age được khai báo là generic class của kiểu Int, có nghĩa là trường value của Age chỉ có thể mang kiểu Int:

Age age = new Age();
age.value = 10;
age.printValue(); //in ra My age is 10
age.value = "Nam"; //khong duoc vi value bat buoc phai mang kieu Int

Mình sẽ lấy thêm 1 ví dụ nữa, ta khai báo class Height như sau:

public class Height<T> extends AbstractParam<T> {

	@Override
	protected void printValue() {
		System.out.println("My height is "+value);
		
	}

}

Ở đây khi định nghĩa Height, ta chưa khai báo rõ kiểu generic (trong 2 trường hợp trước ta đã chỉ ra luôn StringInteger), tức là Height có thể xử lý bất kỳ kiểu dữ liệu gì mà không bị “ép buộc” phải xử lý StringInteger như class EmailAge. Ví dụ:

Height<Float> heightFloat = new Height<>(); //kieu du lieu se duoc xu ly la Float
heightFloat.value = 10f;
heightFloat.printValue(); //in ra My height is 10.0		
Height<Long> heightLong = new Height<>(); //kieu du lieu se duoc xu ly la Long
heightLong.value = 10000L;
heightLong.printValue(); //in ra My height is 10000

Đó là những kiến thức cơ bản về kế thừa generic class, hy vọng mình diễn đạt dễ hiểu.

Khai báo nhiều dữ liệu generic (Multiple type parameters)

Ta không những có thể khai báo kiểu dữ liệu generic cho class xử lý, ta còn có thể khai báo tuỳ thích số lượng các kiểu generic. Ví dụ:

public class MultiGenericParam<T, S> {
	private T firstParam;
	private S secondParam;

	public MultiGenericParam(T firstParam, S secondParam) {
		this.firstParam = firstParam;
		this.secondParam = secondParam;
	}

	public T getFirstParam() {
		return firstParam;
	}

	public void setFirstParam(T firstParam) {
		this.firstParam = firstParam;
	}

	public S getSecondParam() {
		return secondParam;
	}

	public void setSecondParam(S secondParam) {
		this.secondParam = secondParam;
	}
}

Lớp trên sẽ được sử dụng kiểu thế này:

MultiGenericParam<String, String> aParam = new MultiGenericParam<String, String>("value1", "value2");
MultiGenericParam<Integer, Double> dayOfWeekDegrees = new MultiGenericParam<Integer, Double>(1, 2.6);

T , ? super T và ? extends T

Đôi khi ta không nhất thiết phải khai báo generic Type 1 cách chính xác, ? super T? extends T giúp ta điều này. Cụ thể:

  • ? super T tượng trưng cho các lớp là lớp cha của T
  • ? extends T tượng trưng cho các lớp kế thừa T

Để rõ hơn mình sẽ lấy ví dụ như sau. Mình tạo các class:

class Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {} 
class GrannySmith extends Apple {}						

public class FruitHelper {
    public void eatAll(Collection<? extends Fruit> fruits) {} 
    public void addApple(Collection<? super Apple> apples) {}
						
}

Giờ ta sẽ sử dụng các câu lệnh sau để thấy rõ được cách sử dụng của T, ? super T? extends T

FruitHelper fruitHelper = new FruitHelper();
List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Apple()); 
// Hợp lệ, do Apple kế thừa Fruit nên Apple chính là 1 Fruit 
//ArrayList fruits chấp nhận xử lý các đối tượng Fruit nên Apple cũng được chấp nhận
Collection<Banana> bananas = new ArrayList<>();
bananas.add(new Banana());
//Chấp nhận do ArrayList bananas chấp nhận xử lý các đối tượng Banana
Collection<Apple> apples = new ArrayList<>();
fruitHelper.addApple(apples); // Allowed
apples.add(new GrannySmith());
//Chấp nhận do GrannySmith kế thừa Apple
Collection<GrannySmith> grannySmithApples = new ArrayList<>();
fruitHelper.addApple(grannySmithApples);
//Ko chấp nhận do fruitHelper chỉ chấp nhận list các đối tượng Apple
//Tuy GrannySmith kế thừa Apple, nó là 1 Apple nhưng chưa chắc 1 Apple là 1 GrannySmith
fruitHelper.eatAll(grannySmithApples);
//Chấp nhân, do GrannySmith là 1 Fruit
Collection<Object> objects = new ArrayList<>();
fruitHelper.addApple(objects);
//Chấp nhận do Apple kế thừa class Object

 

 

Visibility – truy cập đến thuộc tính trong class

Thuộc tính private

Thuộc tính private chỉ có thể được truy cập trong class chứa nó. Tức là các class khác không thể truy xuất đến các thuộc tính private. Các thuộc tính này thường được truy xuất đến thông qua các phương thức getter và setter.

Ví dụ, ta tạo 1 class như sau:

public class SomeClass {

	private int variable;

	public int getVariable() {
		return variable;
	}

	public void setVariable(int variable) {
		this.variable = variable;
	}

}

Giờ ta sẽ thử truy xuất đến thuộc tính private int variable:

SomeClass sc = new SomeClass();
 // Không được, vì thuộc tính variable là private
 sc.variable = 7; 
System.out.println(sc.variable); 

// được, vì truy cập variable thông qua hàm setter và getter
sc.setVariable(7); 
System.out.println(sc.getVariable());

Thuộc tính public

Thuộc tính public có thể truy xuất thoải mái từ các class khác.

Ví dụ ta khai báo 1 class:

public class TestPublicVariable {
	public int variable; 
}

Ta thử truy xuất đến thuộc tính public int variable:

Test t = new Test(); 
t.variable = 5;
System.out.println(t.number); //in ra 5

Thuộc tính protect

Thuộc tính protect có thể được truy xuất từ trong class chứa nó, trong package chứa class chứa nó và các class kế thừa class chứa nó. Ví dụ:

Ta tạo 1 class:

public class ParentClass {
    protected int value = 10;
}

Trong cùng package chứa ParentClass ta tạo 1 class Test để thử truy xuất đến thuộc tính value:

public class Test {

	public static void main(String[] args) throws IOException {
		
		ParentClass parent = new ParentClass();
		parent.value = 10;
		System.out.println(parent.value);//in ra 10
	}


}

Ta tạo 1 package khác (giả sử new_package), trong package này sẽ tạo 1 class ChildClass như sau:

public class ChildClass {

	public static void main(String[] args) throws IOException {
		
		ParentClass parent = new ParentClass();
		//Không được vì khác package
		parent.value = 10;
		System.out.println(parent.value);//in ra 10
	}


}

Như vậy là 2 class khác package sẽ không truy cập được các thuộc tính protected của nhau.

Giờ ta sẽ cho ChildClass kế thừa ParentClass:

public class ChildClass extends ParentClass{

	public static void main(String[] args) throws IOException {
		
		ParentClass parent = new ParentClass();
		//ok
		parent.value = 10;
		System.out.println(parent.value);//in ra 10
	}


}

Lúc này ChildClass sẽ truy xuất được thuộc tính protected value của ParentClass.

Package Visibility

Nếu không chỉ ra modifier, thuộc tính sẽ được truy xuất bởi các class cùng package class chứa nó.

Ta tạo 1 class:

public class TestVariable {
	public int variable; 
}

Tạo tiếp 1 class cùng packge với class trên:

public class OtherClass{

	public static void main(String[] args) throws IOException {
		
		TestVariable test= new TestVariable ();
		test.variable = 10;
		System.out.println(test.test);
	}


}

Bạn hãy thử tạo 1 class ngoài package chứa TestVariable, bạn sẽ thấy nó không thể truy xuất đến thuộc tính variable của class TestVariable.

Class và object

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

 

Các kiểu dữ liệu nguyên thủy (Primitive Data Types)

Dữ liệu nguyên thủy (Primitive Data Types) là các kiểu dữ liệu được Java cung cấp sẵn cho chúng ta sử dụng. Trong Java 8 có các kiểu dữ liệu cơ bản sau:  byte, short, int, long, char, boolean, float, và double.

Ta sẽ lần lượt điểm qua về các kiểu dữ liệu này.

[toc]

char

Kiểu char dùng để chứa 1 ký tự đơn dạng Unicode 16 bit. Ký tự này được chứa trong cặp dấu nháy đơn. Ví dụ:

char myChar = 'u'; 
char myChar2 = '5'; 
char myChar3 = 65; // myChar3 == 'A'

Lưu ý: char chỉ được dùng để lưu trữ ký tự đơn. Không có chuyện:

char myChar = 'xyz';

Giá trị nhỏ nhất của 1 biến kiểu Char là 0 (2^0-1) và giá trị lớn nhất của biến char là 65535 (2^16-1).

Để biểu diễn ký tự nháy đơn. Ta làm như thế này:

char singleBlock = '\'';

Biểu diễn các ký tự đặc biệt khác dưới dạng char:

char tab = '\t'; 
char backspace = '\b'; 
char newline = '\n'; 
char carriageReturn = '\r'; 
char formfeed = '\f'; 
char singleQuote = '\''; //dấu nháy đơn
char doubleQuote = '\"'; //dấu nháy kép
char unicodeChar = '\uXXXX' // XXXX là mã ký tự trong bảng mã Unicode

Bạn có thể biểu diễn bất cứ ký tự nào trong bảng mã unicode bằng kiểu dữ liệu char:

char heart = '\u2764';
System.out.println(Character.toString(heart)); // in ra "❤"

float

Kiểu dữ liệu Float sử dụng để biểu diễn các số thực 32 bit. Mặc định trong Java, 1 số thực sẽ được hiểu là mang kiểu Double, vì vậy khai báo biến Float ta cần thêm hậu tố f vào sau giá trị biến. Ví dụ:

double doubleExample = 0.5; // không có hậu tố f, => kiểu double
float floatExample = 0.5f; // có hậu tố f, => kiểu float

float myFloat = 92.7f; // 1 biến kiểu float
float positiveFloat = 89.3f; // biến float mang giá trị dương
float negativeFloat = -89.3f; // biến float mang giá trị âm
float integerFloat = 43.0f; // biến float có thể mang giá trị của 1 số nguyên (nhưng nó không phải int) 

1 biến Kiểu dữ liệu Float có thể sử dụng các toán tử cộng, trừ nhân chia và lấy module:

// cộng
float result = 37.2f + -2.6f; // result: 34.6 
// trừ
float result = 45.1f - 10.3f; // result: 34.8 
// nhân
float result = 26.3f * 1.7f; // result: 44.71 GoalKicker.com – Java® Notes for Professionals 76 
// chia
float result = 37.1f / 4.8f; // result: 7.729166 
// phần dư
float result = 37.1f % 4.8f; // result: 3.4999971

Lưu ý:

  • Lưu ý: : Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN là những giá trị biểu diễn cho dương vô cùng, âm vô cùng, và not a number.
  • Float.NaN biểu diễn cho kết quả của những phép tính không thể xác định giá trị, vd như phép chia của 2 giá trị vô cùng. Ví dụ:
System.out.println(1f / 0f); // dương vô cùng
System.out.println(1f / -0f); // âm vô cùng
System.out.println(Float.POSITIVE_INFINITY / Float.POSITIVE_INFINITY); // NaN (not a number)

int

int biểu diễn các giá trị nguyên 32 bit (các giá trị nằm trong khoảng -(2^32)+1 và 2^32-1. Ví dụ:

int example = -42;
int myInt = 284;
int anotherInt = 73; 
int addedInts = myInt + anotherInt; // 284 + 73 = 357
int subtractedInts = myInt - anotherInt; // 284 - 73 = 211

Nếu ta cần biểu diễn những giá trị ngoài khoảng Int, ta cần sử dụng kiểu dữ liệu long.

Các giá trị lớn nhất và nhỏ nhất của int:

nt high = Integer.MAX_VALUE; // GTLN == 2147483647 
int low = Integer.MIN_VALUE; // GTNN == -2147483648 

Giá trị mặc định của int là 0:

int defaultInt; // defaultInt == 0;

double

Tương tự như float, double biểu diễn kiểu số thực. Nhưng vùng giá trị của double lớn hơn float. Double biểu diễn cho kiểu số thực 64 bit. Mặc định 1 số thực sẽ được Java hiểu là kiểu double. Ta có thể định nghĩa 1 số thực như sau:

double example = -7162.37; //Mặc định số thực được hiểu là double
double myDouble = 974.21d; //Có thể thêm hậu tố d vào sau giá trị
double anotherDouble = 658.7; 
double addedDoubles = myDouble + anotherDouble; // 315.51
double subtractedDoubles = myDouble - anotherDouble; // 1632.91 
double scientificNotationDouble = 1.2e-3; // 0.0012

Giá trị mặc định của double là 0.

public double defaultDouble; // defaultDouble == 0.0

Lưu ý:

  • Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN là những giá trị biểu diễn cho dương vô cùng, âm vô cùng, và not a number.
  • Double.NaN biểu diễn cho kết quả của những phép tính không thể xác định giá trị, vd như phép chia của 2 giá trị vô cùng. Ví dụ:
System.out.println(1f / 0d); // Dương vô cùng
System.out.println(1f / -0d); // Âm vô cùng
System.out.println(Float.POSITIVE_INFINITY / Float.POSITIVE_INFINITY); // NaN (not a number)

long

long biểu diễn các giá trị nguyên 64 bit (các giá trị nằm trong khoảng -(2^64)+1 và 2^64-1. Ví dụ:

long example = -42L;
long myLong = 284L;
long anotherLong = 73L;

//Trong java 1 số nguyên mặc định được hiểu là có kiểu int
//1 số nguyên lớn, ví dụ như 549755813888 (2 ^ 39), lớn hơn giá trị max của 
// kiểu int (2^31 - 1), bắt buộc phải thêm hậu tố L
long bigNumber = 549755813888L;
long addedLongs = myLong + anotherLong; // 284 + 73 = 357
long subtractedLongs = myLong - anotherLong; // 284 - 73 = 211

Các giá trị lớn nhất và nhỏ nhất của long:

long high = Long.MAX_VALUE; // high == 9223372036854775807L
long low = Long.MIN_VALUE; // low == -9223372036854775808L

Giá trị mặc định của long là 0:

long defaultLong; // defaultLong == 0L

Chú ý:

Dù ta khai báo kiểu long, nhưng với những biến có giá trị nằm trong khoảng -127 đến 127, biến đó vẫn mang giá trị của 1 đối tượng Integer. Ví dụ:

Long val1 = 127L;
Long val2 = 127L;
System.out.println(val1 == val2); // true
Long val3 = 128L;
Long val4 = 128L;
System.out.println(val3 == val4); // false

Để so sánh chính xác 2 biến long, ta cần làm như sau:

Long val3 = 128L;
Long val4 = 128L;
System.out.println(Objects.equal(val3, val4)); // true

Lúc này kết quả so sánh đã mang giá trị true.

boolean

1 biến kiểu boolean chứa 1 trong 2 giá trị true hoặc false:

boolean foo = true;
System.out.println("foo = " + foo); // foo = true
boolean bar = false;
System.out.println("bar = " + bar); // bar = false
boolean notFoo = !foo;
System.out.println("notFoo = " + notFoo); // notFoo = false
boolean fooAndBar = foo && bar;
System.out.println("fooAndBar = " + fooAndBar); // fooAndBar = false
boolean fooOrBar = foo || bar;
System.out.println("fooOrBar = " + fooOrBar); // fooOrBar = true
boolean fooXorBar = foo ^ bar;
System.out.println("fooXorBar = " + fooXorBar); // fooXorBar = true

Giá trị mặc định của boolean là false.

byte

byte biểu diễn số nguyên 8 bit nằm trong khoảng -128 đến 127.

byte example = -36;
byte myByte = 96;
byte anotherByte = 7;
byte addedBytes = (byte) (myByte + anotherByte); // 103
byte subtractedBytes = (byte) (myBytes - anotherByte); // 89

Giá trị lớn nhất và nhỏ nhất của byte:

byte high = Byte.MAX_VALUE; // high == 127
byte low = Byte.MIN_VALUE; // low == -128

Giá trị mặc định của byte là 0:

byte defaultByte; // defaultByte == 0

short

short biểu diễn giá trị nguyên trong khoảng -32768 đến 32767

short example = -48;
short myShort = 987;
short anotherShort = 17;
short addedShorts = (short) (myShort + anotherShort); // 1,004
short subtractedShorts = (short) (myShort - anotherShort); // 970

Giá trị lớn nhất và nhỏ nhất của short:

short high = Short.MAX_VALUE; // high == 32767
short low = Short.MIN_VALUE; // low == -32768

Giá trị mặc định của short là 0:

short defaultShort; // defaultShort == 0