Java "Double Brace Initialization"의 효율성?
에서 자바의 숨겨진 기능 상단의 대답은 언급 더블 중괄호 초기화를 로, 매우 유혹 구문 :
Set<String> flavors = new HashSet<String>() {{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}};
이 관용구는 "포함 범위에서 [...] 메소드를 사용할 수있는"인스턴스 이니셜 라이저 만있는 익명 내부 클래스를 만듭니다.
주요 질문 : 이것이 들리는 것처럼 비효율적 입니까? 일회용 초기화로 사용을 제한해야합니까? (물론 과시!)
두 번째 질문 : 새 HashSet은 인스턴스 이니셜 라이저에서 사용되는 "this"여야합니다. 누구든지 메커니즘에 대해 밝힐 수 있습니까?
세 번째 질문 :이 관용구 가 프로덕션 코드에서 사용 하기에는 너무 모호 합니까?
요약 : 아주 아주 좋은 답변입니다. 감사합니다. 질문 (3)에서 사람들은 구문이 명확해야한다고 느꼈습니다 (비록 익숙하지 않은 개발자에게 코드가 전달되는 경우 가끔 코멘트를 권장합니다).
질문 (1)에서 생성 된 코드는 빠르게 실행되어야합니다. 추가 .class 파일은 jar 파일을 복잡하게 만들고 프로그램 시작을 약간 느리게 만듭니다 (이를 측정 한 @coobird 덕분에). @Thilo는 가비지 컬렉션이 영향을받을 수 있으며 추가로로드 된 클래스의 메모리 비용이 경우에 따라 요인이 될 수 있다고 지적했습니다.
질문 (2)가 가장 흥미로 웠습니다. 내가 대답을 이해한다면, DBI에서 일어나는 일은 익명의 내부 클래스가 new 연산자에 의해 생성되는 객체의 클래스를 확장하므로 생성되는 인스턴스를 참조하는 "this"값을 갖는다는 것입니다. 매우 깔끔합니다.
전반적으로 DBI는 저를 지적 호기심이라고 생각합니다. Coobird와 다른 사람들은 Arrays.asList, varargs 메소드, Google Collections 및 제안 된 Java 7 Collection 리터럴을 사용하여 동일한 효과를 얻을 수 있다고 지적합니다. Scala, JRuby 및 Groovy와 같은 최신 JVM 언어도 목록 구성을위한 간결한 표기법을 제공하고 Java와 잘 상호 운용됩니다. DBI가 클래스 경로를 복잡하게 만들고, 클래스 로딩 속도를 약간 늦추고, 코드를 좀 더 모호하게 만든다는 점을 감안할 때 나는 아마 피할 것입니다. 그러나 저는 SCJP를 방금 받았으며 Java 의미론에 대한 좋은 본성있는 마상을 좋아하는 친구에게 이것을 봄을 계획합니다! ;-) 모두 감사합니다!
7/2017 : Baeldung 은 이중 중괄호 초기화에 대한 좋은 요약 을 가지고 있으며 이를 반 패턴으로 간주합니다.
12/2017 : @Basil Bourque는 새로운 Java 9에서 다음과 같이 말할 수 있다고 말합니다.
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
그것은 확실히 갈 길입니다. 이전 버전을 사용하고 있다면 Google Collections의 ImmutableSet를 살펴보세요 .
익명의 내부 클래스에 너무 빠져들 때의 문제는 다음과 같습니다.
2009/05/27 16:35 1,602 DemoApp2$1.class
2009/05/27 16:35 1,976 DemoApp2$10.class
2009/05/27 16:35 1,919 DemoApp2$11.class
2009/05/27 16:35 2,404 DemoApp2$12.class
2009/05/27 16:35 1,197 DemoApp2$13.class
/* snip */
2009/05/27 16:35 1,953 DemoApp2$30.class
2009/05/27 16:35 1,910 DemoApp2$31.class
2009/05/27 16:35 2,007 DemoApp2$32.class
2009/05/27 16:35 926 DemoApp2$33$1$1.class
2009/05/27 16:35 4,104 DemoApp2$33$1.class
2009/05/27 16:35 2,849 DemoApp2$33.class
2009/05/27 16:35 926 DemoApp2$34$1$1.class
2009/05/27 16:35 4,234 DemoApp2$34$1.class
2009/05/27 16:35 2,849 DemoApp2$34.class
/* snip */
2009/05/27 16:35 614 DemoApp2$40.class
2009/05/27 16:35 2,344 DemoApp2$5.class
2009/05/27 16:35 1,551 DemoApp2$6.class
2009/05/27 16:35 1,604 DemoApp2$7.class
2009/05/27 16:35 1,809 DemoApp2$8.class
2009/05/27 16:35 2,022 DemoApp2$9.class
이것들은 내가 간단한 응용 프로그램을 만들 때 생성 된 모든 클래스 들이며 많은 양의 익명 내부 클래스를 사용했습니다. 각 클래스는 별도의 class
파일 로 컴파일됩니다 .
이미 언급했듯이 "이중 중괄호 초기화"는 인스턴스 초기화 블록이있는 익명의 내부 클래스입니다. 즉, 일반적으로 단일 개체를 만들기위한 목적으로 각 "초기화"에 대해 새 클래스가 만들어집니다.
Java Virtual Machine이 클래스를 사용할 때 모든 클래스를 읽어야한다는 점을 고려하면 바이트 코드 확인 프로세스 등에서 시간이 걸릴 수 있습니다 . 모든 class
파일 을 저장하기 위해 필요한 디스크 공간의 증가는 말할 것도 없습니다 .
이중 중괄호 초기화를 사용할 때 약간의 오버 헤드가있는 것처럼 보이므로 너무 과도하게 사용하는 것은 좋은 생각이 아닙니다. 그러나 Eddie가 의견에서 언급했듯이 그 영향을 절대적으로 확신 할 수는 없습니다.
참고로 이중 중괄호 초기화는 다음과 같습니다.
List<String> list = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
Java의 "숨겨진"기능처럼 보이지만 다음을 다시 작성한 것입니다.
List<String> list = new ArrayList<String>() {
// Instance initialization block
{
add("Hello");
add("World!");
}
};
따라서 기본적으로 익명 내부 클래스의 일부인 인스턴스 초기화 블록 입니다 .
Joshua Bloch의 Project Coin 에 대한 Collection Literals 제안 은 다음과 같습니다.
List<Integer> intList = [1, 2, 3, 4];
Set<String> strSet = {"Apple", "Banana", "Cactus"};
Map<String, Integer> truthMap = { "answer" : 42 };
슬프게도, 그것은 그것의 방법을하지 않았다 도 자바 7이나 8로 무기한 보류했다.
실험
여기에 내가 테스트 한 간단한 실험이다 - 메이크업 1000 개 ArrayList
요소와의 "Hello"
와 "World!"
비아 그들에 추가 add
하는 방법, 두 가지 방법을 사용하여이 :
방법 1 : 이중 중괄호 초기화
List<String> l = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
방법 2 : ArrayList
및 인스턴스화add
List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");
두 가지 방법을 사용하여 1000 개의 초기화를 수행하는 Java 소스 파일을 작성하는 간단한 프로그램을 만들었습니다.
테스트 1 :
class Test1 {
public static void main(String[] s) {
long st = System.currentTimeMillis();
List<String> l0 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
List<String> l1 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
/* snip */
List<String> l999 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
System.out.println(System.currentTimeMillis() - st);
}
}
테스트 2 :
class Test2 {
public static void main(String[] s) {
long st = System.currentTimeMillis();
List<String> l0 = new ArrayList<String>();
l0.add("Hello");
l0.add("World!");
List<String> l1 = new ArrayList<String>();
l1.add("Hello");
l1.add("World!");
/* snip */
List<String> l999 = new ArrayList<String>();
l999.add("Hello");
l999.add("World!");
System.out.println(System.currentTimeMillis() - st);
}
}
1000 ArrayList
s 및 1000 익명 내부 클래스 확장 을 초기화하는 데 걸린 시간 ArrayList
은를 사용하여 확인 System.currentTimeMillis
되므로 타이머의 해상도가 매우 높지 않습니다. 내 Windows 시스템에서 해상도는 약 15-16 밀리 초입니다.
두 테스트를 10 회 실행 한 결과는 다음과 같습니다.
Test1 Times (ms) Test2 Times (ms)
---------------- ----------------
187 0
203 0
203 0
188 0
188 0
187 0
203 0
188 0
188 0
203 0
보시다시피 이중 중괄호 초기화는 약 190ms의 눈에 띄는 실행 시간을 갖습니다.
한편 ArrayList
초기화 실행 시간은 0ms로 나왔다. 물론 타이머 분해능을 고려해야하지만 15ms 미만일 가능성이 높습니다.
따라서 두 메서드의 실행 시간에 눈에 띄는 차이가있는 것 같습니다. 실제로 두 초기화 방법에 약간의 오버 헤드가있는 것으로 보입니다.
그리고 예, 이중 중괄호 초기화 테스트 프로그램 .class
을 컴파일하여 생성 된 1000 개의 파일이 Test1
있습니다.
지금까지 지적되지 않은이 접근 방식의 한 가지 속성은 내부 클래스를 생성하기 때문에 전체 포함 클래스가 해당 범위에서 캡처된다는 것입니다. 즉, Set이 살아있는 한 포함하는 인스턴스 ( this$0
)에 대한 포인터를 유지하고 가비지 수집되지 않도록 유지하므로 문제가 될 수 있습니다.
이것과 일반 HashSet이 잘 작동하더라도 (또는 더 나은) 새 클래스가 처음에 생성된다는 사실은이 구조를 사용하고 싶지 않게 만듭니다 (구문 설탕을 정말로 갈망하더라도).
두 번째 질문 : 새 HashSet은 인스턴스 이니셜 라이저에서 사용되는 "this"여야합니다. 누구든지 메커니즘에 대해 밝힐 수 있습니까? 나는 순진하게 "this"가 "flavors"를 초기화하는 객체를 지칭 할 것이라고 예상했었다.
이것이 바로 내부 클래스가 작동하는 방식입니다. 자체를 가져 this
오지만 부모 인스턴스에 대한 포인터도 있으므로 포함하는 객체에서도 메서드를 호출 할 수 있습니다. 명명 충돌의 경우 내부 클래스 (귀하의 경우 HashSet)가 우선권을 갖지만 "this"접두사에 클래스 이름을 붙여 외부 메서드를 가져올 수도 있습니다.
public class Test {
public void add(Object o) {
}
public Set<String> makeSet() {
return new HashSet<String>() {
{
add("hello"); // HashSet
Test.this.add("hello"); // outer instance
}
};
}
}
생성되는 익명 서브 클래스를 명확히하기 위해 거기에 메서드를 정의 할 수도 있습니다. 예를 들어 재정의HashSet.add()
public Set<String> makeSet() {
return new HashSet<String>() {
{
add("hello"); // not HashSet anymore ...
}
@Override
boolean add(String s){
}
};
}
누군가가 이중 중괄호 초기화를 사용할 때마다 새끼 고양이가 죽습니다.
구문이 다소 특이하고 실제로 관용적이지 않은 것 외에도 (물론 맛은 논란의 여지가 있음) 애플리케이션에서 불필요하게 두 가지 중요한 문제를 만들고 있습니다.이 문제는 최근 여기에서 더 자세히 블로그에 올렸습니다 .
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
그것은 당신의 클래스 로더에 대한 상당한 오버 헤드입니다. 물론 한 번만 수행하면 초기화 시간이 많이 걸리지 않습니다. 하지만 엔터프라이즈 애플리케이션 전체에서이 작업을 20,000 번 수행한다면 ... 약간의 "구문 설탕"을위한 모든 힙 메모리?
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"); }};
어떤 사람들은 이것이 구문 적으로 자극적이라고 생각할 수 있습니다.
다음 시험 수업을 들으십시오.
public class Test {
public void test() {
Set<String> flavors = new HashSet<String>() {{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}};
}
}
클래스 파일을 디 컴파일하면 다음과 같이 표시됩니다.
public class Test {
public void test() {
java.util.Set flavors = new HashSet() {
final Test this$0;
{
this$0 = Test.this;
super();
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}
};
}
}
이것은 나에게별로 비효율적으로 보이지 않습니다. 이와 같은 성능에 대해 걱정이된다면 프로파일 링 할 것입니다. 그리고 귀하의 질문 # 2는 위의 코드에 의해 답변됩니다. 내부 클래스에 대한 암시 적 생성자 (및 인스턴스 이니셜 라이저) 내부에 있으므로 " this
"는이 내부 클래스를 나타냅니다.
예,이 구문은 모호하지만 주석은 모호한 구문 사용을 명확히 할 수 있습니다. 구문을 명확히하기 위해 대부분의 사람들은 정적 이니셜 라이저 블록 (JLS 8.7 정적 이니셜 라이저)에 익숙합니다.
public class Sample1 {
private static final String someVar;
static {
String temp = null;
..... // block of code setting temp
someVar = temp;
}
}
static
생성자 사용 (JLS 8.6 인스턴스 이니셜 라이저)에 대해 유사한 구문 ( " "제외)을 사용할 수도 있지만, 프로덕션 코드에서 사용되는 것을 본 적이 없습니다. 이것은 일반적으로 덜 알려져 있습니다.
public class Sample2 {
private final String someVar;
// This is an instance initializer
{
String temp = null;
..... // block of code setting temp
someVar = temp;
}
}
당신은 기본 생성자 사이의 코드의 다음 블록이없는 경우 {
와 }
컴파일러에 의해 생성자로 설정되어 있습니다. 이를 염두에두고 이중 중괄호 코드를 푸십시오.
public void test() {
Set<String> flavors = new HashSet<String>() {
{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}
};
}
가장 안쪽 중괄호 사이의 코드 블록은 컴파일러에 의해 생성자로 바뀝니다. 가장 바깥 쪽 중괄호는 익명의 내부 클래스를 구분합니다. 모든 것을 익명으로 만드는 마지막 단계를 수행하려면 :
public void test() {
Set<String> flavors = new MyHashSet();
}
class MyHashSet extends HashSet<String>() {
public MyHashSet() {
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}
}
초기화를 위해 오버 헤드가 전혀 없다고 말하고 싶습니다 (또는 무시할 수있을 정도로 너무 작음). 그러나의 모든 사용은 flavors
반대하는 HashSet
것이 아니라 반대하는 것 MyHashSet
입니다. 여기에는 약간의 오버 헤드가있을 수 있습니다. 하지만 다시 한 번 걱정하기 전에 프로파일 링하겠습니다.
다시 질문 # 2에 대해 위의 코드는 이중 중괄호 초기화와 논리적이고 명시 적으로 동일하며 " this
"가 참조하는 위치를 명확하게합니다 HashSet
..
인스턴스 이니셜 라이저의 세부 사항에 대한 질문이있는 경우 JLS 문서 의 세부 사항을 확인하십시오 .
누출되기 쉬운
성능에 미치는 영향에는 디스크 작업 + 압축 해제 (jar 용), 클래스 확인, perm-gen 공간 (Sun의 Hotspot JVM 용)이 포함됩니다. 그러나 최악의 경우 누출이 발생하기 쉽습니다. 단순히 돌아올 수는 없습니다.
Set<String> getFlavors(){
return Collections.unmodifiableSet(flavors)
}
따라서 집합이 다른 클래스 로더에 의해로드 된 다른 부분으로 이스케이프되고 참조가 거기에 유지되면 클래스 + 클래스 로더의 전체 트리가 누출됩니다. 이를 방지하려면 HashMap에 대한 사본이 필요 new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}})
합니다. 더 이상 귀엽지 않습니다. 나는 관용구를 사용하지 않고 대신new LinkedHashSet(Arrays.asList("xxx","YYY"));
많은 클래스를로드하면 시작하는 데 몇 밀리 초가 추가 될 수 있습니다. 시작이 그렇게 중요하지 않고 시작 후 클래스의 효율성을 살펴보면 차이가 없습니다.
package vanilla.java.perfeg.doublebracket;
import java.util.*;
/**
* @author plawrey
*/
public class DoubleBracketMain {
public static void main(String... args) {
final List<String> list1 = new ArrayList<String>() {
{
add("Hello");
add("World");
add("!!!");
}
};
List<String> list2 = new ArrayList<String>(list1);
Set<String> set1 = new LinkedHashSet<String>() {
{
addAll(list1);
}
};
Set<String> set2 = new LinkedHashSet<String>();
set2.addAll(list1);
Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
{
put(1, "one");
put(2, "two");
put(3, "three");
}
};
Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
map2.putAll(map1);
for (int i = 0; i < 10; i++) {
long dbTimes = timeComparison(list1, list1)
+ timeComparison(set1, set1)
+ timeComparison(map1.keySet(), map1.keySet())
+ timeComparison(map1.values(), map1.values());
long times = timeComparison(list2, list2)
+ timeComparison(set2, set2)
+ timeComparison(map2.keySet(), map2.keySet())
+ timeComparison(map2.values(), map2.values());
if (i > 0)
System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
}
}
public static long timeComparison(Collection a, Collection b) {
long start = System.nanoTime();
int runs = 10000000;
for (int i = 0; i < runs; i++)
compareCollections(a, b);
long rate = (System.nanoTime() - start) / runs;
return rate;
}
public static void compareCollections(Collection a, Collection b) {
if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
throw new AssertionError();
}
}
인쇄물
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
집합을 만들려면 이중 중괄호 초기화 대신 varargs 팩토리 메서드를 사용할 수 있습니다.
public static Set<T> setOf(T ... elements) {
return new HashSet<T>(Arrays.asList(elements));
}
Google Collections 라이브러리에는 이와 같은 많은 편의 방법과 기타 유용한 기능이 많이 있습니다.
관용구의 모호성은 항상 접하고 프로덕션 코드에서 사용합니다. 프로덕션 코드를 작성할 수있는 관용구 때문에 혼란스러워하는 프로그래머가 더 걱정됩니다.
효율성을 제외하고는 단위 테스트 이외의 선언적 컬렉션 생성을 원하는 경우는 거의 없습니다. 이중 중괄호 구문은 매우 읽기 쉽다고 생각합니다.
목록의 선언적 구성을 달성하는 또 다른 방법은 Arrays.asList(T ...)
다음과 같이 사용하는 것입니다.
List<String> aList = Arrays.asList("vanilla", "strawberry", "chocolate");
물론이 방법의 한계는 생성 할 특정 유형의 목록을 제어 할 수 없다는 것입니다.
일반적으로 특별히 비효율적 인 것은 없습니다. 일반적으로 JVM에 하위 클래스를 만들고 생성자를 추가했는지는 중요하지 않습니다. 이것은 객체 지향 언어에서 수행하는 일상적인 일입니다. 이렇게하면 비 효율성을 유발할 수있는 아주 인위적인 경우를 생각할 수 있습니다 (예를 들어이 하위 클래스로 인해 다른 클래스를 혼합하게되는 반복적으로 호출되는 메서드가있는 반면, 전달 된 일반 클래스는 완전히 예측 가능합니다. -후자의 경우 JIT 컴파일러는 첫 번째에서는 실행 불가능한 최적화를 수행 할 수 있습니다. 하지만 정말 중요한 경우는 매우 인위적이라고 생각합니다.
많은 익명의 클래스로 "일을 정리"할 것인지 여부의 관점에서 문제를 더 많이 볼 것입니다. 대략적인 가이드로 이벤트 핸들러에 대해 익명 클래스를 사용하는 것 이상으로 관용구를 사용하는 것을 고려하십시오.
(2)에서 여러분은 객체의 생성자 내부에 있으므로 "this"는 생성중인 객체를 나타냅니다. 다른 생성자와 다르지 않습니다.
(3)의 경우 코드를 유지 관리하는 사람에 따라 다릅니다. 이 사실을 미리 알지 못한다면 제가 제안하는 벤치 마크는 "JDK에 대한 소스 코드에서 이것을 보십니까?"입니다. (이 경우, 익명의 이니셜 라이저를 많이 본 기억이 없으며, 이것이 익명 클래스 의 유일한 내용 인 경우에는 확실히 아닙니다 ). 대부분의 적당한 크기의 프로젝트에서는 어떤 시점에서든 JDK 소스를 이해하기 위해 프로그래머가 정말로 필요하다고 생각합니다. 그래서 거기에서 사용되는 구문이나 관용구는 "공정한 게임"입니다. 그 외에도 코드를 유지 관리하는 사람을 제어 할 수있는 경우 해당 구문에 대해 사람들을 교육하고, 그렇지 않으면 주석을 달거나 피해야합니다.
이중 중괄호 초기화는 메모리 누수 및 기타 문제를 일으킬 수있는 불필요한 해킹입니다.
이 "속임수"를 사용할 정당한 이유는 없습니다. Guava는 정적 팩토리와 빌더를 모두 포함하는 변경 불가능한 멋진 컬렉션 을 제공 하므로 깨끗하고 읽기 쉽고 안전한 구문으로 선언 된 컬렉션을 채울 수 있습니다 .
질문의 예는 다음과 같습니다.
Set<String> flavors = ImmutableSet.of(
"vanilla", "strawberry", "chocolate", "butter pecan");
이것은 더 짧고 읽기 쉬울뿐만 아니라 다른 답변에 설명 된 이중 중괄호 패턴으로 많은 문제를 피할 수 있습니다 . 물론, 직접 생성 된과 유사하게 수행 HashMap
되지만 위험하고 오류가 발생하기 쉬우 며 더 나은 옵션이 있습니다.
이중 중괄호 초기화를 고려할 때마다 구문 트릭을 활용하는 대신 API를 다시 검사하거나 문제를 적절하게 해결하기 위해 새로운 API를 도입 해야합니다 .
Error-Prone은 이제이 anti-pattern 플래그를 지정합니다 .
나는 이것을 조사하고 있고 유효한 답변이 제공하는 것보다 더 심층적 인 테스트를하기로 결정했습니다.
다음은 코드입니다 : https://gist.github.com/4368924
그리고 이것은 내 결론입니다
대부분의 실행 테스트에서 내부 시작이 실제로 더 빠르다는 사실에 놀랐습니다 (경우에 따라 거의 두 배). 많은 수로 작업 할 때 이점이 사라지는 것처럼 보입니다.
흥미롭게도 루프에 3 개의 객체를 생성하는 경우는 다른 경우보다 더 빨리 혜택이 사라집니다. 왜 이런 일이 발생하는지 잘 모르겠으며 결론에 도달하기 위해 더 많은 테스트를 수행해야합니다. 구체적인 구현을 생성하면 클래스 정의가 다시로드되는 것을 피하는 데 도움이 될 수 있습니다.
그러나 많은 수의 경우에도 단일 항목 건물의 경우 대부분의 경우 오버 헤드가 많지 않음이 분명합니다.
한 가지 단점은 이중 중괄호 시작 각각이 전체 디스크 블록을 응용 프로그램의 크기 (압축시 약 1k)에 추가하는 새 클래스 파일을 생성한다는 사실입니다. 작은 설치 공간이지만 여러 곳에서 사용하면 잠재적으로 영향을 미칠 수 있습니다. 이것을 1000 번 사용하면 잠재적으로 전체 MiB를 응용 프로그램에 추가 할 수 있습니다. 이는 임베디드 환경에서 문제가 될 수 있습니다.
내 결론? 남용되지 않는 한 사용해도 괜찮습니다.
당신이 무슨 생각을하는지 제게 알려주세요 :)
나는 두 번째 Nat의 대답을 제외하고는 asList (elements)에서 암시 적 목록을 만들고 즉시 던지는 대신 루프를 사용하는 것입니다.
static public Set<T> setOf(T ... elements) {
Set set=new HashSet<T>(elements.size());
for(T elm: elements) { set.add(elm); }
return set;
}
이 구문은 편리 할 수 있지만이 $ 0 참조가 중첩되기 때문에 많은 this $ 0 참조를 추가하고 각각에 중단 점이 설정되지 않는 한 이니셜 라이저로 디버그 단계를 진행하기 어려울 수 있습니다. 이런 이유로 나는 평범한 setter, 특히 상수로 설정된 곳과 익명의 하위 클래스가 중요하지 않은 장소 (직렬화가 관여하지 않는 것과 같이)에만 이것을 사용하는 것이 좋습니다.
Mario Gleichman 은 Java 1.5 일반 함수를 사용하여 Scala List 리터럴을 시뮬레이션하는 방법을 설명 합니다.하지만 슬프게도 불변의 목록이 생깁니다.
그는이 클래스를 정의합니다.
package literal;
public class collection {
public static <T> List<T> List(T...elems){
return Arrays.asList( elems );
}
}
다음과 같이 사용합니다.
import static literal.collection.List;
import static system.io.*;
public class CollectionDemo {
public void demoList(){
List<String> slist = List( "a", "b", "c" );
List<Integer> iList = List( 1, 2, 3 );
for( String elem : List( "a", "java", "list" ) )
System.out.println( elem );
}
}
이제 Guava의 일부인 Google Collections 는 목록 생성에 대한 유사한 아이디어를 지원합니다. 에서 인터뷰 , 제라드 레비는 말한다 :
[...] 내가 작성하는 거의 모든 Java 클래스에 나타나는 가장 많이 사용되는 기능은 Java 코드에서 반복적 인 키 입력 횟수를 줄이는 정적 메서드입니다. 다음과 같은 명령을 입력하는 것이 매우 편리합니다.
Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();
List<String> animals = Lists.immutableList("cat", "dog", "horse");
2014 년 7 월 10 일 : 파이썬처럼 간단 할 수만 있다면 :
animals = ['cat', 'dog', 'horse']
이것은
add()
각 구성원 을 호출 합니다. 항목을 해시 세트에 넣는 더 효율적인 방법을 찾을 수 있다면 그것을 사용하십시오. 당신이 그것에 대해 민감한 경우 내부 클래스가 쓰레기를 생성 할 가능성이 있습니다.문맥에 의해 반환되는 객체 인 것처럼 그것은 나에게 보인다
new
는 IS,HashSet
.질문이 필요한 경우 ... 더 가능성이 높습니다. 당신을 쫓는 사람들이 이것을 알겠습니까? 이해하고 설명하기 쉬운가요? 둘 다 "예"라고 대답 할 수 있으면 자유롭게 사용하십시오.
참고 URL : https://stackoverflow.com/questions/924285/efficiency-of-java-double-brace-initialization
'code' 카테고리의 다른 글
비어 있지 않은 폴더를 제거 / 삭제하려면 어떻게합니까? (0) | 2020.09.29 |
---|---|
글로벌 Git 무시 (0) | 2020.09.29 |
{this.props.children}에 소품을 전달하는 방법 (0) | 2020.09.29 |
IMG와 CSS 배경 이미지는 언제 사용합니까? (0) | 2020.09.29 |
GitHub에서 직접 npm 패키지를 설치하는 방법은 무엇입니까? (0) | 2020.09.29 |