Java 객체지향 개념 정리II

8 분 소요

객체지향개념

상속

1. 상속(inheritance)의 정의와 장점

  • 기존의 클래스를 재사용해서 새로운 클래스를 작성하는 것
  • 두 클래스를 조상과 자손으로 관계를 맺어주는 것
  • 자손은 조상의 모든 멤버를 상속받는다. (생성자, 초기화블럭 제외)
  • 자손의 멤버개수는 조상보다 적을 수 없다. (같거나 많다.)

2. 클래스간의 관계

상속관계(inheritance)

  • 공통부분은 조상에서 관리하고 개별부분은 자손에서 관리한다.
  • 조상의 변경은 자손에 영향을 미치지만, 자손의 변경은 조상에 아무런 영향을 미치지 않는다. 상속

포함관계(composite)

  • 한 클래스의 멤버변수로 다른 클래스를 선언하는 것
  • 작은 단위의 클래스를 먼저 만들고, 이 들을 조합해서 하나의 커다란 클래스를 만든다.
    class Circle{
      int x;   //원점의 x좌표
      int y;   //원점의 y좌표
      int r;   //반지름(radius)
    }
    
    class Point{
      int x;
      int y;
    }
    class Circle{
      Point c = new Point();  //원점
      int r;  //반지름(radius)
    }
    

3. 클래스간의 관계결정하기 - 상속 vs. 포함

  • 가능한 한 많은 관계를 맺어주어 재사용성을 높이고 관리하기 쉽게 한다.
  • ‘is-a’와 ‘has-a’를 가지고 문장을 만들어 본다.
  • 상속관계: ‘~은 ~이다. (is-a)’
    class Circle extends Point{
      int r; //반지름
    }
    
  • 포함관계: ‘~은 ~을 가지고 있다.(has-a)’
    clas Circle{
      Point c = new Point();  //원점
      int r; //반지름
    }
    

4. 단일상속(single inheritance)

  • Java는 단일 상속만을 허용한다. (C++은 다중상속 허용)
  • Class TVCR extends TV, VCR{} //이와 같은 표현은 허용하지 않는다.
  • 비중이 높은 클래스 하나만 상속관계로, 나머지는 포함관계로 한다.
    class Tv{
      boolean power;  //전원상태(on/off)
      int channel;    //채널
    
      void power(){ power = !power; }
      void channelUp(){ ++channel; }
      void channelDown(){ --channel; }
    }
    
    class VCR{
      boolean power;  //전원상태(on/off)
      int counter = 0;
      void power(){}
      void play(){}
      void stop(){}
      void new(){}
      void ff(){}
    }
    
    class TVCR extends Tv{
      VCR vcr = new VCR();
    
      void play(){
        vcr.play();
      }
      void stop(){
         vcr.STOP();
      }
      void new(){
        vcr.new();
      }
      void ff(){
        vcr.ff();
      }
    }
    

5. Object클래스 - 모든 클래스의 최고조상

  • 조상이 없는 클래스는 자동적으로 Object클래스를 상속받게 된다.
  • 상속계층도의 최상위에는 Object클래스가 위치한다.
  • 모든 클래스는 Object 클래스에 정의된 11개의 메서드를 상속받는다.
  • toString(), equals(Object obj), hashCode()

오버라이딩

1. 오버라이딩(overriding)이란?

  • “조상클래스로부터 상속받은 메서드의 내용을 상속받는 클래스에 맞게 변경하는 것을 오버라이딩이라고 한다”
    class Point{
      int x;
      int y;
    
      string getLocation(){
        return "x: " + x + ", y:" + y;
      }
    }
    class Point3D extends Point{
      int z;
      String getLocation(){    //오버라이딩
        return "x: " + x + ", y:" + ",z:" + z;
      }
    }
    

2. 오버라이딩의 조건

  • 선언부가 같아야 한다. (이름, 매개변수, 리턴타입)
  • 접근제어자를 좁은 범위로 변경할 수 없다. (조상의 메서드가 protected라면, 범위가 같거나 넓은 protected나 public으로만 변경할 수 있다.)
  • 조상크래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

3. 오버로딩 vs. 오버라이딩

  • 오버로딩: 기존에 없는 새로운 메서드를 정의하는 것 (new)
  • 오버라이딩: 상속받은 메서드의 내용을 번경하는 것 (change, modify)
    class Parent{
      void parentMethod(){}
    }
    class Child extends Parent{
      void parentMethod(){}  //오버라이딩
      void parentMethod(int i){}  //오버로딩
    
      void childMethod(){}
      void chileMethod(int i){}  //오버로딩
      void chileMethod(){}  //에러- 중복정의
    }
    

4. super - 참조변수

  • this: 인스턴스 자신을 가리키는 참조변수. 인스턴스의 주소가 저장되어 있음. 모든 인스턴스 메서드에 지역변수로 숨겨진 채로 존재
  • super: this와 같음. 조상의 멤버와 자신의 멤버를 구별하는 데 사용.
    class Parent{
      int x = 10;
    }
    class Child extends Parent{
      int x = 20;
      void method(){
        System.out.println("x=" + x);
        System.out.println("this.x=" + this.x);
        System.out.println("super.x=" + super.x);
      }
    }
    

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

  • 자손클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 합쳐진 하나의 인스턴스가 생성된다.
  • 조상의 멤버들도 초기화되어야 하기 때문에 자손의 생성자의 첫 문장에서 조상의 생성자를 호출해야 한다.
    class Point extends Object{
      int x;
      int y;
    
      Point(){
        this(0,0);
      }
      Point(int x, int y){
        super(); //Object();
        this.x = x;
        this.y = y;
      }
    }
    

package와 import

1.패키지(package)

  • 서로 관련된 클래스와 인터페이스의 묶음
  • 클래스가 물리적으로 클래스파일(*.class)인 것 처럼, 패키지는 물리적으로 폴더이다. 패키지는 서브패키지를 가질 수 있으며, ‘.’으로 구분한다.
  • 클래스의 실제 이름(full name)은 패키지명이 포함된 것이다. (String클래스의 full name은 java.lang.String)
  • rt.jar은 Java API의 기본 클래스들을 압축한 파일

2. 패키지의 선언

  • 패키지는 소스파일에 첫 번째 문장(주석 제외)으로 단 한번 선언한다.
  • 하나의 소스파일에 둘 이상의 클래스가 포함된 경우, 모두 같은 패키지에 속하게 된다.(하나의 소스파일에 단 하나의 public클래스만 허용한다.)
  • 모든 클래스는 하나의 패키지에 속하며, 패키지가 선언되지 않은 클래스는 자동적으로 이름없는(default)패키지에 속한다.

3. 클래스패스(classpath) 설정

  • 클래스패스는 클래스파일(*.class)를 찾는 경로. 구분자는 ‘;’
  • 클래스패스에 패키지가 포함된 폴더나 jar파일을(*.jar) 나열한다.
  • 클래스패스가 없으면 자동적으로 현재 폴더가 포함되지만 클래스패스를 지정할 때는 현재 폴더(.)도 함께 추가해주어야 한다.
  • 클래스패스로 자동 포함된 폴더 for 클래스파일(*.class): 수동생성 해야함
  • 클래스패스로 자동 포함된 폴더 for jar파일(*.jar): JDK설치시 자동생성됨

4. import문

  • 사용할 클래스가 속한 패키지를 지정하는데 사용
  • import문을 사용하면 클래스를 사용할 때 패키지명을 생략할 수 있다.
  • java.lang패키지의 클래스는 import하지 않아도 사용할 수 있다.

5. import문의 선언

  • import문은 패키지문과 클래스선언의 사이에 선언한다.
  • import 패키지명.클래스명; 또는 import 패키지명.*;
  • import문은 컴파일 시에 처리되므로 프로그램의 성능에 아무런 영향을 미치지 않는다.
  • 다음의 두 코드는 서로 의미가 다르다
    import java.util.*;
    import java.text.*;    -> import.java.*;
    
  • 이름이 같은 클래스가 속한 두 패키지를 import할 때는 클래스 앞에 패키지명을 붙여줘야 한다.
    import java.sql.*; //java.sql.Date
    import java.util.*;  //java.util.Date
    

제어자

1. 제어자(modifier)란?

  • 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여한다.
  • 제어자는 크게 접근 제어자와 그 외의 제어자로 나뉜다.
  • 하나의 대상에 여러 개의 제어자를 조합해서 사용할 수 있으나, 접근제어자는 단 하나만 사용할 수 있다.
  • 접근 제어자: public, protected, default, private
  • 그 외: static, final, abstract, native, transient, synchronized, volatile, strictfp

2. static - 클래스의, 공통적인

  • static이 사용될 수 있는 곳 - 멤버변수, 메서드, 초기화 블럭
  • 대상: 멤버변수
    • 모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다.
    • 클래스변수는 인스턴스를 생성하지 않고도 사용 가능하다.
    • 클래스가 메모리에 로드될 때 생성된다.
  • 대상: 메서드
    • 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다.
    • static메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없다.
      class StaticTest{
      static int width = 200;
      static int height = 120;
      
      static{ //클래스 초기화 블럭
            //static변수의 복잡한 초기화 수행
      }
      static int max(int a, int b){
        return a > b ? a : b;
      }
      }
      

3. final - 마지막의, 변경될 수 없는

  • final이 사용될 수 있는 곳 - 클래스, 멤버변수, 지역변수
  • 대상: 클래스
    • 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.
  • 대상: 메서드
    • 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
  • 대상: 멤버변수, 지역변수
    • 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.
      final class FinalTest{
      final int MAX_SIZE = 10; //멤버변수
          
      final void getMaxSize(){
        final int LV = MAX_SIZE;  //지역변수
        return MAX_sIZE;
      }
      }
      class Child extends FinalTest{
      void getMaxSize(){} //에러. 오버라이딩 불가
      }
      

4. 생성자를 이용한 final 멤버변수 초기화

  • final이 붙은 변수는 상수이므로 보통은 선언과 초기화를 동시에 하지만, 인스턴스마다 고정값을 갖는 인스턴스 변수의 경우 생성자에서 초기화한다.
  • 카드의 무늬와 숫자는 한번 결정되면 바뀌지 않아야하는 경우
    Class Card{
      final int NUMBER;  //상수지만 선언과 함께 초기화
      final String KIND;
      static int width = 100;
      static int height = 250;
    
      Card(String kind, int num){
        KIND = kind;
        NUMBER = num;
      }
      Card(){
        this("HEART",1);
      }
      public string toString(){
        return ""+ KIND + " " + NUMBER;
      }
    }
    
    public static void main(string args[]){
      Card c = new Card("HEART",10);
      //C.NUMBER = 5;   에러
      System.out.println(c.KIND);
      System.out.println(c.NUMBER);
    }
    

5. abstract - 추상의, 미완성의

  • abstract가 사용될 수 있는 곳 - 클래스, 매서드
  • 대상: 클래스
    • 클래스 내에 추상메서드가 선언되어 있음을 의미한다.
  • 대상: 메서드
    • 선언부만 작성하고 구현부는 작성하지 않은 추상메서드임을 알린다.
      abstract class AbstractTest{ //추상클래스
      abstract void move();   //추상메서드
      }
      

6. 접근 제어자(access mdifier)

  • 멤버 또는 클래스에 사용되어, 외부로부터의 접근을 제한한다.
  • 접근 제어자가 사용될 수 있는 곳 - 클래스, 멤버변수, 메서드, 생성자
  • private: 같은 클래스 내에서만 접근이 가능하다.
  • default: 같은 패키지 내에서만 접근이 가능하다.
  • protected: 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.
  • public: 접근 제한이 전혀 없다.

7. 접근 제어자를 이용한 캡슐화

  • 접근 제어자를 사용하는 이유
    • 외부로부터 데이터를 보호하기 위해서
    • 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서

8. 생성자의 접근 제어자

  • 일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치한다.
  • 생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.
    final class Singleton{
      private static Singleton s = new Singleton();
      private Singleton(){ //생성자
            
      }
      public static Singleton getInstance(){
        if(s==null){
          s = new Singleton();
        }      
        return s;
      }
    }
    
    class SingletonTest{
      public static void main(String args[]){
        Singleton s = new Singleton(); //에러
        Singleton s1 = Singleton.getInstance();
      }
    }
    

9. 제어자의 조합

  • 클래스: public, (default), final, abstract
  • 메서드: 모든 접근 제어자, final, abstract, static
  • 멤버변수: 모든 접근 제어자, final, static
  • 지역변수: final
    1. 메서드에 static과 abstract를 함께 사용할 수 없다. - static메서드는 몸통(구현부)이 있는 메서드에만 사용할 수 있기 때문이다.
    2. 클래스에 abstract와 final을 동시에 사용할 수 없다. - 클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고, abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다.
    3. abstract메서드의 접근제어자가 private일 수 없다. - abstract메서드는 자손클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손클래스에서 접근할 수 없기 때문이다.
    4. 메서드에 private과 final을 같이 사용할 필요는 없다. - 접근 제어자가 private인 메서드는 오버라이딩될 수 없기 때문이다. 이 둘 중 하나만 사용해도 의미가 충분하다.

다형성(polymorphism)

1. 다형성이란?

  • 여러가지 형태를 가질 수 있는 능력
  • 하나의 참조변수로 여러 타입의 객체를 참조할 수 있는 것. 즉, 조상타입의 참조변수로 자손타입의 객체를 다룰 수 있는 것이 다형성
  • 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있지만, 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

2. 참조변수의 형변환

  • 서로 상속관계에 있는 타입간의 형변환만 가능하다.
  • 자손 타입에서 조상타입으로 형변환하는 경우, 형변환 생략가능
    • 자손타입 -> 조상타입 (Up-casting) : 형변환 생략가능
    • 자손타입 <- 조상타입 (Down-casting) : 형변환 생략불가
      public static void main(String args[]){
      Car car = null;
      FireEngine fe = new FireEngine();
      FireEngine fe2 = null;
      
      fe.water();
      car = fe;  // Car = (Car)fe; 조상<-자손
      few = (FireEngine)car;  // 자손<-조상
      fe.water();
      }
      

3. instanceof 연산자

  • 참조변수가 참조하는 인스턴스의 실제 타입을 체크하는데 사용
  • 이항연산자이며 피연산자는 참조형 변수와 타입. 연산결과는 true, false
  • instanceof의 연산결과가 true이면, 해당 타입으로 형변환이 가능하다.

4. 참조변수와 인스턴스변수의 연결

  • 멤버변수가 중복정의된 경우, 참조변수의 타입에 따라 연결되는 멤버변수가 달라진다. (참조변수타입에 영향받음)
  • 메서드가 중복정의된 경우, 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입에 정의된 메서드가 호출된다. (참조변수타입에 영향받지 않음)

5. 매개변수의 다형성

  • 참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.

6. 여러 종류의 객체를 하나의 배열로 다루기

  • 조상타입의 배열에 자손들의 객체를 담을 수 있다.
    Product p1 = new Tv();
    Product p2 = new Computer();
    Product p3 = new Audio();
    
    Product p[] = new Product[3];
    p[0] = new Tv();
    p[1] = new Computer();
    p[2] = new Audio();
    
    class Buyer{ //물건사는 사람
      int money = 1000;  //소유금액
      int bonusPoint = 0;  //보너스점수
    
      product[] cart = new Product[10];  //구입한 물건을 담은 배열
      int i=0;
    
      void buy(Product p){
        if(money < p.price){
          System.out.prinltn("잔액부족");
          return;
        }
        money -= p.price;
        bonusPoint += p.bonusPoint;
        cart[i++]= p;
      }
    }
    

태그: ,

카테고리:

업데이트: