Generic
제너릭을 사용하지 않는다면
// 방법 1
IntArrayList arr1 = new IntArrayList();
StringArrayList arr2 = new StringArrayList();
BooleanArrayList arr3 = new BooleanArrayList();
// 방법 2
ObjectArrayList arr4 = new ObjectArrayList();
Java
복사
방법 1 : 타입 별로 객체를 만든다.
타입이 다르지만 로직이 같아 객체를 만들때 중복코드가 많아진다.
방법2 : 모든 타입의 조상인 Object 타입의 객체를 생성해 모든 타입을 받을 수 있게 한다.
Object 타입을 사용하다보니 특정 타입의 연산과 메서드를 사용하기 위해 형변환이 필수적이다.
((int)a + (int)b , (String)s + (String)t 처럼 같은 연산+는 타입 별로 정의가 다르다.)
제네릭 사용
class 저장소<T>{
Object data;
T getData(){
return data;
}
}
// 클래스만 봤을 때 미완성의 느낌
저장소<Integer> a저장소 = new 저장소<>();
저장소<String> b저장소 = new 저장소<>();
// 제네릭 클래스(인터페이스) 하나로 여러 타입의 객체를 생성할 수 있다.
Java
복사
이처럼 제네릭은 (클래스) 내부에서 지정하는 것이 아닌 외부(객체를 생성)에서 사용자에 의해 지정되는 일반 타입을 의미한다.
1.
코드의 재사용성이 높아진다. (방법 1 문제 해결)
2.
클래스 외부에서 타입을 지정해주기 때문에 타입을 변환해줄 필요없다. (방법 2 문제 해결)
3.
컴파일 단계에서 타입을 체크해 에러를 방지할 수 있다.
// java Collections 의 구현 코드
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable{...}
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {...}
Java
복사
E, K, V 는 어떤 의미가 있을까
같은 클래스 내에 서로 다른 제너릭 형을 구분하기 위한 용도를 제외하곤 별 의미가 없다.
LinkedList의 경우 Element를 의미하는 E를 사용하고, HashMap은 Key, Value를 의미하는 K,V를 사용한다. (앞써 말했듯 시멘틱의 용도이고 그 이상의 의미는 없다.)
제네릭 배열을 생성하지 못하는 이유
1.
제네릭과 배열의 차이
// 배열
Object[] arr = new String[1];
arr[0] = 1;
Java
복사
배열은 ‘공변' 이므로 위 코드는 컴파일을 성공한다.
컴파일러는 String와 Integer는 Object의 상위타입이므로 가능한 코드라고 판단하지만 두 번째 줄에서 런타임에 ArrayStoreException이 발생한다.
→ String[] 는 Object[]의 상위타입이므로 Object[]로 변할 수 있다.
// 제네릭
ArrayList<Object> list = new ArrayList<String>();
Java
복사
제네릭은 ‘불공변'이므로 위 코드는 컴파일 에러가 발생한다.
→ 제네릭에서는 Object는 Object일 뿐
(ArrayList<?> : 비한정적 와일드카드 타입은 모든 제네릭 타입을 가질 수 있다. )
공변 → 함께 변한다.
https://hwan33.tistory.com/24
2.
런타임 때 배열이 실체화, 제네릭은 소거
배열은 런타임에 실제로 들어있는 데이터의 타입을 알게된다.
제네릭은 런타임에 버려지게 되고 어떠한 타입도 들어올 수 있게 된다. 하지만 컴파일 타입에 미리 에러가 발생할 코드를 걸러내기 때문에 안전하다.
T[] arr = new T[0];
따라서 제네릭 배열은 실체화가 불가능하다.
HashMap
Map 인터페이스는 키와 값으로 구성된 Entry 객체를 저장하는 자료 구조이다.
HashMap은 key의 해시값을 보고 지정된 위치에 값을 저장하므로 검색이 빠르지만 저장순서는 보장하지 않는다. (LinkedHashMap은 순서를 보장)
hashCode()
hashCode는 각 객체가 갖는 유일한 값을 의미하고 key 해시값 계산에 사용한다. (HashMap은 해시값이 같다면 equals() 로 정말 같은지 확인한다.)
만약 클래스를 새로 만들어 HashMap의 Key로 쓰기 위해서 hashCode()와 equals()를 재정의해 해싱을 의도대로 수행되도록 해야한다.
class Person{
String name;
int age;
@Override
public int hashCode(){
return name.hashCode() + id.hashCode();
}
@Override
public boolean equals(Object o){
return this.hashCode == o.hashCode;
}
}
Java
복사
hashCode 와 equals
hashCode는 heap에 저장된 객체의 메모리 주소를 반환한다. (heap이므로 런타임에 결정됨) equals 는 두 객체가 같은지 검사하는 메서드이다.
동일한 객체는 동일한 메모리 주소를 가져야 한다는 것을 의미한다.
동일한 객체 : a.equals(b) = true /
동일한 메모리 주소 : hashCode(a) == hashCode(b)
그렇다고 동일한 메모리 주소를 가져도 객체는 동일할 필요가 없다.
Interface 와 abstract class
•
추상클래스 : 하나 이상의 추상 메서드가 포함된 클래스
•
인터페이스 : 모든 메서드가 추상 메서드
추상클래스든 인터페이스든 모두 상속받는 자식에서 추상 메서드를 구현해야 한다는 공통점을 가지고 있지만 다른 목적을 가진다.
추상클래스는 부모에게 상속받아 기능을 이용한다는 확장의 목적를 가지고 있고, 인터페이스는 상속받은 자식에게 구현을 강제해서 객체가 같은 동작을 보장하는 목적을 가지고 있다.
클래스는 다중상속 불가인 이유는?
class A{ void say(){System.out.println("A"};}
class B{ void say(){System.out.println("B"};}
class C extends A,B{}
...
C c = new C();
c.say(); // A,B 둘 중 어느 say를 해야되나... 다중상속의 폐해
Java
복사
다중상속이 가능하다면 위와 같은 상황에서 어느 메서드를 호출해야 할지 모르기 때문에 불가능하다. → 다중상속의 모호성
(다중상속의 폐해는 오버라이딩으로 막을 수 있지만 자바에선 오버라이딩이 필수가 아니다. )
final 키워드가 붙을 수 없다.
final이 붙은 class는 상속이 불가능하다는 것을 명시하는 것이기 때문에 상속으로 추상 메서드를 채워야하는 추상 클래스와 인터페이스는 final과 공존할 수 없다.
static 과 상속의 관계
Dynamic Method Dispatch 복습
class Parent{
public void say(){System.out.println("parent");}
}
class Child extends Parent{
@Overried
public void say(){System.out.println("child");}
}
...
Parent p1 = new Parent();
Parent p2 = new Child();
Child c = new Child();
p1.say(); // parent
p2.say(); // child
p3.say(); // child
Java
복사
p2 는 컴파일 과정에서 참조타입을 확인해 Parent를 받는다. 하지만 런타임 에서 JVM이 객체의 타입을 확인하고 구현체의 메소드를 실행시킨다.
static과 인스턴스 차이
static은 메모리에 고정으로 할당해 프로그램이 끝날 때까지 유지한다. 그렇기 때문에 컴파일 시점에 JVM의 Method Area에 할당된다. → 정적 바인딩
인스턴스는 런타임에 생성이 되면 Heap에 할당된다. → 동적바인딩
오버라이딩은 결국 인스턴스 메서드이고, 런타임 시 일어나는 동적 바인딩이다.
그러므로 static 메서드는 부모 고유의 것이고 상속과는 관련이 없다.
(상속을 목적으로 하는 추상메서드 또한 static과 공존할 수 없다.)