Set - 순서를 유지하지 않음 - 중복을 유지하지 않음 - null은 하나만 등록 가능 - 구현 클래스 HashSet, LinkedHashSet, TreeSet
HashSet - Set인터페이스 구현 클래스 - 순서를 유지하지 않음, 중복을 허용하지 않음.
LinkedHashSet - HashSet 하위 클래스 - 중복 허용하지 않음 - 순서 유지
TreeSet - SortedSet 인터페이스 구현 - 정렬된 순서에 의해 반복 - 중복 허용 안함 - Comparable 인터페이스 구현 클래스만 추가 가능
List - 순서가 있다. - 배열과 유사한 구조 - 가변 길이(저장 공간이 부족하면 자동으로 공간이 늘어남) - 중복적인 요소도 추가 가능 - 중간에 데이터를 추가하거나 삭제도 가능 - 주요 구현 클래스 - ArrayList, Vector, LinkedList, Stack 등...
ArrayList - List 인터페이스 구현 클래스 - 검색시 속도가 빠름 - 동기화 되지 않음.
LinkedList - List 인터페이스 구현 클래스 - 검색은 느림 - 앞에서 추가하고 뒤에서 삭제가 빈번한 경우 빠름 - 중간에 삽입시 속도 현저히 떨어짐 - 동기화 되지 않음
Vector - List 인터페이스 구현 클래스 - 동기화 지원, 다중 스레드 환경에서 안전
배열과 ArrayList의 차이점 - 배열 : 배열 요소의 크기를 변경할 수 없다. : 배열 중간에 데이터를 삽입 시에 기존 데이터를 덮어쓰기 때문에 기존의 데이터값은 사라진다.
- ArrayList : 가변길이의 자료구조로 데이터의 검색에 유리하며, 추가/삭제에는 성능을 고려 해야 한다. : 리스트의 처음, 끝, 중간에 요소를 추가/삭제하는 기능을 제공한다.
package ex0730;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/*
ArrayList
: List 인터페이스 구현 클래스
: 검색시 속도가 빠름
: 동기화 되지 않음(멀티 스레드에서 안전하지 않음)
LinkedList
: List 인터페이스 구현 클래스
: 검색은 느림
: 앞에서 추가하고 뒤에서 삭제가 빈번한 경우 빠름
: 앞뒤 아무데서나 추가 삭제 가 빈번한 경우
: 중간에 삽입 삭제시에는 속도가 현저히 떨어짐. LinkedList 사용하지 말 것.
: 동기화 되지 않음(멀티 스레드에서 안전하지 않음)
*/
public class Ex01_List {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("자바");
list1.add("오라클");
list1.add("서블릿");
System.out.println("ArrayList...");
print(list1);
List<String> list2 = new LinkedList<String>();
list2.add("서울");
list2.add("부산");
list2.add("대구");
System.out.println("\nLinkedList...");
print(list2);
}
public static void print(List<String> list) {
for(String s : list) {
System.out.print(s +" ");
}
System.out.println();
}
}
내가 여기에서 왜 업캐스팅을 하지? 하고 오늘 수업할 때 선생님께 질문했더니, 예제를 통해 그 이유를 알려주셨다.
업캐스팅을 안했으면 ArrayList 와 LinkedList 두 개를 선언하고 print() 메소드를 정의할 때, 매개변수에
public static void print(ArrayList<String> list1)
public static void print(LinkedList<String> list2) 이렇게 해야 되서, 업캐스팅해서 한 번만 적으면 코드가 짧아진다.
package ex0730;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Ex02_List {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
LinkedList<String> list2 = new LinkedList<>();
timeTest1("LinkedList", list2);
timeTest1("ArrayList", list1); // 앞줄과 순서를 바꿔보면 시간 차이가 보임
System.out.println("-------------------------------");
timeTest2("LinkedList", list2);
timeTest2("ArrayList", list1);
}
public static void timeTest1(String cls, List<String> list) {
long s, e;
s = System.nanoTime();
for(int i=0; i<20000; i++) {
list.add( String.valueOf(i) );
// 가장 뒤에 추가하는 경우 실행 순서에 따라 차이가 있으나 ArrayList가 빠름
}
e = System.nanoTime();
System.out.printf("%s, 시간:%,d\n", cls, (e-s));
list.clear();
}
public static void timeTest2(String cls, List<String> list) {
long s, e;
s = System.nanoTime();
for(int i=0; i<20000; i++) {
list.add( 0, String.valueOf(i) ); // 앞 추가 삭제시 LinkedList 가 빠름
}
e = System.nanoTime();
System.out.printf("%s, 시간:%,d\n", cls, (e-s));
list.clear();
}
}
package ex0729;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/*
- List 인터페이스
: 순서가 있다.
: 배열과 유사한 구조
: 가변 길이(저장 공간이 부족하면 자동으로 공간이 늘어남)
: 중복적인 요소도 추가 가능
: 중간에 데이터를 추가하거나 삭제도 가능
: 주요 구현 클래스 - ArrayList, Vector, LinkedList, Stack 등..
: 동기화 지원 : Vector - 다중 스레드 환경에서 안전
: 동기화 지원 안함 : AraayList, LinkedList - 다중 스레드 환경에서 안전하지 않음(속도 빠름)
*/
public class Ex001_List {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(); // up casting
String s;
// 마지막에 요소 추가
list.add("서울");
list.add("부산");
list.add("인천");
list.add("광주");
list.add("서울"); // 요소의 중복 가능
list.add("대전");
System.out.println(list);
// 2인덱스에 데이터 추가
list.add(2, "대구");
System.out.println(list);
// 데이터 개수 ?
System.out.println("개수 : " + list.size());
// 처음 데이터
s = list.get(0);
System.out.println("처음 : " + s);
// 두번째
s = list.get(1);
System.out.println("두번째 : " + s);
// 마지막
s = list.get(list.size() - 1);
System.out.println("마지막 : " + s);
// 처음에 한국 추가
list.add(0, "한국");
System.out.println(list);
// 처음의 데이터를 대한민국으로 수정
list.set(0, "대한민국");
System.out.println(list);
int idx;
// 인천은 몇 번째 인덱스에 ?
idx = list.indexOf("인천");
System.out.println("인천 인덱스 : " + idx);
idx = list.indexOf("세종"); // 없으면 -1
System.out.println("세종 인덱스 : " + idx);
idx = list.indexOf("서울");
System.out.println("서울(처음부터 검색) : " + idx);
idx = list.lastIndexOf("서울");
System.out.println("서울(뒤부터 검색) : " + idx);
// 부산 존재 여부
System.out.println("부산이 존재합니까 ? " + list.contains("부산"));
// 대한민국 삭제
// list.remove("대한민국");
list.remove(0);
System.out.println(list);
System.out.println("전체 출력 - 1");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
System.out.println("전체 출력 - 2");
for (String str : list) {
System.out.print(str + " ");
}
System.out.println();
System.out.println("전체 출력 - 3");
// 반복자. 순방향만 가능
Iterator<String> it = list.iterator(); // 하나씩 데이터를 꺼낸다.
while (it.hasNext()) { // 데이터가 존재하면 true, 없으면 false
String str = it.next(); // 있는 곳의 데이터를 돌려주고 다음으로 간다.
System.out.print(str + " ");
}
System.out.println();
System.out.println("역순 - 1");
for (int i = list.size() - 1; i >= 0; i--) {
System.out.print(list.get(i) + " ");
}
System.out.println();
System.out.println("역순 - 2");
// ListIterator : 순방향과 역방향 모두 이동 가능
// 반복자의 위치를 가장 마지막으로 이동
ListIterator<String> it2 = list.listIterator(list.size());
while (it2.hasPrevious()) {
String str = it2.previous();
System.out.print(str + " ");
}
System.out.println();
// 모두 지우기
list.clear();
System.out.println("모두 삭제 후 개수 : " + list.size());
}
}
List 인터페이스
순서가 있는 컬렉션
목록에서 각 요소가 삽입되는 위치를 제어 할 수 있다.
요소를 인덱스로 관리하며, 인덱스로 요소를 검색하거나 삭제 할 수 있다.
동일한 요소(객체)를 중복해서 저장할 수 있다.
List 컬렉션은 객체 자체가 저장되는 것이 아니라 객체의 번지를 참조한다.
null도 저장이 가능하며, null을 저장한 경우에는 해당 인덱스는 객체를 참조하지 않는다.
배열과 유사한 구조
가변 길이로 저장 공간이 부족하면 자동으로 공간이 늘어난다.
List를 하기 위해서 제네릭을 배운 것!
List<String> list = new ArrayList<String>();
String인 자료형을 저장할 리스트를 생성
add 메소드로 요소를 추가 할 수 있다.
size 메소드로 리스트의 크기를 알 수 있다.
set 메소드로 원하는 위치에 리스트를 변경 할 수 있다.
contains 메소드로 리스트에서 검색 후 boolean으로 반환해준다.
package ex0729;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Ex002_List {
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>();
list1.add("서울");
list1.add("부산");
list1.add("대구");
List<String> list2 = new ArrayList<String>();
list2.add("강원");
list2.add("경기");
list2.add("경상");
// list2에 list1의 모든 데이터를 추가
list2.addAll(list1);
System.out.println(list2);
// List<String> => String[]
String[] ss = list2.toArray(new String[list2.size()]);
System.out.println("리스트를 배열로 복사...");
for (String s : ss) {
System.out.print(s + " ");
}
System.out.println();
// String[] => List<String>
List<String> list3 = Arrays.asList(ss);
System.out.println("배열을 리스트로 복사 후 : " + list3);
// subList(a, b) : a인덱스에서 b-1인덱스까지의 부분 List
List<String> list4 = list3.subList(1, 4);
System.out.println(list4); // [경기, 경상, 서울]
// 전체 삭제
list1.clear();
System.out.println("전체 삭제 후 : " + list1.size());
// list2의 데이터중 [경상, 서울, 부산] 삭제
System.out.println("삭제 전 : " + list2);
list2.subList(2, 5).clear();
System.out.println("삭제 후 : " + list2);
}
}
list의 메소드를 익히고 적절하게 사용해보도록 하자!
배열과 ArrayList
배열은 한 번 크기가 결정되면 배열의 크기를 변경할 수 없다.
배열의 처음이나 중간에 데이터를 삽입하는 경우, 기존 데이터가 존재하면 데이터를 덮어쓰기 때문에 기존 데이터는 사라진다.
ArrayList는 가변 길이의 자료구조로 데이터의 검색에 유리하며, 추가 또는 삭제에는 성능을 고려해야 한다.
리스트의 처음, 끝, 중간에 자료를 추가 또는 삭제하는 기능을 제공한다.
근데 왜
List<String> list = new Arraylist<>(); 이렇게 업 캐스팅 하는 건쥐..?
package ex0729;
public class Ex06_system {
public static void main(String[] args) {
String s;
s = System.getProperty("os.name");
System.out.println("운영체제 : " + s);
s = System.getProperty("file.encoding");
System.out.println("character set : " + s); // MS949(euc-kr 유사)
s = System.getProperty("java.version");
System.out.println("자바 버전 : " + s);
s = System.getProperty("user.dir");
System.out.println("현재 작업 경로 : " + s);
}
}
package ex0729;
import java.util.Enumeration;
import java.util.Properties;
public class Ex07_system {
public static void main(String[] args) {
// 시스템 환경 설정 정보(운영체제, 인코딩, 작업 경로 등...)
Properties p = System.getProperties();
Enumeration<?> e = p.propertyNames();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
String value = p.getProperty(key);
System.out.println(key + "->" + value);
}
}
}
이런저런것들 볼 수 있음...
package ex0729;
import java.util.Scanner;
public class Ex08_system {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n;
try {
while (true) {
System.out.print("정수 ? ");
n = sc.nextInt();
if (n == 0) {
System.exit(0); // 프로그램 강제 종료. finally 블록은 실행 안됨.
// return; // finally 블럭은 실행됨.
// return 은 main() 메소드를 빠져 나가는 것으로
// main()이 종료된다고 프로그램이 종료되는 것은 아니다.
// main()은 프로그램의 진입점이지만 종료점은 아니다.
}
System.out.println("입력 값 : " + n);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("final block ...");
sc.close();
}
System.out.println("end...");
}
}
package ex0729;
public class Ex01 {
public static void main(String[] args) {
Test1<Integer> t = new Test1<>();
t.append(10);
t.append(20);
t.append(30);
// t.append("자바"); // 컴파일 오류
Integer i = t.get(2);
System.out.println(i);
}
}
class Test1<E> {
private Object[] data;
private int count;
public Test1() {
data = new Object[10];
}
public void append(E e) {
if(count >= data.length) {
// ArrayIndexOutOfBoundsException() : unchecked 예외. 배열의 첨자가 초과한 경우 발생
throw new ArrayIndexOutOfBoundsException("요소의 개수를 초과 했습니다.");
}
data[count++] = e;
}
@SuppressWarnings("unchecked") // 부적절한 컴파일러의 경고를 제거하기 위해 사용
public E get(int index) {
if(index >= count) {
throw new ArrayIndexOutOfBoundsException(index);
}
// 제네릭으로 casting 하면 경고가 발생
return (E) data[index];
}
public int getCount() {
return count;
}
}
@SuppressWarnings("unchecked") // 부적절한 컴파일러의 경고를 제거하기 위해 사용
하지만 정확하게 알 때만 사용. 거의 사용하지 않는다!!
<위 코드는 아래와 비교해서 이해할 것!>
package ex0729;
public class Ex02 {
public static void main(String[] args) {
Test2<Integer> t = new Test2<>();
t.append(10);
t.append(20);
t.append(30);
// Integer[] i = t.get(); // 런타임 오류. ClassCastException. 배열은 강제 형변환이 안됨.
// Cast가 필요
Object[] oo = t.get();
for (Object o : oo) {
Integer i = (Integer) o;
System.out.println(i);
}
// 제네릭배열은 의미가 없다.
}
}
class Test2<E> {
private E[] data;
private int count;
@SuppressWarnings("unchecked")
public Test2() {
// 제네릭 배열 메모리 할당
// data = new E[10]; // 컴파일 오류
data = (E[]) new Object[10]; // 제네릭은 Object로 메모리할당을 해야 함.
}
public void append(E e) {
if (count >= data.length) {
throw new ArrayIndexOutOfBoundsException("배열 요소의 개수를 초과 했습니다.");
}
data[count++] = e;
}
public E[] get() {
return data;
}
public int getCount() {
return count;
}
}
위에서는 Object로 배열을 만들어서 생성자에서 배열의 메모리 할당을 해주었는데,
밑에서는 제네릭클래스에서 제네릭 배열을 만들었다.
배열의 선언을 위해 생성자에서 할당하려고 했을 때
data = new E[10]; 은 컴파일 오류가 발생했다. 메모리 할당을 위해서는 Object로 해야 했다. 그래서 배열 메모리 할당 후에 data에 다시 제네릭타입으로 캐스팅하고 넣었다. 여기서 느낌표가 발생해서 다시
@SuppressWarnings("unchecked") 를 넣어줬다.
메인 클래스에서 제네릭타입을 Integer로 해서 객체를 생성한 후 Integer 값들을 3개를 넣었는데 그 배열을 다시 만들으려 했으나
Integer[] i = t.get(); // 런타임 오류. ClassCastException. 배열은 강제 형변환이 안된다.
결국 이 배열을 받기 위해서는 Object로 배열을 생성해서 받아야 했다.
package ex0729;
public class Ex03 {
public static void main(String[] args) {
Test3<Integer> t = new Test3<>();
Integer[] i = {10,20,30};
t.set(i);
Integer[] i2 = t.get();
System.out.println(i2[1]);
}
}
class Test3<E> {
private E[] data;
public void set(E[] data) {
this.data = data;
}
public E[] get() {
return data;
}
}
package ex0729;
public class Ex04 {
public static void main(String[] args) {
Test4<Number> ob1 = new Test4<>();
ob1.set(new Integer(30)); // 타입 매개변수의 상속 관계는 성립
System.out.println(ob1.get());
// Number n = ob1.get();
// Integer i = ob1.get(); // 컴파일 오류
// Integer i = (Integer)ob1.get();
// Number n = new Integer(30); // 업 캐스팅
Test4<Integer> ob2 = new Test4<>();
ob2.set(new Integer(30));
// Test4<Number> ob3 = ob2; // 컴파일 오류. 제네릭은 업캐스팅 불가.
}
}
class Test4<T> {
private T t;
public void set(T t) {
this.t=t;
}
public T get() {
return t;
}
}
제네릭 타입을 Number로 주었기 때문에
Integer i = ob1.get(); 은 컴파일 오류이고 강제적으로 캐스팅을 한 후에는 가능하다.