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(); 은 컴파일 오류이고 강제적으로 캐스팅을 한 후에는 가능하다.
package ex0728;
// import java.util.Arrays;
public class Ex11_generic {
public static void main(String[] args) {
Object[] obj = new Object[5];
// Object는 모든 클래스 객체를 참조할 수 있다. (up casting)
obj[0] = new String("서울");
obj[1] = new String("부산");
obj[2] = new Integer(30);
obj[3] = new Integer(50);
obj[4] = new String("인천");
// Arrays.sort(obj); // ClassCastException 런타임 오류
// String s = (String)obj[2]; // ClassCastException 런타임 오류
if (obj[1] instanceof String) { // obj[2] 너 String 형이니?
String s = (String) obj[1]; // 스트링 형이면 다운캐스팅하고
System.out.println(s + ":" + s.length()); // 출력하고 : 길이도 출력해
}
for (Object o : obj) {
System.out.println(o);
}
}
}
Object는 모든 클래스들의 조상이므로 (시조라고 해야하나 ㅋㅋ)
자식들은 Object클래스로 up casting이 가능하다. 지금 이 코드는 숫자나 문자열 모두 그냥 Object에 넣었다.
이럴 경우 데이터타입에 관계없이 모든 것을 넣을 수 있다.
하지만 자료형이 다 제각각이므로 Arrays.sort(obj); 과 같은 메소드를 사용하면 런타임 오류가 발생한다.
Integer 30을 강제로 캐스팅 하면 ClassCastException 이라는 런타임 오류가 발생한다. 그래서 A instanceof B 를 써서 배열A에 들어가 있는 데이터타입이 B 이면 강제로 다운캐스팅(다운캐스팅은 업캐스팅한 것만 가능)하고 출력하라고 밑에서 if문을 쓴 것이다.
package ex0728;
public class Ex12_generic {
public static void main(String[] args) {
Test2 t1 = new Test2();
// t1.set(new String("서울"));
t1.set("서울");
String s = (String)t1.get();
System.out.println(s);
// Integer i1 = (Integer) t1.get(); // ClassCastException 런타임 오류.
Test2 t2 = new Test2();
t2.set(30);
Integer i2 = (Integer)t2.get();
System.out.println(i2);
}
}
class Test2 {
private Object ob;
public void set(Object ob) {
this.ob = ob;
}
public Object get() {
return ob;
}
}
s, i2에 넣을 때는 s의 데이터타입인 String으로 다운캐스팅 i2의 데이터타입인 Integer로 다운캐스팅을 해야 한다.
본격적으로 Generic에 대해 알아보자.
다양한 타입의 객체를 다루는 클래스나 인터페이스에서 사용할 데이터 타입을 인스턴스를 생성할 때 결정하는 것으로 JDK 5.0부터 지원한다.
성능 저하를 유발하는 강제 캐스팅을 줄일 수 있다.
컴파일 할 때 타입 오류를 체크하므로 객체 타입의 안정성을 높인다.
반복적인 코드를 줄일 수 있으며, 재사용성 증가로 유지보수가 편리하다.
package ex0728;
public class Ex13_generic {
public static void main(String[] args) {
Test3<String> t1 = new Test3<String>();
t1.set("서울");
// t1.set(50); // 컴파일 오류
String s = t1.get();
System.out.println(s);
// Integer i1 = (Integer)t1.get(); // 컴파일 오류. 미연에 방지 할 수 있다!
Test3<Integer> t2 = new Test3<>(); // new 뒤에 <>에서는 자료형 생략가능. new Test3<Integer> 에서 Integer생략
t2.set(30);
Integer n = t2.get();
System.out.println(n);
/*
Test3 t3 = new Test3(); // 가능하지만 경고 발생. 제너릭은 Object로 처리.
t3.set("서울");
t3.set(50);
*/
}
}
class Test3<T> { // <> 안에는 마음대로 이름을 줄 수 있다.
private T t; // T에 자료형은 String, Integer, Long ...등이 올 수 있음. 아직 정해지지 않았다.
public void set(T t) {
this.t = t;
}
public T get() {
System.out.println(t.getClass()); // 무슨 클래스인지 볼 수 있다.
return t;
}
}
객체를 생성할때 <> 안에 어떤 데이터타입만 저장할 것인지 설정을 해서 생성할 수 있다.
Test3<String> t1 = new Test3<String>();
t1.set(50); 으로 주면 컴파일 오류가 생기기 때문에 안정적으로 코딩할 수 있다.
제네릭을 설정하지 않고 그냥
Test3 t3 = new Test3(); 으로 객체를 생성하면 경고가 발생한다. 이때 제네릭은 Object로 처리되기 때문에
t3.set("서울");
t3.set(50);
모두 넣을 수 있다. 하지만 비추천.
제네릭을 만들 때 <> 안에 마음대로 이름을 줄 수 있으나 관습적으로 대문자 알파벳 한 문자를 사용하고
E : Element
K : Key
N : Number
T : Type
V : Value
S, U, V : 2nd, 3rd, 4th types..
으로 사용한다.
마지막에 확인해보면 t1의 클래스는 <String>으로 줬기 때문에 String으로 뜨고, t2는 Integer로 준 것을 확인 할 수 있다.
package ex0728;
public class Ex14_generic {
public static void main(String[] args) {
Test4<String, Integer> ob = new Test4<>();
ob.set("자바", 100);
ob.print();
ob.set("스프링", 200);
ob.print();
}
}
class Test4<T, U> {
private T t;
private U u;
public void set(T t, U u) {
this.t = t;
this.u = u;
}
public void print() {
System.out.println("T : "+t.getClass().getName()+", "+t);
System.out.println("U : "+u.getClass().getName()+", "+u);
}
}
제네릭 클래스에 멀티 타입 파라미터를 준 경우이다. 만들때 <String, Integer>라고 했으니 T의 자료형은 String형이되고 U의 자료형은 Integer형이 된다.
package ex0728;
public class Ex15_generic {
public static void main(String[] args) {
Test5<Integer> ob = new Test5<>();
ob.set(30);
Integer i = ob.get();
System.out.println(i);
// Test5<String> ob2 = new Test5<>(); // 컴파일 오류 발생.
// Number를 상속받은 class가 아니기 때문이다.
}
}
// 제한된(한정된) 타입 파라미터(bounded type parameter)
// Number를 상속받은 클래스만 가능 (Integer, Long, Double ...)
class Test5<T extends Number> {
private T t;
public void set(T t) {
this.t=t;
}
public T get() {
return t;
}
}
제한된 타입 파라미터만을 받도록 설정한 제네릭 클래스.
T extends Number를 통해 Number를 상속받았기 때문에 그 클래스에 포함되는 것들만 파라미터로 받을 수 있다.
package ex0728;
public class Ex16_genericMethod {
public static void main(String[] args) {
Test6 t = new Test6();
t.print("자바");
t.print(20);
t.disp(30);
// t.disp("자바"); // 컴파일 오류
}
}
// generic Method
class Test6 {
public <U> void print(U u) {
System.out.println(u.getClass().getName() + ", " + u);
}
public <U extends Number> void disp(U u) {
System.out.println(u.getClass().getName() + ", " + u);
}
}
제네릭 메소드 타입 파라미터를 하나 이상 갖는 메소드를 말한다.
선언 방법은 리턴 타입 앞에 <> 기호를 추가하고 <> 기호 안에 타입 파라미터를 기술 한 후 리턴 타입과 매개 변수타입에서 타입 파라미터를 사용한다.
disp(U u) 메소드의 경우 받는 자료형을 Number를 상속받은 것들만 가능하게 설정했으므로
t.disp("자바"); 의 경우 컴파일 오류이다.
package ex0728;
public class Ex17_generic {
public static void main(String[] args) {
TestImpl7<Integer> ob1 = new TestImpl7<>();
ob1.print(10);
DemoImpl7 ob2 = new DemoImpl7();
ob2.print("자바");
}
}
// 제네릭 인터페이스
interface Test7<T> {
public void print(T t);
}
// 구현 클래스-1
class TestImpl7<T> implements Test7<T> {
@Override
public void print(T t) {
System.out.println(t);
}
}
// 구현 클래스-2
class DemoImpl7 implements Test7<String> {
@Override
public void print(String t) {
System.out.println(t);
}
}