1. 제네릭(Generic)
콜렉션을 배우기 위해 꼭 알아야 하는 개념
- 소스 레벨에서 데이터 타입이 결정되지 않고, 별칭(타입파라미터)만 지정한다.
- 데이터 타입은 필드의 타입, 매개변수 타입, 리턴타입 등이다.
- 객체 생성시점, 구현클래스 구현시점, 메소드 실행시점에 별칭을 대신할 데이터 타입을 외부에서 지정하는 것이다.
- 제네릭은 <> 다이아몬드 표기법으로 타입파라미터(별칭)를 지정한다.
- 객체 생성싯점, 구현클래스 구현싯점, 메소드 실행싯점에 데이터타입을 지정한다.
- 데이터타입은 클래스 혹은 인터페이스 타입만 가능하다.
- 기본 자료형은 제네릭의 데이터 타입으로 지정할 수 없다.
1-1. 객체를 저장하는 변수의 타입을 Object로 지정하는 경우 (제네릭 x)
public class Box {
private Object item;
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
}
import java.util.Date;
public class BoxApp {
public static void main(String[] args) {
// String객체를 저장하는 박스객체
Box box1 = new Box();
String item1 = "안녕하세요";
// Box객체에 String객체 저장하기 -> Box객체의 item 변수에는 String객체의 부모객체인 Object 객체의 주소값 저장
box1.setItem(item1);
// Box객체에 저장된 String 객체 획득하기
// Box객체의 item변수에 저장된 객체를 반환받으면 String이 아닌 Object객체의 주소값 반환 (String->Object로 형변환되었기 때문)
Object obj1 = box1.getItem(); // Object로 형변환돼서 저장됐기 때문
// obj1이 저장하고 있는 Object객체의 주소값으로는 실제로 생성해서 저장한 String객체의 고유한 속성과 기능을 실행할 수 없다
// System.out.println("문자열의 길이: "+ obj1.length()) // 오류 ( length = String객체의 고유한 기능 )
// String 객체의 고유한 속성과 기능을 사용하기 위해서는 다시 String 타입으로 강제 클래스 형변환 해야한다.
String value1 = (String) obj1; // 원래 타입으로 강제 형변환
System.out.println("문자열의 길이: " + value1.length());
// Date객체를 저장하는 박스객체
Box box2 = new Box();
Date item2 = new Date();
// Box객체에 Date객체 저장하기 -> Box객체의 item 변수에는 Date객체의 부모객체인 Object 객체의 주소값 저장
box2.setItem(item2);
// Box객체에 저장된 Date 객체 획득하기
// Box객체의 item변수에 저장된 객체를 반환받으면 Date가 아닌 Object객체의 주소값 반환
Object obj2 = box2.getItem();
// obj2가 저장하고 있는 Object객체의 주소값으로는 실제 생성해서 저장한 Date객체의 고유한 속성과 기능을 실행할 수 없다
// System.out.println("유닉스타임: " + obj2.getTime());
// Date객체의 고유한 속성과 기능을 사용하기 위해서는 다시 Date 타입으로 강제 클래스 형변환 해야한다.
Date value2 = (Date) obj2;
System.out.println("유닉스타임: " + value2);
}
}
문자열의 길이: 5
유닉스타임: Tue Oct 04 23:53:50 KST 2022
- private Object item;
-> 객체를 담을 변수 item의 타입을 Object로 지정
-> Object니까 모든 타입을 담을 수 있음
-> Box에는 String, Date도 담김 - Box box1 = new Box();
Box box2 = new Box();
: Box라는 객체를 만들고 그 안에 String객체를 저장할거야
Box 객체 2개가 생성되고, Box 객체 안에는 Object 타입의 변수 item이 있음 - String item1 = "안녕하세요";
Date item2 = new Date();
: String객체와 Date객체도 생성되고, 이들은 Object 객체를 상속받음 - box1, box2, item1, item2 각 변수들이 객체를 참조함
- box1.setItem(item1);
: box1이 바라보는 Box객체의 item에 item1변수가 참조하는 String객체의 주소값을 전달
근데, 내가 전해준건 String 객체인데, item은 Object만 받을 수 있어
-> 클래스 형변환 (String -> Object) -> item은 String객체의 상위객체인 Object객체의 주소값을 갖게 됨
box2.setItem(item2);
: 클래스 형변환 (Date -> Object) -> item2는 Date객체의 상위객체인 Object객체의 주소값을 갖게 됨 - Object obj1 = box1.getItem();
String value1 = (String) obj1;
: Box 객체에서 꺼내어 쓸 때, 나는 String을 원했는데 그 상위객체인 Object 타입으로 반환되어 나옴
Box객체의 변수 타입을 Object로 하게 되면 어떤 객체든 다 담을 수 있어서 좋지만,
Object 타입으로 형변환되어버려서, 꺼낼 때도 Object 타입으로 반환됨
-> 원래 생성했던 타입으로 다시 강제 형변환 필요 ( 꺼내쓸 때마다 형변환 필요 )- 만약,
Date value1 = (Date) obj1;
로 하면, 소스코드 작성할 때는 오류가 안뜨고, 실행할 때서야 오류가 뜸
(담은 건 String인데, 반환은 Date)
- 왜 다시 형변환 하냐?
Object타입으로 반환하면, 내가 생성했던 객체의 고유한 기능을 사용 못함 (length)
만약, toString을 실행 -> 원래 객체로 강제 형변환 필요가 없어
왜? toString은 Object랑 String에 둘다 있는데 재정의가 되어있기 때문에,
원래 객체로 형변환을 안해도, 자동으로 String객체의 toString으로 실행됨
근데 재정의 하지 않은, String객체의 고유한 메소드를 실행하려면
Object -> String으로 강제 형변환 필요
- 만약,
- obj1.length(); // 오류 ( length = String객체의 고유한 기능 )
value1.length(); // 실행
* 객체를 저장하는 변수의 타입을 Object로 지정하는 경우
- 최상위 부모객체이므로 모든 객체를 저장(참조)할 수 있다
- 실제로 생성한 객체의 주소값 대신 그 객체의 최상위 부모객체인 Object객체의 주소값이 저장(참조)
(저장된 모든 객체는 Object 타입으로 항상 클래스형변환)
- 저장한 객체를 꺼낼 때는 항상 Object 타입의 주소값이 반환
- 실제 저장한 객체로 강제 클래스형변환 필요
(강제 클래스형변환할 때 실제로 생성해서 저장한 객체와 다른 타입으로 형변환하도록 소스코드를 작성해도 오류가 검출되지 않는다.)
* 문제점
1) Object로 클래스 형변환이 되어버림
2) 꺼낼때마다 해당 타입으로 형변환 해야돼
3) 내가 형변환을 잘못 적어도 소스코드 단계에서 오류가 안남
1-2. 객체를 저장하는 변수의 타입을 실제 저장하는 객체의 타입으로 지정하는 경우 (제네릭 x)
* 객체를 저장하는 변수의 타입을 실제 저장하는 객체의 타입으로 지정하는 경우
- 지정된 타입의 객체만 저장할 수 있다. (따라서, 프로그램에서 사용하는 객체의 종류만큼 xxxBox 클래스를 정의해야 한다)
- 저장되는 객체가 클래스형변환 없이 실제로 생성한 객체의 주소값이 저장되기 때문에 저장한 객체를 꺼낼 때도 별도로 강제 클래스형변환이 필요없다.
public class FruitBox {
Fruit item;
}
public class BookBox {
Book item;
}
public class ProductBox {
Product item;
}
* 문제점
1) 형변환을 안해도 되는데, 객체를 너무 많이 만들어야 돼
2) 각각 설정한 타입밖에 못담아
1-3. 객체를 저장하는 변수의 타입을 타입파라미터를 사용해서 지정하는 경우 ( 제네릭 )
* 객체를 저장하는 변수의 타입을 소스코드 레벨에서 지정하지 않고, 타입파라미터(별칭)를 사용해서 지정하는 경우
public class Box<T> {
T item;
public void setItem(T t) {
this.item = t;
}
public T getItem() {
return item;
}
}
Box<String> box1 = new Box<String>();
// 생성된 객체에서 (객체 생성할 때 타입을 지정)
public class Box<String> {
String item;
public void setItem(String t) {
this.item = t;
}
public Stirng getItem() {
return item;
}
}
Box<Book> box2 = new Box<Book>();
// 생성된 객체에서는
public class Box<Book> {
Book item;
public void setItem(Book t) {
this.item = t;
}
public Book getItem() {
return item;
}
}
* 자료구조는 하나의 객체에 여러 개의 값을 저장하는 것
'하나의 객체'에 '여러 종류'의 값을 담을때는 -> 변수의 타입을 Object
Book, Album, Gift 등 종류가 다른 객체들을 저장하고 싶을 때만 -> 변수의 타입이 Object
-> 변수가 각 객체의 상위객체인 Object를 참조
-> 다음 순서에 어떤 값이 있는지를 몰라 -> 꺼낼 때마다 그 타입이 뭔지 계속 확인해야돼
근데 그냥
객체를 3개를 만드는거야
Album만 담는 객체, Book만 담는 객체를 따로
∴ 제네릭을 사용하는 이유
<사용안할 경우>
- 소스코드에서 변수타입을 결정해버리면 해당 타입밖에 못담아
- 모든 객체를 다 담으려면 Object 로 형변환 해야하고,
- 꺼낼 때도 원래대로 강제 형변환 해야돼
<제너릭 사용>
- 형변환도 안하고, 모든 객체를 다 담을 수 있음
- 객체를 생성 시점, 메소드 실행시점에 내가 타입을 결정할 수 있어
종류별로 객체를 만들 필요 없이,
Generic만 만들고, 생성할 때의 타입만 t자리에 원하는 타입으로 대입
1-4. 제네릭 클래스
- 소스코드 레벨에서 타입을 지정하지 않고, 객체 생성시점에 타입이 결정되는 클래스
- 타입 파라미터를 하나 이상 가지고 있는 클래스
- 타입 파라미터를 사용해서 객체 생성시 전달받은 타입으로 T의 자리가 대체되는 클래스
- 타입파라미터는 클래스이름 옆에 <T>와 같은 형태로 지정
- 타입파라미터는 <A, B>와 같이 여러 개 지정할 수도 있다.
- 타입파라미터의 별칭은 일반적으로 대문자로 적고, 아무 문자나 적어도 상관없다.
- 여러 종류의 객체를 다루는 클래스를 제네릭 클래스로 정의하면 타입의 안전성을 보장받고, 형변환 코드를 제거할 수 있다.
- 제네릭 클래스의 사용
1) 여러 종류의 객체를 담아야 되는 경우
2) 인터페이스에서 제네릭이 정의되어 있는 경우 - 제네릭 클래스 예)
- 자료구조(Set, List, Map<K, V>)
- 데이터를 반복처리하는 것(Iterator, Enumeration)
- GenericBox<String> box = new Generic();
GenericBox<Book> box = new Generic();
GenericBox<Gift> box = new Generic();
GenericBox<Student> box = new Generic();
- 제네릭 클래스 구현시 주의사항
- T타입의 배열을 생성할 수 없다.
T[] data = new T[10];// 오류.
T[] data; // 정상, T타입의 배열에 대한 변수는 선언할 수 있다. - T타입의 클래스 변수를 생성할 수 없다.
static T t; - T의 자리에는 참조타입만 가능 (기본자료형 타입은 T를 대체할 수 없다)
기본자료형 타입을 지정할 수 없다
GenericBox<int>box = new Generic<int>();
GenericBox<double>box = new Generic<double>(); - 기본자료형 타입 대신 Wrapper 클래스타입을 지정 (객체를 적어야 함)
GenericBox<Integer> box = new Generic<Integer>();
GenericBox<Double> box = new Generic<Double>();
- T타입의 배열을 생성할 수 없다.
- 제네릭 클래스 정의
public class GenericBox<T> {
private T item;
public T getItem() {
return item;
}
public void setItem(T t) {
this.item = t;
}
}
- T의 타입이 String클래스, Date클래스
import java.util.Date;
public class GenericBoxApp {
public static void main(String[] args) {
// box1이 참조하는 객체는 T의 자리가 전부 String으로 대체된 GenericBox객체다.
GenericBox<String> box1 = new GenericBox<>();
// T의 자리가 전부 String으로 대체되었기 때문에 Object로 클래스형변환없이 실제 생성된 String객체를 참조하게 된다.
box1.setItem("안녕하세요");
// T의 자리가 전부 String으로 대체되었기 때문에 GenericBox객체에서 객체를 가져올 때도 String객체의 주소값이 반환된다.
// - 강제 클래스 형변환이 필요없다.
// - 실제로 저장한 객체와 다른 타입의 객체로 형변환하게 되면 컴파일 오류 발생
String value1 = box1.getItem();
System.out.println(value1);
// box2가 참조하는 객체는 T의 자리가 전부 Date로 객체된 GenericBox객체다.
GenericBox<Date> box2 = new GenericBox<>();
box2.setItem(new Date());
Date value2 = box2.getItem();
System.out.println(value2);
}
<좀더 실습>
public class GenericBox2<T> {
private Object[] items = new Object[5]; // generic타입의 배열을 생성할 수 없어서 Object로
private int position = 0;
private int limit = 5;
private final int size = 5;
@SuppressWarnings("unchecked")
public T getItems(int index) {
return (T) items[index]; // items[index]의 값을 T로 형변환
}
public void add(T t) {
resize();
items[position] = t;
position++;
}
public int getSize() {
return position;
}
public void resize() {
if(position == limit) {
limit += size;
Object[] temp = new Object[limit];
System.arraycopy(items, 0, temp, 0, position);
items = temp;
}
}
}
public class GenericBox2App {
public static void main(String[] args) {
GenericBox2<String> box1 = new GenericBox2<>();
box1.add("김유신");
box1.add("강감찬");
box1.add("이순신");
box1.add("류관순");
box1.add("안중근");
int size = box1.getSize(); //몇개 들어있는지 알고싶을 때
for(int index = 0; index<size; index++) {
String value = box1.getItems(index); // index에 있는 값(Object)을 String으로 형변환
System.out.println(value);
}
System.out.println("### 기본자료형타입의 값은 타입파라미터를 Wrapper 클래스타입으로 지정한다.");
// 기본자료형타입의 타입파라미터는 지정할 수 없다.
// 대신 기본자료형타입의 Wrapper 클래스타입을 타입파라미터로 지정한다.
GenericBox2<Integer> scores = new GenericBox2<>();
// void add(Integer t)메소드로 정수를 추가할 때, Integer객체를 생성할 필요없음
// 정수100을 전달해도 t의 타입이 Integer타입이면, 자동으로 new Integer(100)이 전달된다.
scores.add(100); // 오토박싱때문에 알아서 Integer객체에 들어감
scores.add(80);
scores.add(70);
scores.add(90);
scores.add(40);
scores.add(50);
scores.add(60);
size = scores.getSize(); // 몇개 들어있는지
for(int index = 0; index<size; index++) {
int score = scores.getItems(index); // 오토언박싱
System.out.println(score);
}
}
}
김유신
강감찬
이순신
류관순
안중근
### 기본자료형타입의 값은 타입파라미터를 Wrapper 클래스타입으로 지정한다.
100
80
70
90
40
50
60
* 제네릭 클래스에서 타입파라미터의 타입 제한하기
GenericPhoneBox<T> 라고 하면, T에 대한 제한이 없음 ( String, int, Phone ... )
근데, Phone 종류만 담는 Box이고 싶어 ( String, int 이런거 말고 )
GenericPhoneBox<T extends Phone>
Phone과 그 하위객체만 T에 올 수 있음
-> 타입파라미터의 타입을 제한할 수 있음
- <T extends 상위타입>
- T는 지정된 상위타입 및 그 상위타입을 상속받는 하위타입만 지정 가능
- 상위타입은 부모클래스 타입이나 부모인터페이스 타입 모두 가능
- 예시
// Box객체는 모든 종류의 Phone객체를 저장할 수 있다.
public class Box {
private Phone[] items = new Phone[5];
public void add(Phone p){
items[position] = p;
position++;
}
// Box객체는 Phone종류 중에서 특정한 종류만 지정해서 저장할 수 있다.
public class Box<T extends Phone> {
private T item;
public T getItem() {
return item;
}
public void setItem(T t) {
this.item = t;
}
-실습
public class GenericPhoneBox<T extends Phone> { //Phone종류의 객체만 담기게 하고싶어
private T item;
public T getItem() {
return item;
}
public void setItem(T t) {
this.item = t;
}
}
public class GenericPhoneBoxApp {
public static void main(String[] args) {
// Phone 종류의 객체만 저장할수 있다. ( String은 Phone의 하위객체가 아님! )
// GenericPhoneBox<String> box1 = new GenericPhoneBox<>();
// box1.setItem("홍길동");
// box1이 참조하는 객체는 Phone 종류의 객체를 저장할 수 있다.
GenericPhoneBox<Phone> box1 = new GenericPhoneBox<>();
box1.setItem(new Phone());
box1.setItem(new FeaturePhone());
box1.setItem(new SmartPhone());
// box2가 참조하는 객체는 FeaturePhone 종류의 객체를 저장할 수 있다.
GenericPhoneBox<FeaturePhone> box2 = new GenericPhoneBox<>();
// box2.setItem(new Phone());
box2.setItem(new FeaturePhone());
// box2.setItem(new SmartPhone());
// box3이 참조하는 객체는 SmartPhone 종류의 객체를 저장할 수 있다.
GenericPhoneBox<SmartPhone> box3 = new GenericPhoneBox<>();
// box3.setItem(new Phone());
// box3.setItem(new FeaturePhone());
box3.setItem(new SmartPhone());
}
}
1-5. 제네릭 인터페이스
- 제네릭 인터페이스 구현하기
- 구현할 인터페이스
- public interface Comparable<T> {
int compareTo(T t);
}
- public interface Comparable<T> {
- Score에서 Comparable<T> 구현하기
- public class Score implements Comparable<Score> {
// 총점을 기준으로 오름차순 정렬하기 (default - 오름차순)
public int compareTo(Score other) {
return this.total - other.total; (양수가 나오면 > 상태, 음수가 나오면 < 상태, 0이 나오면 같음)
}
} - // 총점을 기준으로 오름차순 정렬하기
* Score a = new Score("김유신", 100, 100, 90);
* Score b = new Score("홍길동", 60, 70, 50); (b = 매개변수로 받은 객체)
* int x = a.compareTo(b); (x에는 '음수, 0, 양수' 중 하나가 반환)
* a.getTotal() > b.getTotal() -> 양수
* a.getTotal() == b.getTotal() -> 0
* a.getTotal() < b.getTotal() -> 음수
- public class Score implements Comparable<Score> {
- 구현할 인터페이스
1) Comparable 인터페이스를 구현하고 있는 배열의 정렬 (int, String타입)
- Arrays.sort(배열이름); 가능 ( 얘네는 원래 Comparable 인터페이스를 구현하고 있음 )
import java.util.Arrays;
public class SortApp1 {
public static void main(String[] args) {
int[] numbers = {10, 30, 1, 51, 65, 37, 95, 70, 18, 48};
String[] names = {"홍길동", "김유신", "강감찬", "안중근", "류관순", "이봉창", "윤봉길"};
// 이 숫자배열과 문자열배열 객체를 정렬하고 싶을 때
Arrays.sort(numbers);
Arrays.sort(names);
System.out.println(Arrays.toString(numbers));
System.out.println(Arrays.toString(names));
}
}
[1, 10, 18, 30, 37, 48, 51, 65, 70, 95]
[강감찬, 김유신, 류관순, 안중근, 윤봉길, 이봉창, 홍길동]
2) Comparable 인터페이스를 구현하지 않은 배열의 정렬 (Score 타입) => 제네릭 인터페이스
- Arrays.sort(배열이름); 불가능 -> 오류 ( Comparable 인터페이스를 구현하지 않으면 정렬 불가능 )
- Comparable 인터페이스를 구현하면 -> Arrays.sort (Collections.sort)를 이용해서 자동 정렬 가능
- 이 인터페이스의 메소드 중에는 compareTo(T t) 가 있다 ( 정수값 반환(음수,0,양수) ) -> 비교가 가능
- 제네릭 인터페이스 ( 구현할 때 타입을 지정 )
public class Score implements Comparable<Score> {
클래스이름 implements Comparable<T타입 지정>
-> sort를 통해 정렬이 되게 하려면 , comparable인터페이스를 구현해야 함
-> 어떤 객체에서 얘를 구현할지 몰라 -> T로 타입을 지정해놓고, 구현할 때 넣으면 됨 - @Override
public int compareTo(Score otherScore) {
return this.total - otherScore.total;
}
-> compareTo 메소드를 재정의하면 지정한 타입으로 매개변수가 자동생성됨
-> 지금 이 Score 객체와 다른 Score 객체를 비교하겠다
-> 값을 비교한 결과가 양수, 0, 음수 중 하나가 나오도록 재정의
-> 오름차순 정렬이 됐음 ( default - 오름차순 )
- 제네릭 인터페이스 ( 구현할 때 타입을 지정 )
- Comparable 인터페이스 구현
public class Score implements Comparable<Score>{
private String name;
private int kor;
private int eng;
private int math;
private int total;
private int average;
public Score(String name, int kor, int eng, int math) {
this.name = name;
this.kor = kor;
this.eng = eng;
this.math = math;
this.total = kor + eng + math;
this.average = total/3;
}
public String getName() {
return name;
}
public int getKor() {
return kor;
}
public int getEng() {
return eng;
}
public int getMath() {
return math;
}
public int getTotal() {
return total;
}
public int getAverage() {
return average;
}
@Override
public int compareTo(Score otherScore) {
System.out.println(this.total +", "+ otherScore.total);
return this.total - otherScore.total;
}
@Override
public String toString() {
return "[[name=" + name + ", kor=" + kor + ", eng=" + eng + ", math=" + math + ", total=" + total
+ ", average=" + average + "]";
}
}
import java.util.Arrays;
public class SortApp2 {
public static void main(String[] args) {
// Score 배열객체를 어떤 기준으로 정렬하고 싶을 때
Score[] scores = new Score[5];
scores[0] = new Score("이순신", 100, 90, 90);
scores[1] = new Score("강감찬", 80, 50, 50);
scores[2] = new Score("류관순", 90, 90, 90);
scores[3] = new Score("김유신", 70, 70, 80);
scores[4] = new Score("홍길동", 30, 70, 50);
// 배열에 저장된 Score객체를 총점을 기준으로 오름차순 정렬하기
Arrays.sort(scores);
System.out.println("정렬 결과: "+Arrays.toString(scores));
}
}
180, 280
270, 180
270, 280
270, 180
220, 270
220, 180
150, 270
150, 220
150, 180
정렬 결과: [[[name=홍길동, kor=30, eng=70, math=50, total=150, average=50], [[name=강감찬, kor=80, eng=50, math=50, total=180, average=60], [[name=김유신, kor=70, eng=70, math=80, total=220, average=73], [[name=류관순, kor=90, eng=90, math=90, total=270, average=90], [[name=이순신, kor=100, eng=90, math=90, total=280, average=93]]
1-6. 제네릭의 사용
1) 객체를 생성할 때 제네릭 클래스 사용하기
- 객체 생성시점에 T의 타입 지정
public class Box<T> {
...
}
Box<Book> bookBox = new Box<>(); -> 객체 생성시점
Box<Employee> employee = new Box<>();
2) 클래스를 구현할 때 제네릭 인터페이스 사용하기
- 인터페이스 구현시점에 T의 타입 지정
public interface Comparable<T> {
int compareTo( T other );
}
public class Score implements Comparable<Score> { -> 인터페이스 구현시점
public int compareTo(Score score) { // 양수,0,음수가 나오도록 재정의
return this.total - other.total;
3) 제네릭 클래스를 사용하는 이유
- 클래스내부에서 사용할 데이터의 타입을 외부에서 지정 (ex. App에서 지정함)
- 객체 생성시점에 데이터 타입을 지정 -> 원하지 않는 타입이 입력되는 것을 막을 수 있음
- 값을 꺼낼 때 형변환이 필요 없음
제너릭을 가장 많이 쓰는 것이 콜렉션
2. 콜렉션 Collection
- 자바가 자료구조를 구현해 놓은 것
- 자료구조 - 객체의 저장/삭제/조회 등의 기능을 제공하는 것
- 자바의 모든 자료구조 구현 클래스는 Collection 인터페이스를 구현한 클래스다.
- Collection 인터페이스에 정의된 모든 기능을 구현하고 있다.
- 자바의 자료구조 특징
- 객체만 저장할 수 있다.
- 크기가 가변적이다.
- 다양한 메소드를 지원한다.
2-1. Collention<E>
- 모든 자료구조 클래스의 최상위 인터페이스다.
- 주요 메소드
- boolean add(E e)
: 자료구조에 새로운 요소를 추가한다. - boolean addAll(Collection< ? extends E > c)
: 자료구조에 다른 자료구조의 모든 요소를 추가한다
( 전달받은 ? 객체는 E 객체의 하위클래스여야 함 )
콜렉션(Set이나 List를 구현한 객체)의 타입파라미터 타입이 Phone이나 Phone의 하위타입인 콜렉션만 추가할 수 있다.
E의 하위타입이 타입파라미터로 지정된 콜렉션만 올 수 있다. - void clear()
: 자료구조의 모든 요소를 삭제한다. - boolean contains(Object e)
: 자료구조에 지정된 객체가 존재하는지 조회한다. - boolean isEmpty()
: 자료구조가 비었는지 조회한다. - Iterator<E> iterator()
: 자료구조의 각 요소를 반복해서 추출해주는 반복자객체를 반환한다. - boolean remove(Object e)
: 자료구조에서 지정된 객체를 삭제한다. - int size()
: 자료구조에 저장된 요소의 개수를 반환한다. - Object[] toArray()
: 자료구조에 저장된 요소를 배열로 반환한다.
- boolean add(E e)
2-2. Collection의 주요 하위 인터페이스
* Set<E>
- 중복을 허용하지 않는다. (동일한 객체를 2개 저장할 수 없다.)
- 주요 구현 클래스
- HashSet<E> : 가장 많이 사용하는 Set구현 클래스
( 해시코드로 비교 ) - TreeSet<E> : 저장되는 요소가 오름차순으로 정렬되어서 저장된다
( Comparable 인터페이스를 구현한 것만 저장됨 -> 정렬을 하니까 )
- HashSet<E> : 가장 많이 사용하는 Set구현 클래스
* List<E>
- 순서가 유지된다. (저장된 순서대로 다시 꺼낼 수 있다.)
- 요소가 저장될 때 마다 index(순번)이 자동으로 부여된다.
- 특정위치에 요소 저장하기, 특정위치의 요소 삭제하기, 특정위치의 요소 꺼내기
- List<E>가 지원하는 추가 메소드
- void add(int index, E e)
- 지정된 위치에 요소를 저장한다.
- E get(int index)
- 지정된 위치의 요소를 꺼낸다
- E remove(int index)
- 지정된 위치의 요소를 삭제한다.
- E set(int index, E e)
- 지정된 위치의 요소를 새 요소로 교체한다.
- void add(int index, E e)
- 주요 구현 클래스
- ArrayList<E> : 가장 많이 사용하는 List구현 클래스(전체 자료구조 클래스 중에서 가장 많이 사용)
- LinkedList<E> : 더블링크로 List를 구현한 클래스(요소의 추가/삭제 성능이 우수하다.)
- Vector<E> : ArrayList와 유사하는 List구현 클래스(멀티스레드 환경에 안전하다.)
- Stack<E> : LIFO(Last-In-First-Out)으로 구현된 List구현 클래스
- Queue<E> : FIFO(First-In-First-Out)으로 구현된 List구현 클래스
※ 자바의 자료구조 구현 클래스의 특징
- Collection<E>의 구현체이다.
( - 구현클래스와 상관없이 저장/삭제/조회 등의 작업을 동일한 방법으로 실행한다. )
- boolean add(E e) 메소드를 사용해서 객체를 저장한다.
( - 저장위치(position)를 관리할 필요가 없다. )
( - 저장하거나 삭제하면 자동으로 저장용량이 증가되거나 줄어든다. )
- 객체만 저장할 수 있다.
- 자료구조에 저장된 객체를 하나씩 다룰 때는 향상된 for문을 사용한다.
( Java8부터는 Stream<E>을 사용 )
1) Set<E>
- HashSet<E>
- Set<E> 인터페이스의 구현클래스
- 객체의 중복저장을 허용하지 않는다.
- 객체의 hash코드값을 비교해서 객체의 중복여부 체크
(hash코드가 같으면 같은 객체) - 배열처럼 값을 여러개 담는 것! (콜렉션)
- 주요 메소드
- boolean add(E e)
: 전달받은 E타입의 객체를 HashSet 객체에 저장 - boolean addAll(Collection<? extends E> c)
: 전달받은 다른 콜렉션 객체에 저장된 모든 객체를 HashSet 객체에 저장
( 전달받은 ? 객체는 E 객체의 하위클래스여야 함 ) - boolean contains(Object e)
: 전달받은 객체와 같은 객체가 HashSet 객체에 저장되어 있으면 true 반환 - boolean remove(Object e)
: 전달받은 객체와 같은 객체가 HashSet 객체에 저장되어 있으면 삭제하고 true 반환 - boolean isEmpty()
: HashSet 객체에 저장된 객체가 하나도 없으면 true 반환 - int size()
: HashSet 객체에 저장된 객체의 개수 반환 - void clear()
: HashSet 객체에 저장된 모든 객체 삭제 - Iterator<E> iterator()
: HashSet 객체에 저장된 객체를 반복처리할 수 있는 Iterator객체를 반환
Iterator 객체는 반복작업을 하는 도중에 저장된 객체를 삭제할 수 있다.
(향상된 for문으로 반복작업을 하는 도중에는 절대 저장된 객체를 삭제할 수 없다.) - Stream<E> stream()
: HashSet 객체에 저장된 객체를 Stream으로 반환 ( 자바 8부터 지원되는 기능 )
(Stream은 다양한 데이터 소스(콜렉션, 배열 등)를 표준화된 방법으로 다루기 위한 객체)
- boolean add(E e)
- TreeSet<E>
- Set<E> 인터페이스의 구현클래스
- 객체의 중복저장을 허용하지 않는다.
- 저장된 객체를 오름차순으로 정렬해서 저장
(TreeSet<E>의 E에 해당하는 객체는 반드시 Comparable<E> 인터페이스를 구현해야 한다. -> 정렬해야되니까)
(String은 자동으로 구현하고 있음)
- HashSet<E>, TreeSet<E> 사용
import java.util.HashSet;
import java.util.TreeSet;
public class SetApp1 {
public static void main(String[] args) {
HashSet<String> names = new HashSet<>(); //배열처럼 객체를 여러개 담을 수 있음
names.add("홍길동"); //내부적인 알고리즘을 통해 정렬
names.add("이순신");
names.add("이순신");
names.add("이순신");
names.add("김유신");
names.add("김유신");
names.add("강감찬");
names.add("류관순");
names.add("안중근");
names.add("윤봉길");
names.add("이봉창");
System.out.println(names); // 이순신, 김유신을 여러번 저장해도, 중복을 허용하지 않기 때문에 한번만 저장
TreeSet<String> names2 = new TreeSet<>();
names2.add("홍길동");
names2.add("이순신");
names2.add("이순신");
names2.add("이순신");
names2.add("김유신");
names2.add("김유신");
names2.add("강감찬");
names2.add("류관순");
names2.add("안중근");
names2.add("윤봉길");
names2.add("이봉창");
System.out.println(names2); // 중복을 허용하지 않고, 오름차순으로 정렬
}
}
[윤봉길, 홍길동, 김유신, 류관순, 강감찬, 이순신, 이봉창, 안중근]
[강감찬, 김유신, 류관순, 안중근, 윤봉길, 이봉창, 이순신, 홍길동]
-HashSet<E>의 주요 메소드 사용
import java.util.HashSet;
import java.util.Set;
public class SetApp2 {
public static void main(String[] args) {
// boolean add(E e)를 사용해서 HashSet객체에 String객체를 저장
Set<String> names0 = new HashSet<>(); // Set이 HashSet의 상위이기 때문에 부모타입으로 함
names0.add("강감찬");
names0.add("이순신");
Set<String> names = new HashSet<>();
names.add("홍길동");
names.add("김유신");
// boolean addAll(Collection<? extends E> c)를 사용해서 HashSet객체에 다른 콜렉션객체에 저장된 객체를 저장
names.addAll(names0); // names 콜렉션 객체에 names0 콜렉션 객체를 저장
System.out.println(names);
// boolean contains(Object o)를 사용해서 HashSet객체에 특정 객체가 존재하는지 확인하기
boolean isExist = names.contains("류관순");
System.out.println(isExist);
// boolean remove(Object o)를 사용해서 HashSet객체에서 특정 객체 삭제
names.remove("강감찬");
System.out.println(names);
// int size()를 사용해서 HashSet객체에 저장된 객체의 개수 조회
int size = names.size();
System.out.println("저장된 객체의 개수: "+size);
// void clear()를 사용해서 HashSet객체에 저장된 모든 객체 삭제
names.clear();
System.out.println(names);
}
}
[홍길동, 김유신, 강감찬, 이순신]
false
[홍길동, 김유신, 이순신]
저장된 객체의 개수: 3
[]
< 사원번호가 같으면, 같은 객체로 인식하기 (동등성 체크) >
- <객체의 동일성>
Set<Employee> employeeSet = new HashSet<>();
employeeSet.add(new Employee(10, "영업1팀", "이순신", 350));
employeeSet.add(new Employee(10, "영업1팀", "이순신", 350));
-> 값이 다 똑같은 객체를 여러개 만들면, Set<E>은 중복값이 입력안되니까 안 될 것 같지만 됨
-> 왜? 객체를 매번 새로 만드니까 다 다른 객체임 (해시코드, 주소값이 다름)
-> 동일성 비교
근데, String 객체는 좀 다름
- <String 객체의 동일성>
Set<String> names = new HashSet<>();
names0.add("홍길동");
names0.add("홍길동");
-> 그러면 얘네도 객체를 매번 새로 만드니까 다른 객체여야 되는데, 왜 중복으로 인식할까?
-> String객체는 특이하게, 똑같은 값을 가지고 있으면 모두 한 객체를 바라봄
-> 그래서 아무리 names에 add기능을 통해 값을 추가해도, 한 객체의 주소값만 나옴
- < hashcode, equals 재정의 >
그러면, 사원번호가 같으면 같은 객체라고 지정을 하고 싶어
-> hashcode, equals 재정의 (동일성 x , 동등성 체크)
-> 아예 같은 객체여야 같은 객체인 동일성 비교를 -> 같은 값을 가지고 있으면 같은 객체로 보는 동등성 비교로 바꿈
import java.util.Objects;
public class Employee {
private int no;
private String dept;
private String name;
private int salary;
public Employee() {}
public Employee(int no, String dept, String name, int salary) {
super();
this.no = no;
this.dept = dept;
this.name = name;
this.salary = salary;
}
public int getNo() {
return no;
}
public String getDept() {
return dept;
}
public String getName() {
return name;
}
public int getSalary() {
return salary;
}
public void setNo(int no) {
this.no = no;
}
public void setDept(String dept) {
this.dept = dept;
}
public void setName(String name) {
this.name = name;
}
public void setSalary(int salary) {
this.salary = salary;
}
// hashCode와 equals 메소드를 재정의해서 사원번호가 같으면 같은 객체로 판단하게 한다. (동등성)
@Override
public int hashCode() {
return Objects.hash(no);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
return no == other.no;
}
@Override
public String toString() {
return "Employee [no=" + no + ", dept=" + dept + ", name=" + name + ", salary=" + salary + "]";
}
}
import java.util.HashSet;
import java.util.Set;
public class SetApp3 {
public static void main(String[] args) {
Set<Employee> employeeSet = new HashSet<>();
employeeSet.add(new Employee(10, "영업1팀", "이순신", 350)); // 값이 똑같아도, 다 다른 객체임 (해시코드, 주소값 다름)
employeeSet.add(new Employee(11, "영업1팀", "이순신", 400));
employeeSet.add(new Employee(12, "영업1팀", "이순신", 350));
employeeSet.add(new Employee(13, "영업1팀", "이순신", 380));
employeeSet.add(new Employee(14, "영업1팀", "이순신", 370));
// HashSet객체에 저장된 Employee 객체를 향상된 for문을 이용해서 반복처리하기
for(Employee emp : employeeSet) { // 오른쪽항에는 배열, 왼쪽항에는 그 배열에서 하나 꺼내서 담을 변수와 타입
System.out.println(emp);
System.out.println("사원번호: "+emp.getNo());
System.out.println("소속부서: "+emp.getDept());
System.out.println("이름: "+emp.getName());
System.out.println("급여: "+emp.getSalary());
}
System.out.println();
// HashSet객체에 저장된 사원들의 총 급여 계산하기
System.out.println("### 사원들의 총 급여 계산하기");
int totalSalary = 0;
for(Employee emp : employeeSet) {
totalSalary += emp.getSalary();
}
System.out.println("총 급여: "+totalSalary);
}
}
Employee [no=10, dept=영업1팀, name=이순신, salary=350]
사원번호: 10
소속부서: 영업1팀
이름: 이순신
급여: 350
Employee [no=11, dept=영업1팀, name=이순신, salary=400]
사원번호: 11
소속부서: 영업1팀
이름: 이순신
급여: 400
Employee [no=12, dept=영업1팀, name=이순신, salary=350]
사원번호: 12
소속부서: 영업1팀
이름: 이순신
급여: 350
Employee [no=13, dept=영업1팀, name=이순신, salary=380]
사원번호: 13
소속부서: 영업1팀
이름: 이순신
급여: 380
Employee [no=14, dept=영업1팀, name=이순신, salary=370]
사원번호: 14
소속부서: 영업1팀
이름: 이순신
급여: 370
### 사원들의 총 급여 계산하기
총 급여: 1850
'수업내용 > Java' 카테고리의 다른 글
[2022.10.06.목] List 실습, Map<K, V> 인터페이스 (0) | 2022.10.06 |
---|---|
[2022.10.05.수] 콜렉션(List<E>) (0) | 2022.10.05 |
[2022.09.30.금] Date, Format, Math 클래스 (0) | 2022.10.01 |
[2022.09.29.목] 학생, 성적 처리 실습 (0) | 2022.09.30 |
[2022.09.28.수] System 클래스 (0) | 2022.09.29 |