제네릭 (Generic)

  • 결정되지 않은(미정) 타입을 타입 매개변수로 처리하고, 실제 사용할 때 구체적인 타입으로 대체하는 기능
  • 제네릭은 타입을 하나의 변수처럼 취급할 수 있게 해주는 문법
  • 어떤 타입인지 정하지 않고, 사용할 때 구체적인 타입으로 지정해서 재사용성과 타입 안정성을 높일 수 있다
public class Box<T>{  //T=type. 아무거나 들어가도 된다는 것. <>가 있으면 제너릭
 public T content;
}
//<>안에는 클래스만 들어감(int안됨, Integer로 넣어야함)
class Program<T>{ //모형자 < > : 제너릭
	T t; //Java가 들어가게 됨. 아직 안정해져서 T로 적음
	void in(T t) {
		this.t =t;
	}
	T get() { //반환형이 int 이런게 아니라 정해지지 않아 T로만 적음
		return t;
	}
}

class Java{
	String s;
	Java(String s){
		this.s = s;
	}
	void show() {
		System.out.println(s);
	}

}

class DB{
	String s;
	DB(String s){
		this.s = s;
	}
	void show() {
		System.out.println(s);
	}

}

public class Test {
	public static void main(String[] args) {

		Program<Java> p = new Program<Java>();
		// ↑      ↑  클래스 2개임

		p.in(new Java("java"));
		Java j = p.get();
		j.show();

		Program<DB> p2 = new Program<DB>();
		p2.in(new DB("db"));
		DB j2 = p2.get();
		j2.show();

	}
}

-------------------------------------------
java  db

1. 제네릭 타입

  • 클래스(인터페이스)명 자체가 제네릭 형태인 경우
  • class 클래스명<A, B …> → 파라미터로 A와 B 정의
class Product<K,M>{ //제네릭 타입
	//필드
	private K kind;
	private M model;

	//메소드
	public K getKind() {return this.kind;}
	public M getModel() {return this.model;}
	public void setKind(K kind) {this.kind = kind;}
	public void setModel(M model) {this.model= model;}
}

class Tv{}
class Car{}

public class Study {
	public static void main(String[] args) {
		//K는 Tv로 대체, M은 String으로 대체
		Product<Tv, String> product1 = new Product<>();

		//Setter 매개값은 반드시 Tv와 String을 제공
		product1.setKind(new Tv());
		product1.setModel("스마트폰");

		//Getter 리턴값은 Tv완 String으로 대체
		Product<Car, String> product2 = new Product<>();

		Tv tv = product1.getKind();
		String tvModel = product1.getModel();

		//Setter 매개값은 반드시 Tv와 String을 제공
		product2.setKind(new Car());
		product2.setModel("SUV자동차");

		//Getter 리턴값은 Car와 String이 됨
		Car car = product2.getKind();
		String carModel = product2.getModel();
	}
}

2. 제네릭 메소드

  • 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터를 정의한 뒤, 리턴 타입과 매개변수 타입에서 사용
  • 원하는 모양대로 짤 수 있는 장점이 있음

    class AA{
    	@Override
    	public String toString() {
    		return "AA";
    	}
    }
    class BB{
    	@Override
    	public String toString() {
    		return "BB";
    	}
    }
    
    class In{
    	<T> void show(T t) { //제너릭 메소드
    		System.out.println(t); //메인 없이 객체 출력만 한경우 → @클래스명16진수
    	}
    }
    
    public class Test {
    	public static void main(String[] args) {
    		AA a = new AA();
    		BB b = new BB();
    
    		In i = new In();
    		i.<AA>show(a); //In클래스 안에 있는 show함수 호출 → a가 들어가 AA출력
    		i.<BB>show(b); //BB 출력
    	}
    }
    
    class Box<T>{
    	//필드
    	private T t;
    
    	//Getter 메소드
    	public T get() {
    		return t;	}
    
    	//Settet 메소드
    	public void set(T t) {
    		this.t = t;	}
    }
    
    public class Study {
    	public static <T> Box<T> boxing(T t){ //타입 파라미터 T정의
    		Box<T> box = new Box<T>();
    		box.set(t);
    		return box;
    	}
    
    	public static void main(String[] args) {
    		//제네릭 메소드 호출
    		Box<Integer> box1 = boxing(100);  //T를 Integer로 대체
    		int intValue = box1.get();
    		System.out.println(intValue); //100 출력
    
    		//제네릭 메소드 호출
    		Box<String> box2 = boxing("홍길동"); //T를 String으로 대체
    		String strValue = box2.get();
    		System.out.println(strValue); //홍길동 출력
    	}
    }
    
    class St<T>{
    	int n;
    	Object [] st; //나중에 넣은게 먼저 출력되도록(Last In First Out. LIFO)
    
    	St(){
    		st = new Object[3]; //생성자 안에서 객체 배열 생성
    		n = 0;
    	}
    
    	public void push(T t) {
    		if(n==3) { //스택이 다 차있다. 요소를 삽입할 수 없다.
    			return; //함수종료
    		}
    		st[n] = t;
    		n++;
    	}
    
    	public T pop() { //반환형이 정해져있지 않음 <T>
    		if(n==0) { //스택이 비어있어 꺼낼 수 없다
    			return null;
    		}
    		n--;  //배열의 인덱스를 줄임
    		return (T)st[n]; //st는 Object타입이고, 이 메소드는 T로 나와야해서 <T>로 강제 형변환
    	}
    }
    
    public class Test {
    	public static void main(String[] args) {
    
    		St<String> s = new St<String>();
    		s.push("java");
    		s.push("db");
    		s.push("html");
    
    		//for문 사용해서 출력
    		for(int i=0;i<3;i++) {
    			System.out.println(s.pop());
    		}
    
    		St<Integer> s2 = new St<Integer>(); //정수로 넣음
    		s2.push(10);  //s2.push(new Integer(10)) 이거랑 같은 작업.
    		s2.push(20);           //└Integer(10) =10 오토박싱으로 자동으로 됨
    		s2.push(30);
    
    		//for문 사용해서 출력
    		for(int i=0;i<3;i++) {
    			System.out.println(s2.pop());
    		}
    	}
    }
    

3. 제한된 타입 파라미터 (Bounded Type Parameters)

  • 특정 타입의 서브클래스나 구현체만 타입 파라미터로 허용하기 위해 사용하는 제네릭 문법
  • 타입 파라미터는 기본적으로 모든 타입으로 대체 가능
  • 모든 타입으로는 대체할 수 없고, 특정 타입이나 특정 클래스를 상속하거나 인터페이스를 구현한 타입만 허용하고 싶을때 제한(bounded)하는 타입 파라미터

     public <T extends 상위타입(인터페이스)> 리턴타입 메소드(매개변수,) {  }
    
    // T는 Number 클래스를 상속한 클래스만 가능
    public class Calculator<T extends Number> {
    public double add(T a, T b) {
    return a.doubleValue() + b.doubleValue();
    }
    }
    
    Calculator<Integer> calc1 = new Calculator<>(); // 가능
    Calculator<String> calc2 = new Calculator<>(); // ❌ 컴파일 에러!
    
    class Food{
    	void show() {
    		System.out.println("음식");
    	}
    }
    
    class Pizza extends Food{
    	@Override
    	void show() {
    		super.show(); //부모에 있는 show함수 호출
    		System.out.println("피자");
    	}
    }
    
    class Fo<T>{
    	T t;
    	void in(T t) {
    		this.t = t;
    	}
    	T out() {
    		return t;
    	}
    }
    
    public class Test {
    	static void pr(Fo <? extends Food>f) {
    		//      └ Fo<? extends Food> f = f
    		// ? (와일드타입 파라미터) = Food로부터 상속받은 모든 자식 사용 가능
    		 Food fo = f.out(); //Food fo = new Pizza(); pizza클래스를 넣는 경우
    		fo.show();
    	}
    
    	public static void main(String[] args) {
    
    		Fo<Food> f = new Fo<Food>();
    
    		f.in(new Food()); //Food의 객체 생성
    		pr(f);
    
    		//Food f2 = new Food();
    		//f.in(f2);
    		Fo<Pizza> f2 = new Fo<Pizza>();
    		f2.in(new Pizza()); //Food의 객체 생성
    		pr(f2);
    	}
    }
    -------------------------------------------------------
    음식 음식 피자
    

4. 와일드카드 타입 파라미터

  • 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드)를 사용할 수 있다
  • ? = 범위에 있는 모든 타입으로 대체할 수 있다는 표시

리턴타입 메소드명(제네릭타입<? extends Student> 변수) {...}
리턴타입 메소드명(제네릭타입<? super Worker> 변수) {...}
리턴타입 메소드명(제네릭타입<?> 변수) {...}
584 586
class Person{}
class Worker extends Person{}
class Student extends Person{}
class HihgSudent extends Student{}
class MiddleSudent extends Student{}

class Applicant<T>{
	public T kind;
	public Applicant(T kind) {this.kind = kind;}
}

class Course{
	//모든 사람이면 등록 가능
	public static void course1(Applicant<?> applicant) {
		System.out.println(applicant.kind.getClass().getSimpleName()+" Course1등록");
	}

	//학생 사람이면 등록 가능
	public static void course2(Applicant<? extends Student> applicant) {
		System.out.println(applicant.kind.getClass().getSimpleName()+" Course2등록");
	}

	//직장인 및 일반인만 등록 가능
	public static void course3(Applicant<? super Worker> applicant) {
		System.out.println(applicant.kind.getClass().getSimpleName()+" Course3등록");
	}
}

public class Test {
	public static void main(String[] args) {
		//모든 사람들 신청 가능
		Course.course1(new Applicant<Person>(new Person()));
		Course.course1(new Applicant<Worker>(new Worker()));
		Course.course1(new Applicant<Student>(new Student()));
		Course.course1(new Applicant<HighStudent>(new HighStudent()));
		Course.course1(new Applicant<MiddleStudent>(new MiddleStudent()));

		//학생 신청 가능
		//Course.course2(new Applicant<Person>(new Person()));  (X)
		//Course.course2(new Applicant<Worker>(new Worker()));  (X)
		Course.course2(new Applicant<Student>(new Student()));
		Course.course2(new Applicant<HighStudent>(new HighStudent()));
		Course.course2(new Applicant<MiddleStudent>(new MiddleStudent()));

		//학생 신청 가능
		Course.course3(new Applicant<Person>(new Person()));
		Course.course3(new Applicant<Worker>(new Worker()));
		//Course.course3(new Applicant<Student>(new Student()));             (X)
		//Course.course3(new Applicant<HighStudent>(new HighStudent()));     (X)
		//Course.course3(new Applicant<MiddleStudent>(new MiddleStudent())); (X)
	}
}

10.6 기출문제

  • 다음 main()을 보고 Gen클래스를 만들어라.
  Gen<String> g=new Gen<String>("seoul","busan");
  System.out.println(g.one()); //seoul 출력
  System.out.println(g.two()); //busan 출력
  System.out.println(g.three()); //false 출력 (두 문자열 비교)
class Gen<T>{
	T a,b;

	Gen(T a, T b){
		this.a = a;
		this.b = b;
	}

	T one() { //문자열이 String 대신 T
		return a;
	}

	T two() {
		return b;
	}

	boolean three() {
		return a.equals(b);
	}
}

public class Study {
	public static void main(String[] args) {
		Gen<String> g = new Gen<String>("seoul", "busan");
		System.out.println(g.one()); // seoul 출력
		System.out.println(g.two()); // busan 출력
		System.out.println(g.three()); // false 출력 (두 문자열 비교)
	}
}
  • 587p 2번

    public class Test {
    	public static void main(String[] args) {
    		Container<String> container1 = new Container<String>();
    		container1.set("홍길동");
    		String str = container1.get();
    
    		Container<Integer> container2 = new Container<Integer>();
    		container2.set(6);
    		int value = container2.get();
    	}
    }
    
  • 587p 3번

    public class Test {
    	public static void main(String[] args) {
    		Container<String, String> container1 = new Container<String. String>();
    		container1.set("홍길동","도적");
    		String name1 = container1.getKey();
    		String job = container1.gerValue();
    
    		Container<String, Integer> container2 = new Container<String. Integer>();
    		container2.set("홍길동",35);
    		String name2 = container2.getKey();
    		String age = container2.gerValue();
    	}
    }
    

태그:

카테고리:

업데이트:

댓글남기기