나는 최근에 본 적이없는 Java 구조를 발견했으며 사용 해야하는지 궁금합니다. 이니셜 라이저 블록 이라고 합니다 .
public class Test {
public Test() { /* first constructor */ }
public Test(String s) { /* second constructor */ }
// Non-static initializer block - copied into every constructor:
{
doStuff();
}
}
코드 블록은 각 생성자에 복사됩니다. 즉, 생성자가 여러 개인 경우 코드를 다시 작성할 필요가 없습니다.
그러나이 구문을 사용하는 세 가지 주요 단점이 있습니다.
- 여러 코드 블록을 정의 할 수 있으며 작성된 순서대로 실행되므로 코드 순서가 중요한 Java에서는 매우 드문 경우 중 하나입니다. 단순히 코드 블록의 순서를 변경하면 실제로 코드가 변경되므로 나에게 해로운 것 같습니다.
- 나는 그것을 사용함으로써 어떤 이점도 보지 못한다. 대부분의 경우 생성자는 미리 정의 된 값으로 서로를 호출합니다. 그렇지 않은 경우에도 코드를 개인용 메서드에 넣고 각 생성자에서 호출 할 수 있습니다.
- 클래스의 끝에 블록을 넣을 수 있고 생성자는 일반적으로 클래스의 시작에 있으므로 가독성이 떨어집니다. 필요하지 않다면 코드 파일의 완전히 다른 부분을 보는 것은 직관적이지 않습니다.
위의 진술이 사실이라면 왜이 언어 구성이 도입 되었는가? 합법적 인 사용 사례가 있습니까?
답변
초기화 블록을 사용하는 두 가지 경우가 있습니다.
첫 번째는 최종 멤버를 초기화하는 것입니다. Java에서는 선언과 함께 인라인으로 최종 멤버를 초기화하거나 생성자에서 초기화 할 수 있습니다. 방법에서는 최종 멤버에게 할당하는 것이 금지됩니다.
유효합니다 :
final int val = 2;
이것도 유효합니다 :
final int val;
MyClass() {
val = 2;
}
이것은 유효하지 않습니다 :
final int val;
MyClass() {
init();
}
void init() {
val = 2; // cannot assign to 'final' field in a method
}
여러 생성자가 있고 초기화 논리가 너무 복잡하여 최종 멤버 인라인을 초기화 할 수 없거나 생성자가 자신을 호출 할 수없는 경우 초기화 코드를 복사 / 붙여 넣거나 사용할 수 있습니다 이니셜 라이저 블록.
final int val;
final int squareVal;
MyClass(int v, String s) {
this.val = v;
this.s = s;
}
MyClass(Point p, long id) {
this.val = p.x;
this.id = id;
}
{
squareVal = val * val;
}
초기화 블록에 대한 다른 유스 케이스는 작은 도우미 데이터 구조를 작성하는 것입니다. 멤버를 선언하고 선언 후 바로 이니셜 라이저 블록에 값을 넣습니다.
private Map<String, String> days = new HashMap<String, String>();
{
days.put("mon", "monday");
days.put("tue", "tuesday");
days.put("wed", "wednesday");
days.put("thu", "thursday");
days.put("fri", "friday");
days.put("sat", "saturday");
days.put("sun", "sunday");
}
답변
일반적으로 비 정적 초기화 블록을 사용하지 마십시오 (정적 블록도 피하십시오).
혼란스러운 구문
이 질문을 보면 3 가지 답변이 있지만이 구문으로 4 명을 속였습니다. 나는 그들 중 하나였으며 16 년 동안 Java를 작성해 왔습니다! 분명히 구문은 오류가 발생하기 쉽습니다! 나는 멀리 떨어져있을 것입니다.
텔레 스코핑 생성자
정말 간단한 것들을 위해, 당신은이 혼란을 피하기 위해 “telescoping”생성자를 사용할 수 있습니다 :
public class Test {
private String something;
// Default constructor does some things
public Test() { doStuff(); }
// Other constructors call the default constructor
public Test(String s) {
this(); // Call default constructor
something = s;
}
}
빌더 패턴
각 생성자 또는 기타 복잡한 초기화가 끝날 때 doStuff ()를 수행해야하는 경우 빌더 패턴이 가장 좋습니다. Josh Bloch 는 빌더가 좋은 아이디어 인 몇 가지 이유를 나열합니다. 빌더는 작성하는 데 약간의 시간이 걸리지 만 올바르게 작성되었으므로 사용하는 것이 좋습니다.
public class Test {
// Value can be final (immutable)
private final String something;
// Private constructor.
private Test(String s) { something = s; }
// Static method to get a builder
public static Builder builder() { return new Builder(); }
// builder class accumulates values until a valid Test object can be created.
private static class Builder {
private String tempSomething;
public Builder something(String s) {
tempSomething = s;
return this;
}
// This is our factory method for a Test class.
public Test build() {
Test t = new Test(tempSomething);
// Here we do your extra initialization after the
// Test class has been created.
doStuff();
// Return a valid, potentially immutable Test object.
return t;
}
}
}
// Now you can call:
Test t = Test.builder()
.setString("Utini!")
.build();
정적 이니셜 라이저 루프
나는 정적 이니셜 라이저를 많이 사용 했지만 때로는 클래스가 완전히로드되기 전에 두 개의 클래스가 서로의 정적 이니셜 라이저 블록에 의존하는 루프가 발생했습니다. 이로 인해 “클래스를로드하지 못했습니다”또는 유사하게 모호한 오류 메시지가 나타납니다. 문제가 무엇인지 파악하기 위해 소스 컨트롤에서 마지막으로 알려진 작동 버전과 파일을 비교해야했습니다. 전혀 재미 없다.
게으른 초기화
정적 이니셜 라이저는 작동하기 때문에 성능상의 이유로 좋으며 너무 혼란스럽지 않을 수 있습니다. 그러나 일반적으로 요즘에는 정적 초기화 프로그램 보다 게으른 초기화 를 선호 합니다. 그들이하는 일이 분명하고, 아직 클래스 로딩 버그에 빠지지 않았으며 초기화 블록보다 더 많은 초기화 상황에서 작동합니다.
데이터 정의
데이터 구조를 만들기위한 정적 초기화 (다른 답변의 예제와 비교) 대신 Paguro의 불변 데이터 정의 도우미 함수를 사용합니다 .
private ImMap<String,String> days =
map(tup("mon", "monday"),
tup("tue", "tuesday"),
tup("wed", "wednesday"),
tup("thu", "thursday"),
tup("fri", "friday"),
tup("sat", "saturday"),
tup("sun", "sunday"));
추방
Java의 초기에는 이니셜 라이저 블록이 일부 작업을 수행하는 유일한 방법 이었지만 지금은 혼란스럽고 오류가 발생하기 쉬우 며 대부분의 경우 더 나은 대안으로 대체되었습니다 (위에서 자세히 설명). 레거시 코드에서 볼 수 있거나 테스트 중일 때 초기화 블록에 대해 아는 것이 흥미 롭습니다.하지만 코드 검토를 수행하고 새 코드에서 하나를 보았을 때 위의 대안은 코드에 엄지 손가락을 넣기 전에 적합했습니다.
답변
final
( barjak ‘s answer 참조 )로 선언 된 인스턴스 변수의 초기화 외에도 static
초기화 블록에 대해서도 언급 합니다.
그것들을 일종의 “정적 구성자 (static contructor)”로 사용할 수 있습니다.
이렇게하면 클래스를 처음 참조 할 때 정적 변수에서 복잡한 초기화를 수행 할 수 있습니다.
다음은 barjak의 예제에서 영감을 얻은 예입니다.
public class dayHelper(){
private static Map<String, String> days = new HashMap<String, String>();
static {
days.put("mon", "monday");
days.put("tue", "tuesday");
days.put("wed", "wednesday");
days.put("thu", "thursday");
days.put("fri", "friday");
days.put("sat", "saturday");
days.put("sun", "sunday");
}
public static String getLongName(String shortName){
return days.get(shortName);
}
}
답변
비 정적 이니셜 라이저 블록과 관련하여 익명의 클래스에서 기본 생성자로 작동하는 것이 가장 중요합니다. 그것이 기본적으로 존재하는 유일한 권리입니다.
답변
나는 진술 1, 2, 3에 전적으로 동의합니다. 나는 또한 이러한 이유로 블록 초기화 프로그램을 사용하지 않으며 왜 Java에 존재하는지 모르겠습니다.
그러나 한 가지 경우 에는 정적 블록 이니셜 라이저 를 사용해야합니다 : 생성자가 확인 된 예외를 throw 할 수있는 정적 필드를 인스턴스화해야 할 때.
private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile
그러나 대신해야합니다 :
private static JAXBContext context;
static {
try
{
context = JAXBContext.newInstance(Foo.class);
}
catch (JAXBException e)
{
//seriously...
}
}
나는 (그것은 또한 마크를 방지 매우 추한이 관용구를 찾을 수 context
등 final
) 그러나 이것은이 유일한 방법 등의 필드를 초기화하는 자바에 의해 지원됩니다.