티스토리 뷰

# 각 자료형의 초깃값

byte int long double char boolean 참조형
(byte)0 0 0L 0.0d '\u0000' false 공백 or NULL

- 배열을 선언하면 배열의 구성요소는 자동으로 0으로 초기화 된다.

- 메소드 내부에서 선언된 지역변수는 초깃값으로 초기화되지 않는다

# 접근 제한자

public: 모든 접근 허용

protected: 같은 패키지의 객체, 상속 관계의 객체 허용

default: 같은 패키지의 객체 허용

private: 현재의 객체 안에서만 사용

 

클래스: public, default

생성자: public, protected, default, private

멤버 변수: public, protected, default, private

멤버 메소드: public, protected, default, private

지역 변수는 접근제한자를 사용할 수 없음.

# 클래스

- final로 선언한 필드는 단 한번만 값을 대입할 수 있다.

- final 클래스는 서브 클래스를 가질 수 없다. (새로운 클래스를 상속할 수 없다.)

- 생성자는 새로 생성한 인스턴스의 초기화를 위해 사용한다

- Object 클래스는 모든 클래스의 상위 클래스이다. 

- 추상 클래스는 인스턴스를 만들 수 없다. - 세부내용은 상속하는 서브클래스에서 구현한다.

# 제네릭

- 제네릭이란 대상의 자료형에 의존하지 않는 클래스[인터페이스] 구현 방식

- 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법.

-> 타입 안정성 보장, 형변환의 번거로움 감소, 코드의 재사용성 향상.

 

<제네릭이 사용되지 않은 경우 - Object를 사용하는 경우.>

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; // StudentInfo와 EmployeeInfo의 공통조상인 Object 타입
    Person(Object info) {
        this.info = info; 
    }
}

public class MainClass {
    public static void main(String[] args) {
        Person p1 = new Person("부장"); // "타입이 안전하지 않다"
        EmployeeInfo ei = (EmployeeInfo) p1.info; // Object -> EmployeeInfo 형변환
        System.out.println(ei.rank);
    }
}

Person 클래스에서는 StudentInfo 혹은 EmployeeInfo 둘중 하나의 값을 받고자 Object로 타입을 세팅해놨는데,

만약 String값을 전해주게 된다고 해도, String 또한 Object의 자손 클래스 중 하나이기 떄문에 문법적으로는 에러가 없이 컴파일이 된다

-> 이는 컴파일과정에서는 문제가 없는데 런타임 실행중에서 exception이 터지기 때문에 더 큰 문제를 초래할 수 있다.

(컴파일 과정에서 에러를 잡아내는게 더더욱 유리하다)

-> 이를 "타입이 안전하지 않다" 라고 표현한다고 한다. -> 의도치 않은 타입이 들어올 가능성이 있다는 뜻.

 

<제네릭이 사용된 코드>

...

class Person<T, S> {
    public T info;
    public S id;
    Person(T info, S id) {
        this.info = info;
        this.id = id;
    }
    public <U> void printInfo(U info) { // 메소드 레벨에서 사용되는 제네릭
        System.out.println(info);
    }
}

public class MainClass {
    public static void main(String[] args) {
        EmployeeInfo e = new EmployeeInfo(1);
        Integer i = new Integer(10);
        // Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i); ** 생략 가능
        Person p1 = new Person(e ,i);
        // p1.<EmployeeInfo>printInfo(e); ** 생략 가능
        p1.printInfo(e);
    }
}

- 매개변수를 통해서 데이터 타입을 알아낼 수 있는 경우 타입 명시는 생략해도 된다.

- 메소드 레벨에서도 제네릭이 사용될 수 있다. 

 

 

< 규칙 >

1. 1개의 대문자를 사용한다 (소문자는 가급적 X)

2. 컬렉션의 자료형은 element의 앞글자인 E를 사용한다

3. Map의 key, value는 K, V를 사용한다.

4. 이외에는 일반적으로 T를 사용한다. 추가 변수는 S

5. 제네릭의 데이터 타입은 "참조형"만 올 수 있다 - 원시 자료형은 올 수 없다(int, long, double 등..)

-> Wrapper 클래스를 대체로 사용해야 한다 (int -> Integer, double -> Double ...)

(Wrapper 클래스? : 원시 자료형을 마치 객체인것처럼 동작하게끔 감싸주는 역할)

 

 

▶ 컬렉션 클래스 이름 뒤에 저장할 객체 타입을 적으면 지정 타입의 객체만 받을 수 있다.

- 다형성을 높이기 위해 부모타입을 지정하여 여러 종류의 객체를 받을 수도 있다

class Animal {...}
class Dog extends Animal {...}
class Cat extends Animal {...}

ArrayList<Animal> list = new ArrayList<Animal>(); // Animal 및 그 자손객체만 받을 수 있다

list.add(new Animal()); // O
list.add(new Dog()); // O
list.add(new TV()); // X
ArrayList<Animal> list = new ArrayList<Dog>(); // X
ArrayList<Dog> list = new ArrayList<Dog>(); // O

 

<와일드 카드>

<?> : 제한 없이 모든 타입이 올 수 있다

<? extends T> : T와 그 자손 타입만 올 수 있다

<? super T> : T와 그 조상 타입만 올 수 있다

static <T> void sort(List<T> list, Comparator<? super T> c)

- 정렬 대상 요소들의 타입(Integer, String 등..) 또는 그 조상 타입(~ Object 까지)만 매개변수로 받겠다는 뜻.

 

class Person<T> { ... } 
class Person<T extends Object> { ... }

위 두개는 동일한 역할을 한다

-> 이 경우에 타입 T는 Object가 갖고있는 메소드 (toString, clone 등..)만 사용할 수 있다

(모든 클래스가 가진 공통 메소드만 사용할 수 있다)

 

+) static에는 제네릭을 사용할 수 없다.

 

# 제네릭 배열을 바로 생성할 수 없는 이유?

public class Stack<E> {
    private E[] stk;
    ...
    Stack(int size) {
        this.stk = new E[size]; // Error!!!
        ...
    }
    ...
}

 

▶ 배열은 공변 - 컴파일은 통과하나 런타임에서 exception 발생

(sub가 super의 하위 타입일때 sub[]는 super[]의 하위 타입이 된다)

Object[] objects = new String[N];
objects[0] = 1; // 컴파일은 가능하나 런타임에서 exception 발생

- 컴파일 시에는 Object[]이지만 런타임 과정 중에는 String[]으로 변환되어 사용된다.

 

 

▶제네릭은 불공변 - 컴파일 단계에서 막힌다

(sub가 super의 하위 타입이어도 List<sub>와 List<super>는 서로 연관이 없다)

ArrayList<Object> list = new ArrayList<Integer>(); // 컴파일부터 불가능

-  때문에 타입 안정성을 지키고 에러가 있는 부분을 조기에 찾아낼 수 있다.

 

 

* 배열은 런타임에 타입이 실체화 된다

* 제네릭 타입은 런타임시에 소거된다

=> 배열은 런타임시 타입안전을 보장하나, 컴파일 시에는 보장하지 않고,

제네릭은 컴파일시 타입안전을 보장하나, 런타임 시에는 보장하지 않는다.

이 두가지가 서로 상반되기 때문에 제네릭과 배열을 결합하여 사용할 수 없는 것..!

 

# 그럼 제네릭 배열을 만드는 방법

1. 와일드 카드 사용

List<?>[] lists = new List<?>[5];
lists[0] = Arrays.asList(1234);
lists[1] = Arrays.asList("ABCD");
    for (List<?> list : lists) {
        System.out.println(list);
    }
}

- <?> 와일드 카드는 모든 타입이 전부 올 수 있다.

 

2. 형변환 활용

public class Stack<E> {
    private E[] stk;
    ...
    Stack(int size) {
        this.stk = (E[]) new Object[size];
    }
    ...
}

- Object 배열에서 (E[])으로 형변환을 거쳐 제네릭 배열을 만들 수 있다.

- 타입 안정성이 보장되지 않기 때문에 경고 라인이 생기나, 코드상 문제가 없다면 정상적으로 실행 된다

(경고 무시하는 방법 - @SuppressWarnings("unchecked"), 무점검 경고)

3. 배열을 굳이 사용하려 하지 말고 List를 사용하자 - 권장

 

# 기타 잡얘기

1. 컴퓨터에서 생성하는 난수는 진짜 난수가 아니다

- srand 메소드에 전달한 seed값이 같으면 결과값이 항상 동일하게 나오게 된다.

(미리 컴퓨터가 만들어둔 난수표가 있기때문에 시드가 같으면 항상 같은 결과가 나온다)

-> 이때 어떤 난수표를 읽을지를 결정하는게 바로 시드의 역할.

- 따라서 난수를 생성하기 위해서 또 난수가 필요한 상황이 생긴다.

- 현재 시간을 난수로 세팅하는 것이 일반적

 

2. java.lang 패키지는 자동 import 된다.

- Java언어와 밀접하게 연관된 클래스나 인터페이스 등을 모아놓은 java.lang 패키지는 import 할 필요가 없다..

- 때문에 이 패키지에 속하는 String, Integer 등을 사용할때 따로 import 하지 안아도 되는 것.

 

 

 

 

 

 

 

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함