오늘의하루

[JAVA] 객체지향 개념 다형성 상속 추상화 인터페이스 본문

JAVA

[JAVA] 객체지향 개념 다형성 상속 추상화 인터페이스

오늘의하루_master 2022. 8. 9. 12:23

1. 객체지향 언어의 특징

  1. 기존의 프로그래밍 언어에 몇 가지 규칙을 추가한 것이다.
  2. 코드의 재사용성이 높다.
  3. 코드의 관리가 수월하다.
  4. 제어자와 메서드를 이용해서 데이터를 보호하기 때문에 신뢰성이 높은 개발이 가능하다.
Class란? 변수와 메서드(객체의 멤버 <구성요소>)의 집합이다.

2. 인스턴스 생성 및 사용

인스턴스 생성이란? class로부터 인스턴스를 생성하는 것

클래스명 참조 변수명 = new 클래스명();
  • 생성 과정 = new 연산자를 통해 class명의 인스턴스 생성 후, 생성된 인스턴스 주소를 참조 변수에 저장

인스턴스를 사용한다는 것은 class에 있는 변수 혹은 메서드들을 사용한다는 것이다.

참조 변수명. 변수;
참조 변수명. 메서드();
  • 클래스에 접근하기 위해서는 인스턴스 주소가 저장돼있는 참조 변수가 필요하다.
    • class란? 서로 관련된 변수, 메서드들을 묶어서 하나의 타입으로 정의하는 것이다.

참조 변수와 인스턴스의 관계

한 개의 인스턴스를 여러 개의 참조 변수들이 가리키는 것은 가능하다.
한 개의 참조 변수가 여러 개의 인스턴스를 가리키는 것은 불가능하다.
Test t1 = new Test(); // 예: 주소 0X100
Test t2 = new Test(); // 예: 주소 0X200

t2 = t1; // 참조변수 t2에 t1의 주소를대입하며 같은 인스턴스를 가르킨다. (예: 주소 0X100)

3. 변수와 메서드

변수는 선언 위치에 따라 변수의 종류, 유효 범위(scope)가 결정된다.

class Test{ 
    // 🟢 클래스 영역
    int iv; // 인스턴스 변수
    static int cv; // 클래스 변수(static 변수, 공유 변수)
    
    // 🔵 메서드 영역
    void method(){
        int lv = 0; // 지역 변수
    }
    // 🔵메서드 영역끝
    
    // 🟢 클래스 영역끝
}
  • 클래스 변수는 클래스가 메모리에 올라갈 때 자동 생성된다.
    • 인스턴스 생성 없이 "클래스명. 클래스 변수명"으로 접근 가능
    • 프로그램 종료될 때 같이 소멸한다.
  • 인스턴스 변수는 인스턴스 생성 시 같이 생성된다.
    • 인스턴스 생성 후 "참조 변수. 인스턴스 변수명"으로 접근 가능
    • 인스턴스 생성 후 참조 변수 없을 시 가비지 컬렉터에 의해 자동 제거된다.
  • 지역 변수는 메서드 호출 시 생성되고 메서드 종료 시 소멸된다.

클래스 변수와 인스턴스 변수에 대한 이야기

메인 클래스가 실행이 되고 Call stack에 main함수가 실행되면 자동으로 클래스 멤버는 생성됩니다. 그리고 인스턴스를 생성하게 되면 해당 클래스 안에 클래스 멤버와 인스턴스 멤버가 있다면 인스턴스 멤버만 생성됩니다. 왜냐하면 클래스 멤버는 항상 공통된 값을 갖기 때문입니다. 


메서드(method)란? 어떤 값을 입력받아 처리하고 그 결과를 돌려주는 명령문의 집합입니다.

  • 반복적인 코드를 줄일 수 있다.
  • 하나의 메서드는 한 가지 기능만 수행하도록 하는 게 좋다.
  • 메서드는 클래스 영역에서만 정의할 수 있다.
  • 메서드는 선언부와 구현부로 나눠집니다.
    • 선언부에는 리턴 값의 타입, 메서드명, 매개변수가 있습니다.
    • 구현부에는 입력받은 값을 처리하는 역할 등이 있습니다.

return문

  • 메서드가 정상적으로 종료되었을 때 메서드를 호출한 곳으로 결과 값을 전달한다.
  • 메서드 선언부에 있는 리턴 값의 타입과 return 할 값의 타입이 일치해야 합니다.
    • 선언부에 리턴 값의 타입이 void인 경우 return을 생략 가능합니다.
    • void 이외의 타입은 모두 return이 필수로 있어야 합니다.

매개변수의 종류와 기능

  • 기본형 매개변수 : 변수의 값을 읽기만 할 수 있다. (read only)
    • 기본형 8가지 : boolean, byte, char, short, int, float, long, double
  • 참조형 매개변수 : 변수의 값을 읽기 및 수정할 수 있다. (read & write)

클래스 메서드와 인스턴스 메서드

  • 인스턴스 메서드
    • 인스턴스 생성 후 "참조 변수. 메서드명()"으로 호출 가능
    • 인스턴스 메서드 내에서 인스턴스 변수, 다른 인스턴스 메서드, 클래스 변수, 클래스 메서드 사용 가능
  • 클래스 메서드
    • 인스턴스 생성 없이 "클래스명. 메서드명()"으로 호출 가능
    • 메서드 내에서 인스턴스 변수, 메서드 사용 불가
    • 인스턴스 변수를 사용하지 않는 메서드가 있다면 static을 붙이는 것을 고려하는 것이 좋다.
class Test{
    int a = 1; // 인스턴스 변수
    static int b = 2; // 클래스 변수
    
    int method(){ // 인스턴스 메서드
        return a + b + staticMethod(49,51);
        // 인스턴스 변수 + 클래스 변수 + 클래스 메서드
    }
    
    static int staticMethod(int i, int y){ // 클래스 메서드
        return i+y;
    }
}

Test.staticMethod(4,8); // 12
Test t1 = new Test();
t1.method(); // 103

메서드 오버 로딩이란? 하나의 클래스에 같은 이름의 메서드를 여러 개 정의하는 것이다.

  • 메서드 명이 같아야 한다.
  • 매개변수의 개수 또는 타입이 달라야 한다.
  • 리턴 타입은 오버 로딩을 구현하는데 아무런 영향을 주지 못한다.
int add(int a, int b){return a+b;}
long add(long x, long y){return x+y;} // 오버로딩 o
long add(long a, int b){return a+b;} // 오버로딩 o
int add(int x, int y){return x+y;} // 오버로딩 x (그냥 중복 정의)

4. 생성자

  • 인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드
  • 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다.
    • 생성자가 없다면 컴파일러는 기본 생성자를 자동으로 추가해준다.
  • 생성자의 이름은 클래스의 이름과 같아야 하며, 리턴 값이 없다
  • 생성자에는 매개변수가 없는 기본 생성자와 매개변수가 있는 생성자로 나뉜다.
⭐ 기본 생성자
class Test{
    int value;
    // Test(){} >> 컴파일러가 자동으로 생성해준다.
}
⭐ 매개변수 있는 생성자
class Car{
    String color;
    int door;
    
    Car(){}
    // 언제나 같은 고정 값을 주고 싶다면?
    // Car(){ this("pink", 4); }
    
     // 생성자 this()
    Car(int door){ this("green"); }
    
    Car(String color){ this(color, 6); }
    
    // 위에 있는 this는 
    Car(String color, int door){
        this.color = color; // 참조변수 this
        this.door = door;
    }
}

Car a = new Car(4);
// 11번째 줄 생성자 호출
// this("green"); 수행 >> 13번째 줄 생성자 호출
// this(color, 6);  수행 >> color에는 "green"이 들어가 있다. >> 16번째 줄 생성자 호출
// color = "green" door = 6 인스턴스 생성 완료

다른 생성자를 호출하는 this()

this()는 같은 클래스의 다른 생성자를 호출할 때 사용하며, this()는 생성자의 첫 문장에서만 가능하다.

참조 변수 this

참조 변수 this는 호출하는 자기 자신을 가리키는 것을 의미한다.

생성자를 이용한 인스턴스 값 복사

class Car{
    String color;
    int door;
    
    Car(Car c){
        color = c.color;
        door = c.door;
    }
    
    Car(String color, int door){
        this.color = color;
        this.door = door;
    }
}

Car a = new Car("red",4);
Car b = new Car(a);
System.out.println(b.color); // "red"
System.out.println(b.door); // 4

5. 변수의 초기화

변수를 선언하고 처음으로 값을 저장하는 것을 말하며, 멤버 변수(인스턴스 변수, 클래스 변수)와 배열은 각 타입의 기본값으로 자동 초기화되므로 생략할 수 있지만 지역 변수의 경우 사용 전 꼭 초기화해줘야 한다.

  • boolean 자동 초기화 false
  • char 자동 초기화 '\u0000' (null)
  • byte, short, int 자동 초기화 0
  • long 자동 초기화 0L
  • float 자동 초기화 0.0f
  • double 자동 초기화 0.0
  • 참조형 변수 자동 초기화 null

초기화의 방법에는 3가지가 있다.

  •  명시적 초기화
int door = 4; // 기본형 변수의 초기화
Test t1 = new Test(); // 참조형 변수의 초기화
  • 생성자를 이용한 초기화
Test(int door){
    this.door = door;
}
  • 초기화 블록
    • 인스턴스 초기화 블록은 { }이며 인스턴스 생성될 때마다 생성자보다 먼저 실행된다.
      • 인스턴스 변수 초기화 시점은 인스턴스가 생성될 때이다.
    • 클래스 초기화 블록은 static { }이며 복잡한 초기화에 사용된다.
      • 클래스 변수 초기화 시점은 클래스가 처음 로딩될 때 단 한 번이다.
class InitTest{
    static int cv = 1;
    int iv = 2;
    
    static{ cv = 2; }
    { iv = 3 }
    InitTest(){ iv = 4; }
}

InitTest t1 = new InitTest();
// 초기화 순서
// 1. cv = 0 > 기본값 자동 초기화
// 2. cv = 1 > 명시적 초기화
// 3. cv = 2 > 클래스 초기화 블럭
// 4. cv = 2, iv = 0 > 인스턴스 변수 자동 초기화
// 5. cv = 2, iv = 2 > 인스턴스 변수 명시적 초기화
// 6. cv = 2, iv = 3 > 인스턴스 변수 초기화 블럭
// 7. cv = 2, iv = 4 > 인스턴스 변수 생성자 초기화

6. 상속과 포함

상속의 정의

  • 기존에 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.
    • 이렇게 만들어진 클래스는 부모와 자식으로 관계를 맺어주는 것이다.
    • 자식은 부모의 모든 멤버를 상속받는다. (생성자, 초기화 블록은 제외)
    • 자식의 멤버 개수는 부모보다 같거나 많다.
    • Java는 단일 상속만을 허용한다.
      • 만약 두 개의 클래스를 상속받고 싶다면 하나는 상속 하나는 포함으로 하면 된다.
  • 부모의 변경은 자식에게 영향을 미치지만 자식의 변경은 부모에게 아무런 영향을 주지 않는다.
class parents{ // int a를 가지고 있다.
    int a;
}

class child extends parents{ // int a와 int b를 가지고 있다.
   int b;
}

포함의 정의

  • 한 클래스의 멤버 변수로 다른 클래스를 선언하는 것
class point{
    int x;
    int y;
}

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

Q. 언제 상속하고 언제 포함을 써야 할까?

상속관계는 "~은 ~이다"
포함관계는 "~은 ~을 가지고 있다"

7. 오버 라이딩

부모 클래스로부터 상속받은 메서드의 내용을 자식 클래스에 맞게 변경하는 것을 오버 라이딩이라고 한다.

  • 선언부가 같아야 한다.
  • 접근 제어자를 좁은 범위로 변경할 수 없다.
    • 접근 제어자 : public > protected > default > private
  • 부모 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

오버 로딩(기존에 없는 새로운 메서드)과 오버 라이딩(상속받은 메서드의 내용을 변경)은 완전히 다른 개념이다.

class Test{
    void TestOver(){}
}

class Child extends Test{
    int x;
    
    void TestOver(){
        System.out.println("hi~ im overriding~");
    }
    void TestOver(int x){
        System.out.println("hi~ im over loading x = " + x);
    }
    
    void TestMethod(){}
    void TestMethod(int x){
        System.out.println("hi~ im TestMethod over loading x = "+ x);
    }
    void TestMethod(){
        System.out.println("hi~ im TestMethod overriding~");
    }
}

8. 참조 변수 super & 생성자 super()

참조 변수 super는 조상의 멤버와 자신의 멤버를 구별하기 위해 사용됩니다.

class parents{
    int x = 10;
    int a = 1;
    int b = 2;
    
    void shot(){
        System.out.println("Shot!!!");
    }
    String getLocation(){
        return "a = " + a + "b = " + b;
    }
}

class child extends parents{
    int x = 20;
    int c = 3;
    void get(){
        System.out.println("x = " + x); // 20
        System.out.println("this.x = " + this.x); // 20
        System.out.println("super.x = " + super.x); // 10
        
        super.shot();
    }
    
    String getLocation(){ // 오버라이딩 + 참조변수 super
        return super.getLocation() + "c = " + c;
    }
}

생성자 super()를 사용하는 이유는 부모의 생성자를 호출하기 위해서다.

  • 모든 생성자에는 다른 생성자를 호출하는 this() 혹은 super()가 첫 줄에 있어야 한다.
    • 없다면 컴파일러가 super()를 첫 줄에 삽입한다.
class point{
    int x;
    int y;
    
    point(){this(0,0)}
    
    point(int x, int y){
        // super();
        this.x = x;
        this.y = y;
    }
}

class Circle extends point{
    int r;
    
    Circle(){
        super(); // 부모 생성자 point() 호출
        r = 10;
    }
    
    Circle(int x, int y, int r){
        super(x, y); // 부모 생성자 point(x,y) 호출
        this.r = r;
    }
}

9. package & import

package란 서로 관련된 클래스와 인터페이스의 묶음을 의미합니다.

  • 클래스가 물리적으로 클래스 파일(*. class)인 것처럼 package는 물리적으로 폴더이다.
    • package는 Sub-package를 가질 수 있으며 '.'으로 구분된다.
  • 클래스의 실제 이름(full name)은 패키 지명이 포함된 것이다.
    • String 클래스의 실제 이름은 java.lang.String이다.
  • rt.jar은 Java API의 기본 클래스들을 압축한 파일이다.
  • 선언을 할 때는 소스파일 가장 첫 번째 문장으로 단 한 번만 선언한다.
  • 하나의 소스파일에 여러 개의 클래스가 포함된 경우, 모두 같은 package에 속하게 된다.
  • 모든 클래스는 하나의 package에 속하며, package가 선언되지 않은 클래스들은 자동적으로 이름 없는 패키지(unnamed)에 속하게 된다.

import문은 사용할 클래스가 속한 패키지를 지정하는 데 사용된다.

  • import문을 사용하면 클래스를 사용할 때 패키지 명을 생략할 수 있다.
// import 없음
java.util.Date today = new java.util.Date();

/*===========================================*/

import java.util.*;
Date today = new Date();
  • import문은 패키지 선언과 클래스 선언 사이에 선언한다.
    • import 패키 지명. 클래스명; (해당 클래스만 import 한다.)
    • import 패키 지명.*; (패키지에 속한 모든 클래스를 import 한다.)
  • import문은 컴파일 시에 처리되므로 프로그램 성능에는 아무런 영향이 없다.

10. 제어자(modifier)

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

static - 공통적인

static이 사용될 수 있는 곳은 멤버 변수, 메서드, 초기화 블록이다.

  • static 멤버 변수
    • 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
  • static 메서드
    • 인스턴스 생성 없이 호출할 수 있는 static 메서드가 된다.

final - 마지막의

final이 사용될 수 있는 곳은 클래스, 멤버 변수, 메서드, 지역 변수이다. 

  • final 클래스
    • 변경될 수 없는 클래스를 말하며 다른 클래스의 부모가 될 수 없다.
  • final 메서드
    • 오버 라이딩할 수 없는 메서드가 된다.
    • 오버 로딩은 가능하다.
    • private과 final을 같이 사용할 필요는 없다.
      • 두 제어자 모두 오버 라이딩을 할 수 없기 때문에 둘 중 하나만 사용해도 의미가 충분하다.
  • final 멤버 변수 & final 지역변수
    • 변경할 수 없는 상수가 된다.
    • 상수는 보통 선언과 동시에 초기화를 하지만 생성자를 통해 초기화할 수 있다.
final class Test{
    final int door;
    final String color;
    // 생성자에서 단 한번만 초기화 할 수 있다.
    
    static int width = 100;
    
    Test(){this(4,"red");}
    
    Test(int door, String color){
        this.door = door;
        this.color = color;
    }
}

Test t1 = new Test(10, "green");
t1.door = 4; // 💥 Error 발생 : 상수는 변경 할 수 없다.

abstract - 추상의

abstract가 사용될 수 있는 곳은 클래스, 메서드이다. (static, final, private 제어자와 같이 사용할 수 없다.)

  • abstract 클래스 (추상 클래스)
    • 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
  • abstract 메서드 (추상 메서드)
    • 선언 부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.
abstract class Test{ // 추상 클래스
    abstract void method(); // 추상 메서드
}

접근 제어자 상세 내용

접근 제어자는 외부로부터 데이터를 보호하기 위함이며, 외부에는 불필요하고 내부적으로만 사용되는 부분을 감추기 위 해 사용된다.

  • public : 접근 제한이 전혀 없다.
    • class와 멤버 변수에 사용 가능
  • protected : 같은 패키지 내에서, 그리고 다른 패키지의 자식 클래스에서 접근이 가능하다.
    • 멤버 변수에 사용 가능
  • default : 같은 패키지 내에서만 접근이 가능하다.
    • class와 멤버 변수에 사용 가능
  • private : 같은 클래스 내에서만 접근이 가능하다.
    • 멤버 변수에 사용 가능
public class MyClass {
    public static void main(String args[]) {
        Time t1 = new Time(12);
        System.out.println(t1.getHour());
    }
}

class Time{
    private int hour;
    
    Time(int hour){
        setHour(hour);
    }
    public int getHour(){ return hour; }
    public void setHour(int hour){
        if(hour < 0 || hour > 23){
            return;
        }
        this.hour = hour;
    }
}

11. 다형성

부모 타입의 참조 변수로 자식 타입의 객체를 다룰 수 있는 것이 다형성이다.

  • 반대로 자식 타입의 참조 변수로 부모 타입의 인스턴스는 다룰 수 없다.
class Tv{
    int channel;
    
    void channelUp(){ ++channel; }
}

class SmartTv extends Tv{
    String text;
    void caption(){ System.out.println(text); }
}

Tv t = new SmartTv();

// 참조변수 t가 접근 가능한 멤버는?
t.channel = 7;
t.channelUp();

 

생성된 인스턴스에는 총 멤버 4개를 가지고 있지만 다형성을 사용해서 부모 타입으로 자식 객체를 다룰 때는 멤버 2개만 다룰 수 있습니다.

참조 변수의 형 변환

  • 서로 상속관계에 있는 타입 간의 형 변환만 가능하다.
  • 자식 타입에서 부모 타입으로 형 변환하는 경우, 형 변환 생략 가능
public class MyClass {
    public static void main(String args[]) {
        FireEngine fe = new FireEngine();
        fe.water();
        
        FireEngine fe2 = null;
        Car c = null;
        c = (Car)fe; // 참조변수 c가 사용할 수 있는 멤버 3개
        // 부모 <- 자식
        // c.water(); 💥 Error!
        fe2 = (FireEngine)c;
        // 자식 <- 부모
        fe2.water();
    }
}

class Car{
    String color;
    int door;
    
    void drive(){System.out.println("drive Brrr~");}
}

class FireEngine extends Car{
    void water(){System.out.println("water~");}
}

instanceof 연산자

참조 변수가 참조하는 인스턴스의 실제 타입을 체크하는 데 사용된다.

  • 연산 결과는 true, false로 반환한다.
  • instanceof 연산 결과가 true이면 해당 타입으로 형 변환이 가능하다.
  • A instanceof B : A가 가리키는 객체의 실제 타입이 B의 자식 클래스이거나 자기 자신일 경우 true를 반환한다.
FireEngine fe = new FireEngine);
        
if (fe instanceof FireEngine){ // true
    System.out.println("this is a FireEngine instance.");
}
if (fe instanceof Car){ // true
    System.out.println("this is a Car instance");
}
if (fe instanceof Object){ // true
    System.out.println("this is a Object instance");
}

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

  • 멤버 변수가 중복되는 경우, 참조 변수의 타입에 따라 연결되는 멤버 변수가 달라진다.
    • 참조 변수 타입에 영향을 받는다.
  • 메서드가 중복 정의된 경우, 참조 변수의 타입에 관계없이 항상 실제 인스턴스의 타입에 정의된 메서드를 호출한다.
    • 참조 변수 타입에 영향받지 않는다.
public class MyClass {
    public static void main(String args[]) {
        parent p = new child();
        child c = new child();
        
        System.out.println("p.x = " + p.x); // 10
        p.method(); // "child method"
        
        System.out.println("c.x = " + c.x); // 20
        c.method(); // "child method"
    }
}

class parent{
    int x = 10;
    
    void method(){
        System.out.println("parents method");
    }
}

class child extends parent{
    int x = 20;
    
    void method(){
        System.out.println("child method");
    }
}

매개변수의 다형성

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

public class MyClass {
    public static void main(String args[]) {
        product t = new Tv(10);
        product c = new computer(5);
        
        Buyer b = new Buyer();
        b.buy(t);
        System.out.println(b.money); // 90
        b.buy(c);
        System.out.println(b.money); // 85
    }
}

class product{
    int price;
    
    product(int x){
        price = x;
    }
}

class Tv extends product{
    Tv(int x){
        super(x);
    }
}
class computer extends product{
    computer(int x){
        super(x);
    }
}

class Buyer{
    int money = 100;
    
    void buy(product p){ // 매개변수에 다형성을 이용한 예제
        money = money - p.price;
    }
    // =======================================================
    // 다형성을 이용하지 않았을 경우는?
    // - 각각 타입에 맞게 만들어줘야한다.
    void buy1(Tv t){
        money = money - t.price;
    }
    void buy2(computer c){
        money = money - c.price;
    }
}

여러 종류의 객체를 하나의 배열로 다룰 수 있다.

부모 타입의 배열에 자식들의 객체를 담을 수 있다.

class product{
    int price;
    
    product(int x){
        price = x;
    }
}

class Tv extends product{
    Tv(int x){
        super(x);
    }
}
class computer extends product{
    computer(int x){
        super(x);
    }
}

product p[] = new product[2];
p[0] = new Tv(5);
p[1] = new computer(10);

java.util.Vector를 이용하여 모든 종류의 객체들을 저장할 수 있는 클래스

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

12. 추상 클래스 & 추상 메서드(abstract)

추상 클래스

  • 추상 메서드를 포함하고 있는 클래스
  • 일반 메서드는 추상 메서드를 호출할 수 있다.
    • 호출할 때 필요한 건 선언부 이기 때문이다.
  • 인스턴스를 생성할 수 없다.
  • 여러 클래스에 공통적으로 사용될 수 있는 부분을 추상 클래스로 만든다.
abstract class Test{
    int x;
    
    Test(){ x = 0; } // 추상 클래스도 생성자가 있어야 한다.
    
    abstract void method(int y); // 추상 메서드
    
    void play(){
        method(int y); // 추상 메서드 사용
    }
}
// 추상 클래스도 결국에는 클래스다.

추상 메서드

  • 선언 부만 있고 구현부가 없는 메서드
  • 꼭 필요하지만 자식마다 다르게 구현될 것으로 예상되는 경우에 사용한다.
  • 추상 클래스를 상속받은 자식 클래스에서는 부모의 추상 메서드 모두의 구현부를 완성해야 한다.
abstract class Test{ // 추상 클래스
   abstract void method1();
   abstract void method2();
}

// 모두 구현하면 해당 클래스는 사용이 가능하다.
class child extends Test{
    void method1(){}
    void method2(){}
}

// 하나라도 구현하지 않으면 해당 클래스는 추상 클래스가 된다.
abstract class child2 extends Test{
    void method1(){}
}

13. 인터페이스

장점

  • 개발 시간을 단축시킬 수 있다.
  • 표중화가 가능하다.
  • 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
  • 독립적인 프로그래밍이 가능하다.

특징

  • 일종의 추상 클래스이며, 추상 클래스보다 추상화 정도가 높다.
  • 실제 구현된 것이 전혀 없는 기본 설계도이다.
  • 추상 메서드와 상수만을 멤버로 가질 수 있다.
  • 인스턴스를 생성할 수 없고, 클래스 작성에 도움을 줄 목적으로 사용된다.
  • 미리 정해진 규칙에 맞게 구현하도록 표준을 제시하는 데 사용된다.
  • 모든 멤버 변수는 public static final이므로 생략이 가능하다.
  • 모든 메서드는 public abstract이므로 생략이 가능하다.
interface Myinterface{
    public static final int x = 1; // 상수
    public abstract void method(); // 추상 메서드
    
    int y = 2; // public static final 생략
    void method2(); // public abstract 생략
}

인터페이스의 상속

  • 인터페이스도 상속이 가능하며, 클래스와 달리 다중 상속이 가능하다.
  • 인터페이스는 Object클래스 같은 최고 조상이 없다.
interface Test1{
    void method1(int x, int y);
}

interface Test2{
    void attack(Unit u);
}

interface Test3 extends Test1, Test2 { } // 다중 상속

인터페이스의 구현

  • 인터페이스를 구현하기 위해서는 implements를 사용해야 한다.
  • 인터페이스에 정의된 모든 추상 메서드를 완성해야 한다.
  • 클래스 상속과 인터페이스의 구현이 동시에 가능하다.
interface carInfo{
    void move();
    void stop(Car c);
}

class Car{
    String color;
    int door;
    
    Car(String color, int door){
        this.color = color;
        this.door = door;
    }
}

class UserCar extends Car implements carInfo{
    int x;
    int y;
    
    UserCar(String color, int door, int x, int y){
        super(color, door);
        this.x = x;
        this.y = y;
    }
    
    public void move(){
        System.out.println("your " + this.color + " car has moved to " + x + ", " + y );
    }
    public void stop(Car c){
        System.out.println("your " + c.color + " car has stopped.");
    }
}

인터페이스를 이용한 다형성

  • 인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.
  • 인터페이스를 메서드의 매개변수 타입으로 지정할 수 있다.
  • 인터페이스를 메서드의 리턴 타입으로 지정할 수 있다.
public class MyClass {
    public static void main(String args[]) {
        Tank tank = new Tank();
        Marine marine = new Marine();
        SCV scv = new SCV();
        battle_cruiser Battle_cruiser = new battle_cruiser();
        
        scv.repair(tank);
        // scv.repair(marine); >> Error
        scv.repair(scv);
        scv.repair(Battle_cruiser);
    }
}

interface Repairable{}

class Unit {
    int hitPoint;
    final int MAX_HP;
    
    Unit (int x){
        MAX_HP = x;
    }
}

class GroundUnit extends Unit{
    GroundUnit(int x){
        super(x);
    }
}

class Marine extends GroundUnit{
    Marine(){
        super(50);
        hitPoint = MAX_HP;
    }
    
    public String toString(){
        return "Marine";
    }
}

class Tank extends GroundUnit implements Repairable{
    Tank(){
        super(150);
        hitPoint = MAX_HP;
    }
    
    public String toString(){
        return "Tank";
    }
}

class SCV extends GroundUnit implements Repairable{
    SCV(){
        super(60);
        hitPoint = MAX_HP;
    }
    
    void repair(Repairable r){
        if (r instanceof Unit){
            Unit u = (Unit)r;
            while (u.hitPoint != u.MAX_HP){
                u.hitPoint++;
            }
        }
    }
}

class AirUnit extends Unit{
    AirUnit(int x){
        super(x);
    }
}

class battle_cruiser extends AirUnit implements Repairable{
    battle_cruiser(){
        super(500);
        hitPoint = MAX_HP;
    }
    
    public String toString(){
        return "battle_cruiser";
    }
}

인터페이스의 이해

  • 인터페이스는 두 객체 간의 중간 역할을 한다.
  • 간접적인 관계를 만들어서 데이터를 보호한다.
// 예제 1
class A{
    public void method(I i){
        i.methodB();
    }
}

interface I {
    void methodB();
}

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

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

A a = new A();
B b = new B();
C c = new C();
a.method(b); // "methodB()"
a.method(c); // "methodB() in c"
// a.method(a); Error a는 interface I를 구현하지 않았다.
        
a.method(new B()); // "methodB()"
a.method(new C()); // "methodB() in c"

// 예제 2
public class MyClass {
    public static void main(String args[]) {
        Time t1 = new Time(12);
        t1.setHour(t1);
        System.out.println(t1.getHour());
    }
}

interface TimeUnit{ // 추상 메서드
    int getHour();
    void setHour(TimeUnit t);
}

class test{
    int x;
    
    test(int x){
        this.x = x;
    }
}

class Time extends test implements TimeUnit{ // 상속과 구현
    private int hour;
    
    Time(int x){
        super(x); // 부모 생성자 호출
    }
    
    public int getHour(){ return hour; }
    public void setHour(TimeUnit t){ // 인터페이스를 매개변수 타입으로 지정
        test time = (test)t; // 형변환
        if (time.x < 0 || time.x > 23){
            return;
        }
        this.hour = time.x;
    }
}

디폴트 메서드

  • 인터페이스에 디폴트 메서드, static 메서드를 추가 가능하게 바뀜(JDK 1.8)
  • 디폴드 메서드는 인터페이스에 추가된 일반 메서드
  • 디폴드 메서드가 기존의 메서드와 충돌하는 경우 아래와 같이 해결한다.
    • 여러 인터페이스의 디폴트 메서드 간의 충돌
      • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버 라이딩해야 한다.
    • 디폴트 메서드와 부모 클래스의 메서드 간의 출동
      • 부모 클래스의 메서드가 상속되고 디폴트 메서드는 무시된다.
interface A{
    void method();
    default void newMethod(){
        System.out.println("default method");
    }
}

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

class C extends B implements A{
    public void method(){
        System.out.println("interface A method()");
    }
}

C c = new C();
c.newMethod(); // "parents method"
c.method(); // "interface A method()"

14. 내부 클래스

  • 클래스 안에 선언된 클래스
  • 특정 클래스 내에서만 주로 사용되는 클래스를 내부 클래스로 선언한다.
  • 내부 클래스에서는 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다.(캡슐화)
  • 내부 클래스의 종류는 변수의 선언 위치에 따른 종류와 동일하다.
    • 인스턴스 내부 클래스
      • 외부 클래스 멤버 변수 선언 위치에 선언한다.
      • 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다.
      • JDK 1.8 기준 상수, 인스턴스 멤버(변수, 메서드)만 선언이 가능합니다.
    • 스태틱 내부 클래스
      • 외부 클래스 멤버 변수 선언 위치에 선언한다.
      • 주로 외부 클래스의 static 멤버, 특히 static메서드에서 사용될 목적으로 선언된다.
      • JDK 1.8 기준 상수, 변수, 인스턴스 멤버, static 멤버를 선언이 가능합니다.
    • 지역 내부 클래스
      • 외부 클래스의 메서나 초기화 블록 안에 선언한다.
      • 선언된 내부에서만 사용될 수 있다.
      • JDK 1.8 기준 상수, 인스턴스 멤버(변수, 메서드)만 선언이 가능합니다.
    • 익명 내부 클래스
      • 클래스 선언과 객체의 생성을 동시에 하는 이름 없는 클래스(1회용)
class Outer{ // 외부 클래스
    class InstanceInner{} // 인스턴스 내부 클래스
    static class StaticInner{} // 스태틱 내부 클래스
    
    void method(){
        class LocalInner{} // 지역 내부 클래스
    }
}

내부 클래스의 제어자와 접근성

  • 내부 클래스의 접근제어자는 변수에 사용할 수 있는 접근제어자와 동일하다.
class Outer{
    private class InstanceInner{}
    protected static class StaticInner{}
    
    void method(){
        class LocalInner{}
    }
}
  • 내부 클래스가 접근할 수 있는 외부 클래스에 대해 알아보기
// 스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다.
Outer.StaticInner si = new Outer.StaticInner();

// 인스턴스 내부 클래스를 생성하기 위해서는 우선 외부 클래스를 생성해야한다.
Outer oi = new Outer();
Outer.InstanceInner ii = oi.new InstanceInner();
ii.method();

System.out.println("ii.iiv1 = " + ii.iiv1);
System.out.println("Outer.StaticInner.sicv = " + Outer.StaticInner.sicv);

class Outer{
    private int outeriv = 1;
    static int outercv = 2;
    final int outerIV = 3;
    int value = 10;
    
    class InstanceInner{
        int iiv1 = outeriv; // 외부 클래스의 private 멤버에도 접근이 가능하다.
        int iiv2 = outercv; 
        int iiv3 = outerIV;
        int value = 30;
        void method(){
            int value = 20;
            System.out.println("value = " + value); // 20 가까운 value
            System.out.println("this.value = " + this.value); // 30 호출한 자기 자신 value
            System.out.println("Outer.this.value = " + Outer.this.value); // 10 외부 클래스의 value
        }
    }
    static class StaticInner{
        // int siv1 = outeriv; 스태틱 클래스는 외부 인스턴스 멤버에 접근할 수 없다.
        // int siv2 = outerIV; 스태틱 클래스는 외부 인스턴스 멤버에 접근할 수 없다.
        int siv3 = outercv; 
    }
    
    void method(){
        int lv = 4;
        final int LV = 5;
        
        class LocalInner{
            int liv1 = outeriv;
            int liv2 = outercv;
            int liv3 = outerIV;
            int liv4 = lv; // 책에서는 안된다고 나왔지만 실제로는 가능하다.
            int liv5 = LV;
            int liv6 = Outer.outercv;
            Outer.InstanceInner aa = new Outer.InstanceInner();
            int InstanceAdd = aa.iiv1 + aa.iiv2 + aa.iiv3;
            void get(){
                System.out.println(liv4);
            }
        }
        LocalInner lc = new LocalInner();
        lc.get();
    }
}

내부 클래스에서 static 멤버를 어디서 선언해야 할 수 있을까?

  • JDK 1.8 기준으로는 static 멤버는 static 클래스에서만 선언이 가능하다.
class Inner{
    class InstanceInner{
        int iv = 100;
        //static int cv = 200; // JDK 17.01 부터 사용 가능
        final static int CONST = 300;
        
        static void InstanceInnerMethod(){} // JDK 17.01 부터 사용 가능
    }
    static class StaticInner{
        int iv = 400;
        static int cv = 500;
    }
    
    void method(){
        class LocalInner{
            int iv = 600;
            static int cv = 700; // JDK 17.01 부터 사용 가능
            final static int CONST = 300;
            
            static void LocalInnerMethod(){} // JDK 17.01 부터 사용 가능
        }
    }
}

익명 클래스

이름이 없는 일회용 클래스, 단하나의 객체만을 생성한다.

Object iv = new Object(){ void method(){} }; // 익명 클래스

static Object cv = new Object(){ void method(){} }; // 익명 클래스

void method(){
    Object lv = new Object(){ void method(){} }; // 익명 클래스
}
Test t1 = new Test(){ // 익명 클래스
     public String str2 = "sung";
     int iv2 = 20;
            
     public void method1(){ // 오버라이딩
         System.out.println(str2);
         System.out.println(iv2);
     }
};
t1.method1(); // sung 20
t1.method2(); // jang 14
// System.out.println(t1.str2); 익명클래스에서 만든 변수를 별도로 접근은 불가능하다.

class Test{
    String str1 = "jang";
    int iv1 = 14;
    
    void method1(){
        System.out.println(str1);
    }
    void method2(){
        System.out.println(str1);
        System.out.println(iv1);
    }
}
Comments