📚 자바의 정석을 정리한 내용입니다.
JVM은 프로그램을 수행하는데 필요한 메모리를 할당 받는다. JVM은 이 메모리를 여러 영역으로 나누는데, 주요 영역 세 가지다.
Method area
클래스가 사용될 때, JVM은 해당 클래스의 클래스 파일*.class를 읽고 클래스에 대한 정보를 이곳에 저장한다. 당연히 클래스 변수도 이곳에 저장된다.
Call Stack 혹은 Execution Stack
메서드 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면 콜스택에 메모리가 할당되고, 작업이 끝나면 반환된다.
Heap
인스턴스가 생성되는 공간이다. 인스턴스 변수도 이곳에 생성된다.
위에서 말한 호출스택Call Stack은 이런 식으로 동작한다.
static 메서드는 언제 만들까?
메서드 내에서 인스턴스 변수를 사용한다면 인스턴스 메서드여야 한다.
반면 메서드 내에서 인스턴스 변수나, 인스턴스 메서드를 사용하지 않는다면 static메서드로 정의할 필요가 있다.
클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
static변수는 인스턴스 생성 없이 사용할 수 있다.
static메서드는 인스턴스 변수를 사용할 수 없다.
클래스 메서드가 호출 되었을 때, 객체(인스턴스)가 존재하지 않을 수 있으므로.
반대는 가능하다.
메서드 내에서 인스턴스 변수를 사용하지 않는다면 satic을 붙이는 것을 고려.
같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면 같은 이름을 사용해서 메서드를 정의할 수 있다.
반환타입이 다른 것은 오버로딩이 아니다.
오버로딩의 대표적인 예로 println메서드가 있다.
void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(double x)
void println(Object x)
void println(String x)
...
println이 이렇게 오버로딩이 되어 있기 때문에 우리는 println메서드 안에 어떤 타입이든 넣어서 쓸 수 있다.
모든 메서드의 이름이 다르다면, 10개의 println은 모두 다른 메서드를 가져야 한다. 그러니까 char를 출력할 때는 printchar boolean을 출력할 땐 printbool...이런 식으로. 이름을 짓기도 어렵고 사용하는 쪽에서도 구분하기가 어렵다.
오버로딩을 사용하면 기억하기도 쉽고 오류 가능성을 줄일 수도 있다. 또한 메서드 이름만 보고 이름이 같으니 같은 기능을 할 것이라는 사실을 짐작할 수 있다.
가변인자로 동적으로 매개변수의 개수를 지정할 수 있다.
1. method(String str1, String str2, String str3)을
2. method(String... str)처럼 쓸 수 있다는 말이다.
method("a", "b")
method("a")
method(new String[]{"A", "B"})
모두 가능하다.
가변인자는 내부적으로 배열을 이용하기 때문에 성능이 떨어지는 단점이 있다. 따라서 꼭 필요한 경우에만 사용하는 게 좋다.
그리고
가변인자는 오버로딩할 수 없다.
1번 메서드를 2번 메서드로 오버로딩했을 경우,
컴파일 시 두 메서드를 구분할 수 없어서 에러가 발생한다.
생성자는 인스턴스를 생성할 때 호출하며, 인스턴스 변수들을 초기화 하거나, 생성 시에 실행해야 할 작업을 위해 사용한다.
class Movie{}의 기본 생성자를 만든다면
public Moive() {}가 될 것이다.
모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다.
하나도 없을 때는 컴파일러가 자동으로 기본 생성자를 만들지만, 하나라도 생성자가 있는 경우에 컴파일러는 아무 일도 하지 않는다.
매개변수 가진 생성자를 만들어놓으면, 해당 클래스의 객체를 생성할 때 원하는 값으로 초기화 할 수 있다. 이런 식으로.
class Ironman{
boolean power;
int strength;
Ironman(boolean power, int strength) {
this.power = power;
this.strength = strength;
}
}
public class Avengers {
public static void main(String[] args) {
Ironman iron = new Ironman(true, 100);
System.out.println(iron.power); //true
System.out.println(iron.strong); // 100
}
}
생성자 내에서 다른 생성자를 호출할 때 this()를 사용한다.
반드시 첫째 줄에서 사용해야 한다는 제약이 있다.
Ironman(int armour) {
// Ironman(); //이런 식으로 호출하면 에러가 발생한다.
this(); //반드시 이렇게 호출해야 한다.
}
이유는, 생성자 내에 초기화 작업 중 다른 생성자를 호출하게 되면, 호출된 다른 생성자 내에서도 멤버변수의 값을 초기화하게 된다. 그러면 다른 생성자를 호출하기 전 초기화 작업과 충돌이 날 가능성이 있다.
class CarCompo {
String color;
String gearType;
int door;
CarCompo() { //기본 옵션
this("white", "auto", 4);
}
CarCompo(String color) { //색상 선택, 나머지 기본 옵션
this(color, "auto", 4);
}
CarCompo(String color, String gearType, int door) { //옵션 모두 선택 가능
this.color = color;
this.gearType = gearType;
this.door = door;
}
public String toString() {
return "color : " + this.color + ", gearType : " + this.gearType + ", door : " + this.door;
}
}
public class Car {
public static void main(String[] args) {
CarCompo defaultCar = new CarCompo();
CarCompo colorOptionCar = new CarCompo("Red");
CarCompo fullOption = new CarCompo("black", "auto", 2);
System.out.println("defaultCar : " + defaultCar);
System.out.println("colorOptionCar : " + colorOptionCar);
System.out.println("fullOption : " + fullOption);
}
}
/*
결과
defaultCar : color : white, gearType : auto, door : 4
colorOptionCar : color : Red, gearType : auto, door : 4
fullOption : color : black, gearType : auto, door : 2
*/
자기 자신을 가리키는 참조변수 this
매개변수의 이름과 인스턴스 변수의 이름이 같을 때, 구분하려고 쓴다.
color = color라고 하면 둘 다 지역 변수로 간주된다.
참고로 static변수에는 this가 없다.
생성자를 포함한 모든 인스턴스 메서드에 자신이 관련된 인스턴스를 가리키는 참조변수 this가 지역변수로 숨겨진 채로 존재한다.
아무튼 정리하자면
this - 인스턴스 자신을 가리키는 참조변수. 인스턴스의 주소가 저장되어 있다.
this() - 생성자. 같은 클래스의 다른 생성자를 호출할 때 쓴다.
this와 this()는 동료가 아니다.
변수에 처음 값을 저장하는 것을 초기화Initialization라고 한다.
멤버변수는 초기화하지 않아도 자료형에 맞는 기본값으로 초기화가 된다.
그러나 지역변수는 반드시 사용하기 전에 초기화해야 한다.
타입의 기본 값
출처 : https://www.youtube.com/watch?v=KQDYyX6EdNA
변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.
int door = 4;
이게 명시적 초기화다.
가장 간단하고 명료하다. 따라서 우선적으로 고려되어야 할 방법이다.
클래스 초기화 블럭
클래스 변수의 복잡한 초기화에 사용한다.
인스턴스 초기화 블럭
인스턴스 변수의 복잡한 초기화에 사용한다.
차이는 간단하다. 아무 이름도 없는 {}블럭이 인스턴스 초기화 블럭이다.
앞에 static을 붙여서 static{}으로 만들면 클래스 초기화 블럭이다.
클래스 초기화 블럭은 클래스가 메모리에 로딩될 때 딱 한 번 실행된다.
반면 인스턴스 블럭은 인스턴스를 생성할 때마다 실행된다.
인스턴스 초기화는 보통 생성자로 한다.
생성자가 여러 개 있을 때, 여러 개의 생성자에서 중복되는 부분을 인스턴스 초기화 블럭을 만들어서 쓴다.
중복을 제거하고 재사용성을 높이는 것이 객체지향프로그래밍이 추구하는 궁극적인 목표다.
class InitTest {
/*명시적 초기화*/
static int cv = 1;
int iv = 1;
static { //클래스 초기화 블럭
cv = 2;
}
{ //인스턴스 초기화 블럭
iv = 2;
}
InitTest() { //생성자
iv = 3;
}
}
출처:자바의 정석 305p
1.cv가 메모리에 로딩. 기본 값인 0으로 초기화. (static int cv)
2.static int cv = 1; 명시적 초기화로 1이 저장된다.
3.그 다음 클래스 초기화 블럭이 실행되면서 cv의 값을 2로 초기화 한다.
4.InitTest클래스의 인스턴스가 생성되면서 iv가 heap에 로딩된다.
iv역시 int형 변수이므로 0으로 초기화
5.명시적 초기화에 의해 iv에 1이 저장.
6.인스턴스 초기화 블럭이 실행되면서 iv를 2로 초기화.
7.마지막으로 생성자가 실행되면서 iv를 3으로 초기화한다.