추상 필드가 아닌 이유는 무엇입니까?


102

Java 클래스는 추상 메서드를 가질 수있는 것처럼 추상 필드를 가질 수없는 이유는 무엇입니까?

예 : 동일한 추상 기본 클래스를 확장하는 두 개의 클래스가 있습니다. 이 두 클래스는 각각 오류 메시지 인 String 상수를 제외하고 동일한 메서드를 포함합니다. 필드가 추상 일 수 있다면이 상수 추상을 만들고 메서드를 기본 클래스로 끌어 올 수 있습니다. 대신 getErrMsg()이 경우 호출되는 추상 메서드를 만들어야합니다. 이 메서드는 String을 반환하고 두 파생 클래스에서이 메서드를 재정의 한 다음 메서드 (이제 추상 메서드를 호출 함)를 가져올 수 있습니다.

필드를 추상적으로 시작할 수없는 이유는 무엇입니까? Java가이를 허용하도록 설계되었을 수 있습니까?


3
필드를 상수가 아닌 것으로 만들고 생성자를 통해 값을 제공하여 2 개의 클래스가 아닌 2 개의 클래스 인스턴스로 끝남으로써이 전체 문제를 피할 수 있었던 것처럼 들립니다.
Nate

5
수퍼 클래스에서 필드를 추상화하면 모든 하위 클래스가이 필드를 가져야한다는 것을 명시 할 수 있으므로 이는 비추 상 필드와 다르지 않습니다.
Peter Lawrey 2010

@ 피터, 내가 당신의 요점을 따르고 있는지 잘 모르겠습니다. 추상 클래스에 비추 상 상수가 지정된 경우 그 값은 모든 하위 클래스에서도 일정합니다. 추상적 인 경우 해당 값은 각 하위 클래스에 의해 구현 / 공급되어야합니다. 그래서 전혀 같지 않을 것입니다.
liltitus27

1
@ liltitus27 3.5 년 전의 내 요점은 추상 필드를 갖는 것이 인터페이스 사용자를 구현에서 분리한다는 전체 아이디어를 깨뜨리는 것 외에는 크게 변하지 않을 것이라고 생각합니다.
Peter Lawrey 2013

이것은 자식 클래스에서 사용자 정의 필드 주석을 허용 할 수 있기 때문에 도움이 될 것입니다
geg

답변:


104

생성자 (unested 코드)에서 초기화 된 추상 클래스의 최종 필드를 사용하여 설명한 작업을 수행 할 수 있습니다.

abstract class Base {

    final String errMsg;

    Base(String msg) {
        errMsg = msg;
    }

    abstract String doSomething();
}

class Sub extends Base {

    Sub() {
        super("Sub message");
    }

    String doSomething() {

        return errMsg + " from something";
    }
}

자식 클래스가 슈퍼 생성자를 통해 최종 초기화를 "잊으면"컴파일러는 추상 메서드가 구현되지 않은 것처럼 경고를 표시 합니다.


여기에 그것을 시도 할 편집자가 없지만 이것이 내가 게시 할 것입니다 ..
Tim

6
"컴파일러가 경고를 표시합니다". 실제로 Child 생성자는 존재하지 않는 noargs 생성자를 사용하려고 할 것이며 이는 경고가 아닌 컴파일 오류입니다.
Stephen C

그리고 @Stephen C의 코멘트에 "추상 메서드가 구현되지 않은 경우와 같이"라고 추가하는 것도 경고가 아니라 오류입니다.
Laurence Gonsalves

4
좋은 대답-이것은 나를 위해 일했습니다. 게시 된 지 오래되었지만 Base선언해야 한다고 생각 합니다 abstract.
Steve Chambers

2
이 방법은 생성자에 추가 인수를 추가하기 때문에 여전히이 방법을 싫어합니다. 네트워크 프로그래밍에서 나는 각각의 새 메시지가 추상 유형을 구현하지만 AcceptMessage의 경우 'A', LoginMessage의 경우 'L'과 같은 고유 한 지정 문자를 가져야하는 메시지 유형을 만드는 것을 좋아합니다. 이를 통해 사용자 지정 메시지 프로토콜이이 구별 문자를 유선에 배치 할 수 있습니다. 이들은 클래스에 암시 적이므로 생성자에 전달되는 것이 아니라 필드로 제공하는 것이 가장 좋습니다.
Adam Hughes

7

나는 그것에 대해 아무 의미가 없다. 함수를 추상 클래스로 이동하고 일부 보호 된 필드를 재정의 할 수 있습니다. 이것이 상수와 함께 작동하는지 모르겠지만 효과는 동일합니다.

public abstract class Abstract {
    protected String errorMsg = "";

    public String getErrMsg() {
        return this.errorMsg;
    }
}

public class Foo extends Abstract {
    public Foo() {
       this.errorMsg = "Foo";
    }

}

public class Bar extends Abstract {
    public Bar() {
       this.errorMsg = "Bar";
    }
}

그래서 당신의 요점은 errorMsg하위 클래스에서 구현 / 재정의 / 무엇이든 강제하고 싶다는 것입니다 . 나는 당신이 기본 클래스에 메소드를 갖고 싶었고 그 필드를 다루는 방법을 몰랐다고 생각했습니다.


4
물론 할 수 있습니다. 그리고 나는 그것을 생각했습니다. 하지만 모든 파생 클래스에서 해당 값을 재정의 할 것이라는 사실을 알고 있는데 왜 기본 클래스에 값을 설정해야합니까? 귀하의 주장은 또한 추상 메서드를 허용하는 데 가치가 없다고 주장하는 데 사용될 수 있습니다.
Paul Reiners

1
이렇게하면 모든 하위 클래스 값을 재정의 할 필요 없습니다 . 또한 protected String errorMsg;하위 클래스에서 값을 설정하도록 어떤 방식 으로든 강제 하는 것을 정의 할 수도 있습니다 . 실제로 모든 메서드를 플레이스 홀더로 구현하는 추상 클래스와 유사하므로 개발자는 필요하지 않지만 모든 메서드를 구현할 필요가 없습니다.
Felix Kling

7
말하는 protected String errorMsg;않습니다 하지 당신이 서브 클래스의 값을 설정하는 것이 적용합니다.
Laurence Gonsalves

1
설정하지 않은 경우 값이 null 인 경우 "시행"으로 간주되는 경우 비 시행이라고 부르는 것은 무엇입니까?
Laurence Gonsalves

9
@felix, 집행계약 에는 차이가 있습니다. 이 전체 게시물은 추상 클래스를 중심으로 이루어지며, 이는 하위 클래스에 의해 이행되어야하는 계약을 나타냅니다. 따라서 추상 필드를 갖는 것에 대해 이야기 할 때 모든 하위 클래스 계약별로 해당 필드의 값을 구현 해야합니다 . 귀하의 접근 방식은 어떤 가치도 보장하지 않으며 계약처럼 예상되는 것을 명시하지 않습니다. 아주 아주 다릅니다.
liltitus27

3

분명히 이것을 허용하도록 설계되었을 있지만 내부적으로는 여전히 동적 디스패치를 ​​수행해야하고 따라서 메서드 호출이 필요합니다. 자바의 디자인 (적어도 초기에는)은 어느 정도 미니멀리스트가 되려는 시도였습니다. 즉, 디자이너는 이미 언어에있는 다른 기능으로 쉽게 시뮬레이션 할 수 있다면 새로운 기능을 추가하지 않으려 고했습니다.


@Laurence- 추상 필드가 포함될 수 있다는 것이 분명 하지 않습니다. 이와 같은 것은 유형 시스템과 언어 사용성에 깊고 근본적인 영향을 미칠 가능성이 있습니다. (다중 상속이 심각한 문제를 가져 오는 것과 같은 방식으로 ... 부주의 한 C ++ 프로그래머를 물릴 수 있습니다.)
Stephen C

0

제목을 읽으면서 추상 인스턴스 멤버를 언급하고 있다고 생각했습니다. 그리고 나는 그것들이 많이 사용되는 것을 보지 못했습니다. 그러나 추상 정적 멤버는 완전히 다른 문제입니다.

나는 종종 자바에서 다음과 같은 메소드를 선언 할 수 있기를 바랐다.

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

기본적으로 부모 클래스의 구체적인 구현이 특정 서명이있는 정적 팩토리 메서드를 제공한다고 주장하고 싶습니다. 이렇게하면 구체적인 클래스에 대한 참조를 얻을 수 있고 Class.forName()내가 선택한 규칙에 따라 구성 할 수 있습니다.


1
그래 잘 ... 이것은 아마도 당신이 반사를 많이 사용하고 있음을 의미합니다!
Stephen C

나에게 이상한 프로그래밍 습관처럼 들린다. Class.forName (..)은 디자인 결함처럼 냄새가납니다.
whiskeysierra

요즘 우리는 기본적으로 이런 종류의 일을 수행하기 위해 항상 Spring DI를 사용합니다. 하지만 그 프레임 워크가 알려지고 대중화되기 전에 우리는 속성이나 XML 파일에 구현 클래스를 지정한 다음 Class.forName ()을 사용하여로드했습니다.
Drew Wills

그들에 대한 유스 케이스를 줄 수 있습니다. "추상 필드"가 없으면 추상 제네릭 클래스에서 제네릭 유형의 필드를 선언하는 것이 거의 불가능합니다 @autowired T fieldName.. 가와 T같이 정의 된 경우 T extends AbstractController, 유형 삭제는 필드가 유형으로 생성되도록 AbstractController하며 구체적이지 않고 고유하지 않기 때문에 자동 연결될 수 없습니다. 나는 일반적으로 그것들이 많이 사용되지 않는다는 데 동의하므로 그들은 언어에 속하지 않습니다. 내가 동의하지 않는 것은 그들에게 쓸모가 없다는 것입니다.
DaBlick

0

또 다른 옵션은 기본 클래스에서 필드를 공용 (원하는 경우 최종)으로 정의한 다음 현재 사용중인 하위 클래스에 따라 기본 클래스의 생성자에서 해당 필드를 초기화하는 것입니다. 순환 종속성을 도입한다는 점에서 약간 그늘진 것입니다. 그러나 적어도 변경 될 수있는 종속성은 아닙니다. 즉, 하위 클래스는 존재하거나 존재하지 않지만 하위 클래스의 메서드 또는 필드는의 값에 영향을 줄 수 없습니다 field.

public abstract class Base {
  public final int field;
  public Base() {
    if (this instanceof SubClassOne) {
      field = 1;
    } else if (this instanceof SubClassTwo) {
      field = 2;
    } else {
      // assertion, thrown exception, set to -1, whatever you want to do 
      // to trigger an error
      field = -1;
    }
  }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.