OOP paradigm의 장점은 생략
OOP 관련 기본 terminology들은 알고 있다고 가정.
making instance
-
Person
이라는 class가 어떻게든 정의되어 있다고 해보자. attribute로 name
이 있고 method로 printName
이 있다고 해보자.
-
기본형과는 다르게, Kotlin처럼 참조형을 저장하는 변수만 만드는게 가능하다. C++과 다르게 instance 자체를 저장하는 변수는 만드는게 안된다.
Person p1;
- instance를 만들거면
new
를 활용. C++과 동일하게 생성자의 이름이 class의 이름과 동일하다.
p1 = new Person();
- field 접근, method 사용 전부 C++과 비슷하다.
p1.name = "Baekrang";
p1.printName();
p1 = new Person();
p2 = new Person();
p1.name = "Baekrang";
p2.name = "BlackRabbit";
p2 = p1;
System.out.println(p1.name);
System.out.println(p2.name);
- instance의 배열은
String
배열 처럼 reference들의 모음이다.
types of variable in class
- C++이랑 매우 유사하다.
- class variable : 동일 class instance들이 공유하는 변수.
static
으로 선언. 프로그램 실행동안에는 계속 존재. 접근은 class 이름을 가지고 한다.
- instance variable : instance마다 가지는 변수. instance 사라지면 소멸. 접근은 instance를 가지고 한다.
- local variable : method, constructor, init block 등에서 정의된 변수. 해당 영역 벗어나면 소멸. class 외부에서 접근 불가.
class MyClass
{
int instance_variable;
static int class_variable;
void classMethod()
{
int local_variable;
}
}
defining function/method
- C/C++이랑 유사하나 굳이 형태를 언급하자면
int method1(int x, int y)
{
return x + y;
}
-
function은 function 내 지역변수랑 parameter만 접근이 가능.
-
특정 class instance의 method는 (조건에 따라) instance variable 접근이 가능하며 그 외에도 parameter 선언된 local variable들 등이 가능. 자세한건 뒤에 배운다...만 C++이나 Kotlin을 써봤으면 뭔소린지 이미 알 것이다.
JVM memory structure
- 사실 C/C++ program load시의 memory 구조랑 매우 유사핟.
- heap : instance 생성 공간. C/C++ malloc area와 유사
- call stack : C/C++ stack area와 유사한 역할
- method area : class 정보 저장. class variable도 여기서 저장된다. C++ static area와 유사.
- call stack의 작동 원리는 시스템 관련 내용을 이미 알고 있다고 가정하고 생략.
Primitive type and Reference type
- 변수가 function/method에 parameter로 전달될때 그 변수의 type이 primitive type이면 값 자체가 전달되고, reference type이면 reference가 전달된다.
- reference가 전달된 경우 그 안의 값을 변동시키면 외부에도 그 영향이 나타난다.
- C++에서 reference를 전달한 경우, C/C++의 pointer을 전달한 경우가 Java의 reference를 전달한 경우랑 마찬가지라고 보면 된다. 크게 어렵진 않다.
- primitive type은
String
을 제외한 모든 기본 자료형들을 말한다. 나머지는 Reference type에 해당.
- function/method에서 반환되는 것의 type이 primitive type인 경우 값 자체가, reference type이면 reference가 반환된다. parameter 전달과 마찬가지.
- 이때 parameter의 할당 방식과 return하는 애들의 할당 방식에 대한 구체적인 원리는 C/C++과 유사하니 생략.
static method vs instance method
- static method는 class들이 '공유'하는 method라고 생각하면 된다. static variable처럼.
- instance variable들을 활용하는게 불가능하다. instance가 만들어지지 않아도 (static variable 처럼) class 정보가 들어간 순간, 즉 프로그램이 시작하면 static method 호출이 가능하기 때문에 이를 허용하면 문제가 생기기 때문.
- 이와 대척되는, instance마다 고유로 가지는 method를 instance method라고 한다.
- 할당 방식 + 메모리 접근 방식을 고려하면 이론적으로 static method가 더 빠르긴 해서 가능하면 static method를 사용하는게 좋으나, 실제로는 JVM측 최적화가 꽤 쎄서 생각보다 차이가 안 난다. 그래도 static이 손해일 일은 없으니 참고.
- static method 선언 방식은 다음과 같다.
static void staticMethod() {
return;
}
- static method안에 static method 호출은 가능하고, instance method 안에 instance/static method를 호출하는것도 가능하나 static method에서 instance method 호출은 불가능하다. instance variable을 못 쓰는것과 같은 이유.
overloading
- 같은 method, 다른 꼴.
- method 이름은 동일한게 여러개가 존재하는게 가능하지만 parameter의 type및 개수까지 완전히 일치하는 형태의 overloading은 안된다. C++과 동일한 규칙.
varargs
- C++ variadic argument와 유사한 문법이다.
- type 이름 +
...
+ 변수 이름 형태로 사용.
public PrintStream printf(String format, Object... args) {...}
- varargs는 무조건 마지막 parameter로 등장해야 한다. 중간이나 앞에 선언시 뒤의 parameter이 varargs에 속하는지를 모르기 때문.
- varargs 영역에 아무것도 안 들어가도 되며, array를 사용해도 된다. 이는 semantic 상으로 varargs를 array로 취급하기 때문.
- 이 때문에 varargs가 들어간 method는 호출할 때마다 array를 생성하는 것으로 성능에 좋지 않은 영향을 줄 수 있으니 유의.
- 그냥 parameter을 array로 사용하는것보다 좋은 점은 인자가 null인 경우나 인자가 없는 경우의 edge case를 고려할 필요가 없기 때문이다. (varargs에선 이 경우 길이가 0인 array가 들어갔다고 생각하면 된다.)
varargs and overloading
- varargs를 가지고 overloading을 하는데, overloading 형태 중에 varargs type으로만 이루어진 overloading이 존재시 오류를 일으킬 수 있다. 이는 어디까지가 varargs고 어디부터가 varargs가 아닌 parameter인지 판단이 불가능하기 때문.
constructor
- 메모리 할당은
new
, initialization은 constructor이 담당. C++과 동일.
- constructor이 하나도 정의된게 없으면 default constructor이 자동으로 생성. C++과 동일.
- constructor이 하나라도 정의되면 default constructor이 자동으로 생성 안됨. 수동으로 정의는 가능. C++과 동일.
- constructor overloading 가능. C++과 동일.
- 단 attribute initialize를 할 때 C++처럼 간략히 생성하는 initialization list 같은건 없고 block 내에서 일일이 다 값을 initialize해야 한다. (사실 이건 상황에 따라 block에서 initialize한거랑 다르지만 여튼) 대충 이런 식으로.
Person(String name) {
myName = name;
}
- 생성자 내에 다른 생성자 호출이 가능하나, 이 경우 생성자 이름이 아닌
this
를 이름으로 사용해야 한다. 그리고 무조건 첫줄에서 호출해야 한다. 이유는 다른데서도 호출이 가능해지면 그 이전에, 호출을 한 constructor이 한 짓이 무의미할 수 있어서 이를 방지하기 위해 막아놓음.
Person(String name) {
this();
myName = name;
}
- 말나온김에, 이
this()
constructor은 그냥 constructor 내에서 constructor 호출 용도로 사용되는 것이며, this
라는 reference variable과는 다르다.
- 그리고
this
라는 reference variable은 static method에서 사용하는게 불가능하다. 이유는 static method에서 instance method를 못쓰는...것과 같이 앞에서 얘기했던 내용과 똑같은 이유.
- C++처럼 Java도
this
parameter이 모든 instance method에 숨겨진 parameter로 존재한다.
- 앞으로 class instance끼리 복사하는 방법을 많이 배울거지만, constructor을 가지고 일단 복사하는 방법은 그냥 constructor이 class type인 reference variable을 parameter로 가지게 하면 된다.
variable initialization
- C++처럼 instance variable들은 따로 constructor에서 initialize를 안해도default값으로 initialize가 이루어진다.
- local variable들은 당연히 사용 전에 initialize를 해야 하니 유의.
- C++처럼 instance variable들이 default 말고 기본적으로 어떻게 initialize할지 explicit하게 나타내는 것도 가능한데, C++이랑 완전 동일하니 넘어가겠다.
initialization block
- kotlin의 init 없는 init block과 동일하다고 생각하면 된다. 각 생성자별 공통된 초기화를 할 때 사용.
- class initialization block도 만드는게 가능한데, initialization block 앞에 static을 붙이면 된다. class load되었을 때 딱 한번 발동. class variable들 초기화 용도.
initialization order
- class variable : class load되었을 때 초기화. default -> explicit -> class initialization block
- instance variable : instance 생성 되었을 때 초기화. default -> explicit -> instance initialization block -> constructor block
- 이 점을 유의해서 무의미한 연산이 없도록 애써보자.