# Programming Language/Java

[JAVA] 제너릭 메소드(Generic Method) 설명 및 예제

  • 이 포스팅은 이미 제너릭을 학습했고, 제너릭 메소드의 개념이 모호하신 분들이 읽으시길 추천드립니다.

제너릭 메소드

  • 제너릭 메소드는 파라미터 타입과 반환 타입으로 타입 파라미터를 가지는 메소드를 말한다.
  • 아래와 같이 메소드의 반환타입 앞에 "<T>" 형태로 명시한다.

     

public static <T> int add(T a, T b) { ... } 
  • 해당 타입 파라미터는 메소드 내에서만 유효하다.
    • 제너릭 클래스에서 명시한 타입 파라미터와 메소드에서 명시한 타입 파라미터는 다르다.

이미 제너릭을 학습한 개발자라면 위의 내용을 이해하는데 무리가 없을 것이다. 본격적으로 예제로 살펴본다.

public static void main(String[] args) {
    
    /* add1 */
    List<String> stringList = add1(new LinkedList<>(), "AAA");
    System.out.println(stringList);
    //List<Integer> integerList = add1(new LinkedList<>(), 123); // Error
    System.out.println("--------------------");
}  
private static List<String> add1(List<String> list, String element) {
    list.add(element); 
    return list;
}

위와 같이 하나의 List와 문자열을 인자로 받아 리스트에 해당 문자열을 추가한 뒤 새로운 List를 반환하는 함수가 있다고 가정한다. 위 코드는 객체지향 프로그램에 적합하지 않는 코드이다. 만약 Integer 형태의 요소를 추가하고 싶다면 메소드를 오버로딩 해야하는가? 우린 조금전에 제너릭 메소드를 학습했다. 바로 적용해본다.

 

public static void main(String[] args) {    
    /* add2 */
    List<String> stringList2 = add2(new LinkedList<>(), "AAA");
    System.out.println(stringList2);
    List<Integer> integerList2 = add2(new LinkedList<>(), 123); // Good
    System.out.println(integerList2);
    // LinkedList<Double> doubleList2 = add2(new LinkedList<>(), 3.1415); // Error    
    System.out.println("--------------------");
}
private static <T> List<T> add2(List<T> list, T element) {
    list.add(element);
    return list;
}

제너릭 메소드의 형태로 바꾸고 모든 레퍼런스 타입이 사용 가능해졌다. 하지만 파라미터의 타입과 반환 타입이 List에 한정되어 있다. 예를들어, Collection의 형태로 파라미터를 전달할 수 없다.

  • 이 문제에서 Collection을 전달하고 싶다면?

  • 더하여 반환 타입을 Collection이 아니라 인자로 전달되는 타입(Collection의 하위 클래스가 될 수 있음)으로 지정하고 싶다면?

제너릭 메소드의 작은 변형이 이를 가능하게 해준다.

 

  public static void main(String[] args) {
    /* add3 */
    Collection<String> stringList3 = add3(new LinkedList<>(), "AAA");
    System.out.println(stringList3);
    //List<Integer> integerList3 = add3(new LinkedList<>(), 123); // Error
    System.out.println("--------------------");
  }
  private static <T> Collection<T> add3(Collection<T> list, T element) {
    list.add(element);
    return list;
  }

위 코드는 다형성을 이용해 Collection의 하위 클래스들을 파라미터로 전달 가능하게 만들었다. 하지만 이전 메소드인 add2에서 명시한 두 번째 요구사항을 충족하지 못했다. 즉, main 메소드에서 List<String> stringList = add(new LinkedList<>(), "AAA") 라는 명령을 수행할 경우 Collection 타입이 반환되기 때문에 에러가 발생하고, 이를 해결하기 위해 아래와 같이 명시적으로 형변환해야 한다.

  • List<String> stringList = (List<String>)add3(new LinkedList<>(), "AAA")

위의 문제를 해결하기 위해 마지막으로 조금 복잡한 형태의 제너릭 메소드를 사용한다.

 

  public static void main(String[] args) {
    /* add4 */
    Collection<String> stringList4 = add4(new LinkedList<>(), "AAA");
    System.out.println(stringList4);
    LinkedList<Integer> integerList4 = add4(new LinkedList<>(), 123);
    System.out.println(integerList4);
    List<Double> doubleList4 = add4(new ArrayList<>(), 3.1415);
    System.out.println(doubleList4);
  }
  private static <T, R extends Collection<T>> R add4(R collection, T element) {
    collection.add(element);
    return collection;
  }

최종적으로 위에서 발생한 문제들을 모두 해결한 형태가 add4 메소드이다. 이 메소드에서 타입 파라미터는 T, R이 사용되는데 T 타입의 인자를 강제했으므로 Collection에는 T 타입의 요소들만 사용 가능하다. 따라서 두 번째 인자로 전달되는 element의 타입과 충돌할 이유가 없다. 또한 나머지 타입 파라미터인 R을 보면 Collection 클래스로 상한 제한이므로 Collection을 포함한 하위 클래스에서만 사용 가능하다.

 

main 메소드의 예시들을 보면, add4의 결과로 Collection, LinkedList, ArrayList를 사용했을 때 문제가 없음을 확인할 수 있고 두 번째 인자로 넘어가는 element의 타입과도 충돌이 발생하지 않음을 확인할 수 있다.


 

'# Programming Language > Java' 카테고리의 다른 글

java.util.Date vs java.sql.Date  (1) 2019.12.18
Short-Circuit Evaluation  (0) 2019.12.18

java.util.Date vs java.sql.Date

2019. 12. 18. 14:43

java.util.Date vs java.sql.Date

  • 자바에서 Date 클래스를 사용하다 보면 java.util의 Date와 java.sql의 Date, 두 가지를 확인할 수 있다.
  • 이 글에서는 두 클래스의 차이를 확인한다.

java.util.Date

  • 1970년 1월 1일 00:00:00 GMT(Epoch Time 또는 POSIX Time) 이후의 특정 시점을 millisecond 단위로 나타낸다.
    • 따라서 클래스 내부에 UTC(협정 세계시)에 대한 정보를 가지고 있다.
  • 자바 11 기준 생성자는 아래와 같다.
Date()
Date(long date)
Date(int year, int month, int date)
Date(int year, int month, int date, int hrs, int min)
Date(int year, int month, int date, int hrs, int min, int sec)
Date(String s)
  • Date 클래스는 더이상 사용을 권고하지 않는다.
    • Date 클래스의 객체는 mutable이며, setTime(0) 등의 메소드로 내부 값을 변경할 수 있다.
      • Immutable 객체의 대한 장점은 이 글에서 다루지 않는다.
    • 또한 Date 클래스는 UTC를 사용하기 때문에 한계를 가진다. 
      • 시스템에 따라 UTC가 다를 수 있다.
  • Java 8이 도입되면서 java.time 패키지의 사용이 권고된다.

java.sql.Date

  • java.sql.Date 클래스는 java.util.Date 클래스를 상속한다.
    • 해당 클래스의 사용 목적은 년도, 월, 일을 유지하는 SQL 데이터를 다루기 위함이다.
    • 즉, 시간 정보는 다루지 않고 0으로 초기화된다.
Date(int year, int month, int day)
Date(long date)

 

  • 해당 클래스는 데이터베이스를 다룰 때 사용되어야 한다.
    • 시간 정보를 유지하지 않으므로, 로컬 환경과 데이터베이스 간의 시간 변환은 JDBC 드라이버의 구현에 따라 다르다.
    • 마지막으로 SQL TIME, SQL TIMESTAMP와 같은 SQL의 데이터 타입을 처리하기 위해 java.sql의 Time 클래스 또는 Timestamp 클래스를 사용할 수 있다.

Conclusion

  • java.util.Date는 Epoch Time 이후 날짜, 시간 정보를 저장한다.
  • java.sql.Date는 시간 정보가 없이 날짜 정보만 저장하며, JDBC에서 일반적으로 사용된다.
  • 자바 8 이후에서는 java.util.Date의 사용을 권고하지 않는다.

Short-Circuit Evaluation

2019. 12. 18. 14:41

Short-Circuit Evaluation

  • SCE를 다루기에 앞서 자바의 논리 연산자를 생각해보자.
  • 논리 연산자에는 AND(&&) 연산과 OR(||) 연산이 있다.
    • 논리 연산자는 피연산자로 boolean 타입 또는 boolean 타입의 값을 결과로 하는 조건식을 취할 수 있다.
    • && 연산이 || 연산보다 우선순위가 높으므로, 함께 사용할 때 괄호를 적절히 사용해야 한다.

AND( && ) 연산

  • 피연산자가 양쪽 모두  참이면 true를 반환한다.

OR( || ) 연산

  • 피연산자 중 한쪽이 참이면 true를 반환한다.
x y x || y x && y
true true true true
true false true false
false true true false
false false false false

SCE

  • 조금만 생각해보면 && 연산에서 왼쪽 피연산자의 조건이 false이면 오른쪽 연산은 볼 필요가 없다는 것을 알 수 있다. 
    • 마찬가지로 ||연산에서 왼쪽 피연산자의 조건이 true이면 어차피 전체 식은 true 이므로 오른쪽의 연산을 확인할 필요가 없다.
      • 즉, 왼쪽의 인자가 전체 식의 값을 결정하기에 충분하지 않은 경우에만 오른쪽의 인자를 평가한다.
    • 더 나아가 논리식이라도 피연산자의 위치에 따라서 연산 속도가 달라질 수 있다.
      • && 연산의 경우, 연산 결과가 false가 나올 가능성이 높은 피연산자를 왼쪽에 놓아야 더 빠른 로직을 구현할 수 있다.
// 아래 코드의 결과를 확인하여 SCE가 무엇인지 확인한다.
int i = 10;
int j = 0;
while(i-->0 || j++<10)
    System.out.printf("%d, %d\n",  i, j);

+ Recent posts