오늘의하루

[Java] 자바의 정석 객체지향 part 복습 본문

JAVA

[Java] 자바의 정석 객체지향 part 복습

오늘의하루_master 2022. 9. 30. 17:46

객체지향언어의 특징

코드의 재사용성이 높고 코드의 관리가 쉽고 제어자를 통해 데이터를 보호 및 코드의 중복을 제거하여 신뢰성 높은 프로그램 개발이 가능하다.

클래스와 객체

클래스란? 객체를 정의해 높은 것이다.
    - 클래스는 데이터와 함수의 결합(구조체 + 함수)이다.
객체란? 속성(변수)와 기능(메서드)를 가진 개념
    - 객체는 인스턴스를 포함하는 일반적인 의미이다.
    - 인스턴스화는 클래스로부터 인스턴스를 생성하는 것이다.

객체의 구성요소

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

class Tv{
    // 변수
    String color;
    boolean power;
    int channel;
    
    // 메서드
    void power(){power = !power;}
    void channelUp(){channel = channel + 1;}
    void channelDown(){channel = channel - 1;}
}

인스턴스의 생성과 사용

인스턴스의 생성 방법

  • 클래스명 참조변수명;
    • 객체를 다루기 위한 참조변수 선언
  • 참조변수명 = new 클래스명();
    • 객체 생성 후 생성된 객체의 주소를 참조변수에 저장
  • 클래스명 참조변수명 = new 클래스명();
Tv t1;
t1 = new Tv();

Tv t2 = new Tv();

인스턴스의 사용 방법

  • 참조변수명.변수 = 값;
  • 참조변수명.메서드(매개변수 값);
Tv t = new Tv(); // 객체 생성 후 참조변수 t에 주소 저장

t.channel = 7;
t.channelDown();
System.out.println(t.channel); // 결과 값 : 6

한개의 객체는 여러개의 참조변수가 주소를 공유 할 수 있다.

  • 한개의 참조변수는 여러개의 객체의 주소를 저장할 수 없다.
Tv t1 = new Tv();
Tv t2 = new Tv();
// 지금은 서로 다른 주소를 가지고 있다.

t1.channel = 10;
t1.channelUp();

t2 = t1;
// 이제 참조변수 t2에는 t1에 저장되어있는 객체의 주소를 저장한다.

System.out.println(t1.channel); // 결과값 : 11
System.out.println(t2.channel); // 결과값 : 11

 

참조변수에 클래스 배열을 저장하기

Tv[] t1 = new Tv[3];

t1[0] = new Tv();
t1[1] = new Tv();
t2[2] = new Tv();

t1[0].channel = 1;
t1[1].channel = 2;
t1[2].channel = 3;

t1[0].channelUp();
t2[1].channelDown();

for(int i = 0; i < 3; i++){
    System.out.println(t1[i].channel);
}
// 결과 2 1 3

변수와 메서드

변수(Variable)

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

class Variables{
    int iv; // 인스턴스 변수
    static int cv; // 클래스 변수
    
    void method(){
        int lv = 0; // 지역변수
    }
}
변수의 종류 선언위치 생성시기
클래스변수 클래스 영역 클래스가 메모리에 올라갈때
인스턴스 변수 인스턴스 생성시
지역변수 메서드 영역 메서드 수행시

인스턴스 변수(instance variable)

  • 각각의 인스턴스의 개별적인 저장공간
  • 객체 생성 후 참조변수가 없으면 가비지 컬렉터에 의해 자동 제거
  • 인스턴스 생성 후 "참조변수명.인스턴스변수명"으로 접근 가능

클래스 변수(class variable)

  • 같은 클래스의 모든 인스턴스들이 공유하는 변수
  • 인스턴스 생성 없이 "클래스명.클래스변수명"으로 접근 가능
  • 클래스가 메모리에 올라갈때 생성 되고 프로그램 종료될때 소멸

지역 변수(local variable)

  • 메서드의 종료와 함께 소멸
  • 조건문, 반복문의 블럭{ } 내에 선언된 지역변수는 블럭을 벗어나면 소멸

메서드(method)

작업을 수행하기 위한 명령문의 집합이며, 하나의 메서드는 하나의 기능만 수행하는 것이 좋다.

리턴타입 메서드명 (타입 변수명, 타입 변수명 ...) // 선언부
{ // 구현부
    // 메서드 호출시 수행할 코드
}

int add(int x, int y){
    int result = x + y;
    return result;
}

return문

메서드가 정상적으로 종료되는 경우 현재 실행중인 메서드를 종료하고 호출한 곳으로 돌아간다.

1. 반환값이 없는 경우
    - return;
    - 메서드의 타입이 void일 경우는 return을 작성하지 않아도 된다.
2. 반환값이 있는 경우
    - 반환값이 있을 때는 모든 경우에 return문이 있어야 한다.
    - return 반환값;

메서드의 호출

"참조변수명.메서드이름(매개변수)"로 호출이 가능하며 매개변수가 없는 경우는 "()"으로 호출한다.

JVM의 메모리 구조

  • 메서드 영역(Method Area)
    • 클래스 정보와 클래스 변수가 저장되는 곳이다.
  • 호출 스택(Call Stack)
    • 메서드의 작업공간으로 메서드가 호출되면 메서드 수행에 필요한 메모리 공간을 할당받고 메서드가 종료되면 사용하던 메모리를 반환한다.
    • Last In Fast Out 구조이다.
  • 힙(Heap)
    • 인스턴스가 생성되는 공간으로 new 연산자에 의해서 생선되는 배열과 객체는 모두 여기에 생성된다.

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

기본형 매개변수의 경우는 변수의 값을 읽기만 가능하지만 참조형 매개변수는 변수의 값을 읽고 수정할 수 있다.

public class Test{
    public static void main(String[] args){
        Data d1 = new Data();
        d1.x = 10;
        System.out.println("main() : x = " + d1.x); // 결과 10
        
        change(d1.x); // 기본형 매개변수
        System.out.println("1. After main() : x = " + d1.x); // 결과 10
        
        change2(d1); // 참조형 매개변수
        System.out.println("2. After main() : x = " + d1.x); // 결과 1000
        
        
    }
    public static void change(int x){ // 기본형 매개변수
        x = 100;
        System.out.println("change() : x = " + x); // 결과 100
    }
    public static void change2(Data d){
    	d.x = 1000;
        System.out.println("chagne2() : x = " + x); // 결과 1000
    }
}

class Data{
    int x;
}

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

인스턴스 메서드(Instance Method)

  • 인스턴스 생성 후 "참조변수명.메서드명()"으로 호출
  • 메서드 내에서 인스턴스 멤버, 클래스 멤버 사용 가능

클래스 메서드(static Method)

  • 인스턴스 생성 없이 "클래스명.메서드명()"으로 호출
  • 메서드 내에서 인스턴스 멤버 사용 불가능하지만 클래스 멤버는 사용 가능
class Test{
    int iv = 1;
    static int cv = 2;
    
    void instanceMethod(){
        staticMethod(); // 클래스 메서드 호출 가능
        System.out.println(cv); // 클래스 변수 사용가능
        System.out.println(iv); // 인스턴스 변수 사용가능
        instanceMethod2(); // 다른 인스턴스 메서드 호출 가능
    }
    
    static void staticMethod(){
        System.out.println(cv); // 클래스 변수 사용가능
        instanceMethod(); // Error! 인스턴스 멤버 사용 불가
    }
    
    void instanceMethod2(){}
}

메서드 오버로딩

하나의 클래스에 같은 이름의 메서드를 여러개 정의하는 것을 말한다.

오버로딩의 조건

  • 메서드의 이름이 같아야한다.
  • 매개변수의 개수 또는 타입이 달라야 한다.
  • 리턴타입만 다른 경우는 오버로딩이 성립되지 않는다.
    • 리턴타입은 오버로딩에 아무런 영향을 주지 못한다.
class Test{
    void show();
    void show(int x);
    void show(boolean x);
}

생성자(Constructor)

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

  • 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다.
Test t = new Test();
1. new 연산자에 의해 메모리(Heap)에 Test클래스의 인스턴스가 생성된다.
2. 생성자 Test()가 호출되어 수행된다.
3. new 연산자의 결과로 생성된 Test객체의 주소가 반환되어 참조변수 t에 저장된다.

생성자는 클래스명과 동일해야 하며 리턴값이 없다.

  • 리턴값이 없지만 void를 쓰지는 않는다.
class Card{
	String Cardkind;
    int Cardnumber;
    
    Card(){ // 매개변수가 없는 기본생성자
    
    }
    
    Card(String kind, int number){ // 매개변수가 있는 생성자
        Cardkind = kind;
        Cardnumber = number;
    }
}

기본생성자는 클래스에 생성자가 하나도 없는 경우 컴파일러가 자동으로 추가해준다.

  • 생성자가 하나라도 있다면 컴파일러가 기본생성자를 추가해주지 않는다.
class Car{
    String color;
    String gearType;
    int door;
    
    Car(){} // 기본생성자
    // 생략 시 컴파일러가 추가해준다.
}

Car c = new Car();

c.color = "white";
c.gearType = "auto";
c.door = 4;

매개변수가 있는 생성자를 사용하는 방법

class Car{
    String color;
    String gearType;
    int door;
    
    car(){}
    car(String c, String g, int d){
        color = c;
        gearType = g;
        door = d;
    }
}

Car c = new Car("white", "auto", 4);
System.out.println(c.color); // 결과 white
System.out.println(c.gearType); // 결과 auto
System.out.println(c.door); // 결과 4

생성자 this()

같은 클래스의 다른 생성자를 호출할때 사용한다.

  • 다른 생성자 호출은 생성자의 첫줄에서만 가능하다.
class Car{
    String color;
    String gearType;
    int door;
    
    Car(){
        this("white", "auto", 4);
        // 같은 클래스에 있는 매개변수 3개를 가진 생성자를 호출한다.
    }
    
    car(String c, String g, int d){
        color = c;
        gearType = g;
        door = d;
    }
}

Car c = new Car();
System.out.println(c.color); // white
System.out.println(c.gearType); // auto
System.out.println(c.door); // 4

참조변수 this

인스턴스 자신을 가리키는 참조변수이다.

class Car{
    String color;
    String gearType;
    int door;
    
    Car(){
        this("white","auto",4);
    }
    
    Car(String color, String gearType, String door){
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }
}

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

똑같은 변수값을 가진 독립적인 인스턴스를 복사해서 만들 수 있다.

class Car{
    String color;
    String gearType;
    int door;
    
    Car(){this("white","auto",4);}
    Car(String color, String gearType, int door){
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }
    Car(Car c){
        color = c.color;
        gearType = c.gearType;
        door = c.door;
    }
}

Car c1 = new Car("red","auto",6);
Car c2 = new Car(c1); // c1의 변수값을 갖는 인스턴스를 복사하여 c2에 주소를 저장한다.

System.out.println(c2.color); // 결과 red
System.out.println(c2.gearType); // 결과 auto
System.out.println(c2.door); // 결과 6

변수의 초기화

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

  • 지역변수의 경우는 사용전 꼭 초기화를 해주어야 한다.
자료형 기본값
boolean false
char '\u0000' = 공백 = ' '
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0 or 0.0d (생략 가능)
참조형 변수 null

멤버 변수의 초기화 방법

1. 명시적 초기화(Explicit initialization)

class Car{
    int door = 4; // 기본형 변수의 초기화
    Engine e = new Engine(); // 참조형 변수의 초기화
}

2. 생성자(Constructor)

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

3. 초기화 블럭(Initialization block)

// 인스턴스 초기화 블럭
{ }

// 클래스 초기화 블럭
static { }

클래스 초기화 블럭은 클래스 변수의 복잡한 초기화에 사용되며 클래스가 로딩 될때 실행된다.

인스턴스 초기화 블럭은 생성자에서 공통적으로 수행되는 작업에 사용되며 인스턴스가 생성될 때 마다 생성자보다 먼저 실행된다.

  • 인스턴스 초기화 블럭에서는 클래스 변수도 초기화 할수 있다.
// 사용 예시

class StaticBlocakTest{
    static int[] arr = new int[10];
    
    static { // 클래스 초기화 블럭
        for(int i = 0; i < arr.length; i++){
            arr[i] = (int)(Math.random() * 10) + 1;
            // 배열 arr에 1~10사이의 값으로 채운다.
        }
    }
}

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

class InitTest{
    static int cv = 1; // 명시적 초기화
    int iv = 2; // 명시적 초기화
    
    static { // 클래스 초기화 블럭
        cv = 3;
    }
    { iv = 4; } // 인스턴스 초기화 블럭
    
    InitTest(){ // 생성자
        iv = 5;
    }
}
  1. 클래스 초기화 cv 기본값으로 cv = 0
  2. 클래스 초기화 cv 명시적 초기화 cv = 1
  3. 클래스 초기화 cv 클래스 초기화 블럭 cv = 3
  4. 인스턴스 초기화 iv 기본값으로 iv = 0 / cv = 3
  5. 인스턴스 초기화 iv 명시적 초기화 iv = 2 / cv = 3
  6. 인스턴스 초기화 iv 인스턴스 초기화 블럭 iv = 4 / cv = 3
  7. 인스턴스 초기화 iv 생성자 iv = 5 / cv = 3

멤버변수의 초기화 시기와 순서 (예제)

class Product{
    static int count = 0; // 클래스 변수
    int serialNo;
    
    { // 인스턴스 초기화 블럭
        ++count;
        serialNo = count;
    }
    
    Product(){}
}

class ProductTest(){
    public static void main(String[] args){
        Product p1 = new Product();
        Product p2 = new Product();
        Product p3 = new Product();
        
        System.out.println("p1의 serial no = " + p1.serialNo); // 결과 1
        System.out.println("p2의 serial no = " + p2.serialNo); // 결과 2
        System.out.println("p3의 serial no = " + p3.serialNo); // 결과 3
        System.out.println("생상된 제품의 수는 모두 " + Product.count + "개 입니다."); 결과 3
    }
}

클래스 변수 초기화 순서

  1. 클래스 변수 초기화 count 기본값 count = 0
  2. 클래스 변수 초기화 count 명시적 초기화 count = 0

p1 인스턴스 생성시 초기화 순서(클래스 변수 count = 0)

  1. 인스턴스 변수 초기화 serialNo 기본값 serialNo = 0
  2. 인스턴스 변수 초기화 serialNo 초기화 블럭 count = 1, serialNo = 1
  3. 인스턴스 변수 초기화 serialNo 기본 생성자

p2 인스턴스 생성시 초기화 순서 (클래스 변수 count = 1)

  1. 인스턴스 변수 초기화 serialNo 기본값 serialNo = 0
  2. 인스턴스 변수 초기화 serialNo 초기화 블럭 count = 2, serialNo = 2
  3. 인스턴스 변수 초기화 serialNo 기본 생성자

p3 인스턴스 생성시 초기화 순서(클래스 변수 count = 2)

  1. 인스턴스 변수 초기화 serialNo 기본값 serialNo = 0
  2. 인스턴스 변수 초기화 serialNo 초기화 블럭 count = 3, serialNo = 3
  3. 인스턴스 변수 초기화 serialNo 기본 생성자

상속(Inheritance)

기존의 클래스를 재사용하여 새로운클래스를 작성하는 것을 말하며, 기존의 클래스는 부모가 되고 새로운 클래스는 자식이되며 자식 클래스는 부모의 모든 멤버를 상속받는다.

  • 상속 시 생성자 및 초기화 블럭은 상속받지 못한다.
  • 부모 클래스의 변경은 자식 클래스에게 영향을 미치지만 자식 클래스의 변경은 부모 클래스에게 아무런 영향을 미치지 못한다.
  • 공통 부분은 부모 클래스에서 관리하고 개별 부분의 경우는 자식 클래스에서 관리한다.
  • 자바는 단일 상속을 원칙으로 한다.
class Parents{
    int x;
    int y;
}

class Child extends Parents{
    int z;
}

Child c = new Child();
c.x = 1;
c.y = 2;
c.z = 3;
System.out.println(c.x); // 결과 1
System.out.println(c.y); // 결과 2
System.out.println(c.z); // 결과 3

 

포함(Composite)

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

  • 2개 이상의 클래스를 상속 받고 싶을 때는 한개는 상속으로 나머지는 포함을 하면 사용 가능하다.
class Point{
    int x;
    int y;
}

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

Circle c = new Circle();
c.r = 10;
c.p.x = 20; // 포함된 참조변수에 있는 x값을 지정
c.p.y = 30; // 포함된 참조변수에 있는 y값을 지정

System.out.println(c.r); // 결과 10
System.out.println(c.p.x); // 결과 20
System.out.println(c.p.y); // 결과 30

상속과 포함은 어떻게 결정하는 걸까?

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

위에 작성되있는 Point와 Circle을 예시로 확인해 보자

  1. 원은 점이다. (상속)
  2. 원은 점을 가지고 있다. (포함)

확인해보면 상속보다 포함이 더 올바르다는 것을 알 수 있다.

클래스간의 관계 (예제)

class Shape{
    String color = "blue";
    void draw(){}
}

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

class Circle extends Shape{
    Point center;
    int r;
    
    Circle(){this(new Point(0,0), 100);}
    Circle(Point center, int r){
        this.center = center;
        this.r = r;
    }
}

class Triangle extends Shape{
    Point[] p;
    
    Triangle(Point[] p){
        this.p = p;
    }
    Triangle(Point p1, Point p2, Point p3){
        p = new Point[]{p1,p2,p3};
    }
}

Circle c1 = new Circle();
Circle c2 = new Circle(new Point(150,150), 50);

Point[] p = {new Point(100,100), new Point(140,50), new Point(200,100)};
Triangle t1 = new Triangle(p);

Object 클래스

Object 클래스는 모든 클래스의 최고 조상이며 부모가 없는 클래스는 자동적으로 Object 클래스를 상속 받게 된다.

  • 이 때문에 모든 클래스는 Object 클래스에 정의된 11개의 메서드를 상속받는다.
  • 메서드 종류 : toString(), equals(Object obj), hashCode() 등등

오버라이딩(Overriding)

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

class Point{
    int x;
    int y;
    
    String getLocation(){
        return "x = " + x + " y = " + y;
    }
}

class Point3D extends Point{
    int r;
    String getLocation(){ // 오버라이딩
        return "x = " + x + " y = " + y + " r = " + r;
        // super 참조변수를 사용할 경우
        // return super.getLocation() + " r = " + r;
    }
}

오버라이딩의 조건

  1. 메서드의 선언부가 같아야 한다.
    • 선언부 = 메서드명, 매개변수, 리턴타입
  2. 접근제어자를 좁은 범위로 변경할 수 없다.
    • 접근 제어자 범위 = private < (default) < protected < public
  3. 부모 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
class Parent{
    void parentMethod() throws IOException, SQLException{} // 예외 2개
}

class Child extends Parent{
    void parentMethod() throws IOException{} // 예외 1개
}

class Child2 extends Parent{
    void parentMethod() throws Exception{} // 예외 1개
}

class Child3 extends Parent{
    private void parentMethod(){} // Error! 접근제어자가 좁은 범위로 변경 불가
}

class Child4 extends Parent{
    public void parentMethod() throws Exception{} // 예외 1개, 접근제어자 넓은 범위로 변경 가능
}

오버로딩과 오버라이딩은 아무런 관련이 없다.

오버로딩(over loading) = 기존에 없는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding) =  상속받은 메서드의 내용을 변경하는 것(change)
class Parent{
    void parentMethod(){}
}

class Child{
    void parentMethod(){System.out.println("overriding");}
    void parentMethod(int i){System.out.println("over loading");}
    
    void childMethod(){}
    void childMethod(int i){System.out.println("over loading");}
    void childMethod(){} // Error! 중복정의
}

참조변수 super

참조변수 super는 부모의 멤버와 자신의 멤버를 구별하는데 사용된다.

class Parent{
    int x = 10;
}

class Child extends Parent{
    int x = 20;
    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
    }
}

class Child2 extends Parent{
    void method(){
        System.out.println("x = " + x); // 가장 가까운 x의 값 10
        // 부모의 x만 있기 때문에 가까운것은 10이다.
        System.out.println("this.x = " + this.x); // 자기 자신의 x의 값 10
        // 부모의 x를 상속받기 때문에 자기 자신 것으로 할수 있다.
        System.out.println("super.x = " + super.x); // 부모의 x의 값 10
    }
}

생성자 super()

상속시 생성자 및 초기화 블럭은 상속되지 않는데 부모의 멤버들도 초기화되어야 하기 때문에 자식 생성자의 첫문장에서 부모의 생성자를 호출할때 사용된다.

class Point{
    int x;
    int y;
    
    Point(){this(0,0);}
    Point(int x, int y){
        super(); // Object() 최고조상인 Object 클래스의 기본생성자 호출
        this.x = x;
        this.y = y;
    }
    
    String getLocation(){
        return "x = " + x + " y = " + y;
    }
}

class Point3D extends Point{
    int z;
    
    Point3D(int x, int y, int z){
        super(x,y); // 부모의 생성자 Point(x,y)호출
        this.z = z;
    }
    
    String getLocation(){ // 오버라이딩
        return "x = " + x + " y = " + y + " z = " + z;
    }
}

제어자

제어자는 크게 접근제어자와 그외 제어자로 나뉘며 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여하는데 사용된다.

접근 제어자 = private, (default), protected, public
그외 제어자 = static, final, abstract, native, transient, synchronized, volatile, strictfp

그 외 제어자 알아보기

static - 공통적인

static이 사용되는 곳은 멤버변수, 메서드, 초기화 블럭

멤버 변수에 사용될 경우는 클래스 변수가 되며 메서드에 사용될 경우 클래스 메서드(static 메서드)가 된다.

class StaticTest{
    static int width = 200;
    static int height = 120;
    
    static {
        height = 240;
    }
    
    static int max(int a, int b){
        return a > b ? a : b;
    }
}

System.out.println(StaticTest.max(StaticTest.width, StaticTest.height)); // 결과 240

 

final - 마지막의, 변경할 수 없는

final이 사용되는 곳은 클래스, 메서드, 멤버변수, 지역변수
대상 의미
클래스 누군가의 부모가 될 수 없는 클래스가 된다.
메서드 오버라이딩을 통해 재정의(change)할 수 없는 메서드가 된다.
멤버 변수 값을 변경할 수 없는 상수가 된다.
보통 선언과 초기화를 동시에 한다.
지역 변수
final class FinalTest{
    final int MAX_SIZE = 10; // 멤버변수 (상수)
    final int NUMBER;
    
    FinalTest(int NUMBER){
        this.NUMBER = NUMBER; // 생성자를 이용한 final 변수 초기화
    }
    
    final void getMaxSize(){
        final int LV = MAX_SIZE; // 지역변수 (상수)
        System.out.println(MAX_SIZE);
    }
}

class Child extneds FinalTest{
     // void getMaxSize(){} // 오버라이딩 Error! final 메서드는 오버라이딩 할 수 없다.
}

abstract - 추상의

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

접근 제어자(Access modifier)

멤버 또는 클래스에 사용되며 외부로 부터의 접근을 제한한다.

종류 의미
private 같은 클래스 내에서만 접근이 가능하다.
(멤버)
default 같은 패키지 내에서만 접근이 가능하다.
(클래스, 멤버)
protected 같은 패키지 내에서, 그리고 다른 패키지의 자식 클래스에서 접근이 가능하다.
(멤버)
public 접근 제한이 없다.
(클래스, 멤버)
public /*or default*/ class AccessModifierTest{
    protected int iv;
    private static int cv;
    
    /*default*/void method(){}
}

접근 제어자를 이용한 캡슐화 (예제)

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;
    }
    
    public String toString(){
        return hour + "시 입니다.";
    }
}

Time t = new Time(10);
System.out.println(t.hour); // Error!
// 접근제어자가 private이기 때문에 같은 클래스에서만 접근이 가능하다.
System.out.println(t.getHour()); // 결과 10
System.out.println(t); // 결과 10시 입니다.
// System.out.println(t.toString());에서 to String()은 생략이 가능하다.

t.setHour(t.getHour() + 1);
System.out.println(t.getHour()) // 결과 11
System.out.println(t); // 결과 11시 입니다.

생성자의 접근제어자

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

  • 생성자에 접근제어자를 통해 인스턴스 생성을 제한할 수 있다.
class Singleton{
    private static Singleton s;
    // private static Singleton s = new Singletion();
    
    private Singleton(){} // 생성자에 private을 사용
    
    public static Singleton getInstance(){
        if(s == null){
            s = new Singleton();
        }
        return s;
    }
}

Singleton s = new Singleton(); // Error! 생성자를 사용할 수 없다.
Singleton s1 = Singleton.getInstance();

제어자의 조합

대상 사용가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근제어자, final, abstract, static
멤버 변수 모든 접근제어자, final, static
지역 변수 final
  1. 메서드에 static과 abstract를 함께 사용할 수 없다.
    • static 메서드는 구현부가 있는 메서드에만 사용할 수 있다.
  2. 클래스에 abstract와 final을 동시에 사용할 수 없다.
    • abstract는 혼자 사용될 수 없고 부모 클래스가 되야 하는데 final은 부모 클래스가 되는것 을 제한한다.
  3. abstract 메서드의 접근제어자가 private이 될 수 없다.
    • abstract 메서드는 자식 클래스에서 구현부를 완성해야하는데 private은 다른 클래스에서의 접근을 제한한다.
  4. 메서드에 private과 final을 같이 사용할 필요가 없다.
    • 두 제어자 모두 오버라이딩을 할 수 없게 만들기 때문이다.

다형성(polymorphism)

부모타입의 참조변수로 자식타입의 객체를 다룰 수 있는것을 말한다.

  • 반대의 경우는 불가능하다.
class Tv{
    boolean power;
    int channel;
    
    void power(){power = !power;}
    void channelUp(){channel = channel + 1;}
}

class CaptionTv{
    String text;
    void caption(){System.out.println(txext);}
}

Tv t = new Tv();
CaptionTv c = new CaptionTv();

// 다형성 활용
Tv t = new CaptionTv();

CaptionTv는 Tv의 자식 클래스이기 때문에 더 많은 멤버를 가지고 있다. 다형성으로 Tv 타입의 참조변수로 CaptionTv의 객체를 가리키면 만들어진 참조변수에서는 Tv에 있는 멤버만 사용이 가능하다.

참조변수의 형변환

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

자식타입에서 부모타입으로 형변환 (Up-casting) = 형변환 생략 가능
부모타입에서 자식타입으로 형변황(Down-casting) = 형변환 생략 불가능
class Car{
    String color;
    int door;
    
    void drive(){System.out.println("drice Brrrr~");}
}

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)fe1; 자식에서 부모타입으로 변환 (형변환 생략가능)
car.water(); // Error! Car클래스에는 water 멤버가 없다.

fe2 = (FireEngine)car; // 부모에서 자식타입으로 변환 (형변환 생략 불가능)
fe2.water(); // 결과 water!!!

instanceof 연산자

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

  • instanceof 연산결과가 true이면 해당 타입은 형변환이 가능하다.
  • A instanceof B는 A가 가리키는 실제 객체의 타입이 B 또는 B의 자식 타입인지 확인한다.
class Car{}
class FireEngine extends Car{}

FireEngine fe = new FireEngine();

if(fe instanceof FireEngine){
    System.out.println("This is a FireEngine instance");
}

if(fe instanceof Car){
    System.out.println("This is a Car instance");
}

if(fe instanceof Object){
    System.out.println("This is an Object instance");
}

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

멤버 변수가 중복 정의된 경우 참조변수의 타입에 따라 연결되는 멤버변수가 달라진다.

  • 멤버변수는 참조변수타입에 영향을 받는다.

메서드가 중복정의된 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입에 정의된 메서가 호출된다.

  • 메서드는 참조변수타입에 영향을 받지 않는다.
class Parent{
    int x = 10;
    
    void method(){System.out.println("Parent Method");}
}

class Child{
    int x = 20;
    
    void method(){System.out.println("Child Method");}
}

Parent p1 = new Child();
Child c1 = new Child();

System.out.println(p1.x); // 결과 10
p1.method(); // 결과 Child Method

System.out.println(c1.x); // 결과 20
c1.method(); // 결과 Child Method

//========================================

class Parent{
    int x = 30;
    
    void method(){Systetm.out.println("Parent Method");}
}

class Child extends Parent{}

Parent p2 = new Child();
Child c2 = new Child();

System.out.println(p2.x); // 결과 30
p2.method(); // 결과 Parent Method

System.out.println(c2.x); // 결과 30
c2.method(); // 결과 Parent Method

매개변수의 다형성

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

  • 코드의 중복성을 줄일 수 있다.
class Product{
    int price;
}

class Tv extneds Product{}
class Audio extneds Product{}

class Buyer{
    int money = 1000;
    
    // 매개변수의 다형성 사용 안했을 때
    void Buy(Tv t){
        money = money - t.price;
    }
    void Buy(Audio a){ // 오버로딩
        money = money - a.price;
    }
    
    // 매개변수의 다형성을 활용 했을 때
    void Buy(Product p){ // 다형성으로 자식 클래스를 가져올 수 있다.
        money = money - p.price;
    }
}

Buyer b = new Buyer();

Product p1 = new Tv();
p1.price = 100;
b.buy(p1); // Product p = new Tv();

Product p2 = new Audio();
p2.price = 20;
b.buy(p2); // Product p = new Audio();

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

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

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

Product[] p = new Product[3];
p[0] = new Tv();
p[1] = new Audio();
p[2] = new Computer();

// 매개변수 다형성 예시에 있는 Buyer 클래스에  배열 넣기

class Buyer{
    int money = 1000;
    
    Product[] cart = new Product[10];
    
    int i = 0;
    
    void Buy(Product p){
        if(money < p.price){
            System.out.println("잔액 부족");
            return;
        }
        money = money - p.price;
        cart[i++] = p;
    }
}

추상클래스 - abstract

클래스가 설계도라면 추상클래스는 "미완성 설계도"이며 추상메서드를 포함하고 있는 클래스이다.

abstract class Player{
    int currentPos;
    
    Player(){ // 추상 클래스도 생성자가 있어야 한다.
        currentPos = 0;
    }
    
    abstract void play(int pos); // 추상 메서드
    abstract void stop(); // 추상 메서드
    
    void play(){ // 일반 메서드안에서 추상 메서드를 사용할 수 있다.
        play(currentPos); // 추상메서드
    }
}
  1. 일반 메서드가 추상 메서드를 호출할 수 있다.
    • 호출할 때 필요한 건 선언부이다.
  2. 추상 클래스는 인스턴스를 생성할 수 없다.
  3. 다른 클래스를 작성하는데 도움을 줄 목적으로 작성한다.

추상메서드 (abstract method)

선언부만 있고 구현부가 없는 메서드이며 추상클래스는 꼭 필요하지만 자식 클래스마다 다르게 구현될 것으로 예상되는 경우에 사용한다.

  • 추상클래스를 상속받는 자식클래스에서 추상 메서드의 구현부를 완성해야 사용 가능하다.
abstract class Player{
    abstract void play(int pos);
    public abstract void stop();
}

class AudioPlayer extends Player{
    void play(int pos){}
    void stop(){} // Error! 접근제어자를 좁은 범위로 할 수 없다.
    public void stop(){} // OK
}

abstract class AbstractPlayer extends Player{
    void paly(int pos){}
    // 추상 메서드 하나를 구현하지 않았기 때문에 아직 추상클래스이다.
}

추상 메서드의 구현부를 완성하는 행위는 오버라이딩이기 때문에 오버라이딩 규칙을 지켜줘야한다.

인터페이스 (interface)

인터페이스는 일종의 추상 클래스이며 실제 구현된 것이 전혀 없는 기본 설계도이다.

  • 디폴트메서드, static메서드 및 추상메서드와 상수만을 멤버로 가질 수 있다.
    • 디폴트메소드는 인터페이스에서 사용할 수 있는 일반 메서드를 말한다.
  • 인터페이스는 생성할 수 없으며 클래스 작성에 도움을 줄 목적으로 사용된다.
interface MyInterface{
    int MAX = 1; // 상수
    void method(); // 추상메서드
    default void newMethod(){} // 디폴트 메서드
}

디폴트 메서드가 기존의 메서드와 충돌하게 된다면?

  1. 여러 인터페이스의 디폴트 간의 충돌
    • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다.
  2. 디폴트 메서드와 부모클래스의 메서드 간의 충돌
    • 부모 클래스의 메서드를 상속받고 디폴트 메서드는 무시된다.

인터페이스의 장점

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

인터페이스 작성

class 대신 interface를 사용한다는 것 외에는 클래스와 동일하다.

interface interfaceName{
    public static abstract method(/*매개변수*/);
    /* public static final 타입 상수이름 = 값 */
    public static final int MAX = 1;
    
    int HEART = 2; // public static final 생략
    String getCardKind(); // public stataic abstract 생략
}

 

  • 인터페이스의 모든 멤버변수는 public static final이기 때문에 생략이 가능하다.
  • 인터페이스의 모든 메서드는 public static abstract이기 때문에 생략이 가능하다.

인터페이스 상속

인터페이스도 클래스처럼 상속이 가능하다.

  • 다중 상속 허용
  • 인터페이스는 Object클래스와 같은 최고 조상이 없다.
interface Movable{
    void move(int x, int y); // public static abstract 생략
}

interface Attackable{
    void attack(Unit u);
}

interface Fightable extends Movable, Attackable{}

인터페이스 구현

인터페이스를 구현하는 것은 클래스를 상속받는것과 같다.

  • extends 대신 implements를 사용한다.
  • 인터페이스를 상속받은 클래스는 추상 메서드를 완성해야 사용가능하다.
  • 클래스 상속과 인터페이스 구현이 동시에 가능하다.
interface Fightable {
    void move(int x, int y);
    void attack(Unit u);
}

abstract class Unit{
    int x;
    int y;
    
    void set();
}

class Fighter implements Fightable{
    public void move(){}
    public void attack(){}
}

abstract class Fighter2 implements Fightable{
    public void move(){}
    // 하나의 추상 메서드를 구현하지 않았기 때문에 추상 클래스가 된다.
}

// 상속과 구현이 동시에 가능하다.
class Fighter3 extends Unit implements Fightable{
    public void move(int x, int y){}
    public void attack(Unit u){}
    public void set(){}
}

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

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

  • 인터페이스를 메서드의 매개변수 타입으로 지정할 수 있다.
  • 인터페이스를 메서드의 리턴타입으로 지정할 수 있다.
class Fighter extends Unit implements Fightable{
    public void move(int x, int y) {}
    public void attack(Unit u) {}
}

Fighter f1 = new Fighter();
Fightable f2 = new Fighter();

// ===============================================

void attack(Fightable f){}
// Fightable 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 받는 메서드

//================================================

Fightable method(){ // Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환한다.
    return new Fightable();
}

인터페이스의 장점 (예제)

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); // Unit(int hp) 생성자 호출
    }
}

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

class Marine extends GroundUnit{
    Marine(){
        super(40);
        hitPoint = MAX_HP;
    }
}

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); // OK
scv.repair(marine); // Error! Marine 클래스는 Repairable을 구현하지 않았다.
Comments