관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch12 - 1 ~ 6[제네릭스란?, 타입변수, 제네릭스 용어, 제네릭 타입과 다형성] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch12 - 1 ~ 6[제네릭스란?, 타입변수, 제네릭스 용어, 제네릭 타입과 다형성]

소소한나구리 2023. 12. 13. 10:30

1) 제네릭스(Generics)

  • 클래스이름<타입> 처럼 작성
  • 컴파일시 타입을 체크해주는 기능 - JDK1.5부터 적용됨
  • 실행시 에러(프로그램 비정상 종료)보다 컴파일 에러가 더 안전하기 때문에 실행시 발생하는 타입에러를 컴파일 에러로 바꾸기 위해 나옴
  • 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌 (ClassCastException - 형변환에러 발생을 막음)

(1) 형변환 에러 발생 예제

  • 제네릭스 적용 전에는 List와 같은 컬렉션에 여러 타입을 저장할 수 있는데 해당 컬렉션의 값을 조회시 특정 타입으로 고정하여 값을 출력 하려고하면 컴파일 시에는 에러가 발생하지 않지만 실행시에 ClassCastException(형변환에러) 에러가 발생됨
  • 제네릭스를 적용하여 컬렉션에 타입을 지정하면 지정한 타입으로만 값을 저장할 수 있게 되어 다른 타입을 저장하려고하면 컴파일 시에 에러가 발생하여 실행에러를 미연에 방지할 수 있음
// 제네릭스 활용 전
import java.util.ArrayList;

public class GenericTest {
    public static void main(String[] args) {
    	ArrayList list = new ArrayList();
    
    	list.add(10);
    	list.add(20);	// int 입력
    	list.add("30");	// 문자열 입력
    
    	// index2번의 값을 Integer타입으로 형변환 후 인티저타입의 참조변수에 저장
    	Integer i = (Integer)list.get(2);
        
    	// list.get이 반환하는 타입이 Object -> Integer로 형변환
    	// 컴파일 OK - 컴파일러의 한계(실제로 들어있는건 문자열)
    	// 실행 에러 발생 (ClassCastException - 형변환에러)
    
    	System.out.println(list);
    }
}

// 제네릭스 활용
import java.util.ArrayList;

public class GenericTest {
    public static void main(String[] args) {
    
    	// 객체에 여러 종류를 넣고자 하면<Object>로  제네릭스 작성 -> 단 아래에서 형변환을 해야함
    	// JDK1.5 이후에는 클래스 안에 Object타입이 있는 클래스들은 제네릭스로 클래스를 만들어야 함
    	ArrayList<Integer> list = new ArrayList<Integer>();	// Integer타입만 넣을 수 있도록 변경
	
    	list.add(10);
    	list.add(20);	// int 입력
    	list.add("30");	// 문자열 입력 -> 컴파일 에러발생(타입 체크가 강화됨)

    	// 형변환 생략 가능(제네릭스에서 이미 알고 있음)
    //	Integer i = (Integer)list.get(2);
    	Integer i = list.get(2);

    	System.out.println(list);
    }
}

 

(2) NullPointerException 에러 방지

  • 참조변수를 선언할 때 null이아닌 빈문자열이나 길이가 0인 배열로 초기화하여 선언하면 실행에러인 NullPointerException을 방지할 수 있음
  • 이러한 아이디어로 실행 시 발생하는 형변환 에러로 변경하고자 한 것이 제네릭스임
// NullPointerException 방지하기 위한 초기화 방법

String str = null;  // null로 초기화
str.length();	    // 메서드 호출 -> NullPointException 발생
String str = "";    // 빈문자열로 초기화로 방지

Object[] obArr = null; //null로 배열 초기화
obArr.length;	       // 배열의 길이 출력 -> NullPointException 발생
Object[] objArr = {};  // 길이가 0인 배열로 초기화로 방지

2) 타입변수

  • JDK 1.5 이후부터 자바에 내장된 클래스들이 Object타입 대신 타입 변수<E>를 선언하여 제네릭클래스로 변경이됨
  • 아무 문자를 써도 상관은 없지만 Type의 T나 Element의 E가 많이 선언되어 있음

(1) 타입변수에 실제 객체를 대입

  • 객체 생성 시 타입변수 대신 사용할 타입을 지정하여 대입하고 그 참조값을 저장할 참조변수의 타입에도 동일한 타입을 지정해야 함
  • 지정한 타입을 제네릭스로 적용하면 타입이 명시적으로 선언 되어있어 동일 타입으로 값을 다룰 경우 형변환이 불필요 함
// 기존의 ArrayList가 정의된 모습
public class ArrayList extends AbstractList {
	private transient Object[] elementData;
	public boolean add(Object o) { /* */ }
	public Object get(int index) { /* */ }
	...
}

// 타입 변수가 사용 된 ArrayList (JDK1.5 이후부터 바뀜)
public class ArrayList<E> extends AbstractList<E> {
	private transient E[] elementData;
	public boolean add(E o) { /* */ }
	public E get(int index) { /* */ }
	...
}

// TV 객체 생성시 타입 변수 대신 실제 타입을 지정 대입, 형변환 생략 가능
ArrayList<Tv> tvList = new ArrayList<Tv>();	// 객체 생성
	tvList.add(new Tv());
	Tv t = tvList.get(0);	// 형변환 불필요

 

(2) 예제

import java.util.ArrayList;

class Tv {}
class Audio {}

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

		// Tv타입의 객체만 저장 가능하도록 제네릭스로 타입을 지정
		ArrayList<Tv> list = new ArrayList<Tv>();
		
		list.add(new Tv());
//		list.add(new Audio());	// 에러 발생, 타입 오류
		
		Tv t = list.get(0);	// list가 Tv 타입이기 때문에 Tv타입 참조변수에 바로 저장이 가능함
	}
}

3) 제네릭스용어

(1) Box<T>

  • 제네릭 클래스, 'T의 Box' or 'T Box'라고 읽음

(2) T

  • 제네릭 안에 있는 T는 타입 변수, 타입 매개변수라고 함

(3) Box

  • 원시타입(raw type) 클래스

(4) class Box<T> { } 

  • <T>에 String 타입을 대입 후 객체를 생성하면 <String> 처럼 표기되고 대입된 타입(매개변수화된 타입, parameterized type)이라고 함
  • 아래처럼 객체를 선언할 때 지정하며, 객체를 생성할 때마다 타입은 변경이 가능함
Box<String> b = new Box<String>();

 

4) 제네릭 타입과 다형성

  • 기본적으로는 참조 변수와 생성자의 대입된 타입은 무조건 일치해야 함
  • 제네릭 클래스간의 다형성과 매개변수의 다형성은 성립함
  • 즉, 제네릭의 타입 자체는 다형성이 적용되지 않지만, 일반적으로 우리가 적용하는 다형성의 관계는 모두 적용된다고 보면 됨

(1) 예제

  • List<Tv> tvList  = new ArrayList<Tv>(); 처럼 제네릭스의 타입이 일치하면 제네릭클래스의 다형성은 적용이 됨
  • ArrayList<Product> productList = ... 처럼 ArrayList객체를 참조한 참조변수를 조상 타입으로 지정하면 해당 참조변수에는 다형성이 적용되어 제네릭스타입의 자손의 객체들을 저장할 수 있음
import java.util.*;

class Product {}
class Tv extends Product {}
class Audio extends Product {}

class Ex12_1 {
	public static void main(String[] args) {
    
		ArrayList<Product> productList = new ArrayList<Product>();
		ArrayList<Tv> tvList = new ArrayList<Tv>();
        
// 		ArrayList<Product> tvList = new ArrayList<Tv>(); // 에러. 제네릭 타입이 다름
//		List<Tv> tvList = new ArrayList<Tv>(); // OK. 다형성(제네릭 클래스간의 다형성)

		//매개변수의 다형성(자손의 객체 사용 가능)
		productList.add(new Tv());	// public boolean add(Product e) { }
		productList.add(new Audio());

		Product p = productList.get(0);	// 형변환 생략 가능
		Tv t = (Tv)productList.get(1);	// 형변환 생략불가(다른타입)
		
		tvList.add(new Tv());	// public boolean add(Tv e) { }
		tvList.add(new Tv());

		printAll(productList);
//		printAll(tvList); // 컴파일 에러가 발생한다.
	}

	public static void printAll(ArrayList<Product> list) {
		for (Product p : list) System.out.println(p);
	}
}

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브 강의