Java에서 Double Brace 초기화 란 무엇입니까?


306

{{ ... }}Java에서 Double Brace 초기화 구문 ( ) 이란 무엇입니까 ?




10
이중 브레이스 초기화는 매우 위험한 기능이므로 신중하게 사용해야합니다. 그것은 계약을 깨뜨리고 까다로운 메모리 누수를 일으킬 수 있습니다. 기사는 세부 사항을 설명합니다.
Andrii Polunin

답변:


302

이중 괄호 초기화는 지정된 클래스 ( 외부 괄호) 에서 파생 된 익명 클래스를 작성하고 해당 클래스 내에 초기 괄호 ( 내부 괄호)를 제공합니다. 예 :

new ArrayList<Integer>() {{
   add(1);
   add(2);
}};

이 이중 괄호 초기화를 사용하면 익명의 내부 클래스를 만들게됩니다. 생성 된 클래스에는 this주변 외부 클래스에 대한 암시 적 포인터가 있습니다. 일반적으로 문제는 아니지만 직렬화 또는 가비지 수집과 같은 일부 환경에서는 슬픔을 유발할 수 있으므로이를 알고 있어야합니다.


11
내부 및 외부 괄호의 의미를 명확히 해 주셔서 감사합니다. 나는 실제로 두 가지 중괄호가 특별한 의미로 허용되는 이유를 궁금해했습니다. 실제로 마술 같은 새로운 트릭으로 만 나타나는 정상적인 Java 구조입니다. 그런 것들이 Java 구문에 의문을 제기합니다. 이미 전문가가 아닌 경우 읽고 쓰는 것이 매우 까다로울 수 있습니다.
jackthehipster

4
이와 같은 "매직 구문"은 많은 언어로 존재합니다. 예를 들어 거의 모든 C 유사 언어는 "x-> 0"의 "0으로 이동"구문을 지원합니다. 공간 배치.
Joachim Sauer

17
우리는 "이중 괄호 초기화"가 그 자체로 존재하지 않는다는 결론을 내릴 수 있습니다. 익명 클래스이니셜 라이저 블록을 결합한 입니다. 일단 결합되면 구문 구조처럼 보이지만 실제로는 그렇지 않습니다. 티.
MC 황제

감사합니다! 익명 내부 클래스 사용으로 인해 이중 중괄호 초기화로 무언가를 직렬화하면 Gson이 null을 반환합니다.
Pradeep AJ- Msft MVP

295

누군가 이중 괄호 초기화를 사용할 때마다 새끼 고양이가 죽습니다.

구문이 다소 독특하지 않고 실제로 관용적이지는 않지만 (물론 맛은 물론 논쟁의 여지가 있습니다), 응용 프로그램에서 두 가지 중요한 문제가 불필요하게 생성 되고 있습니다 .

1. 당신은 너무 많은 익명 클래스를 만들고 있습니다

이중 괄호 초기화를 사용할 때마다 새 클래스가 작성됩니다. 예를 들어이 예 :

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 만 번을 수행한다면 ... "구문 설탕"에 대한 모든 힙 메모리?

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/의 이미지

3. Java에 맵 리터럴이 있다고 가정 할 수 있습니다.

실제 질문에 대답하기 위해 사람들은이 구문을 사용하여 Java에 기존 배열 리터럴과 비슷한 맵 리터럴과 같은 것으로 가장했습니다.

String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};

어떤 사람들은 이것이 문법적으로 자극적 인 것을 발견 할 수 있습니다.


11
"너무 많은 익명 클래스를 만들고 있습니다"-Scala가 익명 클래스를 만드는 방법을 살펴보면 이것이 중대한 문제 인지 확신 할 수 없습니다.
Brian Agnew

2
정적 맵을 선언하는 타당하고 좋은 방법이 아닌가? HashMap이 필드 {{...}}로 초기화되고 static필드 로 선언되면 메모리 누수가 발생하지 않아야하며 익명 클래스가 하나만 있고 동봉 된 인스턴스 참조가 없어야합니다.
lorenzo-s

8
@ lorenzo-s : 예, 2) 및 3) 그때는 적용되지 않으며 1). 운 좋게도 Java 9에는 마침내 Map.of()그러한 목적이 있으므로 더 나은 솔루션이 될 것입니다.
Lukas Eder

3
내부 맵에도 외부 맵에 대한 참조가 있으므로 간접적으로 참조가 있음을 주목할 가치가 있습니다 ReallyHeavyObject. 또한 익명의 내부 클래스는 클래스 본문 내에서 사용되는 모든 로컬 변수를 캡처하므로 상수를 사용하여이 패턴으로 컬렉션 또는 맵을 초기화하지 않으면 내부 클래스 인스턴스가 모든 인스턴스를 캡처하고 실제로 제거 된 경우에도이를 참조합니다. 컬렉션 또는 맵 이 경우 이러한 인스턴스는 참조에 필요한 메모리의 두 배가 필요할뿐 아니라 이와 관련하여 또 다른 메모리 누수가 있습니다.
Holger

41
  • 첫 번째 버팀대는 새로운 Anonymous Inner Class를 만듭니다.
  • 두 번째 괄호 세트는 클래스의 정적 블록과 같은 인스턴스 이니셜 라이저를 작성합니다.

예를 들면 다음과 같습니다.

   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 키워드가 추가되고 한 번만 실행된다는 점만 다릅니다. 얼마나 많은 객체를 생성하든


24

이중 괄호 초기화의 재미있는 응용 프로그램은 여기 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 베이컨을 준비하십시오 !


16

Java에는 "Double Brace initialize"와 같은 것이 없다는 것을 강조하는 것이 중요하다고 생각합니다 . Oracle 웹 사이트에는이 용어가 없습니다. 이 예제에는 익명 클래스와 이니셜 라이저 블록이라는 두 가지 기능이 함께 사용됩니다. 이전 이니셜 라이저 블록이 개발자들에 의해 잊혀져 서이 주제에서 약간의 혼란을 야기한 것 같습니다. 오라클 문서 에서 인용 :

인스턴스 변수의 이니셜 라이저 블록은 정적 이니셜 라이저 블록과 유사하지만 정적 키워드는 없습니다.

{
    // whatever code is needed for initialization goes here
}

11

1- 이중 괄호와 같은 것은 없습니다 :
이중 괄호 초기화와 같은 것이 없다는 것을 지적하고 싶습니다. 일반적인 하나의 버팀대 초기화 블록 만 있습니다. 두 번째 괄호 블록은 초기화와 관련이 없습니다. 답은 두 괄호가 무언가를 초기화한다고 말하지만 그렇지 않습니다.

2- 그것은 익명 클래스뿐만 아니라 모든 클래스에 관한 것입니다.
거의 모든 대답은 익명의 내부 클래스를 만들 때 사용되는 것이라고 말합니다. 나는 그 대답을 읽는 사람들이 익명의 내부 클래스를 만들 때만 사용된다는 인상을 얻을 것이라고 생각합니다. 그러나 모든 클래스에서 사용됩니다. 그 답변을 읽는 것은 익명 클래스 전용의 새로운 특수 기능이며 오해의 소지가 있다고 생각합니다.

3- 목적은 단지 새로운 개념이 아니라 서로 괄호를 배치하는 것입니다.
계속해서,이 질문은 두 번째 개방 브래킷이 첫 번째 개방 브래킷 바로 뒤에있는 상황에 대해 이야기합니다. 일반 클래스에서 사용하면 일반적으로 두 개의 중괄호 사이에 코드가 있지만 완전히 동일합니다. 따라서 브래킷을 배치해야합니다. 그래서 우리는 이것이 새로운 흥미로운 것이라고 말해서는 안됩니다. 이것이 우리 모두가 알고있는 것이기 때문에 대괄호 사이에 코드로 작성되었습니다. "이중 괄호 초기화"라는 새로운 개념을 만들면 안됩니다.

4- 중첩 된 익명 클래스를 생성하는 것은 두 개의 중괄호와 아무 관련이 없습니다
. 너무 많은 익명 클래스를 생성한다는 주장에 동의하지 않습니다. 초기화 블록 때문에 생성하는 것이 아니라 생성하기 때문입니다. 두 개의 중괄호 초기화를 사용하지 않아도 초기화되므로 이러한 문제는 초기화 없이도 발생할 수 있습니다. 초기화는 초기화 된 객체를 생성하는 요소가 아닙니다.

또한 우리는 존재하지 않는이 "중괄호 초기화"를 사용하여 생성 된 문제 나 심지어 일반적인 하나의 대괄호 초기화로 인해 발생하는 문제에 대해 이야기해서는 안됩니다. 그러나 모든 대답은 독자들에게 익명 클래스를 만드는 것이 잘못이 아니라 "이중 괄호 초기화"라는 악의적 인 존재라는 인상을줍니다.


9

이중 괄호 초기화의 모든 부정적인 영향을 피하려면 다음과 같이하십시오.

  1. 깨진 "같음"호환성.
  2. 직접 할당을 사용하는 경우 검사가 수행되지 않습니다.
  3. 가능한 메모리 누수.

다음 작업을 수행하십시오.

  1. 이중 괄호 초기화를 위해 별도의 "빌더"클래스를 만드십시오.
  2. 필드를 기본값으로 선언하십시오.
  3. 해당 클래스에 객체 생성 방법을 넣습니다.

예:

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();

장점 :

  1. 간단하게 사용하십시오.
  2. "같음"호환성을 손상시키지 마십시오.
  3. 작성 방법에서 점검을 수행 할 수 있습니다.
  4. 메모리 누수가 없습니다.

단점 :

  • 없음

결과적으로 가장 간단한 Java 빌더 패턴이 있습니다.

github의 모든 샘플보기 : java-sf-builder-simple-example


4

컬렉션을 초기화하는 바로 가기입니다. 더 알아보기 ...


2
글쎄, 그것은 그것을위한 하나의 응용 프로그램이지만, 결코 유일한 응용 프로그램은 아닙니다.
skaffman

4

이런 뜻인가요?

List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};

생성 시간에 배열 목록 초기화입니다 (해킹)


4

컬렉션을 초기화하기 위해 일부 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));
    }
};

그러나이 사건은 성능에 영향을 미칩니다.이 토론을 확인하십시오.


4

@Lukas Eder가 지적한 것처럼 컬렉션의 이중 괄호 초기화는 피해야합니다.

익명의 내부 클래스를 만들고 모든 내부 클래스는 부모 인스턴스에 대한 참조를 유지하므로 이러한 컬렉션 개체가 선언하는 것보다 많은 개체에서 참조되는 경우 가비지 수집을 방지 할 수 있으며 99 %가 가능합니다.

자바 9 편리한 방법을 도입 List.of, Set.of그리고 Map.of대신 사용해야합니다. 이중 괄호 이니셜 라이저보다 더 빠르고 효율적입니다.


0

첫 번째 괄호는 새로운 익명 클래스를 만들고 두 번째 괄호 세트는 정적 블록과 같은 인스턴스 이니셜 라이저를 만듭니다.

다른 사람들이 지적했듯이 사용하는 것이 안전하지 않습니다.

그러나 컬렉션을 초기화 할 때는 항상이 대안을 사용할 수 있습니다.

  • 자바 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • 자바 9
List<String> list = List.of("A", "B", "C");

-1

이것은 flash 및 vbscript에서 인기있는 with 키워드와 동일한 것으로 보입니다. 그것은 무엇을 this더 이상 바꾸지 않는 방법입니다 .


실제로는 아닙니다. 새 클래스를 만드는 것이 무엇을 바꾸는 방법이라고 말하는 것과 같습니다 this. 이 구문은 익명 클래스 ( this새 익명 클래스의 객체를 참조하는 참조)를 만든 다음 초기화 블록을 사용 {...}하여 새로 만든 인스턴스를 초기화합니다.
grinch
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.