스캐너 (Scanner)

스캐너는 여러분들이 키보드로 입력받거나, 파일로 입력받거나 문자열로 입력을 받는데 도움을 주는 아주 유용한 클래스로 JDK1.5 버전부터 추가된 클래스입니다. 입력에 아주 유용한 클래스이기 때문에 반드시 알아야할 필요가 있습니다. 

Scanner클래스는 키보드, 파일, 문자열 등으로 입력받기 위해 다양한 생성자를 제공하며 다양한 자료형을 입력받을 수 있지요. Scanner에서 입력을 받으려면 next[자료형]() 메소드를 이용하면 되는데, 여기서 next~() 메소드를 표로 정리하였습니다. 이 메소드들과 나머지 유용한 Scanner 메소드의 사용법은 아래의 예를 통해서 사용하며 익히게 될 겁니다. Scanner의 생성자는 다양한데 항상 두번째 argument는 인코딩 방식의 Charset의 이름입니다. 예를 들어 UTF-8인지 ANSI인지 지정할 수 있죠. 지정하지 않는다면 OS에서 지정된 default charset으로 지정됩니다. 보통 쓸일은 없습니다.

 

Scanner의 next~() 메소드


입력받는 자료형과 다른 자료형의 데이터가 읽혀지면 InputMismatchException이 발생하니 상황에 맞춰 예외처리를 하시기 바랍니다. 모든 nextLine()을 제외한 next~() 메소드는 구분자가 공백입니다.

메소드명 설명
nextBoolean() boolean의 자료형을 입력받습니다. 자료형은 두개인 true,false로 대소문자를 구분하지 않습니다.  
nextByte() byte의 자료형을 입력으로 받습니다. 입력 범위(127~ -128) 밖이면 InputMismatchException이 발생합니다.
nextShort() short의 자료형을 입력받습니다.
nextInt() int형의 자료형을 입력받습니다. 
nextLong() long형의 자료형을 입력받습니다.
nextDouble() double형의 자료형을 입력받습니다.
nextFloat() float형의 자료형을 입력받습니다.
next() String형의 문자열을 입력받습니다. 이때 공백 문자까지 입력받습니다.  
nextLine() 문자열을 입력받는데 다른 next~() 메소드와 다른 점은 줄단위로 입력받는다는 점입니다. 

 

 

지금 보여드리는 사용법은 Scanner의 아주 기초적인 키보드에서 입력을 받는 예입니다.

키보드 입력

Scanner는 InputStream을 생성자로 받을 수 있는데, 여기서 키보드 입력을 받으려면 System.in의 InputStream을 넘겨주면 됩니다. 

Scanner in=new Scanner(System.in);

 

아래의 예제는 nextInt()로 문자열의 입력받을 갯수를 입력받아 for 루프로 단어를 입력받는 예제입니다.

public static void main(String[] ar) {
	Scanner in=new Scanner(System.in);
	int N=in.nextInt();
	String []strs=new String[N];
	for(int i=0;i<N;i++) 
		strs[i]=in.next();
		
	for(int i=0;i<N;i++)
		System.out.println("String["+i+"] : "+strs[i]);
		
}

 

정수 입력을 3, 그리고 문자열을 공백으로 주어 입력받은 결과입니다.

3
My Name reakwon
String[0] : My
String[1] : Name
String[2] : reakwon

 

문자열에서 입력

Scanner는 문자열로도 입력을 받을 수 있습니다. 

	String str="2 boys and 2 girls";
	Scanner in=new Scanner(str);
		
	int num1 = in.nextInt();
	String word1 = in.next();
	String word2 = in.next();
	int num2 = in.nextInt();
	String word3 = in.next();
	System.out.println(num1+" "+word1+" "+word2+" "+num2+" "+word3);

 

위의 str의 문자열은 입력된 형식을 정확히 안다는 가정하에 코딩한 것입니다. 2는 int형, 그리고 boys는 문자열 이렇게 Scanner의 메소드로 입력을 받는 예제입니다. 여기서 모든 입력은 공백으로 구분되어 집니다. 아래는 그 결과입니다.

2 boys and 2 girls

 

파일에서의 입력

이번에는 파일을 입력 삼아서 읽어볼까요? 아래의 파일이 프로젝트와 같은 디렉토리에 존재한다고 해봅시다. 아래에 input.txt를 만들어주세요.

위 파일은 모두 실수이죠?  그래서 nextDouble()메소드를 사용할 것인데 이때 double의 자료형이 있는지 검사하며 자료형이 존재하면 내용을 읽어서 출력하고 싶습니다. 이때 사용할 수 있는 메소드가 hasNext[자료형] 입니다.

public static void main(String[] ar) throws IOException {
		
	Scanner in=new Scanner(new File("input.txt"));
	System.out.println("파일로부터 실수 입력");
	while(in.hasNextDouble()) {
		System.out.println(in.nextDouble());
	}
}

 

파일에서 입력을 받을때는 IOException이 발생할 수 있으므로 throws 키워드를 사용해서 IOException을 처리해줍니다. while문에 보이는 hasNextDouble()이 보이시나요? 이 메소드가 다음에 double의 자료형을 갖는 데이터가 있냐고 물어보는 메소드로 있다면 true, 아니면 false를 반환합니다. 나머지의 int, float, boolean 역시 저 메소드 명의 형식을 따라가니까 사용해보시면 됩니다. 여기서는 굳이 정리하지 않겠습니다.

구분자 정하기(Delemiter)

구분자는 기본적으로 공백인데 필요에 따라서는 우리가 정해야될 필요가 있습니다. 그럴때 사용할 수 있는 메소드가 useDelimiter() 메소드입니다. useDelimiter()는 두가지로 오버로딩이 되어있는 메소드인데, 단순 문자열로만 구분할 수 있는 메소드와 정규표현식을 사용하여 쓸 수 있는 구분자가 사용됩니다. 여기서 한번 구분자를 우리가 정해보도록 합시다. 아까 사용했던 input.txt파일을 이렇게 바꿔봅시다.

 

라인 단위로 -로 실수가 구분되어있습니다. 이것을 방금 전에 결과와 같게 만든다면 어떻게 구현하면 좋을까요? 우선 line단위로 입력을 받는다면 그 line은 "실수1-실수2-실수3..." 으로 구분되어있겠죠. 그리고 "-"를 구분자로 삼아서 nextDouble() 메소드로 읽어주면 됩니다. 그래서 Scanner가 2개가 필요합니다. 아래의 코드가 그 구현을 보여줍니다.

public static void main(String[] ar) throws IOException {
		
	Scanner in=new Scanner(new File("input.txt"));
	System.out.println("파일로부터 실수 입력");
		
	while(in.hasNextLine()) {
		Scanner line=new Scanner(in.nextLine());	//Line단위의 입력을 Scanner의 전달
		line.useDelimiter("-");						//"-"을 구분자로 사용
			
		while(line.hasNextDouble()) {				//다음 Double 자료형이 있는가?
			System.out.println(line.nextDouble());	//있다면 Double 출력
		}
	}
}

 

코드의 결과는 동일합니다.

파일로부터 실수 입력
1.1
2.2
3.3
4.4
5.5
11.1
12.2
13.3
14.4
15.5

 

이렇게 Scanner에 대한 사용법에 대해서 알아보았습니다. Scanner는 Stream과 정규 표현식을 자세히 알면 유용한 클래스입니다. 이상으로 Scanner에 대한 코드와 설명이었습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

직렬화(Serialization)

우리는 파일에 텍스트를 기록하고, 이진 데이터를 기록하는 방법은 많이들 알고 계시겠습니다. 그런데 만약 이런 종류의 데이터들이 아니라 객체를 파일로 저장하거나 읽어올 수 있을까요? 네, 물론 있습니다. 여러분이 직렬화만 배운다면 할 수 있습니다. 

직렬화에 대한 아주 직관적인 그림은 아래에 나타내어져있는데요. Account라는 클래스는 email, 이름, 주소, 폰 번호, 등록 일자를 멤버로 갖는 클래스입니다. 이것을 객체화하여 파일이나 네트워크로 write할때는 직렬화를 거쳐서 전달됩니다. 반대로 읽어올때는 역직렬화(Deserialization)를 거쳐서 가져오게 되지요. 

 

직렬화에 메소드는 포함하지 않습니다. 메소드는 각 클래스가 같은 동작을 수행하기 때문에 직렬화해서 저장할 필요는 없습니다. 단지 멤버들마다 다른 값을 가지고 있는 필드들이 직렬화가 됩니다. 

이제 본격적으로 직렬화는 어떻게 수행해야할까요? 우리는 두가지만 알고 있으면 됩니다. 

1. 직렬화가 가능한 클래스를 구현하기

2. 직렬화가 된 클래스의 객체를 쓰고 읽는 Stream 준비하기

 

1. 직렬화가 가능한 클래스 구현하기

이건 간단합니다. 직렬화를 하려면 우선 Serializable을 인터페이스를 implements를 해야합니다. 그래서 우리가 Account클래스를 정의하려면 아래와 같이 정의가 되어야합니다.

class Account implements Serializable{
	private String email;
	private String name;
	private String address;
	private String phone;
	private Date reg_date;
	public Account(String email,String name,String address,String phone) {
		this.email=email;
		this.name=name;
		this.address=address;
		this.phone=phone;
		reg_date=new Date();
	}
	
	public String getEmail() {
		return email;
	}
	public String getName() {
		return name;
	}
	public String getAddress() {
		return address;
	}
	public String getPhone() {
		return phone;
	}
	public String getRegDate() {
		SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
		return format.format(reg_date);
	}
	@Override
	public String toString() {
		return "Information:"+getEmail()+"/"+getName()+"/"+getAddress()+"/"+getPhone()+"/"+getRegDate();
		
	}
}

 

Serializable을 implements하고 있는 것을 볼 수 있네요. 

직렬화 제외하기 - transient

하지만 우리가 직렬화로 파일에다가 기록할때 민감한 데이터이기 때문에 직렬화에 제외하는 방법은 transient라는 키워드를 사용하면 됩니다. 그러면 직렬화에 빠지게 되어 파일에 저장되지 않습니다. 예를 들면 아래와 같이 사용할 수 있겠습니다.

class Account implements Serializable{
	//... 생략 ...
	private Date reg_date;
	private transient String password; 
	public Account(String email,String name,String address,String phone) {
		//...생략...
		reg_date=new Date();
		password="12341234";
	}
	//...생략...
}

 

2. 객체 쓰기 읽기

2-1. 객체 쓰기(ObjectOutputStream)

객체를 쓰려면 Stream을 열어야합니다. 우리는 당장 파일에 그 객체를 쓸 것이기 때문에 FileOutputStream을 사용할 것이고 이 파일 스트림에 객체를 쓸 것이기 때문에 ObjectOutputStream을 사용할 겁니다. 사용법은 아주 간단한데요. 아래의 차례대로 진행하면 됩니다. 우선 FileOutputStream으로 파일의 Stream을 열고 이것을 ObjectOutputStream으로 전달해주면 됩니다.

FileOutputStream fos=new FileOutputStream("user.acc");
ObjectOutputStream oos=new ObjectOutputStream(fos);

 

여기서 user.acc는 우리가 객체를 저장할 파일 이름입니다. 파일을 열때 FileNotFoundException이라는 IOException이 발생할 수 있습니다. 이것을 직접 처리하기가 귀찮으니 우리는 메인 함수에서 throws로 처리하도록 하겠습니다.

public static void main(String[] ar) throws IOException

 

그리고 파일에 써줄 객체를 정의해주고 ObjectOutputStream의 객체 쓰기 메소드인 writeObject에 전달해주면 됩니다. 그리하여 객체를 쓰는 전체코드는 아래와 같습니다. 

public static void main(String[] ar) throws IOException, ClassNotFoundException{
	Account wuser=new Account("reakwon@aaa.ccc","reakwon","seoul","010-1234-1010");
		
	FileOutputStream fos=new FileOutputStream("user.acc");
	ObjectOutputStream oos=new ObjectOutputStream(fos);
	oos.writeObject(wuser);
	oos.close();
}

 

이제 파일이 저장이 되었을텐데 현재 프로젝트 working directory에 있을 겁니다.

 

그리고 메모장으로 그 내용을 까보면 이렇습니다. 중간에 짤리고 알아볼 수 없는 문자들도 있지만 대충 우리가 알 수 있는 것은 멤버의 필드들만 보이는 것을 확인할 수 있고 메소드의 구현부는 확인할 수가 없지요. 물론 우리가 직렬화하지 않길 원하는 멤버 필드(transient로 선언한 멤버 필드) 역시 이곳에 기록되어 있지 않습니다.

 

2-2. 객체 읽기(ObjectInputStream)

이제는 이 파일에 쓰여진 객체를 읽어볼까요? 객체를 읽는데는 파일 읽기 스트림(FileInputStream)을 열고 ObjectInputStream을 통해서 읽어오면 됩니다. 이것 역시 간단하게 ObjectInputStream에 FileInputStream을 전달하고 readObject를 이용해서 객체를 얻어오면 됩니다. 이때 Object객체로 반환하기 때문에 적절히 형변환을 해야합니다. 그래서 객체를 읽는 과정은 아래와 같습니다.

Account ruser=null;
		
FileInputStream fis=new FileInputStream("user.acc");
ObjectInputStream ois=new ObjectInputStream(fis);
ruser=(Account)ois.readObject();

ois.close();

 

혹시나 객체의 클래스를 찾을 수가 없는 예외가 발생할 수 있습니다. 그때 발생할 수 있는 예외가 ClassNotFoundException입니다. 이것도 메인 메소드에서 던져주도록 합시다.

public static void main(String[] ar) throws IOException, ClassNotFoundException{

 

객체를 쓰고 읽는 전체 코드는 이렇습니다. 

public class Main {
	
	public static void main(String[] ar) throws IOException, ClassNotFoundException{
		Account wuser=new Account("reakwon@aaa.ccc","reakwon","seoul","010-1234-1010");
		
		Account ruser=null;
		
		FileOutputStream fos=new FileOutputStream("user.acc");
		ObjectOutputStream oos=new ObjectOutputStream(fos);
		oos.writeObject(wuser);
		
		FileInputStream fis=new FileInputStream("user.acc");
		ObjectInputStream ois=new ObjectInputStream(fis);
		ruser=(Account)ois.readObject();
		
		System.out.println(ruser);
		
		oos.close();
		ois.close();
		
	}
}

 

그래서 읽어보면 제대로 읽어오는 것을 확인할 수가 있네요.

Information:reakwon@aaa.ccc/reakwon/seoul/010-1234-1010/2021-04-07

 

만약 transient로 직렬화에 포함되지 않은 데이터는 null로 읽힙니다.

 

직렬화 클래스 버전 관리

객체의 직렬화나 역직렬화에서 클래스는 완전히 동일한 클래스를 통해서 쓰고 읽혀야합니다. 그렇지 않은 경우는 아래의 에러를 맛 볼 수 있습니다. 여기서 쓸때는 기존의 Account 클래스, 그리고 읽을때는 필드하나를 추가해 변경된 Account클래스로 읽어 보았습니다.

 

Exception in thread "main" java.io.InvalidClassException: aa.Account; local class incompatible: stream classdesc serialVersionUID = 2399026023152107267, local class serialVersionUID = 9178730303496146785
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:722)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2022)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1872)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2179)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1689)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:495)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:453)
	at aa.Main.main(Main.java:15)

 

이렇게 InvalidClassException이라는 예외가 발생하므로 우리는 직렬화 클래스의 버전을 관리해주어야합니다. 자바는 serialVersionUID를 통해서 버전이 같은 클래스인지 아닌지 판단할 수 있습니다. 그래서 이 버전이 같은지 같지 않은지를 통해서 조치를 취해야합니다. 그래서 우리도 위의 serialVersionUID를 사용하여 클래스 버전을 관리할 수 있습니다.

class Account implements Serializable{
	static final long serialVersionUID=1919191919191919L;
    //...생략...
}

 

이처럼 객체를 저장하고 읽을때는 객체가 직렬화가 되어야하고 Object를 쓰는 ObjectOutputStream, Object를 읽는 ObjectInputStream을 사용하여 읽을 수 있습니다. 객체를 파일로 읽고 쓰는 경우는 어떤 경우에 유용할까요? 엑셀 파일을 저장하거나 한글 문서파일을 저장, 그리고 그림판의 파일을 저장하고 불러오는 등 객체를 쓰고 읽어야할 경우는 매우 많으니 알아두시는 게 매우 편합니다.

 

여기서 우리는 File에만 Object를 쓰고 읽었지만 네트워크에서도 가능합니다. 이상으로 객체 직렬화에 대한 포스팅을 마치도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,