생성자에서 재정의 가능한 메서드 호출의 문제점은 무엇입니까?
추상 메소드의 결과에 따라 페이지 제목을 설정하는 Wicket 페이지 클래스가 있습니다.
public abstract class BasicPage extends WebPage {
public BasicPage() {
add(new Label("title", getTitle()));
}
protected abstract String getTitle();
}
NetBeans는 "생성자에서 재정의 가능한 메소드 호출"이라는 메시지를 경고하지만 무엇이 잘못 되었습니까? 내가 상상할 수있는 유일한 대안은 추상 메소드의 결과를 서브 클래스의 수퍼 생성자에게 전달하는 것입니다. 그러나 많은 매개 변수로 읽기가 어려울 수 있습니다.
생성자에서 재정의 가능한 메서드를 호출 할 때
간단히 말해서, 이것은 많은 버그에 대한 가능성을 불필요하게 열어주기 때문에 잘못되었습니다 . @Override
이 호출 되면 객체의 상태가 일치하지 않거나 불완전 할 수 있습니다.
Effective Java 2nd Edition, 항목 17 : 상속을위한 디자인 및 문서 의 인용문 또는 기타 금지 사항 :
상속을 허용하기 위해 클래스가 준수해야하는 몇 가지 제한 사항이 더 있습니다. 생성자는 재정의 가능한 메소드 를 직접 또는 간접적으로 호출해서는 안됩니다 . 이 규칙을 위반하면 프로그램 오류가 발생합니다. 수퍼 클래스 생성자는 서브 클래스 생성자보다 먼저 실행되므로 서브 클래스 생성자가 실행되기 전에 서브 클래스의 재정의 메소드가 호출됩니다. 재정의 메소드가 서브 클래스 생성자가 수행 한 초기화에 의존하는 경우 메소드가 예상대로 작동하지 않습니다.
다음은 설명하는 예입니다.
public class ConstructorCallsOverride {
public static void main(String[] args) {
abstract class Base {
Base() {
overrideMe();
}
abstract void overrideMe();
}
class Child extends Base {
final int x;
Child(int x) {
this.x = x;
}
@Override
void overrideMe() {
System.out.println(x);
}
}
new Child(42); // prints "0"
}
}
때 여기에, Base
생성자 호출이 overrideMe
, Child
초기화 완료되지 않았 final int x
및 방법은 잘못된 값을 가져옵니다. 이것은 거의 확실히 버그와 오류로 이어질 것입니다.
관련 질문
- 부모 클래스 생성자에서 재정의 된 메서드 호출
- 기본 클래스 생성자가 Java에서 재정의 된 메서드를 호출 할 때 파생 클래스 개체의 상태
- 추상 클래스 생성자에서 추상 init () 함수 사용
또한보십시오
매개 변수가 많은 객체 구성
매개 변수가 많은 생성자는 가독성이 떨어지고 더 나은 대안이 존재합니다.
다음은 Effective Java 2nd Edition, Item 2 : 의 인용문 입니다. 많은 생성자 매개 변수가있는 경우 빌더 패턴을 고려하십시오 .
전통적으로 프로그래머는 텔레 스코핑 생성자 패턴을 사용했습니다. 여기서 생성자는 필수 매개 변수 만 사용하고 다른 하나는 단일 선택적 매개 변수를 사용하고 다른 하나는 두 개의 선택적 매개 변수를 사용합니다.
텔레 스코핑 생성자 패턴은 본질적으로 다음과 같습니다.
public class Telescope {
final String name;
final int levels;
final boolean isAdjustable;
public Telescope(String name) {
this(name, 5);
}
public Telescope(String name, int levels) {
this(name, levels, false);
}
public Telescope(String name, int levels, boolean isAdjustable) {
this.name = name;
this.levels = levels;
this.isAdjustable = isAdjustable;
}
}
이제 다음 중 하나를 수행 할 수 있습니다.
new Telescope("X/1999");
new Telescope("X/1999", 13);
new Telescope("X/1999", 13, true);
그러나 현재 name
and 만 설정할 수 없으며 기본값을 isAdjustable
그대로 둡니다 levels
. 당신은 더 생성자 오버로드를 제공 할 수 있지만 매개 변수의 수가 증가함에 따라 분명 수는 폭발 할 것이고, 당신도 다수있을 수 있습니다 boolean
와 int
정말 일에서 엉망으로 만들 것 인수를.
보시다시피, 이것은 작성하기에 유쾌한 패턴이 아니며 사용하기에 훨씬 유쾌하지 않습니다 ( "true"는 무엇을 의미합니까? 13은 무엇입니까?).
Bloch는 다음과 같이 작성할 수있는 빌더 패턴 사용을 권장합니다.
Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();
이제 매개 변수의 이름이 지정되었으며 원하는 순서로 설정할 수 있으며 기본값으로 유지하려는 매개 변수는 건너 뛸 수 있습니다. 이는 특히 같은 유형의 많은 매개 변수가 많은 경우 텔레 스코핑 생성자보다 훨씬 낫습니다.
또한보십시오
- 위키 백과 / 빌더 패턴
- 효과적인 Java 2 판, 항목 2 : 많은 생성자 매개 변수에 직면 할 때 빌더 패턴 고려 ( 온라인 발췌 )
관련 질문
다음은이를 이해하는 데 도움이되는 예입니다.
public class Main {
static abstract class A {
abstract void foo();
A() {
System.out.println("Constructing A");
foo();
}
}
static class C extends A {
C() {
System.out.println("Constructing C");
}
void foo() {
System.out.println("Using C");
}
}
public static void main(String[] args) {
C c = new C();
}
}
이 코드를 실행하면 다음과 같은 결과가 나타납니다.
Constructing A
Using C
Constructing C
알 겠어? foo()
C의 생성자가 실행되기 전에 C를 사용합니다. foo()
C에 정의 된 상태가 있어야하는 경우 (예 : 생성자 가 finished ) C에서 정의되지 않은 상태가 발생하여 문제가 발생할 수 있습니다. 그리고 A에서 덮어 쓴 내용을 알 수 없으므로 foo()
경고가 표시됩니다.
생성자에서 재정의 가능한 메서드를 호출하면 하위 클래스가 코드를 전복시킬 수 있으므로 더 이상 작동하지 않을 수 있습니다. 그렇기 때문에 경고가 나타납니다.
예제에서, 서브 클래스가 재정의 getTitle()
하고 null을 반환 하면 어떻게됩니까 ?
이를 "수정"하기 위해 생성자 대신 팩토리 메소드 를 사용할 수 있습니다. 이는 일반적인 오브젝트 인스턴스화 패턴입니다.
다음은 수퍼 생성자에서 재정의 가능한 메서드를 호출 할 때 발생할 수있는 논리적 문제 를 보여주는 예입니다 .
class A {
protected int minWeeklySalary;
protected int maxWeeklySalary;
protected static final int MIN = 1000;
protected static final int MAX = 2000;
public A() {
setSalaryRange();
}
protected void setSalaryRange() {
throw new RuntimeException("not implemented");
}
public void pr() {
System.out.println("minWeeklySalary: " + minWeeklySalary);
System.out.println("maxWeeklySalary: " + maxWeeklySalary);
}
}
class B extends A {
private int factor = 1;
public B(int _factor) {
this.factor = _factor;
}
@Override
protected void setSalaryRange() {
this.minWeeklySalary = MIN * this.factor;
this.maxWeeklySalary = MAX * this.factor;
}
}
public static void main(String[] args) {
B b = new B(2);
b.pr();
}
결과는 실제로 다음과 같습니다.
minWeeklySalary : 0
maxWeeklySalary : 0
클래스 B의 생성자가 먼저 클래스 A의 생성자를 호출하기 때문에 B 내의 재정의 가능한 메소드가 실행되기 때문입니다. 그러나 메소드 내부에서 아직 초기화되지 않은 인스턴스 변수 인자 를 사용하고 있습니다 (A의 생성자가 아직 완료되지 않았기 때문에) 있다). 계산 논리가 10 배 더 왜곡 된 경우 오류를 추적하기가 얼마나 어려울 지 상상해보십시오.
나는 그것이 누군가를 도울 수 있기를 바랍니다.
하위 클래스 재정의에서 생성자에서 메서드를 호출하면 생성자와 메서드간에 논리적으로 초기화를 나누면 아직 존재하지 않는 변수를 참조 할 가능성이 줄어 듭니다.
이 샘플 링크 http://www.javapractices.com/topic/TopicAction.do?Id=215를 살펴보십시오 .
Wicket의 특정 경우 : 이것이 Wicket 개발자에게 구성 요소의 구성 수명주기에서 명시 적 2 단계 구성 요소 초기화 프로세스에 대한 지원을 추가하도록 요청한 이유입니다.
- 생성자-생성자를 통해
- 초기화-onInitilize를 통해 (가상 메소드가 작동 할 때 생성 후!)
이 링크가 http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize-를 보여 주므로 필요한지 아닌지에 대한 활발한 논쟁이있었습니다 is-broken-for-Pages-td3341090i20.html )
좋은 소식은 Wicket의 우수한 개발자가 2 단계 초기화를 도입했기 때문에 (가장 멋진 Java UI 프레임 워크를 훨씬 더 훌륭하게 만들었습니다!) Wicket을 사용하면 onInitialize 메소드에서 프레임 워크를 재정의하면 자동으로 프레임 워크-컴포넌트의 라이프 사이클에서이 시점에서 생성자가 작업을 완료하여 가상 메소드가 예상대로 작동합니다.
Wicket의 경우 add
메소드 를 호출 하는 것이 좋습니다 onInitialize()
( 구성 요소 수명주기 참조 ).
public abstract class BasicPage extends WebPage {
public BasicPage() {
}
@Override
public void onInitialize() {
add(new Label("title", getTitle()));
}
protected abstract String getTitle();
}
'Programming' 카테고리의 다른 글
트위터 부트 스트랩 3 : 미디어 쿼리를 사용하는 방법? (0) | 2020.02.27 |
---|---|
세계에서 봄 콩은 무엇입니까? (0) | 2020.02.27 |
SQL 데이터베이스 테이블에서 n 번째 행을 선택하는 방법은 무엇입니까? (0) | 2020.02.27 |
OpenCV-Python의 간단한 숫자 인식 OCR (0) | 2020.02.27 |
SQL Server 프로파일 러-한 데이터베이스의 이벤트 만 표시하도록 추적을 필터링하는 방법은 무엇입니까? (0) | 2020.02.27 |