상수는 '값이 변하지 않는 수'를 의미한다.
하지만 자바에서는 한번 그 값이 정해지면 이후로는 변경이 불가능한 변수도 상수라 한다.
변수를 선언할 때 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
}
}
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

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)라고 한다.
책상클래스로부터 만들어진 객체를 책상클래스의 인스턴스라고 한다.
인스턴스와 객체는 같은 의미이므로 두 용어의 사용을 엄격히 구분할 필요는 없지만, 위의 예에서 본 것과 같이 문맥에 따라 구별하여 사용하는 것이 좋다.


| 자료형 | 기본값 |
|---|---|
| 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; //컴파일 에러! - 지역변수를 초기화하지 않고 사용함.
}
}
변수의 초기화에 대한 예
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;
변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.
class Car {
int door = 4; // 기본형(preimitive type) 변수 초기화
Engine e = new Engine(); // 참조형(reference type) 변수 초기화
}
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 { }
{ }
생성자
{ }
생성자
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;
}
재사용성을 높이고 중복을 제거하는 것, 이것이 바로 객체지향프로그래밍이 추구하는 궁극적인 목표이다.
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개 입니다.
잘 보구 갑니다~ ^^