0. 변수와 상수, 리터럴
변수와 상수 모두 “하나의 값을 저장하기위한 메모리 공간” 을 의미하고 있다.
변수는 값을 재 할당할 수 있지만, 상수는 한번 초기화하면 값을 재 할당할 수 없다.
상수는 변수 타입 앞에 final 키워드를 이용해서 사용하며, 변수 타입에 final 키워드가 기술되었을 시, 값을 재할당 할 수 없는 “상수” 가 된다.
상수는 값이 없을 수 없으므로 반드시 선언과 동시에 초기화해야한다.
int age = 29; // 변수
int age = 30; // 메모리 안에 값의 재할당이 가능하다.
final int AGE = 29; // 상수
AGE = 30; // X, 상수는 메모리 안에 값을 재할당 할 수 없다.
- “리터럴” 이란 값 자체를 의미한다.
int score = 100; // 100 은 리터럴
final int MAX = 200; // 200 은 리터럴
String str = "abs" // "abs" 는 리터럴
- 상수는 반드시 선언과 동시에 초기화해야하며, 값을 재 할당 할 수 없다.
- 로컬변수는 반드시 초기화해야만한다.
public class VarEx3 {
public static void main(String[] args) {
int score = 100;
System.out.println(score); // 100
score = 200;
System.out.println(score); // 200
final int SCORE = 300;
System.out.println(SCORE); // 300
//SCORE = 400; Cannot assign a value to final variable 'SCORE'
int local;
//로컬변수는 반드시 초기화 해야한다!
//System.out.println(local);
//Variable 'local' might not have been initialized
}
}
0.1 상수를 사용하는 이유
- 상수를 사용하는 이유는 “값(리터럴)에 의미있는 이름을 지어주기 위함” 이다.
- 코드의 유지보수성을 높이기 위함이다.
final int WIDTH = 20;
final int HEIGHT = 10;
// 앞으로 어떠한 곳에 상수가 쓰이던지 상수 값만 바꾸면 모든 식도 수정 된다.
// 식에서 상수의 이름을 보고 어떤 역할을 하는지 알 수 있다.
int triangleArea = (WIDTH * HEIGHT) / 2;
1. 리터럴의 타입과 접미사
기본 정수형은 int, 기본 실수형은 double 이다.
1.1 진수 별 접미사
- 16 진수 = 0x
- 8 진수 = 0
- 2 진수 = 0b
1.2 정수표기 시 _ 사용가능
- JDK 1.7 에 도입
- 정수형 리터럴의 가독성을 위해서 _ (언더바) 구분자 사용이 가능해졌다.
long l = 100_000_000_000L; // 크기가 큰 정수형 리터럴 _ (언더바)로 구분할 수 있다.
1.3 float 의 f 접미사는 생략할 수 없다.
- 3.14 로 리터럴을 선언 시 3.14 의 타입은 "double"이 된다.
- double 타입을 float 타입에 대입하는 것은 불가능하므로 에러가 발생한다.
float f = 3.14; // X 생략 불가능
2. 타입의 불일치
- 기본적으로 “변수의 타입과 리터럴의 타입은 일치해야한다.”
- 하지만 저장범위(메모리 공간) 가 더 큰 타입에 저장범위가 더 작은 타입을 대입하는 것은 허용된다.
💫 더 작은 타입이 더 큰 타입으로 자동 변환 되는 것 ⇒ 프로모션
💫 더 큰 타입이 더 작은 타입으로 강제 변환 된다는 것 ⇒ 캐스팅
int i = 'A';
long l = 123;
double d = 3.14f;
System.out.printf("Result > i:%d, l:%d, d: %e",i,l,d);
//Result > i:65, l:123, d: 3.140000e+00
// 타입 자동 변환
- 리터럴의 "값"이 변수 타입의 허용범위를 벗어나거나, 리터럴의 "타입"이 변수 타입보다 크다면 컴파일 에러가 발생한다.
int i = 0x123456789;
// X , 리터럴의 타입은 인트이지만 int 타입의 범위를 넘어가는 수이기 때문에 대입 불가
float f = 3.14;
// X , 리터럴의 타입은 double(8byte) 이며 변수의 타입은 float(4byte) 이므로 대입 불가
- byte, short 를 상징하는 접미사는 없으므로, int 타입의 리터럴을 사용한다. 단, int 타입의 리터럴이 변수가 저장할 수 있는 타입의 범위에 속해야만한다.
boolean power = true;
System.out.println(power); // true
System.out.println(!power); // false
//byte b = 128; 리터럴의 범위가 byte 타입의 범위를 넘어섰음
int oct = 010; //8진수, 8
int hex = 0x10; //16진수, 16
System.out.println(oct);
System.out.println(hex);
long l = 1000_000_000; // int -> long 프로모션
long l1 = 10_000_000_000L; //
float f = 3.14f;
double d = 3.14f; // float -> double 프로모션
3. 문자열 리터럴
- ’ 작은 따음표로 "하나의 문자"를 감싸는 것은 문자 리터럴이다.
- " 큰 따옴표로 "문자"를 감싸는 것은 문자열 리터럴이다.
- 문자열은 String 클래스 타입이며, 참조타입이다.
char ch = 'J'; // Character, 문자 리터럴
String str = "Java"; //String, 문자열 리터럴
문자열은 빈 문자열("")을 허용하지만, 문자는 빈 문자를 허용하지 않는다(’’)
String emptyStr = "";
String oneChar = "A";
char ch = ''; // X -> 문자형 리터럴은 반드시 하나의 문자를 가져야한다.
3.0 String (문자열) 접합 연산
- 문자열은 모든 타입과 덧셈 연산할 수 있으며, 결과는 “문자열 리터럴” 이다.
- 문자열을 포함한 + 연산은 접합 연산으로 취급하며, 피연산자 중 문자열이 아닌 쪽을 String 타입으로 바꿔서 접합연산한다.
- String + 기본형 = 문자열 ( 문자열 + 값 )
- String + String = 문자열
- String + 객체 = 문자열 ( 문자열 + 이름@메모리주소)
3.1 String 의 불변성
- String 은 불변성을 가지고 있어서, 한 번 생성되면 값을 바꾸지 못한다.
- 변수에 새로운 문자열을 저장하더라도, 새로운 String 객체를 생성하는 것이다.
String 의 불변성은 메모리 상에서는 큰 낭비가 생긴다. 이로 인하여, Java 에서는 Heap 영역에 String Constant Pool 을 생성하여 객체를 저장해둔다.
- 문자열로 생성한 String 객체는 String Constant Pool 에 기록된다.
- 동일한 문자열로 생성한 String 타입 참조변수는 String Constant Pool 에 있던 같은 객체를 참조하게 된다.
- new 연산자로 String 객체를 생성할 경우, Heap 영역에 따로 String 객체가 생성된다.
문자열로 String 객체를 생성 시 문자열 객체는 Heap 영역에 String Pool 에 저장되고, String Pool 에 있는 문자열과 동일한 문자열로 생성된 String 타입 참조 변수는 같은 String 객체를 참조하는 것이다.
new 연산자로 생성한 String 은 각자 다른 메모리 주소 값을 가지게 된다.
String str = new String("");
System.out.println(str.hashCode());
str = new String("String");
String str1 = new String("String");
System.out.println(System.identityHashCode(str) + " / "
+ System.identityHashCode(str1));
String str2 = "String";
String str3 = "String";
System.out.println(System.identityHashCode(str2) + " / "
+ System.identityHashCode(str3));
/*
692404036 / 1554874502
1846274136 / 1846274136
*/
3.2 접합 연산된 String 객체는 String Constant Pool 에 등록되지않는다.
String str = new String("");
System.out.println(str.hashCode());
str = new String("String");
String str1 = new String("String");
System.out.println(System.identityHashCode(str) + " / "
+ System.identityHashCode(str1));
String str2 = "String";
String str3 = "String";
System.out.println(System.identityHashCode(str2) + " / "
+ System.identityHashCode(str3));
System.out.println(System.identityHashCode(str2+"?"));
//String 접합 연산은 새로운 객체를 리턴한다.
//
692404036 / 1554874502
1846274136 / 1846274136
1639705018
여기서 접합 연산된 String 객체와 같은 문자열을 가진 String 객체를 문자열로 생성한다면
접합 연산으로 생겨난 문자열 객체와 같은 문자열을 가진 String 객체를 생성해도, String Constant Pool 에는 저장되지 않는다.
즉, 접합 연산으로 생겨난 문자열 객체는 String Constant Pool 에 저장되지 않는다.
String str = new String("");
System.out.println(str.hashCode());
str = new String("String");
String str1 = new String("String");
System.out.println(System.identityHashCode(str) + " / " + System.identityHashCode(str1));
String str2 = "String";
String str3 = "String";
System.out.println(System.identityHashCode(str2) + " / "+ System.identityHashCode(str3));
String strPlus = str2+"?";
System.out.println(System.identityHashCode(strPlus));
//String 접합 연산은 새로운 객체를 리턴한다.
String str4 = "String?";
String str5 = "String?";
System.out.println(System.identityHashCode(str4));
System.out.println(System.identityHashCode(str5));
/*
692404036 / 1554874502
1846274136 / 1846274136
1639705018 <<< 접합 연산된 String 객체의 주소 값
1627674070 << 문자열로 생성했던 String 객체의 주소 값만 같음.
1627674070
*/
- 예제
public class VarEx4 {
public static void main(String[] args) {
char ch = 'A';
System.out.println(ch);
int i = 'A'; //프로모션, 65
System.out.println(i);
String str = "";// String 타입은 빈 문자열을 허용한다.
String str2 = "ABCD";
String str3 = "123";
String str4 = str2 + str3;
System.out.println(str4);
System.out.println(""+7+7); // 77
System.out.println(7+7+""); // 14
}
}