package ex_generic;
import java.util.*;
public class Ex1 {
public static void main(String[] args) {
/*
* 제네릭(Generic, 일반화)
*
* < 제네릭 타입 지정을 통한 객체 생성 방법 >
* 클래스명<제네릭타입명> 변수명 = new 클래스명<제네릭타입명>();
*
*/
// Collection Framework(ArrayList 등)의 객체들은 파라미터로 Object 타입을 주로 사용
ArrayList list = new ArrayList();
// Object 타입 파라미터를 갖는 add() 메서드는 어떤 데이터타입도 모두 추가 가능하므로
// 데이터를 추가하는 시점에서는 매우 편리함.
list.add(1);
list.add("TWO");
list.add(3.14);
// 반복문을 사용하여 List 객체의 모든 요소 꺼내서 변수에 저장 후 출력
for (int i = 0; i < list.size(); i++) {
// int data = list.get(i); // 오류 발생!(Object 타입 리턴 시 int 타입에 저장 불가)
// int data = (int) list.get(i); // 오류 발생!
// => 첫번째 데이터는 정수이므로 int형으로 변환 가능하지만,
// 두번째 데이터는 문자열이므로 int형으로 변환이 불가능함
// 모든 데이터타입을 구분없이 저장하기 위해서는 Object 타입 변수 필요함
Object data = list.get(i); // 형변환 불필요
// System.out.println(data);
// => 하나의 변수에 모든 타입을 저장 가능한 장점이 있지만
// 저장된 데이터를 활요하여 작업을 수행하는 경우
// 각 타입이 달라서 문제가 발생할 가능성이 있다!
// 만약, 어떤 문제도 발생하지 않도록 각 데이터타입에 맞는 변수에 저장하려면
// 데이터를 꺼낼 때 타입 판별을 통해 알맞은 타입으로 저장해야한다!
// => instanceof 연산자를 사용하여 각 타입 판별 후 변수에 저장
// 각 타입으로 저장할 때 Object -> 각 타입으로 형변환 필수!
if (data instanceof Integer) { // 정수 판별
int iData = (int) data;
System.out.println(iData);
} else if (data instanceof String) { // 문자열 판별
String strData = (String) data;
System.out.println(strData);
} else if (data instanceof Double) { // 실수 판별
double dData = (double) data;
System.out.println(dData);
}
// => Object 타입을 사용하여 데이터를 저장하는 경우
// 저장 시점에서는 데이터 타입 구분이 필요없으므로 편리하지만
// 실제 데이터를 사용하는 시점에서는 데이터타입이 달라서 문제가 발생할 수 있음
// => 또한, 각 데이터타입 변수에 저장할 때는 추가적으로 형변환이 필요함!
}
System.out.println("================================================");
// 제네릭 타입을 지정하여 ArrayList 객체들을 사용!
// => 객체 생성 시점에서 저장될(사용될) 데이터타입을 지정하면
// 해당 데이터타입만 사용 가능하도록 변경됨
// => 클래스명 뒤에 <> 기호를 명시하고, 기호 사이에 사용할 데이터타입 지정
// 데이터타입은 반드시 참조형 데이터타입만 지정 가능!(기본형 사용 불가)
// ex) int(x) -> Integer, char(x) -> Character
// 제네릭 타입으로 정수를 사용하기 위해 Integer 타입을 지정
ArrayList<Integer> list2 = new ArrayList<Integer>();
// 제네릭 타입으로 Integer 타입 지정 시
// Object 타입을 사용하던 모든 메서드가 Integer 타입으로 변화하게 됨
// 따라서, add() 메서드의 파라미터도 Integer 타입이므로
// 정수 타입 외의 나머지 데이터타입 추가 시 오류 발생하게 된다!
list2.add(1);
// list2.add("TWO"); // 컴파일 에러 발생! Integer 타입만 추가 가능
// list2.add(3.14); // 컴파일 에러 발생!
list2.add(2);
list2.add(3);
// => 데이터를 추가하는 시점(컴파일 시점)이세ㅓ 오류가 발견 가능하며
// 데이터타입 감지를 통해 오류 발생 가능성을 최소화 시킬 수 있다!
for (int i = 0; i < list2.size(); i++) {
// 반복문 등을 통해 데이터를 꺼낼 때
// 제네릭 타입으로 리턴되므로 별도의 형변환이나 데이터타입 검사가 불필요함
int data = list2.get(i);
System.out.println(data);
// => instanceof 불필요, 형변환 연산자 불필요
// => 특정 데이터타입만 저장된다는 보장이 생기므로 간편하게 사용 가능!
}
// 향상된 for문 사용 시
for (int data : list2) {
System.out.println(data);
}
System.out.println("==============================");
// ArrayList 객체(list3) 생성 => 제네릭 타입을 String으로 지정
ArrayList<String> list3 = new ArrayList<String>();
// => list3 객체의 대부분이 String 타입 파라미터 또는 리턴타입이 String으로 변경됨
System.out.println(list3.add("홍길동"));
// list3.add(1); // 컴파일 에러 발생!
for (String data : list3) {
System.out.println(data);
}
}
}
package ex_generic;
import java.util.*;
public class Test1 {
public static void main(String[] args) {
// Person 클래스 인스턴스 2개 생성
// "홍길동", 20
// "이순신", 44
Person p1 = new Person("홍길동", 20);
Person p2 = new Person("이순신", 44);
System.out.println(p1);
System.out.println(p2.toString());
// Person 객체 여러개를 하나의 객체에 저장하여 관리할 경우
// 1. Object[] 배열을 통해 관리
// => 배열은 크기가 불변이므로 추가적인 객체 저장 불가
Object[] objectArr = {p1, p2}; // Person -> Object 업캐스팅 되어 관리됨
for (int i = 0; i < objectArr.length; i++) {
// Object[] 배열 내의 Person 객체를 Object 타입 변수에 저장하면
// 업캐스팅 된 상태이므로 서브클래스인 Person의 상세정보가 보이지 않는다!
// Object o = objectArr[i];
// System.out.println(o.getName()); // 컴파일에러 발생! 참조영역 축소로 인해서 접근 불가!
// Object[] 배열 내의 Person 객체를 꺼내서 Person 타입 변수에 저장
// Person p = objectArr[i]; // Object -> Person 다운캐스팅 필수!
Person p = (Person) objectArr[i]; // 다운캐스팅
// Getter 메서드를 호출하여 이름, 나이를 출력
System.out.println("이름 : " + p.getName());
System.out.println("나이 : " + p.getAge());
}
// 2. ArrayList(Collection)를 활용하여 Person 객체 여러개를 관리할 경우
// => 배열의 단점인 크기 불변을 해결하게 되므로 여러 객체를 자유롭게 추가 가능
// 1) 제네릭을 사용하지 않을 경우
// - 파라미터 또는 리턴타입 Object 타입이 되어 다양한 객체 저장 가능
// - 저장 시점에서 타입 판별이 이루어지지 않으므로 편리하지만
// 대신 데이터를 꺼내는 시점에서 타입 불일치에 따른 오류 발생 가능성이 존재!
// - 데이터를 꺼내기 전 instanceof 연산자를 통한 타입 판별을 수행해야하며
// Objcet 타입을 실제 데이터타입으로 다운캐스팅을 수행!
ArrayList list = new ArrayList();
list.add(p1);
list.add(p2);
list.add(new Person("강감찬", 35)); // 새로 추가되는 객체도 자동으로 확장되어 저장
list.add("전지현"); // Person 객체가 아닌 데이터도 추가가 가능함
// => Person 객체 형태로 꺼내서 사용하는 시점에서 문제가 발생할 수도 있다!
// for문을 사용하여 ArrayList 객체 반복
// => Person 타입 변수에 저장 후 Getter 메서드 호출
for (int i = 0; i < list.size(); i++) {
// 잘못된 데이터가 저장되어 있을 수도 있으므로 타입 판별 후 형변환 필요함
if (list.get(i) instanceof Person) { // list.get(i) 가 Person 타입인가?
// System.out.println(list.get(i));
// System.out.println(list.get(i).getName()); // 오류 발생!
// Person p = list.get(i); // 리턴타입이 Object 이므로 Person 타입에 저장 불가!
Person p = (Person) list.get(i); // 다운캐스팅 필수!
// System.out.println(p);
System.out.println("이름 : " + p.getName());
} else {
System.out.println("타입이 일치하지 않습니다!");
}
}
System.out.println("===========================================");
// 2) 제네릭을 사용할 경우
// => 저장할 객체 타입이 Person 타입이므로 제네릭 타입 <Person> 지정
// - 객체 저장 시 Person 타입 객체만 저장 가능하도록 자동으로 판별 수행
// 즉, 잘못된 객체가 저장될 우려가 없다!
// - Object 타입으로 업캐스팅 되지 않고 Person 타입 자체로 저장되기 때문에
// 객체를 꺼내는 시점에서도 Person 타입 그대로 꺼낼 수 있다!
// => 별도의 다운캐스팅 등 형변환이 불필요!
// - 또한, 추가적인 타입 판별도 불필요!
ArrayList<Person> list2 = new ArrayList<Person>();
// 제네릭타입 <Person> 지정 시 사용 가능한 객체는 무조건 Person 타입 객체만 사용 가능!
list2.add(p1);
list2.add(p2);
list2.add(new Person("강감찬", 35)); // 새로 추가되는 객체도 자동으로 확장되어 저장
// Person 타입이 아닌 객체를 추가 할 경우
// 데이터 타입 판별에 의해 오류가 발생하게 된다!
// list2.add("전지현"); // 컴파일 에러 발생!
// list2.add(new Object());
// 반복문을 통해 list2 인스턴스의 모든 요소 꺼내서 출력
for (int i = 0; i < list2.size(); i++) {
// Person 타입 변수에 저장해서 출력
Person p = list2.get(i); // 형변환 불필요!(리턴타입이 Person)
System.out.println("이름 : " + p.getName());
}
}
}
/*
* Person 클래스 정의
* - 멤버변수 : 이름(name, 문자열), 나이(age, 정수) => private
* - 생성자 : 이름과 나이를 전달받아 초기화하는 생성자
* - Getter / Setter 정의
* - toString() 메서드 오버라이딩
* => ex) 이름, 나이
*/
class Person {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return name + ", " + age;
}
}