몬그로이

자바의 정석 7. 객체지향 프로그래밍2 본문

Organizing Docs

자바의 정석 7. 객체지향 프로그래밍2

Mon Groy 2024. 7. 1. 20:00
상속
- 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다
- 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다

** 접근제어자가 private 또는 default 인멤버들은 상속되지 않는다기보다 상속은 받지만 자손 클래스로부터의 접근이 제한된다

 

상속관계 '~은 ~이다(is - a)'
포함관계 '~은 ~을 가지고 있다(has - a)'

 

오버라이딩 조건1
- 조상 클래스 메서드와 이름이 같아야 한다
- 매개변수가 같아야 한다
- 반환타입이 같아야 한다(JDK1.5이전)
- 접근제어자 범위가 같거나 커야 한다
오버라이딩 조건2
- 예외의 경우 조상 클래스의 메서드보다 같거나 큰 범위로 선언
- 인스턴스 메서드를 static 메서드로, static 메서드를 인스턴스 메서드로 변경 불가능

*Exception 은 모든 예외의 최고 조상

**조상 클래스에 선언된 static 메서드를 자손 클래스에서도 선언하는 것은 오버라이딩이라고 할 수 없다

오버로딩(overleading) 기존에 없는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding) 상속받은 메서드의 내용을 변경하는 것(change, modify)

 

Object 클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자, this() 또는 super() 를 호출해야 한다.
그렇지 않으면 컴파일러가 자동으로 super(); 를 생성자의 첫 줄에 삽입한다

 

기본 생성자가 만들어져 있지 않은 클래스를 상속받은 클래스가 있다고 해 보자.

이 클래스에 생성자를 만들 때, 반환갑+클래스명+매개변수 바로 아랫줄에

생성자를 하나 적어두지 않을 경우,

컴파일러는 자동으로 super(); 라는 생성자를 만들어 넣는다

그런데, 그 super 클래스의 생성자 정의에 매개변수가 빈 기본 생성자가 없을 경우

컴파일 오류가 발생한다

 

Object();

 

package문, import문

package명.* 을 사용하면
해당 패키지 하위에 위치한 모든 클래스를 import하여 패키지명은 생략한 채 사용할 수 있다
컴파일러가 찾는 데에 시간을 조금 더 들일 뿐, 성능상 차이는 거의 없다
단, 하위에 '패키지'를 더 두었을 경우
그 '하위패키지'에 속하는 클래스까지 import 해오지는 않으므로 주의

 

static import문

static 멤버를 호출할 때 패키지명 뿐 아니라 클래스 이름도 생략할 수 있다
import static java.lang.Integer.*; -> Integer 하위 모든 static 메서드 사용시 Integer 생략 가능
import static java.lang.Math.random; -> Math 라는 패키지의 random() 사용시 random()만으로 사용가능
import static java.lang.System.out; -> System 이라는 패키지의 out() 사용시 out()만으로 사용 가능
ex. System.out.println(Math.random());  ->  out.println(random());

** out() 다음에 println 이 오기에 out() 에서 ()가 사라진 것

 


제어자(modifier)

 

접근제어자: public, protected, default, private

그 외: static, final, abstract, native, transient, synchronized, volatile, strictfp

 

인스턴스변수 : 각기 다른 값을 가짐

 

static 멤버변수(클래스변수)

- 모든 인스턴스에서 공통적으로 사용하는 공유 변수로 한 가지 값만 가짐

- 인스턴스를 생성하지 않고 사용 가능

- 메모리가 로드될 때 생성

 

static 메서드

- 인스턴스를 생성하지 않고도 호출 가능

- static 메서드 내에서 인스턴스멤버들을 직접 사용 불가

*인스턴스 멤버를 사용하지 않는 메서드는 static을 붙여 선언하는 것을 고려하자

인스턴스를 생성하지 않고 바로 호출이 가능하므로 더 편리하고 속도도 빠르다

 

public class PracticeStatic {

    static int weight = 200;

    static {
        // 정적 초기화 블록
        System.out.println("클래스 Abc가 초기화됩니다.");
        weight = 300;
    }
}

 

static 변수나 메서드들은 클래스가 생성될 때 수행되므로

static{} 는 처음 프로그램이 실행될 때 PracticeStatic  클래스를 초기화 시킨다

 

final

- 클래스에 사용 : 변경/확장될 수 없는 클래스가 되므로 다른 클래스의 조상이 될 수 없다
- 메서드 : 변경될 수 없는 메서드로, 오버라이딩을 할 수 없다
- 멤버변수 또는 지역변수 :  값을 변경할 수 없는 상수가 된다

*대표적인 final 클래스 : String, Math


형변환 생략 가능
자손타입 -> 조상타입 (up_casting)

형변환 생략 불가능 = 명시적 변환만 가능
조상타입 -> 자손타입(down_casting)

 

형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것이 아니기 때문에

참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다

 

부모타입의 참조변수를 자손타입으로 변환하는 것은

참조변수가 다룰 수 있는 멤버의 개수를 늘리는 것이므로,

실제 인스턴스의 멤버 개수보다 참조변수가 사용할 수 있는 멤버의 개수가 더 많아지는 것이다

따라서 문제가 발생할 가능성이 많아진다

 

주의!

현재 부모타입인 참조변수가 처음 생성될 때의 타입이 자손타입이 아닌 경우

자손타입의 참조변수에 부모타입의 참조변수를 넣으려고 한다면

명시적 변환은 가능하지만(컴파일 가능) 실행시에는 오류가 발생함 (ClassCastException)

 

이를 방지하기 위하여 instanceof 연산자를 사용한다

 

public static void main(String[] args) {

FireEngine fe = new FireEngine();

 

      if (fe instanceof FireEngine) {

       System.out.println("FireEngine instance");

       }

       if (fe instanceof Car) {

       System.out.println("Car instance");

       }

       if (fe instanceof Object) {

       System.out.println("Object instance");

       }

       System.out.println(fe.getClass().getName());

}

*실행 결과 모두 true 로 반환되어 출력될 것

 

어떤 타입에 대한 instanceof 연산의 결과가 true 라는 것은 검사한 타입으로 형변환이 가능하다는 뜻

변수 은닉

 

자손 클래스에서 부모 클래스와 동일한 이름의 변수를 선언한 경우

자손 클래스의 변수는 부모 클래스의 변수를 가리게 됨

그 결과 부모 클래스의 변수는 자손 클래스 내에서 직접 접근할 수 없음

class Parent {

      public String name = "Parent";

}

 

class Child extends Parent {

       public String name = "Child";

}

 

public class Main {

       public static void main(String[] args) {

       Parent parent = new Parent();

       System.out.println("Parent name: " + parent.name); // 출력: Parent name: Parent

 

       Child child = new Child();

       System.out.println("Child name: " + child.name); // 출력: Child name: Child

 

       Parent parentRefChild = new Child();

       System.out.println("Parent reference to Child name: " + parentRefChild.name);

       // 출력: Parent reference to Child name: Parent

       }

}

*변수 은닉은 코드의 가독성과 유지보수성을 떨어뜨림

 

**메서드의 경우는

조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에도

참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩 된 메서드) 가 호출되지만

멤버변수의 경우 참조변수의 타입에 따라 달라짐

 

***static 메서드는 static 변수처럼 참조변수의 타입에 영향을 받음

참조변수의 타입에 영향을 받지 않는 것은 인스턴스 메서드 뿐

그래서 static 메서드는 반드시 참조변수가 아닌 '클래스이름.메서드()'로 호출해야 함

class Parent {

      static void staticMethod() {

       System.out.println("Parent's static method");

       }

 

       void instanceMethod() {

       System.out.println("Parent's instance method");

       }

}

 

class Child extends Parent {

       static void staticMethod() {

       System.out.println("Child's static method");

       }

 

       @Override

       void instanceMethod() {

       System.out.println("Child's instance method");

       }

}

 

public class Main {

       public static void main(String[] args) {

 

       Parent p = new Child(); // Parent 타입의 참조 변수로 Child 객체를 참조

 

       // static 메서드 호출 - 참조 변수 타입에 영향받지 않음, 클래스 이름으로 호출

       Parent.staticMethod(); // 출력: Parent's static method

       Child.staticMethod(); // 출력: Child's static method

       p.staticMethod(); // 권장되지 않음, 참조 변수 타입에 영향받지 않음, 출력: Parent's static method

 

       // 인스턴스 메서드 호출 - 참조 변수 타입에 영향을 받음

       p.instanceMethod(); // 출력: Child's instance method (동적 바인딩)

 

       // 권장되는 static 메서드 호출 방식

       Parent.staticMethod(); // 출력: Parent's static method

       Child.staticMethod(); // 출력: Child's static method

       }

}

 


PrintStream 클래스에 정의된 print(Object obj) 메서드

public static String valueOf(Object obj) {

      return (obj == null) ? "null" : obj.toString();

}

 

print(Object obj) 메서드 사용시

Object에 정의된 String 타입을 반환하는 toString 메서드의 반환값을 반환한다

public class Computer {

       Computer() {}

      

       public String toString() {   // String 타입을 반환하는 toString 메서드

       return "Computer"

       }

}

 

public static void main(String[] args) {

      Computer c = new Computer();

       System.out.println(c);  // Computer 가 출력됨

}


Vector() 10개 이상의 객체를 저장할 수 있는 Vector 인스턴스 생성
10개 이상이 저장되면, 자동적으로 크기가 증가됨
boolean add(Object o) Vector 에 객체를 추가
추가에 성공하면 true, 실패하면 false 반환
boolean remove(Object o) Vector 에 저장되어 있는 객체를 제거
제거에 성공하면 true, 실패하면 false 를 반환
boolean isEmpty() Vector 가 비어있는지 검사
비어있으면 true, 비어있지 않으면 false 반환
Object get(int index) 지정된 위치(index) 의 객체를 반환
반환타입이 Object 타입이므로 적절한 타입으로의 형변환 필요
int size() Vector 에 저장된 객체의 개수 반환

추상클래스

 

미완성 메서드(추상메서드)를 하나라도 가지고 있는 클래스
인스턴스 생성 불가능
상속을 통해 자손클래스에 의해서만 완성 가능

 

추상메서드

선언부만 작성되어 있는 메서드

 

* 추상메서드를 포함하고 있지 않은 클래스에도 키워드 abstract 를 붙여 추상클래스로 지정할 수 있음
그러면 인스턴스를 생성할 수 없게 됨

 

abstract class Player {

      boolean pause;

       int currentPose;

 

       Player() { //추상클래스도 생성자 필요

       pause = false;

       currentPose = 0;

       }

 

       abstract void play(int pos);//사용불가 상태

       abstract void stop();//override 해야만 하는 메서드 (구현을 못하더라도 override 해야함)

 

       void play() { //추상메서드를 사용 가능하게

       play(currentPose); // currentPose 값을 매개변수로 사용하여 play(int pos) 호출

       }

}

 

class AudioPlayer extends Player {

        @Override

        void play(int pos) {

        System.out.println("Playing audio at position " + pos);

        }

 

        @Override

        void stop() {

        System.out.println("Audio stopped");

        }

}

 

public class Main {

        public static void main(String[] args) {

        // AudioPlayer 인스턴스 생성, Player의 생성자 호출

        Player player = new AudioPlayer();

 

        // Player 생성자 호출 결과

        // pause = false;

        // currentPose = 0;

 

        // play() 메서드 호출: currentPose 값을 사용하여 play(int pos) 호출

        player.play();

        // 출력: Playing audio at position 0

        }

}

 

추상메서드로 선언한는 것과

빈 몸통만 만들어 놓는 것은 별 차이가 없어보인다

 

그래도 굳이 abstract 를 붙여서 추상메서드로 선언하는 이유는

자손클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다

 

추상클래스를 이용하면

다양한 클래스를 한 배열에 담을 수 있다

Unit[] group = new Unit[4];

group[0] = new Marine();

group[1] = new Tank();

group[2] = new Marine();

group[3] = new DropShip();


인터페이스란?

 

추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서

추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다

오직 추상메서드와 상수만을 멤버로 가질 수 있다

 

추상클래스를 부분적으로만 완성된 '미완성 설계도' 라고 한다면,

인터페이스는 구현된 것은 아무것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다

 

불완전한 상태이므로 그 자체로 사용되기 보다 다른 클래스를 작성하는데 도움을 줄 목적으로 작성된다

 

인터페이스의 멤버 제약사항
- 모든 멤버변수는 public static fianl,  생략가능
- 모든 메서드는 public abstract,  생략가능
단, static 메서드와 default 메서드는 예외

 

인터페이스의 상속

 

인터페이스는 인터페이스로부터만 상속받을 수 있으며 다중상속이 가능하다

*인터페이스는 클래스의 Object 클래스와 같은 최고조상이 없다

 

인터페이스의 구현

 

1. 인터페이스의 인스턴스를 생성할 수 없으며

2. 인터페이스를 상속받은 클래스는 몸통을 만들어주는 클래스를 작성해야 한다

* 단, 상속할 때 implements 사용

Class 클래스이름 implements 인터페이스이름 {

      //인터페이스에 정의된 추상메서드 구현

}

 

3. 만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면

abstract 를 붙여서 추상클래스로 선언해야 한다

 

4. 상속과 구현을 동시에 할 수도 있다

interface Movable {

      void move (int x, int y);  //public abstract 가 생략된 상태 

}

 

abstract class Fighter extends Unit implements Fightable

      public void move(int x, int y) { /*내용생략*/} //오버라이딩할 때는 조상 메서드보다 넓은 범위의 접근제어자 필수

}

 


두 개의 클래스로부터 상속을 받고자 하는 상황인 경우 (다중상속)

다중상속을 허용하지 않으므로

한 클래스만 상속받고 나머지 한 쪽은 클래스 내에 포함시켜 내부적으로 인스턴스를 생성해 사용한다

 

클래스 TV, 클래스 VCR 이 있을 때 두 클래스 모두 상속받고자 하는 경우

1. VCR 의 메서드와 일치하는 추상메서드를 갖는 IVCR 을 작성한다

2. public Class TVCR extends TV implements IVCR 로 작성한 뒤

3. 클래스 내부에

VCR vcr = new VCR();

을 선언한다

4. IVCR 로부터 받은 메서드 몸통에 

vcr.method1();

처럼 VCR 인스턴스의 메서드를 호출한다

* 사실 인터페이스를 새로 작성하지 않고도 TVCR 클래스에 VCR 클래스를 포함시키는 것만으로도 충분하다


 

리턴타입이 인터페이스라는 것은
메서드가 '해당 인터페이스를 구현한 클래스의 인스턴스'를
반환한다는 것을 의미한다

 

리턴타입이 인터페이스인 메서드를 사용하여 얻는 이점은, 해당 인터페이스를 구현한 클래스들을 반환한다는 점이고, 이는 분산환경 프로그래밍에서 위력을 발휘한다. 메서드 내에 사용된 구현클래스를 다른 구현된 클래스로 교체하기만하면, 해당 메서드를 사용하고 있던 곳에서는 변경할 내용 없이 그대로 그 메서드를 사용하면 되는 것이다


1. 매개변수로 인터페이스를 사용한다는 것의 의미

서로 다른 조상을 상속받고 있는 클래스들을 한 가지의 인터페이스를 implements 받는다면

해당 인터페이스를 매개변수로 받는 한 메서드만 작성하여 효율을 높일 수 있다

 

2. 한 가지 클래스의 상속을 받고 있는 클래스들이 있는데, 일부 클래스에게만 동일한 메서드를 추가하고 싶을 때

해당 메서드를 갖는 인터페이스를 만들고, 그 구현 클래스를 그 일부 클래스에게 포함시켜 추가할 메서드들을 사용하도록 만들 수 있다. 이로써 중복코드 작성을 피할 수 있고, 재사용성도 고려된 결과가 만들어졌다

 

 

상속과 인터페이스 구현을 함께 사용하는 경우

 

public class Barrack implements Liftable {

 

      private LiftableImpl liftableImpl = new LiftableImpl();

 

       public void liftOff() {

       liftableImpl.lift();

       }

      

       @Override

       public void lift() {

       liftableImpl.lift(); // Liftable 인터페이스의 메서드 구현

       }

}

 

인터페이스 구현 없이 구체적인 구현체만 사용하는 경우

public class Barrack {

 

       private LiftableImpl liftableImpl = new LiftableImpl();

      

       public void liftOff() {

       liftableImpl.lift();

       }

}

 

주입한 Impl 의 교체가능성을 염두에 두고 작성하는 경우 

public class Barrack implements Liftable {

 

      private Liftable liftable; // Liftable 인터페이스로 선언

 

       public Barrack(Liftable liftable) {

       this.liftable = liftable;

       }

      

       @Override

       public void lift() {

       liftable.lift(); // Liftable 인터페이스의 lift 메서드 호출

       }

 

       public void liftOff() {

       liftable.lift(); // Liftable 인터페이스의 lift 메서드 호출

       }

 

       // Liftable 인터페이스의 다른 메서드들 구현 (: land())

}

 

 

        public class Main {

 

        public static void main(String[] args) {

        Liftable mockLiftable = new MockLiftableImpl();

        Barrack barrack = new Barrack(mockLiftable);

 

        barrack.liftOff(); // MockLiftableImpl의 lift 메서드 호출

        // 다른 Barrack의 메서드 호출 가능

        }

}

 


Java의 모든 클래스는 기본적으로 Object 클래스를 상속받는다

인터페이스 역시 모든 객체의 조상인 Object 클래스를 간접적으로 상속받는

 

Object 클래스가 기본으로 제공하는 메서드

 

  • equals(Object obj) : 객체의 동등성을 확인하는 메서드.
  • hashCode() : 객체의 해시 코드를 반환하는 메서드.
  • toString() : 객체의 문자열 표현을 반환하는 메서드.
  • getClass() : 객체의 클래스 정보를 반환하는 메서드
  • clone() : 객체를 복제하는 메서드

 


디폴트 메서드

 

추상 메서드의 기본적인 구현을 제공하는 메서드

디폴트 메서드를 인터페이스에 추가했을 때

해당 인터페이스를 구현한 클래스에 변경을 주지 않아도 된다

디폴트 메서드를 오버라이드를 할 필요는 없지만 필요한 경우 가능하다

 

- 여러 인터페이스가 동일한 디폴트 메서드의 선언부를 가지고 있을 경우인터페이스를 implements 한 클래스에서 직접 오버라이드하여 충돌을 해결해야 한다- 디폴트 메서드와 조상 클래스의 메서드간 충돌이 있을 경우에는조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다 *필요한 경우 디폴트 메서드로 오버라이드

 


inner클래스(내부 클래스)

public class InnerEx {

       class InstanceInner{}

       static class StaticInner{}

 

       //인스턴스 멤버끼리 직접접근 가능

       InstanceInner iv = new InstanceInner();

       //static 멤버끼리 직접접근 가능

       static StaticInner cv = new StaticInner();

 

       static void staticMethod() {

             //static 멤버는 인스턴스 멤버에 직접 접근 불가

              // InstanceInner obj1 = new InstanceInner();

              StaticInner obj2 = new StaticInner();

 

              //굳이 접근하려면 아래와 같이 객체생성 먼저

              //인스턴스 클래스는 외부 클래스를 먼저 생성

              InnerEx outer = new InnerEx();

              InstanceInner obj1 = outer.new InstanceInner();

       }

       void instanceMethod() {

              //인스턴스메서드에서는 인스턴스멤버와 static 멤버 모두에 접근 가능

              InstanceInner obj1 = new InstanceInner();

              StaticInner obj2 = new StaticInner();

              //메서드 내에 지역적으로 선언된 내부 클래스는 외부에서 접근불가

              // LocalInner lv = new LocalInner();

       }

 

       void myMethod() {

             class LocalInner{} //scope myMethod()

              LocalInner lv = new LocalInner();

       }

}

 


익명 클래스

 

클래스를 정의하면서 동시에 인스턴스를 생성하는 방법

주로 인터페이스나 추상 클래스의 단일 객체를 만들 때 사용

일회성으로 사용될 경우 유용

 

클래스 정의와 동시에 즉시 인스턴스를 생성

이름이 명시적으로 정의되지 않으므로

수명이나 객체의 참조 변수로만 접근 가능

 

익명클래스 아닌 경우

public class Anonymous {

      public static void main(String[] args) {

              Button b = new Botton("Start");

              b.addActionListener(new EventHandler());

        }

}

 

 class EventHandler implements ActionListener{

       public void actionPerformed(ActionEvente) {

              System.out.println("ActionEvent occurred!!!");

       }

}

 

익명클래스인 경우

public class Anonymous {

       public static void main(String[] args) {

              Button b = new Botton("Start");

              b.addActionListener(new ActionListener() {

                     public void actionPerformed(ActionEvent e) {

                            System.out.println("ActionEvent occurred!!!");

                     }

              });

       }

}

 

 

 

 

 

'Organizing Docs' 카테고리의 다른 글

NoSQL, RDBMS  (0) 2024.07.18
MVP 패턴  (0) 2024.07.17
H2  (0) 2024.06.26
H2 연결 오류 해결하기  (0) 2024.06.26
TIL_029  (0) 2024.05.31