language/java

7. oop(object-oriented Programming) 객체지향 프로그래밍

wooweee 2023. 3. 4. 17:01
728x90

01, 02 상속

  • 상속: 기존의 class를 재사용하여 새로운 클래스를 작성하는 것
  • 상속의 특징
    1. 코드의 재사용성을 높인다.
    2. 코드의 중복을 제거한다.
    3. 프로그램의 생산성과 유지보수에 기여한다.
class Parent { }

class Child extends Parent {}

 

  • 규칙
    1. 한 방향이다. Parent -> child.  parent class의 변화는 child에 영향을 끼치지만 child의 변화가 parent에 영향을 주지 않는다.
    2. child 는 parent class의 모든 멤버를 상속받는다 (단, 생성자와 초기화 블럭은 상속 X)
    3. child 멤버 >= parent 멤버
class Point {
    int x;
    int y;
}

// Point class의 영향 안받음
class Point1 {
    int x,y,z;
}


// Point class의 영향 받음, int x,y도 가지고 있음
class Point1 extends Point {
    int z;
}

// 상속 안 받은 클래스나 받은 클래스나 동일한 결과
Point1 p = new Point()
// []는 저장공간 표현한 것. array 아님
// p[0x100] -> [x][y][z]
// p[0x200] -> [x][y][z]

 

03,04 포함

  • 클래스 관계 = 상속 or 포함 관계
    • 대체적으로 포함 관계를 많이 사용
    • 포함 관계: 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것
class Point {
    int x;
    int y;
}

class Circle {
    Point p = new Point(); // 포함관계
    int r;
}

// 초기화 방법 (상속이랑 다르다)
c.r = 10;
c.p.x = 10;
c.p.y = 10;

 

  • 저장 구조
    • Circle c = new Circle();
    • c [0x100] -> 0x100 [ 0x200 : p ][ 0 : r ] -> 0x200 [0 : x] [0 : y]
  • 포함과 상속 관계 사용하는 기준
    • 상속: ~은 ~이다. ex) 포르쉐는 자동차다
    • 포함: ~은 ~을 가지고 있다. ex) 원은 점을 가지고 있다.

 

05. 단일 상속

  • 자바에서는 단일 상속만 허용
  • 자바에서는 비중이 높은 class 만 상속을 하고 나머지는 그때 그때 포함관계를 이용을 한다.
  • 이유: 다중 상속은 복합적인 기능을 가진 클래스로 쉽게 작성을 할 수 있지만
    1. 클래스간 관계가 매우 복잡해지고
    2. 부모 class들의 멤버간의 이름이 같은 경우 구별할 방법이 없음
class TvDVD extends Tv, DVD {} // error

//둘다 같은 method를 가지고 있을 때 누구의 power(); 인지 모름
class Tv { power(); }
class DVD{ power();}

 

06.  Object class - 모든 class의 조상

  • Object class: 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다.
  • 규칙
    1. 조상이 없는 class는 자동으로 object class를 상속 받는다.
    2. 모든 class는 object class에 정의된 11개의 method를 상속 받는다.
    3. 이미 다른 부모 class로 부터 상속받도록 작성된 클래스에 대해서는 컴파일러가 Object class를 추가하지 않는다. - 결국 마지막 최상위 조상은 Object class 이므로 11개 method 이용 가능
  • 참고 - compiler가 자동 생성해주는 부분
    1. Method
      • 기본 생성자
      • void - return; 
      • super() : 모든 생성자는 첫줄에 다른 생성자를 넣어야 한다.
    2. class 생략
      • extends Object

Object method toString 예제

더보기
class MyPoint {
    int x;
    int y;
}

class Circle extends MyPoint{
    int r;
}


public class prac {
    public static void main(String[] args) {
        Circle c = new Circle();
        System.out.println(c.toString()); 
        //"Circle@7344699f" class @ 객체주소 
        System.out.println(c);  
        // toString() 생략가능 "Circle@7344699f", println이 참조변수를 받으면 toString()을 호출 
    }
}

 

07. 오버라이딩 - 상속
08. overriding의 조건 (암기)

  • overriding: 상속받은 method의 내용을 변경하는 것 * overriding : 덮어쓰다
  • 특징
    1. 선언문 변경 불가
    2. 구현부만 변경가능
    3. 조상의 멤버변수를 상속 받지만 오버라이딩 된 method가 호출 (다형성)
  • 조건(암기 필수) 
    1. 선언부가 조상 class method와 일치해야한다. == 선언문 변경 불가
    2. 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
      • public > protect >(default) > privite
    3. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다. 

  • super
    • 오버라이딩을 통해서 조상것을 덮어졌다.
    • 예제를 통해서 알아 볼 것
      1. 조상타입변수로 변수와 오버라이딩 method 호출
        • 변수: 조상 타입 변수값 나옴
        • method: 자손에서 오버라이딩 된 method 나옴
        • 잘 체크해야되는 것이 변수가 변수명만 동일하고 실제로는 다른 변수인지, 아니면 생성자 와 super()를 통해서 변수가 서로 초기화가 연결되는지 잘 확인 해야한다.
      2. 오버라이딩 되기 전 조상 메서드 호출 방법
        • super.method() 이용
        • super.method를 사용하고 싶으면 class에 미리 super.method를 호출하는 method를 만들고 사용
        • 변수: super를 사용하는 경우 없음
        • method: super를 사용하는 경우가 존재
      3. 생성자를 통한 초기화인 경우 - 자동-> 명시적-> 생성자 초기화 순서로 넘어간다.
        • 조상 변수가 값이 있고 조상 타입으로 다루는 자손 객체의 필드들은 결국 자손의 초기화 값을 나타내게 되어있다.

 

 

  • 조상타입 변수, 오버라이딩 되기 전 조상 메서드 호출
package practice;

class Practice {
    public static void main(String[] args) {

        MyPoint p = new MyPoint3D();
        System.out.println("p.x = " + p.x); // p.x = 1 조상 타입으로 자손의 x에는 접근하지 못하고 자신의 x를 꺼냈다. 자손 type에 숨어있는 x이다.
        System.out.println("p.y = " + p.y); // p.y = 1
        System.out.println("p.getLocation() = " + p.getLocation());

        MyPoint3D c = new MyPoint3D();
        System.out.println("c.x = " + c.x); // 자신의 타입으로 x를 호출한 것은 this.x를 부르는 것
        System.out.println("c.y = " + c.y); // 자신의 타입으로 y를 호출한 것은 this.y를 부르는 것
        System.out.println("c.z = " + c.z);
        c.method(); // 숨겨져있던 조상의 변수를 출력. 자손의 변수와 이름이 동일해서 super.x super.y로 구분지음
        System.out.println("c.getLocation() = " + c.getLocation());
        System.out.println("c.superGetLocation() = " + c.superGetLocation()); // 오버라이딩 전 method 호출

    }
}

class MyPoint {
    int x = 1;
    int y = 1;

    String getLocation() {
        return "x:" + x+", y:" + y;
    }
}


class MyPoint3D extends MyPoint {
    void method(){
        int x1 = super.x;
        int y1 = super.y;
        System.out.println("y1 = " + y1);
        System.out.println("x1 = " + x1);
    }
    int x = 5;
    int y = 5;
    int z = 8;


    // overriding -> 조상 getLocation을 상속받지만 overriding method가 호출됨(다형성)
    String getLocation() {
        return "x:" + x + ", y:" + y +", z:" + z;
    }
    String superGetLocation(){
        return super.getLocation(); // 조상 method 호출 방법
    }

}

 

  • 생성자를 통한 초기화
package practice;

class Practice {
    public static void main(String[] args) {

        MyPoint p = new MyPoint3D();
        System.out.println("p.x = " + p.x); // p.x = 5 -> 생성자를 통해서 값을 받도록 하면 우선권이 생성자로 넘어감 "초기화 순서 자동 -> 명시적 -> 생성자"
        System.out.println("p.y = " + p.y); // p.y = 6 -> 생성자를 통해서 값을 받도록 하면 우선권이 생성자로 넘어감 "초기화 순서 자동 -> 명시적 -> 생성자"
    }
}



class MyPoint {
    int x = 1;
    int y = 1;

    public MyPoint() {
        this(1,1);
    }

    public MyPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
}


class MyPoint3D extends MyPoint {
    // 현재 int x; int y; 가 없다. 이는 조상의 x,y를 사용한다는 의미이다. 
    int z;

    public MyPoint3D() {
        this(5,6,5);
    }

    public MyPoint3D(int x, int y, int z) {
        super();
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

 

 

09. Overriding vs Overloading

 

  • 오버로딩: 기존에 없는 새로운 method를 정의하는 것 (new) - 상속과 관련 X
    1. method 이름이 같다
    2. params 개수, type 달라야함
    3. return type 상관없다.
  • 오버라이딩: 상속받은 method의 내용을 변경하는 것 (change, modify)
    1. 구현부만 건들이는 것
    2. 선언부는 그대로 유지하는 것
    3. 예외는 조상보다 적게 조상과 같은 것

 

ex) 

class Parent {
	void parentMethod(){}
}


class Child extends Parent {
    void parentMethod(){ /*내용 추가*/ }              // overriding
    void          parentMethod     (int i)    {}   // overloading
    // 리턴 타입,      메서드 이름,      params

    void childMethod() {}          // method 정의
    void childMethod(int i) {}    // overloading
    void childMethod() {/*내용 추가*/}     // error, method 정의와 중복
}

 

 

10. 참조변수 super = this

  • 공통점
    • 인스턴스 method(= 생성자) 내에서만 존재
    • static method에서 사용 불가
  • 차이점
    • super : 조상, 자식 member 구별 
    • this     : lv(params)와 iv(memeber) 구별
class Parent {int x = 10;} // int x == int super.x

class Child extends Parent {
    int x = 20; // int x == this.x
    void childMethod() {
        System.out.println("x=" + x);  // 20
        // 가까운 쪽의 값을 먼저 받음. 상속값보다 자기가 선언한 값이 가까움
        
        System.out.println("this.x=" + this.x);   // 20
        System.out.println("super.x=" + super.x); // 10
    }
}

 

 

  • 메모리 구조
  • c [0x100]     ->    0x100 [ 10  - super.x ] [ 20  - this.x  ] [ childMethod() ]
class Parent {int x = 10;} // int x == int super.x

class Child extends Parent {
    void childMethod() {
    System.out.println("x=" + x); // 상속 받은 10

    // this.x는 존재하지 않지만 조상의 x를 상속받았기에 10
    System.out.println("this.x=" + this.x);   
    System.out.println("super.x=" + super.x); // 상속 받은 10
    }
}

 

11. super() - 조상의 생성자

  • 상속은 생성자, 초기화 block은 상속이 되지 않는다.
  • 생성자 혹은 초기화 block이 필요한 경우 super()를 이용하여 조상의 생성자를 호출 가능

 

  • 조건
    1. 모든 생성자의 첫줄에 반드시 조상의 생성자(super()) or 자신의 생성자(this.())를 호출해야 한다.
    2. 생성자 내에 this() 혹은 super() 가 존재 하지 않을 때 compiler가 super()를 자동 등록한다.
public class Ex7_4 {
    public static void main(String[] args) {
        Point3D p = new Point3D(1, 2, 3);
        System.out.println("x=" + p.x + ",y=" + p.y + ",z=" + p.z);
    }
}

class Point {    // class Point extends Object
    int x, y;

    Point(int x, int y) {
        // super(); 첫줄에 조상 생성자 호출하지 않았으므로 조상 기본생성자 호출
        this.x = x;
        this.y = y;
    }
}

class Point3D extends Point {
    int z;
    Point3D(int x, int y, int z) {
        super(x, y); // Point(int x, int y) 호출, 조상 생성자 호출
        this.z = z; // 자신의 멤버를 초기화 - 자손 class가 선언한 int z만 초기화
    }
}
  • error 경우 - 자동주입 패턴을 알아야 한다.
    1. 조상 class 에 params 생성자를 생성
    2. 자손에 따로 생성자를 작성하지 않은 자손 class 생성
    3. 자손 class에 compiler가 자동으로 기본 생성자를 생성
    4. 기본 생성자는 아무것도 없기때문에 자동으로 super()가 등록
    5. 조상 class에는 기본 생성자가 존재하지 않으므로 자손 class는 super()를 찾을 수 없다.
    6. error가 발생한다.
class Parents {
  int x;
  // params 생성자를 가지므로 기본 생성자 생성 X
  Parents(int x) {
    this.x = x;
  }
}

class Children extends Parents {
  int y;
}

 

  • 조상 타입과 자손 타입으로 값 꺼내기
    1. 변수
      • 현재 p.x인거지 lv의 변수명을 바꾸게 되면 (ex - abc로 변경) p.abc를 구하는 것이다.
      • 그리고 p 리모컨은 자손의 x에 접근 할 수 있는 버튼이 존재하지 않는다.
    2. method같은 경우는 조상의 method가 Overriding이 된다.
      • 조상이나 자손 type의 리모컨 모두 동일한 method를 가리킨다.
class Practice {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        // 값 추측해보기
        System.out.println("p.x = " + p.x);
        p.method();
        System.out.println("c.x = " + c.x);
        c.method();
        
    }
}
class Parent {
    int x = 100;
    void method() {
        System.out.println("Parent Method");
    }
}
class Child extends Parent {
    int x = 200;
    void method() {
        System.out.println("Child Method");
    }
}

 

12. 패키지(package)
13. 패키지 선언

  • 패키지: 관련된 클래스의 묶음, 패키지 안에 패키지가 존재할 수 있다.
  • 패키지는 물리적으로 하나의 디렉토리이고 그 안에 관련된 클래스 파일, 관련 패키지가 존재

 

  • 규칙
    1. 주석과 공백을 제외한 첫 번째 문장이어야 함. packgae 패키지명;
    2. 하나의 소스파일에 단 한 번만 선언
    3. 구분을 위해 소문자로 작성을 원칙
    4. 모든 class 파일은 반드시 하나의 패키지에 포함되어야 한다. * package 작성 안할 시 default package에 들어감

 

15. import문

특징

  1. class 이름에서 package 이름을 생략 가능
  2. 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보 제공
  3. Intellij 단축키: option + Enter(static import 또한 동일하다.)
  4. java.lang 패키지의 class는 import 없이 사용가능
  5. 소스파일에서 import 문은 package문 다음, class 선언문 이전에 작성을 해야한다.
  • import문 선언방법
    • import 패키지명.클래스명; or import 패키지명.*;
    • *: 해당 패키지의 바로 아래의 class를 불러오는 것, 바로 아래의 패키지는 건들이지 않음

 

17. 제어자

  • 클래스와 클래스의 멤버(멤버변수, 메서드)에 부가적인 의미 부여
    • 접근 제어자: public, protected, default, private
    • 그 외: static, final, abstract
  • 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하지만 접근 제어자는 한번에 1개만 사용 가능

 

18. static

  • 사용: 멤버변수, 메서드, 초기화 블럭

 

19. final

  • 사용: 클래스, 메서드, 멤버변수, 지역변수
    1. 클래스: 확장될 수 없는 클래스 = 최종 자손  ex) String - 보완기능 가짐
    2. 메서드: 오버라이딩 할 수 없음
    3. 멤버변수, 지역변수: 상수

 

20. abstract

  • 사용: 클래스, 메서드
    • 구현부가 존재하지 않은 메서드를 가지고 있으며 class도 자동적으로 미완성이 됨
    • 인스턴스 생성 불가

 

21. 접근 제어자

  • 사용
    • 클래스: public, default 
    • 멤버변수, 메서드, 생성자: private, default, protected, public

 

  • 범위
    • private: 같은 class 내에서만 접근이 가능
    • default: 같은 package 내에서만 접근이 가능
    • protected: 같은 패키지 + 다른 패키지의 자손클래스(=다른 패키지의 class의 조상 class로 상속 되었을 때) 접근 가능
    • public: 모든게 다됨

 

범위

규칙

  1. 멤버변수, 메서드, 생성자의 제어자 범위가 커도 결국에는 class의 제어자 범위에 맞춰진다.
  2. 다른 범위에서 사용할 때 Method 안에서만 사용가능
  3. 설계도 class 변수들을 사용하려면 instance 생성이 필요하다
  4. instance 생성을 안했을 때 변수의 이름이 동일하다고 문제가 생기지 않는다. 서로 다른 영역으로 확인한다.

 

 

22. 캡슐화와 접근 제어자

 

접근 제어자 사용 이유

  1. 외부로부터 데이터를 보호하기 위해서
  2. 외부에는 불필요한 내부적으로만 상요되는 부분을 감추기 위해서
  3. 수정할 때 범위를 줄여놓으면 test하기 유리

참고

더보기
class Time {

    private int hour;
    private int minute;
    private int second;

    public void setHour(int hour) {
        if(checkParamsHour(hour)) return;
        this.hour = hour;
    }

    // setHour의 true false를 정확히 하기 위함. 
    // 외부까지 알 필요 없으므로 private 사용, 나중에 수정 후 test시 private area만 확인하면 되는 장점 가짐 
    private boolean checkParamsHour(int hour) {
        return hour <0 || hour >23;
    }
	public int getHour() { return hour;}
}

public class TimeTest {
    public static void main(String[] args) {
        Time t = new Time();
        t.setHour(2);
        System.out.println(t.getHour());
        t.setHour(222);
        System.out.println(t.getHour());
    }
}

 

23. 다형성

  • 조상 type 참조변수로 자손 type 객체를 다루는 것 * 반대는 안됨
    • SmartTv s = new SmartTv();
    • Tv t = new SmartTv(); // 다형성
  • 조상 type의 참조변수로 자손 type 객체를 다룰 수 있다. 하지만 조상이 가지고 있는 멤버 만큼만 다룰 수 있다.
  • 자손 type의 참조변수로 조상 객체를 다룰 수 없는 이유: 자손 멤버 중 조상에 없는 것이 있기 때문에 호출시 error 발생

 

class Tv {
  boolean power;
  int channel;
}

class SmartTv extends Tv {
  void caption() {}
}

public class PackageTest {
  public static void main(String[] args) {
    Tv tv = new Tv();
    SmartTv smartTv = new SmartTv();
    Tv tv2 = new SmartTv(); // 다형성
  }
}

 

24. 참조변수의 형변환

  • 서로 상속인 관계에서 객체가 리모컨보다 범위가 넓기만 하면 된다.
  • 객체 범위의 기준: 형변환을 다 떠나서 원래 객체를 의미
    • SmartTv smartTv1 = (SmartTv) tv1; : tv1 참조변수의 실제 객체는 SmartTv
    • SmartTv smartTv2 = (SmartTv) tv;  : tv  참조변수의 실제 객체는 Tv
  • 조상타입으로 형변환 생략가능
  • 자손타입으로 형변환 생략불가
    • SmartTv smartTv1 = (SmartTv) tv1; : tv1의 객체가 SmartTv여도 현재 type의 Tv이기 때문에 생략 불가
class Tv {}
class SmartTv extends Tv {}

public class Practice {
  public static void main(String[] args) {
    // 객체 생성
    Tv      tv      = new Tv();
    SmartTv smartTv = new SmartTv();

    Tv      tv1      = smartTv;      // (Tv) 생략가능
    SmartTv smartTv1 = (SmartTv) tv1;// (SmartTv) 생략불가
    SmartTv smartTv2 = (SmartTv) tv; // runtime error
  }
}

 

26. instanceof (순간적으로 헷갈림) 

  • instanceof 는 형변환을 하기 전 runtime error를 막기 위한 장치
  • 참조변수(R) instanceof 객체(O)
    • R이 가리키는 실제 객체가 O로 형변환 가능한지 boolean으로 나타낸다.
    • R의 객체 범위가 O보다 크거나 같으면 true
    • R의 객체가 O를 조상으로 가지면 true
  • instanceOf가 필터해주는 것 2가지
    1. 관련 없는 type의 객체가 들어오는 것을 막음
    2. R이 O보다 작은 경우 막음
// Car type 중 FireEngine과 관련없는 PoliceCar 들어오면 false
// Car type 중 FireEngine보다 범위가 작은 Car가 들어오면 false 
void(Car c){
	if(c instanceof FireEngine){
    	FireEngine fe = (FireEngine)c;
        fe.water;
    }
}

class Car{}
class FireEngine extends Car{}
class PoliceCar extends Car{}
// R이 O보다 작은 경우 false 반환해서 막음

class Tv {
  boolean power;
  int channel;
}
class SmartTv extends Tv {
  void caption() {}
}

public class My {
  public static void main(String[] args) {
    Tv tv = new Tv();
    System.out.println(tv instanceof Object); // true
    System.out.println(tv instanceof Tv); // true
    System.out.println(tv instanceof SmartTv); // false. t참조변수의 실제 객체가 SmartTv 타입보다 작다.
  }
}

 

27.  매개변수의 다형성 - 장점1

  • 참조형 매개변수: 메서드 호출시 자신의 type 또는 자손 type 객체를 넘겨줄 수 있다.

예제

class Product{
  int price;
  int bonusPoint;
}

class Tv extends Product{}
class Computer extends Product{}

class Buyer {
  int money = 1000;
  int bonusPoint = 0;
  
  // 문제점
  void buy(Tv t){
    money = money - t.price;
    bonusPoint = bonusPoint + t.bonusPoint;
  }
  void buy(Computer c){
    money = money - c.price;
    bonusPoint = bonusPoint + c.bonusPoint;
  }
  // 해결방안
  void buy(Product p){
    money = money - p.price;
    bonusPoint = bonusPoint + p.bonusPoint;
  }
}
  • 문제점: 물품이 새로 생길 때마다 void buy(){} 새로 생성해야함
  • 해결방안: 매개변수에 다형성을 적용
    • 모든 물품의 조상인 product를 이용
    • product를 조상으로 가지는 class는 void buy의 params에 들어갈 수 있다.
  • 참고
    1. 현재 예의 경우 사용하는 field들은 모두 Product내에서 해결이 가능한 field들이다.
    2. instanceOf는 형변환을 하고 싶을 때 조건이 맞는지 체크를 하기 위함인데 현재는 최고 조상인 product를 이용하므로 굳이 체크할 필요가 없다. 

 

29. 여러 종류의 객체를 배열로 다루기 - 장점2

조상의 참조변수의 array를 이용해서 자손들의 array를 구성 가능

 

Product p[] = new Product[10]; // 참조변수 p의 값: 0x100주소
p[0] = new Tv(); // 0x100의 배열의 첫번째칸의 값: 0x200주소
p[1] = new Computer(); // 0x200주소의 객체: new Computer();
  • p [0x100] -> 0x100 [0x200] [0x300] [null][null][null],,,[null] *10개
  • p[0] [0x200] -> 0x200 [][][] 해당 주소에 new Tv(); 객체가 존재

 

  • 정리 - 다형성의 장점 2가지
    1. 다형적 매개변수
    2. 하나의 배열로 여러종류 객체를 다룰수 있다.

 

31. 추상 클래스

abstract class 클래스이름{
    멤버변수;
    메서드;
    생성자;

    추상 메서드;
}
  • 미완성 설계도, 인스턴스 생성불가
  • 추상 클래스는 상속을 통해서 자손클래스에 의해서만 완성이되고 추싱 메서드가 완전히 완성되야 객체 생성이 가능하다.
  • 추상 class안에 생성자, 멤버변수, 메서드를 가질 수 있다.
  • instance method를 가져도 error가 안생기는 이유: instance method를 이용하려면 객체를 생성햐야하는데 추상 class는 객체를 만들수가 없기 때문에 뭔가 일이 생길 경우가 없음. 그래서 괜찮음.
  • 추상class도 매개변수 다형성이 적용이 된다.
public class My {
  public static void main(String args[]) {
    Buyer buyer = new Buyer();
    buyer.buy(new Tv());
  }
}

abstract class Product {
  int price;
  abstract void try1();
}

class Tv extends Product {
  void try1() {}
}

class Buyer {
  int money = 1000;

  void buy(Product p) {
    money -= p.price;
    p.try1();
  }
}

 

32. 추상 메서드

/* 주석을 통해 어떤 기능을 수행할 목적으로 작성했는지 설명 */
abstract 리턴타입 메서드이름();
  • 추상 클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상메서드를 모두 구현해줘야한다
  • 구현하지 않을 시 자손클래스도 추상 클래스가 된다.

 

35. 인터페이스

  • 주: 구현된거 없는 추상 메서드의 집합
    • 모든 메서드: public abstract, 생략가능
  • 부: static method, default method, 상수
    • 모든 상수: public static final, 생략가능
  • abstact class, interface의 차이점
    • 추상클래스: 이것 저것 다 가지고 있고 추가적으로 abstract method를 가지고 있는 것
    • 인터페이스: abstract method만 가지고 있는 것

 

36. 인터페이스의 상속

  • 인터페이스는 인터페이스로부터만 상속 가능
  • 다중 상속 가능
    • 선언부만 가진 미완성 method이므로 동일이름 method의 문제점이 발생하지 않는다.
    • 다중 상속의 동일한 method를 받은 interface를 구현하면 양쪽에서 받은 method를 하나로 동일 취급한다.
    • 다중 구현에서도 동일한 추상 method도 동일 취급 - 37. 인테페이스의 구현
  • Object 클래스와 같은 최고 조상이 없다
    • interface의 조상은 interface만 가능

다중 상속 예

interface Method1 {
  void move();
}
interface Method2 {
  void move();
}
// move()를 양쪽 interface에서 받았지만 하나만 존재
interface Method3 extends Method1, Method2 {} 

class Method4 implements Method3 {
  public void move() {
    System.out.println("hi"); // 하나로 융합된 void move(); 하나만 구현
  }
}

 

37. 인터페이스의 구현

  • 구현 방법
    1. implements
    2. 추상메서드 모두 구현
    3. 추상 메서드 구현선 접근 제어자 public  작성
class Abc implements interface name {
    // 인터페이스에 정의된 추상메서드를 모두 구현해야 함
    // public을 무조건 붙여야함. overriding 규칙으로 조상보다 접근제어자가 좁으면 안된다. 
}
  • 특징
    1. 다중 상속이 있듯이 다중 구현도 존재
      • 다중 구현할 interface의 모든 추상 method를 구현만 하면 된다.
    2. 다중 구현할 class의 interface들의 method가 같을 때, 하나의 method로 취급 후 하나만 구현
    3. abstract class와 interface의 method가 동일시, 하나의 method로 취급 후 하나만 구현 - 복합특징 예제
interface Method1 {
  void move();
}
interface Method2 {
  void move();
}

class Method4 implements Method1, Method2 {
  // method 선언부가 동일한 추상메서드가 들어와서 하나로 취급
  public void move() {
    System.out.println("hi");
  }
}

 

  • 복합 특징
    1. class 구현시 상속과 구현을 동시에 가능 - 순서는 extends, implements 순
    2. 구현(implements): 다중 구현 가능
    3. 상속(extends): 단일 상속만 가능 
      • *이 때 상속은 interface의 상속이 아니라 실제 class들의 상속을 의미
    4. interface 구현이라해도 class를 만든것이기에 조상으로 Object를 가진다.
abstract class AbtractMethod{
    abstract void method();
}

interface Method1{
    void method();
}

interface Method2{
    void method();
}

class Method4 extends AbtractMethod implements Method1, Method2{
    public void method() {
        System.out.println("하나로 취급");
    }
}

 

38. 인터페이스 다형성 예제

 

 

  • 다형성 특징
    1. 인터페이스 타입 변수로 해당 인터페이스 구현체를 다룬다.
    2. 인터페이스 타입을 매개변수로 사용가능
    3. return type이 인터페이스인 경우
      • 해당 인터페이스를 구현한 클래스의 인스턴스를 반환값으로 넣는다는 것
      • return시 자동으로 인터페이스 타입으로 형변환
abstract class AbstractClass {
  abstract void abstractClass_abstractMethod();
  void abstractClass_method() {}
}

interface InterfaceClass {
  void interfaceMethod(InterfaceClass i);
}

class ImplementClass extends AbstractClass implements InterfaceClass {
  void abstractClass_abstractMethod() {}
  // 매개변수가 인터페이스인 경우
  public void interfaceMethod(InterfaceClass i) {}
  // 리턴타입이 interface인 경우
  InterfaceClass getImplementClass() {
    ImplementClass i = new ImplementClass();
    return (InterfaceClass) i; // 자동 형변환 가능
  }
}

public class Practice {
  public static void main(String[] args) {
    ImplementClass implementClass = new ImplementClass();
    // 매개변수가 interface
    implementClass.interfaceMethod(new ImplementClass());
    // 반환타입이 interface
    InterfaceClass interfaceClass1 = implementClass.getImplementClass();
    ImplementClass implementClass2 = (ImplementClass) implementClass.getImplementClass(); // 형변환 생략 불가
  }
}

 

39. 인터페이스의 장점

  1. 개발시간 단축
  2. 표준화 가능
  3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
  4. 독립적인 프로그래밍이 가능 = class간의 의존성을 떨어뜨린다.

독립적 프로그래밍

  • 독립적이지 않은 코드
class A {
  public void method(B b) { // class B라는 구현체에 의존적이다.
    b.method();
  }
}

class B {
  public void method() {
    System.out.println("b");
  }
}

class C {
  public void method() {
    System.out.println("c");
  }
}

public class Practice {

  public static void main(String[] args) {
    A a = new A();
    a.method(new B());
    a.method(new C()); // class A의 params를 C로 변경해야함
  }
}
  • 독립적인 코드
interface I {
  public abstract void method();
}

class A {
  public void method(I i) { // interface에 의존적이다.
    i.method();
  }
}

class B implements I {
  public void method() {
    System.out.println("b");
  }
}

class C implements I {
  public void method() {
    System.out.println("c");
  }
}

public class My {
  public static void main(String[] args) {
    A a = new A();
    a.method(new B());
    a.method(new C()); // class A의 params를 건들일 필요 X
  }
}

 

40. 디폴트 메서드와 static 메서드

  • 문제점
    • 인터페이스에 추상메서드를 추가하면 해당 인터페이스로 구현한 class 모두 새로 추가된 추상 메서드를 구현해야 된다.
    • 인터페이스로 만든 객체들이 많을수록 수정할거리가 많음
  • 해결
    • default method 이용
interface MyInterface {
    void method();
    default void defaultMethod(){/* 구현체 작성 */};
}

 

  • 새로 생긴 문제점
    • 새로 추가된 디폴트 메서드명 == 기존의 메서드와 이름  -> 중복 충돌
  • 해결 규칙
    1. 인터페이스들의 디폴트 메서드 충돌
      • 인터페이스 구현한 클래스에서 디폴트 메서드를 오버라이딩
    2. 디폴트 메서드와 (인터페이스를 구현한 클래스)의 조상 클래스 메서드 충돌
      • 조상 클래스의 메서드가 먼저 상속, 디폴트 메서드는 무시
    3. 규칙 암기가 싫으면 구현 클래스에 같은 내용으로 오버라이딩 하기
public class Main {
    public static void main(String[] args) {
        Child child = new Child();
        child.method1();
        child.method2();
    }
}

class Child extends Parent implements MyInterface, MyInterface2{
    // 1. interface 구현시 defaultMethod 오버라이딩
    public void method1() {
        System.out.println("method1() in Child"); // overriding
    }
    // 3. overriding
    /*
    public void method2(){
        System.out.println("method2() in Child");
    }
    */
}
class Parent{
    // 2. 조상 클래스의 메서드가 상속
    public void method2(){ System.out.println("method2() in parent");}
}

interface MyInterface2{
    default void method1(){ System.out.println("method1() in YouInterface2");}
}

interface MyInterface{
    default void method1(){ System.out.println("method1() in MyInterface");}
    default void method2(){ System.out.println("method2() in MyInterface");}
}