Trong java có 1 framework gọi là Collections. Framework này được đặt trong package java.utils. Nó cung cấp 1 tập hợp các generic class bao gốm các hàm xử lý dữ liệu liên quan đến list mà Array không thể giúp chúng ta giải quyết.
Hãy thử tìm hiểu 1 số phương thức cần biết khi làm việc với Collections
Xóa 1 phần tử của List trong vòng lặp for.
Việc xóa 1 phần tử trong khi duyệt list là 1 việc làm tương đối thường xuyên trong các bài toán thực tế. Ví dụ như ta có 1 list chứa các đối tượng News và Ads. (Dạng này hay gặp ở các app tin tức, khi ta muốn chèn các quảng cáo vào list danh sách tin). Giờ ta chỉ muốn lọc ra các đối tượng News (tức là xóa bỏ đi các đối tượng Ads) trong list. Ta sẽ làm thế nào. Mình sẽ chỉ ra 1 số cách làm sai, và 1 số cách làm đúng. Hãy theo dõi.
Mình có listNews như sau:
List listNews = new ArrayList<>();
listNews.add(new NewsItem());
listNews.add(new NewsItem());
listNews.add(new NewsItem());
listNews.add(new AdsItem());
listNews.add(new AdsItem());
listNews.add(new AdsItem());
class NewsItem
class NewsItem
class NewsItem
class AdsItem
Vì sao cách này sai. Trong vòng lặp, đối tượng AdsItem cuối ko bị xóa đi do i chỉ chạy đến 3, do phần tử AdsItem trước bị xóa. Dẫn đến size của list bị giảm xuống 4 => i chỉ chạy đến 3. Đồng nghĩa với việc đối tượng AdsItem cuối cùng (chỉ số ban đầu là 4) – không bị động tới.
Lúc này ta đã sử dụng đến Iterator, đây là interface cung cấp cho ta phương thức remove dùng để xóa phần tử trong list tạo ra nó.
Cách 2: Sử dụng list should-be-removed:
Ý tưởng chính của cách này là sao chép các phần tử cần xóa sang 1 list mới, sau đó sử dụng hàm removeAll đối với list cũ.
List shoudBeRemoved = new ArrayList<>();
for (Object o : listNews) {
if (o instanceof AdsItem) {
shoudBeRemoved.add(o);
}
}
listNews.removeAll(shoudBeRemoved);
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 ArrayList là class 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 String và Integer), 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ý String và Integer như class Email và Age. 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 và ? 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 và ? 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
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.
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):
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
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 o1 và o2, 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 và 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 và memberCounter của o2 là khác nhau.
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ụ:
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:
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
Hôm nay nhân dịp tí nữa đo đường vì tội vừa đi xe máy vừa gọi điện thoại, mà bản chất cũng chỉ vì sự rắc rối và lóng ngóng khi tìm contact để call. Mình sẽ xây dựng 1 loạt bài tutorial về ứng dụng quay số nhanh trên Android.
Chức năng chủ yếu của ứng dụng này là cho phép tạo ra 1 widget ngoài màn hình home của điện thoại chứa những contact mà ta hay liên lạc. Đồng thời widget này cũng chứa lịch sử cuộc gọi và bàn phím số. Tóm lại nó giúp người dùng đơn giản hóa tối đa thao tác để liên lạc với người khác. Sau đây là video demo của ứng dụng.
[toc]
Chức năng và luồng chạy của ứng dụng
Chức năng chi tiết
Các chức năng của ứng dụng mà mình sẽ làm bao gồm:
Thêm, bớt 1 contact vào danh sách quay số nhanh (trên app)
Xem danh sách quay số nhanh (trên app), cho phép gọi, nhắn tin (đến 1 hoặc nhiều người).
Tạo widget ngoài màn hình home, widget này chứa
Danh sách quay số nhanh, cho phép gọi điện thoại khi click vào contact
Lịch sử cuộc gọi, cho phép gọi điện thoại khi click vào 1 lịch sử
Bàn phím số, cho phép nhập số điện thoại để gọi
Khá đơn giản, đúng không nào.
Luồng chạy của ứng dụng
Ban đầu bạn cần vào app và định nghĩa ra danh sách quay số nhanh. Bằng cách chọn lựa các contact từ danh bạ điện thoại. Danh sách quay số nhanh này có thể thêm bớt, tùy chỉnh về sau.
Sau khi có danh sách quay số nhanh, bạn có thể kéo widget của ứng dụng ra ngoài màn hình home. Widget này có chứa danh sách bạn đã tạo trên app, lịch sử cuộc gọi, bàn phím, cho phép make a phone call 1 cách dễ dàng nhất.
Luồng chạy cũng không có gì phức tạp, nhỉ?
Khởi tạo dự án
Các thư viện sử dụng
Mình sẽ sử dụng những thư viện sau đây để code:
//BUtter Knife dùng để bind view
compile "com.jakewharton:butterknife:$rootProject.butterKnifeVersion"
//SDP android, thư viện chứa các dimensions, hỗ trợ làm layout đa màn hình
compile "com.intuit.sdp:sdp-android:$rootProject.ext.sdpAndroidVersion"
//Material dialog, giao diện dialog material cho android đời thấp
compile 'com.afollestad.material-dialogs:core:0.9.4.4'
//Thư viện log
compile "com.orhanobut:logger:$rootProject.loggerVersion"
//ImageView bo góc, mình lười nên dùng lib luôn
compile 'com.makeramen:roundedimageview:2.3.0'
//HIệu ứng ripple khi click vào các view
compile 'com.balysv:material-ripple:1.0.2'
//Gson dùng để parse json
compile 'com.google.code.gson:gson:2.4'
//Thư viện danh bạ điện thoại
compile 'com.github.tamir7.contacts:contacts:1.1.7'
//Thư viện expandable recyclerview, thư viện này mình import từ 1 module trong project
compile project(':libs:expandablerecyclerview')
Tạo base cho dự án
Permission
Ứng dụng cần quyền truy cập danh bạ, lịch sử cuộc gọi, gọi điện thoại và gửi tin nhắn. Vì vậy ta cần thêm các dòng sau vào file AndroidManifest.xml:
Ở ứng dụng này, trên App ta cần xây dựng 2 màn hình:
Danh sách quay số nhanh
Màn hình Chọn lựa contact từ danh bạ để thêm vào danh sách quay số nhanh.
Mình sẽ sử dụng 2 Activity để xây dựng 2 màn hình này. Để code được ngắn gọn và tường minh, mình sẽ viết 1 lớp BaseActivity.java. Lớp này chứa 1 số phương thức chung có thể sử dụng cho tất cả các Activity trong dự án. Mọi Activity sẽ kế thừa từ lớp BaseActivity này:
public abstract class BaseActivity extends AppCompatActivity {
protected Gson gson;
protected DBHelper dbHelper;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
gson = new Gson();
dbHelper = QuickDialApplication.getInstance().getDbHelper();
ButterKnife.bind(this);
createView();
}
//Hàm abstract trả về layout của activity
protected abstract int getLayoutId();
//Hàm set sự kiện cho các view trong activity
protected abstract void createView();
//Hàm show thông báo toast
public void showToast(String msg) {
if (msg != null) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "null", Toast.LENGTH_SHORT).show();
}
}
//Hàm chuyển activity
public void showActivity(Class t) {
Intent intent = new Intent(this, t);
startActivity(intent);
}
////Hàm chuyển activity kèm theo bundle
public void showActivity(Class t, Bundle bundle) {
Intent intent = new Intent(this, t);
intent.putExtra(Constant.KEY_EXTRA, bundle);
startActivity(intent);
}
}
Màn hình danh sách quay số nhanh QuickDialActivity
Khai báo layout
Sau khi tạo xong base dự án ta sẽ bắt tay vào màn hình đầu tiên, là màn hình Danh sách quay số nhanh. Mình đặt tên Activity tương ứng là QuickDialActivity. Mình tạo 1 file activity_quick_dial.xml làm layout cho activity này:
Layout khi danh sách quay số nhanh đã có contact trông sẽ thế này:
Về cơ bản, layout này chứa:
GridView để hiển thị danh sách quay số nhanh. (gv_quick_dial)
Layout thông báo chưa có liên lạc nào trong danh sách, click vào layout này sẽ chuyển sang màn hình chọn liên lạc (ll_add)
1 vài button chức năng khác (ta chưa cần quan tâm)
Vậy 2 chức năng đầu tiên mà ta cần xây dựng là:
Lấy được danh sách quay số nhanh
HIển thị được danh bạ điện thoại để chọn liên lạc.
Danh sách quay số nhanh
Các liên hệ trong danh sách quay số nhanh cần được lưu trữ trong 1 database để ta có thể đem ra sử dụng. Ở đây mình dùng luôn SQLite. Vậy công việc đầu tiên của ta là xây dựng 1 database bằng SQLite cho phép thêm, xóa 1 contact vào trong database. Mình sẽ tạo 1 package db và tạo file DBHelper.java. Lớp DBHelper này sẽ quản lý việc thêm, xóa dữ liệu về contact vào database. Cụ thể:
Lưu dữ liệu từ đối tượng Contact vào bảng QContact (thuộc thư viện danh bạ mà mình đã nêu ra ban đầu, đối tượng này chứa các thông tin của 1 contact trong danh bạ điện thoại)
Lấy các dữ liệu đã lưu theo dạng List các đối tượng WidgetContactModel để hiển thị ra ở trên Widget ngoài màn hình home.
Ok, vậy giờ mình sẽ định nghĩa đối tượng WidgetContactModel trước. Mình tạo 1 package models, và file WidgetContactModel.class:
public class WidgetContactModel implements Parcelable {
int id;
String displayName;
String numbers;
String avatar;
byte[] avatarBitmap;
public WidgetContactModel(int id, String displayName, String numbers, String avatar) {
this.id = id;
this.displayName = displayName;
this.numbers = numbers;
this.avatar = avatar;
}
public WidgetContactModel(int id, String displayName, String numbers, String avatar, byte[] avatarBitmap) {
this.id = id;
this.displayName = displayName;
this.numbers = numbers;
this.avatar = avatar;
this.avatarBitmap = avatarBitmap;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getNumbers() {
return numbers;
}
public void setNumbers(String numbers) {
this.numbers = numbers;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public byte[] getAvatarBitmap() {
return avatarBitmap;
}
public void setAvatarBitmap(byte[] avatarBitmap) {
this.avatarBitmap = avatarBitmap;
}
public WidgetContactModel(Parcel in) {
id = in.readInt();
displayName = in.readString();
numbers = in.readString();
avatar = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(id);
parcel.writeString(displayName);
parcel.writeString(numbers);
parcel.writeString(avatar);
}
public static final Creator CREATOR = new Creator() {
@Override
public Object createFromParcel(Parcel parcel) {
return new WidgetContactModel(parcel);
}
@Override
public WidgetContactModel[] newArray(int i) {
return new WidgetContactModel[i];
}
};
}
Xong, giờ mình sẽ quay lại viết nội dung cho DBHelper thực hiện các tác vụ của 1 database:
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "quick-dial-db";
private static final int DB_VERSION = 3;
private static final String CREATE_TABLE_CONTACT = "create table QContact(id integer primary key autoincrement not null, display_name text, numbers text, avatar text, avatar_bitmap blob);";
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_CONTACT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
db.execSQL("drop table if exists QContact");
onCreate(db);
}
public ArrayList<WidgetContactModel> getListContact() {
String query = "select * from QContact";
Cursor cursor = getReadableDatabase().rawQuery(query, null);
ArrayList<WidgetContactModel> listContact = new ArrayList<>();
if (cursor.getCount() > 0) {
cursor.moveToFirst();
do {
WidgetContactModel contact;
contact = new WidgetContactModel(cursor.getInt(0), cursor.getString(1), cursor.getString(2), cursor.getString(3), cursor.getBlob(4));
listContact.add(contact);
} while (cursor.moveToNext());
}
cursor.close();
return listContact;
}
public boolean checkContactSaved(ContactModel contact, int position) {
Cursor cursor = getReadableDatabase().query("QContact", new String[]{"id", "display_name", "numbers", "avatar"}, "display_name = ? and numbers = ?", new String[]{contact.getTitle(), contact.getItems().get(position).getPhoneNumber()}, null, null, null);
boolean saved = false;
if (cursor.getCount() > 0) {
cursor.moveToFirst();
do {
boolean firstCondition = cursor.getString(cursor.getColumnIndex("display_name")).equalsIgnoreCase(contact.getTitle());
if (!firstCondition) {
continue;
}
boolean secondCondition = false;
String numbers = cursor.getString(cursor.getColumnIndex("numbers"));
secondCondition = numbers.equalsIgnoreCase(contact.getItems().get(position).getPhoneNumber());
saved = firstCondition && secondCondition;
if (saved) {
break;
}
} while (cursor.moveToNext());
cursor.close();
return saved;
} else {
cursor.close();
return saved;
}
}
public boolean checkContactSaved(Contact contact, int position) {
Cursor cursor = getReadableDatabase().query("QContact", new String[]{"id", "display_name", "numbers", "avatar"}, "display_name = ? and numbers = ?", new String[]{contact.getDisplayName(), contact.getPhoneNumbers().get(position).getNumber()}, null, null, null);
boolean saved = false;
if (cursor.getCount() > 0) {
cursor.moveToFirst();
do {
boolean firstCondition = cursor.getString(cursor.getColumnIndex("display_name")).equalsIgnoreCase(contact.getDisplayName());
if (!firstCondition) {
continue;
}
boolean secondCondition = false;
String numbers = cursor.getString(cursor.getColumnIndex("numbers"));
secondCondition = numbers.equalsIgnoreCase(contact.getPhoneNumbers().get(position).getNumber());
saved = firstCondition && secondCondition;
if (saved) {
break;
}
} while (cursor.moveToNext());
cursor.close();
return saved;
} else {
cursor.close();
return saved;
}
}
public long saveContact(ContactModel contact, int numberPosition, byte[] avatarBitmap) {
if (!checkContactSaved(contact, numberPosition)) {
LogUtils.d("contact save true");
ContentValues contentValues = new ContentValues();
contentValues.put("display_name", contact.getTitle());
StringBuffer numbersBuffer = new StringBuffer();
numbersBuffer.append(contact.getItems().get(numberPosition).getPhoneNumber());
contentValues.put("numbers", numbersBuffer.toString());
contentValues.put("avatar", (contact.getUriAvatar() != null && !contact.getUriAvatar().isEmpty()) ? contact.getUriAvatar() : "");
contentValues.put("avatar_bitmap", avatarBitmap);
return getWritableDatabase().insert("QContact", null, contentValues);
} else {
LogUtils.d("contact save false");
return -1;
}
}
public long removeContact(ContactModel contact, int numberPosition) {
return getWritableDatabase().delete("QContact", "display_name = ? and numbers = ?",
new String[]{contact.getTitle(), contact.getItems().get(numberPosition).getPhoneNumber()});
}
private long removeContact(WidgetContactModel contact) {
return getWritableDatabase().delete("QContact", "display_name = ? and numbers = ?",
new String[]{contact.getDisplayName(), contact.getNumbers()});
}
public List<Boolean> removeContacts(List<WidgetContactModel> listContact) {
List<Boolean> listResult = new ArrayList<>();
for (WidgetContactModel contactModel : listContact) {
listResult.add(removeContact(contactModel) > 0);
}
return listResult;
}
}
Như vậy là đã xong các phương thức quản lý dữ liệu. Tiếp theo ta sẽ làm đến bước hiển thị chúng ra màn hình.
Hiển thị danh sách quay số nhanh
Ta sẽ xây dựng adapter hiển thị danh sách quay số nhanh. adapter này sẽ được sử dụng sau khi lấy được danh sách quay số nhanh từ cơ sở dữ liệu. Mình tạo 1 package adapter và tạo file ListQuickDialAdapter.java.
Adapter này sẽ có 2 dạng item:
Item hiển thị contact
Item hiển thị button Add Contact
Vậy mình sẽ phải xây dựng layout cho 2 dạng item này. Với item hiển thị contact, mình tạo 1 file item_quick_dial.xml như sau:
Tuy nhiên, từ đầu tới giờ ta mới xây dựng module hiển thị danh sách quay số nhanh mà chưa làm module thêm contact vào danh sách quay số nhanh. Chính vì vậy cho tới hiện tại thì database của chúng ta vẫn là database rỗng. Vậy mình sẽ đi tiếp sang chức năng add 1 contact từ danh bạ vào danh sách quay số nhanh.
List contact từ danh bạ mình sẽ hiển thị trong lớp MainActivity. Để cho đầy đủ thì ở QuickDialActivity, mình sẽ set sự kiện click cho ll_add để chuyển qua lớp MainActivity. Đây sẽ là nơi chúng ta implement các phương thức thêm, xóa contact vào database.
@OnClick(R.id.bt_add)
public void onClickAdd() {
showActivity(MainActivity.class);
}
Hết bài 1
Như vậy bài này mình đã trình bày xong về:
Xây dựng database để lưu trữ contact
Xây dựng màn hình hiển thị danh sách quay số nhanh
Bài tiếp theo mình sẽ trình bày về cách lấy danh sách liên lạc từ danh bạ và đổ vào cơ sở dữ liệu. Đồng thời hiển thị lên màn hình danh sách quay số nhanh.
Phần widget ngoài màn hình home khá phức tạp, mình sẽ dành ra 2 bài cuối để trình bày. Các bạn theo dõi nhé!
Chào mọi người, mình là Dương Vũ. Hôm nay mình sẽ viết bài đầu tiên trong loạt bài hướng dẫn lập trình Kotlin, sử dụng trong quá trình phát triển ứng dụng Android. Đây sẽ là loạt bài đầu tiên mình chia sẻ đến những ai yêu thích lập trình di động. Các kiến thức trong đây có thể sẽ mới mẻ, cũng có thể sẽ đơn giản đối với một số người đã trải qua. Vậy hãy cùng mình học tập, giúp đỡ và chia sẻ đến những người có cùng đam mê, sở thích về phát triển ứng dụng nhé.
[toc]
Tại sao lại có loạt bài này
Mình nhận thấy Kotlin là 1 ngôn ngữ cực kỳ hay và thú vị. Giữa hàng trăm hàng nghìn bài hướng dẫn về lập trình Android ngoài kia, mình chưa tìm ra 1 loạt bài nào giới thiệu bài bản, có lộ trình và dễ hiểu để cho người mới có thể nắm bắt. Có thể có những bài hướng dẫn về lập trình Android sử dụng Kotlin, nhưng rất ít bài hướng dẫn về những điều cốt lõi của ngôn ngữ này. Thế nên mình quyết định tạo ra loạt bài viết Hướng dẫn ngôn ngữ lập trình Kotlin. Loạt bài này sẽ đảm bảo cho bạn những kiến thức cốt lõi, quan trọng nhất để bạn có thể sử dụng trong phát triển ứng dụng Android nói riêng và các nền tảng khác nói chung. Và nó cũng hướng cho bạn cách học và cách đào sâu nghiên cứu, mở rộng cho ngôn ngữ này. So, let’s start!!!
Loạt bài này hướng đến ai
Nó hướng đến những người phát triển ứng dụng Android, muốn “chuyển giao công nghệ” từ Java sang Kotlin.
Hướng đến những bạn muốn học lập trình Android, mà chưa cả biết Java là gì. Yes!!! Các bạn có thể nhảy ngay vào Kotlin, hoàn toàn không có trở ngại gì cả.
Hướng đến … mình. Mình thực sự thích viết, đó là sở thích lớn của mình, chưa kể viết sẽ làm mình trau dồi thêm đc kha khá kiến thức, hehe :))
Nội dung của loạt bài hướng dẫn ngôn ngữ lập trình Kotlin
15 bài hướng dẫn cover toàn bộ kiến thức cốt lõi của ngôn ngữ lập trình Kotlin, giúp bạn có đủ kiến thức để bay vào dự án thực tế 1 cách tự tin và dễ dàng. Vì chỉ có 15 bài thôi nên mỗi bài sẽ có độ dài tương đối, các bạn hãy chịu khó theo dõi nhé.
Do bài viết hướng đến cả những người mới bắt đầu, nên đôi chỗ có thể giải thích hơi kỹ càng quá (đối với những người đã là dev có kinh nghiệm), nên chỗ nào các bạn thấy dễ quá, hay dài vl, thì xin các bạn đừng ném gạch, mà hãy next qua phần tiếp theo :)) Thanks
Loạt bài này được viết theo ngôn ngữ… đời sống. Tức là mình sẽ cố diễn giải vấn đề theo cách dân dã nhất, dễ hiểu nhất, chứ không sử dụng nhiều những từ ngữ mang tính học thuật, sẽ rất khó khăn cho ai mới tiếp xúc.
Phần sugguest, định hướng để các bạn tìm tòi thêm những kiến thức mới, nâng cao hơn, phục vụ cho dự án sau này.
Cách theo dõi loạt bài Kotlin sao cho hiệu quả nhất
Hãy cài IntelliJ IDEA
Như đã nói lúc đầu, bài viết này mình sẽ tập trung vào cốt lõi của ngôn ngữ Kotlin, và chỉ Kotlin mà thôi, không Android, không gì khác. Mình sẽ có riêng 1 loạt bài về hướng dẫn lập trình Android bằng Kotlin để vận dụng kiến thức thu được từ loạt bài này. Vì vậy ở trong những bài hướng dẫn của loạt bài này, mình sẽ không dùng Android Studio để hướng dẫn, mà thay vào đó sẽ dùng IntelliJ IDEA để code các demo. Tại sao? Bởi dùng Android Studio thì mình chỉ có thể tạo các dự án Android. Mà cứ mỗi lần muốn xem kết quả của 1 đoạn code, ta lại phải build nguyên 1 … con app! Rất mất thời gian.
Vì thế để đơn giản hóa mình sẽ sử dụng IntelliJ IDEA. IDE này cho phép ta tạo ra các project Kotlin. Đại khái là ta có thể dùng nó để xem kết quả của các đoạn code nhanh gọn đơn giản hơn, thay vì build nguyên 1 cái app. Các bạn hãy yên tâm vì nếu các bạn đã quen dùng AndroidStudio thì IntelliJ IDEA cũng có cách sử dụng hoàn toàn tương tự, giống đến 90%. Vì AS cũng build từ IntelliJ IDEA mà ra thôi. Nếu các bạn chưa biết cách cài đặt IDE này, hãy tham khảo bài viết Hướng dẫn cài đặt IntelliJ IDEA của mình nhé.
Hãy đọc thật kỹ
Mình sẽ viết rất kỹ, rất kỹ về từng vấn đề dù là nhỏ nhất. Vì vậy mỗi bài viết có thể khá dài. Tuy nhiên mình mong các bạn đừng bỏ qua chữ nào, bởi đằng sau mỗi đoạn text miên man đó là các kiến thức cốt lõi được diễn giải ra theo 1 cách dễ hiểu nhất (đối với những người mới). Nếu bạn đọc lướt qua, có thể bạn sẽ bỏ qua mất 1 phần kiến thức sâu hơn, nâng cao hơn mà mình muốn truyền đạt cho các bạn.
Hãy ứng dụng
Mặc dù đây mới chỉ là loạt bài giới thiệu về ngôn ngữ lập trình Kotlin, chúng ta sẽ không xây dựng app trong loạt bài này. Tuy nhiên mình muốn các bạn hãy lập tức ứng dụng những gì học được ngay khi đọc các bài viết. Hãy mở IDE lên và sẵn sàng gõ code. Chỉ có cách code thật nhiều mới có thể giúp chúng ta làm quen với 1 ngôn ngữ mới. Mình cũng xin báo trước rằng mình sẽ sử dụng rất, rất nhiều code để demo. Bởi theo mình nghĩ đó là cách đơn giản nhất giúp các bạn nhanh chóng hiểu được kiến thức. Các bạn cũng hãy tự xây dựng cho mình những đoạn code demo để ứng dụng kiến thức đã học nhé. Sẽ rất hiệu quả đấy!
Hãy chia sẻ
Hãy chia sẻ loạt bài đến những người mới bắt đầu. Những ai còn đang hoang mang giữa vô vàn kiến thức cả tiếng Anh lẫn tiếng Việt ngoài kia. Họ cần 1 lộ trình rõ ràng để làm mục tiêu học tập. Dù đã kiểm tra rất kỹ nhưng có thể ở đâu đó trong các bài viết, mình có sai sót. Hãy đọc và nếu thấy có sơ suất ở đâu, comment cho mình biết để mình cập nhật lại, để nội dung truyền tải được tốt hơn. Hãy cho đi, để được nhận lại.
Bắt đầu
Có thể các bạn sẽ tự hỏi rằng
Java ổn rồi ,cần thêm cái thứ Kotlin đó làm chi.
Thứ mới ra đó (Kotlin) chắc gì đã ổn định, Java thì best rồi, chả cần thiết phải mò vào Kotlin
Tao thích code Java, tao đã quen với nó, tao ghét tất cả mọi thứ khác!
Vân vân
Thì đúng, mình không phủ nhận những điều mà các bạn nghĩ ở trong đầu. Nhưng các bạn hãy thử nhìn vào những điểm tốt của Kotlin so với Java, rồi các bạn sẽ thấy, mình có lý do khi viết loạt bài hướng dẫn này.
Sự ngắn gọn
So với Java, Kotlin là vô cùng, vô cùng ngắn gọn. Bạn có thể hình dung, khi mà việc định nghĩa 1 class trong Java có thể tốn đến vài chục dòng hay cả trăm dòng code thì với Kotlin, ta chỉ tốn 1 dòng để làm điều tương tự. Mình sẽ ví dụ ngay đây. Sau đây sẽ là 1 class trong Java:
public class User {
String name;
int age;
String address;
String phoneNumber;
public User(String name, int age, String address, String phoneNumber) {
this.name = name;
this.age = age;
this.address = address;
this.phoneNumber = phoneNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
Trên đây là 1 class với 4 thuộc tính, hàm khởi tạo và các phương thức get, set cho các thuộc tính. Trong Kotlin nó sẽ như thế này:
data class User(var name:String,var age: Int,var address:String,var phoneNumber:String)
Vỏn vẹn 1 dòng duy nhất! Đó chính là sự khác biệt về sự ngắn gọn của Kotlin. Đây chỉ là 1 ví dụ. Trong các dự án thực tế ta sẽ còn gặp hàng trăm tình huống mà nếu sử dụng Kotlin, số dòng code phải viết có thể giảm đi đến 90%. Rất tuyệt đúng không. (mình sẽ viết những loạt bài về xây dựng các ứng dụng với quy trình như trong thực tế)
An toàn hơn với các đối tượng null
Như các bạn đã biết thì NullPointerException là 1 lỗi hay gặp nhất ở trong các dự án viết bằng Java. Java cho phép chúng ta gán giá trị null đến 1 đối tượng, nhưng khi ta truy xuất đến đối tượng đó, lỗi NullPointerException sẽ được bắn ra. Mình đã từng làm 1 dự án Android, khi update version mới cho app, mình đã quên không cập nhật đối tượng Java theo config mới trên server. Và kết quả là trong 2 ngày đã có hơn 5000 log crash được báo về hệ thống, tất cả đều là NullPointerException gây ra, bạn có thể hiểu được cảm giác thốn đến thế nào, khi có đến 95% người dùng app bị crash :-s Yeah, và Kotlin được thiết kế để giảm thiểu tối đa sự xuất hiện của cái Exception khốn nạn này 😀 Cực an toàn và tiện lợi, mình sẽ có riêng 1 bài về vấn đề này, các bạn hãy đón chờ nghe.
Hoàn toàn tương thích với Java
Yes! Bạn có thể làm 1 dự án với cả Java và Kotlin cùng 1 lúc. Kotlin được thiết kế để giao tiếp với Java 1 cách hoàn hảo. Bạn có thể sử dụng Java class trong Kotlin và ngược lại, cũng có thể sử dụng Kotlin class trong Java. Tuyệt đúng ko? Điều này có nghĩa là Kotlin có thể sử dụng tất cả các thư viện và framework Java hiện có. Điều này cũng có nghĩa là nếu bạn thích, bạn có thể mở rộng, phát triển dự án Java cũ với Kotlin rất dễ dàng.
Trên đây chỉ là 3 trong số rất nhiều điều hay và thú vị khác của Kotlin mà Java không có. Hãy cùng mình tìm hiểu kỹ hơn trong các bài viết loạt bài này nhé.