Data Type이나 Algorithms을 이용하여 재활용성, 유연성을 극대화 하기 위한 Programming technique
OOP나 Structured Programming이라는 paradigm을 벗어나 "단순하고 빠르게" 구현하는것이 목표.
* DataType : Size, Rule
* Generics : 컴파일시 타입을 체크해 주는 기능(compile-time type check)
* 실행시 에러를 컴파일 에러로 = generics
public class smple {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>(); // Type Check가 강화됨
// JDK1.5 부터 Generics가 도입된 이후 문법.
ArrayList<Object> list2 = new ArrayList<Object>(); // 모든 Object 입력 가능
list.add("string");
list.add(1); // Compile Error
String s = list.get(0); // 형변환 생략 가능
}
}
Generic Programming 이전의 오류
불변성의 Data Type으로 이를 Runtime시점(Instance 생성 시점)에 결정되게끔 바꾸면 독립성, 유연성을 대폭 높이게 된다.
DataType을 정의한뒤 logic을 세우는게 아닌 logic에 맞춰 datatype을 결정한다.
▶ 불필요한 형변환으로 Compiler가 Error를 발견하기 어렵다.
▶ 아래 코드에서도 실행 과정에서 Exception이 발생되지 않는다.
Generic Programming의 예
Generic Programming의 개념
객체 타입의 안정성을 높이고 타입체크와 형변환의 번거로움을 줄여줌으로서 코드가 간결해진다.
타입의 명명규칙의 관례(Recommand)
- 대문자 사용.
- 타입이 한개일 경우 큰 상관이 없음
- 두 개 이상의 경우 한문자로 이름 결정
용어 | 뜻 |
E | Element |
K | Key |
N | Number |
T | Type |
V | Value |
Exception = runtime중 발생하는 에러
RuntimeException = 프로그래머의 실수
Runtime보다 Compiletime에 발생하는 Error는 잡을 수 있기에 generics를 이용하여 프로그래머가 저지를 수 있는 실수를 예방한다.
class Tv{}
class Audio{}
public class ex2 {
public static void main(String[] args) {
ArrayList list0 = new ArrayList();
Tv t1 = (Tv)list0.get(0); // 타입 불일치라 형변환이 필요.
ArrayList<Tv> list = new ArrayList<Tv>(); //Tv Type의 객체만 저장 가능
Tv t2 = list.get(0); // Generics로 형변환 필요없다.
list.add(new Tv());
}
}
▶ Generics 용어
Box<T> | Generic Class. 'T의 박스', 'T Box' 라고 읽는다. |
T | 타입 변수 또는 타입 매개변수. (T는 타입 문자) |
Box | 원시 타입(raw type) |
▶ Generics type과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치해야 한다.
- Generics Class간의 다형성은 성립(대입된 타입은 일치해야한다.)
- 매개변수의 다형성도 성립한다.
- 조상의 자손객체도 성립한다.
class Tv extends Product {}
class Audio extends Product {}
ArrayList<Tv> list = new ArrayList<Tv>(); // 일치
ArrayList<Product> list = new ArrayList<Tv>(); // Error
List <Tv> list = new ArrayList<Tv>(); // 일치. ArrayList가 list를 구현
List <Tv> list = new LinkedList<Tv>(); // 일치. LinkedList가 list를 구현
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // OK. Product 자손
list.add(new Audio()); // OK. Product 자손
▶ 구현
import java.util.*;
class Product {}
class Tv extends Product {}
class Audio extends Product {}
public class smple {
public static void main(String[] args) {
ArrayList<Product> productList = new ArrayList<Product>();
ArrayList<Tv> tvList = new ArrayList<Tv>();
// ArrayList<Product> tvList = new ArrayList<Tv>(); // ERROR.
List<Tv> TvList2 = new ArrayList<Tv>(); // OK. 다형성
productList.add(new Tv());
productList.add(new Audio());
tvList.add(new Tv());
// tvList.add(new Audio()); // Error.
printAll(productList);
// printAll(tvList); // Error. 컴파일 에러.
// 참조변수와 생성자 타입이 같아야된다
}
public static void printAll(ArrayList<Product> list){
for(Product p : list){
System.out.println(p);
}
}
}
Iterator<E>
Collections class 뿐만 아니라 Iterator에도 Generics가 적용되어 있다는 것을 알 수 있다.
Generics가 도입되면서 기존의 소스에 Object가 들어간 Class에는 전부 아래와 같이 바뀌었다.
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
▶ 구현
코드가 간결해지는것을 볼 수 있다.
import java.util.*;
public class smple {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("김연아", 1, 1));
list.add(new Student("유재석", 1, 2));
list.add(new Student("홍석천", 2, 1));
// Iteraotr에 generics 적용
Iterator<Student> it = list.iterator();
while(it.hasNext()) {
// generics를 적용 하지 않을 시 형변환을 해야한다.
// Student s = (Student)it.next();
// System.out.println(s.name);
// 아래 코드처럼 코드가 간결해진다.
System.out.println(it.next().name);
}
}
}
class Student{
String name = "";
int ban;
int no;
Student(String name, int ban, int no){
this.name = name;
this.ban = ban;
this.no = no;
}
}
HashMap<K, V>
HashMap처럼 데이터를 key, value의 형태로 저장하는 Collections class는 지정해 줘야 할 타입이 <K, V> 두개다.
여러 개의 타입 변수가 필요한 경우, 콤마(,)로 구분자를 선언한다.
import java.util.*;
public class smple {
public static void main(String[] args) {
HashMap<String, Student> map = new HashMap<>(); //JDK 1.7부터 생성자에 타입지정 생성가능
map.put("김연아" ,new Student("김연아", 1, 1, 100, 100, 100));
map.put("유재석", new Student("유재석", 1, 2, 5, 5, 5));
}
}
class Student{
String name = "";
int ban;
int no;
int kor;
int eng;
int math;
Student(String name, int ban, int no, int kor, int eng, int math){
this.name = name;
this.ban = ban;
this.no = no;
this.kor = kor;
this.eng = eng;
this.math = math;
}
}
Generic Programming의 기본 문법
▶ 다중 매개변수(다중 템플릿)
▶ 타입입자의 생략
인자를 생략하는 SugarCode를 사용.
일반적으로 r-value를 먼저 해석하는데 신형 JAVA는 l-value를 참조하여 r-value를 해석하는 기법을 사용한다. (타입-추론 기법)
▶ 제한된 Generic Class
매개변수 T에 지정할 수 있는 타입의 종류를 제한한다.
public class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정가능
ArrayList<T> list = new ArrayList<T>();
...
}
▷ Class fruit의 자손이면서 Eatable interface도 구현해야 한다면 아래와 같이 & 기호로 연결한다.
class FruitBox<T extends Fruit & Eatable> {
...
}
▷ 예제
import java.util.*;
class Fruit implements Eatable {
public String toString() {
return "Fruit";
}
}
class Apple extends Fruit {
public String toString() {
return "Apple";
}
}
class Grape extends Fruit {
public String toString() {
return "Grape";
}
}
class Toy {
public String toString() {
return "Toy";
}
}
interface Eatable{
}
public class Ex12_3 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
// FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // Error, 타입 불일치
// FruitBox<Toy> toyBox = new FruitBox<Toy>(); // Error.
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // Error, Grape는 Apple의 자손이 아니다.
grapeBox.add(new Grape());
System.out.println("fruitBox - " + fruitBox);
System.out.println("appleBox - " + appleBox);
System.out.println("grapeBox - " + grapeBox);
}
}
class FruitBox<T extends Fruit & Eatable> extends Box<T> { // Fruit을 상속받고 eatable interface를 구현해야는 조건 제약
// &로 사용
}
class Box<T> {
ArrayList<T> list = new ArrayList<T>(); //item을 저장할 list.
void add(T item) { //박스에 item 추가
list.add(item);
}
T get(int i) { // 박스에서 item 꺼낼때
return list.get(i);
}
int size() {
return list.size();
}
public String toString() {
return list.toString();
}
}
▶ Generics의 제약
static맴버는 타입 변수에 지정된 타입, 대입된 타입의 종류에 관계없이 동일한 것이어야 한다.
* static - 모든 instance의 공통적.
▷ 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]; //Error, 지네릭 배열 생성불가
}
}
▶ Generic Method
Method를 호출 할 때 마다 타입을 대입해야한다.
- Collections.sort()가 Generic Method다.
Generic Class에 정의된 타입 매개변수가 T이고 Generic Method에 정의된 타입 매개변수가 T이어도 이 둘은 전혀 별개의 것이다.
class FruitBox<T>{
static <T> void sort(List<T> list, Comparator<? super T> c){
...
}
}
위의 코드에서 Generic Class FruitBox에 선언된 타입 매개변수 T와 Generic Method sort()에 선언된 타입 매개변수 T는 타입 문자만 같은 뿐 서로 다른 것이다.
이 Method를 호출 할 때는 아래와 같이 타입 변수에 타입을 대입해야 한다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)));
System.out.println(Juicer.<Apple>makeJuice(appleBox)));
▶ Generic Type의 형변환
Generic Type과 원시 타입 간의 형변환은 바람직 하지 않다.
Box box = null;
Box<Object> objBox = null;
box = (Box)objBox; //OK. 지네릭 타입 => 원시 타입, 경고 발생
objBox = (Box<Object>box); //OK. 원시 타입 => 지네릭 타입, 경고 발생
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>strBox); //에러. Box<String> => Box<Object>
strBox = (Box<String>objBox); //에러. Box<Object> => Box<String>
Wild card가 사용된 GenericType으로는 형변환 가능
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>(); //위 문장과 동일.
static Juice makeJuice(FruitBox<? extends Fruit> box) {}
FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); //OK
FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); //OK
▶ Generic Type의 제거
Complier는 GenericType을 제거하고 필요한 곳에 형변환을 넣는다.
타입 인자로 타입을 전달.
public class gnExam2 {
public static void main(String[] args) {
box<String> sBox = new box<>();
sBox.set(new String("Rain"));
box<box<String>> bBox = new box<>();
bBox.set(sBox);
System.out.println(bBox.get().get());
box<box<box<String>>> bbBox = new box<>();
bbBox.set(bBox);
System.out.println(bbBox.get().get().get());
}
}
public class box <T> {
private T obj;
public void set(T obj){
this.obj = obj;
}
public T get(){
return obj;
}
@Override
public String toString(){
return "box(" + obj + ")";
}
// T : Type Parameter, Template (타입 매개변수)
// Apple : Type Argument (타입 인자)
}
Type Restriction (타입의 제한)
▶ Class를 이용한 Type restriction
class Box <T extends Number> {...}
Instance 생성시 Type 인자로 Number 또는 이를 Inheritance하는 class만을 올 수 있다.
Box <Integer> iBox = new Box<>(); //또는
BOX <Integer> dBox = new Box<>();
▶ Type restriction의 효과
Type restriction을 통해 compiler가 method가 호출 명령을 만들 수 있다.
class Box<T> {
private T ob;
....
public int toIntValue() {
return ob.intValue(); // ERROR!
}
}
class Box<T extends Number> {
private T ob;
....
public int toIntValue() {
return ob.intValue(); // OK!
}
}
▶ Interface를 이용한 Type restriction
▶Type restrictio의 다중 제한
&를 통해 이중으로 제한을 둘 수 있다.
class Box<T extends Number & Eatable> { ... }
▶ Generic Method
Class 전체가 아닌 Method에만 Generic을 적용
Generic Method는 호출 시점에 결정.
Box<String> sBox = BoxFactory.<String>makeBox("Sweet");
Box<Double> dBox = BoxFactory.<Double>makeBox(7.59); // 7.59에 대해 오토 박싱 진행됨
Box<String> sBox = BoxFactory.makeBox("Sweet"); // 생략 가능
Inheritance of GenericClass
GenericClass도 Inheritance가 가능하며 일반적인 Inheritance Mechanism을 그대로 사용한다.
Target Type
타입-추론을 통해 알아낸 데이터 타입
용어가 javadoc에 많이 나온다.
'JAVA' 카테고리의 다른 글
[Java] Annotation (0) | 2022.08.04 |
---|---|
[Java] enum(열거형) (0) | 2022.08.03 |
[Java] WildCard (0) | 2022.08.01 |
[Java] Collections Framework 예제 (0) | 2022.07.28 |
[Java] Collections Framework - Comparator & Comparable (0) | 2022.07.28 |