생활코딩이 진짜 정리를 너무 잘해놓아서
https://youtu.be/YUinFIexEQ4 이 영상을 보면 이해가 갈 것이다.
꺽새 안에 데이터 타입을 정의할 때 보통 E
로 한다고 들었는데 여기서는 T
로 되어있다.
뭐가 정답인지는 모르겠다
Person클래스 안에 <T>에 들어갈 것을 String이랑 StringBuilder를 넣어줬기 때문에
info는
p1.info : String
p2.info : StringBuilder 데이터 타입이 된다.
근데 문제는 아무타입이나 다 받아버리면 타입안전성이 낮아진다는 것이다.
package org.opentutorials.javatutorials.generic;
class StudentInfo{
public int grade;
StudentInfo(int grade){ this.grade = grade; }
}
class StudentPerson{
public StudentInfo info;
StudentPerson(StudentInfo info){ this.info = info; }
}
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
}
class EmployeePerson{
public EmployeeInfo info;
EmployeePerson(EmployeeInfo info){ this.info = info; }
}
public class GenericDemo {
public static void main(String[] args) {
StudentInfo si = new StudentInfo(2);
StudentPerson sp = new StudentPerson(si);
System.out.println(sp.info.grade); // 2
EmployeeInfo ei = new EmployeeInfo(1);
EmployeePerson ep = new EmployeePerson(ei);
System.out.println(ep.info.rank); // 1
}
}
위 코드는 StudentPerson이랑 EmployeePerson 클래스 둘 다
각 클래스형의 info를 가지고 있고, info를 매개변수로 받은다음 info를 정의해주고 있다.
고로 같은 구조
이다
package org.opentutorials.javatutorials.generic;
class StudentInfo{
public int grade;
StudentInfo(int grade){ this.grade = grade; }
}
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
}
class Person{
public Object info;
Person(Object info){ this.info = info; }
}
public class GenericDemo {
public static void main(String[] args) {
Person p1 = new Person("부장");
EmployeeInfo ei = (EmployeeInfo)p1.info;
System.out.println(ei.rank);
}
}
그래서 person이라는 클래스로 StudentPerson , EmployeePerson을 대체했다.
class Person을 보면,
public Object info
가 있다.
왜 Object
로 했냐면
원래 각각 StudentPerson, EmployeePerson class 에서는
각 클래스형으로 info를 정의해주고 있었기 때문에
두 클래스 형의 부모인 Object로 해주면 모든 클래스형을 품을 수 있어서 Object로 정의했음
그러면 메인 메소드를 보라.
Person p1 = new Person("부장");
info는 '부장'이 된다. 그러면 String형의 '부장' 이 info가 될 것이다.
그 상태에서 '부장'을 형변환 해보자.
EmployeeInfo ei = (EmployeeInfo)p1.info; //'부장'을 EmployeeInfo로 형변환
근데 우리가 코드에서 저장을 하면 java는 바로 컴파일
을 실행한다.
근데 컴파일 했을때 빨간줄이 노출되지 않는다. => 에러미노출
이러면 아주 찾기 어려운 문제가 될 수 있다.
위 코드를 톰캣으로 런 하면
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to
org.opentutorials.javatutorials.generic.EmployeeInfo
at org.opentutorials.javatutorials.generic.GenericDemo.main
(GenericDemo.java:17)
톰캣으로 런 했을때에만 나오는 런타임 오류
로 노출된다.
이것은 매우매우매우 위험한 문제이다.
개발자가 저장하자마자 컴파일이 되서 , 컴파일 오류
로 노출되어야 에러 날 수 있는 오류라는 것을 인지하며 코드를 작성하는데....
런타임 오류는 심각한 문제로 이어질 수 있기 때문에 ㅠㅠ....
이러한 사용은 위험 그 자체.....!!!!!!
오류는 왜났냐면...
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
}
EmployeeInfo는 int형만 가질 수 있는데, '부장'이라는 String이 들어가서 이것을 캐스팅할 수 없어서 오류가 나게된다.
이런 classCastingException 과 같은 오류가 날 때에는 타입이 안전하지 않다.
라고 한다.
그러니까 타입을 엄격하게 제한 할 수 있게 코드를 작성해야한다.
이런점 때문에 다른 예시이긴 하지만 타입의 안전성때문에 자바스크립트 대신 타입스크립트를 사용하는거 같다^^
그만큼 객체지향언어 뿐만 아니라, 스크립트언어에서도 타입의 안전성은 굉장히 굉장히 중요하게 다루고 있다는 것을 증명하는 셈이다..
제네릭을 사용하면서 자꾸 참조형이 아니면 사용할 수 없다고 ㅇㅔ러메시지가 떴는데 그 이유를 알 수 없고 너무답답하고,,
블로그를 보면서도 이해가안되서 제네릭을 공부하게 되엇다 ㅋㅋㅋ
이 블로그를 쓴 이유...
어쨌든..
제네릭에서는 기본데이터 타입을 사용할 수 없다고 한다.
int, String, 등등을 말한다.
참조형은 클래스형이 대표적이다.
그런데 제네릭을 쓰고싶은데, int형, String형을 사용하고 싶을꺼 아니냐>?>??
그럴때에 사용방법을 알려드리려 한다.
package org.opentutorials.javatutorials.generic;
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T, S>{
public T info;
public S id;
Person(T info, S id){
this.info = info;
this.id = id;
}
}
public class GenericDemo {
public static void main(String[] args) {
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
//기본 데이터타입인 int를 참조형으로 사용하기 위해 Integer로 선언
//이러한 클래스를 래퍼(wrapper) 클래스라고 함 = Integer
Person<EmployeeInfo, Integer> p1 =
new Person<EmployeeInfo, Integer>(e, i);
System.out.println(p1.id.intValue());
}
}
아까 타입안전성에 대해 얘기를 했다.
그런데 제네릭으로 보통 사용하는게 위에서 설명했던것이
어쩌구클래스<T,S>
뭐 이런식이다.
그럼 T랑 S에 뭘 넣어줘도 ㄱㅊ은 느낌이다 ;; 참조형이기만 하면;;
그래서 그것땜에 T랑 S에 넣을 데이터 타입을 제한할 수 있다!!!
package org.opentutorials.javatutorials.generic;
abstract class Info{
public abstract int getLevel();
}
class EmployeeInfo extends Info{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
public int getLevel(){
return this.rank;
}
}
class Person<T extends Info>{
public T info;
Person(T info){ this.info = info; }
}
public class GenericDemo {
public static void main(String[] args) {
Person p1 = new Person(new EmployeeInfo(1));
Person<String> p2 = new Person<String>("부장");
}
}
위 코드를 보면, 밑 코드 때문에
즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.
extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.
class Person<T extends Info>{
extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.
package org.opentutorials.javatutorials.generic;
interface Info{
int getLevel();
}
class EmployeeInfo implements Info{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
public int getLevel(){
return this.rank;
}
}
class Person<T extends Info>{
public T info;
Person(T info){ this.info = info; }
}
public class GenericDemo {
public static void main(String[] args) {
Person p1 = new Person(new EmployeeInfo(1));
Person<String> p2 = new Person<String>("부장");
}
}
제네릭 사용이유 블로그 보면 잘 알 수 잇음
내가 블로그에 소개한 것은
class 클래스이름<T>{
}
이런거여서 T에 어떤 자료형이든지 들어갈 수 있고, 이 자료형을 제한하고자 한다면
extends라는 것을 사용해서 상속받은 자료형의 자식까지만 자료형을제한할 수 있다고 소개했다.
하지만 아예 처음부터 래퍼클래스를 이용해서
List<Integer> list = new ArrayList();
이라던지 이렇게 만들 수도 있다.그러면 List 안에 들어가는 자료형은 엄격하게 Integer 형으로 제한하겠다는 뜻이 된다.
이는 컴파일시 , Integer형이 맞는지, 아닌지 바로바로 강한 타입 체크
를 해주는 장점이된다.
제네릭을 사용하지 않는다고 할때에는, 그냥 list만 선언하고 그 안에 들어가는 자료형을 다시 Integer로 캐스팅 해야한다.
List list = new ArrayList();
list.add(1);
// 강제 형 변환 필요
Integer number = (Integer) list.get(0);
근데 캐스팅 하는게 굉장히 성능에 안좋은 영향을 끼친다고 한다.
그래서 처음부터 엄격하게 타입을 제한할 수 있는 제네릭이 효과적인 코딩을 할 수 있는 방법중 하나라고 생각한다.
나같은 경우에는 이미 실무에서 활용하고 있다.
나는 최근 프로젝트에서 적지 않은 양의 데이터를 전달해주는 API를 개발했다.
대충 어떤 기능이었냐면..
사용자가 날짜를 선택하면 한달치 1금액, 2금액, 3금액,4금액, 날짜 , 등등 여러 정보를
하루하루의 데이터를 1달치를 전달해야 했다.
그래서 ArrayList<HashMap<String,Object>> 를 사용해서
각각의 ArrayList에 내가 전달하고싶은 날짜 개수만큼 HashMap데이터를 담았고
HashMap안의 key값에는 1금액, 2금액, 3금액,4금액, 날짜 , 등등 여러 정보 를 담아
전달해주었다.
간편한 방법이라고 생각한다.
앞으로도 나는 제네릭을 꾸준히 사용할 것이다.
생활코딩 제네릭 유튜브 및 사이트
코딩트리 블로그