객체지향 개념에서 모든 것은 객체로 다루어져야 한다.
때로는 기본형 변수도 어쩔 수 없이 객체로 다뤄야 하는 경우가 있다.
래퍼 클래스
들을 이용하면 기본형 값을 객체로 다룰 수 있다.
기본형 | 래퍼클래스 | 생성자 | 활용예 |
---|---|---|---|
boolean | Boolean | Boolean(boolean value) Boolean(String s) | Boolean b1 = new Boolean(true); Boolean b2 = new Boolean("true"); |
char | Character | ||
byte | Byte | ||
short | Short | ||
int | Integer | ||
long | Long | ||
float | Float | ||
double | Double |
int i1 = new Integer("100").intValue();
Integer i3 = new Integer("100");
//많이 사용하는 방법
int i2 = Integer.parseInt("100");
Integer i4 = Integer.valueOf("100");
클래스 내부에서 사용할 데이터 타입을 외부에서 지정해주는 기법
// 일반적인 클래스
class Box {
Object item;
void setItem(Object item) {this.item = item;}
Object getItem() {return item;}
}
// 제네릭 클래스
class Box<T> {
T item;
void setItem(T item) {this.item = item;}
T getItem() {return item;}
}
여기에서 T
는 임의의 변수이고 일종의 매개변수 역할과도 비슷하다. T
자리에는 어떠한 참조형 타입이 들어간다는 뜻이다.
class Apple {
@Override
public String toString() {
return "Apple";
}
}
class Orange {
@Override
public String toString() {
return "Orange";
}
}
class Box {
private Object ob;
public void set(Object o) {
ob = o;
}
public Object get() {
return ob;
}
}
public class GenericsEx {
public static void main(String[] args) {
Box aBox = new Box();
Box oBox = new Box();
aBox.set(new Apple());
oBox.set(new Orange());
Apple ap = (Apple) aBox.get();
Orange og = (Orange) oBox.get();
System.out.println(ap);
System.out.println(og);
}
}
set()
메서드는 파라미터 타입이 Object
이므로 String
도 받을 수 있다.public class GenericsEx {
public static void main(String[] args) {
Box aBox = new Box();
Box oBox = new Box();
// 개발자의 실수
aBox.set("Apple");
oBox.set("Orange");
Apple ap = (Apple) aBox.get();
Orange og = (Orange) oBox.get();
System.out.println(ap);
System.out.println(og);
}
}
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class date220930.Apple (java.lang.String is in module java.base of loader 'bootstrap'; date220930.Apple is in unnamed module of loader 'app')
at date220930.GenericsEx.main(GenericsEx.java:35)
런타임 에러야 어찌되었든 에러라도 뜨니까 어디에서 문제가 일어났는지 알아차릴 수 있다.
더 큰 문제는 다음 상황을 가정해보자.
런타임 에러조차 뜨지 않고 정상적으로 코드가 실행은 되나 의도대로 전혀 프로그램이 작동하지 않을 수 있다.
public class GenericsEx {
public static void main(String[] args) {
Box aBox = new Box();
Box oBox = new Box();
// 개발자의 실수
aBox.set("Apple");
oBox.set("Orange");
System.out.println(aBox.get());
System.out.println(oBox.get());
}
}
Apple
Orange
두 가지의 문제점으로 인해 Generics
라는 새로운 문법을 1.5 버전 이상부터 도입하여 문제를 해결하는 방법이 있다.
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public Object get() {
return ob;
}
}
public class GenericsEx {
public static void main(String[] args) {
Box<Apple> aBox = new Box<Apple>();
Box<Orange> oBox = new Box<Orange>();
Apple ap = aBox.get(); // 타입 변환이 불필요.
Orange og = oBox.get(); // 타입 변환이 불필요.
System.out.println(ap);
System.out.println(og);
}
}
// 개발자의 실수
aBox.set("Apple");
oBox.set("Orange");
set()
메서드는 Apple
타입 객체만 받을 수 있으므로 컴파일 에러
를 발생시킨다.import java.util.ArrayList;
class Fruit { }
class Apple extends Fruit{ }
class Orange extends Fruit{ }
class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }
class FruitBox<F> {
ArrayList<F> list = new ArrayList<F>();
void add(F item) {
list.add(item);
}
}
public class GenericsEx {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox= new FruitBox<Fruit>();
fruitBox.add(new Apple());
fruitBox.add(new Orange());
// fruitBox.add(new Cat()); //컴파일 에러
// fruitBox.add(new Dog()); //컴파일 에러
FruitBox<Animal> animalBox = new FruitBox<>();
}
}
다음과 같이 fruitBox
객체에는 제네릭을 활용하였기 때문에 다형성을 이용해서 Fruit
를 상속 받은 Apple
과 Orange
를 리스트에 넣을 수 있다.
그런데 FruitBox클래스
를 이용해서 animalBox
를 만들어도 아무런 오류는 발생하지 않는다.
만약에 FruitBox클래스
에서 Fruit
에만 있는 메서드를 호출하는 행위를 하게 된다면 오류가 나게 되므로 제한된 제네릭 클래스
가 필요하다.
//---생략
class FruitBox<F extends Fruit> {
ArrayList<F> list = new ArrayList<F>();
void add(F item) {
list.add(item);
}
}
public class GenericsEx {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox= new FruitBox<Fruit>();
fruitBox.add(new Apple());
fruitBox.add(new Orange());
// fruitBox.add(new Cat()); //컴파일 에러
// fruitBox.add(new Dog()); //컴파일 에러
// FruitBox<Animal> animalBox = new FruitBox<>(); //컴파일 에러
}
}
적용한 결과 FruitBox
인스턴스를 생성할 때는 타입변수
에는 Fruit
를 상속받은 타입만 올 수 있다.
만약 Fruit
가 클래스가 아닌 인터페이스여도 implements
가 아니라 extends
이다.
두 개 이상을 상속받아야 한다면 class FruitBox<F extends Fruit & Food> {...}
와 같이 &
연산자를 이용하면 된다.