Cú pháp khởi tạo Double Brace ( {{ ... }}
) trong Java là gì?
Cú pháp khởi tạo Double Brace ( {{ ... }}
) trong Java là gì?
Câu trả lời:
Khởi tạo dấu ngoặc kép tạo ra một lớp ẩn danh xuất phát từ lớp được chỉ định ( dấu ngoặc ngoài ) và cung cấp một khối khởi tạo trong lớp đó ( dấu ngoặc trong ). ví dụ
new ArrayList<Integer>() {{
add(1);
add(2);
}};
Lưu ý rằng một tác dụng của việc sử dụng khởi tạo cú đúp này là bạn đang tạo các lớp bên trong ẩn danh. Lớp được tạo có một this
con trỏ ẩn đến lớp bên ngoài xung quanh. Mặc dù thông thường không phải là một vấn đề, nó có thể gây đau buồn trong một số trường hợp, ví dụ như khi xê-ri hóa hoặc thu gom rác, và đáng để nhận ra điều này.
Mỗi khi ai đó sử dụng khởi tạo cú đúp, một con mèo con sẽ bị giết.
Ngoài cú pháp khá bất thường và không thực sự thành ngữ (dĩ nhiên là có thể gây tranh cãi), bạn không cần thiết phải tạo ra hai vấn đề quan trọng trong ứng dụng của mình, điều mà tôi vừa mới viết chi tiết ở đây .
Mỗi khi bạn sử dụng khởi tạo cú đúp, một lớp mới được tạo. Ví dụ: ví dụ này:
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
... sẽ tạo ra các lớp này:
Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class
Đó là một chút chi phí cho trình tải lớp của bạn - không có gì! Tất nhiên sẽ không mất nhiều thời gian khởi tạo nếu bạn làm điều đó một lần. Nhưng nếu bạn làm điều này 20.000 lần trong suốt ứng dụng doanh nghiệp của mình ... tất cả bộ nhớ heap đó chỉ vì một chút "đường cú pháp"?
Nếu bạn lấy mã ở trên và trả lại bản đồ đó từ một phương thức, người gọi phương thức đó có thể không nghi ngờ gì đến việc giữ các tài nguyên rất nặng không thể thu gom được rác. Hãy xem xét ví dụ sau:
public class ReallyHeavyObject {
// Just to illustrate...
private int[] tonsOfValues;
private Resource[] tonsOfResources;
// This method almost does nothing
public Map quickHarmlessMethod() {
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
return source;
}
}
Map
Bây giờ trả về sẽ chứa một tham chiếu đến thể hiện kèm theo của ReallyHeavyObject
. Bạn có thể không muốn mạo hiểm rằng:
Hình ảnh từ http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-potype/
Để trả lời câu hỏi thực tế của bạn, mọi người đã sử dụng cú pháp này để giả vờ rằng Java có một cái gì đó giống như chữ bản đồ, tương tự như các mảng chữ hiện có:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
Một số người có thể tìm thấy kích thích cú pháp này.
{{...}}
và được khai báo là một static
trường, thì không nên có bất kỳ rò rỉ bộ nhớ nào, chỉ có một lớp ẩn danh và không có tham chiếu thể hiện kèm theo, phải không?
Map.of()
có mục đích đó, vì vậy đó sẽ là một giải pháp tốt hơn
ReallyHeavyObject
. Ngoài ra, các lớp bên trong ẩn danh nắm bắt tất cả các biến cục bộ được sử dụng trong thân lớp, vì vậy nếu bạn không chỉ sử dụng các hằng số để khởi tạo các bộ sưu tập hoặc bản đồ với mẫu này, các thể hiện của lớp bên trong sẽ nắm bắt tất cả chúng và tiếp tục tham chiếu chúng ngay cả khi thực sự bị xóa khỏi bộ sưu tập hoặc bản đồ. Vì vậy, trong trường hợp đó, các trường hợp này không chỉ cần gấp đôi bộ nhớ cần thiết cho các tham chiếu, mà còn có một rò rỉ bộ nhớ khác liên quan đến vấn đề đó.
Ví dụ:
public class TestHashMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<String,String>(){
{
put("1", "ONE");
}{
put("2", "TWO");
}{
put("3", "THREE");
}
};
Set<String> keySet = map.keySet();
for (String string : keySet) {
System.out.println(string+" ->"+map.get(string));
}
}
}
Làm thế nào nó hoạt động
Cú đúp đầu tiên tạo ra một Lớp bên trong vô danh mới. Các lớp bên trong này có khả năng truy cập hành vi của lớp cha của chúng. Vì vậy, trong trường hợp của chúng tôi, chúng tôi thực sự đang tạo một lớp con của lớp Hashset, vì vậy lớp bên trong này có khả năng sử dụng phương thức put ().
Và bộ niềng răng thứ hai không có gì ngoài bộ khởi tạo cá thể. Nếu bạn nhắc các khái niệm java cốt lõi thì bạn có thể dễ dàng liên kết các khối khởi tạo cá thể với các trình khởi tạo tĩnh do cú đúp tương tự như struct. Chỉ khác là bộ khởi tạo tĩnh được thêm bằng từ khóa tĩnh và chỉ được chạy một lần; bất kể bạn tạo ra bao nhiêu đối tượng
Để biết một ứng dụng thú vị về khởi tạo cú đúp, hãy xem ở đây Dwemthy Array trong Java .
Một đoạn trích
private static class IndustrialRaverMonkey
extends Creature.Base {{
life = 46;
strength = 35;
charisma = 91;
weapon = 2;
}}
private static class DwarvenAngel
extends Creature.Base {{
life = 540;
strength = 6;
charisma = 144;
weapon = 50;
}}
Và bây giờ, hãy chuẩn bị cho thịt xông khói chunkyBattleOfGrottoOfSausageSmells
và chunky!
Tôi nghĩ điều quan trọng là phải nhấn mạnh rằng không có thứ gọi là "Khởi tạo cú đúp" trong Java . Trang web của Oracle không có thuật ngữ này. Trong ví dụ này, có hai tính năng được sử dụng cùng nhau: lớp ẩn danh và khối khởi tạo. Có vẻ như khối khởi tạo cũ đã bị các nhà phát triển lãng quên và gây ra một số nhầm lẫn trong chủ đề này. Trích dẫn từ tài liệu của Oracle :
Các khối khởi tạo cho các biến thể hiện trông giống như các khối khởi tạo tĩnh, nhưng không có từ khóa tĩnh:
{
// whatever code is needed for initialization goes here
}
1- Không có thứ gọi là niềng răng đôi:
Tôi muốn chỉ ra rằng không có thứ gọi là khởi tạo niềng răng đôi. Chỉ có một khối khởi đầu truyền thống bình thường. Khối niềng răng thứ hai không liên quan gì đến khởi tạo. Câu trả lời nói rằng hai cái niềng răng đó khởi tạo một cái gì đó, nhưng nó không phải như thế.
2- Không chỉ về các lớp ẩn danh mà tất cả các lớp:
Hầu như tất cả các câu trả lời đều nói rằng đó là một thứ được sử dụng khi tạo các lớp bên trong ẩn danh. Tôi nghĩ rằng những người đọc những câu trả lời đó sẽ có ấn tượng rằng nó chỉ được sử dụng khi tạo các lớp bên trong ẩn danh. Nhưng nó được sử dụng trong tất cả các lớp. Đọc những câu trả lời đó có vẻ như là một tính năng đặc biệt hoàn toàn mới dành riêng cho các lớp ẩn danh và tôi nghĩ đó là sai lệch.
3- Mục đích chỉ là đặt các dấu ngoặc cho nhau, không phải là khái niệm mới:
Đi xa hơn, câu hỏi này nói về tình huống khi khung mở thứ hai chỉ sau khung mở đầu tiên. Khi được sử dụng trong lớp bình thường thường có một số mã giữa hai dấu ngoặc nhọn, nhưng nó hoàn toàn giống nhau. Vì vậy, nó là một vấn đề của việc đặt dấu ngoặc. Vì vậy, tôi nghĩ rằng chúng ta không nên nói rằng đây là một điều thú vị mới, bởi vì đây là điều mà tất cả chúng ta đều biết, nhưng chỉ được viết với một số mã giữa các dấu ngoặc. Chúng ta không nên tạo ra khái niệm mới gọi là "khởi tạo cú đúp".
4- Tạo các lớp ẩn danh lồng nhau không liên quan gì đến hai dấu ngoặc nhọn:
Tôi không đồng ý với lập luận rằng bạn tạo quá nhiều lớp ẩn danh. Bạn không tạo chúng vì một khối khởi tạo, mà chỉ vì bạn tạo chúng. Chúng sẽ được tạo ngay cả khi bạn không sử dụng hai lần khởi tạo niềng răng để những vấn đề đó xảy ra ngay cả khi không khởi tạo ... Khởi tạo không phải là yếu tố tạo ra các đối tượng khởi tạo.
Ngoài ra, chúng ta không nên nói về vấn đề được tạo bằng cách sử dụng "không khởi tạo cú đúp" này hoặc thậm chí bằng cách khởi tạo một khung bình thường, bởi vì các vấn đề được mô tả chỉ tồn tại do tạo lớp ẩn danh nên không liên quan gì đến câu hỏi ban đầu. Nhưng tất cả các câu trả lời đều mang đến cho độc giả ấn tượng rằng đó không phải là lỗi khi tạo các lớp ẩn danh, mà là thứ xấu xa (không tồn tại) này được gọi là "khởi tạo cú đúp".
Để tránh tất cả các tác động tiêu cực của khởi tạo nẹp đôi, chẳng hạn như:
làm những việc tiếp theo:
Thí dụ:
public class MyClass {
public static class Builder {
public int first = -1 ;
public double second = Double.NaN;
public String third = null ;
public MyClass create() {
return new MyClass(first, second, third);
}
}
protected final int first ;
protected final double second;
protected final String third ;
protected MyClass(
int first ,
double second,
String third
) {
this.first = first ;
this.second= second;
this.third = third ;
}
public int first () { return first ; }
public double second() { return second; }
public String third () { return third ; }
}
Sử dụng:
MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();
Ưu điểm:
Nhược điểm:
Và, kết quả là, chúng ta có mẫu xây dựng java đơn giản nhất từ trước đến nay.
Xem tất cả các mẫu tại github: java-sf-builder-Simple-example
Đó là - trong số các mục đích sử dụng khác - một lối tắt để khởi tạo các bộ sưu tập. Tìm hiểu thêm ...
Bạn có thể đặt một số câu lệnh Java làm vòng lặp để khởi tạo bộ sưu tập:
List<Character> characters = new ArrayList<Character>() {
{
for (char c = 'A'; c <= 'E'; c++) add(c);
}
};
Random rnd = new Random();
List<Integer> integers = new ArrayList<Integer>() {
{
while (size() < 10) add(rnd.nextInt(1_000_000));
}
};
Như được chỉ ra bởi @Lukas Eder, việc khởi tạo niềng răng đôi của bộ sưu tập phải được tránh.
Nó tạo ra một lớp bên trong ẩn danh và vì tất cả các lớp bên trong giữ một tham chiếu đến thể hiện cha mẹ nên nó có thể - và 99% có khả năng sẽ - ngăn chặn việc thu gom rác nếu các đối tượng bộ sưu tập này được tham chiếu bởi nhiều đối tượng hơn là chỉ khai báo.
Java 9 đã giới thiệu phương pháp thuận tiện List.of
, Set.of
và Map.of
, mà nên được sử dụng để thay thế. Chúng nhanh hơn và hiệu quả hơn bộ khởi tạo hai nẹp.
Niềng răng đầu tiên tạo ra một Lớp ẩn danh mới và bộ niềng thứ hai tạo ra một bộ khởi tạo cá thể giống như khối tĩnh.
Giống như những người khác đã chỉ ra, nó không an toàn để sử dụng.
Tuy nhiên, bạn luôn có thể sử dụng thay thế này để khởi tạo các bộ sưu tập.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");
Điều này có vẻ giống như với từ khóa rất phổ biến trong flash và vbscript. Đó là một phương pháp thay đổi những gì this
và không có gì hơn.
this
đang có. Cú pháp chỉ tạo một lớp ẩn danh (vì vậy mọi tham chiếu this
sẽ đề cập đến đối tượng của lớp ẩn danh mới đó), sau đó sử dụng một khối {...}
khởi tạo để khởi tạo thể hiện mới được tạo.