JAVA 프로그래밍 사상(3) - 복용류(二)

20262 단어

조합과 계승 사이의 선택


조합과 상속은 모두 새로운 클래스에 하위 대상을 배치할 수 있다. 조합은 표시적으로 이렇게 하고 상속은 은밀하게 한다.
조합 기술은 인터페이스가 아닌 새로운 클래스에서 기존 클래스의 기능을 사용하려는 데 쓰인다.새 클래스에서 우리는 새 클래스에 정의된 인터페이스만 볼 수 있으며, 끼워 넣은 대상이 아닌 인터페이스만 볼 수 있다.이 효과를 얻기 위해서는 새 클래스에 기존 클래스의private 대상을 삽입해야 합니다.
때때로, 클래스의 사용자가 새로운 클래스의 조합 성분에 직접 접근할 수 있도록 하는 것은 매우 의미가 있으며,public (일반적인 경우private) 라고 성명한다.만약 구성원 대상 자체가 구체적인 실현을 숨겼다면 이런 방법은 안전하다'is-a'(하나)의 관계는 계승으로 표현하고has-a(하나)의 관계는 조합으로 표현한다.

보호된 키워드


실제 프로젝트에서 어떤 것들을 가능한 한 이 세계에 숨기려고 하지만, 클래스를 내보내는 구성원들이 접근할 수 있도록 허용한다.
키워드protected는 "클래스 사용자에게 이것은private이지만, 이러한 내보내기 클래스나 같은 가방에 있는 다른 클래스를 계승하는 모든 클래스에 접근할 수 있습니다."라고 가리킨다.(protected도 패키지 내 접근 권한을 제공했다.)
보호된 필드를 만들 수 있지만, 가장 좋은 방법은private로 유지하는 것입니다.'수정 베이스 구현' 의 권한을 계속 유지하고 보호된 방법을 통해 클래스의 상속자의 접근 권한을 제어해야 한다.

향상된 전환


'신류는 현존류의 한 유형'이라는 말은 신류와 기류 간의 관계를 요약하는 데 쓰인다.
//: reusing/Wind.java
// Inheritance & upcasting.

class Instrument {
  public void play() {}
  static void tune(Instrument i) {
    // ...
    i.play();
  }
}

// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
  public static void main(String[] args) {
    Wind flute = new Wind();
    Instrument.tune(flute); // Upcasting
  }
} ///:~

이 예에서 tune() 메서드는 Instrument에서 참조할 수 있습니다.하지만 Wind에서.main () 에서 tune () 방법에 전달되는 것은 윈드 인용입니다. 자바는 윈드 대상이 같은 Instrument 대상이라는 것을 인식하지 않으면, 윈드 () 방법이 Instrument를 통해 호출될 수 있고, 윈드와 저장되지 않습니다.도출 클래스의 인용을 기본 클래스의 인용으로 바꾸는 동작을 상향 전환이라고 한다.
도출류에서 기류로 전환하고 계승도에서 위로 이동하기 때문에 일반적으로 위로 전환이라고 부른다.상향 전환은 비교적 전용 유형에서 비교적 통용되는 유형으로 전환되기 때문에 항상 안전하다.즉 도출류는 기류의 초집합이다.그것은 많은 방법이 있을 수 있지만, 적어도 기류에 포함된 방법을 갖추고 있다.위로 전환하는 과정에서 클래스 인터페이스에서 유일하게 발생할 수 있는 일은 그것을 얻는 것이 아니라 잃어버리는 것이다. 이것이 바로 컴파일러가 '변형을 명확하게 표시하지 않았거나 특수한 표시를 지정하지 않은 상황에서도 이러한 행위를 지원하는 이유이다.

조합과 계승을 재론하다


비록 OOP을 가르치는 과정에서 우리는 여러 차례 계승을 강조했지만, 이것은 가능한 한 그것을 사용한다는 것을 의미하지는 않는다.반대로 이 기술을 신중하게 사용해야 한다. 그 사용 장소는 당신이 이 기술을 사용하는 것이 확실히 효과가 있다고 확신하는 경우에만 한정된다.조합을 사용하느냐 계승하느냐는 너에게 달려 있다. 네가 위로 변신할 필요가 있느냐 없느냐에 달려 있다.

final 키워드


상하문 환경에 따라 자바의 키워드final의 의미는 미세한 차이가 존재하지만 보통'이것은 바꿀 수 없는 것'을 가리킨다.바꾸고 싶지 않은 것은 두 가지 이유에서 비롯될 수 있다. 디자인이나 효율이다. 이 두 가지 이유는 차이가 매우 크기 때문에 키워드final이 오용될 수 있다.
final에서 사용할 수 있는 세 가지 상황: 데이터, 방법, 클래스.

final 데이터


때때로 데이터의 고정불변은 매우 유용하다. 예를 들어 다음과 같다.
영원히 변하지 않는 컴파일링 시 상수
실행할 때 초기화된 값입니다. 바꾸기를 원하지 않습니다.

컴파일러 상수의 경우, 컴파일러는 이 상수 값을 계산식에 사용할 수 있는 모든 값에 가져와서, 실행할 때의 부담을 줄일 수 있다.자바에서, 이러한 상수는 기본 데이터 형식이어야 하며, 키워드final로 표시해야 한다.이 상수를 정의할 때 값을 부여해야 합니다.
static이고final의 영역은 바꿀 수 없는 저장 공간만 차지합니다.
대상에 대한 인용이 기본 형식이 아니라final을 실행할 때 기본 형식과 달리 기본 형식은 수치를 변하지 않게 하고 인용은 인용을 변하지 않게 합니다.인용이 초기화되면 다른 대상을 가리키는 것으로 바꿀 수 없습니다.그러나 대상의 그 자체는 수정할 수 있다.
공백final
자바는 '공백의final' 을 생성할 수 있습니다. 공백의final은final로 성명되었지만 초기값을 정하지 않은 영역을 말합니다.어떤 경우에도 컴파일러는 공백의final을 사용하기 전에 초기화해야 합니다.그러나 공백final은 키워드final의 사용에 있어 더욱 큰 유연성을 제공한다.
//: reusing/BlankFinal.java
// "Blank" final fields.

class Poppet {
  private int i;
  Poppet(int ii) { i = ii; }
}

public class BlankFinal {
  private final int i = 0; // Initialized final
  private final int j; // Blank final
  private final Poppet p; // Blank final reference
  // Blank finals MUST be initialized in the constructor:
  public BlankFinal() {
    j = 1; // Initialize blank final
    p = new Poppet(1); // Initialize blank final reference
  }
  public BlankFinal(int x) {
    j = x; // Initialize blank final
    p = new Poppet(x); // Initialize blank final reference
  }
  public static void main(String[] args) {
    new BlankFinal();
    new BlankFinal(47);
  }
} ///:~

필드의 정의나 모든 구조기에서 표현식으로final에 값을 부여해야 합니다. 이것은final역이 사용되기 전에 항상 초기화되는 이유입니다.
final 매개 변수
Java에서는 매개변수 목록에서 매개변수를 final로 선언할 수 있습니다.이것은 매개 변수가 가리키는 대상을 방법에서 변경할 수 없다는 것을 의미한다.
너는 파라미터를 읽을 수 있지만, 파라미터를 수정할 수 없다.
final 방법
final 방법을 사용하는 이유는 두 가지가 있다.첫 번째 이유는 어떤 계승류가 그 의미를 수정하지 않도록 방법을 잠그는 것이다.이것은 디자인의 고려에서 나온 것이다. 계승에서 방법의 행위가 변하지 않고 덮어쓰이지 않도록 확보하고자 하는 것이다.
과거에final 방법을 권장했던 두 번째 이유는 효율이다.그러나 최근 자바 버전에서는 이러한 방법이 점점 저해되고 있어 final 방법으로 최적화할 필요가 없고 덮어쓰기를 명확하게 금지하고 싶을 때만 방법을final로 설정할 수 있다.
final 및 private 키워드
클래스의 모든private 방법은final로 은밀하게 지정됩니다.private 방법을 사용할 수 없기 때문에 덮어쓸 수 없습니다.private 방법에final 수식어를 추가할 수 있지만, 이 방법에 아무런 의미도 추가할 수 없습니다.
//: reusing/FinalOverridingIllusion.java
// It only looks like you can override
// a private or private final method.
import static net.mindview.util.Print.*;

class WithFinals {
  // Identical to "private" alone:
  private final void f() { print("WithFinals.f()"); }
  // Also automatically "final":
  private void g() { print("WithFinals.g()"); }
}

class OverridingPrivate extends WithFinals {
  private final void f() {
    print("OverridingPrivate.f()");
  }
  private void g() {
    print("OverridingPrivate.g()");
  }
}

class OverridingPrivate2 extends OverridingPrivate {
  public final void f() {
    print("OverridingPrivate2.f()");
  }
  public void g() {
    print("OverridingPrivate2.g()");
  }
}

public class FinalOverridingIllusion {
  public static void main(String[] args) {
    OverridingPrivate2 op2 = new OverridingPrivate2();
    op2.f();
    op2.g();
    // You can upcast:
    OverridingPrivate op = op2;
    // But you can't call the methods:
    //! op.f();
    //! op.g();
    // Same here:
    WithFinals wf = op2;
    //! wf.f();
    //! wf.g();
  }
} /* Output: OverridingPrivate2.f() OverridingPrivate2.g() *///:~

'덮어쓰기' 는 어떤 방법이 기본 클래스의 인터페이스의 일부분일 때만 나타난다.즉, 대상을 기본 유형으로 바꾸고 같은 방법을 사용해야 한다.만약 어떤 방법이 private라면, 그것은 클래스의 인터페이스의 일부분이 아니라, 클래스에 숨겨진 프로그램 코드일 뿐이며, 단지 같은 이름을 가지고 있을 뿐이다.그러나 내보내기 클래스에서 같은 이름으로public, 보호된, 패키지 접근 권한을 생성하는 방법을 사용하면 기본 클래스에 나타나는 '같은 이름만 있음' 은 발생하지 않습니다.이 때 당신은 이 방법을 덮어쓰지 않았습니다. 단지 새로운 방법을 만들었을 뿐입니다.private는 닿을 수 없고 효과적으로 숨길 수 있기 때문에, 그것이 속하는 조직 구조의 원인으로 간주되는 것 외에 다른 어떤 것도 고려할 필요가 없다.

final 클래스


어떤 종류의 전체를final로 정의할 때, 당신은 이 종류를 계승할 생각이 없고, 다른 사람이 이렇게 하는 것도 허락하지 않는다는 것을 나타낸다.다시 말하면, 어떤 고려에서, 당신은 이 종류의 디자인에 대해 어떠한 변동도 하지 않거나, 또는 안전상의 고려에서, 하위 클래스가 있기를 원하지 않는다.
클래스가final로 정의되었든 안 되었든 같은 규칙은final로 정의된 영역에 적용됩니다.그러나final류는 계승을 금지하기 때문에final류의 모든 방법은final로 은밀하게 지정됩니다. 덮어쓸 수 없기 때문입니다.final류에서 방법에 final 수식어를 추가할 수 있지만, 이것은 아무런 의미도 추가하지 않았다.

초기화 및 클래스 불러오기


많은 언어에서, 프로그램은 시작 과정의 일부분으로 즉시 불러옵니다.그 다음에 초기화되고 프로그램이 실행되기 시작한다.이 언어들의 초기화 과정은 static로 정의된 것을 확보하기 위해 조심스럽게 제어해야 하며, 초기화는 번거롭지 않을 것이다.예를 들어 C++에서 어떤 static가 다른 static가 초기화되기 전에 효과적으로 사용할 수 있기를 기대하면 문제가 발생할 수 있습니다.
자바는 이 문제가 발생하지 않을 것이다. 자바의 모든 사물은 대상이기 때문에, 모든 종류의 컴파일러는 자신의 독립된 파일에 존재하고, 이 파일은 프로그램 코드를 사용할 때만 불러옵니다.일반적으로 "클래스의 코드는 처음 사용할 때 불러옵니다."라고 말할 수 있습니다.이것은 클래스를 만드는 첫 번째 대상에서 불러오는 것을 가리키지만,static역이나static 방법에 접근할 때 불러오는 것을 가리킨다.
처음 사용하는 곳도static 초기화가 발생하는 곳입니다. 모든static 대상과static 코드 세그먼트는 불러올 때 프로그램의 순서대로 초기화됩니다.물론 이것은 단지 한 번 발생할 뿐이다.

상속 및 초기화

//: reusing/Beetle.java
// The full process of initialization.
import static net.mindview.util.Print.*;

class Insect {
  private int i = 9;
  protected int j;
  Insect() {
    print("i = " + i + ", j = " + j);
    j = 39;
  }
  private static int x1 =
    printInit("static Insect.x1 initialized");
  static int printInit(String s) {
    print(s);
    return 47;
  }
}

public class Beetle extends Insect {
  private int k = printInit("Beetle.k initialized");
  public Beetle() {
    print("k = " + k);
    print("j = " + j);
  }
  private static int x2 =
    printInit("static Beetle.x2 initialized");
  public static void main(String[] args) {
    print("Beetle constructor");
    Beetle b = new Beetle();
  }
} /* Output: static Insect.x1 initialized static Beetle.x2 initialized Beetle constructor i = 9, j = 0 Beetle.k initialized k = 47 j = 39 *///:~

구조기도 static 방법입니다. 비록 static 키워드가 표시되지 않았지만.(이것은 훗날 깊이 이해할 것이다)Beetle에서 Java를 실행할 때 발생하는 첫 번째 일은 Beetle에 접근하려는 것입니다.main () (static 방법) 은 마운트기가 시작하여 Beetle 클래스의 컴파일 코드를 찾기 시작했습니다. (사실은 Beetle.class라는 파일에서)이번 마운트에 대해 컴파일러는 기본 클래스 (키워드 extend를 통해 알 수 있는 것) 를 발견하고 마운트를 계속합니다.네가 이 기류의 대상을 만들려고 하든지 말든지, 이것은 모두 발생할 것이다.
만약 이 기류에 기류가 있다면 계속 불러옵니다. 이렇게 유추합니다.다음은 루트 클래스의static 초기화가 실행되고 (여기는 Insect) 다음 내보내기 클래스가 순서대로 유추됩니다.
지금까지 필요한 클래스가 불러왔습니다. 대상이 만들어질 수 있습니다.우선 대상의 모든 기본 형식은 기본값으로 설정되고 대상 인용은null로 설정되며, 기본 구조기는 호출되며, 기본 구조기가 완성된 후에 실례 변수는 순서대로 초기화되며, 마지막으로 구조기의 나머지 부분은 실행됩니다.

좋은 웹페이지 즐겨찾기