⇒ 고로 try-catch 나 throws / throw 를 사용해서 예외처리 해야함
Exception 을 상속NullPointerException 과 같은 예외 자주 발생 → null 여부 확인해야 한다. 등RunTimeException 을 상속(RunTimeException은 Exception을 상속)try : 예외가 발생할 수 있는 코드 작성catch : 예외가 발생할때의 대처 작성finally : 예외 발생여부 상관없이 반드시 실행(생략가능)public class ExceptionTest {
public void ex() throws RuntimeException {
System.out.println("예외 날라간드아~ㅋㅋ");
throw new RuntimeException("쿠쿠루 삥뽕");
}
}
public class ExMain {
public static void main(String[] args) {
try {
ExceptionTest ex = new ExceptionTest();
ex.ex();
}catch (RuntimeException e) {
System.out.println("에러 발생!!"+e);
}finally {
System.out.println("생략 안해주셔서 감사합니다..");
}
}
}
/*
출력
예외 날라간드아~ㅋㅋ
에러 발생!!java.lang.RuntimeException: 쿠쿠루 삥뽕
생략 안해주셔서 감사합니다..
*/
Exception 이나 RunTimeException클래스를 상속받아 생성catch(GotWeakerException e) {
throw new FailLiftException("수행을 실패했습니다.",e);
public class FailLiftException extends Exception {
public FailLiftException (String message, Throwable cause) {
super(message, cause);
}
}
cause에 이 예외가 발생한 원인 예외를 넣어주면 연쇄적인 예외처리가 가능하다.
Throwable을 상속 받기에 try-catch 사용가능하지만 치명적인 문제이기에 조취를 취해야함 ⇒ 즉, 잡지마라⇒ 컴파일 시점에 타입이 결정되는 가변적인 타입 매개변수
한마디로 어떤 참조 자료형(클래스 타입)이 들어올지 모르기에 일단 뭐든 들어올 수 있게 열어 놓고 있다가, 실제 객체가 특정 타입으로 생성될 때 그 타입으로 결정되는 것 (양자역학인가..?)
기본적으론 제네릭의 이름에 제약사항은 따로 없다. 사실 변수 이름 지을 때처럼 마음대로 정해도 된다. 하지만 다른 사람을 배려하는 마음으로 통상적인 네이밍을 따르는 것도 바람직 할 것 같다. (보통 대문자 알파벳 문자를 사용한다.)
<T> : Type<E> : Element<K> : Key (Map)<V> : Value (Map)<N> : Number<R> : 리턴 타입(메서드에서 자주 사용)<U> , <S> : 제네릭 타입 여러개 일대 보조적 으로 사용public class GenericBox<T> {
public T stuff;
public GenericBox(T stuff) {
this.stuff = stuff;
}
public T whatHave() {
System.out.println("I have " + stuff);
return stuff;
}
}
public class Main {
public static void main(String[] args) {
GenericBox<String> strBox = new GenericBox<String>("Tools");
GenericBox<Integer> intBox = new GenericBox<>(1234);
GenericBox<Boolean> boolBox = new GenericBox<>(true);
strBox.whatHave();
intBox.whatHave();
boolBox.whatHave();
}
}
/*
출력
I have Tools
I have 1234
I have true
*/
이런식으로 GenericBox의 필드타입을 제네릭을 사용하여 객체가 생성될때 필드의 타입이 결정된다.
참고로 제네릭 클래스의 인스턴스를 생성할 때
Box<String> myBox = new Box<String>();
이건 당연히 되겠지만
Box<> myBox = new Box<String>();
이러면 오류발생 가능(컴파일러가 좌변의<>를 보고 우변의 <>을 추론하기 때문에)
Box<String> myBox = new Box<>();
고로 이건 오류가 안남.
그렇다면 필드값이 Static이어도 제네릭이 사용가능 할까?
답은 안된다.
이유는 크게 두 가지다.
static변수는 객체 생성 없이 접근이 가능static 변수는 클래스 로드 시점에 생성 된다. 제네릭은 객체가 생성될 때 결정된다 → 충돌발생 ⇒ 한마디로 static은 모든 인스턴스가 공유하지만, 제네릭 타입은 각 객체마다 다를 수 있으므로!!Object 또는 특정 상위 클래스로 변환 ⇒ 클래스 수준에서 static 변수를 정의할 방법이 없다. (런타임에는 제네릭이 존재 X, ) 즉, static 변수는 클래스 로드 시점에 메로리에 로딩되어 런타임까지 유지되어야한다. 근데, 제네릭은 타입 소거로 인해서 런타임에서 타입 정보가 사라짐(Object)근데 또 static 메소드는 된다. 뭘까 이 일관되지 않은 규칙은..
이유를 알아보자.
한마디로 Static 메서드의 제네릭 타입은 메서드가 호출될 때 결정되기 때문이다.
static변수와 마찬가지로 static메소드의 정의가 클래스 로드 시점에 메서드 영역에 저장된다.
하지만 static변수와 다르게 static메소드는 이 시점에서 제네릭의 타입이 결정 되지 않고 (static변수는 저장될때 타입이 결정되어야 하지만, static메소드는 호출 될때 결정해도 된다.)
⇒ 메소드가 호출될 때마다 다른 제네릭 타입을 사용할 수 있기때문에 제네릭 사용이 가능하다.
public class GenericBox<T> {
public static <T> void whatHave(T t) {
System.out.println("I have " + t);
}
}
public class Main {
public static void main(String[] args) {
GenericBox.whatHave("Hello");
GenericBox.whatHave(1234);
GenericBox.whatHave(false);
}
}
/*
출력
I have Hello
I have 1234
I have false
*/
앞으로의 설명을 위해 아래와 같은 상속(구현) 관계가 있다고 생각 해보자
interface A
class B implements A
class C extends B
class D
extends 키워드를 사용하여 특정 클래스나 인터페이스를 상속하거나 구현한 타입만 허용
예를 들어
class UpperBoundedClass <T extends B>
이렇게 제한한다면 T는 B를 상속하는 타입만이 올 수 있습니다.(B포함)
⇒ B, C가 올 수 있음.
super 키워드를 사용하여 특정 클래스의 상위 타입만 가능
class LowerBoundedClass <T super B>
⇒ B의 상위 타입인 B, A타입이 가능(B도 포함)
&를 사용하여 두개 이상의 제약을 걸 수 있다.(| 은 안됨!!)
class MultiBoundedClass <T extends B & A>
T 에는 B를 상속함과 동시에 A를 구현한 타입만이 올 수 있음.
⇒ 클래스는 하나만 가능하다(자바에선 다중 상속이 안되기에..)
? 는 제네릭 타입을 사용할 때, 어떤 타입이 올지 모를 겅우 유연하게 처리하는 기능public class WildCard {
public static void printList(List<?> list){
for (Object o : list) {
System.out.println(o);
}
}
}
public class WildCardMain {
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
strList.add("Hello");
strList.add("World");
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
WildCard.printList(strList);
WildCard.printList(intList);
}
}
/*
출력
Hello
World
1
2
*/
위처럼 printList의 매개변수를 List<?> 는 List의 타입이 무엇이든 받을 수 있다.
| 구분 | 제네릭 | 와일드카 |
|---|---|---|
| 타입 결정 | 메서드 호출 시 타입을 지정해야 함 | 어떤 타입이든 받을 수 있음 |
| 사용 목적 | 특정 타입을 강제 | 여러 타입을 유연하게 처리 |
| 타입 안정성 | 타입이 정해져 있어서 안전 | 내부적으로 Object로 처리 |
제네릭과 유사하게 와일드카드의 범위를 제한할 수 있다.
? (Unbounded Wildcard)extends 키워드를 사용하여 제네릭과 동일하게 상속하는 하위 타입만 허용<? extends Number> : Number와 그 하위 타입만 허용(Number를 상속하는)super 키워드를 사용하여 상위 타입만을 허용<? super Integer> : Integer와 그 상위 타입만 허용Optional 객체를 사용하면 null처리를 안전하고 우아하게 가능(정적 팩토리 메서드 사용하여 객체 생성)
new 키워드로 객체를 만드는 대신 static메서드로 객체를 생성하게 하는 메소드null을 감싸는 객체 제공 → null을 직접 다루지 않고 안전한 연산 가능!Optional<String> opt1 = Optional.of("Hello"); : 반드시 값이 있어야 함Optional<String> opt2 = Optional.ofNullable(null); : 값이 null일 수도 있다.Optional<String> op3 = Optional.emtpy(); : 빈 객체 생성import java.util.Objects;
public class Optional<T> {
private final T data;
private Optional(T data) { //private 생성자이므로 외부에서 접근 불가
this.data = data;
}
public static <T> Optional<T> empty() { //빈 객체 반환
return new Optional<>(null);
}
//값이 있는 객체를 반환 (requireNonNull : Null이면 예외 발생)
public static <T> Optional<T> of(T value) {
return new Optional<>(Objects.requireNonNull(value));
}
// null이면 빈 객체 반환, 아니면 그 값 담긴 객체 반환
public static <T> Optional<T> ofNullable(T value) {
return value == null
? new Optional<>(null)
: new Optional<>(value);
}
// 객체에 담긴 값 확인 -> null이면 예외 발생(커스텀 가능!)
public T get() {
if ( data == null ){
throw new NoSuchElementException("No value present");
}
return data;
}
// 값 있음?
public boolean isPresent() {
return data != null;
}
// 빔?
public boolean isEmpty() {
return data == null;
}
}