{{ ... }}
Java에서 Double Brace 초기화 구문 ( ) 이란 무엇입니까 ?
{{ ... }}
Java에서 Double Brace 초기화 구문 ( ) 이란 무엇입니까 ?
답변:
이중 괄호 초기화는 지정된 클래스 ( 외부 괄호) 에서 파생 된 익명 클래스를 작성하고 해당 클래스 내에 초기 괄호 ( 내부 괄호)를 제공합니다. 예 :
new ArrayList<Integer>() {{
add(1);
add(2);
}};
이 이중 괄호 초기화를 사용하면 익명의 내부 클래스를 만들게됩니다. 생성 된 클래스에는 this
주변 외부 클래스에 대한 암시 적 포인터가 있습니다. 일반적으로 문제는 아니지만 직렬화 또는 가비지 수집과 같은 일부 환경에서는 슬픔을 유발할 수 있으므로이를 알고 있어야합니다.
누군가 이중 괄호 초기화를 사용할 때마다 새끼 고양이가 죽습니다.
구문이 다소 독특하지 않고 실제로 관용적이지는 않지만 (물론 맛은 물론 논쟁의 여지가 있습니다), 응용 프로그램에서 두 가지 중요한 문제가 불필요하게 생성 되고 있습니다 .
이중 괄호 초기화를 사용할 때마다 새 클래스가 작성됩니다. 예를 들어이 예 :
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");
}});
}});
}};
...이 클래스를 생성합니다 :
Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class
그것은 클래스 로더에 대한 약간의 오버 헤드입니다. 아무것도 없습니다! 물론 한 번 수행하면 초기화 시간이 많이 걸리지 않습니다. 그러나 엔터프라이즈 애플리케이션 전체에서이 2 만 번을 수행한다면 ... "구문 설탕"에 대한 모든 힙 메모리?
위의 코드를 가져와 메소드에서 해당 맵을 리턴하면 해당 메소드의 호출자가 의심 할 여지없이 가비지 콜렉션 할 수없는 매우 많은 자원을 보유하고있을 수 있습니다. 다음 예제를 고려하십시오.
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
에의 닫는 인스턴스에 대한 참조가 포함됩니다 ReallyHeavyObject
. 다음과 같은 위험을 감수하고 싶지 않을 것입니다.
http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/의 이미지
실제 질문에 대답하기 위해 사람들은이 구문을 사용하여 Java에 기존 배열 리터럴과 비슷한 맵 리터럴과 같은 것으로 가장했습니다.
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
어떤 사람들은 이것이 문법적으로 자극적 인 것을 발견 할 수 있습니다.
{{...}}
로 초기화되고 static
필드 로 선언되면 메모리 누수가 발생하지 않아야하며 익명 클래스가 하나만 있고 동봉 된 인스턴스 참조가 없어야합니다.
Map.of()
그러한 목적이 있으므로 더 나은 솔루션이 될 것입니다.
ReallyHeavyObject
. 또한 익명의 내부 클래스는 클래스 본문 내에서 사용되는 모든 로컬 변수를 캡처하므로 상수를 사용하여이 패턴으로 컬렉션 또는 맵을 초기화하지 않으면 내부 클래스 인스턴스가 모든 인스턴스를 캡처하고 실제로 제거 된 경우에도이를 참조합니다. 컬렉션 또는 맵 이 경우 이러한 인스턴스는 참조에 필요한 메모리의 두 배가 필요할뿐 아니라 이와 관련하여 또 다른 메모리 누수가 있습니다.
예를 들면 다음과 같습니다.
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));
}
}
}
작동 원리
첫 번째 버팀대 는 새로운 Anonymous Inner Class를 만듭니다. 이 내부 클래스는 부모 클래스의 동작에 액세스 할 수 있습니다. 따라서 우리의 경우 실제로 HashSet 클래스의 서브 클래스를 작성하므로이 내부 클래스는 put () 메소드를 사용할 수 있습니다.
그리고 두 번째 중괄호 세트는 인스턴스 이니셜 라이저 일뿐입니다. 핵심 Java 개념을 생각 나게하면 구조체와 비슷한 중괄호로 인해 인스턴스 이니셜 라이저 블록을 정적 이니셜 라이저와 쉽게 연결할 수 있습니다. 정적 이니셜 라이저에는 static 키워드가 추가되고 한 번만 실행된다는 점만 다릅니다. 얼마나 많은 객체를 생성하든
이중 괄호 초기화의 재미있는 응용 프로그램은 여기 Dwemthy 's Array in Java를 참조하십시오 .
발췌
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;
}}
그리고 지금, BattleOfGrottoOfSausageSmells
그리고 ... chunky 베이컨을 준비하십시오 !
Java에는 "Double Brace initialize"와 같은 것이 없다는 것을 강조하는 것이 중요하다고 생각합니다 . Oracle 웹 사이트에는이 용어가 없습니다. 이 예제에는 익명 클래스와 이니셜 라이저 블록이라는 두 가지 기능이 함께 사용됩니다. 이전 이니셜 라이저 블록이 개발자들에 의해 잊혀져 서이 주제에서 약간의 혼란을 야기한 것 같습니다. 오라클 문서 에서 인용 :
인스턴스 변수의 이니셜 라이저 블록은 정적 이니셜 라이저 블록과 유사하지만 정적 키워드는 없습니다.
{
// whatever code is needed for initialization goes here
}
1- 이중 괄호와 같은 것은 없습니다 :
이중 괄호 초기화와 같은 것이 없다는 것을 지적하고 싶습니다. 일반적인 하나의 버팀대 초기화 블록 만 있습니다. 두 번째 괄호 블록은 초기화와 관련이 없습니다. 답은 두 괄호가 무언가를 초기화한다고 말하지만 그렇지 않습니다.
2- 그것은 익명 클래스뿐만 아니라 모든 클래스에 관한 것입니다.
거의 모든 대답은 익명의 내부 클래스를 만들 때 사용되는 것이라고 말합니다. 나는 그 대답을 읽는 사람들이 익명의 내부 클래스를 만들 때만 사용된다는 인상을 얻을 것이라고 생각합니다. 그러나 모든 클래스에서 사용됩니다. 그 답변을 읽는 것은 익명 클래스 전용의 새로운 특수 기능이며 오해의 소지가 있다고 생각합니다.
3- 목적은 단지 새로운 개념이 아니라 서로 괄호를 배치하는 것입니다.
계속해서,이 질문은 두 번째 개방 브래킷이 첫 번째 개방 브래킷 바로 뒤에있는 상황에 대해 이야기합니다. 일반 클래스에서 사용하면 일반적으로 두 개의 중괄호 사이에 코드가 있지만 완전히 동일합니다. 따라서 브래킷을 배치해야합니다. 그래서 우리는 이것이 새로운 흥미로운 것이라고 말해서는 안됩니다. 이것이 우리 모두가 알고있는 것이기 때문에 대괄호 사이에 코드로 작성되었습니다. "이중 괄호 초기화"라는 새로운 개념을 만들면 안됩니다.
4- 중첩 된 익명 클래스를 생성하는 것은 두 개의 중괄호와 아무 관련이 없습니다
. 너무 많은 익명 클래스를 생성한다는 주장에 동의하지 않습니다. 초기화 블록 때문에 생성하는 것이 아니라 생성하기 때문입니다. 두 개의 중괄호 초기화를 사용하지 않아도 초기화되므로 이러한 문제는 초기화 없이도 발생할 수 있습니다. 초기화는 초기화 된 객체를 생성하는 요소가 아닙니다.
또한 우리는 존재하지 않는이 "중괄호 초기화"를 사용하여 생성 된 문제 나 심지어 일반적인 하나의 대괄호 초기화로 인해 발생하는 문제에 대해 이야기해서는 안됩니다. 그러나 모든 대답은 독자들에게 익명 클래스를 만드는 것이 잘못이 아니라 "이중 괄호 초기화"라는 악의적 인 존재라는 인상을줍니다.
이중 괄호 초기화의 모든 부정적인 영향을 피하려면 다음과 같이하십시오.
다음 작업을 수행하십시오.
예:
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 ; }
}
용법:
MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();
장점 :
단점 :
결과적으로 가장 간단한 Java 빌더 패턴이 있습니다.
github의 모든 샘플보기 : java-sf-builder-simple-example
컬렉션을 초기화하기 위해 일부 Java 문을 루프로 넣을 수 있습니다.
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));
}
};
@Lukas Eder가 지적한 것처럼 컬렉션의 이중 괄호 초기화는 피해야합니다.
익명의 내부 클래스를 만들고 모든 내부 클래스는 부모 인스턴스에 대한 참조를 유지하므로 이러한 컬렉션 개체가 선언하는 것보다 많은 개체에서 참조되는 경우 가비지 수집을 방지 할 수 있으며 99 %가 가능합니다.
자바 9 편리한 방법을 도입 List.of
, Set.of
그리고 Map.of
대신 사용해야합니다. 이중 괄호 이니셜 라이저보다 더 빠르고 효율적입니다.