오늘의하루

Java 객체지향 공부 다형성, interface, 상속, 포함 등 본문

JAVA

Java 객체지향 공부 다형성, interface, 상속, 포함 등

오늘의하루_master 2022. 8. 22. 19:09

Java에서 가장 중요한 개념인 객체지향에 대해 공부한 자료입니다.

객체지향 개념

코드의 재사용성을 높이고 코드의 관리를 수월하게 하며 신뢰성이 높은 프로그램의 개발을 가능하게 만들어준다.

클래스와 객체의 정의

  • 클래스란 객체를 정의해 놓은 것을 말한다.
  • 객체란 실제로 존재하는 개념이다.
    • 객체는 인스턴스를 포함하는 일반적인 의미이다.
  • 인스턴스화란 클래스로부터 인스턴스를 생성하는 것을 말한다.

객체의 구성 요소

객체에는 속성(변수)과 기능(메서드)으로 이루어져 있다.

  • 객체에 있는 속성과 기능을 객체의 멤버라고 부른다.

인스턴스 생성과 사용

인스턴스를 생성하는 방법은 일반적으로 "클래스명 참조 변수명 = new 클래스명()"이다.

Tv t; // 객체를 다루기 위한 참조변수를 선언
Tv t = new Tv(); // 객체 생성 후, 생성된 객체의 주고를 참조변수에 저장

인스턴스를 사용하는 방법은 "참조 변수명. 객체 멤버"이다.

class Tv{
    int channel; // 변수
    void channelUp(){ // 메서드
        channel++; // 변수 channel을 1 증가시킨다.
    }
}
Tv t = new Tv(); // 인스턴스 생성 후 생성된 인스턴스의 주소를 참조변수 t에 저장한다.
t.channel = 7; // 생성된 인스턴스의 channel 변수에 7을 대입
t.channelUp(); // 생성된 인스턴스의 channelUp() 메서드를 사용

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

여러 개의 참조 변수가 하나의 인스턴스 주소를 가지고 있는 건 가능하지만 하나의 참조 변수가 여러 개의 인스턴스 주소를 가지고 있는 건 불가능하다.

클래스의 정의

클래스란 데이터와 함수의 결합(구조체 + 함수)을 의미하며, 서로 관련된 값을 묶어서 하나의 타입으로 정의합니다.

선언 위치에 따른 변수의 종류

변수는 선언되는 위치에 따라 종류와 범위(scope)가 결정된다.

class Test{
// 클래스 영역 시작
    int iv; // 인스턴스 변수
    static int cv; // 클래스 변수
    // 메서드 영역 시작
    void method(){
        int lv = 0; // 지역 변수
    }
    // 메서드 영역 끝
// 클래스 영역 끝
}
  • 클래스 변수
    • 클래스 영역에서 선언되며 클래스가 메모리에 올라갈 때 생성된다.
    • 인스턴스 생성 없이 "클래스명. 클래스 변수명"으로 접근 및 사용이 가능하다.
    • 클래스가 로딩될 때 생성되며 프로그램이 종료될 때 소멸한다.
  • 인스턴스 변수
    • 클래스 영역에서 선언되며 인스턴스가 생성될 때 같이 생성된다.
    • 인스턴스 생성 후 "참조 변수명. 인스턴스 변수명"으로 접근 및 사용이 가능하다.
    • 생성되는 인스턴스마다 각기 다른 값을 유지할 수 있다.
  • 지역변수
    • 메서드 영역에서 선언되며 메서드가 호출될 때 생성된다.
    • 메서드 종료 시 함께 소멸된다.

메서드(Method)

메서드란 작업을 수행하기 위한 명령문의 집합이며 어떤 값을 입력받아서 처리하고 그 결과를 돌려준다.

  • 하나의 메서드는 한 가지의 기능만 수행하도록 작성하는 것이 좋다.
  • 메서드는 선언부와 구현부로 나뉘며 클래스 영역에만 정의할 수 있다.
  • 호출 방법은 "참조 변수. 메서드명(매개변수)"이다.
리턴타입 메서드명(매개변수...){ // 선언부
    메서드 호출 시 수행될 코드  // 구현부 
}

int add(int a, int b){
    return a+b;
}

return문

현재 실행 중인 메서드를 종료하고 호출한 메서드로 돌아간다.

  • 반환 값이 없는 경우 "return;"만 작성해주면 된다.
    • 메서드의 리턴 타입이 void인 경우는 return문을 생략할 수 있다.
  • 반환 값이 있는 경우 "return 반환 값;"으로 작성해주면 된다. ( 필수 )
    • 메서드의 리턴 타입과 반환 값의 타입이 일치해야 한다.

JVM의 메모리 구조

  1. 메서드 영역(Method Area)
    • 클래스 멤버의 정보가 저장되는 곳이다.
  2. 호출 스택(Call Stack)
    • 메서드의 작업공간이다.
    • 메서드가 호출되면 필요한 메모리 공간을 할당받는다.
    • 메서드가 종료되면 사용하던 메모리를 반환한다.
  3. 힙(Heap)
    1. 인스턴스가 생성되는 공간이다.
    2. new 연산자에 의해 생성되는 배열과 객체는 모두 여기에 생성된다.

호출 스택에 대해 좀 더 자세한 내용은 아래 글에 작성되어 있다.

 

[javascript] Call Stack과 Event Loop 알아보기

아래 내용은 제로초님의 강의를 듣고 나름대로 정리를 하는 내용입니다. [리뉴얼] Node.js 교과서 - 기본부터 프로젝트 실습까지 - 인프런 | 강의 노드가 무엇인지부터, 자바스크립트 최신 문법, 노

jangto.tistory.com

기본형 매개변수와 참조형 매개변수

기본형 매개변수의 경우 변수의 값을 읽기만 가능하고, 참조형 매개변수의 경우 변수의 값을 읽기 및 변경까지 가능하다.

class Study{
    public static void method(String[] args){
        Test t = new Test();
        t.x = 10;
        System.out.println("Test의 t값은 = " + t.x); // 10
    
        change(t.x); // 1000

        System.out.println("Test의 t값은 = " + t.x); // 10    
        
        change2(t); // 1000
        
        System.out.println("Test의 t값은 = " + t.x); // 1000
        
    }
    static void change(int x){ // 기본형 매개변수
        x = 1000;
        System.out.println("change() t.x = " + t.x);
    }
    
    static void change2(Test t){
        t.x = 1000;
        System.out.println("change2() t.x = " + t.x);
    }
}

class Test{
    int x;
}

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

인스턴스 메서드

  • 인스턴스 생성 후 "참조 변수명. 메서드명(매개변수)"로 호출이 가능하다.
  • 인스턴스 변수 또는 인스턴스 메서드와 관련된 작업을 하는 메서드이다.
  • 메서드 내부에서 인스턴스 멤버 및 클래스 멤버를 사용할 수 있다.

클래스 메서드

  • 인스턴스 생성 없이 "클래스명. 메서드명(매개변수)"로 호출이 가능하다.
  • 인스턴스 변수 또는 인스턴스 메서드와 관련 없는 작업을 하는 메서드이다.
  • 클래스 메서드 내부에서는 인스턴스 멤버 사용이 불가능하지만 클래스 멤버는 사용 가능하다.
class Test{
    long a, b; // 인스턴스 변수
    
    long add1(){ // 인스턴스 메서드
        long sum = add2(100L, 200L); // 클래스 메서드 호출
        return sum+a+b;
    }
    
    static long add2(long x, long y){ // 클래스 메서드
        return x+y;
    }
}

// 클래스 메서드 호출
System.out.println(Test.add2(300L,400L)); // 700L

Test t = new Test();
t.a = 100L;
t.b = 200L;
// 인스턴스 메서드 호출
System.out.println(t.add1()); // 600L

메서드 오버 로딩(Method overloading)

메서드 오버 로딩은 아래에 있는 조건을 만족하는 같은 이름의 메서드를 여러 개 정의한 것을 말한다.

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

생성자(Constructor)

생성자란 인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드를 말한다.

  • 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다.
    • new 연산자에 의해 메모리(heap)에 해당 클래스의 인스턴스가 생성된다.
    • 생성자 "클래스명()"가 호출되어 수행한다.
    • new 연산자의 결과로 생성된 해당 클래스의 인스턴스 주소가 반환되어 참조 변수에 저장된다.
  • 생성자의 이름은 클래스명과 같아야 한다.
  • 기본 생성자란 매개변수가 없는 생성자를 말한다.
    • 클래스에 생성자가 하나도 없을 때 컴파일러가 기본 생성자를 자동으로 추가해준다.
    • 기본 생성자의 형태는 "클래스명(){}"이다.
class Test1{
    int x;
    // Test1(){}; >> 컴파일 시 자동으로 기본생성자가 생성된다.
}

class Test2{
    int x;
    Test2(int num){ // 매개변수 있는 생성자
        x = num;
    }
}

Test1 t1 = new Test1(); // ok
Test2 t2 = new Test2(); // Error!
// 이걸 쓰고 싶으면 해당 클래스안에 기본생성자를 하나 만들어줘야 한다.

Test2 t3 = new Test2(1); // ok

 

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

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

class Car{
    String color;
    int door;
    
    Car(){
        this("white", 4); // "this()" 사용
        // Car("white", 4)를 의미합니다.
    }
    Car(String c, int d){
        color = c;
        door = d;
    }
}

참조 변수 this

this는 인스턴스 자신을 가리키는 참조 변수이며, 인스턴스의 주소가 저장되어 있다.

  • 모든 인스턴스 메서드에 지역변수로 숨겨진 채로 존재한다.
class Car{
    String color;
    int door;
    
    Car(){
        this("white", 4);
    }
    Car(String color, int door){
       this.color = color;
       this.door = door;
    }
}

변수의 초기화

변수를 선언하고 처음으로 값을 저장하는 것을 말하며 멤버 변수와 배열은 각 타입의 기본값으로 자동 초기화되므로 초기화를 생략할 수 있다.

  • 지역변수의 경우 사용 전 필수로 초기화를 해줘야 한다.
class Test{
    int x;
    
    void method(){
       int lv1; // 지역변수
       int lv2 = lv1; // Error! 지역변수를 초기화 하지 않고 사용했다.
    }
}

자료형과 기본값 알아보기

  • boolean = false
  • char = '\u0000' (null)
  • byte = 0
  • short = 0
  • int = 0
  • long = 0L
  • float = 0.0f
  • double = 0.0
  • 참조형 변수 = null

멤버 변수의 초기화 방법

  • 명시적 초기화
class Test{
    int door = 4;// 기본형 변수의 초기화
    Engine e = new Engine(); // 참조형 변수의 초기화
}
  • 생성자
class Test{
   int door;
   Stirng color;
   Test(int door, String color){ // 생성자
       this.door = door;
       this.color = color;
   }
}
  • 초기화 블록 
    • 인스턴스 초기화 블록 : {}
      • 생성자에서 공통적으로 수행되는 작업에 사용된다.
      • 인스턴스 생성될 때마다 생성자보다 먼저 실행된다.
    • 클래스 초기화 블록 : static {}
      • 클래스 변수의 복잡한 초기화에 사용되며 클래스가 로딩될 때 실행된다.
class Test{
    static int[] arr = new int[10]; // 명시적 초기화
    
    static{ // 클래스 초기화 블록
        for(int i = 0; i < arr.length; i++){
            arr[i] = (int)(Math.random()*10)+1;
        }
    }
}

멤버 변수의 초기화 시기와 순서

class Test{
    static int cv = 1; // 명시적 초기화
    int iv = 2; // 명시적 초기화
    
    static {cv = 3;} // 클래스 초기화 블록
    {iv = 4} // 인스턴스 초기화 블록
    
    Test(){ // 생성자
        iv = 5;
    }
}
  1. 클래스 변수 cv는 int형의 기본값 0
  2. 클래스 변수 cv는 명시적 초기화로 1
  3. 클래스 변수 cv는 초기화 블록에 의해 3
  4. 인스턴스 변수 iv는 int형의 기본값 0
  5. 인스턴스 변수 iv는 명시적 초기화로 2
  6. 인스턴스 변수 iv는 초기화 블록에 의해 4
  7. 인스턴스 변수 iv는 생성자에 의해 5

상속(inheritance)

상속이란 기존의 클래스를 재사용해서 새로운 클래스를 작성하는 것이다.

  • 두 클래스를 부모와 자식으로 관계를 맺어주는 것이다.
  • 자식 클래스는 부모 클래스의 모든 멤버를 상속받는다.(단, 생성자와 초기화 블록은 제외)
  • 부모 클래스의 변경은 자식 클래스에게 영향을 주지만 자식 클래스의 변경이 부모 클래스에게 영향을 주지 못한다.
  • 사용 방법은 "자식 클래스 extends 부모 클래스"이다.
  • 상속은 단일 상속만 가능하다.
class Point{
    int x;
    int y;
}

class Point3D extends Point{
    int z;
}

포함(compsite)

포함이란 한 클래스의 멤버 변수로 다른 클래스를 선언하는 것을 말한다.

class Point{
    int x;
    int y;
}

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

상속과 포함 구분하기

상속과 포함 둘 중 어느 것을 사용하는 게 좋을지를 구분해보자

  • 상속관계 : ~은 ~이다. (~is ~a)
    • 예: Circle은 Shape이다.
  • 포함관계 : ~은 ~을 가지고 있다. (~has ~ a)
    • 예: Circle은 Point을 가지고 있다.

모든 클래스의 최고 조상 Object 클래스

조상이 없는 클래스들은 자동적으로 Object 클래스를 상속받는다.

  • Object 클래스에 있는 11개의 메서드를 상속받는다.
    • 예 : toString(), equals(Object obj), hashCode() 등등

오버 라이딩 (Overriaing)

부모 클래스로부터 상속받은 메서드의 구현부를 상속받은 자식 클래스에 맞게 변경하는 것을 말합니다.

  • 오버 로딩과 아무런 관계가 없는 것이다.
  • 선언부가 같아야 한다. (메서드명, 매개변수, 리턴 타입)
  • 접근 제어자가 좁은 범위로 변경할 수 없다.
    • 접근 제어자 범위 순서: public > protected > default > private
  • 부모 클래스의 메서드보다 많은 수의 예의를 선언할 수 없다.
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 : " + y + " z : " + z;
    }
}

참조 변수  super

super는 this와 비슷하지만 부모의 멤버와 자신의 멤버를 구별하기 위해 사용됩니다.

class Parent{
    int x = 10;
    
    String getLocation(){
        return "x = " + x;
    }
}

class Child extends Parent{
    int x = 20;
    int y = 30;
    
    void method(){
        System.out.println("x = " + x); // 가까운 x 20
        System.out.println("this.x = " + this.x); // 자기 자신 x 20
        System.out.println("super.x = " + super.x); // 부모의 x 10
    }
    
    String getLocation(){ // 오버라이딩
        return super.getLocation() + " y = " + y; // 부모 메서드 호출
        // x = 10, y = 30
    }
}

부모 클래스의 생성자 호출 "super()"

상속을 받은 자식 클래스의 인스턴스를 생성할 때 부모의 멤버들도 같이 초기화해줘야 한다.

class Point{
    int x;
    int y;
    
    Point(){
        this(0,0);
    }
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

class Point3D extends Point{
    int z;
    
    Point3D(int z){
        super();
        this.z = z;
    } // 결과 x = 0 y = 0 z = z
    // 또는
    
    Point3D(int x, int y, int z){
         super(x,y);
         this.z = z;
    } // 결과 x = x y = y z = z
}

패키지 (Package)

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

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

import문

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

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

import java.util.Date;
class Test{
    Date today = new Date();
}
  • java.lang 패키지는 import하지 않아도 패키 지명 생략이 가능하다.
  • import문은 패키지 문과 클래스 선언의 사이에서 선언된다.
  • import문을 선언하는 방법
    • import 패키 지명. 클래스명;
      • 패키지에 있는 클래스는 패키 지명을 생략할 수 있다.
    • import 패키 지명.*;
      • 패키지 안에 있는 모든 패키 지명을 생략할 수 있다.
  • import문은 컴파일 시에 처리되므로 프로그램의 성능에는 아무런 영향을 주지 않는다.
  • 다른 패키지에 같은 이름의 클래스가 있다면 꼭 패키지 명을 작성해주는 게 좋다.
import java.sql.*;
import java.util.*;

java.util.Date today = new java.util.Date();

제어자(modifier)

제어자란 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여합니다.

  • 제어자는 크게 접근제어자와 그 외 제어자로 나뉩니다.
  • 접근제어자 : public, protected, default(생략 가능), private
  • 그 외 제어자 : static, final, abstract, native, transient, synchronized, volatile, strictfp

Static - 공통적인

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

  • static 멤버 변수
    • 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
    • 클래스 변수는 인스턴스 생성 없이 "클래스명. 변수명"으로 사용이 가능하다.
    • 클래스가 메모리에 로딩될 때 생성된다.
  • static 메서드
    • 인스턴스 생성 없이 "클래스명. 메서드명(매개변수)"로 사용 가능하다.
    • static 메서드 내부에서는 인스턴스 멤버를 사용할 수 없다.

final - 마지막의

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

  • final 클래스
    • 부모 클래스가 될 수 없다.
  • final 메서드
    • 오버 라이딩을 할 수 없다.
  • final 멤버 변수, final 지역변수
    • 값을 변경할 수 없는 상수가 된다.
    • 상수는 선언과 동시에 초기화해주는 게 좋다.
      • 인스턴스 변수의 경우는 생성자를 통해 상수를 초기화할 수 있다.

abstract - 추상의

abstract가 사용될 수 있는 곳은 클래스, 메서드이다.

  • abstract 클래스
    • 추상 클래스라고 부르며 추상 메서드가 선언된 클래스를 의미합니다.
  • abstract 메서드
    • 추사 메서드라고 부르며 선언 부만 작성하고 구현부가 없는 메서드를 의미합니다.

접근제어자

멤버 또는 클래스에 사용되어 외부로부터의 접근을 제한하는 역할을 합니다.

  • public = 클래스, 멤버 변수
    • 접근 제한이 없다.
  • protected = 멤버 변수
    • 같은 패키지 내부와 다른 패키지의 자식 클래스에서 접근이 가능하다.
  • default(생략 가능) = 클래스, 멤버 변수
    • 같은 패키지 내부에서만 접근이 가능하다.
  • private = 멤버 변수
    • 같은 클래스 내에서만 접근이 가능하다.
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;
    }
}

Time t = new Time(); // Error! 기본생성자가 없다.
// 기본생성자가 있더라도 t.hour은 불가능하며 setHour 메서드를 통해서만 가능하다.
Time t = new Time(4); // Ok

생성자와 접근 제어자

일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치한다.

  • 생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.
class Test{
    private static Test t;
    private String name;
    
    private Test(){
        name = "Java";
    }
    
    public static Test getInstance(int a){
        if (a > 0){
            t =new Test();
        }
        return t;
    }
    public void getname(){
        System.out.println("Access Successful! name = " + name);
    }
}

Test t1 = new Test();
// Error! 생성자는 private 접근제어자이기 때문에 다른 클래스에서는 사용할 수 없다.

Test t2 = Test.getInstance(3); // 메서드를 통해서 인스턴스를 생성한다.
t2.getname();
Test t3 = Test.getInstance(-1);
t3.getname();
        
System.out.println(t2); // Access Successful! name = Java
System.out.println(t3); // Access Successful! name = Java

제어자의 조합

대상 사용가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근 제어자, final, abstract, static
멤버 변수 모든 접근 제어자, final, static
지역 변수 final
  • 메서드에 static과 abstract를 함께 사용할 수 없다.
    • static 메서드는 구현부가 있는 메서드만 사용할 수 있기 때문이다.
  • 클래스에 abstract와 final을 동시에 사용할 수 없다.
    • 클래스에 사용되는 final은 부모 클래스가 될 수 없다는 의미이며, abstract는 상속을 통해서 완성해야 하는 의미이기 때문에 서로 모순된다.
  • abstract 메서드의 접근제어자가 private일 수 없다.
    • abstract 메서드는 자식 클래스에서 구현해줘야 하는데 private이면 접근할 수 없기 때문이다.
  • 메서드에 private과 final을 같이 사용할 필요는 없다.
    • private인 메서드는 오버 라이딩할 수 없는데 final도 오버 라이딩할 수 없기 때문에 중복된다.

다형성(polymorphism)

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

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

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

Tv t1 = new Tv();
Tv t2 = new SmartTv();

t2.text = "Java"; // Error!
// 실제 인스턴스는 SmartTv타입이지만 참조변수의 타입은 Tv이기 때문에 text를 사용할 수 없다.

참조 변수의 형 변환

서로 상속관계에 있는 타입 간의 형 변환만 가능하다.

class Car{
    String color;
    int door;
    
    void drive(){ System.out.println("drive, Brr~"); }
    void stop(){ System.out.println("stop!!!"); }
}
class FireEngine extends Car{
    void water(){ System.out.println("water!!!"); }
}

Car car = null;
FireEngine fe1 = new FireEngine();
FireEngine fe2 = null;

fe1.water(); // water!!!
car = fe1; // Car car = (Car)fe1;
// car.water(); >> Error!!!
fe2 = (FireEngine)car; // FireEngine fe2 = (FireEngine)car;
fe2.water(); // water!!!

instanceof 연산자

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

  • instanceof의 연산 결과가 true이면 해당 타입으로 형 변환이 가능하다.
  • A instanceof B = B타입이 A의 실제 인스턴스의 타입의 부모라면 true를 반환한다.
class Car{}
class FireEngine extends Car{}

Car car = new Car();
FireEngine fe = new FireEngine();
Object obj = new Object();

System.out.println(fe instanceof Car); // true
System.out.println(fe instanceof FireEngine); // true
System.out.println(car instanceof FireEngine); // false
System.out.println(car instanceof Object); // true
System.out.println(obj instanceof Car); // false

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

  • 멤버 변수가 중복 정의된 경우, 참조 변수의 타입에 따라 연결되는 멤버 변수가 달라진다.
    • 참조 변수 타입에 영향을 받는다.
  • 메서드가 중복 정의된 경우, 참조 변수의 타입에 관계없이 항상 실제 인스턴스의 타입에 정의된 메서드가 호출된다.
    • 참조 변수 타입에 영향을 받지 않는다.
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");
    }
}

Parent p = new Child();
Child c = new Child();
        
System.out.println("p.x = " + p.x); // 100 변수는 참조변수의 타입에 따라 연결된다.
p.method(); // Child method 메서드는 실제 생성된 인스턴스의 타입에 정의된 메서드가 호출된다.
System.out.println("c.x = " + c.x); // 200 변수는 참조변수의 타입에 따라 연결된다.
c.method(); // Child method 메서드는 실제 생성된 인스턴스의 타입에 정의된 메서드가 호출된다.

매개변수의 다형성

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

class Product{
    int price;
}

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

class Buyer{
    int money = 100;
    
    void buy(Product p){
        money = money - p.price;
    }
}

Product tv = new Tv();
tv.price = 10;
Product computer = new Computer();
computer.price = 20;
        
Buyer b = new Buyer();
b.buy(tv); // buy 메서드의 매개변수 Product가 tv의 부모이기 때문에 가능하다.
b.buy(computer); // buy 메서드의 매개변수 Product가 computer의 부모이기 때문에 가능하다.
System.out.println(b.money); // 70

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

부모 타입의 배열에 자식들의 인스턴스를 담을 수 있다.

class Product{
    int price;
}

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

class Buyer{
    int money = 100;
    int i = 0;
    Product[] cart = new Product[10]; // 여러 객체들을 담을 하나의 배열 cart를 만들었다.
    
    void buy(Product p){
        if (money < p.price){
            System.out.println("잔액부족");
            return;
        }
        money = money - p.price;
        cart[i++] = p; // cart 배열에 하나씩 객체를 p타입의 객체를 넣는다.
    }
}

Product tv = new Tv();
tv.price = 10;
Product computer = new Computer();
computer.price = 20;
        
Buyer b = new Buyer();
b.buy(tv);
b.buy(computer);
        
for (int i = 0; i < 10; i++){
    System.out.println(b.cart[i]);
}

추상 클래스 (abstract class)

클래스가 설계도라면 추상 클래스는 미완성된 설계도입니다.

  • 일반 메서드가 추상 메서드를 호출할 수 있다. (필요한 건 선언 부이기 때문이다.)
    • 추상 메서드는 선언 부만 있고 구현부가 없는 메서드이다.
    • 자식 클래스마다 다르게 구현될 것으로 예상되는 경우 사용한다.
    • 상속받는 자식 클래스에서는 추상 메서드의 구현부를 완성해줘야 한다.
  • 완성된 클래스가 아니므로 인스턴스를 생성할 수 없다.
abstract class Player{
    int x;
    
    Player(){ // 추상 클래스도 생성자가 있어야한다.
        x = 10;
    }
    
    abstract void play(int pos); // 추상 메서드
    abstract void stop(); // 추상 메서드
    
    void play2(){
        play(x); // 추상 메서드를 사용할 수 있다.
    }
}
  • 일반 클래스에서 공통적으로 사용될 수 있는 부분을 뽑아서 추상 클래스로 만든다.
class Marine{
    int x, y; // 현재 위치
    void move(int x, int y){ /* 지정된 위치로 이동 */ }
    void stop(){ /* 현재 위치에 정지 */ }
    void stimPack(){ /* 스팀팩을 사용한다. */ }
}

class Tank{
    int x, y;
    void move(int x, int y){ /* 지정된 위치로 이동 */ }
    void stop(){ /* 현재 위치에 정지 */ }
    void changeMode(){ /* 공격모드를 변환한다. */ }
}

class Dropship{
    int x, y;
    void move(int x, int y){ /* 지정된 위치로 이동 */ }
    void stop(){ /* 현재 위치에 정지 */ }
    void load(){ /* 선택된 대상을 태운다. */ }
    void unload(){ /* 선택된 대상을 내린다. */ }
}

//==========================추상 클래스를 이용해서 만들기=================================

abstract class Unit{ // 공통된 부분을 추상 클래스로 만든다.
    int x, y;
    abstract void move(int x, int y); // 추상메서드
    void stop() { /* 현재 위치에 정지 */ }
}

class Marine extends Unit{
    void move(int x, int y){ /* 지정된 위치로 이동 */ }
    void stimPack(){ /* 스팀팩을 사용한다. */ }
}

class Tank extends Unit{
    void move(int x, int y){ /* 지정된 위치로 이동 */ }
    void changeMode(){ /* 공격모드를 변환한다. */ }
}

class Dropship{
    void move(int x, int y){ /* 지정된 위치로 이동 */ }
    void load(){ /* 선택된 대상을 태운다. */ }
    void unload(){ /* 선택된 대상을 내린다. */ }
}

인터페이스 (interface)

추상 클래스보다 추상화 정도가 높은 일종의 추상 클래스이다.

  • 실제 구현된 것이 아무것도 없다.
  • 추상 메서드와 디폴트 메서드, 상수만을 멤버로 가질 수 있다.
    • 추상 메서드는 모두 public abstract이기 때문에 생략이 가능하다.
    • 상수는 모두 public static final이기 때문에 생략이 가능하다.
  • 추상 클래스와 마찬가지로 인스턴스를 생성할 수 없다.
interface InterfaceName{
    public static final int x = 1; // 상수
    public abstract method1(String s); // 추상 메서드
    int y = 2;
    method2(String s);
}
  • 인터페이스는 다중 상속이 가능하다.
  • 인터페이스는 Object 클래스 같은 최고 조상이 없다.
interface A{ void move(int x, int y); }
interface B{ void attack(Unit u); }

interface C extends A, B{}
  • 인터페이스를 구현하는 것은 클래스를 상속받는 것과 같다.
  • 클래스 상속과 인터페이스 구현 동시에 가능하다.
class 클래스명 extends 클래스명 implements 인터페이스명{
    인터페이스에 정의된 추상 메서드 모두 구현
}

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

인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.

class Fighter extends Unit implement Fightable{
    public void move(int x, int y){ /* 내용 생략 */}
}

Fighter f = new Fighter();
Fightable fa = new Fighter();
  • 인터페이스를 메서드의 매개변수 타입으로 지정할 수 있다.
void attack(Fightable){ /* 내용 생략 */}
// Fightable 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 받는 메서드
  • 인터페이스를 메서드의 리턴 타입으로 지정할 수 있다.
Fightable method(){ // Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환한다.
    return new Fighter();
}

인터페이스의 장점

  1. 개발 시간을 단축시킬 수 있다.
  2. 표준화가 가능하다.
  3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
  4. 독립적인 프로그래밍이 가능하다.
interface Repairable{}

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

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

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++;
            }
        }
    }
}

Tank tank = new Tank();
Marine marine = new Marine();
SCV scv = new SCV();
        
scv.repair(tank);
scv.repair(marine); // Error!! Marine클래스는 Repairable인터페이스를 구현하지 않았다.

디폴트 메서드

JDK 1.8 이후 인터페이스에 디폴트 메서드 및 static 메서드를 추가 가능하게 바뀌었다.

  • 디폴트 메서드는 인터페이스에 추가된 일반 메서드이다.
  • 디폴트 메서드가 기존의 메서드와 충돌하는 경우 아래와 같이 해결한다.
    • 여러 인터페이스의 디폴트 메서드 간의 충돌
      • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버 라이딩해야 한다.
    • 디폴트 메서드와 부모 클래스의 메서드 간의 충돌
      • 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

내부 클래스(inner class)

내부 클래스란 클래스 안에 선언된 클래스를 말합니다.

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
class A{ // 외부 클래스
    ...
    class B{ // 내부 클래스
        ....
    }
    ....
}

내부 클래스의 종류과 특징

내부 클래스의 종류는 변수의 선언 위치에 따른 종류와 유효 범위(scope)가 유사하다.

내부클래스 특징
인스턴스 클래스 외부 클래스의 멤버변수 선언위치에 선언한다.
주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱 클래스 외부 클래스의 멤버변수 선언위치에 선언한다.
주로 외부 클래스의 static 멤버, static 메서드에서 사용될 목적으로 선언된다.
지역 클래스 외부 클래스의 메서드나 초기화 블록안에 선언되며, 선언된 내부에서만 사용될 수 있다.
익명 클래스 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)
class Outer{
    class InstanceInner{} // 인스턴스 클래스
    static class StaticInner{} // 스태틱 클래스
    
    void Method(){
        class LocalInner{} // 지역 클래스
    }
}

'JAVA' 카테고리의 다른 글

Collections & Collection class 정리  (0) 2022.08.23
[JAVA] Generics 정의 및 타입변수, 다형성  (0) 2022.08.23
[JAVA] Map 관련 내용  (0) 2022.08.19
[JAVA] TreeSet  (0) 2022.08.19
[JAVA] HashSet  (0) 2022.08.18
Comments