GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Java Core Java 8 Lớp Collectors trong Java 8

Lớp Collectors trong Java 8

Đăng vào 18/06/2018 . Được đăng bởi GP Coder . 18692 Lượt xem . Toàn màn hình

Stream.collect() là một trong các phương thức đầu cuối (terminal operation) của Stream API trong Java 8. Nó cho phép thực hiện các thao tác có thể thay đổi trên các phần tử được lưu giữ trong Stream. Chẳng hạn như: chuyển các phần tử sang một số cấu trúc dữ liệu khác, áp dụng một số logic bổ sung, tính toán, …

Lớp Java Collectors cung cấp nhiều phương thức khác nhau để xử lý các phần tử của Stream API. Chẳng hạn như:

  • Collectors.toList()
  • Collectos.toSet()
  • Collectors.toMap()
  • …

Trong bài viết này, tôi sẽ giới thiệu với các phương thức được xây dựng sẵn trong lớp Collectors và cách tạo, sử dụng một Custom Collectors.

Nội dung

  • 1 Các Collectors được xây dựng sẵn (build-in)
  • 2 Custom Collectors

Các Collectors được xây dựng sẵn (build-in)

Các Collectors được xây dựng sẵn trong lớp java.util.stream.Collectors.* 

Trong phần tiếp theo của bài viết, chúng ta sẽ lần lượt tìm hiểu cách sử dụng từng phương thức trong lớp Collectors.

Collectors.toList()

Collectors.toList() : có thể được sử dụng để thu thập tất cả các phần tử Stream vào một List (kết quả luôn là ArrayList).

Ví dụ:


List<String> result = list.stream().collect(Collectors.toList());

Collectors.toSet()

Collectors.toSet() : có thể được sử dụng để thu thập tất cả các phần tử Stream vào một Set (kết quả luôn là HashSet).

Ví dụ:


List<String> result = list.stream().collect(Collectors.toSet());

Collectors.toCollection()

Khi sử dụng Collectors.toSet() và Collectors.toList(), bạn không thể xác định bất kỳ lớp cài đặt cụ thể nào của chúng như LinkedList, LinkedHashSet, TreeSet, … Nếu muốn sử dụng lớp cài đặt cụ thể, chúng ta cần phải sử dụng phương thức Collectors.toCollection(c) , với Collection được cung cấp tương ứng.

Ví dụ:


List<String> result = list.stream().collect(Collectors.toCollection(LinkedList::new));

Collectors.toMap()

Collectors.toMap() : có thể được sử dụng để thu thập tất cả các phần tử Stream vào một Map.

Cú pháp:


public static Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {}

Trong đó:

  • keyMapper : là một Function<T, R> được sử dụng để trích xuất một khóa (key) từ một phần tử Stream.
  • valueMapper : là một Function<T, R> được sử dụng để trích xuất một giá trị (value) được liên kết với một khóa (key) đã cho.
  • mergeFunction : được sử dụng để giải quyết xung đột giữa các giá trị được liên kết với cùng một khóa. Tham số này không bắt buộc. Tuy nhiên, nếu trong danh sách có key trùng, nó sẽ throw một ngoại lệ IllegalStateException.
  • mapSupplier : là một Supplier trả về một instance của Map mới, rỗng, trong đó kết quả sẽ được chèn vào. Tham số này không bắt buộc, mặc định là HashMap.

Ví dụ 1:


List<String> list = Arrays.asList("Java", "C++", "C#", "PHP");

Map<String, Integer> result = list.stream().collect(Collectors.toMap(Function.identity(), String::length));

// => {C#=2, Java=4, C++=3, PHP=3}

Function.identity() : là một phương thức tiện ích để xác định đối số và kết quả trả về cùng một giá trị.

Đôi khi trong danh sách các phần tử khi chuyển sang Map có các phần tử trùng key, khi đó chúng ta cần truyền vào đối số thứ 3 của Collectors.toMap() một BinaryOperator để giải quyết vấn đề này.

Ví dụ 2:


package com.gpcoder.collectors;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Student {

	private String name;
	private Integer score;

	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}

	public String getName() {
		return name;
	}

	public Integer getScore() {
		return score;
	}
}

public class CollectorsExample2 {

	public static void main(String[] args) {
		List<Student> students = Arrays.asList( //
				new Student("B", 70), //
				new Student("A", 80), //
				new Student("C", 75), //
				new Student("A", 100) //
		);

		// Resolve collisions between values associated with the same key
		Map<String, Integer> result1 = students.stream().collect( //
				Collectors.toMap(Student::getName, Student::getScore, //
						(s1, s2) -> (s1 > s2 ? s1 : s2) // BinaryOperator
				));
		System.out.println("result1 : " + result1);

		// Identify the instance of LinkedHashMap
		Map<String, Integer> result2 = students.stream().collect( //
				Collectors.toMap(Student::getName, Student::getScore, //
						(s1, s2) -> (s1 > s2 ? s1 : s2), // BinaryOperator
						LinkedHashMap::new // Supplier
				));
		System.out.println("result2 : " + result2);
	}
}

Output của chương trình trên:


result1 : {A=100, B=70, C=75}
result2 : {B=70, A=100, C=75}

Collectors.collectingAndThen()

Collectors.collectingAndThen() là một bộ thu thập đặc biệt cho phép thực hiện một hành động khác ngay lập tức sau khi thu thập kết thúc.

Ví dụ:


List<String> list = Arrays.asList("Java", "C++", "C#", "PHP");

List<String> result5 = list.stream().collect(

Collectors.collectingAndThen(Collectors.toList(), x -> x.subList(0, 2)));

// => [Java, C++]

Collectors.joining()

Collectors.joining() : có thể được sử dụng join các phần tử Stream<String>.

Cú pháp:


public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {}

Trong đó:

  • delimiter : chuỗi ký tự phân tách các phần tử.
  • prefix : chuỗi ký tự được thêm vào đầu kết quả.
  • suffix : chuỗi ký tự được thêm vào cuối kết quả.

Ví dụ:


List<String> list = Arrays.asList("Java", "C++", "C#", "PHP");

String result6 = list.stream().collect(Collectors.joining());

// => JavaC++C#PHP

String result7 = list.stream().collect(Collectors.joining(", "));

// => Java, C++, C#, PHP

String result8 = list.stream().collect(Collectors.joining(" ", "PRE-", "-POST"));

// => PRE-Java C++ C# PHP-POST

Collectors.counting()

Collectors.counting() : được sử dụng để đếm các phần tử trong Stream.

Ví dụ:


List<String> list = Arrays.asList("Java", "C++", "C#", "PHP");

Long result9 = list.stream().collect(Collectors.counting()); // => 4

Collectors.summarizingDouble/Long/Int()

Collectors.summarizingDouble/Long/Int() : trả về một lớp đặc biệt chứa thông tin thống kê về dữ liệu số trong Stream các phần tử được trích xuất.

Ví dụ:


List<String> list = Arrays.asList("Java", "C++", "C#", "PHP");

IntSummaryStatistics result10 = list.stream().collect(Collectors.summarizingInt(String::length));

// => IntSummaryStatistics{count=4, sum=12, min=2, average=3.000000, max=4}

Collectors.averagingDouble/Long/Int()

Collectors.averagingDouble/Long/Int() : trả về giá trị trung bình của các phần tử.

Ví dụ:


Double result = list.stream().collect(Collectors.averagingDouble(String::length));

Collectors.summingDouble/Long/Int()

Collectors.summingDouble/Long/Int() : trả về giá trị tổng của các phần tử.

Ví dụ:


Double result = list.stream().collect(Collectors.summingDouble(String::length));

Collectors.maxBy()/ Collectors.minBy()

Collectors.maxBy() / Collectors.minBy() : trả về giá trị lớn nhất/ nhỏ nhất của một Stream theo bộ Comparator được cung cấp.

Ví dụ:


Optional<String> result = list.stream().collect(Collectors.maxBy(Comparator.naturalOrder())); // => PHP

Optional<String> result = list.stream().collect(Collectors.minBy(Comparator.naturalOrder())); // => C#

Collectors.groupingBy()

Collectors.groupingBy() : được sử dụng để nhóm các đối tượng theo một số thuộc tính và lưu trữ các kết quả trong một Map.

Ví dụ:


package com.gpcoder.collectors;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

class Book {
	private Integer id;
	private String title;
	private Integer cagegoryId;

	public Book(Integer id, String title, Integer cagegoryId) {
		super();
		this.id = id;
		this.title = title;
		this.cagegoryId = cagegoryId;
	}

	public Integer getId() {
		return id;
	}

	public String getTitle() {
		return title;
	}

	public Integer getCagegoryId() {
		return cagegoryId;
	}

	@Override
	public String toString() {
		return "Book [id=" + id + ", title=" + title + ", cagegoryId=" + cagegoryId + "]";
	}

}

public class CollectorsExample3 {

	public static void main(String[] args) {
		List<Book> books = Arrays.asList( //
				new Book(1, "A", 1), //
				new Book(2, "B", 1), //
				new Book(3, "C", 2), //
				new Book(4, "D", 3), //
				new Book(5, "E", 1) //
		);

		Map<Integer, Set<Book>> result = books.stream()
				.collect(Collectors.groupingBy(Book::getCagegoryId, Collectors.toSet()));
		result.forEach((catId, booksInCat) -> System.out.println("Category " + catId + " : " + booksInCat.size()));
	}
}

Output của chương trình trên:


Category 1 : 3
Category 2 : 1
Category 3 : 1

Collectors.partitioningBy()

Collectors.partitioningBy() : là một trường hợp đặc biệt của Collectors.groupingBy() chấp nhận một Predicate và thu thập các phần tử của Stream vào một Map với các giá trị Boolean như khóa và Collection như giá trị.

  • Key = true, là một tập hợp các phần tử phù hợp với Predicate đã cho
  • Key = false, là một tập hợp các phần tử không khớp với Predicate đã cho.

Ví dụ:


Map<Boolean, Set<Book>> partitioningBy = books.stream()
    .collect(Collectors.partitioningBy(b -> b.getCagegoryId() > 2, Collectors.toSet()));
System.out.println(partitioningBy);

Output của chương trình:


{
false=[Book [id=3, title=C, cagegoryId=2], 
    Book [id=2, title=B, cagegoryId=1], 
    Book [id=1, title=A, cagegoryId=1], 
    Book [id=5, title=E, cagegoryId=1]], 
true=[Book [id=4, title=D, cagegoryId=3]]
}

Collectors.reducing()

Collectors.reducing() : thực hiện giảm các phần tử đầu vào của nó trong một BinaryOperator được chỉ định.

Cú pháp:


public static <T> Collector<T,?,T> reducing​(T identity, BinaryOperator<T> op) {}

public static <T> Collector<T, ?, Optional<T>> reducing​(BinaryOperator<T> op) {}

Trong đó:

  • identity : giá trị khởi tạo để thực hiện reduction (cũng là giá trị được trả về khi không có phần tử đầu vào).
  • op : một BinaryOperator<T> được sử dụng để giảm các phần tử đầu vào.

Ví dụ: Trong ví dụ bên dưới chúng ta sẽ sử dụng reducing() để tìm nhân viên lớn tuổi nhất theo từng company, tính tổng lương phải trả cho tất cả nhân viên.


package com.gpcoder.collectors;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

class Employee {
	private String name;
	private Integer age;
	private String companyName;
	private Integer salary;

	public Employee(String name, Integer age, String companyName, Integer salary) {
		this.name = name;
		this.age = age;
		this.companyName = companyName;
		this.salary = salary;
	}

	public String getName() {
		return name;
	}

	public Integer getAge() {
		return age;
	}

	public String getCompanyName() {
		return companyName;
	}

	public Integer getSalary() {
		return salary;
	}
}

public class CollectorsExample4 {

	public static void main(String[] args) {
		
		List<Employee> list = Arrays.asList( //
				new Employee("Emp1", 22, "A", 50), //
				new Employee("Emp2", 23, "A", 60), //
				new Employee("Emp3", 22, "B", 40), //
				new Employee("Emp4", 21, "B", 70) //
		);

		// Find employees with the maximum age of each company
		Comparator<Employee> ageComparator = Comparator.comparing(Employee::getAge);

		Map<String, Optional<Employee>> map = list.stream().collect(
				Collectors.groupingBy(Employee::getCompanyName,
						Collectors.reducing(BinaryOperator.maxBy(ageComparator))));

		map.forEach((k, v) -> System.out.println(
				"Company: " + k + 
				", Age: " + ((Optional<Employee>) v).get().getAge() + 
				", Name: " + ((Optional<Employee>) v).get().getName()));
		
		// Summary salary
		Integer bonus = 30;
		Integer totalSalaryExpense = list.stream()
                .map(emp -> emp.getSalary())
				.reduce(bonus, (a, b) -> a + b);
		System.out.println("Total salary expense: " + totalSalaryExpense);
	}
}

Ouput của chương trình trên:


Company: A, Age: 23, Name: Emp2
Company: B, Age: 22, Name: Emp3
Total salary expense: 250

Custom Collectors

Mỗi static method trong lớp Collectors đều trả về một object kiểu Collector. Collector là một interface được khai báo trong java.util.stream.Collector.

Hãy xem sơ đồ sau:

Các Generic type của interface Collector<T, A, R>:

  • T : kiểu dữ liệu của phần tử trong Stream
  • A : kiểu dữ liệu được sử dụng để giữ một phần kết quả của hoạt động thu thập.
  • R : kiểu dữ liệu của kết quả thu thập được (collect).

Có 4 interface liên quan:

  • Supplier : biểu diễn một operation không có đầu vào và đầu ra là 1 kết quả nào đó.
  • BiConsumer : biểu diễn một operation có 2 biến đầu vào nhưng đầu ra không có gì.
  • Function : Biểu diễn một hàm có 1 biến đầu vào và đầu ra là 1 kết quả nào đó.
  • BinaryOperator : biểu diễn một operation có đầu vào là 2 toán tử cùng loại (same type), kết quả cũng cùng kiểu như đối số. Đây là một trường hợp của BiFunction khi mà toán tử và kết quả có cùng kiểu (type).

Các phương thức được yêu cầu bởi interface Collector:

  • supplier() : có vai trò khởi tạo result container.
  • accumulator() : lưu trữ các phần tử vào result container.
  • combiner() : kết hợp 2 result container thành 1.
  • finisher() : thực thi final transform cho container (nếu được yêu cầu).
  • characteristics() : được sử dụng để cung cấp Stream với một số thông tin bổ sung sẽ được sử dụng cho các tối ưu hóa nội bộ. Ví dụ như trong trường hợp chúng ta không chú ý đến thứ tự các phần tử trong một Set, có thể sử dụng Characteristics.UNORDERED.

Các Characteristics được hỗ trợ:

  • CONCURRENT : đặc điểm này có nghĩa là đối tượng accumulator() được trả về bởi suppier().get() có thể được thay đổi từ nhiều Thread đồng thời.
  • UNORDERED : đặc điểm này có nghĩa là hàm được trả về bởi finisher() không đảm bảo thứ tự của các phần tử trong Stream. Ví dụ Collectors.toSet() có các đặc điểm này vì kết quả là Set không đảm bảo thứ tự của các phần tử của Stream.  Đặc điểm này chủ yếu hữu ích khi làm việc với các luồng song song. Bộ thu thập UNORDERED cho phép tối ưu hóa nhiều hơn được thực hiện trong quá trình tách luồng thành các phần.
  • IDENTITY_FINISH : đặc điểm này có nghĩa là hàm được trả về bởi finisher() là identity() và chúng ta có thể cast đối tượng accumulator() thẳng đến kiểu kết quả (R), bỏ qua finisher().

Tạo Collector kế thừa từ interface Collector


public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A,T> accumulator();
    BinaryOperator<A> combiner();
    Function<A,R> finisher();
    Set<Characteristics> characteristics();
}

Ví dụ tạo một ProductCollector để đếm số lượng Product theo từng loại PRODUCT_TYPE dựa vào danh sách được cung cấp.


package com.gpcoder.collectors;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

enum PRODUCT_TYPE {
	LAPTOP, SMART_PHONE, TABLET
}

class Product {
	private Integer id;
	private String name;
	private PRODUCT_TYPE productType;

	public Product(Integer id, String name, PRODUCT_TYPE productType) {
		super();
		this.id = id;
		this.name = name;
		this.productType = productType;
	}

	public Integer getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public PRODUCT_TYPE getProductType() {
		return productType;
	}

	@Override
	public String toString() {
		return "Product [id=" + id + ", name=" + name + ", productType=" + productType + "]";
	}
}

class ProductCollector implements Collector<Product, Map<PRODUCT_TYPE, List<Product>>, Map<PRODUCT_TYPE, Integer>> {

	@Override
	public Supplier<Map<PRODUCT_TYPE, List<Product>>> supplier() {
		return HashMap::new;
	}

	@Override
	public BiConsumer<Map<PRODUCT_TYPE, List<Product>>, Product> accumulator() {
		return (map, product) -> {
			List<Product> products;
			if (map.get(product.getProductType()) == null) {
				products = new ArrayList<>();
			} else {
				products = map.get(product.getProductType());
			}
			products.add(product);
			map.put(product.getProductType(), products);
		};
	}

	@Override
	public BinaryOperator<Map<PRODUCT_TYPE, List<Product>>> combiner() {
		return (left, right) -> {
			left.putAll(right);
			return left;
		};
	}

	@Override
	public Function<Map<PRODUCT_TYPE, List<Product>>, Map<PRODUCT_TYPE, Integer>> finisher() {
		return (map) -> {
			final Map<PRODUCT_TYPE, Integer> res = new HashMap<>();
			for (Map.Entry<PRODUCT_TYPE, List<Product>> entry : map.entrySet()) {
				res.put(entry.getKey(), entry.getValue().size());
			}
			return res;
		};
	}

	@Override
	public Set<Characteristics> characteristics() {
		return Collections.singleton(Characteristics.UNORDERED);
	}

}

public class CollectorsExample5 {

	public static void main(String[] args) {

		List<Product> products = new ArrayList<>();

		products.add(new Product(1, "Dell", PRODUCT_TYPE.LAPTOP));
		products.add(new Product(2, "Asus", PRODUCT_TYPE.LAPTOP));
		products.add(new Product(3, "Acer", PRODUCT_TYPE.LAPTOP));
		products.add(new Product(4, "HP", PRODUCT_TYPE.LAPTOP));

		products.add(new Product(5, "iPhone", PRODUCT_TYPE.SMART_PHONE));
		products.add(new Product(6, "Samsung", PRODUCT_TYPE.SMART_PHONE));
		products.add(new Product(7, "Sony", PRODUCT_TYPE.SMART_PHONE));

		products.add(new Product(8, "iPad", PRODUCT_TYPE.TABLET));
		products.add(new Product(9, "Samsung Galaxy", PRODUCT_TYPE.TABLET));

		ProductCollector productCollector = new ProductCollector();
		Map<PRODUCT_TYPE, Integer> result = products.stream().collect(productCollector);
		System.out.println(result);
	}
}

Output của chương trình trên:


{LAPTOP=4, SMART_PHONE=3, TABLET=2}

Sử dụng Collector.of()

Để có thể tạo custom Collector, chúng ta phải cài đặt các phương thức của interface Collector. Tuy nhiên, thay vì cài đặt interface theo cách truyền thống, chúng ta sẽ sử dụng phương thức static Collector.of() để tạo custom Collector.

Cú pháp:


Collector.of(  
  supplier,
  accumulator,
  combiner,
  finisher, 
  Collector.Characteristics.CONCURRENT,
  Collector.Characteristics.IDENTITY_FINISH,
  // ...
);

Ví dụ: lớp ProductCollector ở trên có thể được viết lại như sau:


Collector<Product, // T
		Map<PRODUCT_TYPE, // A
		List<Product>>, Map<PRODUCT_TYPE, Integer>> // R
productCollector2 = Collector.of( //
		HashMap::new, // supplier
		(map, product) -> { // accumulator
			List<Product> list;
			if (map.get(product.getProductType()) == null) {
				list = new ArrayList<>();
			} else {
				list = map.get(product.getProductType());
			}
			list.add(product);
			map.put(product.getProductType(), list);
		}, (left, right) -> { // combiner
			left.putAll(right);
			return left;
		}, (map) -> { // finisher
			final Map<PRODUCT_TYPE, Integer> res = new HashMap<>();
			for (Map.Entry<PRODUCT_TYPE, List<Product>> entry : map.entrySet()) {
				res.put(entry.getKey(), entry.getValue().size());
			}
			return res;
		}, Collector.Characteristics.UNORDERED // characteristics
);

Map<PRODUCT_TYPE, Integer> result2 = products.stream().collect(productCollector2);
System.out.println(result2);

Output của chương trình trên:


{LAPTOP=4, SMART_PHONE=3, TABLET=2}

Tài liệu tham khảo:

  • https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
  • https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collector.html
  • http://www.baeldung.com/java-8-collectors
  • https://blog.frankel.ch/custom-collectors-java-8/
  • https://docs.oracle.com/javase/10/docs/api/java/util/function/BinaryOperator.html
4.7
11
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Java 8 Được gắn thẻ: Java 8

Function trong Java 8
Sắp xếp trong Java 8

Có thể bạn muốn xem:

  • Optional trong Java 8 (17/05/2018)
  • Function trong Java 8 (11/06/2018)
  • Functional Interface trong Java 8 (30/04/2018)
  • Interface trong Java 8 – Default method và Static method (26/04/2018)
  • Phương thức tham chiếu trong Java 8 – Method References (03/05/2018)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (97928 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (97590 lượt xem)
  • Giới thiệu Design Patterns (87587 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (86245 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83681 lượt xem)

Nội dung bài viết

  • 1 Các Collectors được xây dựng sẵn (build-in)
  • 2 Custom Collectors

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link