지네릭스
- 컴파일시 타입을 체크해 주는 기능
public class GenericTset{
public static void main(String[] args){
// ArrayList list = new ArrayList();
ArrayList<Integer> list = new ArrayList();
list.add(10);
list.add(20);
// list.add("30");
list.add(30); //컴파일 처크
// 컴파일 OK, But 실행시 에러발생
// Integer i = (Integer)list.get(2);
System.out.println(list);
}
}
장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.
RuntimeException을 compile단계에서 잡을 수 있도록 하는게 좋음
에러를 먼저 인식할 수 있으니까
ClassCastException은 지네릭스로 인해 컴파일 단계에서 체크가능
또한 String str = null; 보다는 String str = ""; 을 써야되는 이유도 NullPointerException과 연관돼있음 (str.length())
타입 변수
- 클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용.
타입 변수는 무엇이든지 상관없음, 일반적으로 T나 E(Element의 첫글자) 가 자주 쓰임
예전에는 반환타입이 Object라서 형변환이 필요했지만
지네릭스 도입으로 인해 <E>에 Tv가 대입되므로 형변환이 필요 없다.
지네릭스 용어
º Box<T> 지네릭 클래스. 'T의 Box'또는 'T Box'라고 읽는다.
º T 타입 변수 또는 타입 매개변수.(T는 타입 문자)
º Box 원시 타입(raw type)
class Box<T> {} // 지네릭 클래스 선언
Box<String> b = new Box<String>();
- 참조 변수와 생성자의 대입된 타입은 일치해야 한다.
ArrayList<Tv> list = new ArrayList<Tv>(); // OK
ArrayLIst<Product> list = new ArrayList<Tv>(); // 에러.
- 지네릭 클래스간의 다형성은 성립 (여전히 대입된 타입은 일치해야)
List<Tv> list = new ArrayList<Tv>(); // OK. 다형성. ArrayList가 List를 구현
List<Tv> list = new LinkedList<Tv>(); // OK. 다형성. LinkedList가 List를 구현
- 매개변수의 다형성도 성립
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // OK
list.add(new Audio()); // OK
// 꺼낼 때
Product p = list.get(0);
Tv t = (Tv)list.get(1); //이때는 형변환 해야 함
제한된 지네릭 클래스
- extends로 대입할 수 있는 타입을 제한
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayLIst<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. Toy는 Fruit의 자손이 아님
- 인터페이스인 경우에도 extends를 사용
interface Eatable {}
class FruitBox<T extends Eatable> {...}
지네릭스의 제약
- 타입 변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Grape> grapeBox = new Box<Grape>(); // OK
- static 멤버에 타입 변수 사용 불가
class Box<T>{
static T item; // 에러
static int compare(T t1, T t2) {...} // 에러
}
- 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
class Box<T> {
T[] itemArr; // OK. T타입 배열을 위한 참조변수
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // 에러. 지네릭 배열 생성불가
}
}
객체 생성이나 배열 생성 불가능 (왜냐면 타입이 확정되어있어야 하는데 지네릭은 그렇지 않으므로)
와일드 카드 <?>
- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능
ArrayList<? extends Product> list = new ArrayList<Tv>(); // OK
ArrayList<? extends product> list = new ArrayList<Audio>(); // OK
ArrayList<Product> list = new ArrayList<Tv>(); // 에러. 대입된 타입 불일치
º <? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
º <? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능
º <?> 제한 없음. 모든 타입이 가능 <? extends Object>와 동일
- 매서드의 매개변수에 와일드 카드를 사용
static Juice makeJuice(FruitBox<? extends Fruit> box {
...
}
System.out.println(Juice.makeJuice(new FruitBox<Fruit>())); // OK.
System.out.println(Juice.makeJuice(new FruitBox<Apple>())); // OK.
지네릭 메서드
- 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
static <T> void sort(List<T> list, Comparator<? super T> c)
- 클래스의 타입 매개변수<T>와 메서드의 타입 매개변수 <T>는 별개
- 메서드를 호출할 때마다 타입을 대입해야(대부분 생략 가능)
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
...
System.out.println(Juice.<Fruit>makeJuice(fruitBox)); // OK
System.out.println(Juice.makeJuice(appleBox)); // OK. 생략 가능
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {...}
- 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가
System.out.println(<Fruit>makeJuice(fruitBox)); // 에러. 클래스 이름 생략불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // OK.
System.out.println(Juice.<Fruit>makeJuice(fruitBox)); // OK.
지네릭 메서드는 메서드를 호출할 때마다 다르 지네릭 타입을 대입할 수 있게 한 것
와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
지네릭 타입의 형변환
- 지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다.(경고 발생)
Box<Object> objBox = null;
Box box = (Box)objBox; // OK. 지네릭 타입 -> 원시 타입.
objBox = (Box<Object>) box; // OK. 원시 타입 -> 지네릭 타입.
objBox = (Box<Object>) strBox; // 에러. Box<String> -> Box<Object> (x)
strBox = (Box<String>) objBox; // 에러. Box<Object> -> Box<String> (x)
- 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능
Box<Object> objBox = (Box<Object>) new Box<String>(); // 에러. 형변환 불가능
Box<? extends Object> wBox = (Box<? extends Object>) new Box<String>(); // OK.
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일
지네릭 타입의 제거
- 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
1. 지네릭 타입의 경계를 제거
class Box<T extends Fruit> {
void add(T t) {
...
}
}
->
class Box {
void add(Fruit t) {
...
}
}
2. 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가
T get(int i) {
return list.get(i);
}
->
Fruit get(int i){
return (Fruit)list.get(i);
}
3. 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가
열거형
- 관련된 상수들을 같이 묶어 놓은 것. Java는 타입에 안전한 열거형을 제공
값, 타입 모두 체크 한다
열거형의 정의와 사용
- 열거형을 정의하는 방법
enum Direction { EAST, SOUTH, WEST, NORTH }
enum 열거형이름 { 상수명1, 상수명2, ...}
- 열거형 타입의 변수를 선언하고 사용하는 방법
class Unit{
int x, y;
Direction dir;
void init(){
dir = Direction.EAST;
}
}
- 열거형 상수의 비교에 ==와 compareTo() 사용가능( 비교연산자 사용x)
if(dir==Direction.EAST) {
x++;
} else if (dir > Direction.WEST) { // 에러. 열거형 상수에 비교연산자 사용불가
...
} else if (dir.compareTo(Direction.WEST) > 0) { //compareTo()는 사용가능
}
열거형의 조상 - java.lang.Enum
- 모든 열거형은 Enum의 자손이며, 아래의 메서드를 상속받는다.
º Class<E> getDeclaringClass() : 열거형의 Class객체를 반환
º String name() : 열거형 상수의 이름을 문자열로 반환
º int ordinal() : 열거형 상수가 정의된 순서를 반환(0부터 시작)
º T valueOf(Class<T> enumType, String name) : 지정된 열거형에서 name과 일치하는 열거형 상수를 반환
- values(), valueOf()는 컴파일러가 자동으로 추가
static E[] values()
static E valueOf(String name) // -> Direction d = Direction.valueOf("WEST");
Direction[] dArr = Direction.values(); // 열거형에 있는 상수들을 배열로 반환
for(Direction d : dArr)
System.out.printf("%s=%d%n", d.name(), d.ordinal());
열거형에 멤버 추가하기
- 불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적는다.
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
- 괄호()를 사용하려면, 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.
enum Direction {
EAST(1), SOUTH(5), WEST(-1), NORTH(10);
private final int value; // 정수를 저장할 필드(인스턴스 변수)를 추가
Direction(int value) {this.value = value; } // 생성자를 추가
public int getValue() { return value; }
}
- 열거형의 생성자는 묵시적으로 private이므로, 외부에서 객체생성 불가
Direction d = new Direction(1); // 에러. 열거형의 생성자는 외부에서 호출불가
애너테이션
- 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공
소스코드(*.java) 와 설정파일(XML)을 분리해서 작성했었음, 그 설정에 대한 정보를 소스코드 안에 @이름 과 같은 형식으로
제공함(Junit이라는 특정프로그램에 대해서 작동함) 대규모로 개발할 때 XML을 다같이 수정하기 번거로우니까 애너테이션이 이러한 부분에서 편리함
표준 애너테이션
- Java에서 제공하는 애너테이션
º @Override
- 오버라이딩을 올바르게 했는지 컴파일러가 체크하게 된다.
- 오버라이딩할 때 메서드이름을 잘못적는 실수를 하는 경우가 많다.
class Child extends Parent {
void parentmethod() {} // parentMethod인데 오타일 때
}
->
class Child extends Parent{
@Override
void parentmethod() {} // 에러 출력해줌
}
º @Deprecated
- 앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙인다.
- @Deprecated의 사용 예
스프링부트 공부하면서 아래와 같은 deprecated가 생각이 났다
스프링 버전이 올라가면서 WebSecurityConfigureAdapter가 deprecated됨
위와 같은 함수형 열거방식도 deprecated되었다
- @Deprecated가 붙은 대상이 사용된 코드를 컴파일하면 나타나는 메시지
º @FunctionalInterface
- 함수형 인터페이스(14장)에 붙이면, 컴파일러가 올바르게 작성했는지 체크
함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약이 있음
@FunctionalInterface
public interface Runnable {
public abstract void run(); // 추상 메서드
}
º @SuppressWarnings
- 컴파일러의 경고메시지가 나타나지 않게 억제한다.
- 괄호()안에 억제하고자하는 경고의 종류를 문자열로 지정
@SuppressWarnings("unchecked") // 지네릭스와 관련된 경고를 억제
ArrayList list = new ArrayList(); // 지네릭 타입을 지정하지 않았음
list.add(obj); // 여기서 경고가 원래는 발생
메타 애너테이션
- 메타 애너테이션은 '애너테이션을 위한 애너테이션'
- 메타 애너테이션은 java.lang.annotation패키지에 포함
º @Target
- 애너테이션을 정의할 때, 적용대상 지정에 사용
@Target({FIELD, TYPE, TYPE_USE})
public @interface MyAnnotation { }
@MyAnnotation
class MyClass {
@MyAnnotation
int i;
@MyAnnotation
MyClass mc;
}
º @Retention
- 애너테이션이 유지되는 기간을 지정하는데 사용
- 컴파일러에 의해 사용되는 애너테이션의 유지 정책은 SOURCE이다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
- 실행시에 사용 가능한 애너테이션의 정책은 RUNTIME이다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
애너테이션 타입 정의하기
- 애너테이션을 직접 만들어 쓸 수 있다.
@interface 애너테이션이름 {
타입 요소이름(); // 애너테이션의 요소를 선언한다.
}
- 애너테이션의 메서드는 추상 메서드이며, 애너테이션을 적용할 때 지정(순서x)
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType();
DateTime testDate();
}
애너테이션의 요소
- 적용시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null제외)
@interface TestInfo{
int count() default 1; // 기본값을 1로 지정
}
@TestInfo // @TestInfo(count = 1) 과 동일
public class NewClass {...}
- 요소가 하나이고 이름이 value일 때는 요소의 이름 생략가능
@interface TestInfo{
String value();
}
@TestInfo("passed") // @TestInfo(value="passed")와 동일
class NewClass {...}
- 요소의 타입이 배열인 경우, 괄호 {}를 사용해야 한다.
@interface TestInfo{
String[] testTools();
}
@TestInfo(testTools={"JUnit", "AutoTester"})
@TestInfo(testTools={}) // 값이 없을 때는 괄호{}가 반드시 필요
마커 애너테이션 - Marker Annotation
-요소가 하나도 정의되지 않은 애너테이션
ex) @Deprecated, @Test
정답 : b, d (마우스로 드래그)
이유 :
b - value만 요소 이름 생략 가능
d - 배열일 경우 괄호{}가 필요함
정답 : c,d,g (마우스로 드래그)
이유:
c - Fruit과 자손들만 대입이 가능하므로 Object는 안됨
d - 타입 불일치
g - new 연산자는 타입이 명확해야하므로 와일드 카드 불가능
'자바 스터디' 카테고리의 다른 글
[자바의정석] CH14 2.6~2.8 (0) | 2023.08.05 |
---|---|
[자바의 정석] CH13.8 쓰레드의 실행제어 (0) | 2023.07.30 |
[자바의 정석] ch11.9 ~ ch.11.11 (0) | 2023.07.12 |
[자바의 정석] CH 9(Math ~ Objects) (0) | 2023.06.29 |
[자바의 정석] CH8 (0) | 2023.06.24 |