[TIL 14th] 역시나 자바도 입력 메소드가 나를 괴롭게 하는군요
by 노실언니어제 문제를 풀었는데 틀린게 많아서 다시 풀어보려한다.
[문제1] 아래 할당문에 오류가 있다면 수정하기
long var = 50000000000;
float var = 3.14;
double var = 100.0;
boolean var = 0;
float = 1e2f;
[답] 전반적으로 내가 틀린 이유는 컴파일러가 자동으로 형변환을 해주지 않는데 해줄거라고 생각해서였다.
long var = 50000000000;
당연히 맞는 코드인 줄 알았으나, error: integer number too large 발생
컴파일러가 500...00 이라는 숫자 리터럴을 읽고 자동으로 int 형으로 처리를 하려했으나
이 과정에서 해당 숫자가 int 범위를 벗어나다보니 에러가 난 듯 싶다.
컴파일러가 이 숫자값을 자동으로 int 형 처리하지 말고 애초에 long 형으로 판단해야하므로
답 long var = 50000000000L; 이 맞는 코드이다.
float var = 3.14;
소수 리터럴은 컴파일러가 자동으로 double 형 처리를 한다고 한다.
double 형 값을 더 좁은 범위의 float 형에 할당하려고 해서 에러가 나는걸까?
error: incompatible types: possible lossy conversion from double to float
그런 것 같다. 그러면 변수형을 double 로 해주거나, 실수 리터럴 뒤에 F를 붙여주면 되겠다.
더 좁은 범위의 type로는 자동형변환이 일어나지 않는다.
답 double var = 3.14; 혹은 float var = 3.14F;
double var = 100.0;
답 : 문제 없음
boolean var = 0;
0을 false 로 생각하지 못하나보다.
error: incompatible types: int cannot be converted to boolean
답 boolean var = false;
float = 1e2f;
1e2(100)을 float로 인식하라는 말인 줄 알았는데 아닌가보다
error: not a statement
ㅋㅋㅋㅋㅋㅋㅋㅋ아 변수명이 없구나 ㅠㅠ
답 float var = 1e2f;
[문제2] 다음 코드가 컴파일 에러가 발생하는 이유는?
double value = 2e-350;
[답] 값이 double 형의 허용 범위 밖에 있음
double 타입의 양수쪽 범위는 " 4.9e-324 ~ 1.8e308 " 이다.
2e-350은 4.9e-324 보다 더 적은 숫자이기때문에 double 타입의 허용 범위 밖에 있다.
따라서, 컴파일 에러가 발생한다.
오늘 공부한 것
타입변환 할당문에서의 자동타입변환 연산에서의 자동타입변환
강제타입변환: 기본타입 간 강제변환, String타입에서/으로의 강제변환
시스템출력장치(System.out)의 출력 메소드: println, print, printf
시스템입력장치(System.in)의 입력 메소드: read
Scanner 클래스의 객체 활용법
할당문에서 작은 범위의 타입에서 큰 범위의 타입으로 할당할 때는 자동형변환(Promotion)이 일어난다.
정수→실수, byte→short→int→long→float→double, char→int 으로의 형변환
char 은 음의 정수를 포함하지 않으므로, byte→char 은 자동으로 변환되지 않는다.
정수 연산식의 두 피연산자의 타입이 모두 int 형 이내라면 전부 int로 자동형변환(Promotion)이 일어난다.
(예) byte + short → int + int → int
정수 연산식의 두 피연산자 중 허용 범위가 더 작은 타입의 변수는 더 큰 범위의 타입으로 자동형변환이 일어난다.
(예) char + long → long + long → long
실수 연산식에서 두 피연산자의 타입이 같다면 자동형변환이 일어나지 않고, 그대로 해당 타입으로 연산된다.
(예) float + float → float
(예) double + double → double
실수 연산식에서 두 피연산자의 타입이 다르다면 허용 범위가 작은 변수는 더 큰 범위의 타입으로 자동형변환이 일어난다.
(예) float + double → double + double → double
(예) int + float → float + float → float
리터럴 간의 연산은 컴파일 단계에서 미리 연산을 수행하여 바이트 코드에는 계산 결과값이 리터럴로 적혀있다.
+ 는 피연산자의 타입에 따라 산술연산, 결합연산을 수행한다
피연산자가 둘 다 숫자형인 경우, 산술연산을 수행한다.
피연산자 중 하나라도 문자열인 경우, 결합연산을 수행한다. 이 때, 문자열이 아닌 피연산자의 문자열 자동형변환이 일어난다.
(예) 문자열 + double → 문자열 + 문자열 → 문자열
기본타입(Primitive type) 끼리 Casting 하는 방법은 변수 앞에 (타입명)을 적는 것이다.
(예) char a = (char) b;
(예) double a = (double) b / c;
문자열→기본타입(char제외)으로의 강제형변환은 Integer.parseInt(문자열) 과 같은 방식으로 한다.
이 때, 숫자 외의 값이 포함된 문자열을 숫자타입으로 강제형변환하는 경우, NumberFormatException이 발생한다.
(예) byte a = Byte.parseByte(str);
(예) double a = Double.parseDouble(str);
(예) boolean a = Boolean.parseBoolean(str);
문자열→문자형으로의 강제형변환은 문자열.charAt(0); 을 사용한다.
(예) char a = str.charAt(0);
기본타입(char포함)→문자열로의 강제형변환은 String.valueOf(기본타입값) 메소드를 사용한다.
(예) String str = String.valueOf(a);
시스템 출력/입력장치 이름 → System.out / System.in
└ 파라미터 값을 출력하고 개행도 할 것 → println(값); (ln = line)
└ 파라미터 값을 출력만 할 것 → print(값);
└ 첫 번째 파라미터 값인 format string에 맞추어 포맷된 결과물을 출력할 것(개행X) → printf("포맷스트링", 값1, 값2, · · · );
└ 입력장치로 입력한 키코드를 읽을 것 → read();
└ System.in.read() 의 한계 : 키코드 하나를 읽을 뿐, 정상적인 문자열이나 한글 등을 읽어올 수 없다.
└ 해결 : Scanner 클래스의 객체를 생성하여 활용하기
└ ① import java.util.Scanner; 로 Scanner 클래스를 사용하기 위한 패키지를 임포트해온다.
└ ② Scanner sc = new Scanner(System.in); 으로 표준입력스트림을 사용하는 Scanner 객체 sc를 생성한다.
└ ③ 입력받아야하는 데이터의 형식에 따라 적절한 메소드를 사용한다. (예) nextLine(), nextInt(), nextDouble() 등
└ ④ nextInt()나 nextDouble() 처럼 입력버퍼에 있는 줄바꿈 문자를 읽지 않고 남기는 메소드를 사용한 경우에는
즉각 nextLine() 메소드를 사용하여 버퍼에 남아있는 줄바꿈 문자를 빼낸다.
└ ⑤ Scanner 객체의 사용을 마친 뒤에는 효율적인 리소스 사용을 위해 sc.close(); 로 제거해주는 것이 좋다.
쬐끔 더 공부한 것
1) char = char + char 은 컴파일 오류
int 범위 이내의 정수 타입 변수(byte, char, short)가 산술 연산식에서 피연산자로 사용되면 해당 변수는 int 로 자동형변환된다.
따라서, char 형 + char 형은 int 형 + int 형으로 자동형변환되고 결과값도 int 형이 된다.
이 int 형 결과값을 char 형 변수에 할당하는 것은 더 좁은 범위의 타입에 할당하는 상황이 되어 컴파일 오류가 발생한다.
오류를 해결하기 위해서는 1. 결과값을 char형으로 강제형변환한 후에 char형 변수에 할당하거나
2. int형 변수에 할당한 후 char형으로 강제형변환한 후에 출력하여야 한다.
char a = 65;
char b = 66;
char c = a + b; // error: incompatible types: possible lossy conversion from int to char
System.out.println(c);
// 해결1.
char c = (char) (a + b);
System.out.println(c);
// 해결2.
int c = a + b;
System.out.println((char)c);
2) 강제형변환 방법이 너무 중구난방이라 한 번 더 정리
기본 타입
└ 정수형 : byte short char int long
└ 문자형 : char
└ 실수형 : float double
클래스
└ 문자열형 : String
자동형변환 규칙 (작→큰)
정수형만 존재하는 이항연산에서 피연산자가 int 범위 이내면 int 로 자동형변환
정수형만 존재하는 이항연산에서 int 범위보다 큰 피연산자가 있다면 해당 연산자의 자료형으로 자동형변환
실수형이 포함된 이항연산은 범위가 큰 피연산자의 자료형으로 자동형변환
+ 이항연산에서 피연산자가 모두 숫자형이면 덧셈연산진행
+ 이항연산에서 String 형 피연산자가 있다면 나머지 피연산자도 String 형으로 자동형변환
강제형변환 방법 (큰→작 혹은 형식의변화)
기본 타입 간의 강제형변환은 (타입명) 변수명 으로 간단하게 가능하다 = Casting
기본 타입 → 문자열형 String.valueOf(기본타입값);
문자열형 → 기본 타입(char제외) Integer.parseInt(문자열);과 같은 형식
문자열 속에 숫자만 있는 것이 아닌 경우 오류던짐 : NumberFormatException
문자열형 → char형 문자열.charAt(0);
3) 어차피 까먹을꺼지만 포맷스트링 활용해보기
public class practice {
public static void main(String[] args){
int value = 123;
System.out.printf("상품의 가격:%d원\n", value);
System.out.printf("상품의 가격:%6d원\n", value); // 6칸확보 + 우측정렬
System.out.printf("상품의 가격:%-6d원\n", value); // 6칸확보 + 좌측정렬
System.out.printf("상품의 가격:%06d원\n", value); // 6칸확보 + 우측정렬 + 빈공간0채움
double area = 3.141592 * 10 * 10;
System.out.printf("반지름이 %d인 원의 넓이:%f\n", 10, area);
System.out.printf("반지름이 %1$d인 원의 넓이:%2$f\n", 10, area);
System.out.printf("반지름이 %d인 원의 넓이:%15f\n", 10, area); // 15칸확보 + 우측정렬
System.out.printf("반지름이 %d인 원의 넓이:%15.2f\n", 10, area); // 15칸확보 + 소숫점이하둘째까지 + 우측정렬
System.out.printf("반지름이 %d인 원의 넓이:%-15f\n", 10, area); // 15칸확보 + 좌측정렬
System.out.printf("반지름이 %d인 원의 넓이:%-15.2f\n", 10, area); // 15칸확보 + 소숫점이하둘째까지 + 좌측정렬
System.out.printf("반지름이 %d인 원의 넓이:%015f\n", 10, area); // 15칸확보 + 우측정렬 + 빈공간0채움
System.out.printf("반지름이 %d인 원의 넓이:%015.2f\n", 10, area); // 15칸확보 + 우측정렬 + 빈공간0채움
// 포맷 스트링에서 format specifier의 시작인 %가 아닌 %를 출력하고 싶다면 %%를 쓰자 (\%가 아님)
System.out.printf("완성도는 %d%%입니다.\n",95);
}
}
4) 입력을 받을 때는 엔터가 말썽이다
public class practice {
public static void main(String[] args) throws IOException {
// throws ~ 를 쓰지 않으면 에러남
// error: unreported exception IOException; must be caught or declared to be thrown
int keyCode = System.in.read();
System.out.println("keyCode: " + keyCode);
keyCode = System.in.read();
System.out.println("keyCode: " + keyCode);
keyCode = System.in.read();
System.out.println("keyCode: " + keyCode);
}
}
특이한 점
Line 5 에서 입력을 받는다, 이 때 a 만 입력해서는 어떤 일도 일어나지 않는다.
a ↵ 를 입력해야 read() 명령어가 끝나고 keyCode에 값이 할당된다
그런데, 'a', ' ↵ ' 의 입력이 다 저장되어서(아무래도 입력 스트림 버퍼이지 않을까)
Line 5 의 변수 keyCode 에는 'a' 의 키코드인 97이 저장되고, Line 6에서 키코드 97이 출력되며
Line 8 에서 입력을 받지 않고 keyCode 에 10이 저장되고, Line 9에서 키코드 10이 출력된다.
Line 11에서 입력을 다시 받으려했고 나는 ' ↵ ' 를 입력했다. 그리고, Line 12에서 10이 출력된 걸 보니
엔터 ↵ 의 키코드가 10이었던 것이다.
(책에는 ↵ 의 키코드가 CR13, LF10 두 개라는데, 내가 한 실습으로는 10밖에 안 떴다)
입력명령을 사용할 때는 생각과 다르게 굴러가는 경우가 많았는데, 바로 이런 상황이다.
입력 명령어를 3번이나 썼는데, 한 번만 입력받고 자기맘대로 처리되는 상황.
엔터가 말썽이다.
public class practice {
public static void main(String[] args) throws IOException {
int keyCode;
while(true) {
keyCode = System.in.read();
System.out.println("keyCode: " + keyCode);
}
}
}
위 코드를 돌려보면 입력을 한 번 길게 받으면 반복문이 여러번 돌아간다.
언제까지? 입력한 값이 다 read() 될 때까지.
'h'+'e'+'y'+'↵' 를 입력하면, 반복문이 4번 돌아가는 동안은 입력창이 안 뜨고, 그 다음 다시 입력창이 뜬다.
'h'+' '+'i'+'↵' 를 입력하면 똑같이 입력창이 안 뜬 채로 출력만 4번 되다가, 다시 입력창이 뜬다.
아무래도 입력 명령어가 돌아가는 원리는 이런 것 같다(추측)
- read() 메소드는 '입력받아라/읽어라' 라기보단 '입력 스트림 버퍼에 있는 값 중 가장 오래된 값 하나를 빼와라' 같다.
- 그래서, 입력 스트림 버퍼에 값이 없는데 read() 명령어가 들어오면 일단 입력을 받아서 버퍼를 채운 후에 read()하는거다.
- 버퍼에 값이 넉넉한 상황에서 read() 명령을 하면, 사용자 입력을 받지않고 계속 버퍼에서 빼오는 일만 쭉쭉 하다가
- 버퍼에 값이 없는데 read() 명령이 오면 이제 다시 사용자에게 입력을 받는거다. 이런 과정의 반복인 것이 아닐까?
그러니까, read() 명령어를 나는 사용자에게 입력을 받는 명령어라고 착각을 했는데 사실은 버퍼에서 읽어오는 명령어인 것이다!
(까지 My 뇌피셜)
5) 향상된 입력방식인 Scanner 객체의 다양한 메소드들
어떤건 입력시 사용된 엔터를 버퍼에 넣고 어떤건 안 넣고...
어떤건 엔터까지 읽고 어떤건 엔터를 안 읽고 버퍼에 냄겨두고... 복잡스 해서 정리해둔다.
+ Scanner 클래스를 사용하기 위해서는 import java.util.Scanner; 로 Scanner 패키지를 불러와야하고
Scanner 객체의 사용을 종료한 시점에는 객체명.close(); 로 닫아주는 것 잊지 않기 :D
1. nextLine()
nextLine() 메소드는 입력 버퍼에 남아있는 모든 데이터를 빼내어 읽고 문자열로 반환한다.
⭐ 이 때, 개행문자(\n)가 버퍼 중간에 나오면 개행 문자까지를 빼내어서 읽고 개행 문자를 뺀 나머지 문자열만 반환한다.
(따라서, 읽은 문자열에 개행문자도 추가하고 싶다면, 수동으로 추가해줘야 한다.)
다른 메소드들은 버퍼에서 개행 문자를 안 가져온다. 개행문자 앞까지만 가져오고 개행 문자를 버퍼에 버려두고 온다.
nextLine()은 개행문자까지 버퍼에서 꺼내오고, 그리고 꺼내는 왔지만 반환할 때는 개행문자를 뺀다는 점이 특별하다.
입력 버퍼에 아무 것도 없는데 메소드를 사용하면, 입력이 들어올 때까지 사용자 입력을 받는다. (개행문자만 입력해도 ⭕)
⭐ 버퍼에 개행문자만 있는데 메소드를 사용하면, 버퍼에서 개행문자를 가져온 다음 개행 문자를 뺀 빈문자열"" 를 반환한다.
2. next()
next()메소드는 입력 버퍼에서 다음 구분자까지 읽고 문자열로 반환한다.
⭐⭐⭐ 이 때, 구분자로 사용되는 공백 문자(스페이스, 탭)과 개행 문자를 다르게 처리한다.
- 공백문자가 버퍼 중간에 나오면 공백 문자까지를 빼내어서 읽고 공백문자를 뺀 문자열을 반환한다. 버퍼에 공백문자가 안 남는다.
- 개행문자가 버퍼 중간에 나오면 개행 문자 전까지를 빼내어서 읽고 문자열로 반환한다. 버퍼에 개행문자가 남는다.
⭐ 버퍼에 구분자만 있는데 메소드를 사용하면, 구분자가 아닌 문자가 버퍼에 들어올 때까지 사용자 입력을 받는다.
3. nextByte, nextShort(), nextInt(), nextLong(), nextFloat(), nextDouble(), nextBoolean()
위 메소드들은 입력 버퍼에서 다음 구분자가 나오기 직전까지를 읽고 해당 타입의 값으로 반환한다.
즉, 구분자는 입력 버퍼에 찌꺼기처럼 남는다.
만약, 변환할 수 없는 값이 입력되면 InputMismatchException 예외가 발생한다. → 따라서, 입력문을 try-catch 구문으로 감싼다.
// 예시
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try {
System.out.print("Enter a float value: ");
float number = sc.nextFloat(); // float 입력 받기
System.out.println("You entered: " + number);
} catch (java.util.InputMismatchException e) {
System.out.println("Invalid input! Please enter a valid float.");
}
sc.close();
}
}
4. hasNext(), hasNextLine(), hasNextByte(), hasNextShort(), hasNextInt(), · · ·
다음 입력이 있는지, 다음 줄의 입력이 있는지, 입력이 해당 타입인지 확인한 후 true/false를 반환하는 함수
5. useDelimiter(String)
입력을 분리하는 기준(구분자)를 설정하는 메소드
기본 구분자는 스페이스, 탭, 줄바꿈이다.
6. close()
Scanner 객체의 사용이 끝나면 close() 메소드로 리소스를 해제한다.
'Today I Learned' 카테고리의 다른 글
[TIL 15th] 객체지향 철학에 대한 책을 샀다 (0) | 2025.01.14 |
---|---|
[TIL 13th] JAVA 2byte 정수형 char, short의 가장 큰 차이, 그리고 큰/작은 따옴표의 차이 (0) | 2025.01.09 |
[TIL 12th] SQL코드카타, Case when이 최선이 아닌 문제 (0) | 2024.12.31 |
[TIL 11th] 나의 첫 팀 프로젝트를 마무리하며, 코드 메모 및 보완점 (0) | 2024.12.31 |
[TIL 10th] 코드카타 까먹은 코드들 / 파이어베이스 쿼리사용법 / 협업시 유의점 (0) | 2024.12.30 |
블로그의 정보
노력하는 실버티어
노실언니