[java] 스프링 입문을 위한 자바 객체지향의 원리와 이해 정리 4

자바가 확장한 객체지향

abstract 키워드 - 추상 메서드와 추상 클래스

상위 클래스에서 각 특색을 가진 하위 클래스에 공통된 객체 멤버 메서드를 필요로 할때

ex) 동물 클래스의 say 메서드를 각 하위 클래스 예를들어서 펭퀸이라는 하위 클래스가 오버라이딩하여 say 메소드를 사용한다고 가정했을때 동물 클래스의 참조 변수를 하위 클래스의 객체로 선언한다고 생각해보자. 이때 동물 클래스는 동물 클래스 자체의 객체 멤버로 say 메소드를 가져야하고 이 메소드는 동물 클래스 자체 객체를 구현했을 때를 위해 메소드의 몸체도 존재해야 한다. 하지만 그럴필요도 없고 하위 클래스들을 위해 공통적으로 필요한 객체 멤버 메소드이기 때문에 이때 abstract 키워드를 사용해서 추상메서드와 추상 클래스로 사용한다.

위와 같은 가정은 두가지 문제가 발생한다.

//동물 객체는 어떻게 울어야 하나?

// 동물 참조 변수 배열로 모든 동물을 울게 하려면 하위 클래스에서 오버라이딩 할 울어보세요 메서드가 필요하다.

이 두가지를 해결하는게 abstract 키워드를 활용한 추상 메서드, 추상 클래스이다.

메서드 선언은 있으나, 몸체는 없는 형태로 메서드를 구현하는 방식이다.

위와 같은 상황에서 abstract 키워드와 함께 추상 클래스와 추상 메소드를 구현했을때 아래와 같은 코드를 작성하면

동물 짐승 = new 동물()

Cannot instantiate the type 동물과 같은 오류가 발생한다.

추상클래스는 인스턴스, 즉 객체를 만들 수 없는 클래스가 된다.

생성자

보통 메서드를 호출할 때는 메서드()과 같이 소괄호를 열고 닫는다.

new 클래스명()도 마찬가지다. 반환값이 없고 클래스명과 같은 이름을 가진 메서드를 객체를 생성하는 메서드라고 해서 이를 보통 객체 생성자 메서드라고 하고 흔히 생성자라고 많이 한다.

여기서 중요한점은 우리는 이런 메서드를 만든적이 없고 이는 자바가 알아서 인자가 없는 기본 생성자를 자동으로 만들어준다는 점이다.

public class 동물 {

}

public class Driver {
  public static void main(String[] args) {
    동물 뽀로로 = new 동물();
  }
}

동물 클래스 안에는 어떠한 메서드도 없다. 하지만 하나의 메서드가 존재한다. 바로 아무런 인자도 갖지 않는 기본 생성자 메서드다.

하지만 아래처럼 코드를 작성한다면

public class 동물 {
  public 동물(String name) {
    System.out.println(name);
  }
}

public class Driver {
  public static void main(String[] args) {
    동물 뽀로로 = new 동물("쁘르르");
    동물 뽀로리 = new 동물();
  }
}

인자를 주지 않는 동물 생성자에서 에러가 발생한다.

이유는

두가지 이유로 인자가 있는 생성자를 만들었기 때문에 더이상 기본생성자가 작동하지 않았다.

생성자는 개발자가 필요한 만큼 오버로딩해서 만들 수 있다.

클래스 생성 시의 실행 블록, static 블록

static 블록은 클래스가 스태틱 영역에 배치될 때 실행되는 코드 블록이다.

public class 동물 {
  static {
    System.out.println("동물 클래스 고")
  }
}

public class Driver {
  public static void main(String[] args) {
    동물 뽀로로 = new 동물();
  }
}

해당 코드는 “동물 클래스 고” 라는 결과를 가진다.

public class 동물 {
  static {
    System.out.println("동물 클래스 고")
  }
}

public class Driver {
  public static void main(String[] args) {
    System.out.println("메인만 고고")
  }
}

해당 코드는 “메인만 고고” 결과만을 갖는다.

static 블록은 클래스가 스태틱 영역에 배치될 때 실행된다. 하지만 메인 메서드에서 동물 클래스를 사용하지 않기 때문에 배치되지 않는다.

static 영역에서 사용할 수 있는 속성과 메서드는 당연히 static 멤버 뿐이다. 객체멤버(힙), 스택에 접근할 방법도 없을 뿐더러 객체 멤버는 클래스가 static 영역에 자리 잡은 후 객체 생성자를 통해 힙에 생성되기 때문이다.

정리하면 해당 패키지나 클래스가 처음 사용될 때 스태틱 영역에 로딩되는 점을 명심하자.

public class 동물 {
  static int age = 0;
  static {
    System.out.println("동물 클래스 고")
  }
}

public class Driver {
  public static void main(String[] args) {
    System.out.println("메인만 고고")
    System.out.println(동물.age)
  }
}

위 코드의 결과는 아래와 같다.

“메인만 고고”

“동물 클래스 고”

“0”

  1. 메인 메서드가 실행
  2. 동물 클래스 스태틱영역으로 배치
  3. 동물 클래스의 static 블록에 있는 메서드 실행
  4. 메인 메서드에서 실행한 콘솔 실행

정리해보면 클래스 정보는 해당 클래스가 코드에서 맨 처음 사용될 때 T 메모리의 스태틱 영역에 로딩되며 이때 단 한번 해당 클래스의 static 블록이 실행된다.

클래스가 제일 처음 사용될 때는

프로그램이 실행될 때 클래스를 스태틱영역에 올리지 않고 사용할 때 올리는 이유는 스태틱 영역도 메모리 이고 메모리는 최대한 늦게 사용을 시작하고 최대한 빨리 반환하는 것이 정석이기 때문이다.

실무에서 static을 사용할 일은 거의 없지만 특성을 기억해놓는것은 좋다.

@BeforeClass 어노테이션 살펴보기

final 키워드

클래스, 변수, 메서드 모든 부분에서 사용이 가능하다

final과 클래스

public final class 고양이() {

}

이 클래스는 상속을 허락하지 않는다.

public class 길고양이 extends 고양이 {}

위 코드는 에러를 발생시킨다.

final과 변수

변경 불가능한 상수가 된다.

정적상수: 선언시에 또는 정적 생성자에 해당하는 static 블록 내부에서 초기화가 가능하다.

객체상수: 선언시에 또는 객체 생성자 또는 인스턴스 블록에서 초기화 할 수 있다.

public class 고양이 {
  final static int 정적상수1 = 1;
  final static int 정적상수2;

  final int 객체상수 = 1;
  final int 객체상수2;

  static {
    정적상수2 = 2;
    //상수는 한 번 초기화되면 값을 변경할 수 없다.

    //정적상수2 = 3 //에러
  }

  고양이() {
    객체상수2 = 23;

    //객체상수2 = 11; // 에러


    final int 지역상수1 = 123;
    final int 지역상수2;


    지역상수2 = 1233;
  }
}

final과 메서드

오버라이딩을 금지시킨다.

interface 키워드와 implements

interface Speakable() {
  double PI = 3.14159;
  final double absoluteZeroPoint = -275.15;

  void sayYes();
}

interface Speakable() {
  public static finaldouble PI = 3.14159;
  public static final double absoluteZeroPoint = -275.15;

  public abstract void sayYes();
}

위 두코드는 같은코드다.

인터페이스는 추상 메서드와 정적상수만 가질 수 있기 때문에 따로 메서드에 public abstract, 속성에 public과 static, final을 붙이지 않아도 자동으로 자바가 알아서 붙여준다.

위 두코드 중 주관적이지만 명백하게 키워드를 다 작성한 코드가 좋다고 보여진다.

this 키워드

class 펭귄() {
  int var = 10;

  void test() {
    int var = 20;

    var?
    this.var?
  }
}

271 라인은 20

272 라인은 10이다.

지역 변수에 저장돼 있는 값이 아닌 객체 변수에 저장돼 있는 값을 사용하고 싶은데 지역 변수와 객체 변수의 이름이 같은 경우 this.var라고 하면 객체 변수 var에 저장한 값을 사용하게 된다.

super 키워드

super는 바로 위 상위 클래스의 인스턴스를 지칭한다.

super.method 와 같이 접근이 가능하다.

super 키워드로 바로 위의 상위 클래스 인스턴스에는 접근할 수 있지만 super.super형태로 위위 클래스의 인스턴스에는 접근이 불가하다.

예비고수를 위한 한마디

class 펭귄 {
  void test() {
    System.out.println("Test")
  }
}

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

    뽀로로.test
  }
}

위와 같은 상황일 때 만약 뽀로로 인스턴스가 100개 1000개라면 펭귄 객체는 똑같이 1000개가 힙에 생성되야 한다.

하지만 똑같은 객체 멤버 메소드, 속성을 갖고 있는 객체를 그 수만큼 만드는 건 메모리 낭비다.

JVM은 지능적으로 객체 멤버 메서드 test를 static 영역에 단 하나만 보유한다. 그리고 눈에 보이지 않지만 test() 메서드를 호출할 때 객체 자신을 나타내는 this 객체 참조 변수를 넘긴다. 즉 만들어진 객체가 막상 사용하는 메소드는 static 영역에 있는 메소드인 것이다.