본문 바로가기
Language/Java

[Java] 자바가 확장한 객체 지향 (2)

by jaee_ 2021. 9. 27.
본 글은 📚 스프링 입문을 위한 자바 객체지향의 원리와 이해 를 읽고 정리한 내용입니다.

이전 글 : [Java] 자바가 확장한 객체 지향 (1)

 

[Java] 자바가 확장한 객체 지향 (1)

본 글은 📚 스프링 입문을 위한 자바 객체지향의 원리와 이해 를 읽고 정리한 내용입니다. 1. abstract 키워드 - 주상 메소드와 추상 클래스 추상메소드(Abstract Method)란 간단하게 설명하면 선언부는

yeoonjae.tistory.com


5. instanceof 연산자

인스턴스는 클래스를 통해 만들어진 객체이다. instanceof 연산자는 만들어진 객체가 특정 클래스의 인스턴스인지 물어보는 연산자이다. instatnceof 연산자의 결과는 boolean 형태이며 사용법은 아래와 같다.

객체_참조_변수 instanceof 클래스명
class 동물 {

}
class 조류 extends 동물{

}
class 펭귄 extends 조류{

}

public class Driver{

    public static void main(String[] args) {
        동물 동물객체 = new 동물();
        조류 조류객체 = new 조류();
        펭귄 펭귄객체 = new 펭귄();

        System.out.println(동물객체 instanceof 동물);
        
        System.out.println(조류객체 instanceof 동물);
        System.out.println(조류객체 instanceof 조류);
        
        System.out.println(펭귄객체 instanceof 동물);
        System.out.println(펭귄객체 instanceof 조류);
        System.out.println(펭귄객체 instanceof 펭귄);
        
        System.out.println(펭귄객체 instanceof Object);
    }
}

// 결과
true
true
true
true
true
true
true

모든 결과가 true가 출력되는 것을 볼 수 있다. 그런데 instanceof 연산자가 강력하긴 하지만 객체 지향 설계 5원칙 가운데 LSP (리스코프 치환 법칙)를 어기는 코드에서 주로 나타나는 연산자이기에 코드에 instanceof 연산자가 보인다면 냄새나는 코드가 아닌지, 즉 리펙토링의 대상이 아닌지 점검해 봐야 한다.

 

instanceof 연산자는 클래스들의 상속 관계뿐만 아니라 인터페이스의 구현 관계에서도 동일하게 적용된다. 아래의 코드를 참고하자.

interface 날수있는 {

}

class 박쥐 implements 날수있는 {

}

class 참새 implements 날수있는 {

}

public class Driver2 {

    public static void main(String[] args) {
        날수있는 박쥐객체 = new 박쥐();
        날수있는 참새객체 = new 참새();

        System.out.println(박쥐객체 instanceof 날수있는);
        System.out.println(박쥐객체 instanceof 박쥐);

        System.out.println(참새객체 instanceof 날수있는);
        System.out.println(참새객체 instanceof 참새);
    }
}

// 결과
true
true
true
true

결과가 모두 true가 반환되는 것을 볼 수 있다.


6. package 키워드

package 키워드는 네임스페이스 (이름공간) 를 만들어주는 역할을 한다. 만약 회사의 여러 개발 조직이 하나의 프로젝트에 참여하고 있다고 가정해보자. A 개발팀에서 Customer라는 클래스를 작성했다. 그리고 B 개발팀에서도 Customer라는 클래스를 작성한다면 두개의 클래스는 이름 충돌이 발생한다. 이 때 이름 공간을 나누고 A 개발팀에서는 A.Customer 라는 클래스를, B 개발팀에서는 B.Customer 라는 클래스 이름을 지정하면 이름 충돌을 피할 수 있다. 즉, A.Customer 와 B.Customer 가 완전 별개가 되는 것이다. 여기서 A 또는 B를 패키지로 보면 된다. 


7. interface 키워드와 implements 키워드

인터페이스는 public 추상 메소드와 public 정적 상수만 가질 수 있다고 했다. 

interface Speakable {

    double PI = 3.14159;
    final double absoluteZeroPoint = -275.15;

    void sayYes();
}

class Specker implements Speakable {

    @Override
    public void sayYes() {
        System.out.println("I say NO!");
    }
}

public class Driver {

    public static void main(String[] args) {
        System.out.println(Speakable.absoluteZeroPoint);
        System.out.println(Speakable.PI);

        Specker reporter1 = new Specker();
        reporter1.sayYes();
    }
}

// 결과
-275.15
3.14159
I say NO!

하지만 위의 예제를 보면 추상을 의미하는 abstract 키워드도 보이지 않으며 정적 멤버를 나타내는 static 키워드도 보이지 않는다. 왜 보이지 않는 것일까?

 

바로 인터페이스는 추상 메소드와 정적 상수만 가질 수 있기 때문에 메소드에 public 과 abstract 를, 속성에 public과 static, final을 붙이지 않아도 자동으로 자바가 붙여준다. 즉 위의 인터페이스 Speakable 코드와 아래의 코드가 동일하다는 말이다. 

interface Speakable {

    public static final double PI = 3.14159;
    public static final double absoluteZeroPoint = -275.15;

    public abstract void sayYes();
}

정말 같은 코드인지 확인해보자. 

interface Speakable {

    double PI = 3.14159;
    final double absoluteZeroPoint = -275.15;

    public abstract void sayYes();
}

public class Driver {

    public static void main(String[] args) {// ... 생략}

    public static void test(){
        // 에러 발생 : Cannot assign a value to final variable 'PI'
        // final이기 때문에 최종 변수 'PI'에 값을 할당할 수 없습니다. 라는 뜻이다.
        Specker.PI = 3.14;

        // 에러 발생 : Cannot assign a value to final variable 'absoluteZeroPoint'
        // final이기 때문에 최종 변수 'absoluteZeroPoint'에 값을 할당할 수 없습니다. 라는 뜻이다.
        Specker.absoluteZeroPoint = -275.0;
    }
}

위의 코드를 실행하면 PI, absoluteZeroPoint 의 변수에 값을 할당할 수 없다는 오류 메세지를 확인할 수 있다. 변수에 값을 할당할 수 없다는 것을 final 변수라는 의미이고, 클래스명으로 접근할 수 있는 것을 보니 정적 속성이라는 것을 알 수 있다. 즉 PI, absoluteZeroPoint 변수는 final static 멤버이다. 

 

추가적으로, 자바 8에서는 "변수에 저장할 수 있는 로직(함수)"인 람다가 등장함으로 인해 변수는 값을 저장할 수 있고, 메서드의 인자로 쓰일 수 있고, 메서드의 반환값으로 사용할 수 있다. 결국 람다로 인해 변수에 로직을 저장할 수 있고, 로직을 메서드의 인자로 쓸 수 있고, 로직을 메서드의 반환값으로 사용할 수 있다는 결론에 도달한다. 이것은 함수형 언어가 지닌 특성을 자바도 수용했다는 것을 의미한다.

 

자바에서 람다는 인터페이스를 기초로 하고 있다. 이에 따라 자바 8 이전까지의 인터페이스는 정적 상수와 추상 메서드만 가질 수 있었지만 자바 8부터는 디폴트 메서드라고 하는 객체 구상 메서드와 정적 추상 메서드를 지원할 수 있게 언어 스펙이 바뀌었다. 


8. this 키워드

this는 객체가 자기 자신을 지칭할 때 쓰는 키워드이다. 일상생활에서 "나"라고 하는 대명사와 같은 것이라고 보면 된다. 

class 사람{
    int age = 10;

    void test(){
        int age = 27;

        System.out.println("age : "+age);
        System.out.println("this.age : "+this.age);
    }
}
public class Driver {

    public static void main(String[] args) {
        사람 홍길동 = new 사람();
        홍길동.test();
    }
}

// 결과
age : 27
this.age : 10

위의 코드의 T메모리 구조는 아래와 같다.

코드상 age는 지역변수와 객체변수가 존재한다. 그냥 age를 출력했을 때 지역변수의 age에 우선권이 있다. 그렇기 때문에 그냥 age를 출력했을 땐 27 이, this.age를 출력했을 땐 객체 자체의 변수인 10이 출력되는 것이다. 아래의 내용을 기억해두자.

  • 지역 변수와 속성(객체 변수, 정적 변수)의 이름이 같은 경우 지역 변수가 우선한다. 
  • 객체 변수와 이름이 같은 지역 변수가 있는 경우 객체 변수를 사용하려면 this 를 접두사로 사용한다.
  • 정적 변수와 일므이 같은 지역 변수가 있는 경우 정적 변수를 사용하려면 클래스명을 접두사로 사용한다.

9. super 키워드

this는 객체 멤버 메소드 내부에서 객체 자신을 지칭하는 키워드이다. 단일 상속만을 지원하는 자바에서 super는 바로 위 상위 클래스를 지칭하는 키워드이다. 

class 동물 {

    void method() {
        System.out.println("동물");
    }
}

class 조류 extends 동물 {

    @Override
    void method() {
        super.method();
        System.out.println("조류");

//        super.super.method(); -- 오류 메세지
    }
}

class 펭귄 extends 조류 {

    @Override
    void method() {
        super.method();
        System.out.println("펭귄");
    }
}

public class Driver {

    public static void main(String[] args) {
        펭귄 뽀로로 = new 펭귄();
        뽀로로.method();

    }
}

// 결과
동물
조류
펭귄

위의 코드와 결과를 보면 super 키워드로 바로 위 상위 클래스의 인스턴스에 접근할 수 있지만 주석처리해놓은 super.super처럼 상위의 상위 클래스로 접근은 불가능한 것을 볼 수 있다. 

 


참고

- 스프링 입문을 위한 자바 객체지향의 원리와 이해

댓글