자바 API 6 [HashMap, HashSet, Iterator]

 

 Map계열의 자료구조는 List계열과 달리 인덱스가 없고, 키와 값만 있다.
그리고 당연한 얘기지만, 키는 유니크 해야 한다. 

- HashMap

키와 값으로 이루어져 있으며, 키는 유니크 해야하고, 값은 중복되어도 상관없다.
 key값으로 컨트롤 되는 컬렉션 클래스이기 때문이다.


HashMap<Integer, String> hashMap = new HashMap<Integer, String>();

hashMap.put(0, "str0");
hashMap.put(1, "str1");
hashMap.put(2, "str2");
hashMap.put(3, "str3");

System.out.println(hashMap.toString());

String str = hashMap.get(2);
System.out.println(str);

hashMap.remove(2);
System.out.println(hashMap.toString());

hashMap.clear();
System.out.println(hashMap.toString());

예제 코드를 보면
첫째 줄에서 hashMap변수를 선언 및 초기화할 때, key와 value의 타입을 지정해주었다.
HashMap다음 꺽새('<', '>')안에 첫번째로 오는 것이 key가 될 값의 타입을 지정해 준 것이며,
두번째로 오는 것이 value의 타입을 지정해 준것이다.

따라서 세번째 줄을 분석하자면, 
hashMap에 "str0"문자열을 저장하면서 그 값의 '키'값으로 숫자 0을 할당시키고 있다. 

해당 "str0"문자열을 hashMap변수로부터 가져오려면 아래와 같이 키값을 이용해 가져오면된다.
hashMap.get(0);    // * 여기서 0은 배열이나 list계열 컬렉션에서 쓰이는 인덱스(index)가 아니다. 


HashMap에서 기본적으로 사용되는 메소드들은 다음과 같다.
hashMap.put(key, value);
hashMap.get(key);
hashMap.remove(key);
hashMap.clear();

각 메소드에 대한 결과는 위 예제코드에 대한 결과를 통해 확인할 수 있다.

{0=str0, 1=str1, 2= str2, 3=str3}
str2
{0=str0, 1=str1, 3=str3}
{}

 

 


- Iterator

Iterator는 반복자이다.  데이터를 반복적으로 검색하는데 아주 유용한 인터페이스 이며, 
모든 자료구조형은 iterator()메소드를 지원하고 있다.

예제 코드를 보면 쉽게 이해할 수 있다.


HashMap<Integer, String> hashMap = new HashMap<Integer, String>();

hashMap.put(0, "str0");
hashMap.put(1, "str1");
hashMap.put(2, "str2");
hashMap.put(3, "str3");
System.out.println(hashMap.toString());

// Iterator 시작부
Iterator<Integer> iterator = hashMap.keySet().iterator();
while(iterator.hasNext()) {
  String str = hashMap.get(iterator.next());
  System.out.println(str);
}

Iterator안에 데이터는 Integer형이 들어가며 hashMap의 keySet()으로 부터 데이터를 얻어온다.
hashMap의 keySet()은 0,1,2,3

예제코드에서 10번째 줄은 hashMap의 keySet()을 반복자(iterator)로 지정하여 Iterator 객체를 생성및 선언하는 부분이다.

> Iterator의 메소드
1) hasNext() 메소드는 반복자의 다음 순번 값의 존재 여부를 반환한다.
    다음 순번 값이 없으면 false를 반환하므로 반복문은 종료된다.
2) next() 메소드는 다음 순번의 값을 반환한다. 

따라서 위의 예제코드를 실행하면 결과는 다음과 같다.

{0=str0, 1=str1, 2= str2, 3=str3}
str0
str1
str2
str3

 

 


- HashSet

Set계열 자료구조에서는 데이터의 순서는 없다. 지만 중복된 데이터는 허락하지 않는다.

 


HashSet<String> hashSet = new HashSet<String>();

hashSet.add("str0");
hashSet.add("str1");
hashSet.add("str2");
hashSet.add("str3");
hashSet.add("str2");
System.out.println(hashSet.toString());

hashSet.remove("str0");
System.out.println(hashSet.toString());

int i = hashSet.size();
System.out.println("사이즈 : "+ i);

결과는 아래와 같다.
{str3, str1, str2, str0}
{str3, str1, str2}
사이즈 : 3

순서가 없으므로 add() 메소드를 이용해 데이터를 넣는 것과 상관없이, 데이터가 뒤죽박죽 들어간다.

위의 예제에서는 HashSet이 기초자료형인 String타입으로 만들어져 똑같은 문자열"str2"을 두번 add할 때, 자동으로 걸려졌으나 객체자료형으로 HashSet을 사용할 때는 주의가 필요하다.

 

 

 

이미지를 보면, 이름이 이순신이고 학년이 6인 학생(객체자료형)을 HashSet에서 remove메소드를 이용하려 제거 하려고 하였으나 지워지지 않은 것을 확인할 수 있다.

이는 처음에 hashSet변수에 add메소드를 이용하여 만들어진 "이순신"학생과
remove하기 위해 새로 new 생성자를 이용하여 만들어진 "이순신"학생은 서로 다른 주소값을 가지고 있기 때문이다.

때문에 다음과 같은 방법을 이용한다.

 

 

 

우측의 빨간 네모상자 안과 같이 equals와 hashCode를 내가 만든 객체에 오버라이드 해준다.
그러고 나면 remove메소드 호출 시 정상적으로 제거되는 것을 확인할 수 있다.

equals에서는 새로 들어온 object의 toString값과 현재 가지고 있는 객체의 toString값을 비교하여 return하고, 
hashCode는 해당 객체의 hashCode를 정수형으로 뽑아주는 역할을 한다.
hashCode와 equals를 재정의 해줌으로써 두 객체가 같다고 보도록 만든것이다. 

 

 

'Java' 카테고리의 다른 글

자바 예외처리 [Exception]  (0) 2020.01.22
자바 API 7 [Random과 SecureRandom의 차이]  (0) 2020.01.21
자바 API 5 [InputStream,OutputStream]  (0) 2020.01.21
자바 API 4 [StringTokenizer]  (0) 2020.01.20
자바 API 3 [Timer]  (0) 2020.01.20

- 입출력 API [InputStream,OutputStream]

 

 

InputStream, OutputStream이 월등히 많이 쓰인다.


1. InputStream사용법


1) InputStream(추상)클래스를 이용해서 객체를 만든다. 또는 다른 클래스의 메소드에서 반환되는 타입 객체를 얻는다.

2) read() 메소드를 이용해서 데이터를 읽는다.
3) read(), read(byte[]) 두 개의 메소드를 이용할 수 있다.

read() - 1byte 씩 읽는다. 속도가 느리다. 
read(byte[]) - byte[] 만큼씩 읽는다. 속도가 빠르다.

InputStream 하위에는 
FileInputStream
ByteArrayInputStream
ObjectInputStream
등등 있음.

 

 

 

2. OutputStream 사용법


1) OutputStream(추상)클래스를 이용해서 객체를 만든다. 또는 다른 클래스의 메소드에서 반환되는 타입 객체를 얻는다.
2) write() 메소드를 이용해서 데이터를 읽는다.
3) write(), write(byte[]), write(byte[], int, int)세 개의 메소드를 이용할 수 있다.
4) write(byte[], int, int)는 데이터를 원하는 위치에서 원하는 숫자만큼 쓸 수 있다.

 

 

 

3. 예외처리와 무조건 close() 실행

 


1) I/O를 하면서 반드시 해야 하는 예외처리가 있다. IOException이다. (위 예제에서는 Exception으로 함. 얘는 모든 예외가 다 타는 놈이니까 가능.)
2) I/O작업 마지막은 close()로 외부연결을 끝내야 한다.

 

다음과 같이 finally문은 cath문이 실행되도 무조건 실행되는 부분이기 때문에 finally 안에서
os 객체가 더이상 null이 아니면 os.close()메소드를 실행해준다.
이때도 역시나, I/O관련 작업이기 때문에 try~ catch문 필수 !

 

 

4. FileInputStream과 FileOutputStream을 이용한 파일 복사.

 

5. DataInputStream, DataOutputStream > 문자열 읽고, 쓰기

byte단위로 문자열을 처리하는 InputStream, OutputStream보다 편리하게 고안된 클래스이다.
DataInputStream의 readUTF()메소드를 이용하면 byte씩 읽어올 필요 없이 한번이 읽어올 수 있다.

 

파일 업로드 시 파일명 가져올 때 FilenameUtils

String filleName ="javain.txt";


String extension = FilenameUtils.getExtension(fileName); // txt
String fileName = FilenameUtils.getBaseName(fileName); // javain
String fileName = FilenameUtils.getName(fileName); // javain.txt

 

 

- StringTokenizer

 

StringTokenizer 클래스는 문자열을 분할할 때, 대표적으로 사용된든 클래스이다.

사용법은 쉬우므로 바로 예제를 보면

 


public static void main (String args[]){
  str1 = "오늘 날씨는 춥고, 눈이 많이 오고 있습니다.";
  str2 = "2017/12/10";

  StringTokenizer tokenizer1 = new StringTokenizer(str1);
  StringTokenizer tokenizer2 = new StringTokenizer(str2, "/");

  System.out.println("str1 문자열 수 : " + tokenizer1.countTokens());
  System.out.println("str2 문자열 수 : " + tokenizer2.countTokens());

  while(tokenizer1.hasMoreTokens()){
    System.out.println(tokenizer1.nextToken());
  }
 
  while(tokenizer2.hasMoreTokens()){
    System.out.println(tokenizer2.nextToken());
  }
}

위 코드를 실행하면 결과는 다음과 같다.

 

str1 문자열 수 : 7
str2 문자열 수 : 3
오늘
날씨는
춥고,
눈이
많이
오고
있습니다.
2017
12
10

 


StringTokenizer 클래스는 다음과 같이 객체 생성시, 문자열과 분리자(token)를 설정해줄 수 있다.

 

 

StringTokenizer tokenizer1 = new StringTokenizer(str1);
StringTokenizer tokenizer2 = new StringTokenizer(str2, "/");

 

첫 번째 줄을 보면 StringTokenizer객체 생성시 파라미터로 문자열만 보내주고 있는데,
이 경우 문자열의 분리자 역할은 자동으로 공백(space)이 하게 됩니다.

두 번째 줄을 보면 파라미터로 문자열과 함께 "/"문자를 전달하고 있는데,
"/" 문자를 분리자로 설정하는 것입니다.

StringTokenizer에서 많이 쓰이는 메소드들은 다음과 같습니다.

countTokens()       : 토큰(분리된 문자열)의 갯수
hasMoreTokens()  : 다음 순번의 토큰이 있는지 여부
nextToken()           : 다음 토큰을 가져온다

 

 

 

- Timer 클래스

일정한 시간 간격을 두고 어떠한 일을 수행시키고자 할 때 사용하는 클래스이다.

Timer, TimerTask 클래스
Timer객체는 일정한 시간이 되면 TimerTask 객체가 작동 된다.
단, TimerTask클래스는 추상 클래스이다. 따라서, TimerTask클래스를 상속받는 클래스를 만들어서 사용해야 한다.


import java.util.TimerTask;

public class ExTimerTask1 extends TimerTask{

  @Override
  public void run(){
    System.out.println("ExTimerTask1 : 이곳의 작업이 실행됩니다.");
  }
}

import java.util.TimerTask; 

public class ExTimerTask2 extends TimerTask{ 

  @Override 
  public void run(){ 
    System.out.println("ExTimerTask2 : 이곳의 작업이 실행됩니다."); 
  } 
}

다음은 TimerTask를 상속받는 ExTimerTask1 클래스와 ExTimerTask2 클래스이다.

TimerTask 추상클래스를 상속받게되면 꼭 재정의해서 써야하는 run() 메소드가 하나 있는데,
이것은 어떠한 일정 시간이되면, 바로 수행을 하게 될 로직이 들어가는 메소드이다.

 


import java.util.Timer;

public class TimerEx {
  public TimerEx() throws InterruptedException {

    System.out.println("^^");
    Timer timer = new Timer(true);
    TimerTask t1 = new ExTimerTask1();  //JVM(자바가상머신)이 컴파일 시점에 default생성자를 이용해 객체를 생성
    TimerTask t2 = new ExTimerTask2();

    timer.schedule(t1, 2000);  // 2초후 실행
    timer.schedule(t2, 10000); // 10초후 실행

    Thread.sleep(11000);      // 11초 동안 작업을 잠시 중단하고 다음 라인부터 계속 실행
    System.out.println("**");
  }
}

timer.schedule메소드를 실행할 때, 실행시킬 객체와 시간을 설정해주는데
이때 시간은 천 단위로 초를 입력한다.

Thread.sleep(11000); 을 걸어준 이유는, TimerEx객체가 실행되서 처음부터 수행하는데 로직 자체가 먼저 끝나버리는 것을 방지하기 위해 걸어주었다. 
Thread에 대해서는 이후 포스팅에서 자세히 다룰 것이다.

결과는
시작과 동시 "^^" 문자열이 출력되고,
2초 후에 "ExTimerTask1 : 이곳의 작업이 실행됩니다." 문자열이 출력되고,
그로부터 8초후(합이 10초 후)에 "ExTimerTask1 : 이곳의 작업이 실행됩니다." 문자열이 출력되고,
마지막으로 그로부터 1초후(합이 11초 후)에 "**" 문자열이 출력되면서 종료된다.

Timer timer new Timer(true); 에서 타이머 객체 생성시 "true"를 넣어준 이유는
프로그램이 실행되는 데몬이 모두 끝나면 종료시키기 위해서 넣어준 것으로,
true를 설정해 주지 않으면 로직이 다 끝난 뒤에도 타이머 객체가 계속 메모리에 상주를 하게되는 상황이 발생하게 된다.

깊이 알면 어려우므로 타이머 객체 생성시 true값을 넣어 주어야지만, 로직이 끝났을때 타이머 객체가 함께 종료된다는 것을 알아두면 된다.

 

 

 

개발을 할 떄 가장 많이 쓰이는 기능 중에 하나는 아마도 랜덤 숫자를 구하는 작업 일 것이다.
예전에는 Math.random()을 많이 이용하였으나, 요즘은 Random클래스를 많이 이용한다.


- Math.random()과 Random 클래스의 차이

> Math.Random()

Random()메소드는 return 타입이 double형으로, double 타입의 임의의 수를 발생한다.
가이드를 보면 static이고, 변환 타입이 double인 것을 확인할 수 있다.

 

Random()메소드는 static으로 선언되었기 때문에 위와 같이 객체 생성 절차 없이
double d = Math.random();
바로 Math의 Random()메소드를 실행시킴으로써 랜덤 숫자를 구할 수 있다.

하지만, return 타입이 double형 이기 때문에 결과는 0.xxx... 과 같은 소수값으로 반환된다.
따라서 결과값에 '*10 '(곱하기 10) 을 해주는 작업이 필요하다.


double d = Math.random();
System.out.println(d);

int di = (int)(d*10);
System.out.println(di);

다음과 같이 결과에 10을 곱해주고, int형으로 명시적 형변환을 해준다음 사용해야 한다.


결과: 
0.59656663..(랜덤값)
5

 


> Random 클래스

Random 클래스는 많이 쓰이는 클래스이고 return 타입이 int이므로 사용법이 아주 간단하다.

Random클래스는 Math 클래스와 다르게 직접 new생성자를 이용하여 객체생성 후 사용한다.
또한 return 타입이 int이다.

위 이미지를 보면 Random클래스의 nextInt메소드는 int형으로 bound값을 파라미터로 받는다.
따라서, random.nextInt(100);  을 실행시키면
1부터 100이하의 정수를 return 해준다.


Random random = new Random();
int i = random.nextInt(100);
System.out.println(i);

결과:
42(랜덤값)

 

 


- Scanner 클래스

키보드에서 타이핑하는 문자열 또는 입출력 프로그래밍에서 문자열을 읽어올때 사용된다.

Scanner scanner new Scanner(System.in);

다음은 선언부이다.
new생성자를 이용해 Scanner 객체를 생성해주면서 매개변수로 "System.in"을 넣어주어야 한다.

int i = scanner.nextInt();            //정수를 입력받는다.
String str = scanner.next();        //문자열을 입력받는다.
String strln = scanner.nextLine();  //'개행문자(\n)'를 만나기 전까지 문자열을 입력받는다.

 

 


- Wrapper 클래스

Wrapper클래스는 기초데이터를 객체데이터로 변화시키는 클래스이다. 

 


Integer integer = new Integer(10);

int i = 10;

 

위 두줄은 결과적으로 같은 코드이지만 위에는 객체(Object)자료형이고 밑에는 기초자료형이다.
위 이미지를 보면 객체자료형을 기초자료형으로 변환시켜 넣어주기위해 
return 타입이 int인 integer.intValue()메소드를 이용하는 것도 확인할 수 있다.

 

 

 

- String

String은 객체 자료형이나 그 선언및 사용에 있어서 아래와 같이 기본자료형 처럼 사용한다.
String string = "Helloworld~";

객체 자료형 임에도 뒤에 생성자(new)를 붙이지 않고 기초데이터를 만들 때 처럼 초기화 하고 있다.
이유는 아마도 많이 쓰이는 객체라서 편하게 사용하라고 이렇게 만든 것 같다.


그래서 아래 두 경우 모두 사용할 수 있다.
String string = "HelloWorld~";
String string = new String("HellowWorld~~");

 

 

다음은 주요 String관련 메소드들의 실행결과이다.


String str1 = "abcdefg";
String str2 = "HIJKLMN";
String str3 = "opqrstu";

System.out.println(str1.concat(str2));  // abcdefgHIJKLMN
System.out.println(str1.substring(3));  // defg
System.out.println(str1.length());      // 7
System.out.println(str1.toUpperCase()); // ABCDEFG
System.out.println(str2.toLowerCase()); // hijklmn
System.out.println(str1.charAt(3));     // d
System.out.println(str1.indexOf('c'));  // 2

String str4="abcdefg";
System.out.println(str1.equals(str4));  // true

string str5="abc def ghi";
System.out.println(str5.trim());        // abcdefghi

System.out.println(str1.replace('a','Z')); // Zbcdefg
System.out.println(str1.replaceAll('abcd','ZZZZZ')); // ZZZZZefg
System.out.println(str1);               // abcdefg

- String의 문제점

메모리 과소비


예를들어
String str1 = "ABC"
str1에 "DEF" 문자열을 더 붙이는 경우
기존에 str1이 가리키고 있던 메모리 주소값에 있는 "ABC"에 + "DEF"를 갖다 붙이는게 아니라

메모리 주소를 새로 따서 "ABCDEF"를 저장하고 , 기존에 "ABC"값을 가지고 있던 메모리 주소는 무시한다. 무시된 메모리는 가비지콜렉터가 나중에 회수를 진행하지만, 회수 되기 전까지는 메모리가 사용되고 있으므로 회수전까지는 메모리가 불필요하게 소비되는것이다.

그래서 String클래스의 대안으로 StringBuffer와 StringBuilder가 등장했다.

- StringBuilder

StringBuilder와 StringBuffer는 제공하는 메서드들도 같으며 String과 달리 메모리에 값을 append하는 방식으로 동작 또한 동일하다. 

 

한 가지 중요한 차이점이 있는데,

멀티 스레드 환경에서 StringBuilder클래스는 여러 스레드들이 접근 가능하고

StringBuffer클래스는 멀티스레드 환경에서 더 안전하다고 한다. 

StringBuffer는 동기화를 처리해주지만, StringBuilder는 동기화를 처리해주지 않는다는 것이다.

 

다음은 주요 StringBuffer, StringBuilder관련 메소드들의 실행결과이다.


StringBuilder str1 = new StringBuilder("abcdefg");

str1.append("hijklmn"); // abcdefghijklmn
str1.insert(3, "AAA");  // abcAAAdefghijklmn
str1.delete(3, 5);      // abcAdefghijklmn  (3번째 자리에서 5번째 자리 앞까지 지워라)
str1.deleteCharAt(10);  // abcAdefghiklmn

 


날짜(Calendar) API

날짜와 시간을 표현할 때 많이 쓰이는 Calendar클래스이다.


Calendar calendar = Calendar.getInstance();
// 위 calendar 객체를 뽑아내는것을 보면 싱글턴 패턴으로 짜여진 것을 예상할 수 있다.
// new를 이용하는 객체생성없이 getInstance를 이용해 객체 변수를 뽑아내고 있다.

int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1; //월은 0부터 시작하므로 1을 더해줘야 함
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);

System.out.println(year+"년 " + month + "월 " + day + "일");
System.out.println(hour+"시 " + minute + "분 " + second + "초");

 

int[] A = {1,3,2,5,4};

 

배열 A가 있을 때,

1. 오름 차순 정렬 방법. 

 

Arrays.sort(A);

 

A의 결과는 {1, 2, 3, 4, 5}가 된다. 

 

 

2. 역정렬인 내림 차순 정렬 방법.

Arrays.sort를 호출할 때 두번 째 인자로 Collections.reverseOrder() 메서드를 넘겨주면 되지만, 

이때, 첫번째 인자의 배열을 Primitive(기초) Type으로 넘기려고 하면 Error가 발생한다. 

 

Integer[] A = {1,3,2,5,4};

 

Arryas.sort(A, Collections.reverseOrder());

 

A의 결과는 {5,4,3,2,1} 이 된다. 

 

----------------------------------------------------

ArrayList 객체의 정렬 

ArrayList의 경우 Collections.sort();로 정렬 가능. 

 

+ Recent posts