[자바의 정석 스터디] Ch.2 변수
변수의 종류는 클래스 변수, 인스턴스 변수, 지역 변수가 있다.
- 지역변수는 0으로 초기화되지 않기 때문에, 읽기 전에 값을 꼭 초기화해야 한다.
- 직관적으로 표현하면, 지역변수에 값을 할당하지 않고 출력하거나 호출하면 에러가 발생. 클래스나 인스턴스 변수는 특정 값을 할당해주지 않고, 선언만 한 다음 바로 불러도 0으로 초기화 된 상태.
- 그렇지 않으면 컴파일할 때 에러가 발생한다.
변수의 타입
기본형 (Primitive type)
값 (Data)는 문자와 숫자, 논리(boolean)로 나눌 수 있다.
- 문자
- char (문자’열’에 해당하는 String은 기본형이 아님)
- 숫자 (저장하려는 값의 크기에 따라 쓰면 된다.)
- 정수: byte, short, int (디폴트), long
- 실수: float, double (디폴트)
- 논리
- boolean (true, false)
이 8개의 타입을 기본형이라고 한다. 데이터의 가장 기본이 되는 애들이란 뜻.
boolean, char, byte, short, int, long, float, double
(참고로, C언어에서 char 타입의 크기는 1byte. 자바에서 2바이트 유니코드를 사용하기 때문에 2바이트)
- int의 최댓값은 21억. 그 이상의 값이 담길 것 같으면 long으로 선언하자.
- 메모리 4 byte 더 써야됨
- float의 최댓값은 3.4 * 10의 38제곱이니까 웬만하면 이 안에 다 들어감.
실수형의 표현범위
실수형의 타입을 선택할 때는 표현범위만 고려해서는 안되고, 정밀도를 함께 생각해야 한다. 어차피 float이나 double이나 10의 제곱 개념을 활용하기 때문에 표현 범위는 굉장히 크다.
그보다는 정확한 계산을 하기 위해 정밀도를 고려해야 하며, 그러한 경우엔 double을 사용하는 것이 좋다. 그래서 double이 디폴트 타입이다.
참조형 (Reference type)
기본형을 제외한 나머지 (String, System 등). 기본형은 Java가 설계될 당시부터 8개로 정해져있지만, 참조형은 우리가 마음껏 만들어낼 수 있다.
참조변수는 객체의 주소를 저장하기 위한 것이다. (6장에서 자세히 배운다.)
- 참조변수가 가질 수 있는 값은 null 또는 객체 주소 뿐이다.
- 사실 마음껏 만들어낼 수 있는 건 객체. 참조변수 선언 및 초기화는 이걸 가리키는 표지판을 세우는 것이다.
Date today;
- 이 문장은 참조형 변수 today를 선언한 거고, 변수의 타입은 Date이다.
- Date가 기본형에 없으므로 참조형 변수임을 쉽게 알 수 있다.
today = new Date();
- Date 객체를 생성하고, 객체의 주소를 참조변수 today에 저장했다.
참조 변수 이해 (vs 기본형)
참조 변수는 객체의 메모리 주소를 저장하는 변수이다. 기본형처럼 그 자체로 값이 아니다.
다시 말해서 기본형은 실제 값 자체를 메모리에 저장하지만, 참조형은 메모리 주소를 저장한다. 총 4 byte (32bit OS) 혹은 8 byte (64bit OS)의 메모리가 참조변수를 위해 할당되어 있다.
헷갈리지 말 것은, 가리킬 주소를 저장하는데 4 byte라는 것이므로 실제로 운용할 수 있는 메모리는 훨씬 크다는 거다.
32bit JVM에서는 참조변수(객체의 주소를 저장하는 변수)로 4 byte (32 bit)의 메모리 공간을 사용한다. 그래서 이러한 참조변수로 표현할 수 있는 메모리의 범위는 2^32 byte, 즉 4GB 정도이다.
- 그러나 이 중에서 운영체제나 다른 프로그램이 사용하는 공간을 제외하면 실제로 참조변수에 할당할 수 있는 메모리는 2GB 정도로 줄어든다.
또한 64bit JVM에서는 참조변수(객체의 주소를 저장하는 변수)로 8 byte (64 bit)의 메모리 공간을 사용한다. 그래서 이러한 참조변수로 표현할 수 있는 메모리의 범위는 2^64 byte, 즉 약 1600만 TB 정도이다.
상수 (constant)
상수의 정의: 변수이긴 한데, 한 번만 값을 저장 가능한 변수.
- 즉, 처음 특정 값을 할당하면 해당 메모리 공간은 다른 값으로 대체할 수 없다.
상수의 선언
상수 선언 방법은 변수와 동일하다. 다만 변수 타입 앞에 final 이라는 키워드를 붙여야 한다.
final int a = 1; // 상수 선언 + 초기화
final int b; // 상수 선언
b = 3; // 상수 초기화
a = 2; // 다른 값 할당 -> 에러
예전에는 상수를 선언과 동시에 초기화했어야 했는데, 이제는 선언 따로 초기화 따로 가능.
리터럴 (literal)
리터럴이 사실 우리가 평상시 알고있는 상수이다. 즉, 그 자체로 값을 의미한다.
- 근데 왜 상수라 안함? - 이미 위에서 언급한 상수라는 개념을 먼저 만들어버려서.. 이름 겹치니까..
리터럴의 접두사와 접미사
모든 값에는 타입이 있다. 따라서 리터럴에도 타입이 있다. (값=리터럴이니까 당연히..)
리터럴의 타입을 구분하는 법은.. 그냥 딱 보면 안다.
- 예를 들어 작은 따옴표(’ ’)로 감싸져있으면 문자형, 큰 따옴표(” ”)로 감싸져있으면 문자열.
그러나 정수형과 실수형은 한 눈에 구분이 안되는 경우도 있기 때문에 접미사를 따로 붙인다.
접미사
정수형 중에 long 타입의 경우는 뒤에 L를 붙이고, 없으면 int 타입이다.
실수형은 float의 경우 f, double의 경우 d를 붙인다.
- 생략되어 있으면 d로 취급한다.
따라서 정리하면, 정수형에 붙이는 L과 실수형에 붙이는 f만 신경쓰면 된다.
(참고로 문자형의 \\n 는 개행문자인데, 그 자체로 하나의 문자로 인식)
쉽게 말해서 long 쓰고 싶으면 long 변수에 값 초기화할 때 L 붙이고,
float 쓰고 싶으면 float 변수에 값 초기화할 때 f 붙이라는 소리.
// 변수 타입과 리터럴 타입은 일치해야 한다
boolean power = true;
char ch = 'A';
String str = "ABC";
byte b = 127;
byte b = 128; // 에러. byte의 범위를 넘어섬
long sales2021 = 10_000_000_000L; // 만약 접미사 L을 붙이지 않으면 int로 인식하는데, long으로 선언했기 때문에 에러 발생
double d = 3.14d;// d 생략해도 됨
float f = 3.14f; // 만약 접미사 f를 붙이지 않으면 double로 인식하는데, float으로 선언했기 때문에 에러 발생
접두사
정수형 리터럴의 경우, 접두사를 붙여서 10진수가 아닌 8진수, 16진수 등으로 표현 가능하다.
- 그냥 쓰면 우리가 늘 쓰는 10진수
- 접두사 0b 가 붙으면 2진수
- JDK 1.7부터 추가됐음
- 접두사 0이 붙으면 8진수
- 접두사 0x 등이 붙으면 16진수
int a = 0b0101; // 2진수
System.out.println(a); // 5
int b = 0100; // 8진수
System.out.println(b); // 64
int c = 0x100; // 16진수
System.out.println(c); // 256
퀴즈: 다음 리터럴의 타입은?
- 10.
- 이건 10.0 이며, 접미사가 안 붙었으므로 실수 double 타입이다.
- .10
- 이건 0.1 이며, 접미사가 안 붙었으므로 실수 double 타입이다.
- 10f
- 이건 10 이며, 접미사 f가 붙었으므로 실수 float 타입이다.
- 1e3
- 이건 1000 이며, 기호 e(10의 n승)는 실수형에만 사용되므로 사실 1000.0 이다. 접미사가 안 붙었으므로 실수 double 타입이다.
double e = 3e3;
System.out.println(e); // 3000.0
변수 - 리터럴 타입 불일치 케이스
일반적으로는 변수와 리터럴의 타입이 일치해야 한다.
그러나 반드시는 아니다.
데이터 손실이 없는, 표현 범위가 더 큰 쪽에서 작은 쪽으로는 가능하다.
- 표현 범위가 ‘변수 > 리터럴’ 인 경우 괜찮다.
- 메모리 범위가 아니라 표현 범위임! (예를 들어 int는 21.x억 까지)
- 물건보다 물건을 담을 그릇이 더 크면 괜찮은 것과 같은 이치
- ex) double a = 10f;
- 해당 원리로, int 변수 안에 char 리터럴을 넣을 수도 있다.
- ex) int i = ‘A’;
- ‘A’의 문자 코드인 65가 int의 범위보다 작기 때문에 가능
int charTest = 'A';
System.out.println(charTest); // 65
- 표현 범위가 ‘변수 < 리터럴’인 경우 에러
- long I = 3.14f; → long 타입은 8 바이트, float은 4 바이트니까 괜찮지 않을까?
- X. 이건 이해를 잘 못한거임. 8바이트 4바이트는 그만큼 메모리를 쓴다는 거고, 형변환이 가능한 경우는 ‘표현 범위’를 기준으로 나눈다.
- 메모리는 long 타입이 더 쓰지만, 그건 상관이 없다. 표현범위가 float보다 더 작기 때문에 불가능하다.
- 이런 경우 명시적 형변환을 따로 해줘야 한다.
- long I = 3.14f; → long 타입은 8 바이트, float은 4 바이트니까 괜찮지 않을까?
- (예외) byte와 short 변수에 int 리터럴 저장이 가능하다
- 단, 해당 변수의 표현 범위 이내일 때만 가능함. (2에 위배되지 않게)
- 또한 int 리터럴 → long에 넣는 것도 가능하다.
- 때문에 정수형일 때는 리터럴이 21억 이상의 숫자일 때만 long 타입으로 접미사 L을 붙여주면 되는 거다!
문자와 문자열
문자는 char 타입의 변수, 문자열은 String에 담아야 한다.
- char (캐릭터) 타입은 ‘’, String은 “”
char a = ‘A’; // ok
char b = 'AB'; // 에러. 하나의 문자만 가능
char c = ''; // 에러. 빈 문자 불가능
String aa = "ABC"; // ok
String bb = ""; // ok. 빈 문자열 가능
String은 원래 자바에서 제공하는 클래스이다. 문자열을 다룰 때 사용.
원래 클래스는 String s2 = new String(”AB”); 이런식으로 new 연산자를 써서 객체를 생성해야 하는데, 문자열은 워낙 자주 쓰이므로 String만 이렇게 변수처럼 사용할 수 있게 되었다.
String AA = new String("AA 스트링");
System.out.println(AA); // AA 스트링
String BB = "BB 스트링";
System.out.println(BB); // BB 스트링
문자열 합치기
문자열끼리 합치면 두 문자열이 결합된다.
문자열에 어떤 타입을 더하면 결과는 반드시 문자열이 된다.
- 만약 문자열 + 숫자를 결합하면 → 숫자가 문자열로 변환된 뒤 결합된다.
이를 이용해서, 필요한 경우 숫자 → 문자열 변환을 쉽게 할 수 있다.
- 빈 문자열 + 숫자 를 이용
int number = 7;
String str = ""+number;
System.out.println(str); // "7"
System.out.println(str.getClass().getSimpleName()); // String
퀴즈1: “” + 7 + 7은?
→ 문자열 “77”이 된다.
퀴즈2: 7 + 7 + “”은?
→ 문자열 “14”가 된다.
문자와 숫자간의 변환
해당 내용은 빈번히 사용되므로 외우면 좋다.
문자 ↔ 숫자 간의 변환
문자와 숫자간의 변환은 ‘0’ 을 더하거나 빼면 된다.
- 숫자 → 문자: ‘0’ 더하기
- 이러면 유니코드 숫자로 나오는데, 형변환 (char) 을 앞에 넣어주면 원하는 대로 문자가 출력된다.
- ‘0’ = 48이다.
- 문자 → 숫자: ‘0’ 빼기
System.out.println('3' - '0'); // 3
System.out.println(3 + '0'); // 51 (유니코드 숫자)
System.out.println((char)(3 + '0')); // '3'
숫자,문자 → 문자열 변환
숫자 → 문자열
- 앞에서 배운대로 숫자 + “” (빈 문자열)을 해주면 된다.
문자 → 문자열
- 마찬가지로 문자 + “” (빈 문자열) 해주면 된다.
System.out.println(3+""); // "3"
System.out.println('3'+""); // "3"
문자열 → 숫자, 문자 변환
- 문자열 → Int 변환: Integer.parseInt(”문자열”);
- 문자열 → Double 변환: Double.parseDouble(”문자열”);
- 문자열 → char 변환: “문자열”.CharAt(0);