🌑 변수(variable)
변수(variable)를 왜 사용할까 ?
- 변수(variable): 데이터(값)을 저장할 수 있는 메모리상의 공간
➙ 변수는 이름 그대로 "변할 수 있는 수"이다 ! - 항상 코드를 작성할 때 고려해야할 점 !
1. 가독성이 좋고, 코드가 일관성이 있는가 ?
2. 중복된 코드를 어떻게 줄일 수 있을까(간결성) ? 혹은 내가 쓴 코드를 재사용할 수 있을까 ?
3. 다음에 구현할 기능을 고려해서 코드의 구조를 설계하자 (이건 좀 어려운듯...)
변수의 선언과 초기화
- 변수 선언방법
variableType variableName;
- 변수의 초기화(Initialization)
: 변수를 먼저 선언하고, 선언한 변수에 처음으로 값을 저장(대입 연산자 '=')하는 것 !
➙ 즉, 변수를 사용하기 전에 항상 해당 변수를 "초기화(intitialization)"부터 해줘야 한다 !
- 두 변수의 값 교환
🌒 변수의 타입(Type)
변수의 타입 ?
- 변수는 다루고자 하는 데이터의 종류에 따라 다양한 형식(type)이 존재하는데, 이를 "타입(type)"이라 하는 것 !
package variable;
public class Var7 {
public static void main(String[] args) {
int a = 100; //정수
double b = 10.5; //실수
boolean c = true; //불리언(boolean) true, false 입력 가능 char d = 'A'; //문자 하나
String e = "Hello Java"; //문자열, 문자열을 다루기 위한 특별한 타입 System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println(e);
}
}
기본형(primitive type)과 참조형(reference type)
▼ 기본형(primitive type)
- 실제 값(data)를 저장 !
💡 컴퓨터가 처리하는 최소 처리 단위 ?
" 1 Byte "
그렇다면, 'CPU가 한 번에 처리할 수 있는 데이터의 크기'는?
➙ 32bit CPU에선 '4 Byte(32bit)', 64bit CPU에선 '8 Byte(64bit)',
그리고 이를 '워드(word)'라고 하고, 즉 CPU의 성능에 따라 달라진다 !
▷ 정수형 자료형
- byte : -128 ~ 127 (1byte, 2^8)
- short : -32,768 ~ 32,767 (2byte, 2^16)
- int : -2,147,483,648 ~ 2,147,483,647 (약 20억) (4byte, 2^32)
- long : -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 (8byte, 2^64)
➙ 실무에선 기본으로 'int'를 사용하고, 20억이 넘어갈 것 같으면 'long'을 사용 !
( 그나마 파일 전송을 할 때 'byte'를 사용하는 정도 ? )
- 'n bit'로 표현할 수 있는 정수의 개수: 2^n
'n bit'로 표현할 수 있는 양수/음수의 범위: -2^(n-1) ~ 2^(n-1)-1
➙ 양수는 0도 포함되기 때문에 '2^(n-1) -1'까지 ! - 4 Byte보다 작은 자료형(byte, short)의 값을 계산하더라도 int형을 사용하는 것이 효율적 !
➙ JVM의 피연산자 스택(operand stack)은 피연산자를 4 byte 단위로 저장하기 때문에 어차피 변환된다 !
- "부호 없는" 정수형의 오버플로우
ex) 4 bit 2진수로 표현할 수 있는 최댓값은 '1111(=15)'인데, 여기서 값을 증가시키게 되면 ?
➙ '1 0000(=16)'이 아닌 '0000(=0)', 즉 "오버플로우(overflow)"가 발생해 최솟값이 나타나게 된다 !
- "부호 있는" 정수형의 오버플로우
➙ 부호비트가 0에서 1이 될 때 "오버플로우(overflow)"가 발생한다 !
ex) 4 bit 2진수로 표현할 수 있는 최댓값은 '0111(=7)'인데, 여기서 값을 증가시키게 되면 ?
➙ 최솟값인 '1000(-8)'이 나타나게 된다 !
💡 오버플로우(overflow) ?
해당 타입이 표현할 수 있는 값의 범위를 넘어선 것을 말한다 !
▷ 실수형 자료형
- float : 대략 -3.4E38 ~ 3.4E38, 7자리 정밀도 (4byte, 232)
- double : 대략 -1.7E308 ~ 1.7E308, 15자리 정밀도 (8byte, 264)
➙ '실수'를 다룰 땐 고민없이 'double'을 사용하자 !
💡 메모리 비용 아끼려면 구분해서 사용해야 하는 것이 아닌가 ?
메모리 용량은 매우 저렴하기 때문에 개발 속도나 효율에 초점을 맞추는 것이 더 좋다 !
- 정수형과 저장형식이 달라 훨씬 큰 값을 표현할 수 있으나 그만큼 오차가 발생하기에, '정밀도(precision)'를 고려해야 한다 !
( 정밀도가 "15자리"라는 것은 10진수로 15자리의 수를 오차없이 저장할 수 있다는 의미 ! )
➙ '부동소수점수(floating-point)'의 형태로 저장된다 ! [ 부호(0,1) + 지수(부호있는 정수) + 가수 ] - "실수형"도 "정수형"처럼 오버플로우가 발생하나 ?
➙ 오버플로우(overflow): 변수의 값이 표현범위의 최대값을 벗어나면 ! → 무한대
/ 언더플로우(underflow): 양의 최솟값보다 작은 값이 되는 경우 → 0
▷ 논리형 자료형
- boolean : true, false (1byte)
➙ 조건문에서 주로 사용된다 !
- 'boolean'은 연산 및 변환 불가능.
- 기본값(default)은 'false'이며, Java에선 대소문자가 구분되기 때문에 'False'를 저장하려 하면 에러가 발생한다 !
▷ 문자형
- char : 문자 하나(2 byte)
➙ 사실 실무에선 문자 하나를 표현할 일이 거의 없다 !
- 내부적으로 정수(Unicode)로 저장된다.
ex) 변수 'ch'에 문자 'A'를 저장하게 되면, 'ch'엔 유니코드(unicode)인 '65'가 저장되는 것이다. - 특정 문자의 유니코드를 알고 싶으면, 해당 문자를 저장한 변수 'ch'를 정수형으로 변환(casting)하면 된다.
ex) char ch = 'A' -> int code = (int)ch; -> code의 출력값은 '65' - 특수 문자는 어떻게 저장할까 ?
➙ [tab]: \t, [backspace]: \b, [new line]: \n, [carriage return]: \r,
[역슬래쉬(\)]: \\, [작은따옴표]: \', [큰따옴표]: \",
[유니코드(16진수) 문자]: \u유니코드 (ex: char a='\u0041')
💡 유니코드(Unicode) ?
각 나라별 언어들을 모두 표현하기 위해 나온 2 Byte(지금은 더 확장됌) 코드 체계.
유니코드 인코딩 종류: UTF-8, UTF-16(Java에서 사용) 등
➙ 두 인코딩 모두 처음 128문자가 아스키(ASCII)와 동일하다.
( ASCII(American Standard Code for Information Interchange): 128개(=2^7)의 문자 집합(0~9, A~Z, a~z)을 제공하는 7 bit부호. )
💡 문자를 다룰 땐 문자 하나든 문자열이든 문자열 자료형 "String"을 사용하자 !
"String"
C언어에서는 문자열을 char형 배열로 표현하지만, 자바에서는 문자열을 위한 String이라는 클래스를 별도로 제공
int, double, char, ...은 "기본(primitive) 자료형"로 분류되고, String 자료형은 "참조(reference) 자료형"으로 분류된다 !
▼ 참조형(reference type)
- '객체의 주소'를 저장하며, 앞서 말한 8개의 기본형을 제외한 "나머지 타입"이다 !
ex) String
className variableName;
상수(Constant) & 리터럴(Literal)
상수(Constant)
- 값을 한 번만 저장할 수 있는 공간.
- 선언과 동시에 초기화 !
- 상수의 이름은 대문자로 하는 것이 암묵적인 관례
final variableType constant = constantValue
리터럴(Literal)
- 기존에 우리가 알고 있던 "상수"의 다른 이름.
➙ 고정된 값, 즉 "그 자체로 값을 의미하는 것"을 말한다 !
(final) variableType variableName = literal
리터럴의 타입과 접미사
- L, f 은 생략 불가능 !
- 접미사 'f(F)', 'd(D)', 'E or e'
➙ "실수형 리터럴" ( "double타입"은 접미사 생략 가능 ) - 리터럴의 접두사
int octNum = 0(Literal) // Octal
int hexHum = 0x(Literal) // Hexadecimal
int binNum = 0b(Literal) // Binary
리터럴 타입 지정
- 정수 리터럴은 `int`를 기본으로 사용한다. 따라서 `int` 범위까지 표현할 수 있다.
숫자가 `int` 범위인 약 20억을 넘어가면 `L`을 붙여서 정수 리터럴을 `long`으로 변경해야 한다.
( 소문자 `l` 모두 가능하긴 한데 숫자1과 착각할 수 있어서 사용 X ) - 실수 리터럴은 기본이 `double`형을 사용한다. `float`형을 사용하려면 `f`를 붙여서 `float`형으로 지정해줘야 한다 !
💡 타입이 불일치하는 경우엔 오류가 발생할까 ? (cf. 형변환)
저장범위 넓은 타입의 변수에 저장범위가 좁은 타입의 값을 저장하는 것은 가능 !
(ex: int i = 'A'; long l = 123; double d = 3.14f)
( 그릇에 물을 계속 붓게 되면 넘치듯이, 그 반대는 당연히 불가능.. )
문자 리터럴 & 문자열 리터럴
- 흔히 알던 '문자('J')'가 '문자 리터럴'이고, '문자열("Java")'이 '문자열 리터럴' !
- 기본형이든 참조형이든 어떤 타입의 변수도 문자열과 덧셈연산을 수행하면 문자열이 된다 !
ex) String name = "Ja" + "va"; / String str = name + 8.0; - 기본형 타입의 값을 문자열로 변환하고 싶으면?
➙ 빈 문자열("")을 더해주면 된다 !
변수 명명 규칙
변수 이름은 가독성이 좋은 코드의 출발점 !
- Java에선 변수의 이름을 짓는데 규칙과 관례가 있다.
➙ 규칙은 필수(컴파일 에러)이고, 관례는 필수까진 아니지만 사실상 규칙처럼 생각하고 따르는게 좋다 !
( 혼자 개발하는게 아니니까.. )
규칙
- 변수 이름은 숫자로 시작할 수 없다. (ex: `1num`, `1st`)
➙ 그러나 숫자를 이름에 포함하는 것은 가능하다 (ex: `myVar1`, `num1`). - 이름에는 공백이 들어갈 수 없다.
- 자바의 예약어를 변수 이름으로 사용할 수 없다. (ex: `int`, `class`, `public`)
- 변수 이름에는 영문자(a-z , A-Z), 숫자(0-9), 달러 기호($) 또는 언더바(_)만 사용할 수 있다.
관례
클래스는 대문자로 시작, 나머지는 소문자로 시작 !
- "낙타 표기법"을 적용하면 Java의 모든 관례는 끝 !
➙ 클래스는 첫 글자 대문자, 나머지는 모두 첫 글자 소문자로 시작 + 낙타 표기법- 클래스: `Person`, `OrderDetail`
- 변수를 포함한 나머지: `firstName`, `userAccount`
- 여기엔 딱 2가지 예외가 있다 !
- 상수는 모두 대문자를 사용하고 언더바로 구분한다. (ex: `USER_LIMIT`)
- 패키지(package)는 모두 소문자를 사용한다. (ex: `org.spring.boot`)
- 상수는 모두 대문자를 사용하고 언더바로 구분한다. (ex: `USER_LIMIT`)
💡 낙타 표기법(Camel Case)
낙타표기법(Camel Case)은 프로그래밍에서 변수, 함수, 클래스 등의 이름을 지을 때 많이 사용하는 표기 법 중 하나이다. 이름에 공백을 넣지 않고도 여러 단어를 쉽게 구분하고 변수 이름을 쉽게 구분할 수 있다는 장점이 있고, 대부분의 프로그래밍 언어에서는 이름에 공백을 포함할 수 없기 때문에, 낙타표기법을 많이 사용한다 !
- 변수 이름은 그 용도를 명확하게 알 수 있도록 지어주자 !
(ex:`studentCount` , `maxScore` , `userAccount` , `orderCount`)
입력 & 출력 (I&O)
입력 (Input)
- Scanner
import java.util.*; // Scanner클래스를 사용하기 위한 import
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine()
// Scanner 클래스의 메서드 : nextLine(), nextInt(), nextFloat() ...
출력 (Output)
- println()
: 변수의 값을 그대로 출력 후 줄바꿈
➙ 출력 형식 지정 불가
ex) System.out.println(10.0/3); // 3.33333... - 실수의 자리수 조절 불가
System.out.println(0x1A); // 26 - 10진수로만 출력 - printf()
: '지시자(specifier)'를 통해 지정된 형식으로 출력 !
ex) %d, %b, %f, %c, %s
System.out.printf("age:%d", age);
🌓 진법 (p42 ~ p54)
- 비트(bit)와 바이트(byte)
➙ 앞서 말한 'CPU가 한 번에 처리할 수 있는 데이터의 크기' 정도만 ! - 진법 변환
➙ 학과 수업에서 배운다 ! - 1의 보수법(1's complement) & 2의 보수법(2's complement)
➙ 학과 수업에서 배운다 !
🌔 형 변환
형변환 (Casting)
- 변수 또는 상수(리터럴)의 타입을 다른 타입으로 변환하는 것 !
- 작은 범위에서 큰 범위로 값을 넣는 것은 당연히 가능 !
(ex: `int` ➙ `long` ➙ `double`) - 하지만 큰 범위에서 작은 범위로 값을 넣는 건 아래와 같은 문제가 발생해 값이 손실될 수 있다 !
➙ "소수점 버림", "오버플로우"
( 같은 값을 저장해도 해당 변수의 타입에 따라 실제로 저장되는 값을 다르다는 것에 유의하자 ! )
형변환 - 자동 형변환
- 작은 범위에서 큰 범위에 값을 대입하는 것은 자동으로 형변환되어 별 문제없이 잘 수행된다.
➙ 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다 !
package casting;
public class Casting1 {
public static void main(String[] args) {
int intValue = 10;
long longValue;
double doubleValue;
longValue = intValue; // int -> long
System.out.println("longValue = " + longValue); //longValue = 10
doubleValue = intValue; // int -> double
System.out.println("doubleValue1 = " + doubleValue); //doubleValue1 =
10.0
doubleValue = 20L; // long -> double
System.out.println("doubleValue2 = " + doubleValue); //doubleValue2 =
20.0
}
}
- 만약 형변환이 생략된다면, 컴파일러는 기존의 값을 최대한 보존할 수 있는 타입으로 "자동 형변환"을 수행한다 !
intValue = 2024
doubleValue = intValue
doubleValue = (double)intValue // 1. 자동으로 타입이 맞춰진다.
doubleValue = (double)2024 // 2. 변수 값을 읽는다.
doubleValue = 2024.0 // 3. 타입을 변환한다.
💡 형변환이 가능한 경우 ?
1. boolean을 제외한 나머지 7개의 기본형은 서로 형변환 가능
2. 기본형과 참조형은 서로 형변환 불가능
3. 값의 범위가 작은 타입에서 큰 타입으로의 형변환은 생략 가능
형변환 - 명시적 형변환
- 개발자가 앞서말한 값 손실 위험을 감수하고도 값을 대입하고 싶은 경우에 데이터 타입을 강제로 변환하기 위한 방법 ?
➙ "명시적 형변환"
anyTypeValue = (anyType)toTypeValue // 형변환 방법
- 정수형(int)와 실수형(double) 간의 형변환 (정밀도 = 유효자릿수)
➙ 8자리 이상의 값은 10진수로 약 7자리의 정밀도를 제공하는 float가 아닌 약 15자리의 정밀도를 제공하는 double로 변환해야 오차가 발생하지 않는다 !
( int는 최대 10자리의 정밀도를 요구한다 ! )
💡 큰 범위(long)의 값을 작은 범위(int)의 변수에 대입할 때의 문제1: "오버플로우"
보통 오버플로우가 발생하면 마치 시계가 한바퀴 돈 것 처럼 다시 처음부터 시작한다. 오버플로우는 절대 발생해선 안되는 문제이다 !
( -2147483648는 `int`가 허용하는 가장 작은 값 )
// 정상 범위
maxIntValue = 2147483647; //int 최고값
intValue = (int) maxIntValue; //변수 값 읽기
intValue = (int) 2147483647L; //형변환
intValue = 2147483647;
// 초과 범위
maxIntOver = 2147483648L; //int 최고값 + 1
intValue = (int) maxIntOver; //변수 값 읽기
intValue = (int) 2147483648L; //형변환 시도
intValue = -2147483648;
💡 큰 범위(double)의 값을 작은 범위(double)의 변수에 대입할 때의 문제2: "소수점 버림"
소수점 이하의 값이 손실된다 !
package casting;
public class Casting2 {
public static void main(String[] args) {
double doubleValue = 1.5;
int intValue = 0;
//intValue = doubleValue; // 주석을 제거하면, 컴파일 오류 발생
intValue = (int) doubleValue; // 형변환
System.out.println(intValue); // 출력:1 (소수점 버림)
}
}
계산과 형변환
형변환은 대입 뿐만 아니라, 계산을 할 때도 발생한다 !
- 같은 타입끼리의 계산은 같은 타입의 결과를 낸다.
➙ `int` + `int`는 `int`를, `double` + `double`은 `double`의 결과가 나온다. - 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.
➙ `int` + `long`은 `long` + `long`으로 자동 형변환이 일어난다.
package casting;
public class Casting4 {
public static void main(String[] args) {
int div1 = 3 / 2; // int / int
System.out.println("div1 = " + div1); //1
double div2 = 3 / 2; // 자동 형변환: div2 = (double) 1
System.out.println("div2 = " + div2); //1.0
double div3 = 3.0 / 2; // 자동 형변환: 3.0 / (double) 2
System.out.println("div3 = " + div3); //1.5
double div4 = (double) 3 / 2; // 명시적 형변환
System.out.println("div4 = " + div4); //1.5
// int형끼리 연산해서 소수까지 구하고 싶으면 ?
int a = 3;
int b = 2;
double result = (double) a / b; // 명시적 형변환
System.out.println("result = " + result); //1.5
}
}