03. 자바와 객체 지향
캡슐화: 정보 은닉
자바에서 정보 은닉이라고 하면 접근 제어자(private, default, protected, public)를 떠오르는데요. 이 4개의 접근 제어자에 대해 간략하게 설명하면 다음과 같습니다.
public : 모두가 ~ 접근 가능
protected : 상속 / 같은 패키지 내의 클래스에서 접근 가능
default : 같은 패키지 내의 클래스에서 접근 가능
private : 본인 클래스 내에서만 접근 가능
그런데 잊기 쉬운 것은 protected도 default와 같이 같은 패키지 내의 클래스에서 접근 가능하다는 점입니다. 또 접근 제어자를 고려할 때 중요한 점은 이것이 정적 멤버 / 메서드 인지, 인스턴스 멤버 / 메서드인지 구분해야 한다는 것입니다. 정적 멤버 / 메서드와 위와 같은 4개의 접근 제어자 규칙에 따르지만 정적 멤버 / 메서드와 인스턴스 멤버 / 메서드 간에 관계도 있기 때문입니다. 즉, 인스턴스 메서드에서 정적 멤버 / 메서드에 접근하는 것은 가능하지만 정적 메서드에서 인스턴스 멤버 / 메서드에 접근하는 것이 안된다는 것입니다. 이는 전에 T메모리에서 봤듯이 인스턴스 멤버 / 메서드는 객체가 생성되어야 만들어지기 때문입니다.
정적 멤버에 접근할 때는 물론 객체를 생성해 객체참조변수명.정적멤버 형식으로 접근이 가능하기도 하지만 클래스명.정적멤버로 보통 접근을 합니다. 그 이유는 아래 그림을 보면 쉽게 이해할 수 있습니다.
참조 변수의 복사
최근에 로또 미션을 구현하면서 생겼던 오류가 이 부분은 제대로 이해하지 못해서 발생하였습니다. 기본 자료형이 아닌 객체 참조 변수를 복사하면 어떻게 될까요? 이 경우 Call by Value(값에 의한 호출)이 아닌 Call by Reference(참조에 의한 호출) 또는 Call by Addresss(주소에 의한 호출)이 발생하게 됩니다. 아래 코드에 대해 T메모리로 나타내면 다음과 같습니다.
Animal ref_a = new Animal();
Animal ref_b = ref_a;
ref_a와 ref_b는 완전히 다른 변수이지만 두 변수가 같은 객체를 참조하고 있는 것입니다. 위와 같이 =
을 이용하는 것 말고도 return
을 통해 반환값을 객체를 넘길 때도 주소값이 넘어가기 때문에 이 부분은 인지하고 사용하여야 합니다. 값만 복사된다고 생각한다면 의도하지 않은 결과가 발생할 수 있습니다. 만약 값만 복사하여 넘기고 싶다면 clone()
를 사용하거나 new
를 통해 새로운 객체 생성 후 초기화하여 넘겨줄 수 있습니다.
04. 자바가 확장한 객체 지향
abstract 키워드 - 추상 메서드와 추상 클래스
추상 메서드는 어떨 때 쓰일까요? 예를들어 상속 형태가 적절하다고 생각해서 상위 클래스('동물')와 이를 상속받는 하위 클래스('고양이', '강아지')가 있다고 생각합시다. 각각의 하위 클래스에서 Override하기 위해선 상위 클래스에선 그 Override할 원본 메서드가 필요한데요. 만약 그 메서드가 '울다()'라는 메서드라면 상위 클래스에선 어떻게 구현해놓아야 할까요? 또 실수로 아무런 의미없는 상위 클래스의 객체를 생성해버린다면 어떨까요?
그래서 등장한 것이 추상 메서드와 추상 클래스 인 것입니다. 이렇게 명세만 제시하고 싶은 메서드와 클래스는 abstract 키워드를 사용하여 선언만 해놓는 것입니다. 이렇게 하면 추상 메서드 내에서는 따로 구현할 필요없고 추상 클래스는 객체 생성이 안되기 때문에 위 문제가 한번에 해결되는 것이죠.
생성자
생성자를 만들지 않았을 때 저희는 이렇게 객체를 선언합니다. 동물 뽀로로 = new 동물();
그런데 여기서 동물()
이 뭘까요? 이것도 메서드 중 하나인데요. 반환값도 없고 클래스명과 같은 이름을 가졌을 뿐 아니라 객체를 생성하는 메서드라 하여 '객체 생성자 메서드'라 합니다. 줄여서 보통 생성자라 부릅니다. 따로 생성자가 선언하지 않았을 때 사실은 컴파일러가 자동으로 이렇게 기본 생성자를 만들어줍니다.
public class 동물 {
public 동물() {}
}
그러나 주의할 점은 따로 사용자가 인자가 하나라도 있는 생성자를 구현해놓으면 컴파일러는 기본 생성자를 안 만들어준다는 것입니다.
클래스 생성 시의 실행 블록, static 블록
객체 생성자와 마찬가지로 클래스 생성자도 존재할거라 기대할 수도 있는데요. 사실 그런건 없지만 이와 비슷한게 있는데 그게 static 블록입니다. 클래스가 Static 영역에 배치될 때 딱 한번 실행됩니다. 아 static 블록을 좀 더 자세히 설명하기 전에 고백해야할 것이 있는데요. 이전에 Static영역에 배치되는 것이 java.lang와 더불어 사용자가 만든 모든 클래스들이 배치된다고 하였습니다. 하지만 사실은 사용자가 만든 모든 클래스가 한번에 다 올라가는 건 아닙니다. Static영역도 메모리이므로 처음부터 안 사용되는 건 최대한 늦게 배치하는게 낫겠죠. 따라서 Static영역에 배치될 때는 아래와 같은 상황일 때 입니다.
- 클래스의 정적 속성을 사용할 때
- 클래스의 정적 메서드를 사용할 때
- 클래스의 인스턴스를 만들 때
따라서 특정 클래스 안에 static 블록이 있다면 실행 시점은 위의 3가지 상황일 때 그 때 비로소 Static영역에 배치되면서 실행되는 것입니다.
추가적으로 static 블록과 유사하게 인스턴스 블록이란 것도 있다고 하네요. 아무런 표시없이 {}
블록을 사용하는데요. 객체 생성자가 실행되기 전 실행된다고 하네요. 거의 사용되진 않지만 혹시나 다른 코드에서 혹시나 보게될 수도 있으니까 참고해야겠어요.
final 키워드
final 키워드는 클래스, 변수, 메서드에서 사용될 때 그 전하는 메시지가 분명한데요. 간략하게 정리해보죠.
final과 클래스 → 상속을 허락하지 않겠다
final과 변수 → 변경 불가능한 상수로 만들겠다
final과 메서드 → 재정의, 즉 오버라이딩을 막겠다
instanceof 연산자
이전에 상속을 설명하면서 설명을 하였는데요. 부가적인 부분에 대해 알아보도록 하겠습니다. 먼저 instanceof
는 아래와 같이 사용되죠.
객체_참조_변수 instanceof 클래스명
객체 참조 변수가 상위 클래스로 선언되었더라도 new 뒤의 실제 객체 타입에 의해 처리 되기 때문에 자기 자신부터 그 위의 상위 클래스 모두 제대로 판별이 됩니다. 또 상속 관계 뿐만 아니라 인터페이스 구현 관계에서도 동일하게 적용됩니다!
interface 날수있는 {
}
class 조류 implements 날수있는 {
}
class 펭귄 extends 조류 {
}
public class Driver {
public static void main(String[] args) {
날수있는 조류객체 = new 조류();
조류 펭귄객체 = new 펭귄();
System.out.println(조류객체 instanceof 날수있는);
System.out.println(조류객체 instanceof 조류);
System.out.println(펭귄객체 instanceof 날수있는);
System.out.println(펭귄객체 instanceof 조류);
System.out.println(펭귄객체 instanceof 펭귄);
}
}
예를들어 다음과 같은 코드가 있다면 모두 true로 제대로 판별이 됩니다.
true
true
true
true
true
interface 키워드와 implements 키워드
인터페이스는 무조건 public으로 사용된다는 것은 알고 있었습니다. 외부에 노출되는 대변인의 역할을 하기 위해선 그럴 수 밖에 없죠. 그런데 선언할 때 그 안에 컴파일러가 public, static, final을 알아서 붙여주는지는 몰랐네요.
interface Speakable {
double PI = 3.14159;
final double absoluteZeroPoint = -275.15;
void sayYes();
}
위 코드는 사실 아래와 같이 컴파일 되는 것이죠.
interface Speakable {
public static final double PI = 3.14159;
public static final final double absoluteZeroPoint = -275.15;
public abstract void sayYes();
}
변수는 public static final
로 상수 처리하고 메서드는 추상 클래스로 알아서 만들어주는 것이군요!
this 키워드, super 키워드
this
는 객체 자기 자신을 지칭할 때 쓰는 키워드입니다. 컴파일러는 변수의 값을 알아볼 때 가장 가까운 값, 그러니까 같은 이름의 변수가 있다면 그 안의 지역변수, 그리고 객체변수 순서로 우선권을 줍니다. 만약 this
키워드를 사용하면 객체 자신을 지칭함으로써 객체변수 먼저 호출할 수 있는 것이죠.
super
키워드는 바로 위 상위 클래스를 지칭한다는 것, super.super
형태로 사용 못한다는 것, super.변수
, super.메서드()
로 사용될 수 있다는 것만 알면 될 것 같네요!!
보너스
예를들어 다음과 같은 코드가 있다고 생각해보겠습니다.
class 펭귄 {
void test() {
System.out.println("Test");
}
}
public class Driver {
public static void main(String[] args) {
펭귄 뽀로로 = new 펭귄();
뽀로로.test();
}
}
이를 실행해보면 Stack영역에 'main()스택 프레임' 위에 '뽀로로.test()'가 아니라 '펭귄.test()'가 있는 실제 실행되고 있는 것을 볼 수 있습니다. 그 이유는 만약 new 펭귄[100]
가 생성되었다고 해봅시다. 그러면 Heap영역에 펭귄 객체가 100개나 만들어져야 할 것입니다. 그런데 메서드는 다 똑같은데 매 객체마다 메모리 할당되어있다면 너무 메모리 낭비아닙니까? 따라서 JVM은 객체 멤버 메서드 test()
를 Static영역에 두고 객체 자신를 말해주는 this를 받아 이에 따라 test()
메서드를 호출한다고 하네요. T메모리로 나타내면 이렇게 되는 것이죠.
test()
에서 눈에 보이진 않지만 사실은 test(펭귄 this)
로 객체를 받고 있으며 이에 따라 각각 객체의 상태값을 이용하여 Static영역에 있는 메서드가 호출되는 것이죠.
이렇게 해서 4장 정리도 무사히 끝마쳤네요. 평소 아무렇지 않게 사용하고 있었지만 그 뒤에서 어떤 일이 이루어지고 있었는지 알 수 있는 유익한 시간이었습니다. 아직 풀리지 않는 궁금증도 여러 있지만 이 뒤 5장을 좀 더 읽으면서 그 답을 찾아보려 합니다.
'Book > programming' 카테고리의 다른 글
프로가 되기 위한 웹기술 입문1 (0) | 2018.11.07 |
---|---|
스프링 입문을 위한 자바 객체지향의 원리와 이해6 (2) | 2018.10.27 |
스프링 입문을 위한 자바 객체지향의 원리와 이해4 (0) | 2018.10.18 |
스프링 입문을 위한 자바 객체지향의 원리와 이해3 (0) | 2018.10.18 |
스프링 입문을 위한 자바 객체지향의 원리와 이해2 (0) | 2018.10.14 |