본문 바로가기

IT/개발공부

Java 제네릭, 람다식

반응형

 

 

 

본 게시물은 개인 공부용 포스트 입니다.

 

 

 

1. 제네릭

제네릭 의미

제네릭 클래스, 제네릭 인터페이스, 제네릭 메소드

- 클래스, 인터페이스, 메소드를 정의할 때 타입 매개변수 (타입 파라미터)를 선언하고 사용할 수 있다.

 

 

장점

  • 여러 유형에 걸쳐 동작하는 일반화된 클래스나 메소드를 정의할 수 있다.
  • 자료형을 한정함으로써 컴파일 시점에 자료형 검사가 가능하다. (실행 오률을 찾아 고치는 것은 어렵기 때문)
  • 캐스트 (형변환) 연산자의 사용이 불필요하다.

 

예) List 인터페이스를 구현한 ArrayList 클래스

Class ArrayList<E> implements List<E> ...{
	boolean add(E e){ ... }
    E get(int index){ ... }
    e remove(int index){ ... }
    ...
}


//사용 예
List list1 = new ArrayList();
list1.add("Hello");
String s1 = (String)list1.get(0);	//형변환 필요

List<String> list2 = new ArrayList<String>();
list2.add("hello");
String s2 = list2.get(0);		//형변환 필요 없음

 

 

 

재네릭 클래스

  • 클래스 정의에서 타입 파라미터를 선언한다. (클래스를 사용할 때는 타입을 명시해야 함)
  • 타입 파라미터는 참조형만 가능하다. (필드의 자료형, 메소드 반환형, 인자의 자료형으로 사용할 수 있음)
  • 컴파일 할 때, 명확한 타입 검사 수행 가능 (메소드 호출 시 인자의 유형이 맞는지, 메소드 호출의 결과를 사용할 때 유형이 맞는지)
  • 자료형을 매개변수로 가지는 클래스와 인터페이스를 제네릭 타입이라고 한다.

 

 

제네릭 클래스 정의 문법

class 클래스이름<T1, T2, ...>{ ... }

 

  • 클래스 이름의 오른편, 각 괄호 <> 안에 타입 파라미터를 표시
  • 컴마(,)로 구분하여 여러 개의 타입 파라미터 지정 가능
  • 타입 파라미터는 타입을 전달 받기 위한 것이다.
  • 보통 E, K, V, N 등을 사용

 

 

 

제네릭 클래스의 필요성

  • 제네릭 타입을 사용하지 않으면 컴파일 시점에서 오류를 검출하지 못함
  • 의미가 명확하면 생성자 호출 시, 괄호만 사용 가능
    • 예)  Data2<String> b3 = new Data2<>();

 

 

 

예제) 2개의 타입 매개변수를 가지는 클래스

interface Pair<K, V>{
	public K getKey();
    public v getValue();
}


class OrderedPair<K, V> implements Pair<K, V>{
	private K key;
    private V value;
    
    public OrderedPair(K key, V value){
    	this.key = key;
        this.value = value;
    }
    public K getKey(){ return key; }
    public v getValue(){ return value; }
}


public class MultipleType{
	public static void main(String args[]){
    	pair<String, Integer> p1;
        p1 = new OrderedPair<>("Even", 8);
        Pair<String, String> p2;
        p2 = new OrderedPair<>("hello", "java");
        ...
    }
}

 

 

 

 

 

 

RAW 타입

: 제네릭 타입이지만 일반 타입처럼 사용하는 경우, 제네릭 타입을 지칭하는 용어 (타입 매개변수 없이 사용되는 제네릭 타입)

ㅖ) Data2 data = new Data2("hello"); -> Data2는 제네릭 타입 Data2<T>의 raw 타입

 

 

 

제네릭 메소드

: 자료형을 매개변수로 가지는 메소드 

  • 하나의 메소드 정의로 여러 유형의 데이터를 처리할 때 유용하다.
  • 메소드 정의에서 반환형 왼편, 각 괄호 <>안에 타입 매개변수를 표시하면 된다. (타입 매개변수를 메소드의 반환형이나 메소드 매개변수의 자료형, 지역 변수의 자료형으로 사용할 수 있음)
  • 인스턴스 메소드와 static 메소드 모두 제네릭 메소드로 정의 가능
  • 제네릭 메소드를 호출할 대, 타입을 명시하지 않아도 인자에 의해 추론이 가능

예)

public static <T> T getLast(T[] a){
	return a[a.length-1];
}

 

 

예2)

class Util{
	public static<K, V> boolean compare(pair<K, V> p1, Pair<K, V> p2){
    	return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue());
    }
}


public class GenericsTest5{
	public static void main(String args[]){
    	Pair<Integer, Stirng> p1 = new OrderedPair<>(1, "apple");
        Pair<Integer, Stirng> p2 = new OrderedPair<>(2, "pear");
        boolean same = Util.<Integer, String>compare(p1, p2);
        System.out.println(same);
    }
}

 

 

 

제네링 타입 사용 시 유의 사항

  • 기본 자료형은 타입 매개변수로 지정X  (Data<int> d = new Data<>(); -> 안됨)
  • 타입 매개변수로 객체 생성을 할 수 없음 (class Data<T>{ private T t1 = new T() } -> 안됨)
  • 타입 매개변수의 타입으로 static 데이터 필드 선언 X (class Data<T>{private static T t2; } -> 안됨)
  • 제네릭 타입의 배열 선언 X (Data <Integer>[] array; -> 안됨)

 

2. 람다식

람다식 : 인터페이스를 구현하는 익명 클래스의 객체 생성 부분을 수식화 한 것 

 

람다식 구문

- 메소드 매개변수의 괄호, 화살표, 메소드 몸체로 표현

인터페이스 객체변수 = (매개변수목록) -> {실행문목록}

 

예)

//원래 메소드
Runable runnable = new Runnable(){
	public void run(){ ...};
}

//람다식
Runnable runnable = ( ) -> { ... };

 

 

 

람다식 기본 문법

  • 익명 구현 클래스의 객체 생성 부분만 람다식으로 표현 (익명 서브 클래스의 객체 생성은 람다식으 될 수 없음)
  • 인터페이스에는 추상 메소드가 1개만 있어야 함 (2개 이상의 추상 메소드를 포함하는 인터페이스는 사용 불가)
  • 람다식의 결과 타입을 타깃 타입 이라고 함
  • 1개의 추상 메소드를 포함하는 인터페이스를 함수적 인터페이스라고 함
    • 메소드가 1개 뿐이므로 메소드 이름을 생략할 수 있음
    • 람다식은 이름 없는 메소드 선언과 유사함

 

 

예)

interface Addable{
	int add(int a, int b);
}


public class LambdaExpressionTest{
	public static void main(String[] args){
    	
        //익명 클래스
        Addable ad1 = new Addable(){
        	public int add(int a, int b){
            	return (a + b);
            }
        };
        System.out.println(ad1.add(100, 200));		//300 출력
        
        //매개변수 자료형과 return 문을 가진 람다식
        Addable ad2 = (int a, int b) -> {
        	return (a + b);
        };
        System.out.println(ad2, add(10, 20);		//30 출력
        
        //간단한 람다식
        Addable ad3 = (a, b) -> {a + b};
        System.out.println(ad3.add(1, 2));			//3 출력
    }
}

 

 

 

 

예2)

interface MyInterface1{ public void method(int a, int b); }
interface MyInterface2{ public void method(int a); }

public class LambdaTest1{
	public static void main(String args[]){
    	MyInterface1 f1, f2, f3;
        MyInterface2 f4, f5;
        
        f1 = (int a, int b) -> { System.out.println(a + b); };
        f1.method(3, 4);
        f2 = (a, b) -> { System.out.println(a + b); };
        f2.method(5, 6);
        f3 = (a, b) -> System.out.println(a + b);
        f3.method(7, 8);
        
        f4 = (int a) -> { System.out.println(a); };
        f4.method(9);
        f5 = a -> System.out.println(a);
        f5.method(10);

 

 

 

함수적 인터페이스 (function interface)

  • 1개의 추상 메소드만 가지는 단순한 인터페이스를 함수적 인터페이스라고 한다.
  • 패키지 java.util.function에서 표준 함수적 인터페이스가 제네릭 인터페이스로 제공됨
  • 함수적 인터페이스를 구현하는 클래스를 정의할 때, 익명 클래스 정의를 활용할 수 있으나 함다식이 효율적 이다.

예)

Consumer<T>는 void accept(T t)를 가짐

 

예2)

public class RunnableTest4{
	public static void main(String args[]{
    	Thread thd = new Thread(() -> System.out.println("my thread"));		//쓰레드
        thd.start();
    }
}

import java.util.function.*;

public class ConsumerTest{
	public static void main(String args[]){
    	consumer<String> con = t -> System.out.println("Hello " + t);		//accept 정의 (t인자를 받아서 return)
        con.accept("Java");													//hello Java가 출력됨
    }
}

 

 

 

 

 

[참고]

방송통신대학교 Java 프로그래밍

반응형