본문 바로가기

Java/Java 2

[Java] 5. 자바 중급 활용

반응형

1. 예외처리

컴파일 에러런타임 에러

컴파일 에러 : 소스코드 작성과정의 에러

런타임 에러 : 프로그램 로직 상의 문제나 실행 중 부적절한 데이터 혹은 객체 참조로 인해 발생

 

try~ catch 블럭

특정 클래스의 메서드를 사용할 때 컴파일러에 의해 작성이 요구된다.

통합개발도구는 컴파일 이전에 미리 관련해서 처리해야 한다고 알려주고 기본 코드도 자동 생성

 

예외 처리 클래스는 java.lang.Exception 클래스  / 사용자 정의 예외 클래스는 Exception 클래스

 

예외처리 유형

Checked Exception

  • Exception 클래스를 상속받아 구현된 클래스
  • 컴파일러에 의해 강제로 예외처리 요구된다.

Unchecked Exception

  • RuntimeException 클래스를 상속받아 구현된 클래스
  • 강제로 예외처리가 요구되지는 않지만 필요에 따라 예외처리 가능

 

 

중복 catch 블럭과 finally 구문

 

중복 catch 블럭 사용하기

사용하는 메서드들이 모두 서로 다른 예외처리를 요구한 경우

try{
	method1();
}
catch (AAAException e){
	...
}

try{
	method2();
}
catch (BBBException e){
	...
}

try 블럭에 메서드를 묶고 catch 블럭을 나열하는 방식

try{
	method1();
    method2();
}
catch (AAAException e){
	...
}
catch (BBBException e){
	...
}

두 개의 예외에 대해 동일한 처리를 위한 묶음 예외 처리

catch (AAAException | BBBException e){
	...
}

부모 자식의 관계인 경우 부모 클래스만 명시해 예외처리

예외처리가 동일한 경우 최상위 클래스인 Exception만 처리해도 동일한 결과

catch(Exception e){
	...
}

 

예외처리 흐름과 처리 내용

try 블럭 안에 있는 코드들이 차례로 실행되며 예외 발생 이후 코드는 실행되지 않고

catch 블럭에서 해당 예외처리를 따르게 된다.

 

일반적인 예외 처리 블럭에서는 예외상황 추적을 위해 e.printStackTrace() 메서드를 이용해 콘솔에 예외상황을 출력, 문제 해결을 위한 도움 메시지로 활용하거나 로그를 이용한 형태로 구현한다.

 

catch(Exception e) {
	e.printStackTrace(e);
    ...
}

finally

예외가 발생한 경우 혹은 발생하지 않은 경우 모두 수행되는 블럭 지정

코드의 성공 / 실패 여부와 상관없이 반드시 실행되는 구문

try{
	method1();
    method2();
}
catch (AAAException e) {
	...
}
catch (BBBException e) {
	...
}
finally{
	...
}

 

예외 던지기와 예외 클래스 작성

만든 메서드에 예외 처리를 강제하는 방법

 

메서드를 만들때 throws 구문을 넣어주면 현재 메서드에서 특정 예외 처리를 하지 않겠다는 의미

즉, try~catch 블랙을 사용해야 한다. (해당 메서드를 호출하는 쪽에서 해당 예외 클래스에 대한 처리)

 

이 방법은 만든 메서드를 호출할 때 특정 예외처리를 강제할 때와, 전용 예외처리 클래스를 활용할 때도 적용

 

public void printData() throws IOException, MyException {

}
  • printData() 메서드 안에서 IOException 을 발생시키는 메서드를 사용할 경우 자체 처리를 하지 않고 호출하는 쪽으로 예외처리를 넘김.
  • 제공되는 예외클래스 이외에 내가 만든 예외클래스 역시 throws 할 수 있음.

 

직접 예외 클래스를 만드는 경우 Exception 혹은 RuntimeException 클래스를 상속받아 구현

해당 예외는 예를 들면 앞의 printData() 메서드내에서 특정상황에 throw MyException 코드를 작성함으로써 예외 코드를 동작하도록 할 수 있다.

public class MyException extends Exception {
    ...
}

 


2. 문자열 다루기

문자로 이루어진 배열로 아스키코드로 변환되어 숫자로 이루어진 구조가 된다.

 

자바에서는 String 클래스가 문자열 타입

 

문자열 생성 및 비교

String 클래스는 불변(immutable) 클래스로,

  • 생성된 String 인스턴스가 갖고 있는 문자열값은 읽어올 수만 있고, 변경할 수는 없다.
  • ’+’ 연산자를 이용해서 문자열을 결합하는 경우 인스턴스내의 문자열이 바뀌는 것이 아니라 새로운 문자열이 담긴 String 인스턴스가 생성됨.
  • 문자열간의 결합등의 작업은 문자열 + 보다는 StringBuffer클래스를 사용.
String s1 = "hello";
String s2 = "hello";
  • “hello” 라는 값을 가지는 새로운 String 클래스의 인스턴스 생성후 s1은 해당 인스턴스 참조.
  • s2는 이미 “hello” 값을 가지는 인스턴스가 있으므로 새로운 객체를 생성하지 않고 s1의 변수 주소 할당.
  • 따라서 s1 == s2 비교는 true.
String s1 = new String("hello");
String s2 = "hello";
  • s1은 새로운 인스턴스 생성 새로운 주소값 할당.
  • 이때 “hello” 값을 가지는 인스턴스가 있다면 해당 인스턴스 주소를 값으로 가짐. 없다면 새롭게 인스턴스 생성후 참조.
  • s2는 이미 “hello” 값을 가지는 인스턴스가 있으므로 새로운 객체를 생성하지 않고 s1의 인스턴스 참조. 단 s2변수의 주소는 새롭게 할당.
  • 따라서 s1 == s2 비교는 false.

비교연산시 같지 않다고 나오는 내용이 아닌 변수의 주소의 주소값을 비교하기 때문

문자열 비교시엔 String 클래스에서 제공하는 equals() 메서드를 사용하는게 맞다.

System.out.println(s1 == s2);   // false
System.out.println(s1.equals(s2))   // true

String 클래스 주요 메서드

 

join(), StringJoiner

join() : 문자열에 구분자를 넣어 결합하거나 분리

String cars = "hyundai,mercedes,bmw";
String[] arr = cars.split(","); // ["hyundai","mercedes","bmw"]
String str = String.join("-", arr);
System.out.println(str);    // hyundai-mercedes-bmw
  • split() 메서드는 StringTokenizer 클래스를 사용해 구현할수도 있음.

StringJoiner 클래스는 문자열 결합을 도와주는 클래스로 형식에 맞게 문자열을 결합하는데 이용

StringJoiner sj = new StringJoiner(",", "[", "]");
String[] carArr = { "hyundai", "mercedes", "bmw" };
for(String s : carArr)
  sj.add(s.toUpperCase());
System.out.println(sj.toString());  // [HYUNDAI,MERCEDES,BMW]

 

trim(), substring(), replace(), toCharArr()

trim() : 문자열 양쪽의 공백 제거 메서드

substring() : 문자열 내용 중 일부만 추출

replace() : 특정 문자를 다른 문자로 대체

String s1 = " Hello World    ";
System.out.println(s1.trim());  // Hello World

String s2 = s1.substring(1,3)   
System.out.println(s1.trim());  // He

System.out.println(s1.replace('l','k'));  // Hekko Workd
Char[] carr = s2.toCharArr();   // ['H','e','l','l','o',' ','W','o','r','l','d',] 공백생략.

문자열 원소의 위치 및 크기 관련

  • indexOf(): 특정 문자가 처음 나오는 위치를 리턴.
  • lastIndexOf(): 특정 문자가 마지막으로 나오는 위치를 리턴.
  • charAt(): 지정된 인덱스의 문자를 리턴.
  • startsWith(): 특정 문자 혹은 문자열 패턴으로 시작하는지 확인.
  • length(): 문자열의 길이를 리턴.

StringBuffer, StringBuilder 클래스 활용

문자열 결합시 새로운 인스턴스가 계속해서 생성 -> for문을 돌면서 지속적으로 문자열의 결합하는 형태는 성능에 영향을 미침

 

이러한 경우엔 StringBuffer와 StringBuilder클래스는 기본적으로 동일한 클래스이고, 문자열을 결합하는 데 유용한 클래스

 

두 클래스의 차이는 멀티스레드에 안전하게 처리되었는지의 차이

StringBuffer는 Thread safe로 멀티스레드 처리에 안전하지만 성능이 저하될 수 있다.

StringBuilder는 멀티스레드 처리가 없는 경우 성능에 유리하다.

StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(" World");
String str = sb.toString();
  • 최종적으로 문자열이 필요한 경우에만 toString() 으로 변환.
  • 서로다른 StringBuffer 인스턴스에 같은 값이 들어 있어도 ==, equals()를 이용한 비교는 false.
  • toString() 으로 변환한다음 equals() 로 비교해야 함.

3. 날짜와 시간 다루기

java.time 패키지의 LocalDate, LocalDateTime 클래스를 사용해 날짜와 시간 등을 다루는 방법

제네릭을 통해 타입을 일반화 하고, 자료구조 프로그래밍에 활용

 

java.util.Date  java.util.Calendar 클래스를 사용했지만 사용에 불편함이 많아

JDK8 이후부터는 java.time 패키지의 클래스로 많은 부분이 대체되었다.

 

날짜를 화면에 표현하기 위해서는 적절한 형식 지정이 필요하므로 형식 지정 클래스도 알아야 한다.

 

날짜, 시간 구하기

LocalDate, LocalTime, LocalDateTime

가장 기본이 되는 클래스들로 각각 날짜, 시간, 날짜와시간 을 다루는 클래스

new()를 통해 직접적인 인스턴스 생성이 불가능 하고 static 메서드를 이용하는 방식을 사용

  • now(): 현재 날짜, 시간에 기반해 인스턴스 생성
  • of(): 인자로 전달되는 특정 날짜, 시간에 기반한 인스턴스 생성
  • atTime(): 특정 시간정보에 기반해 LocalDateTime 인스턴스를 생성
LocalDate d1 = LocalDate.now();
LocalDate d2 = LocalDate.of(2019,10,10);
LocalTime t1 = LocalTime.now();
LocalTime t2 = LocalTime.of(7, 20,20);

4. 제네릭

다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 과정에 타입체크

 

 

ArrayList는 배열과 유사한 자료구조를 제공하는 클래스로 Object 타입의 데이터를 저장

그런데 Object 클래스는 모든 자바 클래스의 슈퍼클래스 이므로 실제로는 모든 자바 클래스가 원소로 들어갈 수 있다

 

매우 편한 구조이기는 하지만 ArrayList 로 부터 참조 원소들을 꺼내 사용할때 타입들이 서로 다를 수 있기 때문에 메서드의 사용등이 차이가 있어 타입 비교를 해야하는 문제가 발생한다.

 

제네릭 사용의 장점

  • 제네릭 클래스 타입의 객체를 생성할때 개발자가 원하는 타입을 지정할 수 있음.
  • 타입 안정성을 제공.
  • 의도하지 않은 타입의 객체가 저장되는 것을 막아 잘못 형변환 되는 오류를 방지.
  • 형변환의 번거로움을 줄여줌. -> 간결한 코드 유지 가능
class Storage<T> {
    T item;
    // getter, setter 생략
}

class App {
    public static void main(String[] args) {
        Storage<String> storage = new Storage<>();
    }
}
  • 타입파라미터를 의미하며 임의의 객체타입 지정이 가능.
  • 타입파라미터는 원시형은 안되고 객체타입만 가능하므로 int 의 경우 Integer 랩퍼클래스를 사용.
  • new 에서는 <> 타입 명시 하지 않아도 됨.(타입 추론 가능)

 

제네릭을 사용할때 주의 할 점은 다음과 같습니다.

  • T는 인스턴스변수로 간주되기 때문에 static멤버에는 타입변수 T를 사용할 수 없음.
  • 지네릭 타입의 배열을 생성하는 것은 불가능.
  • new, instacneof 연산자의 경우 컴파일 시점에 타입T를 명확하게 알아야 하기 때문에 T를 피연산자로 사용할 수 없음.

제네릭 고급 사용

제네릭 메서드

 메서드의 선언에 제네릭이 사용되는 형태

메서드의 인자 혹은 리턴에 제네릭이 사용될 수 있으며 다양한 타입을 처리해야 하는 경우 유용하게 활용 할 수 있다.

 

Storage 를 인자로 하는 메서드

public <T> void print(Storage<T> storage) {
}
  • 인자에 제네릭 클래스를 사용한 경우 메서드 앞에 를 붙여 주어야 함.

리턴타입 역시 제네릭 클래스를 사용할 수 있다

public List<String> getList() {
}

인자와 리턴이 모두 제네릭을 가지는 경우에는 다소 복잡하다.

public <T> List<Character> convert(Storage<T> storage) {
}

 

와일드 카드

제네릭 타입을 사용할때 발생할 수 있는 문제점으로

예로는 특정 제네릭 타입을 인자로 받는 메서드를 구현하는 상황이 있다.

 

앞에서 만든 Storage 를 인자로 하는 메서드

public void print(Storage<String>) {
    ..
}

인자로 String 타입 파라미터를 가지는 Storage 클래스가 지정되어 있기 때문에 Storage 타입은 해당 메서드를 이용할 수 없다

 

제네릭의 경우 클래스 타입 자체는 동일하므로 오버로딩이 적용되지 않는다.

 

따라서 와일드 카드를 사용해 사용할 수 있는 타입에 유연성을 부여하는 방법

public void print(Storage<? extends Storage) {

}
  • ?는 사실상 Object 클래스라고 볼 수 있음.
  • extends , super 를 통해 올수 있는 타입의 관계를 특정할 수 있음.
  • ? extends T : T와 그 자손만 가능.
  • ? super T : T와 그 부모만 가능.
반응형