본문 바로가기
개발/Spring | Java | Error

자바의 정석 - 변수의 초기화

by 하얀 루돌프 2022. 3. 13.

상수(Constants)

상수는 '값이 변하지 않는 수'를 의미한다.
하지만 자바에서는 한번 그 값이 정해지면 이후로는 변경이 불가능한 변수도 상수라 한다.

변수를 선언할 때 final 이라는 선언을 추가하면 그 변수는 '상수'가 된다.

  • 값을 딱 한 번만 할당할 수 있다.
  • 한 번 할당된 값은 변경이 불가능하다.
  • 상수의 이름은 모두 대문자로 짓는다.
  • 이름이 둘 이상의 단어로 이뤄질 경우 단어 사이에 언더바를 넣는다.
	class Constants {
    		public static void main(String[] args) {
        		final int MAX_SIZE = 100;
                	final int CONST_ASSIGEND;
                	CONST_ASSIGEND = 12; // 할당하지 않았던 상수의 값 할당
                        System.out.println(MAX_SIZE); // 100
                        System.out.println(CONST_ASSIGEND); // 12
            	}
        }




리터럴(Literals) 상수

int num = 157;

변수를 선언과 동시에 157이라는 값으로 초기화 하였다.
이 때 대입연산자(=) 오른편에 위치한 숫자 157을 가리켜 '리터럴' 또는 '리터럴 상수'라 한다.

그 자체로 데이터인 것을 '리터럴(literal)'이라고 한다.
사실 리터럴은 상수(constant)와 의미가 같지만 프로그래밍에는 상수를
'값을 한번 저장하면 변경할 수 없는 저장공간'으로 정의하기 때문에
이와 구분하기 위해 '리터럴'이라는 용어를 사용한다.

단, 리터럴 상수인 157을 자바 컴파일러는 무엇으로 인식할까?
int형 정수로? 아니면 long형 정수로? 정답은 int형 정수로 인식한다.
왜냐하면 그렇게 약속했고, 만들어졌기 때문이다.
따라서 아래 문장을 컴파일하면 다음과 같은 오류 메시지를 볼 수 있다.

long num = 3147483647;	// 컴파일 에러 발생

그리하여 리터럴 상수의 표현방식이 존재한다.

진법설명

10진수 Decimal (23)
2진수 Binary (0B로 시작한다. 0B10111)
8진수 Octal (0으로 시작한다. 027)
16진수 HexaDecimal 또는 Hex (0x로 시작한다. 0x17)
byte num = (byte)10;

int num = 11 + 22 + 33; // 10진수 정수를 리터럴 상수로 표현방법
int num = 011 + 022 + 033; // 8진수 정수 표현방법
int num = 0x11 + 0x22 + 0x33 // 16진수 정수 표현방법

long num2 = 3147483647L; // long형 표현방법
double num3 = 3.000499D; // double형 표현방법

byte seven = (byte)0B111; // 2진수 표현방법
int num205 = 0B11001101;

int num205 = 100_000_000; // 단위가 큰 수의 표현 및 인식에 도움을 주기 위해서 중간 언더바도 허용




그 밖의 표현방식

	다음의 상황에서는 앞 또는 뒤에 붙은 숫자 0을 생략할 수 있다.
    0.5 → .5
    5.0 → 5.
    0.7f → .7f
    7.0f → 7.f
    




복합 대입 연산자

short num = 10;
num = (short) (num + 77L); // 형 변환 안하면 컴파일 오류 발생

num += 88L; // 복합대입연산자는 형 변환 필요하지 않다.

System.out.println("7.0 == 7" + (7.0 == 7)); // true 반환
// == 연산을 위해 자동 형 변환이 일어난다.

복합대입연산자는 대입 연산자가 다른 연산자와 묶여서 정의된 형태의 연산자이다. 형 변환을 알아서 해주는 것으로, 명시적 형 변환을 명시안해주기때문에 코드를 줄일 수 있는 장점을 얻을 수 있다.


기본형의 자동형변환이 가능한 방향
char의 범위는 0~65535, short의 범위는 -32768~32767이므로 서로 범위가 달라서 둘 중 어느 쪽으로 형변환시 값 손실이 발생할 수 있으므로 자동적으로 형변환이 안된다.

모든 형변환에 캐스트연산자를 이용한 형변환이 이루어져야 하지만, 값의 표현범위가 작은 자료형에서 큰 자료형의 변환은 값의 손실이 없으므로 캐스트 연산자를 생략하는 것을 허용한다. 이 때 JVM 내부에서 자동적으로 형변환이 수행된다.




논리 연산자 사용 시 주의할 점: Short-Circuit Evaluation(Lazy Evaluation) 이하 줄여서 SCE


의도하지 않은 결과가 나온 경우: Short-Circuit

int num1 = 0;
int num2 = 0;
boolean result;

result = ((num1 += 10) < 0) && ((num2 += 10) > 0);
System.out.println("result = " + result);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);


result = ((num1 += 10) > 0) || ((num2 += 10) > 0);
System.out.println("result = " + result);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
        
/* 실행결과
result = false
num1 = 10
num2 = 0
result = true
num1 = 20
num2 = 0
*/

&&의 왼쪽 피연산자가 false이면, 오른쪽 피연산자는 확인하지 않는다.
||의 왼쪽 피연산자가 true이면, 오른쪽 피연산자는 확인하지 않는다.
따라서 너무 많은 연산을 한 줄에 담는 건 좋지 않다.

num1 += 10;
num2 += 10;
result = (num1 < 0) && (num2 > 0);




객체와 인스턴스

  • 클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instaniate)라고 하며,
    어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다.
  • 책상클래스로부터 만들어진 객체를 책상클래스의 인스턴스라고 한다.
  • 인스턴스와 객체는 같은 의미이므로 두 용어의 사용을 엄격히 구분할 필요는 없지만, 위의 예에서 본 것과 같이 문맥에 따라 구별하여 사용하는 것이 좋다.

객체의 구성요소

  • 객체는 속성과 기능의 집합이라고 할 수 있다. 그리고 객체가 가지고 있는 속성과 기능을 그 객체의 멤버(구성원, member)라 한다.
  • 클래스란 객체를 정의한 것이므로 클래스에는 객체의 모든 속성(멤버변수)과 기능(메소드)이 정의되어있다.
  • TV를 사용하려면, TV 리모콘을 사용해야하고, 에어콘을 사용하려면, 에어콘 리모콘을 사용해야하는 것처럼
    Tv인스턴스를 사용하려면, Tv클래스 타입의 참조변수(여기선 Tv t)가 필요한 것이다.

6. 변수의 초기화

자료형기본값

boolean false
char `\u0000'
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d 또는 0.0
참조형 변수 null
  • 변수를 선언하고 처음으로 값을 저장하는 것을 '변수의 초기화'라고 한다.
  • 가능하면 선언과 동시에 적절한 값으로 초기화 하는 것이 바람직하다.
class InitTest {
	int x;			// 인스턴스변수
    	int y = x;		// 인스턴스변수
    
        void method1() {
        	int i;		//지역변수
           	int j = i;	//컴파일 에러! - 지역변수를 초기화하지 않고 사용함.
        }
}
  • 인스턴스변수 x는 초기화를 하지 않아도 자동적으로 int형의 기본값인 0으로 초기화, 따라서 int y = x; 도 가능
  • 지역변수는 반드시 사용하기 전에 초기화를 해주어야 한다.

변수의 초기화에 대한 예

int i = 10;
int j = 10;

int i = 10, j = 10; // 같은 타입의 변수는 콤마(,)를 사용해서 함께 선언하거나 초기화 할수 있다.
int i = 10, long j = 0; // 에러 타입이 다른 변수는 함께 선언하거나 초기화 할 수 없다.

int j = i; // 에러!!! 변수 i가 선언되기 전에 i를 사용할 수 없다.
int i = 10;	

멤버변수의 초기화 방법

1. 명시적 초기화(explicit initialization)

변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.

class Car {
	int door = 4;		// 기본형(preimitive type) 변수 초기화
    	Engine e = new Engine();	// 참조형(reference type) 변수 초기화
}

2. 초기화 블럭(initialization block)

  • 인스턴스 초기화 블럭: 인스턴스 변수를 초기화 하는데 사용
  • 클래스 초기화 블록: 클래스 변수를 초기화 하는데 사용, 인스턴스 초기화 블럭 앞에 단순히 static을 덧붙이기만 하면 된다.
class InitBlock {
	static { /* 1등으로 실행 - 클래스 초기화 블럭입니다. */ }
    
    	{ /* 2등으로 실행 - 인스턴스 초기화 블럭입니다. */ }
        
        InitBlock() { /* 3등으로 실행 - 기본생성자 */}     
    
}
class BlockTest {
	static {
    		System.out.println("static {  }");	// 클래스 초기화 블럭
    	}
        
        {
        	System.out.println(" {  } ");		// 인스턴스 초기화 블럭
        }
        
    	public BlockTest() {
    		System.out.println("생성자");
    	}
        
        
        public static void main(String args[]) {
        	BlockTest bt1 = new BlockTest();
            	BlockTest bt2 = new BlockTest();
            
        }
}
[실행결과]
static { }
{ }
생성자
{ }
생성자
  • 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때마다 수행 된다.
  • 생성자 보다 인스턴스 초기화 블럭이 먼저 수행된다.

3. 생성자(constructor)

Car() {
	System.out.println("Car인스턴스가 생성되었습니다.");
    	color = "white";
        gearType = "Auto";
}

Car(String color, String gearType) {
	System.out.println("Car인스턴스가 생성되었습니다.");
    	this.color = color;
    	this.gearType = gearType;
}

예를 들면, 위와 같이 클래스의 모든 생성자에 공통적으로 수행되어야 하는 문장들이 있을 때, 각 생성자마자 써주기 보다는 인스턴스 블럭을 이용하면 코드가 보다 간결해진다.

{ System.out.println("Car인스턴스가 생성되었습니다."); }	// 인스턴스 블럭

Car() {

    	color = "white";
        gearType = "Auto";
}

Car(String color, String gearType) {
    	this.color = color;
    	this.gearType = gearType;
}

재사용성을 높이고 중복을 제거하는 것, 이것이 바로 객체지향프로그래밍이 추구하는 궁극적인 목표이다.


4. 멤버변수의 초기화 시기와 순서

class InitTest {
    static int cv = 1;		// 명시적 초기화1
    int iv = 1;			// 명시적 초기화2
    
    static { cv = 2; }		// 클래스 초기화 블럭
    
    { iv = 2; }			// 인스턴스 초기화 블럭
    
    InitTest() {		// 생성자
        iv = 3;
    }
    
    public static void main(String args[]) {
    		InitTest it = new InitTest();
    }
            
}

클래스변수는 인스턴스 변수보다 항상 먼저 생성되고 초기화 된다.

JVM 메모리에 올라가는 순서 (❤: 클래스, 💙: 인스턴스)
1. ❤ cv가 메모리(method area)에 생성되고, cv에는 int형의 기본값인 0이 cv에 저장된다.
2. ❤ 명시적 초기화1(int cv=1)에 의해서 cv에 1이 저장된다.
3. ❤ 클래스 초기화 블럭(cv = 2)가 수행되어 cv에는 2가 저장된다.
4. 💙InitTest클래스의 인스턴스가 생성되면서 iv가 메모리(heap)에 존재하게 된다. - (iv 역시 int형 변수이므로 기본값 0이 저장된다.)
5. 💙명시적 초기화2에 의해서 iv에 1이 저장된다.
6. 💙인스턴스 초기화 블럭이 수행되어 iv에 2가 저장된다
7. 💙생성자가 수행되어 iv에는 3이 저장된다.



package java_study;

class Product {
    static int count = 0;   // 생성된 인스턴스의 수를 저장하기 위한 변수
    int serialNo;   // 인스턴스 고유의 번호

    {
        ++count;
        serialNo = count;
    }   // Product인스턴스가 생성될 때마다 count의 값을 1씩 증가시켜서 serialNo에 저장

    public Product() {} // 기본생성자, 생략가능
}

public class ProductTest {
    public static void main(String[] args) {
        Product p1 = new Product();
        Product p2 = new Product();
        Product p3 = new Product();

        System.out.println("p1의 제품번호는 " + p1.serialNo);
        System.out.println("p2의 제품번호는 " + p2.serialNo);
        System.out.println("p3의 제품번호는 " + p3.serialNo);
        System.out.println("생산된 제품의 수는 모두 " + Product.count + "개 입니다.");
    }
}

출력결과

p1의 제품번호는 1
p2의 제품번호는 2
p3의 제품번호는 3
생산된 제품의 수는 모두 3개 입니다.

'개발 > Spring | Java | Error' 카테고리의 다른 글

Stream map  (0) 2022.03.28
자바의 정석- 프로세스와 쓰레드  (0) 2022.03.13
java.util.Objects, java.util.Random, java.util.regex(정규식)  (0) 2022.03.13
StringBuilder와 StringBuffer 차이  (0) 2022.03.13
JAVA Object  (0) 2022.03.13