ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 학원 DAY 21. 제네릭(Generic), comparable인터페이스
    기록 2022. 10. 4. 21:04

    제네릭(Generic)

    소스 레벨에서 데이터 타입이 결정되지 않고, 별칭(타입파라미터)만 지정한다.

    데이터 타입은 필드의 타입, 매개변수 타입, 리턴타입 등이다.

    객체 생성 싯점, 구현클래스 구현 싯점, 메소드 실행 싯점에 별칭을 대신할 데이터 타입을 외부에서 지정하는 것이다.

    제네릭은 <> 다이아몬드 표기법으로 타입파라미터(별칭)를 지정한다. (위치는 클래스명 뒤에 <지정하고자하는 타입>작성한다.)

    데이터타입은 클래스 혹은 인터페이스 타입만 가능하다.

    기본 자료형은 제네릭의 데이터 타입으로 지정할 수 없다.


    < none제너릭일 때 >

    내가 전달해준 값은 0x444이지만 타입이 String이지 Object가 아니라서 String 객체에서 안쪽으로 탐색하고 클래스형변환이 일어나서 실제로 item이 갖게 되는 값은 0x333이 된다.

    박스 안에 여러가지 종류의 객체를 담으려면 객체를 담을 수 있는 변수를 Object로 만들면 된다. 이 경우 항상 클래스형변환이 일어난다. 

    그런데, 값을 꺼낼 때 항상 Object타입으로 나와서 내가 원하는 String타입으로 꺼내기 위해서는 원래 타입으로 강제 클래스형변환을 해줘야 한다. 

    String의 고유한 기능이 아닌 Object에도 있는 toString을 호출하면 실제로 생성한 String객체에 재정의된 toString이 실행된다. 하지만 String의 고유한 속성과 기능을 사용하기 위해서는 다시 String타입의 강제 클래스 형변환을 해야 한다.

     

    #1. item 변수의 타입을 Object 타입으로 지정하는 경우 문제점

    // item 객체에 모든 객체를 저장할 수 있다.

    // 저장되는 모든 객체는 Object타입으로 클래스형변환된다.

    // 저장된 객체를 꺼내서 사용할 때는 반드시 실제로 생성해서 저장한 객체의 타입으로 강제 클래스형변환 해야 한다.

    // 강제 클래스형변환할 때 실제로 생성해서 저장한 객체와 다른 타입으로 형변환하도록 소스코드를 작성해도 오류가 검출되지 않는다. (런타임 에러 발생) 

    public class Box {

    Object item;

    }

     

    #2. Item 변수의 타입을 실제로 저장되는 객체의 타입으로 지정하는 경우 문제점

    // 지정된 타입의 객체만 저장할 수 있다. (따라서, 프로그램에서 사용하는 객체의 종류만큼 xxxBox 클래스를 정의해야 한다.) (클래스를 많이 만들어야 하는 문제점)

     

    public class FruitBox {

    Fruit item;

    }

    public class BookBox {

    Book item;

    }

    public class ProductBox {

    Product item;

    }

     

    따라서, #1과 #2의 문제를 해결하기 위해 Generic을 사용한다.


    < 제너릭 >

    ### item 변수의 타입을 소스코드레벨에서 지정하지 않고 타입파라미터를 사용해서 지정하는 경우

    public class Box<T> {

    T item;

    public void setItem(T t) {

    this.item = t;

    }

    public T getItem() {

    return item;

    }

    }

     

    Box<String> box1 = new Box<String>();

    // 생성된 객체 

    // 생성하는 시점에 무엇을 담을지를 결정

     

    public class Box<String> {

    String item;

    }

    public void setItem(String t) {

    this.item = t;

    }

    public String getItem() {

    return item;

    }

    }

     

    Box<Book> box2 = new Box<Book>();

    // 생성된 객체

    public class Box<Book> {

    Book item;

    }

    public void setItem(Book t) {

    this.item = t;

    }

    public Book getItem() {

    return item;

    }

    }

     

    T자리에 내가 지정한 타입으로 변경되어 실행이 된다.

    생성하는 시점 에 어떤 타입의 객체를 담을지를 결정한다. -> 제너릭

    * 제네릭을 활용해서 Box클래스를 구현한 경우의 잇점
      - 원하지 않는 자료형의 값이 입력되는 것을 컴파일 싯점에 알아낼 수 있다.
      - 값을 꺼낼 때 타입변환이 필요없다.

     

    package day22.generic;
    
    /*
     * 제너릭 클래스
     * 		타입파라미터를 사용해서 객체 생성시 전달받은 타입으로 T의 자리가 대체되는 클래스다.
     * 			타입파라미터는 클래스이름 옆에 <T>와 같은 형태로 지정한다.
     * 			타입파라미터는 <A, B>와 같이 여러 개 지정할 수 있다.
     * 			타입파라미터의 별칭은 일반적으로 대문자로 적고, 아무 문자나 적어도 상관없다. T:타입 E:element R:return 	
     * 		소스코드 레벨에서 타입을 지정하지 않고, 객체 생성싯점에 타입이 결정되는 클래스다.
     * 		T의 자리에는 참조타입만 가능하다. (기본자료형타입은 T를 대체할 수 없다.)
     * 
     *      GenericBox<String> box1 = new Generic<String>();
     *      GenericBox<Book> box1 = new Generic<Book>();
     *      GenericBox<Gift> box1 = new Generic<Gift>();
     *      GenericBox<Student> box1 = new Generic<Student>();
     *      
     *      // 타입파라미터 자리에 기본자료형타입을 지정할 수 없다.
     *      GenericBox<int> box = new Generic<int>();  // 컴파일 오류
     *      GenericBox<double> box = new Generic<double>();  // 컴파일 오류
     *      
     *      // 타입파라미터 자리에 기본자료형타입 대신 기본자료형의 Wrapper클래스타입을 지정한다.
     *      GenericBox<Integer> box = new Generic<Integer>();
     *      GenericBox<Double> box = new Generic<Double>();
     *      
     */
    
    public class GenericBox<T> {
    	private T item;
    	
    	public T getItem() {
    		return item;
    	}
    	public void setItem(T t) {
    		this.item = t;
    	}
    }
    package day22.generic;
    
    import java.util.Date;
    
    public class GenericBoxApp {
    
    	public static void main(String[] args) {
    		//box1이 참조하는 객체는 T의 자리가 전부 String으로 대체된 GenericBox객체다.
    		GenericBox<String> box1 = new GenericBox<>();   // 뒤에는 <String>이라고 안적어도 됨. jvm에 타입추론 기능이 있어서
    		// T의 자리가 전부 String으로 대체되었기 때문에 String객체를 저장하면 Object로 클래스형변환없이 실제 생성된 String객체를 참조하게 된다. 
    		box1.setItem("안녕하세요");
    		// T의 자리가 전부 String으로 대체되었기 때문에 GenericBox객체에서 객체를 가져올 때도 String객체의 주소값이 반환된다.
    		// 		- 강제 클래스 형변환이 필요없다.
    		// 		- 실제로 저장한 객체와 다른 타입의 객체로 형변환하게 되면 컴파일오류가 발생한다.
    		String value1 = box1.getItem();
    		// Date value3 = (Date) box1.getItem(); // 컴파일오류
    		
    		// box2가 참조하는 객체는 T의 자리가 전부 Date로 대체된 GenericBox객체다.
    		GenericBox<Date> box2 = new GenericBox<>();
    		box2.setItem(new Date());
    		Date value2 = box2.getItem();
    		System.out.println("유닉스타임: " + value2.getTime());
    		
    	}
    
    }

     

    제네릭 클래스

    • 타입 파라미터를 하나 이상 가지고 있는 클래스
    • 여러 종류의 객체를 다루는 클래스를 제네릭 클래스로 정의하면 타입의 안전성을 보장받고, 형변환 코드를 제거할 수 있다.
    • 제네릭 클래스 예)
      • 자료구조(Set, List, Map<K, V>)
      • 데이터를 반복처리하는 것(Iterator, Enumeration)
    • 제네릭 클래스 구현시 주의사항
      • T타입의 배열을 생성할 수 없다.
      • T타입의 클래스 변수를 생성할 수 없다.
      class Box<T> {
        static T t;	      // 오류, 클래스 변수를 선언할 수 없다.
        T[] data = new T[10];   // 오류, T타입의 배열은 생성할 수 없다.
        T[] data;	              // 정상, T타입의 배열에 대한 변수는 선언할 수 있다.
        public Box() {
          data = (T[]) new Object[10];
        } 	
      }

     

    제네릭 클래스 정의

      public class Sample<T> {
        private T data; 
        public void setData(T t) { 
          this.data = t; 
        }
      }
     public static void main(String[] args) {
        // T의 타입이 String 클래스가 된다.
        Sample<String> s1 = new Sample<String>();		
        
        // T의 타입이 Customer 클래스가 된다.
        Sample<Customer> s2 = new Sample<Customer>();
        
        // T의타입이 Apple 클래스가 된다.
        Sample<Box<Apple>> s3 = new Sample<Box<Apple>>();	
        
        // T의 타입이 Object가 된다
        Sample s4 = new Sample();	
        Sample<Object> s5 = new Sample<Object>();
    
        // Sample<int> s4 = new Sample<int>(); // 컴파일 오류
        // T의 타입이 Integer가 된다.
        Sample<Integer> s5 = new Sample<Integer>();

     

    인터페이스에 제네릭이 정의되어 있는 경우

     

    제네릭 인터페이스들은 어떤 객체에서 구현할지 모르기 때문에 인터페이스 옆에 <T>라고 적혀있는 것이다.

    제네릭 인터페이스들은 보통 인터페이스를 구현할 때 타입을 지정한다.

     

    인터페이스에 제네릭 사용하기

    • 구현 클래스가 사용할 데이터타입을 외부에서 지정하게 하는 것
    • 제네릭 타입 지정하기
      • 구현클래스 정의 싯점에 데이터타입을 지정하기
        public inteface Comparable<T> {
          int compareTo(T other);
        }
        
        //제네릭 인터페이스를 구현한 클래스의 T가 Product로 대체된다.
        public class Product implements Comparable<Product> {
          private String name;
          private int price;
          
      public int compareTo(Product other) {
            return this.price - other.price;
          }
        }
      • 구현클래스 정의 싯점에 데이터타입을 지정하지 않고, 객체 생성싯점에 지정하게 하기
        public interface Collection<E> {
          boolean add(E e);
        }
      
        public class ArrayList<E> implements Collection<E> {
          ...
          public boolean add(E e) {
        수행문
          }
        }
        ArrayList<Product> products = new ArrayList<Product>();

    제너릭 클래스에서 타입파라미터의 타입 제한 

    package day22.generic.phone;
    /*
     * 제네릭 클래스에서 타입파라미터의 타입을 제한할 수 있다.
     * 		<T extends 상위타입>
     * 			T는 지정된 상위타입 및 그 상위타입을 상속받은 하위타입만 지정가능하다.
     * 			상위타입은 부모클래스 타입이나 부모인터페이스타입 모두 가능하다. implements라고 안적음. extends가 제한하는 의미로 쓰였기 때문에
     */
    
    public class GenericPhoneBox<T extends Phone> {
    	private T item; 
    	
    	public T getItem() { 
    		return item;
    	}
    	
    	public void setItem(T t) {
    		this.item = t;
    	}
    }
    package day22.generic.phone;
    
    public class GenericPhoneBoxApp {
    
    	public static void main(String[] args) {
    		
    		// String은 Phone의 하위타입이 아니기 때문에 String객체를 담는 Box객체는 생성할 수 없다.
    		// GenericPhoneBox<String> box1 = new GenericBox<>();
    		// box1.setItem("홍길동");
    		// box1.setItem("홍길동");
    		// box1.setItem("홍길동");
    		// box1.setItem("홍길동");
    		// box1.setItem("홍길동");
    		
    		// box1이 참조하는 객체는 Phone 종류의 객체를 저장할 수 있다.
    		GenericPhoneBox<Phone> box1 = new GenericPhoneBox<>();
    		box1.setItem(new Phone());
    		box1.setItem(new FeaturePhone());
    		box1.setItem(new SmartPhone());
    	
    		// box2가 참조하는 객체는 FeaturePhone 종류의 객체를 저장할 수 있다.
    		GenericPhoneBox<FeaturePhone> box2 = new GenericPhoneBox<>();
    		//box2.setItem(new Phone());
    		box2.setItem(new FeaturePhone());
    		box2.setItem(new FeaturePhone());
    		box2.setItem(new FeaturePhone());
    		box2.setItem(new FeaturePhone());
    		box2.setItem(new FeaturePhone());
    		//box2.setItem(new SmartPhone());
    		
    		// box3가 참조하는 객체는 SmartPhone 종류의 객체를 저장할 수 있다.
    		GenericPhoneBox<SmartPhone> box3 = new GenericPhoneBox<>();
    		//box3.setItem(new Phone());
    		//box3.setItem(new FeaturePhone());
    		box3.setItem(new SmartPhone());
    		box3.setItem(new SmartPhone());
    		box3.setItem(new SmartPhone());
    		box3.setItem(new SmartPhone());
    		box3.setItem(new SmartPhone());
    		box3.setItem(new SmartPhone());
    
    	}
    
    }

    Comparable<T> 인터페이스

    • Comparable 인터페이스를 구현한 객체는 오름차순으로 정렬가능한 객체가 된다.
    • Comparable 인터페이스의 compareTo메소드를 재정의해서 어떤 값을 기준으로 정렬할 것인지 구현할 수 있다.
    • 주요 메소드
      • int compareTo(T other)
        • 반환값이 양의 정수면 비교대상이 되는 other보다 큰 값을 가지는 객체다.
        • 반환값이 0이면 비교대상이 되는 other과 같은 값을 가지는 객체다.
        • 반환값이 음의 정수면 비교대상이 되는 other보다 작은 값을 가지는 객체다.
    • Comparable 인터페이스를 구현한 객체는 Arrays.sort(T[] array), Collections.sort(List list) 메소드를 이용해서 정렬할 수 있다.
    • 대표적인 Comparable 인터페이스의 구현 클래스
      • String
      • Date
      • Byte, Short, Integer, Long, Float, Double, Character
    • Comparable<E> 인터페이스 구현하기
      • 번호를 기준으로 오름차순 정렬되도록 Comparable인터페이스 구현하기
        public class Person implements Comparable<Person> {
          private int no;
          private String name;
          
          public int compareTo(Person other) {
            return this.no - other.no;
          }
        }
      • 이름을 기준으로 오름차순 정렬되도록 Comparable인터페이스 구현하기
        public class Person implements Comparable<Person> {
          private int no;
          private String name;
          
          public int compareTo(Person other) {
            return this.name.compareTo(other.name);
          }
        }

     

     

     

     

    댓글

Designed by Tistory.