포스트

(Day 19) - OOP, 내장변수 this와 생성자, Initializer

이 글은 제가 교육을 수강하며 기록하고 추가한 내용입니다. 강사님과 무관하게 잘못된 내용이 있을 수 있습니다.


클라우드 기반 웹 데브옵스 프로젝트 개발자 교육 과정 (5기)

  • 비트캠프 엄진영 강사님 (https://github.com/eomjinyoung/)
  • 훈련기관 : 네이버클라우드주식회사
  • 기간: 2023-11-14 ~ 2024-5-22
  • 남은 일자 : 112 일 ( 19/129 )

본 내용은 제가 교육을 수강하며 기록한 내용으로, 빠른 템포를 따라가며 기록하고 제가 공부한 내용을 추가하므로 내용의 정확성이 부족할 수 있는 점 양해 부탁드립니다.

18일(2023-12-8, 금)

복습

OOP 에서는 ex02가 코어이다. 클래스를 메서드들을 분리하는 것, 사용자 정의 데이터타입을 만들기 위한 것 두가지 목적이 있다는 것을 Calculator, Score 두 클래스를 떠올리면서 기억하라.

용어: static, non-static, instance, reference, parameter, argument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	class 클래스명 {

	static int 스태틱 필드; (클래스 변수, 클래스 필드로도 불림) <= 클래스 로딩시 Method Area 생성된다, JVM 종료시 삭제된다.

	int 논스태틱 필드; <= (인스턴스 변수, 인스턴스 필드라고도 불림) new 명령으로만 생성된다. Heap 영역에 생성된다. 인스턴스를 찾아주는 레퍼런스가 없을  JVM의 GC가 삭제한다.  시점은 JVM  결정한다.

	void 메서드(int 파라미터) { <= 
	  int 로컬변수; <= 메서드 호출  생성
	  {
	    int 로컬변수
	  }
	}

	...

	}

클래스명 레퍼런스명 = new 클래스명(); <= 클래스의 인스턴스 변수를  영역에 실제로 생성하고, 인스턴스 주소를 레퍼런스명에 저장

클래스명.메서드(아규먼트) <= 메서드 호출
클래스명.스태틱 필드 <= 가능. Method Area의  하나의 변수를 말하게 된다.
클래스명.논스태틱 필드 <= 불가. 인스턴스 변수인데 Method Area의 변수를 찾고 있음
레퍼런스명.논스태틱 필드 <= 가능.  영역의 특정 인스턴스를 가리키는 레퍼런스를 통해 인스턴스의 변수에 접근.

이런 걸 계속 그리고 버리고 그리고 버리고.. 안보고 그릴 수 있을 때 까지 해야 한다.

로컬변수
메서드 블럭 안에 정의된 변수다. 스택 영역에 메서드의 프레임 안에 생성된다. 메서드 호출시 생성되며 메서드 호출이 끝나면 삭제된다.
static 변수 (클래스 변수)
클래스가 로딩될 때 생성된다. 로딩 시 단 한번만 생성된다.

참고

java에서 클래스 변수를 레퍼런스명.클래스변수명 형태로 접근할 수도 있다. 그러나 이건 그 코드를 읽는 개발자가 인스턴스 변수로 착각하게 만들기 때문에 피해야 한다.

import static 패키지경로.변수명;

유지보수를 쉽게 하기 위해서 계속계속 발전한다. 원래는 0, 1, 2 같은 원시값을 쓰다가 메인 클래스에서 int 변수를 만들어서 보기 좋은 글자로 쓰다가 그 변수는 변하지 않으니까 final이 붙고 다음엔 그걸 쓰는 클래스로 그 변수들을 옮겨서 응집성을 강화하고 그 변수들은 다른 인스턴스들이 공통으로 쓰니까 static으로 선언하고 매번 클래스명.스태틱변수 하고 부르기 힘드니까 import static 이라는 문법까지 나온 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 클래스 변수의 응용 : 상수 변수를 import 하기 
//
package com.eomcs.oop.ex03;

// Member 클래스를 외부의 다른 클래스에서도 사용한다면,
// nested class 로 선언하지 많고 패키지 멤버로 분리하라.
//
// 패키지 멤버의 스태틱 필드를 상요할 때는 다음과 같이 import로 
// 그 변수의 소속을 미리 밝힐 수 있다.
// => 스태틱 변수의 소속 클래스를 미리 밝혀두면 
//    클래스 이름 없이 스태틱 변수를 바로 사용할 수 있다.
import static com.eomcs.oop.ex03.Member.GUEST;
import static com.eomcs.oop.ex03.Member.MANAGER;
import static com.eomcs.oop.ex03.Member.MEMBER;

// 여러 개를 한 번에 명시하고 싶다면 다음과 같이 wildcard(*)로 지정해도 된다.
//import static com.eomcs.oop.ex03.Member.*;

public class Exam0163 {

  public static void main(String[] args) {

    Member m4 = new Member();
    m4.id = "aaa";
    m4.password = "1111";
    m4.type = GUEST; // import static 명령문에서 변수의 소속을 이미 밝혔기 때문에 클래스 이름을 적을 필요가 없다.
    // 만약 import에 선언되지 않았다면 스태틱 변수명 앞에 클래스명을 붙여야 한다.
    // 예) Member.GUEST

    Member m5 = new Member();
    m5.id = "bbb";
    m5.password = "1111";
    m5.type = MANAGER;

    Member m6 = new Member();
    m6.id = "ccc";
    m6.password = "1111";
    m6.type = MEMBER;
  }
}


스태틱 메서드 호출시에도…

레퍼런스.인스턴스메서드() 형태로 호출은 가능하다. 근데 이것도 인스턴스 메서드로 착각하게 만들 수 있어서 나쁜 코드다.

널 포인트 예외

1
2
3
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.eomcs.oop.ex03.Exam0210$A.m2()" because "obj2" is null

at com.eomcs.oop.ex03.Exam0210.main(Exam0210.java:40)

레퍼런스가 가리키는 오브젝트가 없이 널이어도 컴파일은 가능하나 실행하면 오류가 난다. 레퍼런스가 가리키는 인스턴스가 제대로 있는지 확인해보자.

빌트인 변수 (내장 변수)

인스턴스 메서드의 경우 this라는 내장 변수를 사용할 수 있다. 이클립스라면 윈도우 중 바이트코드 윈도우에서 로컬변수에 this가 있는 것으로 확인할 수 있다. 이 this 변수는 인스턴스 주소를 가진 변수이다. this는 뭔가 특별한 명령이 아니다. 말 그대로 이 메서드의 인스턴스의 주소 바이트 코드를 뜯어보면 알 수 있다. this.변수나 메서드 형태로 쓰는데 this를 생략한 경우, 로컬 변수가 없을 때는 자동으로 붙는다. 초반에는 생략하지 말자. 확실히 이해하고 공부하고 싶으니까.

기술은 모르면 모른채로 쭉 간다.

i18n l10n k8s … 그냥 사이의 알파벳 쓰기가 불편하니 줄인 것 뿐… 근데 모르면 쭉 간다.

this 키워드를 쓰지 않고도 같은 일을 하는 인스턴스 메서드를 작성해보시오

그러면 왜 this 키워드를 쓰는지 알 수 있다. 어차피 이렇게 해야 되는데, 더 편하게 할 수 있게 해준거다! 근데 왜 이렇게 해야 하는지, 그 원인인 불편함을 경험해보지 않으면 초보가 이해하기 어렵다.

초보를 혼동시키는 언어의 경제성

아래와 같이 용어가 축약되어 간다. 엄밀히는 틀렸으나 다들 이해를 충분히 해서… 인스턴스의 주소를 가리키는 레퍼런스 인스턴스를 가리키는 레퍼런스 인스턴스 주소 객체

생성자

파라미터를 안 받는 생성자. Score s1 = new Score(): 에서… new Score() 이거는 뭘까? 인스턴스를 생성하는데, 파라미터를 안 받는 생성자를 써서 생성한다는 것이다. 생성자가 없이 인스턴스를 생성할 수 없다. 생성자를 만들지 않으면 어차피 컴파일러가 기본생성자를 자동으로 추가한다. 반대로 개발자가 정의한 생성자가 하나라도 있으면 기본생성자를 만들지 않는다. 기본 세팅을 필수로 하려는 문법이니 당연하다.

한 클래스에서 생성자는 여러 개를 정의할 수 있다. 세팅을 하는 경우의 수가 다양할 수 있기 때문이다.

모든 클래스는 생성자를 가진다. 이클립스의 바이트코드 윈도우에서 생성자를 정의하지 않아도 public <init>()V 생성자를 확인할 수 있다.

생성자는 왜 쓰이나?

생성된 인스턴스가 제대로 쓰일 수 있도록 유효한 값으로 초기화시켜야 하기 때문이다. 인스턴스의 변수들에 어떤 값을 넣어줘야 제대로 동작하는 인스턴스를 생성할 수 있을텐데, 이 기본값을 넣는 방법을 정의하는 것이 생성자다. 변수에 대입 뿐 만이 아니라 메서드와 같이 다른 연산들도 가능하다. 기본값이 필요하지 않으면 파라미터없는 생성자를 쓰는 거고..

생성자this

생성자는 메서드다! this에는 특별한 문법이 있다. this(); 는 생성자를 호출하는 특별한 문법이다. this()로 생성자 메서드를 콜 할 때는

  • 생성자 메서드에서만 쓸 수 있다.
  • 메서드 바디의 가장 첫번째 라인에 있어야 한다.

왜 이런 문법을 만들었을까? 생성자의 재사용성을 높이려는 것으로 보인다. 아래 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 생성자 - 여러 개의 생성자 정의하기

package com.eomcs.oop.ex03;

  

public  class  Exam0430  {

  

static  class  Score  {

String  name;

int  kor;

int  eng;

int  math;

int  sum;

float  average;

  

Score()  {

System.out.println("Score()");

this.name  =  "이름없음";

}

  

Score(String  name)  {

System.out.println("Score(String)");

this.name  =  name;

}

  

Score(String  name,  int  kor,  int  eng,  int  math)  {

System.out.println("Score(String,int,int,int) 호출!");

this.name  =  name;

this.kor  =  kor;

this.eng  =  eng;

this.math  =  math;

this.compute();

}

  

public  void  compute()  {

this.sum  =  this.kor  +  this.eng  +  this.math;

this.average  =  this.sum  /  3f;

}

}

  

public  static  void  main(String[]  args)  {

  

// 생성자가 여러 개 일 때 파라미터에 전달하는 값으로 호출될 생성자를 구분한다.

Score  s1  =  new  Score();

  

// 인스턴스 생성 후에 나중에 따로 생성자를 호출할 수 없다!

// s1.Score("홍길동", 100, 90, 77); // 컴파일 오류!

  

Score  s2  =  new  Score("유관순");

Score  s3  =  new  Score("홍길동",  100,  90,  77);

// Score s4 = new Score(true); // 논리 값을 받는 생성자는 없다!

  

System.out.printf("%s, %d, %d, %d, %d, %.1f\n",

s1.name,  s1.kor,  s1.eng,  s1.math,  s1.sum,  s1.average);

  

System.out.printf("%s, %d, %d, %d, %d, %.1f\n",

s2.name,  s2.kor,  s2.eng,  s2.math,  s2.sum,  s2.average);

  

System.out.printf("%s, %d, %d, %d, %d, %.1f\n",

s3.name,  s3.kor,  s3.eng,  s3.math,  s3.sum,  s3.average);

}

}

  

// 생성자?

// => 인스턴스(객체)를 생성한 후, 쓰이기 전에 유효한 값으로 초기화시키는 작업을 수행한다.

//

자바 디버거

프로그램을 한 문장씩 천천히 실행해보자. Run 말고 Debug를 해보는 것이다.

이클립스에서 Debug를 실행하면…

private 생성자?

생성자는 인스턴스 메서드다. 인스턴스 메서드인데 private이다? 그 메서드를 다른 클래스에서 접근할 수 없다. 그러면 생성자를 (메서드를) 호출할 수 없으니 그 클래스를 인스턴스로 생성할 수 없다. 즉, private 생성자가 있다면 이 클래스를 인스터스로 생성하지 말라는 의미다.

인스턴스 필드와 스태틱 필드의 자동 초기화

  • 클래스가 로딩될 때, 인스턴스가 생성될 때
  • 메모리에 생성되는 필드들은
  • 모든 비트가 전기 없음 으로 설정된다. 전기 없음 = 신호 없음
  • 모든 비트가 전기 없음의 상태인 경우,
  • 원시값은
    • 모든 정수 원시값은 0을 나타내게 되고
    • 부동소수점 또한 0.0f 를 나타낸다.
    • char 데이터 타입도 0이다. (문자 집합에서 00000000 00000000 = ‘0’)
    • boolean = false 를 나타낸다.
  • 레퍼런스는
    • 00000000 00000000 00000000 00000000 = null을 의미한다.

이것들은 인스턴스 필드를 정의한 클래스를 인스턴스로 생성한 다음에, 실제로 그 값들을 출력해보면 알 수 있다. (출력할 수 있는 것도 자동으로 초기화가 되기 때문이다. 로컬 변수는 이런 자동초기화를 하지 않기에 초기화하지 않고 그 값에 접근하려고 하면 컴파일러가 에러로 처리한다.)

반대로 실제로 모든 비트를 0으로 만들고 싶으면 위에서 전기 없음의 상태를 나타내는 값을 할당하면 된다.

정수는 0 부동소수점은 0.0f 논리 변수는 false 레퍼런스는 null

결론: 인스턴스 변수는 자동으로 모든 비트가 0으로 초기화된다는 걸 알자.

클래스에서 인스턴스 변수를 선언할 때 개발자가 직접 초기화하지 않아도 특정한 값(모든 비트 0)으로 초기화가 된다는 것을 알고 있자.

Initializer

클래스에서는 초기화 블럭 두 가지를 사용할 수 있다.

  1. static 초기화 블럭 = static initializer 생성자와 유사하다, static 초기화 블럭에는 스태틱 필드를 유효한 값으로 초기화시키는 코드를 작성한다.
  2. instance 초기화 블럭 = instance initilizer 생성자에서 공통되는 블럭들이 많다보니 발생한 문법이다. 클래스의 맨 처음에 코드블럭을 만들면 된다. { ... } 그러면 이 코드는 컴파일 시 모든 생성자의 앞부분에 복사된다.

이러한 초기화 블럭들은 여러개가 작성될 수 있다. 컴파일 시 순서대로 하나의 블럭으로 합쳐진다. (바이트 코드로 확인할 수 있다.)

초기화 블럭 아래에는 주로 생성자에 대한 선언이 작성된다.

클래스 로딩시

클래스가 로딩되면 스태틱 필드가 생성되고, 스태틱 초기화 블럭이 실행된다.

  • 일반적으로 클래스는 단 한번만 로딩된다.
  • Class.forName() 을 통하여 강제로 클래스를 로딩시키는 것이 가능하다.

컴파일 시

인스턴스 블럭의 코드를 생성자로 옮긴다. 생성자가 여러 개일 때는 각가의 생성자에 모두 인스턴스 블럭을 옮긴다.

ex03 정리하자

관습적으로 암기해야 하는 내용이 많다.

1
2
3
4
5
Class.forName("com.eomcs.oop.ex03.Exam0650$A");
// import 하는 것과 상관없이 반드시 패키지 이름을 포함해서 클래스 이름을 지정해야 한다.
// 주의!
// => import 문장에서는 $ 대신 .을 써야 한다.
// 예) import com.eomcs.oop.ex03.Exam0650.A;

자바 컴파일러가 소스파일을 컴파일 할 때 중첩클래스가 있는 경우 바깥클래스$내부클래스 이렇게 따로 컴파일된다. $ 를 쓴다!!! 이런 건 암기를 해야 한다.. 둘 다 허용하면 안되나?

Initialization Block .. 인스턴스나 스태틱 다 합쳐질건데 왜 따로 나눠서 여러개 만들 수 있게 해놨나?

그냥은 아니다. 같은 이름의 변수를 여러번 초기화할 수 있다… 근데 이게 의미가 있는건가…? GPT야 도와줘.

권장사항

  • 별도의 목적을 가지고 있지 않다면 초기화 블럭(Initialization)은 하나만
  • 줄여서 작성하는 경우(변수 초기화 문장)가 가독성이 더 좋음. 단순 값의 할당이면 이렇게 하자. ```java

    인스턴스 블럭은 전부 생성자로 넘어간다.

    인스턴스 초기화 블록 사용 후

  • 여러 생성자에 공통으로 존재해야 하는 코드가 있다면 별도의 블록으로 뽑아내는 것이 소스 코드 관리에 좋다.
  • 이럴 때 사용하라고 만든 문법이 인스턴스 블록이다.
  • 다음과 같이 인스턴스 초기화 블록을 사용하여 생성자에 공통으로 들어갈 코드를 작성하면 된다. ```

디자인 패턴에 대해서..

Gang of Four… 도발적인 사람들… 어떻게 하면 재사용성이 높고 유지보수비용을 낮추는 프로그램을 설계할 수 있을까? 그 노하우, 기법이 디자인패턴이다. 사람들이 세미나 등을 통해서 성공사례등을 공유하면서 노하우를 뽑아놓은 게 디자인 패턴이다. SOLID GRASP … 수천 수만개의 설계 노하우, 성공사례들을 모아서 분석하여 그 중에서 핵심이 될 수 있는 설계기법을 추출했다. 이 핵심적 설계기법들을 조합해서 다시 무수히 많은 설계기법들을 도출할 수 있다. 마치 뭔가를 쪼개고 쪼개고 쪼개고… 하면 전자와 양성자, 중성자가 나오듯이 말이다.

나중에 마이바티스나 IOC 컨테이너도 만들어 볼 것

점점 수준이 아주 깊어짐. 기술 수준이 높아짐!! 물론 진짜 마이바티스나 IOC 컨테이너랑 똑같이 만든다는 게 아니라 핵심 기술이 어떻게 구현되는지 살펴보겠다는 것임.

중첩 클래스

1
2
3
4
5
6
클래스 A { => A.class로 컴파일되는데, B클래스 내용은 완전히 빠진다.
  클래스 B {
  ... => A$B.class  컴파일된다.
  }
  ...
}

빠른 성장의 방법

바닥기초를 익히고 성장하면 성장이 빠르다.

IoT 진영의 현황

IoT는 수많은 데이터를 다루는 플랫폼

현재는 C가 대세

자바도 ME로 참전하려 했으나…

Java ME(Java Platform, Micro Edition)는 모바일 및 임베디드 시스템을 위한 경량 Java 플랫폼으로서 개발되었습니다. 그러나 Java ME는 시간이 지남에 따라 상대적으로 성공적이지 않은 플랫폼으로 여겨지고 있습니다. 다양한 이유로 인해 Java ME는 몇 가지 제한과 도전에 직면했습니다.

  1. 경쟁의 등장: Android 및 iOS와 같은 모바일 운영 체제의 등장으로 인해 Java ME의 입지가 점차 감소했습니다. 이러한 플랫폼들은 더 강력하고 다양한 기능을 제공하며 개발자들이 더 풍부한 애플리케이션을 개발할 수 있도록 했습니다.

  2. 기술적 제한: Java ME는 초기에 모바일 기기의 제한된 리소스와 하드웨어 제약 조건을 고려하여 설계되었습니다. 그러나 이로 인해 개발자들이 제한된 환경에서 작업해야 했고, 이로 인해 덜 풍부한 사용자 경험을 제공하는 애플리케이션을 만들 수밖에 없었습니다.

  3. 개발자 경험: 다른 플랫폼에 비해 Java ME의 개발자 경험이 떨어졌습니다. 다양한 모바일 기기 및 제조사 간의 호환성 문제가 발생하며 개발자들이 일관된 경험을 제공하기 어려웠습니다.

  4. 기술 진보의 부재: Java ME는 기술적인 측면에서 다른 플랫폼에 비해 뒤처져 있는 면이 있었습니다. 새로운 기술 및 기능이 계속해서 발전하면서 Java ME는 이러한 트렌드에 뒤쳐지게 되었습니다.

이러한 이유로 Java ME는 현재 모바일 및 임베디드 시장에서 크게 주목받지 못하고 있습니다. 대부분의 개발자 및 기업은 더 강력하고 현대적인 플랫폼에 중점을 두고 있습니다.

구글이 노리는 것

안드로이드 개발자 많다. 이걸 IoT 개발할 수 있게 플랫폼을 만든다면…

삼성 Things

디바이스를 만드는데 그냥 줄 수 없지

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.