
ㅎㅇ
오늘은 올리고싶은 마치사진을 못 찾아서 그냥 내사진 올림미다
색감이랑 패닝이랑 잘 된 것 같다. 근데 저거 셔속이 좀 낮았는데 어떻게 안 흔들리고 저렇게 잘 찍었지
인스타그램 데일리스크럼에 마치노래를 하나씩 담아서 올리는데... 22일차.. 내 플리에 있는 노래를 거의 다 담았다. 어떡할까요
..사실 이거 초안 작성이 22일차고 다 읽었을 때는 30일차였다ㄷㄷㄷㄷㄷㄷ
다이아몬드 연산자
ArrayList<String> songs = new ArrayList<>();
원래 정석적인 ArrayList 선언은 양쪽의 꺽쇠괄호에 ArrayList 객체에 들어갈 객체의 타입이 들어가야하는데, new 코드 뒤에 오는 ArrayList의 꺽쇠괄호에는 아무것도 들어가지 않는다. 이는 등호 왼쪽에 들어가는 값과 오른쪽에 들어가는 값이 동일하기 때문에 꺽쇠괄호에 아무것도 들어가지 않는다.
다만 변수 선언과 초기화 부분이 떨어져있다면 명시해야한다.
ArrayList의 다형성
ArrayList로 반환되는 경우에는 List로 반환타입을 정의해도 된다. List는 인터페이스
ArrayList에서 지원되는 메서드는 대부분 List에서 온 것이기 때문에 List의 하위 타입에서도 적용시킬 수 있다.
toString()
객체를 그대로 출력할 때는 오류가 생기는데, toString 메서드를 구현해서 출력문을 구성하면 객체를 출력할 때 어떻게 출력할지 미리 지정할 수 있다.
toString은 Object 클래스에 속해있는 메서드이며, 파이썬의 __str__ 메서드와 같은 역할을 한다.
제너릭과 타입 안정성
제너릭은 타입 안정성을 갖춘 컬렉션을 만들기 위한 용도로 사용된다고 한다.
우선 제너릭은 다양한 클래스와 메서드에서 다양한 타입을 안전하게 처리하기 위한 문법이다..고 한다.
제너릭을 사용하면 타입안정성을 지킴과 더불어 람다까지 사용할 수 있다.
그냥
new ArrayList<String>();
여기서 꺽쇠괄호 안에 들어가는 내용이 제너릭 타입지정이라고 보면 된다.

헤드퍼스트자바 독서 #4편에서 Object에 대한 내용을 잠깐 다뤘다.
저 사례처럼 제너릭 타입지정을 하지 않으면 ArrayList에는 모든 리스트나 컬렉션에 Object 타입의 객체를 추가할 수 있었고, 모든 ArrayList가 ArrayList<Object>로 선언되었다.
하지만 저 문제가 생길 수 있다. 나는 Dog 객체만 넣고 싶은데 누가 Cat 객체를 넣어버리면 .bark() 메서드는 사용할 수 없고 무용지물이 되어버린다. 컴파일러도 체크를 못하고.
제너릭을 쓰면 배열에 들어가는 각 객체의 레퍼런스를 지정하므로 Dog 객체가 들어갈 자리에 Cat 객체가 들어가도 컴파일러가 잡아낼 수 있다. 이처럼 타입 안정성을 위해 사용하는 문법이 바로 제너릭이다.
제너릭 클래스 사용하기
저런 제너릭 타입을 사용하는 클래스가 제너릭 클래스. 대표적으로 ArrayList와 ResponseEntity 등이 있다. 꺽쇠괄호 쓰는거
public interface List<E> extends Collection<E> {
...
boolean add(E e);
...
}
이게 List 인터페이스 클래스와 그 안에 있는 add 메서드 부분인
ArrayList의 경우에는 이 인터페이스를 implement하고 추가로 AbstractList<E>도 확장한다.
E는 컬렉션에 저장하고 컬렉션에서 리턴할 원소의 타입이다. 아직 타입이 정해지지 않았기 때문에 E를 쓰는거고 String, Integer, Dog 등으로 E가 정해지면 그 밑에 메서드들은 모두 E를 하나로 고정하게된다.
E라는 단어에 딱히 의미가 있는 건 아니고, Element에서 따왔다. E가 아니어도 돌아가긴한다. 개발자 간의 약속 정도?
컬렉션이나 리스트에 사용되는 제너릭은 '컬렉션에 저장할 원소'라는 뜻에서 E, 이외의 경우에는 일반적 타입이라는 뜻의 T, 반환형의 경우 R을 쓰기도 한다.
암튼
List<String>.... //뒤에 생략
이런 식으로 List를 불러왔다면 (List 뿐 아니라 List의 하위클래스인 ArrayList 같은 클래스도 포함)
List 선언부와 메서드에 있는 E값이 모두 String으로 들어가게 된다. 이 치환은 컴파일러가 한다. 그렇기 때문에 위에서 말한 타입 안정성이 가능하다는 것이다. E와 호환되지 않는 객체들은 저 자리에 들어갈 수 없기 때문에...
제너릭 메서드
public interface List<E> extends Collection<E> {
...
boolean add(E e);
...
}
다시 이 List 인터페이스로 돌아오면, 메서드로 add가 있는 것을 알 수 있다.
클래스 선언 시 타입 메서드를 선언했으므로 (여기서는 E) 메서드의 인자로 E를 줄 수 있다. E가 String으로 지정되면 리스트에 String 객체를 추가한다는 방식으로
다만 메서드에 클래스 선언부에서 쓰이지 않는 타입의 인자를 전해줄 수도 있는데
public <T extends Animal> void takeThing<ArrayList<T> list)
헤드퍼스트자바 11장 366쪽에 나온 코드인데
이렇게 메서드 선언부 리턴타입 선언부 바로 앞에서 선언해주면 된다. 저려면 클래스 선언부에서 제너릭 타입으로 지정한 인자에 관계없이 Animal 인자가 list에 들어갈 수 있다.
추가로
public <T extends Animal> void takeThing(ArrayList<T> list)
public void takeThing(ArrayList<Animal> list)
둘은 다르다
전자는 Animal 클래스의 하위클래스로 구성된 ArrayList는 무엇이든지 들어올 수 있으나
후자는 ArrayList<Animal>만 사용가능하다. 하위클래스는 불러올 수 없다.
제너릭의 확장
확장은, 구현과 같다. extends 로 사용되며 'A는 B이다'라는 관계를 나타낸다. 제너릭에서 extends는 클래스인지 인터페인스인지를 구분하지 않는다.
정렬하기 - 컬렉션
모든 List는 추가된 순서대로 원소들이 나열되며 저장된다.
List 이외에 Collection 클래스가 있는데, 원소들을 자연스러운 순서를 기준으로 정렬한다.
기본적으로 Collections.sort(); 메서드를 활용하면 (인자로는 List를 준다) 알파벳 순으로 값이 저장된다. 기호 -> 숫자 -> 영어대문자 A부터 -> 영어 소문자 a부터 순으로 값이 저장된다. 다만 이 방식은 String일 때 쓰는거고... 홍대 맛집 아카이빙 프로젝트 등에서 사용하는 Member 객체의 이름 순으로 정렬을 한다던가..하면 또 다른 문제가 있다.
Member 객체에는 이름 말고도 이메일, 투표가능여부 등의 여러 인스턴스가 있다. 따라서 컬렉션에 Member 객체를 넣어주기 위해서는 어느 것을 기준으로 할 지 정해야한다. 그냥 돌리면 컴파일 오류가 난다.
컬렉션의 메서드 sort의 API 문서에 들어가보면
public static <T extends Comparable<? super T>> void sord(List<T> list)
라고 sort 메서드가 선언되어 있는데, 이는 T가 반드시 Comparable 타입이어야하며, Comparable과 이를 확장하는 타입으로 구성된 (하위클래스 같이) List를 받는다는 뜻이다. ? super T 부분은 Comparable 타입의 매개변수가 T 또는 T의 상위타입이란 뜻
암튼
ArrayList<Member>
이런 식으로 구성된 ArrayList에서 sort 메서드를 실행시키면 오류가 생긴다. sort 메서드는 Comparable 타입을 확장(여기서 Comparable 타입은 인터페이스이므로 구현. extends를 썼지만 구현과 확장을 모두 내포한다)하는 객체만 제너릭 인자로 받을 수 있기 때문에 Comparable 객체의 구현체가 아닌 Member는 오류가 나고, String 은 선언부에서 Comparable을 구현해서 가능한 것이다.
Comparable 구성하기
Comparable 인터페이스에는
public interface Comparable<T> {
int compareTo(T o);
}
만 작성되어 있어서 sort 메서드에 넣은 객체에 이를 구현해주면 된다. 인자의 객체가 주어진 객체보다 작으면 음의 정수, 같으면 0, 크면 양의 정수를 리턴한다.
Member 객체에 compareTo 메서드를 추가해주고, 이 compareTo 메서드는 비교할 다른 객체 (보통은 Member 객체 간 비교가 이루어지니 Member 객체)를 인자로 받아서 비교하는 메서드를 작성해주면 된다.
class Member implements Comparable<Member> {
private String name;
private String age;
public int compareTo(Member member) {
return name.compareTo(member.getName());
}
...
}
이런식으로
어차피 getName을 통해 얻은 String 객체는 compareTo 메서드가 존재하므로 비교만 해주면 된다.
조건이 여러개일 때 - Comparator 사용하기
만약 Member 객체를 이름 순 뿐 아니라 나이 순으로 정렬하고 싶다면?
Collection.sort()는 인자로 리스트 뿐 아니라 Comparator를 줄 수 있고, List 타입도 Comparator를 받아 sort 메서드를 실행시킬 수 있다.
Comparator를 인자로 받는 Collection.sort()와 List.sort()는 같은 기능을 한다. List.sort()는 자바 8버전 이전에 도입된 것으로 이전 버전에서는 Collection.sort()를 써야한다.
public interface Comparator<T> {
int compare(T o1, T o2);
}
Comparator는 비교하려는 원소와는 별개의 별도의 클래스이다.
나이를 비교하고 싶다면 AgeComparator, 이름이면 NameComparator를 만들고 Comparator를 인자로 받는 메서드를 호출하면 Comparator를 이용해서 정렬을 한다.
class NameCompare implements Comparator<Member> {
public int compare(Member member1, Member member2) {
return member1.getName().compareTo(member2.getName());
}
}
MemberList.sort(NameCompare);
sort 메서드에는 compareTo를 사용하지 않고 Comparator 구현체에 있는 compare 메서드만 이용한다.
위 상황에서는 member1과 2의 이름을 받아 정렬하는 구조를 나타낸 것.
원소의 개수에 상관없이 원소 간 크기 비교는 알아서 자동으로 처리된다.
Comparator와 Comparable은 동시에 구현이 가능하다 둘 중 하나는 사용되지 않는다.
또한 Comprator와 List는 별개의 클래스이며, 리스트 내에서 꼭 Comparator를 구현해야할 필요는 없다. 어차피 구현해봤자 사용되지는 않는다. 다만 Comparable을 사용할 경우에는 리스트 내에 꼭 Comparable의 구현체와 CompareTo() 메서드가 필요하다.
Comparator와 Comparable을 혼용하기보다는 Comparator를 구현하는 클래스에서 모든 방법을 정의하는게 더 낫다. 그냥 여러 개 만들라는 뜻
Comparator가 너무 많은 것 아닌가?
위의 예시는 NameComparator와 AgeComparator를 사용했는데, 정렬해야할 기준이 늘어나면 Comparator도 늘어날 수 밖에 없다. 따라서 간소화시켜야한다.
class NameCompare implements Comparator<Member> {
public int compare(Member member1, Member member2) {
return member1.getName().compareTo(member2.getName());
}
}
위 코드에서도 return문 한 줄만 빼면 나머지는 필요없는 부분이기도하고
방법1. 인자 정의 익명 내부 클래스
MemberList.sort(new Comparator<Member>() {
public int compare(Member member1, Member member2) {
return member1.getName().compareTo(member2.getName());
}
});
이렇게한다면 코드가 실행될 때 자동으로 정렬이 된다.
다만 sort를 해야할 때마다 이 코드를 써야하고 여전히 return문 한 줄만 있으면 되는 것에 비해 불필요한 코드가 많다.
방법2. 람다
람다 배우고 싶었는데 드디어 등장하는구나
위처럼 지저분하고 긴 코드보다는 한 줄에 명확하게 끝낼 수 있는 람다를 사용할 수 있다.
MemberList.sort((one, two) -> one.getName().compareTo(two.getName()));
이렇게.. 그래서 sort 메서드가 필요한 곳마다 반복적으로 이 코드를 넣어주면 된다.
컴파일러가 이미 알거나 추측할 수 있는 부분은 걷어내고 꼭 필요한 return문만 한 줄의 코드로 만든 것이다.
람다는 12장에서 더 다룰 예정이지만, 람다의 조건은
Comparator 같이 구현해야할 메서드가 오직 한 개인 경우는 SAM(Single Abstract Method, 단일 추상 메서드) 인터페이스 또는 함수형 인터페이스라고 하며, 이 경우에는 람다 표현식으로 구현할 수 있다. 왜냐하면 클래스와 메서드의 선언부분을 컴파일러가 알고 있기 때문이다.
내림차순의 경우에는 getName 부분에서 one과 two를 바꿔서 써주면 된다.
Compare 메서드는 음수가 반환되면 첫 번째 객체가 앞, 양수면 두 번째 객체가 앞, 0이면 동순서이기 때문이다.
Compare 메서드는 인자로 준 (one, two)에서 음수가 반환되면 (one, two)를, 양수가 반환되면 (two, one) 으로 인자의 순서를 결정하기 때문에 내림차순의 경우에는 two.getName이 one.getName보다 작아서 음수가 반환되면 위치 그대로 (one, two)로 인자의 순서를 결정하고 반대의 경우에는 양수가 반환되어 (two, one)으로 인자의 순서를 결정한다. 결국 큰 원소가 앞으로 오도록 인자의 순서가 결정된다.
class NameCompare implements Comparator<Member> {
public int compare(Member member1, Member member2) {
return member1.getName().compareTo(member2.getName());
}
}
이 부분을 보면 클래스 선언부에 있는 Comparator<Member> 부분은 컴파일러가 sort()를 사용할 리스트의 제너릭타입으로부터 추측이 가능하다.
또한 메서드 선언부에서 준 인자의 타입도 똑같이 컴파일러가 추측가능하기 때문에 이 부분을 지워버린 것이라고 할 수 있다.
중복이 안되게 - Set
컬렉션에는 크게 세 가지 인터페이스들이 있다. List, Set, Map
List는 인덱스의 위치가 중요하며 중복을 허용한다.
Map은 key-value 형식으로 이루어진 컬렉션. value 값은 중복이 가능하다 (key는 안됨)
Set은 순서에 상관없이 같은 객체를 참조하는 원소가 두 개 이상 들어갈 수 없다.
그럼 중복이란?
1. 레퍼런스 동치
힙에 있는 객체를 서로 다른 레퍼런스가 참조하는 경우를 뜻한다. 객체는 각 개체마다 일종의 일련번호인 hashCode가 존재하며, 두 레퍼런스가 참조하는 객체의 해시코드가 같으면 같다고 한다. == 연산자를 사용한다.
2. 객체 동치
두 레퍼런스가 힙에 있는 서로 다른 객체를 참조하고 있으나 두 객체가 동치로 간주할 수 있는 경우. 이 경우에는 hashCode() (객체의 해시코드를 반환하는 메서드)와 equals() 메서드를 오버라이드하여 객체간 서로 같은 동치관계라고 인식할 수 있게끔해야한다.
HashSet
Set의 구현체이다. 그냥 set이라고 보면 된다.
HashSet에 인자로 컬렉션 객체를 넣어주면 HashSet은 set의 구현체이므로 중복된 객체를 제외하고 컬렉션에 있는 원소를 HashSet에 넣는다.
이 때 사용되는게 레퍼런스 동치로, HashSet은 HashCode가 다르면 동치로 치지 않는다.
예를 들어서 Member 객체를 HashSet에 넣는다고하면 Member에서 Age와 Name이 같다고하더라도 같은 객체로 보지 않는다. 해시코드 반환값이 다르니까
따라서 Member 객체는 hashCode()와 equals()를 오버라이드하여 두 Member 객체를 비교할 때 객체 동치라면 같은 해시코드를 반환하도록 hashCode() 메서드와 객체 동치일 경우 equals를 true로 반환하도록 오버라이드해야한다.
public boolean equals(Object aMember) {
Member other = (Member) aMember;
return name.equals(other.getName());
}
public int hashCode() {
return name.hashCode();
}
물론 실제로 이것은 이름만 가지고 동치여부를 판단하므로 옳다고 볼 수는 없지만, 예제니까 살펴보자면
'이름'을 기준으로 객체 동치 여부를 판단한 것이기 때문에 이름이 같은 경우 equals가 true를 반환하고 hasCode에서도 이름의 hashcode를 반환한다. (String 객체는 기본적으로 equals와 hashCode 메서드를 지원한다.)
equals에 Object 타입을 넣고 메서드 내에서 캐스팅하는 이유는 HashSet 등 equals 메서드를 호출해서 값의 동치 여부를 따지는 객체는 기본적으로 equals 메서드의 인자를 Object로 상정하고 계산을 하기 때문에 Member 객체를 그대로 넣으면 오류가 생긴다.
equals() 메서드를 오버라이드했으면 hashCode() 메서드도 반드시 오버라이드 해야한다.
왜냐하면 자바는 무조건 값을 같다고 칠 수 있으면 같은 해시코드를 반환해야하기 때문이다. https://mangkyu.tistory.com/101 참고, equals() 메서드를 오버라이드하지 않으면 기본적으로 == 메서드를 사용한다.
다만 해시값이 같아도 다른 객체일 수 있다. (해시충돌)
그렇기 때문에 해시값이 같더라도 실제 같은 값인지 equals() 메서드를 통해 같은지 여부를 확인햐아한다. 이는 HashSet에 이미 구현되어 있다. 우리는 HashSet이 호출할 hashCode와 equals 메서드만 잘 정의하면 된다.
집합정렬을 유지하기 - TreeSet
Set<Member> memberSet = new TreeSet<>(memberList);
이런 방식으로 생성할 수 있다.
기본적으로 생성될 때부터 정렬을 유지해준다. 다만 항상 정렬상태를 유지하기 때문에 속도가 다소 느리다.
생성자의 인자로 전해주는 객체는 compareTo() 메서드를 미리 구현해놔야한다.
아니면 생성자의 인자로 정렬할 컬렉션과 함께 Comparator나 람다를 넘겨주어야한다.
Set<Member> memberSet = new TreeSet<>((m1, m2) -> m1.getName().compareTo(m2.getName)));
memberSet.addAll(memberList);
이런식으로.. 첫 줄이 객체 생성이고 둘째 줄이 객체에 값을 주입한 것이다.
TreeSet의 유의점
둘 중 하나는 반드시 만족되어야한다.
1. 정렬할 집합의 원소가 Comparable의 구현체여야한다.
2. Comparator를 인자로 전해준다. (TreeSet의 오버로드된 생성자 이용하기)
객체를 Comparable의 구현체로 삼거나 Comparator 클래스를 만들어 전달해주어야한다.
TreeSet은
... = new TreeSet<>();
이렇게 빈 TreeSet 객체를 만들고 comparable의 구현체인 객체를 주거나 생성자의 인자로 Comparable 또는 람다 표현식을 전달해주어야한다.
Map
Map은 컬렉션 인터페이스의 확장된 버저은 아니지만 컬렉션 프레임워크의 일원으로 간주된다.
이건 스프링에서 정말 많이 썼으니까 메서드만 정리하면
put - 투입 / get - key를 통해 인자를 받기
컬렉션 팩토리
컬렉션은 add, put 등의 메서드를 사용해 인자를 넣고 컬렉션을 쓰는 동안 그 원소들에 대해 내용을 바꾸는 편이 아니다. 따라서 컬렉션을 생성하고 수정이 불가능하고싶으면
List<String> memberList = new ArrayList<>();
memberList.add("마치");
memberList.add("만치");
memberList.add("철수");
return Collections.unmodifiableList(memberList);
를 사용하면 된다. 다만 모든 리스트마다 저런 작업을 할 수는 없으므로 메서드 하나만 호출하여 저런 수정불가한 자료구조를 만들 수 있다. 자바 9에서 추가된 '컬렉션을 위한 간편 팩토리 메서드'라고한다.
이를 통해 만들어지는 컬렉션은 값을 추가하거나 수정할 수 없다.
또한, 만들어지는 컬렉션은 ArrayList나 HashMap 등 표준 컬렉션이 아니다.
- List 팩토리 메서드 - List.of()
add를 여러 번 했야했던 List와 달리 한 줄이면 ok
List<String> names = List.of("마치", "만치", "철수");
객체를 추가하고 싶다면
List<Member> members = List.of(new Member("마치", 21),
new Member("만치", 22),
new Member("철수", 20));
- Set 팩토리 메서드 - Set.of()
그냥 List를 Set으로 바꿔서 사용하면 된다.
- Map 팩토리 메서드 - Map.of(), Map.Entries()
Map<String, Integer> memberMap = Map.of("마치", 22,
"만치", 21,
"철수", 20);
선언한 Map 형식에 맞게 key-value 형식으로 값을 전달하면 된다. 항목이 10개 미만이라면 Map.of를 쓰면된다.
Map<String, Integer> memberMap = Map.ofEntries(Map.entry("마치", 22),
Map.entry("만치", 21),
Map.entry("철수", 20));
항목이 10개 이상이거나 보다 명확하게 key-value 구조를 알고 싶다면 Map.ofEntries()를 쓸 수 있다.
Map.entry가 번거로우면 정적임포트를 사용하면 된다.
제너릭과 타입안정성
List<Member> members = List.of(new student(), new professor(), new leaver());
takeMembers(members)
public static void takeMembers(List<Member> members) {
...
로직
...
}
이러면 잘 작동한다. (Student와 professor와 leaver 모두 Member의 하위클래스이다)
그러나 위의 코드는 Member로 구성된 List 안에 student도 넣고, professor도 넣고 했지만, 가끔 student나 professor로만 이루어진 리스트도 필요하다. 그렇다고
List<students> sts = List.of(new student(), new student());
takeMembers(sts);
이러면 takeMembers 메서드에서 잘못된 인자를 전했다고 오류가 발생한다.
분명히 members의 하위클래스인 student 클래스이기 때문에 호환은 가능하지만 Member 객체를 받는 takeMembers 메서드에는 인자로 student 로 이루어진 리스트를 전해주지 못하는데
그 이유는 '로직' 부분에서 어떤 일이 일어날지 모르기 때문이다.
List<Member>가 올 것으로 상정하고 professor 객체를 add해버린다면 오류가 발생할게 뻔하기 때문이고, 이를 막기위해서 컴파일에러가 난다.
상위클래스를 원소로 받는 리스트를 만들고 이를 넘겨주어야한다.
메서드 선언시 List의 원소 타입을 지정하면 무조건 그 타입의 원소로만 이루어진 리스트만을 넣어주어야한다. (상속관계 여부에 무상관)
그런데 난 하위클래스도 인자로 받게하고 싶다 - 와일드카드
public void takeMembers(List<? extends Member> members) {
...
로직
...
}
이런식으로 와일드카드를 사용하면 Member의 하위클래스 객체 또한 받을 수 있다.
만약 로직부분이 와일드카드가 아닌 경우,
public void takeMembers(List<Member> Members) {
for (Member member : Members) {
System.out.println(member.getName());
}
}
이라면, List<student> 객체를 전해주면 에러가 터져버린다.
하지만 저 와일드카드를 사용한다면 Member의 확장된 클래스는 모두 저 메서드를 사용할 수 있다.
다만 원소를 수정하거나 추가하는 작업은 할 수 없다. 컴파일러가 막아버리기 때문이다.
제너릭 타입 매개변수 사용하기
public <T extends Member> void takeMembers(List<T> list) {
for (T li : list) {
System.out.println(li.getName());
}
}
이런식으로 메서드를 선언하면?
원소를 수정하고 추가할 수 있을 뿐아니라 인자로 전하고 반환되는 값의 타입을 지정하고 알 수 있다는 장점이 있다.
인자로 전할 객체가 확장되지 않을 경우에는 저렇게 와일드카드를 써도 상관이 없으나 메서드의 리턴 타입을 지정하거나 타입 자체에서 추가로 조작이 필요한 경우에는 제너릭 타입 매개변수 방식이 더 낫다.
추가로...
private takeStudents(List<student> sts) { }
takeStudents(new ArrayList<>())l
제너릭 타입을 공란으로 지정하고 메서드의 인자로 넘기면 컴파일러가 알아서 처리해준다. 메서드 서명으로 알아낸다.
후아~
맨날 T니 뭐니 꺽쇠괄호에 선언된 것이 궁금했는데 알아볼 수 있었다.
대체적으로는 리스트와 컬렉션에 맞춰서 알아봤지만, 전반적으로는 어떻게 제너릭 타입을 설정하고 상속관계에서 이를 적용하는지 알아볼 수 있었다. 이걸 한 번 더 정리하면 좋을 것 같은데 주간회고록에서 한 번 더 정리해볼 것이다.
대충 리스트 원소비교에서
Comparable - 비교할 객체를 Comparable의 구현체로 만든 뒤에 compareTo 메서드를 만든다.
Comparator - 비교할 객체를 어떻게 비교할 것인지 미리 지정해서 전달하기
제너릭 타입은 상속관계에 상관없이 제너릭 타입으로 지정된 객체만 받을 수 있지만
<T extends Member>
를 사용하면 하위클래스에 호환이 되게도 사용할 수 있다. 굳이 T가 아니어도 된다.
간혹 상속관계에 상관이 없을경우에는 T 대신 ?를 사용해 와일드카드를 사용할 수 있으나 리스트 원소의 추가 등은 불가능하다.
대체적으로 컴공개에서 배운 파이썬과 어느정도 겹치는 내용이 좀 있었다.
파이썬 배울 때 객체지향프로그래밍도 좀 같이 배워둘 걸;; 컴공개 때조차 객체지향을 잘 몰라서 좀 헤맨 것이 아쉽다. 지금이면 좀 더 잘 이해할텐데
'언어공부 > Java | Kotlin' 카테고리의 다른 글
| [2025 백엔드] 헤드퍼스트자바 독서 #9 - 13장. 위험한 행동 (4) | 2025.08.13 |
|---|---|
| [2025백엔드] 헤드퍼스트자바 독서 #8 - 12장. 람다와 스트림 (4) | 2025.08.13 |
| [2025백엔드] 헤드퍼스트자바 독서 #6 - 10장. 숫자는 정말 중요합니다 (5) | 2025.08.03 |
| [2025백엔드] 헤드퍼스트자바 독서 #5 - 9장. 객체의 삶과 죽음 (3) | 2025.07.27 |
| [2025백엔드] 헤드퍼스트자바 독서 #4 - 8장. 심각한 다형성 (2) | 2025.07.26 |