자바에서 화면으로부터 입력을 받아오는 방법은 정말 다양합니다.
그 중에서 가장 기본적이고 보편적인 방법이 Scanner 클래스를 이용한 방법입니다.
Scanner가 보편적으로 쓰이는 이유와 Scanner의 사용법, 유의사항 등
Scanner 클래스에 대해 알아보겠습니다.
입출력(I/O)이란?
I/O란 Input/Output의 약자로 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말합니다.
Scanner는 Input/Ouput 중 Input에 해당하며, 화면으로부터(사용자로부터) 입력을 받는 것을 도와주는 클래스입니다.
Scanner 클래스
화면으로부터 읽어온 바이트를 정수, 실수, 문자, 문자열, boolean 등의 타입으로 변환해 리턴하는 클래스
- java.util.Scanner : java.util 패키지 아래 클래스로 존재
- 동작 방향 : 공백 문자(white-space)로 입력을 토큰으로 나눈 후, 토큰을 사용자의 목적에 따라 데이터 유형으로 변환 후 반환
- 공백 문자(white-space) : 기본으로 설정 되어 있는 ' '(space), '\t', '\n', '\f', 'r' 등
+) 공백 문자로 허용되는 문자들은 Character.isWhitespace에서 허용된 문자들만 사용 가능 - 데이터 유형은 사용하는 메소드(함수)에 따라 다양한 데이터 타입으로 변환
즉, Scanner은 화면으로부터 입력받은 데이터를 사용자가 원하는 데이터 타입으로 변환해 반환해주기 때문에 편의성이 좋아 가장 기본적이고 보편적인 입력 방법이 되었습니다.
하지만, 편의성을 갖추기 위해 정말 많은 타입 변환 함수들을 갖고 있기 때문에 속도면에서 매우 느리다는 단점이 있습니다. 또한 다양한 경우의 수(다양한 공백 문자, 데이터 타입 등)를 다루기 때문에 정확한 사용법을 알아야 원하는 데이터를 얻을 수 있습니다.
기본적인 Scanner 사용법
1. Scanner 클래스 import
// 1.
import java.util.Scanner; // Scanner 클래스 호출
// 2.
import java.util.*; // util 패키지 내 전체 클래스 호출
Scanner를 사용하기 위해선 가장 먼저 Scanner 클래스를 import 해줘야 합니다.
Scanner 클래스는 java.util 패키지 안에 존재하므로 java.util.Scanner을 import 해주면 됩니다.
java.util.*는 java.util 패키지 내의 모든 클래스를 뜻하므로 이것을 import 해줘도 Scanner을 사용할 수 있지만, 효율성이 떨어지므로 필요한 클래스만 import 해주는 것이 좋습니다.
2. Scanner 객체 생성
// 1.
Scanner 객체_이름 = new Scanner(System.in); // Scanner 객체 생성
// 2.
Scanner sc = new Scanner(System.in);
다음으로는 위와 같은 방법으로 Scanner 객체를 생성합니다.
일반적으로 Scanner의 객체이름으로는 in, input, sc, scan, kb(keyboard) 등을 많이 사용합니다.
여기서 System.in이란 ?
자바에서는 표준 입출력을 위해서 세가지 입출력 스트림인 System.in, System.out, System.err를 제공합니다.
이 세가지 입출력 스트림 중 System.in은 콘솔로부터 데이터를 입력 받는데 사용되는 스트림입니다.
이 세가지 입출력 스트림은 자바 애플리케이션의 실행과 동시에 자동적으로 생성됩니다.
그러므로 따로 생성 코드를 작성하지 않고도 사용이 가능합니다.
즉, System.in는 스트림을 통해서 콘솔로부터 입력받은 데이터를 Scanner 클래스에 넘겨주기 위해 사용됩니다.
3. 메소드를 통해 원하는 값 변수에 저장하기
// 논리형
boolean bool = sc.nextBoolean();
// 정수형
byte b = sc.nextByte();
short s = sc.nextShort();
int i = sc.nextInt();
long l = sc.nextLong();
// 실수형
float f = sc.nextFloat();
double d = sc.nextDouble();
// 문자형
String str = sc.next(); // 공백을 기준으로 한 단어를 읽어 반환
String strl = sc.nextLine(); // 개행을 기준으로 한 줄을 읽어 반환
원하는 데이터 타입의 메소드를 이용하여 입력으로부터 받아온 데이터를 변수에 저장합니다.
이때 주의해야할 것들이 Scanner 클래스는 많은 경우의 수를 다루기 때문에
메소드마다 구분자가 다른 경우, 등으로 원하는 값을 얻지 못할 수 있습니다.
그러므로 각각 메소드를 사용할 때 주의할 점을 알아두는 것이 중요합니다.
Scanner 사용시 주의 사항
1. Scanner 클래스에는 Char형을 입력받는 메소드가 없습니다.
Scanner 클래스에는 Char형 데이터를 입력받는 메소드가 없기 때문에
Char형 데이터를 입력 받기 위해서는 다른 입력 클래스를 쓰던가 아래와 같은 방법으로 Char형 데이터를 입력받을 수 있습니다.
Scanner sc = new Scanner(System.in);
// 1.
char c = sc.next().charAt(0); // 공백을 기준으로 한 단어를 읽어 그 중 첫번째 문자를 반환
// 2.
char d = sc.nextLine().charAt(0); // 개행을 기준으로 한 문장을 읽어 그 중 첫번째 문자를 반환
2. 여러 개의 문자열을 입력으로 받을 때는 "\n"를 유의해야 합니다. (next(), nextLine())
Scanner 클래스 내에 문자열을 입력받는 메소드는 대표적으로 next() 와 nextLine()이 있습니다.
위에 주석에 설명과 같이 next()는 공백을 기준으로 한 단어를 읽어 와서 반환하는 메소드이며,
nextLine()은 개행을 기준으로 한 문장을 읽어 와서 반환하는 메소드입니다.
두개의 메소드를 따로 쓸때는 전혀 문제가 되지 않지만,
문자열 여러개를 입력 받을 때 두 메소드를 같이 쓸 경우엔
두 메소드가 어떻게 동작하는지를 정확히 알아야 원하는 데이터를 입력받을 수 있습니다.
next()는 개행문자를 무시하고 입력받고 nextLine()은 개행문자를 포함하여 한줄 단위로 입력을 받습니다.
좀 더 예를 들어 설명을 하자면,
사용자가 키보드로 apple을 입력하고 엔터를 치면 버퍼에는 [apple\n]이 입력됩니다.
이때 next() 메소드를 사용했다면 버퍼에서 [apple]만 꺼내와 전달합니다. 즉 버퍼에는 [\n]이 아직 남아 있는 상태가 됩니다.
반대로 nextLine() 메소드를 사용하면 버퍼에서 [apple\n] 모두 꺼내 전달합니다. 그래서 버퍼에는 [] 아무것도 남아있지 않습니다.
그러므로 여러 개의 문자열을 입력 받을 때에는 "\n"을 유의해가며 메소드를 사용해야 합니다.
그렇지 않으면 아래와 같이 원하는 데이터를 얻을 수 없는 경우가 생깁니다.
// 사용자 입력 사항
// apple\n
// banana\n
Scanner sc = new Scanner(System.in);
String strA, strB;
strA = sc.next();
strB = sc.nextLine();
System.out.println(strA);
System.out.println(strB);
// 결과값
// apple
//
사용자가 원하는 값은 strA에는 apple 값이, strB에는 banana가 들어가길 원했겠지만,
이 코드에 실제 값은 실제로 apple 값을 누르고 엔터를 치자마자 결과값이 출력이 되며
strA에는 apple 값이 들어가며 strB에는 어떤 값도 들어가지 않습니다.
이유는 위에 설명한 것과 같이 next()는 개행문자를 무시하고 입력을 받기 때문에 버퍼에서 [apple]만 가져갑니다. 그 이후에 버퍼에는 [\n]이 남아있게 됩니다. 그 이후에 실행되는 nextLine()은 개행문자(\n)를 구분자로 쓰는데 현재 버퍼에 [\n]가 남아있으므로 입력이 완료되었다고 판단합니다. 그래서 버퍼의 처음부터 \n까지를 입력이라 간주하고 전달하는데 버퍼에는 달랑[\n]만 존재하므로 strB에는 어떤 값도 저장되지 않고 프로그램이 종료되는 것입니다.
이 경우에서 문제를 해결하기 위해서는 아래와 같이 코드를 변경해야 합니다.
// 사용자 입력 사항
// apple\n
// banana\n
Scanner sc = new Scanner(System.in);
String strA, strB;
// 1.
strA = sc.nextLine();
strB = sc.next();
// 2.
strA = sc.next();
strB = sc.next();
// 3.
strA = sc.nextLine();
strB = sc.nextLine();
System.out.println(strA);
System.out.println(strB);
// 결과값
// apple
// banana
입력받는 문자열이 단어로 구성되어 있는 경우엔,
nextLine()을 먼저 쓴 후, next()를 쓰거나 next()를 연속해서 써도 원하는 값을 입력받을 수 있습니다.
입력받는 문자열이 문장이거나 문장+단어인 경우엔
경우에 따라 nextLine()과 next()를 혼합해 사용하거나, 간단하게는 그냥 모두 nextLine()으로 입력을 받으면 됩니다.
즉, 여러 개의 문자열을 입력받을 때에는 입력받는 문자열의 경우를 따져 알맞게 next()와 nextLine() 중 올바른 메서드를 사용해야 원하는 데이터 값을 제대로 입력받을 수 있습니다.
3. 여러 데이터 형의 입력을 받을 때는 "\n"를 유의해야 합니다.
이 경우는 2번 경우와 비슷한 경우지만 메소드를 바꾸는 것으로 해결이 되지 않는 경우입니다.
여러 데이터 형을 입력받을 때 특히 (문자열 외 데이터 형) + (문자열 데이터 형)을 순서대로 입력 받아야 할 때 발생하는 문제점입니다.
대다수의 메소드가 개행 문자를 무시한 채 입력을 받기 때문에 발생하는 문제입니다.
예를 통해서 설명을 하자면,
// 사용자 입력 사항
// 1\n
// banana
Scanner sc = new Scanner(System.in);
int n;
String str;
n = sc.nextInt();
str = sc.nextLine();
System.out.println(n);
System.out.println(str);
// 결과값
// 1
//
사용자로부터 숫자 다음에 문자열을 입력받고자 하는 상황입니다.
사용자가 키보드로 1을 치고 엔터를 치자마자 위의 경우처럼 결과값이 출력되고 프로그램이 종료됩니다.
이유는 위와 같이 nextInt() 메소드는 버퍼에서 1만 가져와 입력으로 받기 때문에 nextLine()이 버퍼에 남아있는 [\n]를 보고 입력됐다 판단해 \n앞부분을 반환하고 프로그램이 종료된 것입니다.
하지만 이 경우에는 위와 같이 개행문자를 포함하는 Int형 입력 메소드가 없기 때문에, Int형 메소드를 변경할 수 없습니다.
이와 같은 경우에는 아래와 같이 코드를 변경하여 문제를 해결 할 수 있습니다.
// 사용자 입력 사항
// 1\n
// banana
Scanner sc = new Scanner(System.in);
int n;
String str;
// 1.
n = sc.nextInt();
str = sc.next();
// 2.
n = sc.nextInt();
sc.nextLine(); // 개행문자 제거
str = sc.nextLine();
// 3.
n = Integer.parseInt(sc.nextLine());
str = nextLine();
System.out.println(n);
System.out.println(str);
// 결과값
// 1
// banana
첫번째는 숫자 다음 입력 받는 문자열이 단어일 경우에 사용할 수 있는 것으로 nextInt()를 통해 정수형 데이터를 입력받고 next()를 통해 다음 문자열(단어)를 입력받으면 됩니다.
하지만 문제점은 다음으로 입력 받는 문자열이 문장일 경우인데요.
이럴때는 2번과 같이 nextInt()를 통해 정수형 데이터를 입력받은 후, sc.nextLine()을 통해서 개행문자를 제거해준 후, str에 nextLine()을 입력해 문장을 입력해주는 방법으로 입력을 받으면 됩니다.
또 다른 방법으로는 3번 방법으로, 모두 nextLine()으로 문자로 읽어와 따로 데이터 형변환을 해주는 방식입니다. 하지만 이 방법은 그닥 효율적이지 못한것이 Scanner을 쓰는 이유가 형변환된 데이터값을 받기 위해서 속도가 느림에도 쓰는것인데 문자열로 입력을 받아와서 다시 또 따로 형변환을 진행하는것은 효율적이지 않습니다.
결론적으로,
Scanner은 입력 받은 데이터를 자동으로 형변환하여 반환해주기 때문에 매우 편리한 입력 클래스입니다.
하지만 그만큼 여러 데이터 형을 다루기 때문에 속도면에서는 느린 편입니다.
또한, 각각의 메소드마다 특징이 다르기 때문에 메소드별 특징을 이해해야 정확한 값을 입력받을 수 있습니다.