Công việc hàng ngày trong công ty của mình cảm giác nó lặp đi lặp lại. Hình thành 1 thói quen mà khi có task mới, rồi đọc tài liệu là biết mình làm gì. Trong ngân hàng hầu như các framework hay các hàm common chung đã được dựng sẵn mà chỉ cần biết cách sử dụng để phục vụ cho việc phát triển tính năng mới. Từ đó mà ta quên đi những khái niệm cơ bản mà trước đó khi bước chân vào ngành lập trình đã học, rồi ôn luyện để đi phỏng vấn để có công việc đầu tiên. (Tự nói bản thân )
Lắm lúc cũng tự hỏi là nếu cứ tiếp tục làm việc những cái quen thuộc này thì kiến thức mình sẽ bị quên lãng đi. Chính vì thế nên mình viết bài này để lưu lại những thứ cần nhớ. Những thứ mà vốn dĩ không thể quên (thì mình lại quên ).
Thôi vào bài luôn nhá. Lan man quá.
Fundamentals (Cơ bản)
Basic Syntax
Về phần này thì chắc quá quen thuộc nên chỉ nhắc lại một vài gạch đầu dòng:
- Tạo biến, đặt tên
- Tạo biến kiểu nguyên thủy
- Tạo mảng
- Sử dụng var (Cái này không thấy dùng lúc đi làm)
- Sử dụng toán tử (Thường sau sẽ sử dụng BigDecimal - Nó sẽ liên quan nhiều tới số phẩy động. Vì khi làm việc thực tế biến đổi từ số vô tỉ mà sang String thì bạn biết rồi đấy, toang)
- Các toán tử boolean ( == > <...)
- Biểu thức, câu lệnh, khối lệnh
- Câu lệnh điều khiển (if else ...)
- Rẽ nhánh switch (Có cả rẽ nhánh dạng biểu thức -> )
Data Types and Variables
Variables in Java (Biến trong Java)
- Local Variable (Biến cục bộ): phải khởi tạo trước khi sử dụng
public void calculate() { int localVar; // Không khởi tạo // System.out.println(localVar); // Lỗi: Variable 'localVar' might not have been initialized
}
- Instance Variable (Biến thực thể) Có giá trị mặc định:
- Số nguyên (int, long. ...) -> 0
- Số thực (float, double) -> 0.0
- char -> ký tự rỗng \u0000
- boolean -> false
- Đối tượng (Object, String) -> null
- Static Variable (Biến tĩnh) Biến tĩnh không liên kết với bất kỳ đối tượng nào => không sử dụng từ khóa this.
public class VariableExample { // Instance Variable (Biến thực thể): Đi kèm khi đối tượng mới được tạo ra String instanceVar = "I am an instance variable"; // Static Variable (Biến tĩnh): Chia sẻ giữa các đối tượng. Được tạo ra khi nạp vào bộ nhớ (class loader) static String staticVar = "I am a static variable"; public void displayVariables() { // Local Variable (Biến cục bộ): Chỉ tồn tại bên trong method String localVar = "I am a local variable"; System.out.println(localVar); // Truy cập biến cục bộ System.out.println(instanceVar); // Truy cập biến thực thể System.out.println(staticVar); // Truy cập biến tĩnh } public static void main(String[] args) { // Tạo một đối tượng để truy cập biến thực thể VariableExample example = new VariableExample(); // Gọi phương thức hiển thị các biến example.displayVariables(); // Truy cập biến tĩnh mà không cần tạo đối tượng System.out.println("Accessing static variable directly: " + VariableExample.staticVar); }
}
Data Types in Java (Kiểu dữ liệu trong Java)
Primitive Types (Kiểu dữ liệu nguyên thủy):
- byte 8 bit (-2^7 -> 2^7 - 1 ~ -128 -> 127)
- short 16 bit
- int 32 bit
- long 64 bit (L)
- float 32 bit (f)
- double 64 bit
- boolean 1bit true/false
- char unicode 16 bit (Java sử dụng Unicode chứ không phải hệ thống mã ASCII)
Non-Primitive Types (Kiểu dữ liệu không nguyên thủy):
- Class
- Interface
- Arrays
- Enum
- Records (Java 14)
Functions
Khi nhắc đến hàm thì không thể thiếu khái niệm Lambda được đúng không?
1. Lambda expression:
Là một cách biểu diễn hàm ẩn danh (anonymous function) trong Java. Nó cho phép bạn viết mã ngắn gọn và dễ hiểu hơn, đặc biệt trong các tác vụ như callback hoặc xử lý trên collection.
(đối số) -> { biểu thức hoặc thân hàm } // Cách truyền thống
Runnable r = new Runnable() { @Override public void run() { System.out.println("Chạy bằng Runnable"); }
};
r.run(); // Dùng Lambda
Runnable rLambda = () -> System.out.println("Chạy bằng Lambda");
rLambda.run();
2. Functional Interface
Lambda chỉ được sử dụng khi làm việc với functional interface (interface chỉ có một phương thức trừu tượng).
Một số functional interfaces phổ biến: Runnable, Callable, Comparator, Gói java.util.function với: Function, Predicate, Consumer, Supplier, v.v.
@FunctionalInterface // đảm bảo tính hợp lệ và rõ ràng.
interface MyFunctionalInterface { void execute(); // Một phương thức trừu tượng
} // Chấp nhận 1 tham số và không trả về giá trị
import java.util.function.Consumer;
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello, World!"); // Output: Hello, World! // Không nhận tham số, trả về một giá trị.
import java.util.function.Supplier;
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get()); // Output: Một số ngẫu nhiên // Chuyển đổi một giá trị đầu vào thành giá trị đầu ra.
import java.util.function.Function;
Function<Integer, String> intToString = num -> "Number: " + num;
System.out.println(intToString.apply(5)); // Output: Number: 5 // Kiểm tra điều kiện và trả về true hoặc false.
import java.util.function.Predicate;
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // Output: true // Sử dụng Functional Interface với Lambda
@FunctionalInterface
interface Calculator { int calculate(int a, int b);
} public class Main { public static void main(String[] args) { Calculator addition = (a, b) -> a + b; Calculator multiplication = (a, b) -> a * b; System.out.println(addition.calculate(5, 3)); // Output: 8 System.out.println(multiplication.calculate(5, 3)); // Output: 15 }
}
Default và Static Methods Functional interface có thể chứa các phương thức default và static. Điều này không ảnh hưởng đến việc nó chỉ có một phương thức trừu tượng.
- Phương thức default
- Thêm tính năng mới vào interface (không phá vỡ tính tương thích)
- Có thể override trong lớp thực thi
- Phương thức static:
- Không bị override và chỉ gọi trực tiếp từ interface
- Sử dụng chủ yếu để cung cấp method tiện ích liên quan tới interface
@FunctionalInterface
interface Converter { double convert(double value); // Default method default void displayUnit(String unit) { System.out.println("Converting to unit: " + unit); } // Static method static double toKilometers(double miles) { return miles * 1.60934; }
} public class Main { public static void main(String[] args) { Converter fahrenheitToCelsius = f -> (f - 32) * 5 / 9; double celsius = fahrenheitToCelsius.convert(98.6); fahrenheitToCelsius.displayUnit("Celsius"); // Gọi default method double kilometers = Converter.toKilometers(10); // Gọi static method System.out.println("Temperature in Celsius: " + celsius); // Output: 37.0 System.out.println("Distance in Kilometers: " + kilometers); // Output: 16.0934 }
}
3. Sử dụng Lambda với Stream API
// Ví dụ: Lọc danh sách
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors; List<String> names = Arrays.asList("An", "Bình", "Chi", "Dung");
List<String> filtered = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList()); System.out.println(filtered); // Output: [An] // Ví dụ: Biến đổi và tổng hợp dữ liệu
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream() .map(x -> x * 2) // Nhân mỗi phần tử với 2 .reduce(0, Integer::sum); // Cộng tổng
System.out.println(sum); // Output: 30
4. Biến được sử dụng trong Lambda
String prefix = "Xin chào, ";
Consumer<String> greeter = name -> System.out.println(prefix + name);
greeter.accept("Thế giới"); // Output: Xin chào, Thế giới
5. Method Reference
Lambda có thể được thay thế bằng method reference để ngắn gọn hơn.
Các loại method reference:
- Static Method: Class::method
- Instance Method: instance::method
- Constructor: Class::new
List<String> names = Arrays.asList("An", "Bình", "Chi");
names.forEach(System.out::println); // Output từng phần tử
Working with Date / Time
Java 8 đã giới thiệu các API mới cho Ngày và Thời gian để giải quyết những hạn chế của các API cũ như java.util.Date và java.util.Calendar.
Các lớp sử dụng phổ biến hiện nay là LocalDate, LocalTime và LocalDateTime
Lợi ích sử dụng:
- Đơn giản và dễ dàng thao tác (Như cộng ngày, cộng tháng - thay vì phải sử dụng thủ công trong Caclendar)
- An toàn trong đa luồng (Thread-safe): Lớp mới là bất biến (immutable) giá trị chúng không thể thay đổi sau khi khởi tạo
- Không cần quản lý múi giờ
1, Working with LocalDate
LocalDate localDate = LocalDate.now(); // Giả sử lấy ngày 12 tháng 11 năm 1998
LocalDate.of(1998, 11, 12);
LocalDate.parse("1998-11-12"); // Đoạn mã sau lấy ngày địa phương + 1 ngày
LocalDate tomorrow = LocalDate.now().plusDays(1); // Cách lấy tháng trước của hiện tại, chấp nhận enum như đơn vị thời gian ChronoUnit
LocalDate previousMonthSameDay = LocalDate.now().minus(1, ChronoUnit.MONTHS);
Còn nhiều hàm rất hay có thể khi làm việc sẽ gặp cần thì GPT thôi
2. Working with LocalTime
3. Working With LocalDateTime
LocalDateTime now = LocalDateTime.now(); // Tạo ngày từ tham số cụ thể
LocalDateTime specificDateTime = LocalDateTime.of(2024, 12, 13, 14, 30, 45); // Chuyển đổi định dạng ISO thành LocalDateTime
LocalDateTime parsedDateTime = LocalDateTime.parse("2024-12-13T14:30:45"); // Truy xuất thông tin
.getYear() .getMonth() .getDayOfMonth() .getHour() .getMinute() .getSecond() // Thao tác ngày giờ
.plusWeeks(1) .minusMonths(1) // So sánh isBefore() isAfter() // Chuyển đổi toLocalDate() toLocalTime() // Định Dạng và Phân Tích Chuỗi
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = specificDateTime.format(formatter);
Có thể làm việc với múi giờ khác nhau với ZonedDateTime
Các trường hợp hay gặp trong thực tế:
- Thường từ input sẽ truyền ngày theo định dạng String: "dd/MM/yyyy"
Ta có thể biến đổi nó:
try { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate localDate = LocalDate.parse(dateStr, formatter); System.out.println("LocalDate: " + localDate);
} catch (DateTimeParseException e) { System.out.println("Chuỗi ngày không hợp lệ: " + e.getMessage());
}
Dùng try-catch vì khi không khớp định dạng dd/MM/yyyy sẽ bắn ra ngoại lệ
Các lớp bổ sung để làm việc với ngày Period, làm việc với thời gian Duration.
4. Làm việc với Database
Trong Java SE 8, JDBC được cập nhật để hỗ trợ các kiểu mới trong gói java.time. Mặc dù không có thay đổi nào trong API công khai của JDBC, các phương thức hiện tại như setObject và getObject sẽ đủ để xử lý các kiểu dữ liệu mới này.
Mối Quan Hệ Giữa ANSI SQL và Java SE 8
ANSI SQL | Java SE 8 |
---|---|
DATE | LocalDate |
TIME | LocalTime |
TIMESTAMP | LocalDateTime |
TIME WITH TIMEZONE | OffsetTime |
TIMESTAMP WITH TIMEZONE | OffsetDateTime |
4.1 Truy xuất dữ liệu từ Database (getObjecT)
Ví dụ: Lấy dữ liệu kiểu DATE, TIME hoặc TIMESTAMP từ cơ sở dữ liệu và ánh xạ sang đối tượng tương ứng của java.time.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime; public class JDBCTimeExample { public static void main(String[] args) throws Exception { Connection connection = DriverManager.getConnection("jdbc:your_database_url", "username", "password"); String query = "SELECT date_column, time_column, timestamp_column FROM your_table"; PreparedStatement preparedStatement = connection.prepareStatement(query); ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { LocalDate date = resultSet.getObject("date_column", LocalDate.class); LocalTime time = resultSet.getObject("time_column", LocalTime.class); LocalDateTime timestamp = resultSet.getObject("timestamp_column", LocalDateTime.class); System.out.println("Date: " + date); System.out.println("Time: " + time); System.out.println("Timestamp: " + timestamp); } resultSet.close(); preparedStatement.close(); connection.close(); }
}
4.2 Lưu Dữ Liệu Vào Database (setObject)
Ví dụ: Lưu các đối tượng LocalDate, LocalTime hoặc LocalDateTime vào cơ sở dữ liệu.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime; public class JDBCSetTimeExample { public static void main(String[] args) throws Exception { Connection connection = DriverManager.getConnection("jdbc:your_database_url", "username", "password"); String query = "INSERT INTO your_table (date_column, time_column, timestamp_column) VALUES (?, ?, ?)"; LocalDate date = LocalDate.now(); LocalTime time = LocalTime.now(); LocalDateTime timestamp = LocalDateTime.now(); PreparedStatement preparedStatement = connection.prepareStatement(query); preparedStatement.setObject(1, date); preparedStatement.setObject(2, time); preparedStatement.setObject(3, timestamp); int rowsInserted = preparedStatement.executeUpdate(); System.out.println("Rows inserted: " + rowsInserted); preparedStatement.close(); connection.close(); }
}
Loops
Quanh đi quẩn lại thì có for, while, do-while
Exception Handling
Trong Java, ngoại lệ được chia thành hai loại chính:
1. Checked Exceptions
- Được kiểm tra trong quá trình biên dịch (compile time), thường intelliJ check hết rồi. Và bắt phải xử lý trước khi chạy được code.
- Bắt buộc phải xử lý bằng cách try-catch hoặc khai báo throws. VD: IOException, SQLException, ClassNotFoundException. Thường đọc file thì bọc try-catch
2. Unchecked Exceptions
- Ngoại lệ xảy ra trong thời gian chạy (runtime) và không bị kiểm tra tại thời điểm biên dịch
- Không bắt buộc phải xử lý, nhưng khi xảy ra lỗi chương trình sẽ dừng. VD: NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException.
Ngoài ra còn Error: Là lỗi nghiêm trọng nằm ngoài tầm kiểm soát của chương trình: Như OutOfMemoryError, StackOverflowError.
3. Tùy chỉnh ngoại lệ (Custom Exception)
Bạn có thể tự định nghĩa ngoại lệ riêng bằng cách kế thừa lớp Exception hoặc RuntimeException.
class InvalidAgeException extends Exception { public InvalidAgeException(String message) { super(message); }
} public class Main { public void validateAge(int age) throws InvalidAgeException { if (age < 18) { throw new InvalidAgeException("Tuổi không đủ để đăng ký!"); } }
}
Trong dự án Spring Boot thì ta thường xử lý lỗi tập trung sử dụng @ControllerAdvice và @ExceptionHandler để bắt lỗi trả về
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; @ControllerAdvice
public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(Exception.class) public ResponseEntity<Object> handleGlobalException(Exception ex, WebRequest request) { logger.error("Lỗi xảy ra: ", ex); ErrorResponse errorResponse = new ErrorResponse( HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage() ); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); }
}
DataStructures
Là cách tổ chức và lưu trữ dữ liệu để có thể truy cập và sử dụng chúng hiệu quả. Java cung cấp 1 bộ thư viện trong gói java.util.
1. Danh sách (Lists)
Danh sách lưu trữ các phần tử theo 1 thứ tự cụ thể.
ArrayList | LinkedList | Vector |
---|---|---|
Dựa trên mảng động | Dựa trên danh sách liên kết kép | Dựa trên mảng động |
Thích hợp truy cập ngẫu nhiên nhanh | Tốt hơn khi thêm/xóa phần tử ở giữa danh sách | Đồng bộ (synchronized) -> chậm hơn ArrrayList |
Không đồng bộ (non-synchronized) | Cũng hỗ trợ làm Queue/Deque | Dùng khi cần đảm bảo an toàn luồng (thread-safe) |
2. Tập hợp (Sets)
Set là một tập hợp không chứ các phần tử trùng lặp
- HashSet: Dựa trên bảng băm (hash table) và không đảm bảo thử tự các phần tử
- LinkedHashSet: Giữ thứ tự thêm phần tử
- TreeSet: Dựa trên cây nhị phân, duy trì thứ tự tự nhiên hoặc dựa trên Comparator được cung cấp
3. Bản đồ (Maps)
Map là một cấu trúc dữ liệu ánh xạ các cặp khóa (key) và giá trị (value) Tương tự Set
Ngoài ra còn Queue (FIFO) và Stack (LIFO) Ta sẽ tìm hiểu datastructure làm việc trong thread, loại nào là safe-thread ở bài sau khi tìm hiểu sâu hơn về luồng.
OOP, Interfaces, Classes
Các loại khái niệm chính trong OOP:
- Lớp (Class)
- Đối tượng (Object)
- Tính đóng gói (Encapsulation)
- Tính kế thừa (Inheritance)
- Tính đa hình (Polymorphism)
- Overloading (nạp chồng phương thức): cùng 1 lớp, Compile-time
- Overriding (ghi đè phương thức): khác lớp, Runtime Những điều cần lưu ý: 1, Access Modifier: phương thức ghi đè không được có mức truy cập thấp hơn lớp cha
- Exceptions: Lớp con không được ném ngoại lệ không tồn tại trong lớp cha (Được ném ngoại lệ con mà ngoại lệ con đó là con của ngoại lệ cha nằm ở lớp cha)
- Không ghi đè phương thức final, private và static (tuy nhiên có thể che khuất (method hiding)
class Parent { static void display() { System.out.println("Static method in Parent"); }
} class Child extends Parent { static void display() { System.out.println("Static method in Child"); }
} public class Main { public static void main(String[] args) { Parent obj = new Child(); obj.display(); // Output: Static method in Parent }
}
- Tính trừu tượng (Abstraction):
- Lớp trừu trượng (Abstract Class)
- Giao diện (Interface)
Working with Files and APIs
Using Files.readString() – Java 11
Path filePath = Path.of("c:/temp/demo.txt"); String content = Files.readString(fileName);
Tuy nhiên sẽ bị throws OutOfMemoryError nếu file quá lớn. Lớn hơn 2GB Giải pháp tốt hơn: Dùng BufferedReader, SeekableByteChannel, hoặc MappedByteBuffer để xử lý tệp lớn thay vì lưu toàn bộ vào String.