컴파일시 타입을 체크해주는 기능입니다.
이전까지는 타입이 체크가 안되어 runtime error가 발생하던것을 지네릭스를 만들어 complie error로 변경해서 미리 형 변환 에러를 잡기 용이합니다.
객체의 타입 안정성을 높이고 형 변환의 번거로움을 줄여줍니다.(코드의 간결)
//Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
tvList.add(new Audio()); //컴파일에러발생!
//Tv t = (Tv)tvList.get(0); // 형변환 불필요
Tv t = tvList.get(0);
JDK1.5(2000년대초)부터 아래와 같이 지네릭 클래스로 변경된 컬렉션 프레임워크가 등장했습니다.
ArrayList(일반클래스) -> ArrayList(지네릭클래스)
클래스를 작성할때, Object타입 대신 타입 변수(E)를 선언해서 사용합니다.
public class ArrayList extends AbstractList {
private transient Object[] elementData;
public boolean add(Object o) { }
public Object get(int index) { }
. . .
}
//위 클래스를 아래와 같이 변경합니다.
public class ArrayList<E> extends AbstractList<E> {
private transient E[] elementData;
public boolean add(E o) { }
public E get(int index) { }
. . .
}
객체를 생성할 시, 타입변수(E) 대신 실제 타입을 대입해줍니다.
ArrayList<Tv> tvList = new ArrayList<Tv>();
Box<T> - 지네릭 클래스. 'T의 Box'또는 'T Box'라고 읽는다.
T - 타입변수 또는 타입 매개변수.(T는 타입문자)
Box - 원시타입(일반클래스)
//아래 String은 대입된 타입(매개변수화된 타입)이라고 부른다.
//Box<String>은 지네릭 타입 호출을 한 모습니다.
Box<String> b = new Box<String>();
참조 변수와 생성자의 대입된 타입은 일치해야합니다.
상속관계에 있는 클래스들이라 할지라도 타입을 다르게 적으면 에러가 발생합니다.
class Product { }
class Tv extends Product { }
class Audio extends Product { }
ArrayList<Tv> list = new ArrayList<Tv>();
ArrayList<Product> list = new ArrayList<Tv>(); // 에러발생
//그러나 지네릭 클래스간의 다형성은 용인됩니다.
List<Tv> list = new ArrayList<Tv>();
List<Tv> list = new LinkedList<Tv>();
//참조변수 선언 후 매개변수의 다형성은 허용됩니다.
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());
list.add(new Audio());
지네릭 클래스의 활용예
Iterator<E>클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용합니다
//일반클래스
public interface Iterator {
boolean hasNext();
Object next();
void remove();
}
//지네릭 클래스로 변경
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
예제)
import java.util.*;
class Ex12_2 {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("자바왕", 1, 1));
list.add(new Student("자바짱", 1, 2));
list.add(new Student("홍길동", 2, 1));
Iterator<Student> it = list.iterator();
//Iterator it = list.iterator();
while(it.hasNext()) {
Student s = it.next();
//Student s = (Student)it.next();
System.out.println(s.name);
}
}
}
class Student {
String name = "";
int ban;
int no;
Student(String name, int ban, int no) {
this.name = name;
this.ban = ban;
this.no = no;
}
}
결과)
자바왕
자바짱
홍길동
여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언합니다.
public class HashMap<K,V> extends AbstractMap<K,V> {
. . .
public V get(Object key) { }
public V put(K key, v value) { }
public V remove(Object key) { }
. . .
}
get(), remove()의 key가 그냥 Object인 이유
내부적으로 쓰이는 hash메서드의 매개변수 타입이 Object이기 때문입니다.
예제)
import java.util.*;
class Ex12_2 {
public static void main(String[] args) {
//JDK1.7부터 생성자에 타입지정 생략가능
HashMap<String, Student> map = new HashMap<>();
map.put("자바왕", new Student("자바왕", 1, 2, 100, 100, 100));
System.out.println(map);
Student s = map.get("자바왕");
System.out.println(s.name);
}
}
class Student {
String name = "";
int ban;
int no;
int kor;
int eng;
int math;
Student(String name, int ban, int no, int kor, int eng, int math) {
this.name = name;
this.ban = ban;
this.no = no;
this.kor = kor;
this.eng = eng;
this.math = math;
}
}
결과)
{자바왕=Student@5ca881b5}
자바왕
extends로 대입할 수 있는 타입을 제한할 수 있습니다.
//Fruit의 자손객체만 지네릭 타입으로 제한
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
. . .
}
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> appleBox = new FruitBox<Toy>(); // 에러
//인터페이스인 경우에도 extends를 사용합니다.
interface Eatable { }
class FruitBox<T extends Eatable> { }
//만약 인터페이스와 함께 타입제한을 지정하는 경우 아래와 같이 작성합니다.
class FruitBox<T extends Fruit & Eatable> { }
class Box<T> {
static T item; // 에러
static int compate(T t1, T t2) { } // 에서
. . .
}
class Box<T> {
T[] itemArr;
. . .
T[] toArray() {
T[] tmpArr = new T[item.Arr.length]; // 에러
. . .
}
}
하나의 참조변수로 대입된 타입이 다른 객체를 참조 가능합니다.
ArrayList<? extends Product> list = new ArrayList();
ArrayList<? extends Product> list = new ArrayList();
타입 | 종류 |
---|---|
<? extends T> | 와일드 카드의 상한 제한. T와 그 자손들만 가능 |
<? super T> | 와일드 카드의 하한 제한. T와 그 조상들만 가능 |
<?> | 제한없음. 모든 타입이 가능. <? extends Object>와 동일 |
이중 <? extends T>가 제일 많이 사용됩니다.
//메서드의 매개변수에 와일드를 사용할 수 있습니다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) {
tmp += f + " ";
}
return new Juice(tmp);
}
지네릭 타입이 선언된 메서드를 의미합니다.(타입변수는 메서드 내에서만 유효)
static <T> void sort(List<T> list, Comparator<? super T> c)
클래스의 타입 매개변수와 메서드의 타입 매개변수 는 별개입니다.
class FruitBox<T> {
. . .
static <T> void sort(List<T> list, Comparator<? super T> c) {
. . .
}
}
메서드를 호출할 때마다 타입을 대입해야합니다.
class Juicer {
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) {
tmp += f + " ";
}
return new Juice(tmp);
}
}
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));
순수 지네릭 타입간의 형변환은 불가능합니다.
지네릭 타입과 원시 타입간의 형변환은 가능하긴하나 바람직 하지 않습니다.
Box<Object> objBox = null;
Box box = (Box)objBox; // 가능하나 경고발생
objBox = (Box<Object>)box; // 가능하나 경고발생
와일드 카드가 사용된 지네릭 타입으로는 형변환이 가능합니다.
Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러
Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>();
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일
//일반 제너릭 참조변수에 와일드카드 제너릭을 바인딩 할 수 있으나 경고가 발생합니다.
Box<String> sBox = (Box<? extends Object>)wBox;
컴파일시 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣습니다.
class Box<T extends Fruit> {
void add(T t) {
. . .
}
}
class Box {
void add(Fruit t) {
. . .
}
}
//만약 지네릭 제한자가 있다면 해당 클래스로 그렇지 않다면 Object로 형변환됩니다.
//위와 같이 되는 이유는 하위호환성때문에 그러합니다.
//이전 버젼과 같이 호환되게 해야 안정성이 유지됩니다.
T get(int i) {
return list.get(i);
}
Fruit get(int i) {
return (Fruit)list.get(i);
}
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) {
tmp += f + " ";
}
return new Juice(tmp);
}
static Juice makeJuice(FruitBox box) {
String tmp = "";
Iterator it = box.getList().iterator();
while(it.hasNext()) {
tmp += (Fruit)it.next() + " ";
}
return new Juice(tmp);
}
관련된 상수들을 같이 묶어 놓은 것, Java는 타입에 안전한 열거형을 제공합니다.
class Card {
static final int CLOVER = 0;
static final int HEART = 1;
static final int DIAMOND = 2;
static final int SPADE = 3;
static final int TWO = 0;
static final int THREE = 1;
static final int FOUR = 2;
final int kind;
final int value;
}
//아래와 같이 열거형으로 변경
class Card {
enum Kind {CLOVER, HEART, DIAMOND, SPADE}
enum Value {TWO, THREE, FOUR}
final Kind kind;
final Value value;
}
비교연산자 이용시 값과 타입 둘다 체크합니다.
다른 타입끼리는 값이 같더라도 비교를 할 수 없습니다.
if(Card.Kind.CLOVER==Card.Value.TWO) //컴파일 에러.
열거형을 정의하는 방법
enum 열거형 이름 {상수명1, 상수명2, . . .}
열거형 타입의 변수를 선언하고 사용하는 방법
class Unit {
int x, y; // 유닛의 위치
Direction dir; // 열거형 인스턴스 변수를 선언(상수 1개만 저장가능)
void init() {
dir = Direction.EAST; // 유닛의 방향을 EAST로 초기화
}
}
열거형 상수의 비교에는 ==와 compareTo()를 사용해야합니다.
if (dir==Direction.EAST) { // true
x++;
} else if(dir > Direction.WEST) { // 에러발생, 비교연산자 사용불가
. . .
} else if(dir.compareTo(Direction.WEST) > 0) { // compareTo를 사용해야 가능
. . .
}
모든 열거형은 Enum의 자손이며, 아래의 메서드를 상속받습니다.
메서드 | 설명 |
---|---|
Class getDclaringClass() | 열거형의 Class객체를 반환 |
String name() | 열거형 상수의 이름을 문자열로 반환 |
int ordinal() | 열거형 상수가 정의된 순서를 반환(0부터 시작) |
T valueOf(Class enum Type, String name) | 지정된 열거형에서 name과 일치하는 열거형 상수를 반환 |
values(), valueOf()는 컴파일러가 자동으로 추가됩니다.
static E valueOf(String name) - 열거형 변수로 반환할때 사용합니다.
static E[] values() - 열거형이 갖고있는 모든 상수를 배열로 반환합니다.
예제)
enum Direct { EAST, SOUTH, WEST, NORTH }
class Ex12_5 {
public static void main(String [] args) {
Direct d1 = Direct.EAST;
Direct d2 = Direct.valueOf("WEST");
Direct d3 = Enum.valueOf(Direct.class, "EAST");
System.out.println("d1="+d1); // d1=EAST
System.out.println("d2="+d2); // d2=WEST
System.out.println("d3="+d3); // d3=EAST
System.out.println("d1==d2 ? "+(d1==d2)); // d1==d2 ? false
System.out.println("d1==d3 ? "+(d1==d3)); // d1==d3 ? true
System.out.println("d1.equals(d3) ? "+d1.equals(d3)); // d1.equals(d3) ? true
//System.out.println("d2 > d3 ? "+(d2 > d3)); //에러
//열거형은 비교연산자를 사용할 수 없기 때문에 compareTo()를 사용
System.out.println("d1.compareTo(d3) ? " + (d1.compareTo(d3))); // d1.compareTo(d3) ? 0
System.out.println("d1.compareTo(d2) ? " + (d1.compareTo(d2))); // d1.compareTo(d2) ? -2
switch(d1) {
case EAST: //Direct.EAST라고 쓸수 없다.
System.out.println("The direction is EAST");
break;
case SOUTH:
System.out.println("The direction is SOUTH");
break;
case WEST:
System.out.println("The direction is WEST");
break;
case NORTH:
System.out.println("The direction is NORTH");
break;
}
//The direction is EAST
}
}
불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적습니다.
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
괄호를 사용하려면, 인스턴스 변수와 생성자를 새로추가해 줘야합니다.
enum Direction {
EAST(1), SOUTH(5), WEST(-1), NORTH(10);
private final int value; // 정수를 저장할 필드
//EAST(1), SOUTH(5), ...이 모든 상수들은 사실 생성자를 사용하는 것과 마찬가지 입니다.
Direction(int value) { //생성자를 추가
this.value = value;
}
public int getValue() {
return value;
}
}
열거형의 생성자는 묵시적으로 private이므로, 외부에 객체생성이 불가능 합니다.
Direction d = new Direction(1); // 에러. 열거형의 생성자는 외부에서 호출불가
예제)
enum Direct2 {
EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^");
private static final Direct2[] DIR_ARR = Direct2.values();
private final int value;
private final String symbol;
Direct2(int value, String symbol) {
this.value = value;
this.symbol = symbol;
}
public int getValue() {
return value;
}
public String getSymbol() {
return symbol;
}
public static Direct2 of(int dir) {
if (dir < 1 || dir > 4) {
throw new IllegalArgumentException("Invalid value :" + dir);
}
return DIR_ARR[dir - 1];
}
// 방향을 회전시키는 메서드. num의 값만큼 90도씩 시계방향으로 회전한다.
public Direct2 rotate(int num) {
num = num % 4;
if(num < 0) num +=4; // num이 음수일 때는 시계반대 방향으로 회전
return DIR_ARR[(value-1+num) % 4];
}
public String toString() {
return name()+getSymbol();
}
}
class Ex12_6 {
public static void main(String[] args) {
for(Direct2 d : Direct2.values()) {
System.out.printf("%s = %d \n", d.name(), d.getValue());
}
//EAST = 1
//SOUTH = 2
//WEST = 3
//NORTH = 4
Direct2 d1 = Direct2.EAST;
Direct2 d2 = Direct2.of(1);
System.out.printf("d1 = %s, %d \n", d1.name(), d1.getValue()); // d1 = EAST, 1
System.out.printf("d2 = %s, %d \n", d2.name(), d2.getValue()); // d2 = EAST, 1
System.out.println(Direct2.EAST.rotate(1)); // SOUTHV
System.out.println(Direct2.EAST.rotate(2)); // WEST<
System.out.println(Direct2.EAST.rotate(-1)); // NORTH^
System.out.println(Direct2.EAST.rotate(-2)); // WEST<
}
}
주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공합니다.
설정파일(.xml)을 소스코드(.java)안에 함께 작성하는 방법을 찾다가 애너테이션이 탄생되었습니다.
/* ~ / - javadoc.exe를 위한 주석(문서화를 위한 특수주석)
애너테이션의 사용예
//@Test는 JUnit단위 테스트 프로그램을 위한 애너테이션입니다.
@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
. . .
}
Java에서 제공하는 애너테이션
애너테이션 | 설명 |
---|---|
@Override | 컴파일러에게 오버라이딩 메서드라는 것을 알린다. |
@Deprecated | 앞으로 사용하지 않을것을 권장하는 대상에 붙인다. |
@SuppressWarnings | 컴파일러의 특정 경고메세지가 나타나지 않게 해준다. |
@SafeVarargs | 지네릭스 타입의 가변인자에 사용한다. |
@FunctionalInterface | 함수형 인터페이스라는 것을 알린다. |
@Native | native메서드에서 참조되는 상수 앞에 붙인다. |
애너테이션을 만들때 사용합니다.
애너테이션 | 설명 |
---|---|
@Target | 애너테이션이 적용가능한 대상을 지정하는데 사용한다. |
@Documented | 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다. |
@Inherited | 애너테이션이 자손 클래스에 상속되도록 한다. |
@Retention | 애너테이션이 유지되는 범위를 지정하는데 사용한다. |
@Repeatable | 애너테이션을 반복해서 적용할 수 있게 한다. |
오버라이딩을 올바르게 했는지 컴파일러가 체크하게 합니다.
오버라이딩할 때 메서드이름을 잘못적는 실수를 하는 경우가 많습니다.
예시)
class Parent {
void parentMethod() { }
}
class Child extends Parent {
//함수명의 대소문자를 틀리는 경우
void parentmethod() { }
}
//아래와같이 변경해줍니다.
class Child extends Parent {
@Override
void parentmethod() { } // 컴파일러가 오류가 발생했다고 전달
}
앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙입니다.
예시)
//Date클래스의 getDate()
@Deprecated
public int getDate() {
return normalize().getDayOfMonth();
}
위 메서드를 사용할 경우 컴파일러에서 경고메세지가 나옵니다.
함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크합니다.(람다식에서 자세히..)
함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약이 있습니다.
작성하지 않아도되나 사용할 시 실수를 줄여줍니다.
예시)
@FunctionalInterface
public interface Runnable {
public abstract void run(); //추상메서드
}
메서드가 한개가 아닐시 에러가 발생합니다.
컴파일러의 경고메세지가 나타나지 않게 억제합니다.
괄호()안에 억제하고자 하는 경고의 종류를 문자열로 지정합니다.
예시)
@SuppressWarnings("unchecked") //지네릭스와 관련된 경고를 억제
ArrayList list = new ArrayList(); //지네릭 타입을 지정하지 않았음
list.add(obj);
//둘이상의 경고를 동시에 억제하려면 다음과 같이 해야합니다.
@SuppressWarnings({"deprecation", "unchecked", "varargs"})
eclipse과 같은 ide에서는 컴파일시 자세한 경고문이 안나올 수 있습니다.
CLI환경에서 "javac -Xlint 파일명.java"다음과같이 컴파일하면 자세한 경고문을 볼 수 있습니다.
애너테이션을 만들 때 사용하는 애너테이션입니다.
메타 애너테이션은 java.lang.annotation패키지에 포함됩니다.
애너테이션을 정의할 때, 적용대상 지정에 사용합니다.
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppreddWarnings {
String[] value();
}
대상타입 | 의미 |
---|---|
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수 |
TYPE_USE | 타입이 사용되는 모든곳 |
예시)
import static.java.lang.annotation.ElementType.*;
@Target({FILED, TYPE, TYPE_USE})
public @interface MyAnnotation { } //MyAnnotation을 정의
@MyAnnotation //적용대상이 TYPE인 경우
class MyClass {
@MyAnnotation // 적용대상이 FILED인 경우
int i;
@MyAnnotation // 적용대상이 TYPE_USE인 경우
MyClass mc;
}
애너테이션이 유지(retention)되는 기간을 지정하는데 사용합니다.
유지 정책 | 의미 |
---|---|
SOURCE | 소스파일에만 존재. 클래스파일에는 존재하지 않음 |
CLASS | 클래스 파일에 존재. 실행시에 사용불가. 기본값 |
RUNTIME | 클래스 파일에 존재. 실행시에 사용가능 |
컴파일에 의해 사용되는 애너테이션의 유지정책은 SOURCE입니다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) //컴파일시만 사용
public @interface Override { }
실행시에 사용 가능한 애너테이션 정책은 RUNTIME입니다.
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) //런타임중에도 사용
public @interface FunctionalInterface { }
javadoc으로 작성한 문서에 포함시키려면 @Documented를 붙입니다.
애너테이션을 자손클래스에서 상속하고자 할 때, @Inherited를 붙입니다.
예시)
@Inherited
@interface SuperAnno { }
@SuperAnno
class Parent { }
class Child extends Parent { } // Child에 애너테이션이 붙은 것으로 인식
반복해서 붙일 수 있는 애너테이션을 정의할 때 사용합니다.
예시)
@Repeatable(ToDos.class)
@interface ToDo {
String value();
}
//@Repeatable인 @ToDo를 하나로 묶을 컨테이너 애너테이션도 정의해야합니다.
@interface ToDos { //여러개의 ToDo애너테이션을 담을 애너테이션 ToDos
ToDo[] value(); // ToDo애너테이션 배열타입의 요소를 선언. 이름이 반드시 value여야함
}
//@Repeatable이 붙은 애너테이션은 반복해서 붙일 수 있습니다.
@ToDo("delete test codes")
@ToDo("override inherited methods")
class MyClass {
. . .
}
애너테이션을 직접 만들어 쓸 수 있습니다.
@interface 애너테이션이름 {
타입 요소이름(); //애너테이션의 요소를 선언한다.
. . .
}
애너테이션의 메서드는 추상 메서드이며, 애너테이션을 적용할 때 지정합니다.(순서X)
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType { FIRST, FINAL }
DateTime testDate(); // 자식이 아닌 다른 애너테이션(@DateTime)을 포함할 수 있다.
}
//실사용예
@TestInfo(
count=3, testedBy="Kim",
testTools={"JUnit","AutoTester"},
testType=TestType.First,
testDate=@DateTime(yymmdd="160101", hhmmss="235959")
)
public class NewClass {
. . .
}
적용시 값을 지정하지 않으면, 사용될 수 있는 기본 값 지정이 가능합니다.(null제외)
@interface TestInfo {
int count() default 1;
}
@TestInfo // @TestInfo(count=1)과 동일
public class NewClass {
. . .
}
요소가 하나이고 이름이 value일 때는 요소의 이름을 생략가능합니다.
@interface TestInfo {
String value();
}
@TestInfo("passed") //@TestInfo(value="passed")와 동일
public class NewClass {
. . .
}
요소의 타입이 배열인 경우, 괄호{}를 사용해야합니다.
@interface TestInfo {
String[] testTools();
}
@TestInfo(testTools={"JUnit", "AutoTester"})
public class NewClass {
. . .
}
java.lang.annotation.Annotation
Annotation은 모든 애너테이션의 조상이지만 상속은 불가능합니다.
@interface TestInfo extends Annotation { // 에러발생
...
}
사실 Annotation은 인터페이스입니다.
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String to String();
Class<? extends Annotation> annotationType(); //애너테이션의 타입을 반환
}
요소가 하나도 정의되지 않은 애너테이션
public @interface Override { }
public @interface Test { }
@Test
public void method() {
. . .
}
애너테이션의 요소를 선언할 때 아래의 규칙을 반드시 지켜야 합니다.
@interface AnnoTest {
int id = 100; // 상수는 써도된다
String major(int i, int j); //에러 매개변수X
String minor() throws Exception; // 에러 예외X
ArrayList<T> list(); //에러 타입매개변수X
}