Java 기초 다지기 2

변현섭·2023년 6월 9일
0

Spring 잡학사전

목록 보기
2/10

4. 맵

리스트와 배열이 순차적으로(sequentially) 해당 요소 값을 구하는 반면, 맵은 key를 통해 value를 얻는 방식이다. Map인터페이스를 구현한 자료형에는 HashMap, LinkedHashMap, TreeHashMap이 있다. 자바의 맵 중 가장 기본적인 HashMap에 대해 알아보자.

① put

  • 맵에 key-value쌍을 입력한다. key와 value의 자료형에 맞게 제네릭스를 각각 지정한다.
  • 참고로 put의 파라미터는 key → vaule의 순으로 입력해야 한다.

② get

  • key에 해당하는 value값을 얻는다.

③ containsKey

  • 맵에 해당 키가 있는지 여부를 true/false로 반환한다.

④ remove

  • key에 해당하는 key-value쌍을 삭제한 후 삭제된 value를 반환한다.

⑤ size

  • 맵의 데이터의 개수, 즉 key-value쌍의 개수를 반환한다.

⑥ keyset

  • 맵의 모든 key를 반환한다. 이 때 모아진 key는 Set 자료형으로 반환된다.

  • key만 따로 모아 리스트로 관리하고 싶다면, Set자료형을 List형으로 바꾸어 사용하면 된다. Set자료형에 대해서는 아래에서 다루겠다.

5. 집합(Set)

Set인터페이스를 구현한 자료형에는 HashSet, TreeSet, LinkedHashSet이 있다. 이 중 HashSet에 대해 알아보기로 하자. 먼저 예시를 보자.

위의 예시에는 이상한 점이 두 가지 있다. "l" 문자가 하나 없어졌다는 것과 순서가 뒤죽박죽 되었다는 것이다. 그 이유는 Set자료형이 중복을 허용하지 않고, 순서가 없는(Unordered) 자료형이기 때문이다. 당연히 리스트나 배열에서 지원하는 인덱싱 연산도 지원하지 않는다. 이러한 점은 맵과 유사하다. 집합 자료형은 주로 중복을 제거하기 위해 사용된다.

1) 집합연산

아래와 같이 s1, s2집합을 생성하고 두 집합 간의 연산을 수행해보자. 참고로, 제네릭스에는 원시자료형을 쓸 수 없기 때문에 Wrapper클래스를 사용해주어야 한다.

① 교집합

  • retainAll메서드를 이용하면 교집합을 구할 수 있다.
  • s1.retainAll(s2)를 하지 않고, intersection.retainAll(s2)를 한 이유는 단지 s1의 데이터를 유지하기 위함이다.(전자의 경우, s1의 데이터가 교집합 성분만 남고 다 날아가버리기 때문에 instersection이라는 새로운 객체를 생성하여 거기에 s1을 copy해준 것뿐이다. 중요한 내용이 아니다.)

② 합집합

  • addAll메서드를 이용하여 합집합을 구할 수 있다.

③ 차집합

  • removeAll메서드를 이용하여 차집합을 구할 수 있다.

2) 내장메서드

① add

  • 집합 자료형에 값을 추가한다.
  • addAll메서드를 이용하면 한번 여러 값을 추가할 수 있다. addAll은 합집합을 구하는 데에 사용되는 함수이다.

② remove

  • 특정 값을 제거한다. 파라미터로 원소 값을 입력 받는다.

Ⅱ. 입출력

1. 콘솔 입출력

콘솔 출력은 이미 잘 알고 있기 때문에(System.out.println) 콘솔 입력에 대해서만 다루도록 하겠다. 사용자가 입력한 문자열을 얻기 위해서는 System.in을 이용해야 한다. 여기서 System.in은 InputStream의 객체이다.

InputStream의 read메서드는 1byte만을 읽을 수 있기 때문에 몇 글자를 입력하든 한 글자만 인식한다. 더구나 a를 입력하고 결과를 출력하면 뜬금없이 a에 해당하는 아스키코드 값인 97이 나온다. 이는 자바에서 문자열을 유니코드 즉, 2바이트로 해석하기 때문이다. 문자열을 1바이트로 해석하려다 보니 어쩔 수 없이 대응되는 아스키코드 값이 출력된 것이다. 문자도 하나밖에 입력 받을 수 없고, 문자로 출력도 안 되다 보니 실용성이 낮다.
사실 문자를 여러 개 입력 받으려면 위의 예시에서 int a를 byte[] a로 바꿔주면 된다.

하지만, 여전히 문자로 출력되지 않고 아스키코드 값이 출력된다. 이를 해결하기 위해서 InputStreamReader를 쓸 수 있다. InputStreamReader 객체를 생성하기 위해선 생성자 입력으로 InputStream객체가 필요하다.

이제는 byte배열이 아닌 char배열을 사용할 수 있게 되었다. 당연히 abc를 입력하면 abc가 출력된다. 하지만 여전히 아쉬운 부분은 고정적으로 3byte 스트림만 읽을 수 있다는 것이다. 사용자가 엔터키를 입력할 때까지 사용자의 입력을 받아들이기 위해 BufferReader를 사용할 수 있다.

BufferReader를 사용하려면 InputStreamReader객체가 필요하다. 즉, BufferReader는 생성자 입력으로 InputStreamReader 객체를 받고, InputStreamReader는 생성자 입력으로 InputStream 객체를 받는 것이다. 물론 이를 한 줄로 쓰는 방법도 있다. 둘 중 선호하는 방법을 사용하라.

헷갈림 방지를 위해 보충 설명하면, InputStream은 byte를 읽고, InputStreamReader는 character를 읽고, BufferedReader는 String을 읽는다. BufferReader에서 문장을 입력 받는 메서드는 readLine()으로 띄어쓰기를 포함한 문자열을 원하는 대로 입력 받을 수 있다.

단, String타입으로만 입력 받을 수 있기 때문에 정수나 실수형으로 변환이 필요하다면, Wrapper클래스를 사용하여 형변환해야 한다. 한 가지 더 중요한 사항은 문자열을 입력 받을 때는 반드시 예외처리를 해주어야 한다는 것이다. 예외처리를 하지 않으면, 컴파일 에러가 난다. 이는 IOException이 CheckedException 즉, 컴파일에러를 일으키는 ExceptionClass이기 때문이다.

우리는 throws 키워드를 통해 함수를 호출한 쪽에 예외처리를 떠넘길 수 있다고 배웠다. 위 예시와 같이 main 함수에 throws 키워드를 사용하면 main함수를 호출하는 JVM이 그 책임을 떠맡게 되는데, 이는 예외를 잘 처리한 것은 아니다. JVM이 예외를 떠맡게 되면, 콘솔에 예외가 발생한 이유를 출력하는 것으로 예외를 처리한다. 에러 메시지는 사용자가 읽기에 불편하기 때문에 개발자가 직접 예외를 처리하는 것이 좋다.

단순히 사용자 입력을 받는 것 치곤 너무 복잡하고 어렵다고 느낀다면 다음의 간단한 방법을 사용할 수도 있다. 바로 Scanner클래스를 이용하는 것이다.

Scanner클래스도 생성자의 입력으로 InputStream의 객체인 System.in을 필요로 한다. Scanner의 내장메서드에는 단어 하나를 입력 받는 next(), 정수를 입력 받는 nextInt(), 문자열을 입력 받는 nextLine()이 있다. String만 받을 수 있는 BufferReader 와 달리 다양한 타입을 받을 수 있고 사용법도 훨씬 간단하다. 다만, 실행시간 차이가 두 배 가까이 난다고 한다. 되도록이면 BufferReader를 쓰는게 효율적이겠으나, 간단한 프로그램이라면 코드의 가독성을 높이기 위해 Scanner를 사용할 수 있다.

2. 파일 입출력

1) 파일 쓰기

① FileOutputStream

  • 자바에서 파일을 생성하려면 FileOutputStream을 사용하면 된다. 아래의 예시는 D 드라이브에 out이라는 텍스트 파일을 생성한다.

  • 참고로 ouptut.close라는 써도 되고 안 써도 되지만 쓸 것을 권장한다. 이 명령은 우리가 사용한 output파일을 닫아주는 명령으로 더 이상 사용하지 않겠다는 의미이다.
  • 물론, 자바프로그램이 종료되면서 자동으로 파일 객체를 닫아 주긴 하지만 다시 사용할 경우 오류가 발생할 수 있으므로 주의해야 한다. 특히, 파일을 수정한 결과를 출력하는 등의 경우에는 변경사항이 잘 적용될 수 있도록 반드시 닫아주어야 한다.

이번에는 파일에 내용을 적어보자.

  • FileOutputStream은 OutputStream클래스를 상속받기 때문에 바이트 단위로 데이터를 처리한다. 따라서 값을 써주기 위해 write메서드를 사용하려면 String을 byte배열로 바꿔주어야 한다.
  • 눈치 챘을지 모르지만 여기에도 예외처리를 해주었다. IOException은 Checked Exception이기 때문에 처리해주지 않으면 컴파일이 불가하다고 설명했다. 이 내용이 파일 입출력에서도 동일하게 적용된다.
  • 참고로 \r은 엔터를 치는 효과를 갖는다. 물론 \n만해도 줄바꿈이 되지만, 가끔 줄바꿈이 제대로 표시되지 않는 경우가 있어 \r\n을 사용하여 이러한 오류를 방지할 수 있다.

② FileWriter

  • 문자열을 파일에 쓸 때에는 FileOutputStream이 좀 불편하다. 위 예시에서도 알 수 있듯 String을 byte배열로 변환해야 하기 때문이다. 이 때, FileWriter를 사용하면 변환 없이 바로 사용이 가능하다.

    ③ PrintWriter
  • FileWriter에 개행문자 \r\n을 사용하는 것조차 개선하고 싶다면 PrintWiter를 쓸 수 있다. 개행문자를 대신하여 우리에게 익숙한 println함수로 파일에 문자열을 작성할 수 있다.

  • 쉽게 말해 콘솔에 출력할 내용을 파일에 출력하도록 redirection한 것이라 할 수 있다.

④ 파일에 내용 추가하기

  • 위의 예제를 학습하다 보면 알 수 있겠지만, 위의 코드를 계속 실행한다고 해서 문장이 계속 덧붙여지지는 않는다(수정만 된다).
  • 기존 내용은 그대로 두고 내용을 덧붙여야 할 때에는 추가모드로 파일을 열어야 한다.
  • PrintWriter에서는 덧붙임 기능을 지원하지 않기 때문에 FileWriter를 사용해야 한다. FileWriter의 두번째 파라미터에 true를 넣어주면 append모드가 되어 기존 내용에 덧붙여진다.
  • 사실 PrintWriter를 사용하는 방법이 있기는 한데, 별로 선호하는 방식은 아니다. 그 이유는 PrintWiter의 생성자 입력으로 FileWriter의 객체를 전달해야 하기 때문이다.

2) 파일 읽기

FileInputStream클래스를 이용하여 파일을 읽을 수 있다. 다만 byte배열을 이용하여 파일을 일기 때문에 길이를 지정해주어야 한다는 단점이 있다.

b의 값을 출력할 때 문자열로 변환하여 출력하기 위해 즉시 반환 객체의 생성자 입력으로 b를 넣어주었다. 고정 길이를 지정해야 하고, String으로 변환도 해야 하는 문제를 해결하기 위해 BufferReader와 FileReader를 사용할 수 있다.

BufferReader 의 내장메서드인 readLine은 더 이상 읽을 Line이 없으면 null을 반환하기 때문에 이를 이용하면 길이를 지정해줄 필요가 없어진다.

profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글