Exception처리가 없으면 프세스가 실행 시, 예외상황을 맞딱뜨릴 경우, 프로세스가 멈추게 된다. 예외상황을 맞딱드려도 정상적으로 원활한 프로세스의 진행을 위해 Exception을 사용한다.
위의 catch문 안에 있는 e.getMessage()는 예외상황에 대한 메시지를 보기위한 코드이다.
e.getMessage(); 를 많이 사용하기도 하고, 메세지를 자세히 보고자 할때는 e.printStackTrace();를 사용한다.
- Exception의 종류
Exception의 종류는 다양하다.
가장 많이 볼 수 있는 흔한 Exception으로는
ArrayIndexOutOfBoundsException은 일반적으로 배열에서 존재하지 않는 index값에 접근할 경우 발생하는 Exception이며, NumberFormatException은 주로 문자열(String)을 숫자(Number)로 바꿔주려고 할 때, 불가능한 경우에 나타나는 Exception이다.
A에서 걸리면 A만 실행, B에서 걸리면 B만 실행, A도 B도 아니면 C가 실행된다. Exception e에서는 모든것이 실행 되는 것이다.
- finally
try~ catch 를 이용할 경우 try{}에서 문제가 발생하면 catch문이 실행 된다. finally 문은 try와 catch문의 영향 없이 무조건 실행 된다.
try문에서 문제가 발생하지 않아도 finally는 무조건 실행된다. 문제가 발생해도 catch문을 거쳐 finally문이 무조건 실행 된다.
- 예외처리 문법 throws
try~catch문은 예외가 발생했을 때 자체적으로 catch문을 이용해서 해결한다. throws의 경우에는 예외를 발생시킨 호출 쪽으로 예외를 던져버리는 방식이다.
try~catch가 좋다 throws 가 좋다 이런 문제가 아니라 상황에 맞는 처리를 해주어야 한다.
- 자주 발생하는 Exception
예외(Exception)의 종류는 아주 많다. java API Doc을 보면 수십개가 있는것을 확인할 수 있다.
ArrayIndexOutOfBoundsException = 배열을 사용시 존재하지 않는 index값을 호출하면 발생 NullPointerException = 존재하지 않는 객체를 가리킬 때 발생 NumberFormatException = 문자를 숫자로 처리할 때 발생 (보통 숫자로 변경 할 수 없는 문자열을 변경하려고 할때 발생) 등
- DB관련 Exception ClassNotFoundException = 드라이브 이름을 찾지 못했을 때 SQLException: db url,id,pw가 올바르지 않을 때
java.util.Random 클래스는 난수를 생성할 때 seed값으로 '시간'을 이용한다. 따라서, 각각 동일한 시간에 Random 클래스를 이용하여 난수를 생성하게 되면, 동일한 값이 리턴된다. [ deterministic(결정론적)]
java.security.SecureRandom클래스는 이와 다르게 non-deterministic(비결정론적) output을 생산한다. 예측할수 없는 seed를 이용하여 난수를 생성하기 때문이다.
참고 : API document에서 발췌
[Unlike the java.util.Random class, the java.security.SecureRandom class must produce non-deterministic output on each call. What that means is, in case of java.util.Random, if you were to recreate an instance with the same seed each time you needed a new random number, you would essentially get thesameresult every time. However, SecureRandom is guaranteed to NOT do that - so, creating a single instance or creating a new one each time does not affect the randomness of the random bytes it generates
Additionally, SecureRandom must produce non-deterministic output and therefore it is required that the seed material be unpredictable and that output of SecureRandom be cryptographically strong sequences as described in RFC 1750: Randomness Recommendations for Security.]
다음은 각각 Random과 secureRandom 을 이용하여 1부터 100이하의 난수를 생성하는 예제이다.
예제 코드를 보면 첫째 줄에서 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();
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를 재정의 해줌으로써 두 객체가 같다고 보도록 만든것이다.
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";
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, 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값을 넣어 주어야지만, 로직이 끝났을때 타이머 객체가 함께 종료된다는 것을 알아두면 된다.