쓰레드를 구현하는 방법은 1. Tread 클래스를 상속하는 것과 2. Runnable 인터페이스를 구현하는 방법이 있는데
Tread 클래스를 상속할 경우 다른 클래스의 상속을 못 받기 때문에 Runnable 인터페이스를 구현하는 방법을 일반적으로 사용한다.
알다시피 자바는 단일 상속만을 지원하기 때문이다.
main 메서드를 채워주듯이 쓰래드 내에서 run() 메서드를 구현해주면 된다.
1번 방법은 첫번째 박스와같이 스레드를 생성 > 곧바로 start() 를 호출하여 실행하면 된다.
2번 방법은 두번째 박스와 같이 Runnable 인터페이스를 구현 후 start() 메서드를 호출한다.
Runnable인터페이스를 구현하기 위해서 해야 할 일은 추상메서드인 run()의 몸통{ }을 만들어 주는 것뿐이다.
쓰레드를 구현한다는 것은, 위의 두 방법 중 어떤 것을 선택하든지, 그저 쓰레드를 통해 작업하고자 하는 내용으로 run()의 몸통({ })을 채우는 것일 뿐이다.
Runnable인터페이스를 구현한 경우,Runnable인터페이스를 구현한 클래스의 인스턴스 를 생성한 다음,이 인스턴스를 Thread클래스의 생성자의 매개변수로 제공해야 한다.
예제에서 두가지 방법으로 쓰레드를 구현하였다.
방법이 달라도 run() 메소드 내에 쓰레드가 수행할 작업을 작성하는 것은 동일하다.
run() 메소드 대신 start() 메소드를 실행하는 것에 유의한다.
Tread 메소드를 상속하여 작성한 쓰래드는 현재 실행중인 쓰래드의 이름을 알고싶을때 this.getName() 만으로 손쉽게 호출할 수 있다.
Runnable 인터페이스를 구현하여 작성한 쓰래드는 Tread.currentTread().getName 과 같이 현재 실행중인 스레드 객체를 불러와 이름을 호출해야하는 것을 알 수 있다.
Thread클래스를 상속받으면, 자손 클래스에서 조상인 Thread클래스의 메서드를 직접 호출할 수 있지만, Runnable을 구현하면 Thread클래스의 static메서드인 current ThreadO를 호출하여 쓰레드에 대한 참조를 얻어 와야만 호출이 가능하다.
Thread를 상속받은 ThreadExl_1에서는 간단히 getName()을 호출하면 되지만, Runnable을 구현한 ThreadExl_2에는 멤버라고는 run()밖에 없기 때문에 Thread클래 스의 getName()을 호출하려면, ‘Thread.currentThread().getName()' 와 같이 해야한다.
쓰레드의 이름을 지정하지 않으면 Tliread-번호’ 의 형식으로 이름이 정해진다.
쓰레드를 생성했다고 해서 자동으로 실행되는 것은 아니며 startO를 호출해야만 쓰레드가 실행된다.
사실은 startO가 호출되었다고 해서 바로 실행되는 것이 아니라, 일단 실행대기 상태에 있다가 자신의 차례가 되어야 실행된다. 물론 실행대기중인 쓰레드가 하나도 없으면 곧바 로 실행상태가 된다.
작성한 쓰레드는 멀티 스레드로서 두가지 스레드가 동시에 실행되는데 데이터를 넓혀보면 다음과같은결과를 확인할 수 있다.
작성순서와 길이 등 우선순위가 실행시마다 다르게 출력됨을 알 수 있다.
이는 OS 의 스케줄러가 어떤 스래드를 실행할지 정하기 때문이다.
만약 콘솔이 한줄로 나온다면 이미지속 버튼을 클릭하여 줄바꿈 한다.
한 가지 더 알아 두어야 하는 것은 한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다는 것이다.
즉, 하나의 쓰레드에 대해 start( )가 한 번만 호출될 수 있다는 뜻이다.
그래서 만일 쓰레드의 작업을 한 번 더 수행해야 한다면 새로운 쓰레드를 생성한 다음에 start()를 호출해야 한다.
멀티스레드로 작성된 내용을 주석처리 후 싱글 스레드 작성로 변경 시 출력결과를 보자
하나의 작업(0)이 끝나야 다음작업(1)이 실행되는 것을 확인할 수 있다.
그러나 멀티스레드로 작성시 두 작업이 번갈아 실행된다.
어떤 작업이 먼저, 얼마나 실행될지는 앞서 기술한대로 OS 스케줄러에 따라 결정된다.
쓰레드의 실행 - start()
main메서드에서 run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아니라 단순 히 클래스에 선언된 메서드를 호출하는 것일 뿐이다
반면에 start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생 성한 다음에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 한다.
모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 하기 때문 에,새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종 료되면 작업에 사용된 호출스택은 소멸된다.
쓰래드를 구현한 후 작성한 메소드는 run()이었지만 다음과같이 호출시엔 start() 메서드를 사용한다.
쓰래드의 실행전 다음과같은 두가 사항을 기억해야 한다.
1. 이 start() 메서드를 호출하는 것이 즉시 실행을 의미하는 것은 아니라는 것
2. 또한 먼저 작성한 메서드가 먼저 실행되는 것도 아니라는 것
예제 속 콘솔창을 보자.
두번째 줄에 작성한 t2.start() 가 먼저 실행되어 0보다 1이 먼저 출력되었으며
0과 1을 얼마나 출력할지, 얼마나 지속할지 일정하게 정해진 것이 아니라는 점을 확인할 수 있다.
우리는 알게모르게 쓰레드를 사용해왔다. 바로 main 메서드이다.
start() 메소드는 쓰레드의 즉시 실행을 의미하지 않는다고 했다.
두번째 그림 2.Call stack 에서 확인할 수 있듯 '새로운 호출스택을 생성' 해 줄 뿐이다.
이는 해당 스택에서 run() 메소드가 실행될 수 있음을 의미한다.
main 메서드가 기본 쓰레드를 생성후 start() 메서드로 새로운 호출스택을 생성한다.
이렇게 start() 로 인해 생성된 스택에서 또다른 쓰레드(run 쓰레드)는 이전의 (main 쓰레드)와는 별개로 독립적인 작업을 수행할 수 있는 것이다.
위의 그림에서와 같이 쓰레드가 둘 이상일 때는 호출스택의 최상위에 있는 메서드일지라도 대기상태에 있을 수 있다.
스케줄러는 실행대기중인 쓰레드들의 우선순위를 고려하여 실행순서와 실행시간을 결정하고,
각 쓰레드들은 작성된 스케줄에 따라 자신의 순서가 되면 지정된 시간동안 작업 을 수행한다.
지금까지는 main메서드가 수행을 마치면 프로그램이 종료되었으나, 위의 그림에서와 같 이 main메서드가 수행을 마쳤다하더라도 다른 쓰레드가 아직 작업을 마치지 않은 상태라면 프로그램이 종료되지 않는다.
객체 입출력(ObjectInputStream/ObjectOutputStream) 등의 기능을 제공하는 보조스트림이 있다
EX)
FileInputStream fis = new FileInputStream("sample.txt"); //기반 스트림 생성
BufferedInputStream bis = new BufferedInputStream(fis); //보조스트림 생성
bis.read(); //보조스트림으로부터 데이터 읽어옴
성능 향상 보조 스트림
느린 속도로 인해 입출력 성능에 영향을 미치는 입출력 소스를 이용하는 경우 사용
입출력 소스와 직접 작업하지 않고 버퍼에 데이터를 모아 한꺼번에 작업을 하여 실행 성능을 향상 (입출력 횟수를 줄임)
BufferedInputStream/Reader
BufferedOutputStream/Writer
형변환 보조 스트림
기본 스트림이 byte 기반 스트림이고, 보조 스트림이 char 기반 스트림인 경우에 사용
형변환 보조 스트림의 종류
System.in : 콘솔로부터 데이터를 입력 받음 System.out : 콘솔로 데이터를 출력 System.err : 콘솔로 데이터를 출력
ex :
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.in을 InputStreamReader로 변환하여 바이트기반 스트림을 문자 기반 스트림으로 변환 후 버퍼를 이용한
보조스트림과 연결
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
Application1
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Application1 {
public static void main(String[] args) {
/* java.io 패키지의 입출력 스트림은 기본 스트림과
* 필터 스트림으로 분류할 수 있다.
* 기본 스트림은 외부데이터에 직접 연결이 되는 스트림이고
* 필터 스트림은 외부 데이터에 직접 연결하는 것이 아니라
* 기본 스트림에 추가로 사용할 수 있는 스트림이다.
* 주로 성능을 향상 시키는 목적으로 사용되며 생성자를 보면
* 구분이 가능하다.
* 생성자 쪽에 매개변수로 다른 스트림을 이용하는 클래스는
* 필터 스트림이라고 볼 수 있다. */
/* 버퍼를 이용해서 성능 향상을 시키는 보조스트림 */
/* BufferedInputStream/BufferedOutputStream */
/* BufferedReader/BufferedWriter */
/* BufferedWriter 인스턴스 생성 */
BufferedWriter bw = null; //임포트 필요
try {
/* IOException e 핸들링 */
bw = new BufferedWriter(new FileWriter("src/com/greedy/section03/filterstream/testBuffered.txt"));
bw.write("안녕하세요\n");
bw.write("반갑습니다\n");
/* 버퍼를 이용하는 경우 버퍼가 가득 차면 자동으로 내보내기를하지만
* 버퍼가 가득 차지않은 상태에선 강제로 내보내기 해야한다.
* 이때 flush()를 해주면 파일에 기록된다. */
bw.flush();
// 안녕하세요
// 반갑습니다
} catch (IOException e) {
e.printStackTrace();
} finally {
/* close()를 호출하면 내부적으로 flush()를 하고나서
* 자원을 반납한다. */
if (bw != null) {
try {
/* close도 try-catch 구문으로 IOException을
* 핸들리 해 주어야 한다. */
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/* BufferedReader
* 버퍼에 미리 읽어온 후 한 줄 단위로 읽어들이는 기능을 제공하며
* 기본 스트림보다 성능을 개선시킨다.
* */
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("src/com/greedy/section03/filterstream/testBuffered.txt"));
/* readLine() 메소드를 추가로 제공한다.
* 버퍼의 한 줄을 읽어와서 문자열로 반환한다. */
String temp;
while((temp = br.readLine()) != null ) {
// 더이상 읽을값이 없으면 null 반환
System.out.println(temp);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Application2
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class Application2 {
public static void main(String[] args) {
/* 형변환 보조 스트림
* 기본 스트림이 byte 기반 스트림이고,
* 보조스트림이 char 기반스트림인 경우에 사용된다.
* 중간에서 변환을 해주는 용도이다.
* */
/* 표준 스트림
* 자바에서는 콘솔이나 키보드 같은 표준 입출력 장치로부터
* 데이터를 입출력하기 위한 스트림을 표준 스트림 형태로
* 제공하고 있다. System 클래스의 필드 in,out,err가
* 대상 데이터의 스트림을 의미한다.
*
* System.in (InputStream) : 콘솔로부터 데이터를 입력받는다.
* System.out (PrintStream) : 콘솔로 데이터를 출력한다.
* System.err (PrintStream) : 콘솔로 데이터를 출력한다.
*
* 즉, 자주 사용되는 자원에 대해 미리 스트림을 생성해두었기 때문에
* 개발자가 별도로 스트림을 생성하지 않아도 된다.
*
* */
/* 이런 표준 스트림 중 콘솔로부터 읽어오는 기반 스트림이 InputStream인데
* Buffer를 이용해서 성능을 향상 시키고 싶은 경우에
* 형변환 보도스트림을 사용할 수 있다.
*
* */
/* 보조 스트림을 통해 기반 스트림을 '형변환' 해주어야 보조스트림과 연결할 수 있다. */
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
/* readLine() : '한줄씩' 입력받음 */
try {
System.out.println("문자열 입력 : ");
String value = br.readLine();
System.out.print("value : " + value);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println();
/* 출력을 위한 것도 동일한 방식으로 사용할 수 있다. */
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
try {
bw.write("java oracle jdbc");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
Application3
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Application3 {
public static void main(String[] args) {
/* 외부 데이터로부터 읽어오는 데이터를 바이트형으로만 읽어오면
* 정수, 문자, 문자열 등 여러 데이터 타입을 취급하는 경우
* 별도로 처리를 해주어야 한다.
*
* 예) 정수 입력 받아 처리하려면 parsing을 해주어야 한다.
* */
/* 데이터 자료형 별로 처리하는 기능을 추가한 보조 스트림을 제공하고 있다.
* DataInputStream/DataOutputStream */
/* DataOutputStream
* 데이터 형 별로 파일에 기록하는 DataOutputStream 인스턴스 생성 */
DataOutputStream dout = null;
try {
// 보조 스트림이므로 기반 스트림 필요
dout = new DataOutputStream(new FileOutputStream("src/com/greedy/section03/filterstream/score.txt"));
/* 파일에 자료형별로 기록 */
dout.writeUTF("김영희"); //문자열을 입력하려면 writeUTF 이용
dout.writeInt(95);
dout.writeChar('A');
dout.writeUTF("김철수");
dout.writeInt(87);
dout.writeChar('B');
dout.writeUTF("홍길동");
dout.writeInt(73);
dout.writeChar('C');
} catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
} finally {
if (dout != null) {
try {
dout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/* text 파일에 int 부분이 제대로 해석 되지않고 결과가나옴
* 데이터로 읽어와서 다시 사용해야
* */
/* 데이터형 별로 읽어오는 DataInputStream 인스턴스 작성 */
DataInputStream din = null;
try {
din = new DataInputStream(new FileInputStream("src/com/greedy/section03/filterstream/score.txt"));
/* 파일에 기록한 순서대로 타입을 읽어와야한다. utf -> int -> char */
/* 만약 순서를 임의로 바꿀 시 올바르게 데이터를 읽어오지 못한다. */
System.out.println(din.readUTF() + ", " + din.readInt() + ", " + din.readChar());
System.out.println(din.readUTF() + ", " + din.readInt() + ", " + din.readChar());
System.out.println(din.readUTF() + ", " + din.readInt() + ", " + din.readChar());
/* 무한루프로 읽어들이게 되면 파일에 더이상 읽을 것이 없는 경우
* EOFException을 발생시킨다.
* catch 블럭에 EOFException을 핸들링하는 코드를 추가한다.
* */
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (din != null) {
try {
din.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
기본 데이터 타입 보조 스트림
기본 자료형 별 데이터를 읽고 쓰기 가능하도록 기능을 제공
(단, 입력된 자료형의 순서와 출력 될 자료형의 순서는 일치해야 함)
직렬화와 역직렬화
직렬화(serialization)
Serializable인터페이스를 implements하여 구현
serialVersionUID 필드
직렬화된 클래스와 같은 클래스임을 알려주는 식별자
컴파일 시 JVM이 자동으로 serialVersionUID 정적필드를 추가해 주기 때문에 별도로 작성하지 않아도 오류는 나지 않지만 자동 생성 시 역질력화에서 예상하지 못한 InvalidClassException을 유발할 수 있으므로 명시해 줄 것을 권장
ex)
private static final long serialVersionUID = -6423919775137290062L;
Application4
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.greedy.section03.filterstream.dto.MemberDTO;
public class Application4 {
public static void main(String[] args) {
/* 객체단위로 입출력을 하기 위한
* ObjectInputStream/ObjectOutputStream 을 살펴보자
* */
MemberDTO[] outputMembers = {
new MemberDTO ("user01", "pass01", "김영희", "young7777.com", 25, '여', 1250.7),
new MemberDTO ("user02", "pass02", "김철수", "chul999.com", 27, '남', 1221.6),
new MemberDTO ("user03", "pass03", "유관순", "yoo123.com", 18, '여', 1234.3)
};
ObjectOutputStream objOut = null;
try {
objOut = new ObjectOutputStream(new BufferedOutputStream
(new FileOutputStream("src/com/greedy/section03/filterstream/testObjectStream.txt")));
for(int i = 0; i < outputMembers.length; i++) {
objOut.writeObject(outputMembers[i]);
}
objOut.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objOut != null) {
try {
objOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/* 오류발생 : NotSerializableException
* 이는 직렬화 처리를 해주지않아서 발생하는 에러이다.
*
* 직렬화란?
* 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부에서도 사용할 수 있도록
* 바이트(byte) 형태로 데이터를 변환하는 기술을 말한다.
*
* 반대로 바이트로 변환 된 데이터를 다시 객체로 변환하는 기술을 역직렬화라고 한다.
*
*
* */
MemberDTO[] inputMembers = new MemberDTO[3];
ObjectInputStream objIn = null;
try {
objIn = new ObjectInputStream(new BufferedInputStream(new FileInputStream("src/com/greedy/section03/filterstream/testObjectStream.txt")));
/* 읽어온 object가 해당하는 class가 없을 경우 ClassNotFoundException
* 이 발생할 수 있다. */
// while(true) {
// System.out.println(objIn.readObject());
// }
for (int i = 0; i < inputMembers.length; i++) {
inputMembers[i] = (MemberDTO) objIn.readObject();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
// } catch (EOFException e) {
// System.out.println("파일을 모두 읽어왔습니다.");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (objIn != null) {
try {
objIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
for (MemberDTO member : inputMembers) {
System.out.println(member);
}
/* 직렬화 처리한 클래스에서 transient 된 point 필드는 숨김처리 되었다. */
// MemberDTO [id=user01, pwd=pass01, name=김영희, email=young7777.com, age=25, gender=여, point=0.0]
// MemberDTO [id=user02, pwd=pass02, name=김철수, email=chul999.com, age=27, gender=남, point=0.0]
// MemberDTO [id=user03, pwd=pass03, name=유관순, email=yoo123.com, age=18, gender=여, point=0.0]
}
}
class MemberDTO implements java.io.Serializable
/* 객체로 입출력을 하기 위해서는 반드시 직렬화 처리를 해야한다.
* java.io.Serializable 라는 인터페이스를 상속받아야 한다.
*
* 직렬화 대상 클래스에 Serializable 인터페이스만 구현하면
* 직렬화가 필요한 상황인 경우 해당 인터페이스를 상속받았을시 데이터를 직렬화 처리한다.
*
* */
/* 노란줄이 간 이유 : does not declare a static final serialVersionUID field of type long */
public class MemberDTO implements java.io.Serializable {
/* 직렬화 시의 id와 역직렬화시의 id가 불일치하면 역직렬화에 실패한다.
* 명시적으로 작성해두지 않으면 jvm이 자동 생성하므로 수정사항 발생 시 id도 변경된다.
* 따라서 명시적으로 작성해두는 것을 권장한다. */
private static final long serialVersionUID = 7171399052371122105L;
/* 회원 정보를 관리하기 위한 용도의 DTO 클래스 */
/* 필드 선언 */
private String id;
private String pwd;
private String name;
private String email;
private int age;
private char gender;
/* 직렬화 대상에서 제외하고 싶은 필드의 경우 transient키워드를 이용할 수 있다. */
private transient double point; // 입출력의 대상에서 제외하겠다는 뜻
/* 기본 생성자 */
public MemberDTO() {}
/* 매개변수 생성자 */
public MemberDTO(String id, String pwd, String name, String email, int age, char gender, double point) {
super();
this.id = id;
this.pwd = pwd;
this.name = name;
this.email = email;
this.age = age;
this.gender = gender;
this.point = point;
}
/* 게터세터 생성 */
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public double getPoint() {
return point;
}
public void setPoint(double point) {
this.point = point;
}
/* toString() 오버라이드 */
@Override
public String toString() {
return "MemberDTO [id=" + id + ", pwd=" + pwd + ", name=" + name + ", email=" + email + ", age=" + age
+ ", gender=" + gender + ", point=" + point + "]";
}
}
자바7에서 추가된 기능으로 finally에서 작성했던 close처리를 try문에서 자동으로 close처리
Application1
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Application1 {
public static void main(String[] args) {
/* 위에서 배운 예외처리를 가장 많이 활용하는 것이 io(input/output)
* 이다. 아직 io를 배우지 않았지만 io문법보다는 try-catch 블럭의
* 실제 사용과 흐름에 집중해서 살펴보자. */
/* FileNotFoundException 발생 */
// BufferedReader in = new BufferedReader(new FileReader("test.dat"));
BufferedReader in = null;
/* try 블럭 내부와 finally 블럭 내부에서 모두 사용하기위해
* 지역변수가 아닌 전역 변수로 선언
* 즉, finally 블럭에서 사용하려면 레퍼런스 변수를 try 블럭
* 밖에서 선언해야 한다. */
try {
/* FileReader라는 클래스의 생성자에 예외를 throws해놓았다.
* 사용하는 쪽에서 반드시 예외 처리를 해야하기 때문에 try-catch
* 블럭안에서 생성자를 호출하여 인스턴스를 생성해야 한다. */
in = new BufferedReader(new FileReader("test.dat"));
// FileNotFoundException: test.dat
// (지정된 파일을 찾을 수 없습니다)
String s;
/* readLine메소드도 IOException을 throws 하므로
* catch 블록을 추가해서 예외처리 구문을 작성해야 한다. */
while ((s = in.readLine()) !=null) {
System.out.println(s);
}
/* FileNotFoundException와 EOFException 을
* 동시에 처리할 수 있다.
* 같은 레벨의 자손을 한 번에 처리할 수 있다.
* */
} catch (FileNotFoundException | EOFException e) {
e.printStackTrace();
} catch (IOException e) {
/*입출력에 관한 추상화 된 예외 클래스이다.
* FileNotFoundException와 EOFException은 IOException의 후손이다.
*
* catch 블럭은 여러개를 작성할 시 상위 타입이 하단에 오고
* 후손 타입이 먼저 작성되어야 한다.
*
* */
e.printStackTrace();
} finally {
/* 파일을 스트림 해왔다면 반드시 닫아주는 작업이 필요
* 예외 처리 구문과 상관없이 반드시 수행해야하는 경우 작성하며
* 보통 사용한 자원을 반납할 목적으로 사용하게 된다.
* */
try {
/* 입출력에 사용한 스트림을 닫아주는 메소드이다.
* IOException을 위임한 메소드이기 때문에 finally 블럭
* 안이더라도 예외처리를 중첩으로 해주어야 한다. */
/* 그냥 실행하면 NullPointerException이 발생한다.
* 파일을 찾지 못해 객체를 생성하지 못하고
* 레퍼런스 변수는 null 값을 가지고 있는데
* null 을 참조하는 상태에서 참조연산자 사용시 발생하는 예외이다.
* NullPointerException은 uncheckedException이므로
* try-catch로 처리하기 보다는
* 보통은 if-else 구문으로 해결가능하다.
* */
// in.close();
// java.lang.NullPointerException
if(in != null)
in.close();
} catch (IOException e) {
e.printStackTrace();
} // IOException 발생
}
}
}
Application2
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Application2 {
public static void main(String[] args) {
/* try-with-resource */
/* JDK 1.7에서 추가된 문법이다.
* close 해야 하는 인스턴스의 경우 try 옆에 괄호안에서 생성하면
* 해당 try-catch 블럭이 완료될 때 자동으로 close 처리 해준다.
* */
/* 소괄호 안에서 스트림을 읽어온다. */
try (BufferedReader in = new BufferedReader(new FileReader("test.dat"))) {
String s;
while ((s = in.readLine()) != null) {
System.out.println(s);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Exception과 오버라이딩
오버라이딩 시 throws하는 Exception의 개수와 상관없이 같거나 후손 범위여야 함
import java.io.IOException;
public class SuperClass {
/* 예외를 던지는 메소드를 하나 작성한다. */
public void method() throws IOException {}
}
class SubClass extends SuperClass
import java.io.FileNotFoundException;
import java.io.IOException;
public class SubClass extends SuperClass {
/* 예외없이 오버라이딩 할 수 있다. */
// @Override
// public void method() {}
// /* 같은 예외를 던져주는 구문으로 오버라이딩 할 수 있다. */
// @Override
// public void method() throws IOException {}
/* 부모의 예외 처리 클래스보다 상위의 예외로는
* 후손클래스를 오버라이딩 할 수 없다. */
// @Override
// public void method() throws Exception {}
/* 하지만 부모의 예외처리 클래스보다 더 하위에 있는 예외
* (즉, 더 구체적인 예외상황)인 경우에는 오버라이딩 할 수 있다. */
@Override
public void method() throws FileNotFoundException {}
}
입출력
File 클래스
파일 시스템의 파일을 표현하는 클래스
파일크기, 파일 속성, 파일 이름 등의 정보와 파일 생성 및 삭제 기능을 제공
File 인스턴스 생성
File file = new File(“file path");
File file = new File("C:/data/fileTest.txt");
파일/디렉토리 생성 및 삭제 메소드
파일/디렉토리 생성 및 삭제 메소드
Application
import java.io.File;
import java.io.IOException;
public class Application {
public static void main(String[] args) {
/* File을 이용한 스트림을 사용하기 앞서
* File 클래스의 기본사용법을 확인 해보자. */
/* jdk 1.0 부터 지원하는 API로 파일 처리를 수행하는 대표적인 클래스이다.
* 대상 파일에 대한 정보로 인스턴스를 생성하고
* 파일의 생성, 삭제 등등의 처리를 수행하는 기능을 제공하고 있다. */
/* 파일 클래스를 이용해서 인스턴스를 생성한다.
* 대상 파일이 존재하지 않아도 인스턴스를 생성할 수 있다.
* 메모리상에 인스턴스를 생성하기만 했기 때문 */
File file = new File("src/com/greedy/section01/file/test.txt");
/*실제로 만들어보자*/
try {
/* createNewFile을 통해 파일을 생성할 수 있고 성공실패여부를
* boolean 으로 반환한다. */
boolean createSuccess = file.createNewFile();
/* 제대로 생성이 됐는지 확인 */
/* 최초 실행시 새롭게 파일이 만들어지므로 true 가 반환
* 하지만, 파일이 한번 생성 되고 난 이후는 새롭게 파일을 만들지
* 않기 때문에 false를 반환한다. */
System.out.println("createSuccess : " + createSuccess);
// true (refresh 선행 필수)
} catch (IOException e) {
e.printStackTrace();
}
/* 생성한 파일의 정보 */
System.out.println("파일의 크기 : " + file.length() + "byte");
System.out.println("파일의 경로 : " + file.getPath());
System.out.println("현재 파일의 상위 경로 : " + file.getParent());
/* 절대 경로란 최상위 루트 위치로부터의 경로를 의미한다. */
System.out.println("파일의 절대 경로 : " + file.getAbsolutePath());
// 파일의 크기 : 0byte
// 파일의 경로 : src\com\greedy\section01\file\test.txt
// 현재 파일의 상위 경로 : src\com\greedy\section01\file
// 파일의 절대 경로 : D:\dev\1_java\workspace\chap14-io\src\com\greedy\section01\file\test.txt
/* 파일 삭제 */
/* 삭제 후 성공 여부를 boolean 으로 반환한다.
* */
boolean deleteSuccess = file.delete();
/* 지워졌는지 여부 확인 */
System.out.println("deleteSuccess : " + deleteSuccess);
// deleteSuccess : true
}
}
입출력
입출력(IO)란?
Input과 Output의 약자로 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터를 주고 받는 것
장치와 입출력을 위해서는 하드웨어 장치에 직접 접근이 필요한데 다양한 매체에 존재하는 데이터들을 사용하기 위해 입출력 데이터를 처리 할 공통적인 방법으로 스트림을 이용
스트림
스트림(Stream)이란?
입출력 장치에서 데이터를 읽고 쓰기 위해서 자바에서 제공하는 클래스
모든 스트림은 단반향이며 각각의 장치마다 연결할 수 있는 스트림 존재
하나의 스트림으로 입출력을 동시에 수행할 수 없으므로 동시에 수행하려면 2개의 스트림이 필요
스트림 종류
기반 스트림이란? 실제 데이터가 전송되는 스트림, 보조스트림이란? 기반 스트림에 종속해 기능적인 역할을 하는 스트림
Application1
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Application1 {
public static void main(String[] args) {
/* 입출력과 관련된 API는 java.io 에서 제공하고 있다.
* API 문서에서 목록을 확인해보면 대부분
* InputStream/OutputStream & Reader/Writer 로 끝난다.
* 이 중 InputStream과 Reader 는 데이터를 읽어오는 입력스트림이고
* OutputStream과 Writer는 데이터를 내보내는 출력스트림이다.
*
* 또한 InputStream과 OutputStream은 데이터를 1바이트 단위로
* 입/출력을 하고 Reader와 Writer는 문자 (2 or 3바이트) 단위로
* 작업을 한다.
*
* 자바 프로그램과 연결되는 외부 데이터의 타입이 무엇인지는 클래스
* 이름을 보고 유추가 가능하다.
* InputStream/OutputStream & Reader/Writer 를 빼고
* 남은 단어가 바로 외부 데이터의 타입이다.
*
* ex : FileInputStream 은 InputStream을 제거하고 남은단어가
* File 이므로 외부 데이터는 File임을 알 수 있다.
*
* */
/* FileInputStream */
FileInputStream fin = null;
try {
/* 대상 파일이 존재하지 않는 경우 발생하는
* FileNotFoundException 에 대해서 핸들링 해야 한다. */
/* 인스턴스만 생성한 후 실행해보면 예외가 발생한 것을 볼 수 있다. */
/* 파일을 직접 생성한 뒤 다시 실행 하면 예외가 발생하지 않는데
* 스트림인스턴스가 정상적으로 생성되었기 때문이다. */
fin = new FileInputStream("src/com/greedy/section02/stream/testInputStream.txt");
/* 파일 생성 : 해당 폴더 우클릭 - new - file - 하위 폴더 선택후 -
* 파일이름.txt 입력 */
int value;
/* read()는 throw IOException이므로
* 'add catch clause to surrounding try '
* 눌러서 catch block 추가
* read() : 파일에 기록 된 값을 순차적으로 읽어오고 더이상
* 읽어올 데이타가 없는 경우 -1반환
* */
// while ((value = fin.read()) != -1) {
//
// /* 값을 정수로 읽어온다. */
// System.out.println(value);
//
// /* 문자로 출력하고 싶은 경우 형변환 하면 된다. */
// System.out.println((char) value);
// }
/* 출력 */
// 97
// a
// 98
// b
// 99
// c
// 100
// d
// 101
// e
/* 1byte 씩 읽어와야 하는 경우도 존재하긴 하지만 대부분의 경우 비효율적이다.
* byte 배열을 이용해서 한 번에 데이터를 읽어오는방법도 제공한다.
* */
/* File 클래스의 length()로 파일의 길이를 알 수 있다. */
System.out.println("파일의 길이 : " + new File("src/com/greedy/section02/stream/testInputStream.txt").length());
/* File의 길이만큼의 byte 배열을 만든다. */
int fileSize = (int) new File("src/com/greedy/section02/stream/testInputStream.txt").length();
byte[] bar = new byte[fileSize];
/* read() 메소드의 인자로 생성한 byte 배열을 넣어주면
* 파일의 내용을 읽어서 byte 배열에 기록해준다. */
fin.read(bar);
/* 해당 스트림은 파일의 끝까지 다 읽어들인 스트림이기 때문에
* 위에 1바이트씩 읽어온 while 반복문을 주석해야 정상적으로 동작한다. */
for (int i=0; i < bar.length; i++) {
System.out.print((char) bar[i]);
}
// 파일의 길이 : 5
// abcde
/* 한글 값을 입력하는 경우 한글이 깨져서 나온다.
* 한글은 한 글자에 3byte 이기 때문에 3byte 데이터를
* 1byte로 읽어오면 글자가 깨지게 된다. */
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
/* fin 인스턴스가 null 이 아닌 경우 자원 반납을 해야한다. */
if (fin != null) {
try {
/* 자원해제를 하는 경우에도 IOException을 핸들링 해야한다.
* 이미 자원이 반납 된 경우 발생하는 Exception이다.
* */
/* 자원 반납을 해야하는 경우
*
* 1. 장기간 실행 중인 프로그램에서 스트림을 닫지않는 경우
* 다양한 리소스에 누수(leak)가 발생한다.
* 2. 뒤에서 배우는 버퍼를 이용하는 경우 마지막에 flush()로
* 버퍼에 있는 데이터를 강제로 전송해야 한다.
* 만약 잔류 데이터가 남은 상황에서 추가로 스트림을 사용한다면
* 데드락(deadlock)상태가 된다.
* 판단하기 어렵고 의도하지 않은 상황에서도 이런 현상이 발생할 수 있기
* 때문에 마지막에는 flush()를 무조건 실행해주는 것이 좋다.
* close() 메소드는 자원을 반납하며 flush() 를 해주기 때문에
* close() 만 제대로 해주어도 된다.
* */
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Application2
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Application2 {
public static void main(String[] args) {
/* FileReader */
/* FileInputStream과 사용하는 방법이 거의 동일하다.
* 단, byte 단위가 아닌 character 단위로 읽어들이는 부분이 차이점이다.
* 따라서 2바이트던 3바이트던 글자단위로 읽어오기 때문에
* 한글을 정상적으로 읽어올 수 있는 방식이다. */
FileReader fr = null;
try {
/* 파일이 존재하지 않는 경우 파일을 찾지 못한다는 예외가 발생하므로
* 파일을 추가해서 정상적으로 스트림이 생성되게 한다. */
fr = new FileReader("src/com/greedy/section02/stream/testReader.txt");
// testReader.txt (지정된 파일을 찾을 수 없습니다)
/* 파일 생성 : 패키지에 대고 우클릭 - new - file - 해당패키지 재 클릭 -
* 파일명.txt 입력 */
/* 파일 내용을 읽어오는 것도 동일하다. */
/* 한글 값을 입력해도 하나의 글자 단위로 읽어온다. */
// int value;
//
// while((value = fr.read()) != -1) {
// System.out.print((char) value);
// }
// 안녕하세요 출력
/* 단 byte 배열로 읽어오면 한글은 깨지게 되므로
* char 배열로 내용을 읽어오는 기능을 제공하고 있다.
* */
/* 배열의 길이를 구하기 위한 과정 */
char[] carr = new char[(int) new File("src/com/greedy/section02/stream/testReader.txt").length()];
/* 위에서 길이를 구했다면 이번엔 내용을 읽어줘야함 */
fr.read(carr);
/* 길이만큼 반복을 돌며 읽어와 char로 형변환 한 carr의 인덱스를 출력함 */
for(int i = 0; i < carr.length; i++) {
System.out.print(carr[i]);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
/* 사용이 끝났다면 반드시 닫아주어야 함 */
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
InputStream
바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스
추상클래스이므로 객체를 생성할 수 없다.
OutputStream
바이트 기반 출력 스트림의 최상위 클래스로 추상 클래스
추상클래스이므로 객체를 생성할 수 없다.
Reader
문자 기반 입력 스트림의 최상위 클래스로 추상 클래스
Writer
문자 기반 입력 스트림의 최상위 클래스로 추상 클래스
Application3
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Application3 {
public static void main(String[] args) {
/* FileOutputStream */
/* 프로그램의 데이터를 파일로 내보내기 위한 용도의 스트림이다.
* 1바이트 단위로 데이터를 처리한다.
* */
FileOutputStream fout = null;
try {
/* 스트림으로 길을 만들고 도착지가될 파일을 괄호속에 입력한다. */
/* FileNotFoundException을 핸들링해야하는 것은 동일하지만
* 실행해도 예외가 발생하지 않고 인스턴스가 잘 생성된다.
* 파일을 수동으로 만들어줘야하는 InputStream과 달리,
* OutputStream의 경우 대상 파일이 존재하지않으면 파일을 자동으로
* 생성해준다.
*
* 단, 만약 지정할 경로의 스펠링을 틀린다면()오류가 발생한다.
* 경로가 정확한 상태에서 파일만 없는 것은 자동으로 만들어 주는 것이다.
* FileNotFoundException이 핸들링하는 부분은 경로
* => (지정된 경로를 찾을 수 없습니다.)
*
* */
/* 두번째 인자로 true를 전달하면 이어쓰기가 된다.
* 전달하지 않으면 (false) 덮어쓰기가 된다.
* */
fout = new FileOutputStream("src/com/greedy/section02/stream/testOutputStream.txt");
/* 이어쓰기 해보자 */
fout = new FileOutputStream("src/com/greedy/section02/stream/testOutputStream.txt" /*, true*/);
/* write() 메소드는 IOException을 핸들링 해야한다. */
fout.write(97);
// IOException 이 발생할 가능성이 있기에 catch 블록 작성
/* byte 배열을 이용해서 한 번에 기록할 수도 있다.
* 10 : 개행문자 (엔터)
* */
/* byte 배열을 이용해서 한 번에 기록할 수도 있다. */
byte[] bar = new byte[] {98, 99, 100, 101, 102, 10, 103, 104, 105};
fout.write(bar);
/* bar의 1번 인덱스부터 3의 길이만큼 파일에 내보내기 */
fout.write(bar, 1, 3); // 1번인덱스부터 3의 길이만큼
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fout != null) {
try {
/* IOException를 핸들링 해 주어야 한다. */
fout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Application4
import java.io.FileWriter;
import java.io.IOException;
public class Application4 {
public static void main(String[] args) {
/* FileWriter */
/* 프로그램의 데이터를 파일로 내보내기 위한 용도의 스트림이다.
* 1글자 단위로 데이터를 처리한다.
* */
FileWriter fw = null;
try {
/* IOException을 핸들링 해주기 위해 try-catch 구문실행 */
/* 단, 경로를 잘못 입력하면 FileNotFoundException
* 에러가 발생한다. Writer의 경우 대상파일이 존재하지 않으면
* 파일을 자동으로 생성해준다.
* */
/* 두번째 인자로 true 전달시 내용을 이어쓰기가 된다.
* 인자를 전달하지 않으면 (또는 false) 덮어쓰기가 된다. */
fw = new FileWriter("src/com/greedy/section02/stream/testWriter.txt", true);
/* write() 메소드도 IOException을 핸들링 해야 한다. */
/* true 가 두번째 인자로 전해져있기 때문에 새로고침을 누를 수록
* a가 이어써서 늘어난다. */
fw.write(97);
/* 문자 단위 출력도 내부 버퍼를 사용하므로 쌓여있는 데이터를
* flush()로 내보내 줘야 최종적으로 파일에 출력되는
* 모습을 확인 할 수 있다. 또는 close()로 자원을 반납하면
* 반납 전에 flush()가 호출되므로 파일에 출력되는 모습을
* 확인할 수 있다.
* */
// fw.flush();
/* 문자 기반 스트림은 직접 char 자료형으로 내보내기도 가능하다. */
fw.write('A');
/* 혹은 char 배열도 가능하며 */
fw.write(new char[] {'a', 'p', 'p', 'l', 'e'});
/* 문자열도 가능하다. */
fw.write("우리나라 대한민국");
// 이어쓰는 상황이므로 새로고침시 같은 내용이 한번 더 출력된다.
// 출력 : aaaaAapple우리나라 대한민국
} catch (IOException e) {
e.printStackTrace();
} finally {
/* 1 바이트가 아닌 데이터를 다룰 때는 내부적으로
* 데이터를 쌓아서 다 차면 내보내기 때문에 의도한다면
* 자체적으로 닫아서 내보내기 해주어야 한다.
* 그게 flush와 close이다. */
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileInputStream
파일로부터 바이트 단위로 읽을 때 사용하며, 그림, 오디오,비디오,텍스트 파일 등 모든 종류의 파일 읽기 가능
InputStream의 하위 클래스로 InputStream의 메소드를 그대로 사용 가능
FileInputStream 인스턴스 생성
FileInputStream객체가 생성될 때 파일과 직접 연결
만약 파일이 존재하지 않으면 FileNotFoundException이 발생하므로 예외처리 필수
FileInputStream fis = new FileInputStream("C:/data/test.txt");
FileReader
텍스트 파일로부터 Byte 단위가 아닌 character 단위로 읽어 들임 (2바이트이던 3바이트이던 글자 단위로 읽어 오기 때문에 한글을 정상적으로 읽어올 수 있음)
FileInputStream과 사용하는 방법이 거의 동일
Reader의 하위 클래스로 Reader의 메소드를 그대로 사용 가능
FileReader 인스턴스 생성
1. FileReader객체가 생성될 때 파일과 직접 연결 됨 2. 만약 파일이 존재하지 않으면 FileNotFoundException이 발생하므로 예외처리 필수
FileReader fr = new FileReader("C:/data/test.txt");
FileReader fr = new FileReader(new File("C:/data/test.txt"));
FileOutputStream
파일로부터 바이트 단위로 출력할 때 사용하며, 그림, 오디오,비디오,텍스트 파일 등 모든 종류의 파일 읽기 가능
OutputStream의 하위 클래스로 OutputStream의 메소드를 그대로 사용 가능
FileOutputStream 인스턴스 생성
FileOutputStream객체가 생성될 때 파일과 직접 연결 만약 파일이 존재하지 않으면 자동으로 생성하지만 이미 파일이 존재하는 경우 파일을 덮어쓰는 단점이 있음
FileIOutputStream fis = new FileOutputStream("C:/data/test.txt");
만일 기존 파일에 이어서 계속 추가 작성하고 싶다면 아래와 같이 작성
FileIOutputStream fis = new FileOutputStream("C:/data/test.txt", true);
FileWriter
텍스트 파일에 Byte 단위가 아닌 character 단위로 출력 (FileReader와 마찬가지로 글자 단위로 데이터를 처리)
FileOutputStream과 사용하는 방법이 거의 동일
Writer의 하위 클래스로 Writer의 메소드를 그대로 사용 가능
FileWriter 인스턴스 생성
FileWriter객체가 생성될 때 파일과 직접 연결 됨 만약 파일이 존재하지 않으면 자동으로 생성하지만 이미 파일이 존재하는 경우 파일을 덮어쓰는 단점이 있음
FileWriter fw = new FileWriter("C:/data/test.txt");
만일 기존 파일에 이어서 계속 추가 작성하고 싶다면 아래와 같이 작성
FileWriter fw = new FileWriter("C:/data/test.txt", true);
보조 스트림
스트림의 기능을 향상시키거나 새로운 기능을 추가하기 위해서 사용
보조 스트림은 실제 데이터를 주고 받는 스트림이 아니기 때문에 입출력 처리가 불가능
기반 스트림을 먼저 생성한 후 이를 이용하여 보조 스트림을 생성
보조 스트림의 종류
입출력 성능(BufferedInputStream/BufferedOutputStream)
기본 데이터 타입 출력(DataInputStream, DataOutputStream)
객체 입출력(ObjectInputStream/ObjectOutputStream) 등의 기능을 제공하는 보조스트림이 있다
EX)
FileInputStream fis = new FileInputStream("sample.txt"); //기반 스트림 생성
BufferedInputStream bis = new BufferedInputStream(fis); //보조스트림 생성
bis.read(); //보조스트림으로부터 데이터 읽어옴
구현 클래스 : HashMap, HashTable(HashMap의 이전버전), LinkedHashMap, Properties, TreeMap
Map 계열 주요 메소드
Application1
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Application1 {
public static void main(String[] args) {
/* Map 인터페이스의 특징 */
/* Collection 인터페이스와는 다른 저장 방식을 가진다.
* 키(key)와 값(value)을 하나의 쌍으로 저장하는 방식을 사용한다.
*
* 키(key)란?
* 값(value)을 찾기 위한 이름 역할을 하는 객체를 의미한다.
*
* 1. 요소의 저장 순서를 유지하지 않는다.
* 2. 키는 중복을 허용하지 않지만, 키가 다르면 중복 되는 값은 저장 가능하다.
*
*
* HashMap, HashTable, TreeMap 등의 대표적인 클래스가 있다.
* HashMap이 가장 많이 사용 되며 HashTable은 jdk 1.0부터 제공되며
* HashMap과 동일하게 동작 된다. 하위 호환을 위해 남겨놓았기 때문에
* 가급적이면 HashMap을 사용하는 것이 좋다.
*
* */
/* HashMap 인스턴스 생성 */
HashMap hmap = new HashMap();
// Map hmap2 = new HashMap();;
/* 키와 값 쌍으로 저장한다.
* 키와 값 둘다 반드시 객체여야 한다.
* */
hmap.put("one", new Date());
hmap.put(12, "red apple");
hmap.put(33, 123);
/* 기본자료형은 오토박싱 처리된다. : int => Integer */
/* 저장 순서 유지하지 않음 */
System.out.println("hmap : " + hmap);
// hmap : {33=123, one=Tue Jan 11 09:25:59 KST 2022, 12=red apple}
/* 키는 중복 저장 되지 않음 (set방식) : 최근 값으로 덮어쓰기 */
hmap.put(12, "yellow banana");
System.out.println("hmap : " + hmap);
// hmap : {33=123, one=Tue Jan 11 09:25:59 KST 2022, 12=yellow banana}
/* 키가 다르면 값 객체 저장은 중복으로 가능함 */
hmap.put(11, "yellow banana");
System.out.println("hmap : " + hmap);
// hmap : {33=123, one=Tue Jan 11 09:26:24 KST 2022, 11=yellow banana, 12=yellow banana}
/* 값 객체의 내용을 가져올 때 */
System.out.println("키 11에 대한 객체 : " + hmap.get(11));
// 키값을 전달시 value값을 리턴한다.
// 키 11에 대한 객체 : yellow banana
/* 키 값을 가지고 삭제를 처리할 때 */
hmap.remove(11);
System.out.println("hmap : " + hmap);
// hmap : {33=123, one=Tue Jan 11 09:28:43 KST 2022, 12=yellow banana}
/* 저장 된 객체 수를 확인할 때 */
System.out.println("hmap에 저장된 객체 수 : " + hmap.size());
// hmap에 저장된 객체 수 : 3
/* 노란줄이 생기는 이유? 제네릭을 사용하지 않았기 때문 */
/* 제네릭 설정한 HashMap 인스턴스 생성 */
HashMap<String, String> hmap2 = new HashMap<>();
hmap2.put("one", "java");
hmap2.put("two", "oracle");
hmap2.put("three", "jdbc");
hmap2.put("four", "html");
hmap2.put("five", "css");
/* 방법 1. ketset()을 이용해서 키만 따로 set 으로 만들고,
* iterator()로 키에 대한 목록을 만든다.
* */
// Set<String> keys = hmap2.keySet();
// Iterator<String> keyIter = key.iterator();
// ㄴ 한줄로 만들기
Iterator<String> keyIter = hmap2.keySet().iterator();
while(keyIter.hasNext()) {
String key = keyIter.next();
String value = hmap2.get(key);
System.out.println(key + " = " + value);
// four = html
// one = java
// two = oracle
// three = jdbc
// five = css
}
/* 방법2. 저장 된 value 객체들만 values()로 Collection으로 만든다. */
Collection<String> values = hmap2.values();
/* 2-1. iterator()로 목록 만들어서 처리 */
Iterator<String> valueIter = values.iterator();
while(valueIter.hasNext()) {
System.out.println(valueIter.next());
}
/* 2.2 배열로 만들어서 처리 */
Object[] valueArr = values.toArray();
for (int i = 0; i < valueArr.length; i++) {
System.out.println(i + " : " + valueArr[i]);
// 0 : html
// 1 : java
// 2 : oracle
// 3 : jdbc
// 4 : css
}
/* 방법3. Map의 내부 클래스인 EntrySet을 이용 : entrySet */
Set<Map.Entry<String, String>> set = hmap2.entrySet();
Iterator<Map.Entry<String, String>> entryIter = set.iterator();
while(entryIter.hasNext()) {
Map.Entry<String, String> entry = entryIter.next();
System.out.println(entry.getKey() + " : " + entry.getValue());
// four : html
// one : java
// two : oracle
// three : jdbc
// five : css
}
}
}
Properties
키와 값을 String 타입으로 제한한 Map 컬렉션 - 문자열로만 받음
주로 Properties는 프로퍼티(*.properties)파일을 읽어 들일 때 주로 사용
프로퍼티(*.properties)파일 - 옵션정보, 데이터베이스 연결정보, 국제화(다국어)정보를 기록하여 텍스트 파일로 활용 - 애플리케이션에서 주로 변경이 잦은 문자열을 저장하여 관리하기 때문에 유지보수를 편리하게 만들어 줌 - 키와 값이 ‘=‘기호로 연결되어 있는 텍스트 파일로 ISO 8859-1 문자셋으로 저장되고, 한글은 유니코드(Unicode)로 변환되어 저장
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class Application2 {
public static void main(String[] args) {
/* Properties */
/* 설정 파일의 값을 읽어서 어플리케이션에 적용할 때 사용한다. */
Properties prop = new Properties();
prop.setProperty("driver", "oracle.jdbc.driver.OracleDriver");
prop.setProperty("url", "jsbc:oracle:this:@127.0.0.1:1521:xe");
prop.setProperty("user", "student");
prop.setProperty("password", "student");
System.out.println(prop);
// {password=student, driver=oracle.jdbc.driver.OracleDriver, user=student, url=jsbc:oracle:this:@127.0.0.1:1521:xe}
/* prop -> 문서화 하겠다 */
try {
prop.store(new FileOutputStream("driver.dat"), "jdbc driver");
prop.store(new FileWriter("driver.txt"), "jdbc driver");
prop.storeToXML(new FileOutputStream("driver.xml"), "jdbc driver");
} catch (IOException e) {
e.printStackTrace();
}
/* 파일로부터 읽어와서 Properties에 기록한다. */
Properties prop2 = new Properties();
// prop2.load(new FileInputStream("driver.dat"));
try {
// prop2.load(new FileInputStream("driver.dat"));
// prop2.load(new FileReader("driver.txt"));
prop2.loadFromXML(new FileInputStream("driver.xml"));
/* Properties의 모든 키 값 목록을 대상 스트림에 내보내기 한다. */
prop2.list(System.out);
System.out.println(prop2.getProperty("driver"));
System.out.println(prop2.getProperty("url"));
System.out.println(prop2.getProperty("user"));
System.out.println(prop2.getProperty("password"));
// oracle.jdbc.driver.OracleDriver
// jsbc:oracle:this:@127.0.0.1:1521:xe
// student
// student
} catch (IOException e) {
e.printStackTrace();
}
}
}
예외처리
오류와 에러
시스템 상에서 프로그램에 심각한 문제가 발행해서 실행중인 프로그램이 영향을 받는 것은 오류와 예외로 구분할 수 있음
오류(Error)
시스템 상에서(물리적) 프로그램에 심각한 문제를 발생하여 실행중인 프로그램이 종료되는 것
예외(Exception)
오류와 마찬가지로 비정상적으로 종료시키지만 미리 예측하고 처리할 수 있는 미약한 오류
소프트웨어 개발자로서의 관심영역은 예외쪽에 있다.
예외 클래스 계층 구조
Exception과 Error 클래스 모두 Throwable클래스의 자손이다.
예외 클래스들의 최상위 클래스는 Exception 클래스이다.
예외처리를 해야하는 Checked Exception과 해주지 않아도 되는 Unchecked Exception으로 나뉜다.
IOException은 반드시 체크해야하는 예외처리의 종류이다. 즉, Checked Exception이다.
예외처리
예외는 예외처리를 통해 코드의 흐름을 컨트롤 가능
예외 처리 방법
1. thorws로 위임 (Exception 처리를 호출한 메소드에게 위임)
메소드 선언 시 throws ExceptionName문을 추가하여 호출한 상위 메소드에게 처리를 위임
2. try-catch로 처리 (Exception이 발생한 곳에서 직접 처리)
- try : exception 발생할 가능성이 있는 코드를 안에 기술 - catch : try 구문에서 exception 발생 시 해당하는 exceptio에 대한 처리 기술 여러 개의 exception처리가 가능하나 exception간의 상속 관계 고려 - finally : exception 발생 여부와 관계없이 꼭 처리해야 하는 로직 기술 중간에 return문을 만나도 finally구문은 실행되지만 System.exit();를 만나면 무조건 프로그램 종료 주로 java.io나 java.sql 패키지의 메소드 처리 시 이용
Throws로 예외 던지기
위임은 완전한 해결책은 아니다.
try~cathch로 예외 처리
Application1
public class Application1 {
public static void main(String[] args) throws Exception {
/* 예외처리
*
* 오류(Error)
* 시스템 상에서 프로그램에 심각한 문제를 발생하여 실행중인 프로그램이
* 종료되는 것을 말한다.
* 이러한 오류는 개발자가 미리 예측하여 처리하는 것이 불가능하며,
* 오류에 대한 처리는 할 수 없다.
*
* 예외(Exceptrion)
* 오류와 마찬가지로 실행 중인 프로그램을 비정상적으로 종료시키지만
* 발생할 수 있는 상황을 미리 예측하고 처리할 수 있는 미약한 오류를 말한다.
* 개발자는 이러한 예외에 대해 예외 처리를 통해 예외상황을 적절히 처리하여
* 코드의 흐름을 컨트롤 할 수 있다.
*
*
* */
/* 예외처리 방법
*
* 1. throws로 위임 : 나를 호출한 상위클래스로 던짐
* 2. try-catch로 처리 : 그자리에서 바로 처리
* */
/* 1. throws로 위임 */
ExceptionTest et = new ExceptionTest();
// et.checkEnoughMoney(10000, 50000);
// 빨간줄의 원인? : 호출시 반드시 예외처리 해주어야 하기 때문
// 1. throw, 2. try-catch
/* 상품가격 10000원, 가진돈 50000원 -> 정상수행 */
et.checkEnoughMoney(10000, 50000);
// throws 선택시 메소드 위로 오류가 위임된다, 동시에 정상동작확인
// 가지고 계신 돈은 50000원 입니다.
// 상품을 구입하기 위한 금액이 충분합니다.
// 즐거운 쇼핑 하세요~
/* 상품가격 50000원, 가진돈 10000원 -> 정상수행 */
et.checkEnoughMoney(50000, 10000);
/* 에러 발생 구문 이하 구문은 동작하지 않고 되돌아온다.
* 메인 메소드 또한 예외를 처리하지 않고 위임했다.
* 따라서 프로그램은 비정상적으로 종료되고 아래 구문은 출력되지않음 */
System.out.println("프로그램을 종료합니다.");
/* 예외 발생시 두번째 구문은 발동되지않음, 게다가 위임한 코드조차
* 예외에 대처하고 있지않으므로 에러 구문을 출력시킨다. */
}
}
Application2
public class Application2 {
public static void main(String[] args) {
/* 2. try-catch 를 이용한 방법 (예외 발생하지 않음) */
ExceptionTest et = new ExceptionTest();
/* 상품 가격 10000원, 가진 돈 50000원 */
// et.checkEnoughMoney(10000, 50000);
// 호출시 무조건 핸들링하라고 하는 오류가 발생한다.
try {
/* 예외 발생 가능성이있는 메소드는 try블럭안에서 호출한다. */
/* 상품 가격 10000원, 가진 돈 50000원 */
//et.checkEnoughMoney(10000, 50000);
/* 상품 가격 50000원, 가진 돈 10000원 */
et.checkEnoughMoney(50000, 10000);
System.out.println("=======상품 구입 가능=======");
} catch (Exception e) {
/* Exception이 발생했을경우 catch 블럭쪽 코드가 실행된다.
* 예외발생없이 정상출력될 경우 catch 블럭쪽 코드는 실행되지않는다.
* 즉 위의 메소드 호출시 예외가 발생한 경우 catch블럭코드를 실행한다.
* 이때 예외 발생한 위치의 하단 코드는 동작하지않는다. */
System.out.println("=======상품 구입 불가=======");
}
/* 프로그램 종료 확인 */
System.out.println("프로그램을 종료합니다.");
/* 예외 발생하지않을시 콘솔출력 - try */
// 가지고 계신 돈은 50000원 입니다.
// 상품을 구입하기 위한 금액이 충분합니다.
// 즐거운 쇼핑 하세요~
// =======상품 구입 가능=======
// 프로그램을 종료합니다.
/* 예외 발생시 콘솔출력 - catch */
// 가지고 계신 돈은 10000원 입니다.
// =======상품 구입 불가=======
// 프로그램을 종료합니다.
}
}
ExceptionTest
public class ExceptionTest {
/* 예외를 발생시키는 메소드를 하나 작성한다. */
public void checkEnoughMoney(int price, int money) throws Exception {
System.out.println("가지고 계신 돈은 " + money + "원 입니다.");
if (money >= price) {
System.out.println("상품을 구입하기 위한 금액이 충분합니다.");
} else {
/* 강제로 예외 발생
* 예외가 발생한다는것 = 어딘가에 해당 객체가 생성된다는 것
* 예외를 발생시킨 뒤 헤드에 throws 구문을 추가한다.
* 예외를 발생시킨 쪽에서는 throws로 예외에 대한 책임을
* 위임해서 해당 예외에 대한 처리를 강제화 시킨다.
* */
throw new Exception(); // throw = 발생한다로 해석
// Unhandled exception type Exception
// add throws Exception 클릭시 상위메소드에 처리를 넘김
}
/* 예외가 발생하지 않는 경우에만 실행한다. */
System.out.println("즐거운 쇼핑 하세요~");
}
}
사용자 정의 예외
Exception 클래스를 상속받아 예외 클래스를 작성하는 것
RuntimeException 후손 클래스
예외처리가 강제화 되어있지않다.
후손 클래스
설명
ArithmeticException
0으로 나누는 경우 발생 if문으로 나누는 수가 0인지 검사
NullPointerException
Null인 참조 변수로 객체 멤버 시도 시 발생 객체 사용 전에 참조 변수가 null인지 확인
NegativeArraySizeException
배열 크기를 음수로 지정한 경우 발생 배열 크기를 0보다 크게 지정
ArrayIndexOutOfBoundsException
배열의 index범위를 넘어서 참조하는 경우 배열명.length를 사용하여 배열의 범위 확인
Class CastException
Cast연산자 사용 시 타입 오류 instanceof연산자로 객체 타입 확인 후 cast연산
예외처리 방법
finally로 예외 처리
예외 처리 구문과 상관 없이 반드시 수행해야 하는 경우 작성 (보통 사용한 자원을 반납할 목적)
NegativeException extends Exception
public class NegativeException extends Exception {
/* 사용자 정의 예외를 만드려면 Exception을 상속받아야 함 */
/* 기본 생성자 */
public NegativeException() {}
/* 매개변수 생성자 */
public NegativeException(String message) {
super(message);
}
}
NotEnoughMoneyException extends Exception
public class NotEnoughMoneyException extends Exception {
/* 사용자 정의 예외 클래스를 만들기 위해서는 Exception 클래스를
* 상속 받으면 된다. Exception 클래스는 Throwable 클래스를 상속받아
* 구현되어 있다.
*
* Throwable은 Error 와 Exception 두 가지를 추상화 해서 만들었다.
* 예외처리는 Exception을 가장 최상휘 클래스로 여긴다.
* */
/* 기본생성자 */
public NotEnoughMoneyException () {}
/* 문자열을 부모 생성자 쪽으로 전달하며 초기화 하는 생성자 */
public NotEnoughMoneyException (String message) {
/* 예외 인스턴스 생성 시점에 예외 메세지를 부모 생성자 쪽으로
* 전잘해서 인스턴스를 생성한다. */
super(message);
}
/* money와 price가 각각 음수로 입력되는 경우 일반적인 상식에서 벗어나는
* 프로그램이 된다. 만약 각각 음수로 입력 되는 경우 발생시킬 예외를
* NegativeException 으로 정의한다.
*
* 그리고 이를 상속받는 PriceNegativeException과
* MoneyNegativeException을 정의한다.
* */
}
MoneyNegativeException extends NegativeException
public class MoneyNegativeException extends NegativeException{
/* 앞서 정의한 사용자 정의 예외처리 클래스를 상속받게 한다.
* Throwable - Exception - NegativeException - MoneyNegativeException
* 의 상속관계가 된다. */
/* 기본 생성자 */
public MoneyNegativeException () {}
/* 매개변수 생성자 */
public MoneyNegativeException (String message) {
super(message);
}
}
PriceNegativeException extends NegativeException
public class PriceNegativeException extends NegativeException {
/* 앞서 정의한 사용자 정의 예외처리 클래스를 상속받게 한다.
* Throwable - Exception - NegativeException
* - MoneyNegativeException & PriceNegativeException
* 의 상속관계가 된다. */
/* 기본 생성자 */
public PriceNegativeException () {}
/* 매개변수 생성자 */
public PriceNegativeException (String message) {
super(message);
}
}
Application1
import com.greedy.section02.userexception.exception.MoneyNegativeException;
import com.greedy.section02.userexception.exception.NotEnoughMoneyException;
import com.greedy.section02.userexception.exception.PriceNegativeException;
public class Application1 {
public static void main(String[] args) /*throws PriceNegativeException, MoneyNegativeException, NotEnoughMoneyException*/ {
/* 사전에 정의 되어 있는 Exception의 종류는 굉장히 많이 있다.
* 하지만 RuntimeException의 후손 대부분은 예외처리를 강제화 하지 않는다.
* 간단한 조건문 등으로 처리가 가능하기 때문에 따로 강제화 하지않는다. */
/* 사전에 정의 된 예외 클래스 외에 개발자가 우너하는 명칭의
* 예외 클래스를 작성하는 것이 가능하다.
* extends Exception 으로 예외처리 클래스를 상속받아 더 구체적인
* 예외 이름을 정의하는 것이다.
*
* */
ExceptionTest et = new ExceptionTest();
/* 상품가격보다 가진 돈이 적은 경우 */
//et.checkEnoughMoney(50000, 30000);
/* 실행시 오류가 발생한다. 실행한 오류의 내용은 다음과 같다.
* 직접 설정한 오류가 출력됨을 확인할 수 있다.
*
* com.greedy.section02.userexception.
* exception.NotEnoughMoneyException:
* 가진 돈 보다 상품 가격이 더 비쌉니다.*/
/* try-catch 블럭으로 확인하기 */
/* 정상적인 프로그램의 흐름을 만들기 위해서는 try-catch 블럭을 작성하는게 좋다. */
try {
/* 상품 가격보다 가진 돈이 적을 경우 (에러가 발생할 가능성있는 메소드를 넣는다.)*/
/* 실행해보면 예외 종류와 에러 메세지가 출력된다. */
// et.checkEnoughMoney(50000, 30000);
// 출력 : NotEnoughMoneyException: 가진 돈 보다 상품 가격이 더 비쌉니다.
/* 상품가격을 음수로 입력한 경우 */
// et.checkEnoughMoney(-50000, 50000);
// 출력 : PriceNegativeException: 상품가격은 음수일 수 없습니다.
/* 가진 돈을 음수로 입력한 경우 */
// et.checkEnoughMoney(50000, -50000);
// 출력 : MoneyNegativeException: 가지고 있는 돈은 음수일 수 없습니다.
/* 정상적으로 구매가 가능한 돈을 가진 경우 */
/* 위의 코드들을 주석하지 않으면 이 코드는 동작하지 않는다.
* catch 블록으로 가는 것이다. */
et.checkEnoughMoney(30000, 50000);
// 출력 : 가진 돈이 충분합니다. 즐거운 쇼핑하세요~
} catch (Exception e) { // 다형성을 이용해 상위개념 Exception으로 받음
e.printStackTrace();
}
/* exception이 발생한 경우 catch 블록에 잡히기 때문에 if문 하위의 출력문은 출력되지 않는다. */
}
}
ExceptionTest
import com.greedy.section02.userexception.exception.MoneyNegativeException;
import com.greedy.section02.userexception.exception.NotEnoughMoneyException;
import com.greedy.section02.userexception.exception.PriceNegativeException;
public class ExceptionTest {
public void checkEnoughMoney(int price, int money)
throws PriceNegativeException, MoneyNegativeException, NotEnoughMoneyException{
// throws Exception {
/* 3개의 예외 클래스는 모두 Exception 클래스의 후손이다.
* (throws의 공통된 조상은 Exception이다.)
* 그러므로 Exception만으로도 처리할 수 있다. */
/* 아까는 Exception으로 예외를 발생시켰지만, 그건 그냥 '예외'
* 라는 의미이다. 예외 클래스의 이름만으로 어떠한 예외가 발생했는지
* 알 수 있도록 하기 위해서는 명명이 중요하다.
* 사용자 정의 예외 클래스를 추가할 것이다.
* */
/* 먼저 상품 가격이 음수인지 확인하고, 음수인 경우 예외를 발생시킨다. */
if (price < 0) {
throw new PriceNegativeException("상품가격은 음수일 수 없습니다.");
}
/* 가진 돈도 음수인지 확인하고, 음수인 경우 예외를 발생시킨다. */
if(money < 0) {
throw new MoneyNegativeException("가지고 있는 돈은 음수일 수 없습니다.");
/* PriceNegativeException, MoneyNegativeException
* 둘 중 하나를 발생시킬 수 있는 메소드가 됨 */
}
/* 위의 두 값이 정상 입력 되었더라도 상품 가격이 가진 돈보다 큰 경우 예외 발생 */
if (money < price) {
throw new NotEnoughMoneyException ("가진 돈 보다 상품 가격이 더 비쌉니다.");
}
/* 모든 조건을 만족하는 경우 정상적으로 물건 구입 가능 */
System.out.println("가진 돈이 충분합니다. 즐거운 쇼핑하세요~");
}
}
Application2
import com.greedy.section02.userexception.exception.MoneyNegativeException;
import com.greedy.section02.userexception.exception.NotEnoughMoneyException;
import com.greedy.section02.userexception.exception.PriceNegativeException;
public class Application2 {
public static void main(String[] args) {
ExceptionTest et = new ExceptionTest();
/* 이 메소드를 입력시 컴파일 에러가 뜨고 해결책을 누르면 아래와같이 뜬다. */
// et.checkEnoughMoney(20000, 30000);
try {
/* 예외 발생 가능성이있는 메소드 호출 */
et.checkEnoughMoney(30000, 50000);
/* 예외 발생별로 catch 블럭을 따로 작성해서 처리할 수 있다. */
} catch (PriceNegativeException e) {
/* 예외 인스턴스 생성 시 전달한 메세지를 getMessage()로 가져올 수 있다. */
System.out.println("PriceNegativeException 발생!");
System.out.println(e.getMessage());
} catch (MoneyNegativeException e) {
System.out.println("MoneyNegativeException 발생!");
System.out.println(e.getMessage());
} catch (NotEnoughMoneyException e) {
System.out.println("NotEnoughMoneyException 발생!");
System.out.println(e.getMessage());
} finally {
/* 예외 발생 여부와 상관없이 실행할 내용 */
/* finally 블록은 무조건 동작한다. */
System.out.println("finally 블럭의 내용이 동작함");
}
/* 프로그램을 종료하는지 확인 */
System.out.println("프로그램을 종료합니다.");
/* 다음과 같이 예외사항이 처리됨을 확인할 수 있다. */
// et.checkEnoughMoney(-20000, 30000); 인 경우
// PriceNegativeException 발생!
// 상품가격은 음수일 수 없습니다.
// et.checkEnoughMoney(20000, -30000);
// MoneyNegativeException 발생!
// 가지고 있는 돈은 음수일 수 없습니다.
// et.checkEnoughMoney(50000, 30000);
// NotEnoughMoneyException 발생!
// 가진 돈 보다 상품 가격이 더 비쌉니다.
// et.checkEnoughMoney(30000, 50000);
// 가진 돈이 충분합니다. 즐거운 쇼핑하세요~
/* finally 블럭 삽입시 다음과 같이 실행된다. */
// et.checkEnoughMoney(-30000, 50000);
// PriceNegativeException 발생!
// 상품가격은 음수일 수 없습니다.
// finally 블럭의 내용이 동작함
// 프로그램을 종료합니다.
// et.checkEnoughMoney(30000, 50000);
// 가진 돈이 충분합니다. 즐거운 쇼핑하세요~
// finally 블럭의 내용이 동작함
// 프로그램을 종료합니다.
}
}
Application3
import com.greedy.section02.userexception.exception.MoneyNegativeException;
import com.greedy.section02.userexception.exception.NotEnoughMoneyException;
import com.greedy.section02.userexception.exception.PriceNegativeException;
public class Application3 {
public static void main(String[] args) {
/* multi-catch */
/* jdk 1.7에서 추가된 구문으로
* 동일한 레벨의 다른 타입의 예외를 하나의 catch 블럭으로 다룰 수 있다.
* */
ExceptionTest et = new ExceptionTest();
/* 예외 발생 가능성이 있는 메소드 호출 */
/* et.checkEnoughMoney(20000, 10000); 입력 후 multi-catch 클릭 */
try {
et.checkEnoughMoney(20000, 10000);
}
/* catch 블럭을 나열하는 경우 각 Exception의 상속관계를 고려해서 나열한다.
* Exception은 모든 타입의 Exception의 부모이기 때문에 먼저 동작하면 아래 catch 블록은
* 동작하지 않는다.
* */
catch (PriceNegativeException | MoneyNegativeException | NotEnoughMoneyException e) {
/* 예외 클래스명, 예외 발생 위치, 예외 메세지 등을
* stack 호출 역순으로 빨간색 글씨를 이용해서
* 로그 형태로 출력해주는 기능 */
e.printStackTrace();
System.out.println(e.getClass() + "발생!");
System.out.println(e.getMessage());
} catch(Exception e) { // 모든 타입의 Exception 의 부모
System.out.println("모든 종류의 Exception 발생!");
/*PriceNegativeException , MoneyNegativeException
* , NotEnoughMoneyException 외에서 발생한 예외를 처리하는 catch 블록이다.
* 이상태에서 문제가 없지만 만약 첫번째 catch 블럭 위로 올라가게 된다면
* 컴파일 에러가 발생한다.
*
* 순서가 바뀔경우 다형성에 의해서 첫번째 캐치구문만 검사하고
* 아래 블럭은 검사하지 않는다. -> 컴파일 에러
* 즉, catch 블럭은 여러개 나열될 수 있지만 가장 맨 위에 와야하는 것은
* 자식 catch 블럭이다.
*
* */
}
finally {
System.out.println("finally 블럭의 내용이 동작함");
}
System.out.println("프로그램을 종료합니다.");
/* 다음과같이 에러메세지가 콘솔창에 출력된다. */
// NotEnoughMoneyException: 가진 돈 보다 상품 가격이 더 비쌉니다.
// class com.greedy.section02.userexception.exception.NotEnoughMoneyException발생!
// 가진 돈 보다 상품 가격이 더 비쌉니다.
// finally 블럭의 내용이 동작함
// 프로그램을 종료합니다.
}
}
인접 참조를 링크해서 체인처럼 관리, 앞뒤로 서로의 링크를 참조한다. (특정 인덱스에서 인스턴스를 제거하거나 추가하게 되면 바로 앞/뒤 링크만 변경하면 되기 때문에 인스터스 삭제와 삽입이 빈번하게 일어나는 곳에서는 ArrayList보다 성능이 뛰어남)
Application1
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
public class Application1 {
public static void main(String[] args) {
/* List 인터페이스를 구현한 모든 클래스는 요소의 저장 순서가 유지되며,
* 중복 저장을 허용한다.
* ArrayList, LinkedList, Vector, Stack이 있다.
*
* */
/* ArrayList
* 가장 많이 사용되는 컬렉션 클래스이다.
* JDK 1.2부터 제공된다.
* 내부적으로 배열을 이용하여 요소를 관리하며, 인덱스를 이용해
* 배열요소게 빠르게 접근할 수 있다.
*
* ArrayList는 배열의 단점을 보완하기 위해 만들어졌다.
* 배열은 크기를 변결할 수 없고, 요소의 추가, 삭제, 정렬들이 복잡
* 하다는 단점을 가지고 있다.
*
* ArrayList는 크기변경 (새로운 더 큰 배열을 만들고 데이터 옮기기)
* , 요소의 추가, 삭제, 정렬기능등을 미리 메소드로 구현해서 제공하고있다.
* 자동적으로 수행되는 것이지 속도가 빨라지는 것은 아니다.
*
* */
/* ArrayList는 인스턴스를 생성하게 되면 내부적으로 10칸까지
* 배열을 생성해서 관리한다.
* */
ArrayList alist = new ArrayList();
/* 다형성을 적용하여 상위 레퍼런스로 ArrayList 객체를 만들수도 있다.
* ArrayList는 List인터페이스를 상속받기 때문이다.
* List인터페이스 하위의 다양한 구현체들로 타입변경이 가능하기 때문에
* 레퍼런스 타입은 List로 해 두는 것이 더 유연한 코드를 작성하는 것이다.
* */
List list = new ArrayList();
/* 더 상위 타입인 Collection 타입을 사용할 수도 있다.
* Collection 타입은 Array 와 List의 상위타입이기 때문
* */
Collection clist = new ArrayList();
/* ArrayList는 저장 순번이 유지되며 index(순번)이 적용된다.
* ArrayList는 Object 클래스의 하위타입 인스턴스를 모두
* 저장할 수 있다. */
alist.add("apple"); //String
alist.add(123); //int
// 객체만 저장가능, autoBoxing 처리해서 기본자료형들을 자동으로 변환 해준다.
alist.add(45.67); //double
alist.add(new Date());
/* toString 메소드가 오버라이딩 되어있다.
* 출력 해 보면 저장 순서를 유지하고 있다.
* */
System.out.println("alist : " + alist);
// alist : [apple, 123, 45.67, Mon Jan 10 10:23:25 KST 2022]
/* size()메소드는 배열의 크기가 아닌 요소의 갯수를 반환한다.
* 내부적으로 관리되는 배열의 사이즈는 외부에서 알 필요가 없기
* 때문에 기능을 제공하지 않는다. */
System.out.println("alist의 size : " + alist.size());
// alist의 size : 4
/* 내부 배열에 인덱스가 지정되어 있기 때문에 for문으로 접근 가능하다. */
for (int i = 0; i < alist.size(); i++) {
/* 인덱스에 해당하는 값을 가져올 때는 get(인덱스전달값)
* 메소드를 사용한다. */
System.out.println(i + " : " + alist.get(i));
}
// 0 : apple
// 1 : 123
// 2 : 45.67
// 3 : Mon Jan 10 10:25:35 KST 2022
/* ArrayList는 데이터의 중복 저장을 허용한다.
* 배열과 같이 인덱스로 요소들을 관리하기 때문에 인덱스가
* 다른 위치에 동일한 값을 저장하는 것이 가능하다.
* */
alist.add("apple"); // apple 인덱스를 추가한다.
System.out.println("alist : " + alist);
// alist : [apple, 123, 45.67, Mon Jan 10 10:26:59 KST 2022, apple]
/* 원하는 인덱스 위치에 값을 추가할 수도 있다.
* 값을 중간에 추가하는 경우 인덱스 위치에 덮어쓰는 것이 아니고
* 새로운 값이 들어가는 인덱스 위치에 값을 넣고 이후 인덱스는
* 하나씩 뒤로 밀리게 된다.
* */
alist.add(1, "banana");
System.out.println("alist : " + alist);
// alist : [apple, banana, 123, 45.67, Mon Jan 10 10:28:58 KST 2022, apple]
/* 지정된 값을 삭제할 때는 remove() 메소드를 사용한다.
* 중간 인덱스의 값을 삭제하는 경우 자동으로 인덱스를 하나씩 앞으로 당긴다.
* */
alist.remove(2);
System.out.println("alist : " + alist);
// alist : [apple, banana, 45.67, Mon Jan 10 10:31:04 KST 2022, apple]
/* 지정된 위치의 값을 수정할 때에도 인덱스를 활용할 수 있으며
* set() 메소드를 사용한다.
* */
alist.set(1, true);
System.out.println("alist : " + alist);
// alist : [apple, true, 45.67, Mon Jan 10 10:32:00 KST 2022, apple]
/* 코드 작성중 노란줄이 많이가는 이유?
* : 모든 컬렉션 프레임 워크 클래스는 제네릭 클래스로 작성 돼 있다. */
List<String> stringList = new ArrayList<>(); // 타입 추론이 가능하기에 생략 가능
/* 스트링 타입만 저장하는 객체이다, 라는 의미 */
/* 제네릭 타입을 지정하면 지정한 타입 외의 인스턴스는 저장하지 못한다. */
stringList.add("apple");
//stringList.add(123); // int 타입은 컴파일 에러
stringList.add("banana");
stringList.add("orange");
stringList.add("mango");
stringList.add("grape");
System.out.println("stringList : " + stringList);
// stringList : [apple, banana, orange, mango, grape]
/* 저장 순서를 유지하고 있는 stringList를 오름차순 정렬해보자 */
/* Collection 인터페이스가 아닌 Collections 클래스이다.
* Collection 에서 사용되는 기능들을 static 메소드로 구현한 클래스이며
* 인터페이스명 뒤에 s가 붙은 클래스들은 관례상 비슷한 방식으로 작성 된 클래스를 의미하게 된다.
* */
Collections.sort(stringList);
/* sort 메소드를 사용하면 list가 오름차순 정렬 처리 된 후 정렬 상태가 유지된다.
* 즉, 원본에 영향을 미친다.
* */
System.out.println("stringList : " + stringList);
// stringList : [apple, banana, grape, mango, orange] // 오름차순정렬
/* 조금 복잡하지만 내림차순 정렬을 할 수도 있다.
* 하지만 기본적으로 ArrayList에는 역순으로 정렬하는 기능은 제공되지않는다.
* 역순 정렬은 LinkedList에 정의되어있는데 현재 사용하는 ArrayList를
* LinkedList로 변경할 수 있다. 둘다 Collection을 상속받고 있다.
* */
stringList = new LinkedList<>(stringList); //arrayList -> LinkedList
/* Iterator 반복자 인터페이스를 활용해서 역순으로 정렬한다.
* 제네릭 적용하는 것이 좋다.
* LinkedList 타입으로 형변환 한 후 descendingIterator()메소드를 사용하면
* 내림차순으로 정렬 된 Iterator 타입의 목록으로 반환해준다.
*
* Iterator란?
* Collection 인터페이스의 Iterator() 메소드를 이용해서 인스턴스를 생성할 수 있다.
* 컬렉션에서 값을 읽어오는 방식을 통일된 방식으로 제공하기 위해서 사용한다.
* 반복자라고 불리우며 반복문을 이용해서 목록을 하나씩 꺼내는 방식으로 사용하기 위함이다.
* 인덱스로 관리되는 컬렉션이 아닌 경우에는 반복문을 사용해서 요소에 하나씩 접근할 수 없기 때문에
* 인덱스를 사용하지 않고도 반복문을 사용하기 위한 목록을 만들어주는 역할이라고 보면 된다.
* descendingIterator() - 내림차순
*
* hasNext() : 다음 요소를 갖고 있는 경우 true, 더 이상 요소가 없는 경우 false를 반환
* next() : 다음요소를 반환
* */
Iterator<String> dIter = ((LinkedList<String>)stringList).descendingIterator();
while(dIter.hasNext()) {
System.out.println(dIter.next());
}
// orange
// mango
// grape
// banana
// apple
/* 한번 출력한 뒤엔 다시 출력할 수 없다. */
// while(dIter.hasNext()) {
// System.out.println(dIter.next());
// }
//
/* 역순으로 정렬 된 결과를 저장하기 위해서는 새로운 ArrayList를 만들어
* 저장 해 두면 된다.
* */
List<String> descList = new ArrayList<>();
while(dIter.hasNext()) {
descList.add(dIter.next());
}
System.out.println("descList : " + descList);
}
}
Application2
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import com.greedy.section01.list.comparator.AscendingPrice;
import com.greedy.section01.list.dto.BookDTO;
public class Application2 {
public static void main(String[] args) {
/* 여러 권의 책 목록을 관리할 ArrayList 인스턴스 생성 */
List<BookDTO> bookList = new ArrayList<>();
/* 도서 정보 추가, BookDTO 타입만 받아야 하기때문에 String 타입으로 작성할 수 없다. */
bookList.add(new BookDTO(1, "홍길동전", "허균", 50000));
bookList.add(new BookDTO(2, "목민심서", "정약용", 30000));
bookList.add(new BookDTO(3, "동의보감", "허준", 40000));
bookList.add(new BookDTO(4, "삼국사기", "김부식", 46000));
bookList.add(new BookDTO(5, "삼국유사", "일연", 58000));
/* 정렬 전 책 리스트 출력 */
for(BookDTO book : bookList) {
System.out.println(book);
}
// BookDTO [number=1, title=홍길동전, author=허균, price=50000]
// BookDTO [number=2, title=목민심서, author=정약용, price=30000]
// BookDTO [number=3, title=동의보감, author=허준, price=40000]
// BookDTO [number=4, title=삼국사기, author=김부식, price=46000]
// BookDTO [number=5, title=삼국유사, author=일연, price=58000]
/* 제네릭의 타입 제한에 의해 Comparable 타입을 가지고 있는 경우에만
* sort가 가능하다. 즉, 사전에 기준이 필요하다. */
// Collections.sort(bookList); // 정렬되지 않는다.
/* 가격 순으로 오름차순 정렬 - AscendingPrice 추가 */
/* Comparator 인터페이스를 상속받아 정렬 기준을 정해준 뒤
* List의 sort() 메소드의 인자로 정렬 기준이 되는 인터페이스를 넣어주게 되면
* 내부적으로 우리가 오버라이딩 한 메소드가 동작하여 그것을 정렬기준으로 삼는다.
* */
bookList.sort(new AscendingPrice());
System.out.println("가격 오름차순 정렬 -------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
// 가격 오름차순 정렬 -------------------
// BookDTO [number=2, title=목민심서, author=정약용, price=30000]
// BookDTO [number=3, title=동의보감, author=허준, price=40000]
// BookDTO [number=4, title=삼국사기, author=김부식, price=46000]
// BookDTO [number=1, title=홍길동전, author=허균, price=50000]
// BookDTO [number=5, title=삼국유사, author=일연, price=58000]
/* 인터페이스를 구현할 클래스를 재사용 하는 경우 AscendingPrice 클래스처럼 작성하면 되지만
* '한번만 사용'하기 위해서는 조금 더 간편한 방법을 이용할 수 있다.
* 즉, 재사용하지않는 목적에 한 해서 익명 클래스를 이용한 방법이다.
*
* 익명클래스는 뒤에 {}를 만들어서 마치 Comparator 인터페이스를 상속받은 클래스인데
* 이름이 없다고 생각하고 사용하는 것이다.
*
* */
bookList.sort(new Comparator<BookDTO> () {
@Override
public int compare(BookDTO o1, BookDTO o2) {
/* 여기에 내림차순 정렬 조건을 넣어주면 된다.
* 아까와는 반대로 오름차순 정렬 된 상태인 경우 순서를 바꿔야한다.
* 양수를 반환해서 순서를 바꾸라는 플래스로 이용했었다.
*
* 양수값이 반환되면 바꿀필요없으며, 음수가 반환되면 순서를 바꾸고,
* 같을시 0반환
* */
return o2.getPrice() - o1.getPrice();
}
});
System.out.println("가격 내림차순 정렬 -------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
// BookDTO [number=5, title=삼국유사, author=일연, price=58000]
// BookDTO [number=1, title=홍길동전, author=허균, price=50000]
// BookDTO [number=4, title=삼국사기, author=김부식, price=46000]
// BookDTO [number=3, title=동의보감, author=허준, price=40000]
// BookDTO [number=2, title=목민심서, author=정약용, price=30000]
/* 제목 순 오름차순 정렬 */
bookList.sort(new Comparator<BookDTO>() {
@Override
public int compare(BookDTO o1, BookDTO o2) {
/* 문자열은 대소비교를 할 수 없다.
* 문자 배열로 변경 후 인덱스 하나하나를 비교해서 어느 것이 더 큰 값인지 확인해야하는데
* String 클래스의compareTo() 메소드에서 이미 정의 해 놓았다.
* */
/* 앞의 값이 더 작은 경우 음수 반환,
* 같으면 0반환
* 앞의 값이 더 큰 경우 양수반환 (즉, 바꿔야 하는 경우) */
return o1.getTitle().compareTo(o2.getTitle());
}
});
System.out.println("제목 오름차순 정렬 -------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
/* 제목 내림차순 정렬 */
bookList.sort(new Comparator<BookDTO> () {
@Override
public int compare(BookDTO o1, BookDTO o2) {
// 위 오름차순 순서와 반대되는 값을 반환해야 한다.
// 값이 반전될 수 있게 하는 처리가 필요하다.
return o2.getTitle().compareTo(o1.getTitle());
}
});
System.out.println("제목 내림차순 정렬 -------------------");
for(BookDTO book : bookList) {
System.out.println(book);
}
// 제목 내림차순 정렬 -------------------
// BookDTO [number=1, title=홍길동전, author=허균, price=50000]
// BookDTO [number=5, title=삼국유사, author=일연, price=58000]
// BookDTO [number=4, title=삼국사기, author=김부식, price=46000]
// BookDTO [number=2, title=목민심서, author=정약용, price=30000]
// BookDTO [number=3, title=동의보감, author=허준, price=40000]
}
/* Vector의 경우 스레드 동기화 처리가 된다는 점만 다르고 ArrayList와 동일하게 동작한다.
* 그래서 따로 작성하지 않을 것이다.
* JDK 1.0부터 사용하긴 했지만 하위 호환을 위해 남겨놓았고 성능 문제로 현재는 사용하지 않는다.
* 가급적이면 ArrayList를 사용하면 된다. */
}
BookDTO
public class BookDTO {
/* 필드 */
private int number;
private String title;
private String author;
private int price;
/* 필드뒤에오는건 기본 생성자 */
public BookDTO() {}
/* 필드뒤에오는건 매개변수를 가지는 생성자 */
public BookDTO(int number, String title, String author, int price) {
super();
this.number = number;
this.title = title;
this.author = author;
this.price = price;
}
/* 게터 세터 */
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
/* toString */
@Override
public String toString() {
return "BookDTO [number=" + number + ", title=" + title + ", author=" + author + ", price=" + price + "]";
}
}
AscendingPrice implements Comparator<BookDTO>
import java.util.Comparator;
import com.greedy.section01.list.dto.BookDTO;
public class AscendingPrice implements Comparator<BookDTO> {
/* 목적 : 정렬기준 설정해주기 */
// @Override
// public int compare(Object o1, Object o2) {
// // 오브젝트 타입에서는 접근 불가능함, 다운캐스팅 필요
// return 0;
// }
@Override
public int compare(BookDTO o1, BookDTO o2) { // 임포트 필요
// TODO Auto-generated method stub
/* sort()에서 내부적으로 사용하는 메소드이다.
* 인터페이스를 상속받아서 메소드 오버라이딩하는 것을 강제화 해놓았다.
* */
/* 정렬 기준/판단기준 설정, 비교 대상 두 인스턴스의 가격이 오름차순 정렬이 되기 위해서는
* 앞의 가격이 더 작은 가격이어야 한다.
* 만약, 뒤의 가격이 더 작은 경우 두 인스턴스의 순서를 바꿔야 한다.
* 그 때 두 값을 바꾸라는 신호로 양수를 보내주게 되면 정렬 시 순서를 바꾸는 조건으로 사용 된다.
* (음수는 바꾸지X)
* */
/* 양수, 음수 형태로 두 비교 값이 순서를 바꿔야 하는지를 알려주기 위한 용도의 변수 */
int result = 0;
if (o1.getPrice() > o2.getPrice()) {
/* 오름차순을 위해 순서를 바꿔야 하는 경우 양수반환 */
result = 1;
} else if (o1.getPrice() < o2.getPrice()) {
/* 이미 오름차순 정렬로 되어 있는 경우 음수를 반환 */
result = -1;
} else {
/* 두 값이 같은 경우 0을 반환*/
result = 0;
return 0;
}
return result;
/* 정렬의 기준을 작성 해 놓은것이다. 상황에 따라 1, -1, 0이 반환되게끔. */
}
}
Application3
import java.util.LinkedList;
import java.util.List;
public class Application3 {
public static void main(String[] args) {
/* LinkedList
* ArrayList가 배열을 이용해서 발생할 수 있는 성능적인 단점을 보환하고자 고안되었다.
* 내부는 이중 연결리스트로 구현되어 있다.
*
* 단일 연결 리스트
* : 저장한 요소가 순서를 유지하지 않고 저장되지만 이러한 요소들 사이를 링크로 연결하여
* 구성하며 마치 연결 된 리스트 형태인 것처럼 만든 자료구조이다.
* 요소의 저장과 삭제 시 다음 요소를 가리키는 참조 링크만 변경하면 되기 때문에
* 요소의 저장과 삭제가 빈번히 일어나는 경우 ArrayList보다 성능면에서 우수하다.
* 단, 다음값만 가지고 있기 때문에 이전값으로 역순이동 하려는 경우엔 어려움을 겪어
* 이러한 단점을 보완하기위해 이중연결 리스트가 고안되었다.
*
* 이중 연결 리스트(LinkedList)
* : 단일 연결 리스트는 다음 요소만 링크하는 반면 이중 연결 리스트는 이전 요소도 링크하여
* 이전 요소로 접근하기 쉽게 고안된 자료구조이다.
*
*
* 하지만 내부적으로 요소를 저장하는 방법에 차이가 있는것이다.
* 각 컬렉션 프레임 뭐크 클래스들의 특징을 파악하고 그에따라 적합한 자료구조를 구현한
* 클래스를 선택하는 것이 좋다.
*
*
*
* */
/* LinkedList 인스턴스 생성 */
List<String> linkedList = new LinkedList<>();
/* 요소를 추가할 때는 add를 이용한다. */
linkedList.add("apple");
linkedList.add("banana");
linkedList.add("orange");
linkedList.add("mango");
linkedList.add("grape");
/* 저장된 요소의 갯수는 size() 메소드를 이용한다. */
System.out.println(linkedList.size());
// 콘솔창 출력 : 5
/* for문과 size()를 이용해서 반복문을 활용할 수도 있다.
* 요소를 꺼내올 때는 get()을 사용하며,
* 인자로 전달하는 정수는 인덱스처럼 사용하면 된다.
* */
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(i + " : " + linkedList.get(i));
}
// 0 : apple
// 1 : banana
// 2 : orange
// 3 : mango
// 4 : grape
/* 요소를 제거할 때는 remove 메소드를 이용하며 인덱스를 활용한다. */
linkedList.remove(1);
/* 향상된 for문도 사용가능하다. */
for (String s: linkedList) {
System.out.println(s);
}
// apple
// orange
// mango
// grape
/* set() 메소드를 이용해서 요소를 수정할 수도 있다. */
linkedList.set(0, "pineapple");
/* toString 메소드가 오버라이딩 돼 있어서 모든 요소 정보를 쉽게 볼 수 있다. */
System.out.println(linkedList);
// 출력: [pineapple, orange, mango, grape]
/* isEmpty() 메소드를 이용해서 list 가 비어있는지 확인할 수 있다. */
System.out.println(linkedList.isEmpty());
// false : 비어있지 않으므로 출력
/* 리스트 내 요소를 모두 제거하는 clear() 메소드를 이용할 수도 있다. */
linkedList.clear();
System.out.println(linkedList.isEmpty());
// true : clear() 메소드로 비웠으므로 true
}
}
Stack
stack은 List에서 파생되었다.
stack은 제한적으로 접근할 수 있는 나열 구조로 데이터를 저장
쌓아나간다고 생각, 넣을때는 바닥부터, 꺼낼때는 위에 쌓인 것 부터
후임선출(LIFO - Last Input First Out)방식의 자료 구조 - 마지막에 넣은것이 가장 처음 나온다
Application4
import java.util.Stack;
public class Application4 {
public static void main(String[] args) {
/* Stack */
/* Stack은 리스트 계열 클래스의 Vector 클래스를 상속받아 구현되었다.
* 스택메모리 구조는 선형 메모리 공간에 데이터를 저장하며
* 후입선출 (LIFO - Last Input First Out) 방식의 자료구조라 부른다.
* */
/* Stack 인스턴스 생성 */
Stack<Integer> integerStack = new Stack<>(); // 임포트
/* Stack에 값을 넣을 때는 push() 메소드를 이용한다.
* 벡터의 기능을 상속받았으므로 add() 이용이 가능하긴 하지만
* push()를 이용하는 것이 좋다.
* */
integerStack.push(1); // 오토 박싱이 되므로 그냥 인트값을 넣어준다.
integerStack.push(2);
integerStack.push(3);
integerStack.push(4);
integerStack.push(5);
/* 값을 출력할때는 순서대로 나온다. */
System.out.println(integerStack);
// [1, 2, 3, 4, 5]
/* Stack의 Search()메소드는 위에서부터의 순번을 찾아오는데,
* 스택의 자료구조는 아래서부터 쌓이는 방식이라고 했다. */
/* 스택에서 요소를 찾을때 search를 이용할 수 있따.
* 인덱스가 아닌 위에서부터의 순번을 의미한다.
* 또한 가장 상단의 위치가 0이 아닌 1부터 시작한다. */
System.out.println(integerStack.search(5));
// 콘솔 출력: 1
/* Stack에서 값을 꺼내는 메소드는 크게 2가지로 볼 수 있다.
* peek(): 해당 스택의 가장 마지막에 (상단에 있는) 요소반환
* pop() : 해당 스택의 가장 마지막에 있는 (상단에 있는) 요소 반환 후
* 반환했던 요소를 제거
* */
System.out.println("peek() : " + integerStack.peek());
System.out.println(integerStack);
// peek() : 5
// [1, 2, 3, 4, 5]
System.out.println("pop() : " + integerStack.pop());
System.out.println(integerStack);
// pop() : 5
// [1, 2, 3, 4]
/* pop은 꺼내면서 요소를 제거학 때문에 스택이 비어있는 경우 에러가 발생할 수 있다. */
System.out.println("pop() : " + integerStack.pop());
System.out.println("pop() : " + integerStack.pop());
System.out.println("pop() : " + integerStack.pop());
System.out.println("pop() : " + integerStack.pop());
System.out.println("pop() : " + integerStack.pop());
// 모두 출력+ 제거후 더 이상 반환할 요소가 없을때 에러를 발생시킨다.
// java.util.EmptyStackException
}
}
Queue
queue
queue는 선형 메모리 공간에 데이터를 저장
선입선출(FIFO - First Input First Out)방식의 자료구조
Application5
import java.util.*;
import java.util.LinkedList;
import java.util.Queue;
public class Application5 {
public static void main(String[] args) {
/* Queue */
/* Queue는 선형 메모리 공간에 데이터를 저장하는
* 선입 선출(FIFO - First Input First Out) 방식의 자료구조이다.
* Queue 인터페이스를 상속받는 하위 인터페이스들은
* Deque, BlockingQueue, TransferQueue 등 다양하지만
* 대부분의 큐는 LinkedList를 이용한다.
* */
/* Queue자체로는 인터페이스이기 때문에 인스턴스 생성이 불가능하다. */
// Queue<String> que = new Queue<>();
// 생설할 수는 없는데 그 이유는 인터페이스 이기 때문
/* LinkedList로 인스턴스 생성 */
Queue<String> que = new LinkedList<>();
/* 큐에 데이터를 넣을때에는 offer() 를 이용한다. */
que.offer("first");
que.offer("second");
que.offer("third");
que.offer("fourth");
que.offer("fifth");
System.out.println(que);
// [first, second, third, fourth, fifth]
/* 큐에서 데이터를 꺼낼때는 2가지 메소드가 있다.
* peek() : 해당 큐의 가장 앞에있는 요소 (먼저들어온 요소)를 반환한다.
* poll() : 해당 큐의 가장 앞에있는 요소 (먼저들어온 요소)를 반환하고 반환한 요소를 제거한다.
* */
System.out.println("peek() : " + que.peek());
System.out.println(que);
// peek() : first
// [first, second, third, fourth, fifth]
System.out.println("poll() : " + que.poll());
System.out.println(que);
// poll() : first
// [second, third, fourth, fifth] // 출력된 데이터는 제거되었다.
}
}
Set
set이란?
저장 순서가 유지되지 않고, 중복 인스턴스도 저장하지 못하게 하는 자료구조
null값도 중복하지 않게 하나의 null만 저장 가능
구현 클래스 : HashSet, LinkedHashSet, TreeSet
Set 계열 주요 메소드
Iterator
컬렉션에 저장된 요소를 접근하는데 사용되는 인터페이스
List와 Set계열에서만 사용
Map의 경우 사용할 수 없다. Map의 경우엔 Set 또는 List'화' 시켜서(바꿔서) iterator()를 사용
HashSet
Set에 인스턴스를 저장할 때 hash함수를 사용하여 처리 속도가 빠름
동일 인스턴스 뿐 아니라 동등 인스턴스도 중복하여 저장하지 않음
동일 : 완전히 같은 인스턴스 동등 : 다른 인스턴스이지만 특정한 기준들의 속성 값이 같음
LinkedHashSet
HashSet과 거의 동일하지만 Set에 추가되는 순서를 유지함
TreeSet
이진 트리를 기반으로 한 Set컬렉션으로, 왼쪽과 오른쪽 자식 노드를 참조하기 위한 두 개의 변수로 구성
추가된 값이 기존 값(5)을 기준으로 큰지(7) 작은지(3) 판단하여 정렬된 형태(3-5-7)로 저장한다, 즉 기존값 기준 더 작은 것은 왼쪽에, 더 큰값은 오른쪽에
Applicaion1
import java.util.HashSet;
import java.util.Iterator;
public class Applicaion1 {
public static void main(String[] args) {
/* Set 인터페이스를 구현한 Set 컬렉션 클래스의 특징
* 1. 요소의 저장 순서를 유지하지 않는다.
* 2. 같은 요소의 중복 저장을 허용하지 않는다.
* (null값도 중복되지않아 하나의 null만 저장한다.)
*
* */
/* HashSet 클래스
* Set 컬렉션 클래스에서 가장 많이 사용 되는 클래스 중 하나이다.
* JDK 1.2 부터 제공되고 있으며 해시 알고리즘을 사용하여 검색 속도가
* 빠르다는 장점을 가진다.
*
* */
/* HashSet 인스턴스 생성 */
HashSet<String> hset = new HashSet<>();
/* 다형성을 적용하여 상위 인터페이스를 타입으로 사용가능 */
// Set hset2 = new HashSet();
// Collection hset3 = new HashSet();
hset.add(new String("java"));
hset.add(new String("oracle"));
hset.add(new String("jdbc"));
hset.add(new String("html"));
hset.add(new String("css"));
/* toString 이 오버라이딩 되어 있다.
* 저장 순서 유지 안됨*/
System.out.println(hset);
// [css, java, oracle, jdbc, html]
//저장한 순서와 담겨있는 순서는 같지않다.
/* 중복 데이터 저장 허용 안됨 */
hset.add(new String("java"));
System.out.println(hset);
// [css, java, oracle, jdbc, html]
// 원래 저장돼있기때문에 추가 저장은 안됨
System.out.println("저장 된 객체 수 : " + hset.size());
// 저장 된 객체 수 : 5
System.out.println("포함 여부 확인 : " + hset.contains(new String("oracle")));
// 포함 여부 확인 : true
/* 저장 된 객체를 하나씩 꺼내는 기능이 없음 */
/* 반복문을 이용한 연속 처리 하는 방법 */
/* 1. toArray()로 배열로 변경한 뒤 for loop 사용 */
Object[] arr = hset.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(i + " : " + arr[i]);
}
// 0 : css
// 1 : java
// 2 : oracle
// 3 : jdbc
// 4 : html
/* 2. iterator()로 목록 만들어 연속 처리 */
Iterator<String> iter = hset.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
// css
// java
// oracle
// jdbc
// html
/* 지우는 방법 */
hset.clear();
System.out.println("empty? : " + hset.isEmpty());
// empty? : true
}
}
Application2
import java.util.LinkedHashSet;
import java.util.TreeSet;
public class Application2 {
public static void main(String[] args) {
/* LinkedHashSet 클래스 */
/* HashSet이 가지는 기능을 모두 가지고 있고
* 추가적으로 저장 순서를 유지하는 특징을 가지고 있다.
* JDK 1.4부터 제공하고 있다. */
LinkedHashSet<String> lhset = new LinkedHashSet<>();
lhset.add("java");
lhset.add("oracle");
lhset.add("jdbc");
lhset.add("html");
lhset.add("css");
/* 저장된 순서를 유지하고있다. */
System.out.println("lhset : " + lhset);
// lhset : [java, oracle, jdbc, html, css]
/* 만들어진 링크드해쉬셋을 가지고 트리셋으로 객체를 생성하면
* 같은 타입의 객체를 자동으로 비굑하여 오름차순으로 정렬한다.
* */
TreeSet<String> tset = new TreeSet<>(lhset);
System.out.println(tset);
// [css, html, java, jdbc, oracle]
}
}
Application3
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class Application3 {
public static void main(String[] args) {
/* TreeSet 클래스 */
/* TreeSet 클래스는 데이터가 정렬 된 상태로 저장 되는 이진 검색 트리의
* 형태로 요소를 저장한다.
* 이진 검색 트리는 데이터를 추가하거나 제거하는 등의 기본동작 시간이 매우 빠르다.
* jdk 1.2 부터 제공되고 있으며
* Set 인터페이스가 가지는 특징을 그대로 가지지만 정렬 된 상태를 유지한다는 것이
* 다른 점이다.
* */
TreeSet<String> tset = new TreeSet<>();
// Set<String> tset2 = new TressSet<>();
// 다형성을 이용하여 상위타입으로 선언하는 것도 가능하다.
tset.add("java");
tset.add("oracle");
tset.add("jdbc");
tset.add("html");
tset.add("css");
/* 자동 오름차순 정렬 */
System.out.println(tset);
// [css, html, java, jdbc, oracle]
/* 목록 만들어서 하나씩 대문자로 변경해서 출력처리 */
Iterator<String> iter = tset.iterator();
while(iter.hasNext()) {
System.out.println(iter.next().toUpperCase());
}
// CSS
// HTML
// JAVA
// JDBC
// ORACLE
/* 배열로 바꾸어 연속 처리하기 */
Object[] arr = tset.toArray();
for(Object obj : arr) {
System.out.println(((String)obj).toUpperCase());
}
// 그냥 오브젝트에서는 대문자변환이 불가능하므로
// 오브젝트를 참조형인 스트링으로 강제 타입 변환 해준다.
/* 로또번호 발생기 (TreeSet의 이용) */
Set<Integer> lotto = new TreeSet<>();
while(lotto.size() < 6) {
lotto.add((int)(Math.random() * 45) +1);
} // add가 중복을 막아줌, 정렬역시 treeset이 해주므로 정렬도 필요없음
System.out.println("lotto : " + lotto);
// lotto : [8, 11, 13, 34, 35, 43]
}
}
byte b = Byte.parseByte("1");
short s = Short.parseShort("2");
int i = Integer.parseInt("3");
long l = Long.parseLong("4");
float f = Float.parseFloat("0.1");
double d = Double.parseDouble("0.2");
boolean bool = Boolean.parseBoolean("true");
char c = "abc".charAt(0);
Wrapper : 기본자료형을 String으로 바꿀 시
String b = Byte.valueOf((byte)1).toString();
String s = Short.valueOf((short)2).toString();
String i = Integer.valueOf(3).toString();
String l = Long.valueOf(4L).toString();
String f = Float.valueOf(0.1f).toString();
String d = Double.valueOf(0.2).toString();
String bool = Boolean.valueOf(true).toString();
String ch = Character.valueOf('a').toString();
Application1
public class Application1 {
public static void main(String[] args) {
/* Wrapper 클래스 */
/* 상황에 따라 기본 타입의 데이터를 인스턴스화 해야 하는 경우들이 발생한다.
* 이때 기본 타입 데이터를 먼저 인스턴스로 변환 후 사용해야 하는데
* 8가지에 해당하는 기본 타입의 데이터를 인스턴스화 할 수 있도록 해주는
* 클래스를 래퍼(Wrapper)클래스라고 한다.
* */
/* Boxing 과 UnBoxing */
/* 기본타입을 래퍼클래스의 인스턴스로 인스턴스화 하는 것을 박싱이라고 하며,
* 래퍼 클래스 타입의 인스턴스를 기본 타입으로 변경하는 것을 언박싱이라고 한다. */
int intValue = 20;
Integer boxingNumber1 = new Integer(intValue);
// 인스턴스화 - 박싱(Boxing) // 생성자 이용
// The constructor Integer(int) is deprecated since version 9
// 자바 9 버전부터는 안될 수 있으므로 사용하지 않을것을 권장
Integer boxingNumber2 = Integer.valueOf(intValue);
// static 메소드 이용
int unBoxingNumber1 = boxingNumber1.intValue();
// 언박싱 (unBoxing) // intValue() 이용
/* 오토 박싱(Auto Boxing)과 오터 언박싱(Auto UnBoxing)
* JDK 1.5 부터는 박싱과 언박싱이 필요한 상황에서 자바 컴파일러가 이를
* 자동으로 처리해 준다. 이런 자동화 된 박싱과 언박싱을 오토 박싱, 오토 언박싱
* 이라고 한다. 굳이 메소드를 이용한 방식을 취하지 않아도 된다.
* */
Integer boxingNumber3 = intValue;
// 자동변환, 오토 박싱
int unBoxingNumber2 = boxingNumber3;
// 오토 언박싱
/* Wrapper 클래스 값 비교 */
int inum = 20;
Integer integerNum1 = new Integer(20);
Integer integerNum2 = new Integer(20);
Integer integerNum3 = 20;
Integer integerNum4 = 20;
/* 기본 타입과 래퍼 클래스 타입은 == 연산으로 비교 가능하다. */
System.out.println("int와 Integer 비교 : " + (inum == integerNum1));
System.out.println("int와 Integer 비교 : " + (inum == integerNum3));
// int와 Integer 비교 : true
// int와 Integer 비교 : true
/* 생성자를 이용해 생성한 인스턴스의 경우 ==로 비교하지 못한다. (주소값 비교)
* 단, 오토 박싱을 이용해서 생성한 값은 == 비교가 가능하다.
* */
System.out.println("Integer와 Integer 비교 : " + (integerNum1 == integerNum2));
System.out.println("Integer와 Integer 비교 : " + (integerNum1 == integerNum3));
System.out.println("Integer와 Integer 비교 : " + (integerNum3 == integerNum4));
// Integer와 Integer 비교 : false
// Integer와 Integer 비교 : false
// Integer와 Integer 비교 : true
//new 생성자로 인스턴스가 새로 성성돼어 서로 다른 주소값을 참조하고있으므로 false가 출력된다.
/* 래퍼클래스 타입의 인스턴스의 주소값이 아닌 "값"을 비교할때는 equals()를 사용한다. */
System.out.println("equals() : " + (integerNum1.equals(integerNum2)));
System.out.println("equals() : " + (integerNum1.equals(integerNum3)));
System.out.println("equals() : " + (integerNum3.equals(integerNum4)));
// equals() : true
// equals() : true
// equals() : true
}
}
Application2
public class Application2 {
public static void main(String[] args) {
/* parsing : 문자열(String)값을 기본 자료형 값으로
* 변경하는 것을 parsing이라고 한다.
* 래퍼클래스 내부에 정의 돼있는 기능이다.
* */
byte b = Byte.parseByte("1");
System.out.println("b : " + b); // b : 1
// byte b1 = Byte.parseByte("g");
// System.out.println("b1 : " + b1);
// b1 : java.lang.NumberFormatException 에러가 발생한다.
short s = Short.parseShort("2");
System.out.println("s : " + s); // s : 2
int i =Integer.parseInt("4");
System.out.println("i : " + i); // i : 4
long l = Long.parseLong("8");
System.out.println("l : " + l); // l : 8
float f = Float.parseFloat("4.0");
System.out.println("f : " + f); // f : 4.0
double d = Double.parseDouble("8.0");
System.out.println("d : " + d); // d : 8.0
boolean bl = Boolean.parseBoolean("true");
System.out.println("bl : " + bl); // bl : true
/* Character는 parsing 기능을 제공하지 않는다. */
char c = "abc".charAt(0);
System.out.println("c : " + c); // c : a
}
}
Application3
public class Application3 {
public static void main(String[] args) {
/* parsing 과 반대로 기본 자료형 값을 문자열로 변경하는 경우도 필요하다 */
/* valueOf() : 기본자료형 값을 Wrapper 클래스 타입으로 변환시키는 메소드
* toString() : 필드 값을 문자열로 반환하는 메소드
*
*
* */
String b = Byte.valueOf((byte)1).toString();
System.out.println("b : " + b);
// toString은 객체가 가진 필드값을 문자열로 반환해주는 메소드
// 문자열 타입 b : 1
String s = Short.valueOf((short)2).toString();
System.out.println("s : " + s);
String i = Integer.valueOf(4).toString(); // i : 4
System.out.println("i : " + i);
String l = Long.valueOf(8L).toString();
System.out.println("l : " + l); // l : 8
String f = Float.valueOf(4.0f).toString(); // f : 4.0
System.out.println("f : " + f);
String d = Double.valueOf(8.0).toString(); // d : 8.0
System.out.println("d : " + d);
String bl = Boolean.valueOf(true).toString(); // bl : true
System.out.println("bl : " + bl);
String c = Character.valueOf('a').toString(); //c : a
System.out.println("c : " + c);
/* String 클래스의 valueOf 메소드를 사용할 수도 있다. */
/* 위에서 기본자료형을 래퍼 클래스로 바꿔주는 과정보다 조금 더 간단한 과정
* 모든 데이터 타입에 대하여 오버로딩 되어있다.
* String.valueOf() 괄호 속 기본자료형 -> String 으로 바꿔준다.
* */
String str = String.valueOf(10);
System.out.println("str : " + str); // str : 10
/* 문자열 합치기를 이용해 String으로 변환할 수도 있다. */
String str2 = 123 + "";
System.out.println("str2 : " + str2); // str2 : 123
}
}
Date
시스템으로부터 현재 날짜, 시간 정보를 가져와서 다룰 수 있게 만들어진 클래스
생성자 2개만 사용하고 나머지는 모두 deprecated
deprecated는 향후 버전이 업데이트 되면서 사라지게 될 기능이니 가급적이면 사용을 권장하지 않는다는 것을 의미
Date today = new Date(); //시스템으로부터 현재 날짜, 시간 정보를 가져와 기본 값으로 사용
Date when = new Date(123456798L); //long형 정수 값을 가지고 날짜 시간 계산 //1970년 1월 1일 0시 0분 0초를 기준으로 함
Application1
import java.util.Date;
public class Application1 {
public static void main(String[] args) {
/* Date 클래스 */
/* JDK 1.0 부터 날짜를 취급하기 위해 사용되던 Date 클래스는
* 생성자를 비롯하여 대부분의 메소드가 Deprecated 되어있다. (API 문서참조)
* */
/* Date는 java.sql.Date 와 java.util.Date가 존재한다.
* 한 클래스에서 두개의 타입을 전부 사용하게 되면 import를 하더라도 사용하는
* 타입이 어느 패키지에 있는 Date 클래스인지에 대한 모호성이 발생하여
* import를 하더라도 풀 클래스 이름을 작성해 주어야 한다.
* 단, 여기에서는 java.util.Date 만 사용할 것이다.
* */
/* 1. 기본생성자 사용하는 방법 */
/* 기본생성자로 인스턴스를 생성하면 운영체제의 날짜/시간 정보를 이용해서
* 인스턴스를 만들게 된다.
* */
java.util.Date today = new java.util.Date();
/* toString 메소드가 오버라이딩 되어 있어서 쉽게 필드 값을 출력해 볼 수 있다. */
System.out.println(today);
// Fri Jan 07 10:29:36 KST 2022, 실행 당시의 시간 출력
/* 2. Date(long date) 사용하는 방법 */
/* getTime() : 1970년 1월 1일 0시 0분 0초 이후 지난 시간을 millisecond
* 로 계산해서 long타입으로 반환한다.
* */
System.out.println(today.getTime());
// 1641519153289 출력
/* Returns the number of milliseconds since January 1, 1970, 00:00:00
* GMTrepresented by this Date object.Returns:the number of milliseconds
* since January 1, 1970, 00:00:00 GMTrepresented by this date.
*/
Date time = new Date (1641519153289L);
// import java.util.Date; 임포트
/* 방금 실행했을때의 시간이 나오게 된다. */
System.out.println(time);
/* 지금 이 순간은 시간으로 추출할 수 있지만
* 특정 일자를 의도적으로 출력하기는 어렵다는 한계가 있다. */
}
}
Calendar와 GregorianCalendar
Calendar
Calendar 클래스는 추상클래스 이기 때문에 new 연산자를 통해 인스턴스 생성이 불가능
getInstance( )메소드를 통해서 인스턴스를 생성
Calendar calendar = Calendar.getInstance();
GregorianCalendar
Calendar 클래스를 상속받은 클래스로 인스턴스 생성이 가능
Calendar gregorianCalendar = new GregorianCalendar();
Calendar today = Calendar.getInstance();
int year = today.get(Calendar.YEAR);
int month = today.get(Calendar.MONTH) + 1;
int date = today.get(Calendar.DATE);
int ampm = today.get(Calendar.AM_PM);
int hour = today.get(Calendar.HOUR);
int min = today.get(Calendar.MINUTE);
int sec = today.get(Calendar.SECOND);
String ampmString = (ampm == Calendar.AM) ? "오전" : "오후";
Application2
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class Application2 {
public static void main(String[] args) {
/* java.util.calendar 클래스 사용 */
/* 추상클래스이므로 객체생성이 불가능 하다, new Calendar불가
* API문서를 살펴보면 Calendar 클래스는 abstract 클래스로 작성 돼있다.
* 따라서 Calendar 클래스를 이용해서 인스턴스를 생성하는 것이 불가능하다.
*
* */
/* Calendar 클래스를 이용한 인스턴스 생성 방법에는 두가지가 있다.
* 1. getInstance() static 메소드를 이용해서 인스턴스를 반환하는 방법
* 2. 후손 클래스인 GregorianCalendar클래스를 이용해서 인스턴스를
* 생성하는 방법
* */
/* 1. getInstance() static 메소드 이용 */
Calendar calendar = Calendar.getInstance(); // java.util 임포트 필요
/* toString 이 오버라이딩 되어 있어서 모든 필드의 값을 확인할 수 있다.
* Date 클래스에 비해 매우 많은 필드들이 값을 가지고 있다.
* 또한 생성 된 인스턴스 타입이 후손 클래스 타입인 GregorianCalendar
* 타입인 것을 확인할 수 있다.
* 이 방식으로 인스턴스를 생성하게 되면 운영체제의 날짜/시간 정보를 이용해서
* 인스턴스를 생성하게 된다.
* */
System.out.println(calendar);
// java.util.GregorianCalendar[time=1641519600946,areFieldsSet=true,
//출력 시 수많은 필드와 그레고리안 메소드로 인스턴스로 생성된 것이 보인다.
//
/* 특정 날짜/시간 정보를 이용해서 인스턴스를 생성하는 방법을 GregorianCalendar
* 의 생성자로 제공한다. */
/* 2. GregorianCalendar 이용하는 방법 */
/* 2-1. 기본 생성자 사용 */
Calendar gregorianCalendar = new GregorianCalendar();
// import java.util.GregorianCalendar; 임포트 필요
System.out.println(gregorianCalendar); // 현재 실행되는 시스템의 시간
/* 2-2. 년, 월, 일, 시, 분, 초 정보를 이용해서 인스턴스를 생성 */
/* 2014년 9월 18일 16:42:00 */
int year = 2014;
int month = 8;
// month 유의할 점 : 인덱스 처럼 0부터 월을 세기 때문에 범위가 0부터 11이다.
// 즉 8은 9월을 의미한다.
int dayOfMonth = 18;
int hour = 16;
int min = 42;
int second = 0;
Calendar birthday = new GregorianCalendar (year, month, dayOfMonth, hour, min, second);
System.out.println(birthday);
/* 상황에 따라 특정 일자를 기준으로 한 Date 타입의 인스턴스가 필요한 경우도 있다.
* 먼저 GregorianCalendar를 이용해서 특정 날짜/시간 정보로 인스턴스를 생성한 후
* 1970년 1월 1일 0시 0분 0초를 기준으로 지난 시간을 millisecond 로 계산해서
* long형으로 반환하는 메소드가 있다.
* */
System.out.println(birthday.getTimeInMillis());
// 1411026120000
// Long 타입의 getTimeInMillis()
/* 출력된 정수로 인스턴스를 생성하면 해당 날짜/시간 정보를 가지는
* Date 인스턴스가 된다. */
Date date = new Date(birthday.getTimeInMillis()); // 임포트 필요
System.out.println(date);
// Thu Sep 18 16:42:00 KST 2014
/* 실제 사용시 이런 형태로 만드는 경우가 많다. */
Date date2 = new Date(new GregorianCalendar(year, month, dayOfMonth,
hour, min, second).getTimeInMillis());
System.out.println(date2);
// Thu Sep 18 16:42:00 KST 2014
/* 생성 된 인스턴스의 필드 정보를 Calendar 클래스에 있는 get() 메소드를 사용하여
* 반환 받을 수 있다.
* */
int birthYear = birthday.get(1);
int birthMonth = birthday.get(2);
int borthDayOfMonth = birthday.get(5);
System.out.println(birthYear);
System.out.println(birthMonth);
System.out.println(borthDayOfMonth);
// 2014
// 8 // 9월이지만 0부터 고려되기 때문에 8로 나온다.
// 18
/* birthday.get(1)의 1,2,5는 무엇을 의미할까?
* 년, 월, 일의 의미를 담아서 상수필드에 지정한 것이다. */
/* 인자로 전달하는 정수에 따라 필드 값을 반환 받을 수 있다.
* 하지만 이렇게 사용하게 되면 각 필드에 매칭되는 정수를 다 외워야 사용이
* 가능 해 진다. 따라서 해당값들을 상수필드 형태로 제공하고 있다. */
System.out.println(Calendar.YEAR);
System.out.println(Calendar.MONDAY);
System.out.println(Calendar.DATE);
// 1
// 2
// 5
/* 그러면 우리는 get() 메소드의 인자로 정수 대신 각 상수 필드값을 호출하는
* 식으로 코드를 개선하면 보다 의미 전달이 쉬운 코드가 된다.
* 참조변수.get(Calendar.YEAR/MONTH/DATE)
* */
System.out.println("year : " + birthday.get(Calendar.YEAR));
System.out.println("month : " + birthday.get(Calendar.MONTH));
System.out.println("dayOfMonth : " + birthday.get(Calendar.DATE));
/* 요일(일(1), 월(2), 화(3)... 토(7)의 의미이다.) */
System.out.println("dayOfWeek : " + birthday.get(Calendar.DAY_OF_WEEK));
// year : 2014
// month : 8
// dayOfMonth : 18
// dayOfWeek : 5
String day = "";
switch (birthday.get(Calendar.DAY_OF_WEEK)) {
case Calendar.SUNDAY : day = "일"; break;
case Calendar.MONDAY : day = "월"; break;
case Calendar.TUESDAY : day = "화"; break;
case Calendar.WEDNESDAY : day = "수"; break;
case Calendar.THURSDAY : day = "목"; break;
case Calendar.FRIDAY : day = "금"; break;
case Calendar.SATURDAY : day = "토"; break;
}
System.out.println("요일 : " + day);
// 요일 : 목
System.out.println("ampm : " + birthday.get(Calendar.AM_PM));
//0은 오전 1은 오후
System.out.println(birthday.get(Calendar.AM_PM)
== Calendar.AM ? "오전" : "오후");
// 오후 출력
System.out.println("hourOfDay : " + birthday.get(Calendar.HOUR_OF_DAY));
// 24시간 체계
System.out.println("hourOfDay : " + birthday.get(Calendar.HOUR));
// 12시간 체계
// hourOfDay : 16
// hourOfDay : 4
System.out.println("min : " + birthday.get(Calendar.MINUTE));
System.out.println("min : " + birthday.get(Calendar.SECOND));
// min : 42
// min : 0
}
}
제네릭
제네릭(Generic)이란?
데이터의 타입을 일반화 한다는 것을 의미
제네릭을 활용하면 타입 변환 및 타입 검사에 들어가는 코드 생략이 가능
타입을 특정화 하는게 아닌 일반화 해둔다
제네릭 클래스에 extends 키워드를 사용해 타입 제한 가능
GenericTest<T>
public class GenericTest<T> {
/* 제네릭 설정은 클래스 선언부 마지막 부분에 다이아몬드 연산자
* 를 이용하여 작성하게 된다. 다이아몬드 연산자 내부에 작성하는
* 영문자는 관례상 대문자로 작성한다.
* */
/* 다이아몬드 연산자 내부에 작성한 T는 타입변수라고 부른다.
* 타입 변수를 자료형 대신 사용할 것인데, 가상으로 존재하는 타입이며
* T가 아닌 영문자를 사용해도 무방하다.
* 또한 여러개의 타입변수를 작성할 때는 ,를 이용하여 여러개를
* 기술 할 수도 있다.
* 사용하는 쪽에서 작성한 제네릭 클래스를 이용할 시
* 실제 사용할 타입을 타입변수자리에 맞춰서 넣어주게 되면
* 컴파일 시점에서 타입이 결정되게 된다.
*
* */
/* 현재 해당 필드는 타입이 결정되지 않은 상태이다. */
private T value;
/* setter/getter 작성시에도 구체적인 타입 대신 T를 이용할 수 있다. */
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return this.value;
}
}
Application
public class Application {
public static void main(String[] args) {
/* 제네릭(generic) */
/* 제네릭의 사전적인 의미는 일반적인 이라는 의미이다.
* 자바에서 제네릭이란 데이터의 타입을 일반화한다는 의미를 가진다.
*
* 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에
* 지정하는 방법을 말한다.
* 컴파일시에 미리 타입 검사를 시행하게 되면 클래스나 메소드 내부에서 사용되는 객체의
* 타입안정성을 높일 수 있으며, (잘못된 타입인 경우 컴파일 에러를 발생시킴)
* 반환 값에 대한 타입 변환 및 타입 검사에 들어가는 코드 생략이 가능해진다.
* (instanceof비교 및 다운 캐스팅 작성 불필요)
*
*
* JDK 1.5 버전부터 추가된 문법이다.
*
* */
/* 타입을 Integer로 인스턴스를 생성하는 경우 */
GenericTest<Integer> gt1 = new GenericTest<Integer>();
/* 메소드의 인자 및 반환 값 모두 Integer 타입인 것을 알 수 있다. */
gt1.setValue(10);
System.out.println(gt1.getValue());
System.out.println(gt1.getValue() instanceof Integer);
// 10
// true
/* 타입을 String 으로 인스턴스를 생성하는 경우 */
GenericTest<String> gt2 = new GenericTest<String>();
gt2.setValue("홍길동");
System.out.println(gt2.getValue());
System.out.println(gt2.getValue() instanceof String);
/* JDK 7부터 타입 선언시 타입변수가 작성되면 타입 추론이 가능하기 때문에
* 생성자 쪽의 타입을 생략하고 사용할 수 있게 한다.
* */
GenericTest<Double> gt3 = new GenericTest<>();
// GenericTest<> 타입을 추론할 수 있는 기능 : 더블을 썼으니 더블일 것이다.
gt3.setValue(0.5);
System.out.println(gt3.getValue());
System.out.println(gt3.getValue() instanceof Double);
}
}
와일드카드
제네릭 클래스 타입의 객체를 메소드의 매개변수로 받을 때 그 객체의 타입을 제한 가능
<?> : 제한 없음
<? Extends Type> : 와일드카드의 상한 제한 (Type과 Type의 후손을 이용해 생성한 객체만 매개변수로 사용 가능)
<? super Type> : 와일드카드 하한 제한 (Type과 Type의 부모를 이용해 생성한 객체만 매개변수로 사용 가능)
//NewAvante이거나 그 후손 타입으로 만들어진 자동차만 매개변수로 사용 가능
//NewAvante이거나 그 부모 타입으로 만들어진 자동차만 매개변수로 사용 가능
Application1
import com.greedy.section02.extend.Bunny;
import com.greedy.section02.extend.DrunkenBunny;
import com.greedy.section02.extend.Rabbit;
import com.greedy.section02.extend.RabbitFarm;
public class Application1 {
public static void main(String[] args) {
/* 제네릭 클래스 작성 시 extends 키워드를 이용하면
* 특정 타입만 사용하도록 제한을 걸 수 있다. */
/* 토끼의 후손이거나 토끼인 경우에만 타입으로 사용 가능하다.
* 그 외의 타입으로 타입 지정 시 컴파일 단계에서 에러를 발생시킨다. */
/* Animal 타입으로는 제네릭 클래스 인스턴스 생성이 불가능하다. */
// RabbitFarm <Animal> farm1 = new RabbitFarm<> () ;
// 다른 패키지 이므로 임포트 필요
/* Mammal 타입으로는 제네릭 클래스 인스턴스 생성이 불가능하다. */
// RabbitFarm <Mammal> farm1 = new RabbitFarm<> () ;
/* 전혀 다른 타입인 Snake 타입으로는 제네릭 클래스 인스턴스 생성이 불가능하다. */
// RabbitFarm <Snake> farm1 = new RabbitFarm<> () ;
/* Rabbit 타입이나 Rabbit의 후손으로는 인스턴스 생성이 가능하다. */
RabbitFarm<Rabbit> farm1 = new RabbitFarm<> () ; // 임포트 필요
RabbitFarm<Bunny> farm2 = new RabbitFarm<> () ; // 임포트 필요
RabbitFarm<DrunkenBunny> farm3 = new RabbitFarm<> () ; // 임포트 필요
/* 제네릭을 사용해서 올바른 타입을 타입 변수로 지정하는 경우에는
* 인스턴스 내부에 있는 타입 자체가 Rabbit 타입을 가지고 있는 것이
* 보장되어 있기 때문에 형변환 생략이 가능하다.
* */
farm1.setAnimal(new Rabbit());
((Rabbit)farm1.getAnimal()).cry(); // 굳이 타입캐스팅하지않아도 된다.
farm1.getAnimal().cry();
// 토끼가 울음 소리를 냅니다. 끾끾!
farm2.setAnimal(new Bunny());
farm2.getAnimal().cry();
// 바니바니 바니바니 당근 당근
farm3.setAnimal(new DrunkenBunny());
farm3.getAnimal().cry();
// 봐니봐니 봐니봐니 당근! 당귿@!#$@
}
}
Application2
import com.greedy.section02.extend.Bunny;
import com.greedy.section02.extend.DrunkenBunny;
import com.greedy.section02.extend.Rabbit;
import com.greedy.section02.extend.RabbitFarm;
import com.greedy.section02.extend.WildCardFarm;
public class Application2 {
public static void main(String[] args) {
/* 와일드 카드(WildCard)
* 제네릭 클래스 타입의 객체를 메소드의 매개변수로 받을 때,
* 그 객체의 타입변수를 제한 할 수 있다.
*
*
* <?> : 제한 없음
* <? extends Type> : 와일드 카드의 상한 제한, 상속받은 클래스만 사용가능
* (Type 과 Type의 후손을 이용해 생성한 인스턴스만 인자로 사용가능)
* <? super Type> : 와일드 카드의 하한 제한, 상속하는 상위 클래스만 사용가능
* (Type 과 Type의 부모를 이용해 생성한 인스턴스만 인자로 사용가능)
*
* */
/* WildCardFarm 객체생성 */
WildCardFarm wildCardFarm = new WildCardFarm(); //임포트 필요
/* 농장 생성 자체가 불가능 한 것은 매개변수로 사용할 수 없다. */
// wildCardFarm.anyType(new RabbitFarm<Mammal>(new Mammal()));
/* 매개변수의 타입 제한이 없는 경우 */
wildCardFarm.anyType(new RabbitFarm<Rabbit>(new Rabbit()));
// 토끼가 울음 소리를 냅니다. 끾끾!
wildCardFarm.anyType(new RabbitFarm<Bunny>(new Bunny()));
// 바니바니 바니바니 당근 당근
wildCardFarm.anyType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
// 봐니봐니 봐니봐니 당근! 당귿@!#$@
/* Bunny 이거나 Bunny의 후손 토끼 농장만 매개변수로 사용이 가능한 경우 */
// wildCardFarm.extendsType(new RabbitFarm<Rabbit>(new Rabbit()));
// 에러 : in the type WildCardFarm is not applicable for the arguments (RabbitFarm<Rabbit>)
//Bunny 이거나 Bunny의 후손만 가능한데 Rabbit은 그 상위 타입이기 때문
wildCardFarm.extendsType(new RabbitFarm<Bunny>(new Bunny()));
// 바니바니 바니바니 당근 당근
wildCardFarm.extendsType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
// 봐니봐니 봐니봐니 당근! 당귿@!#$@
/* Bunny 이거나 Bunny의 상위 토끼 농장만 매개변수로 사용이 가능한 경우 */
wildCardFarm.superType(new RabbitFarm<Rabbit>(new Rabbit()));
wildCardFarm.superType(new RabbitFarm<Bunny>(new Bunny()));
// 바니바니 바니바니 당근 당근
// wildCardFarm.superType(new RabbitFarm<DrunkenBunny>(new DrunkenBunny()));
// 에러: in the type WildCardFarm is not applicable for the arguments (RabbitFarm<DrunkenBunny>)
// Bunny 이거나 Bunny의 부모만 가능한데 DrunkenRabbit은 그 하위 타입이기 때문
}
}
interface Animal
public interface Animal {
/* 해당 인터 페이스를 상속받는 Mammal(포유류) 클래스를 만들자 */
}
Bunny extends Rabbit
/* 바니도 한종류의 토끼이다. Rabbit을 상속받는다. */
public class Bunny extends Rabbit {
/* cry() 메소드를 오버라이딩하자 */
@Override
public void cry () {
System.out.println("바니바니 바니바니 당근 당근");
}
/* Bunny 를 상속 받는 DrunkenBunny를 만들자 */
}
DrunkenBunny extends Bunny
/* DrunkenBunny는 Bunny를 상속받는다. */
public class DrunkenBunny extends Bunny {
@Override
public void cry() {
System.out.println("봐니봐니 봐니봐니 당근! 당귿@!#$@");
}
/* 이제 제네릭을 이용한 클래스를 하나 만들자
* RabbitFarm (토끼 농장) */
}
Mammal implements Animal
/* 포유류도 동물이기 때문에 Animal 인터페이스를 상속받는다. */
public class Mammal implements Animal{
/* 파충류 클래스도 만든다. (Reptile) */
}
Rabbit extends Mammal
/* Mammal 클래스를 상속받는다. */
public class Rabbit extends Mammal {
public void cry() {
System.out.println("토끼가 울음 소리를 냅니다. 끾끾!");
}
/* Reptile 클래스를 상속받는 Snake 클래스를 만들자 */
}
RabbitFarm <T extends Rabbit>
/* 타입변수는 아직 어떤 토끼가 될 지 모른다.
* 단, 토끼이거나 토끼의 후손만 가능하다.
* */
private T animal ;
/* 생성자 */
public RabbitFarm() {}
public RabbitFarm (T animal) {
this.animal = animal;
}
/* 세터 게터 */
public void setAnimal (T animal) {
this.animal = animal;
}
public T getAnimal () {
return this.animal;
}
}
Reptile implements Animal
/* 파충류도 동물이기 때문에 Animal 인터페이스를 상속받는다. */
public class Reptile implements Animal{
/* Mammal 클래스를 상속받는 Rabbit 클래스를 만들자 */
}
Snake extends Reptile
/* Reptile을 상속받는다. */
public class Snake extends Reptile{
/* Rabbit 클래스를 상속받는 Bunny 클래스를 만들자. */
}
WildCardFarm
public class WildCardFarm {
/* 매개변수로 전달 받는 토끼농장을 구현할 때 사용한
* 타입변수에 대해 제한 할 수 있다.
*
* */
/* 토끼 농장에 있는 토끼가 어떤 토끼던 상관없다. */
public void anyType(RabbitFarm<?> farm) {
farm.getAnimal().cry();
}
/* 토끼 농장의 토끼는 Bunny 이거나 그 후손 타입으로 만들어진
* 토끼농장만 매개변수로 사용 가능 */
public void extendsType(RabbitFarm<? extends Bunny> farm) {
farm.getAnimal().cry();
} // bunny를 상속받은 친구만 가능하다는 뜻 // 상한을 둠(상위방향에 한계를 둠)
/* 토끼 농장의 토끼는 Bunny 이거나 그 부모 타입으로 만들어진
* 토끼농장만 매개변수로 사용 가능 */
public void superType(RabbitFarm<? super Bunny> farm) {
farm.getAnimal().cry();
} // bunny를 상속받은 친구만 가능하다는 뜻 // 하한을 둠 (아래방향에 한계를 둠)
}
응용 프로그램에서 사용할 수 있도록 운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스
이전에 배운 인터페이스와는 다른 개념이다.
API는 패키지 별로 묶여있다.
자바 API
자바 플랫폼 위에서 동작하는 애플리케이션 개발 시 활용 (유용한 클래스 및 인터페이스 제공)
JDK를 설치하면 시스템을 제어하거나 편의 기능을 위한 API를 제공
Object
모든 클래스는 Object 클래스의 후손
java.lang 패키지에 존재
Obejct 클래스가 가진 메소드 중 관례상 많이 오버라이딩 해서 사용하는 메소드들이 존재 ex) toString(), equals(), hashCode()
java.lang 패키지는 자동으로 임포트 되기 때문에 따로 임포트 해 줄 필요없다.
제공하는 메소드
toString( )
문자열로 변환 인스턴스 생성 시 사용한 full class name, @, 그리고 16진수 해쉬코드가 문자열로 반환
equals( )
매개변수로 전달 받은 인스턴스와 == 연산하여 true 또는 false를 반환 동일 인스턴스인지를 비교, '같은지'를 물어보는데 '주소값'을 기준으로 물어보는 것 (동일 객체 : 주소가 동일한 인스턴스) (동등 객체 : 주소가 다르더라도 필드 값이 동일한 인스턴스)
hashCode( )
인스턴스의 주소값을 변환하여 생성한 인스턴스의 고유 값을 반환
class Book
public class Book {
/* 필드생성 */
private int number;
private String title;
private String author;
private int price;
/* 기본생성자 */
public Book() {
super();
}
/* 매개변수 생성자 */
public Book(int number, String title, String author, int price) {
super();
this.number = number;
this.title = title;
this.author = author;
this.price = price;
}
/* 게터와 세터 */
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
/* 우클릭 - source - Generate toString() 생성 */
@Override
public String toString() {
return "Book [number=" + number + ", title=" + title + ", author=" + author + ", price=" + price + "]";
}
/* toString()에 대해서 오버라이딩 */
// @Override
// public String toString() {
// return "Book [number=" + this.number
// + ", title=" + this.title
// + ", author=" + this.author
// + ", price=" + this.price
// + "]";
// }
/* equals------------------------------------------------ */
/* equals()에 대해서 오버라이딩 */
@Override
public boolean equals(Object obj) {
/* 두 인스턴스의 주소가 같으면 이후 다른 내용을 비교할 것 없이
* true 반환, 즉 주소가 동일한 인스턴스(동일 객체)인지 확인 */
if (this == obj)
return true;
/* this는 인스턴스가 생성되면 주소값이 저장된다. null일 수 없다.
* 따라서 전달 받은 레퍼런스 변수에 null값이 저장 돼 있다면
* 비교하려는 두개의 인스턴스는 서로 다른 인스턴스이다.
* */
if (obj == null)
return false;
/* 동일한지의 여부를 묻고싶은 것이므로
* this와 obj의 클래스가 같지 않다면 필드값을 비교할 필요가 없다. */
if (getClass() != obj.getClass())
return false;
/* 전달 받은 레퍼런스 변수를 Book 타입으로
* (강제)형변환하여 각 필드별로 비교를 시작한다. */
Book other = (Book) obj;
/* String 타입의 경우 null여부 먼저 확인 */
if (author == null) {
if (other.author != null)
return false; // this상 null other는 not null이므로 false
/* this가 null이 아닌 경우 String 클래스의 equals를 호출해서 값 비교 */
} else if (!author.equals(other.author))
return false;
/* 숫자값은 비교적 쉽게 바로 값 비교 가능 */
if (number != other.number)
return false;
if (price != other.price)
return false;
/* title도 String 타입이므로 author와 동일 */
if (title == null) {
if (other.title != null)
return false; // null
} else if (!title.equals(other.title))
return false;
/* 모든 조건을 통과하면 두 인스턴스의 모든 필드는 같은 값을
* 가지므로 true 반환 */
return true;
}
/* 객체의 동등성을 비교하기 위해서 equals를 오버라이딩 한다. */
// 두 인스턴스의 == 연산비교 : false
// 두 인스턴스의 == 연산비교 : true
/* hashCode------------------------------------------------ */
@Override
public int hashCode() {
/* 필드마다 곱해줄 소수값 선언
* 31은 소수이기 때문에 연산 시 동일한 hashCode 값이 나오지않을 확률을
* 증가시킨다.
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
*
* 31이 통상 적인 관례이며 String 클래스의 hashCode에도 사용한 값이다.
* 아주 낮은 확률로 같은 수가 나오는 경우가 있을 수 있지만 소수값은
* 그 확률을 최소화 시킨다.
*
* */
/* 필드값이 같으면 동일 해쉬코드를 반환한다 : 를 확인하기 위한 목적 */
final int prime = 31;
/* prime(31)과의 곱셉 연산을 result에 누적시킨 값 선언 */
int result = 1;
/* String 클래스의 hashCode메소드는 이미 재정의 되어있다.
* 같은 값을 가지는 문자열은 동일한 hashCode값을 반환한다.
* */
result = prime * result + ((author == null) ? 0 : author.hashCode());
result = prime * result + number;
result = prime * result + price;
result = prime * result + ((title == null) ? 0 : title.hashCode());
/* 동등 객체는 동일한 hashCode값을 반환하게 된다. */
return result;
// book1의 hashCode : 2010084336
// book2의 hashCode : 2010084336
}
}
class Application1
import com.greedy.section01.object.book.Book;
public class Application1 {
public static void main(String[] args) {
Book book1 = new Book(1, "홍길동전", "허균", 50000);
Book book2 = new Book(2, "목민심서", "정약용", 30000);
Book book3 = new Book(3, "칭찬은 고래를 춤추게 한다", "고래", 40000);
System.out.println("book1.toString : " + book1.toString());
System.out.println("book2.toString : " + book2.toString());
System.out.println("book3.toString : " + book3.toString());
/* java.lang.Object 클래스의 toString()메소드는
* 인스턴스가 생성될 때 사용한 full class name과 @ 그리고 16진수 해쉬코드가
* 문자열로 반환된다. 16진수 해쉬코드는 인스턴스의 주소를 가리키는 값으로
* 인스턴스마다 모두 다르게 반환된다. */
// return getClass().getName() + "@" + Integer.toHexString(hashCode());
// book1.toString : com.greedy.section01.object.book.Book@4926097b
// book2.toString : com.greedy.section01.object.book.Book@39ed3c8d
// book3.toString : com.greedy.section01.object.book.Book@71dac704
/*(+)
* 레퍼런스 변수만 출력하는 경우도 동일한 결과가 나온다.
* 이 경우도 자동으로 toString()메소드를 호출한다.
* 이러한 점을 이용해서 toString() 메소드를 재정의해서 사용하게된다. */
System.out.println("book1 : " + book1);
System.out.println("book2 : " + book2);
System.out.println("book3 : " + book3);
// book1 : com.greedy.section01.object.book.Book@4926097b
// book2 : com.greedy.section01.object.book.Book@39ed3c8d
// book3 : com.greedy.section01.object.book.Book@71dac704
/* toString을 오버라이딩 한 결과 */
// book1 : Book [number=1, title=홍길동전, author=허균, price=50000]
// book2 : Book [number=2, title=목민심서, author=정약용, price=30000]
// book3 : Book [number=3, title=칭찬은 고래를 춤추게 한다, author=고래, price=40000]
/* 상속관계이므로 Object의 모든 기능을 book도 이용할 수 있다.
* 오버라이드시 후손의 메소드를 호출하는 것이다.
* toString을 자식클래스에서 재정의했으므로
* 부모클래스인 오브젝트의 toString 호출시 book에서 재정의한 결과가 나온다. */
}
}
class Application2
import com.greedy.section01.object.book.Book;
public class Application2 {
public static void main(String[] args) {
/* java.lang.Object 의 equals는 매개변수로 전달받은
* 인스턴스와 == 연산하여 true or false 를 반환한다.
* 즉, 동일한 인스턴스인지 비교하는 기능을 한다.
*
* 동일 객체와 동등객체
* 동일객체 : 주소가 동일한 인스턴스를 동일객체라고 한다.
* 동등객체 : 주소는 다르더라도 필드 값이 동일한 객체를 동등객체라고 한다.
*
* equals() 메소드의 기본 기능은 동일객체 판단을 한다고 볼 수 있다.(동일성 확인)
* 그러한 경우 equals()를 오버라이딩하여 각각의 필드가 동일한 값을 가지는기 확인하고
* 모든 필드값이 같은 값을 가지는 경우 true, 아닌 경우 false를 반환하도록 작성한다.
*
* */
Book book1 = new Book(1, "홍길동전", "허균", 50000);
Book book2 = new Book(1, "홍길동전", "허균", 50000);
System.out.println("두 인스턴스의 == 연산비교 : " + (book1 == book2));
System.out.println("두 인스턴스의 == 연산비교 : " + (book1.equals(book2)));
/* 동일객체 비교 */
// 두 인스턴스의 == 연산비교 : false
// 두 인스턴스의 == 연산비교 : false
/* 오버라이딩 후 동등객체 비교 */
// 두 인스턴스의 == 연산비교 : false
// 두 인스턴스의 == 연산비교 : true
}
}
class Application3
import com.greedy.section01.object.book.Book;
public class Application3 {
public static void main(String[] args) {
/* Object 클래스의 명세에 작성 된 일반 규약에 따르면
* equals() 메소드를 재 정의하는 경우 반드시 hashCode() 메소드도
* 재정의하게 되어 있다.(hashCode와 equals는 같이 간다고 생각)
* 만약 hashCode()를 재정의하지 않으면 같은 값을 가지는 동등 객체는
* 같은 해시코드 값을 가져와야 한다는 규약을 위반하게 된다.
* (강제성은 없지만 규약대로 작성하는 것이 좋음)
*/
/* 동등 객체 생성 후 hashCode를 출력하면
* java.lang.Object 는 인스턴스 주소이므로 다른값이 나온다.
* 오버라이딩해서 동등 객체의 경우 같은 hashCode 를 리턴하도록 한다. */
Book book1 = new Book(1, "홍길동전", "허균", 50000);
Book book2 = new Book(1, "홍길동전", "허균", 50000);
System.out.println("book1의 hashCode : " + book1.hashCode());
System.out.println("book2의 hashCode : " + book2.hashCode());
/* 출력시 필드값은 같은 동등객체임을 알 수 있다. 주소값은 다르다. */
// book1의 hashCode : 1227229563
// book2의 hashCode : 971848845
/* 동등객체 판단을 위한 오버라이딩 */
/* hashCode 오버라이딩 후 동등객체로 판단되어 같은 주소값을 반환한다. */
// book1의 hashCode : 2010084336
// book2의 hashCode : 2010084336
}
}
String
Java.lang 패키지에 존재하는 클래스로 문자열을 처리하는 여러가지 메소드를 제공
제공하는 메소드
charAt()
해당 문자열의 특정 인덱스에 해당하는 문자 반환
compareTo()
두 문자열이 같은지 다른지에 따라 int 값을 반환
compareToIgnoreCase()
대소문자를 구분하지 않고 비교 후 int 값을 반환
concat()
문자열에 인자로 전달 된 문자열을 합침
indexOf()
문자열을 앞에서부터 탐색하여 일치하는 위치의 인덱스를 int 값으로 반환
lastIndexOf()
문자열을 뒤에서부터 탐색하여 일치하는 위치의 인덱스를 int 값으로 반환
trim()
문자열의 앞 뒤 공백을 제거 후 문자열을 반환
toLowerCase()
모든 문자를 소문자로 변환 후 문자열을 반환
toUpperCase()
모든 문자를 대문자로 변환 후 문자열을 반환
Application1
public class Application1 {
public static void main(String[] args) {
/* String 클래스의 자주 사용되는 메소드 */
/* chatAt() : 해당 문자열의 특정 인덱스에 해당하는 문자를 반환한다.
* 인덱스는 0부터 시작하는 숫자 체계를 의미하며
* 인덱스를 벗어난 정수를 인자로 전달하는 경우에는 IndexOutOfBoundsException
* 이 발생한다. */
String str1 = "apple";
for (int i = 0; i < str1.length(); i++) {
System.out.println("charAt(" + i + ")" + str1.charAt(i));
}
// charAt(0)a
// charAt(1)p
// charAt(2)p
// charAt(3)l
// charAt(4)e
/* compareTo() : 인자로 전달된 문자열과 사전 순으로 비교를 하여
* 두 문자열이 같아면 0을 반환, 인자로 전달된 문자열보다 작으면 음수를,
* 크면 양수를 반환한다.
* 단, 이 메소드는 대소문자를 구분하여 비교한다.
* */
String str2 = "java";
String str3 = "java";
String str4 = "JAVA";
String str5 = "oracle";
/* 같으면 0을 반환한다. */
System.out.println("compareTo() : " + (str2.compareTo(str3)));
// compareTo() : 0
/* 대문자와 소문자는 32만큼 차이가 난다. */
System.out.println("compareTo() : " + (str2.compareTo(str4)));
System.out.println("compareTo() : " + (str4.compareTo(str2)));
// compareTo() : 32
// compareTo() : -32
/* j부터 o까지 5만큼 차이가 난다. */
System.out.println("compareTo() : " + (str2.compareTo(str5)));
System.out.println("compareTo() : " + (str5.compareTo(str2)));
// compareTo() : -5
// compareTo() : 5
/* 목적 : 음수가 나오는지 양수가 나오는지를
* 파악하여 어느값이 앞 뒤에있는지 판단하기 위함
* 즉, 구체적으로 어떤 값이 나오는지보다 양수인지 음수인지를
* 판단할 목적으로 주로 사용된다.
* */
/* 대소문자를 구분하지않고 비교한다. */
/* 대소문자를 비교하는 메소드 : compareToIgnoreCase() */
System.out.println(str3.compareToIgnoreCase(str4));
// 0, 출력값 0, 즉 대소문자만 다른 java가 같은 값이란 얘기이다.
/* concat() : 문자열에 인자로 전달 된 문자열을 합치기
* 해서 새로운 문자열을 반환한다.
* 단 합쳐진 이후, 원본 문자열에는 영향을 주지 않는다.
* */
System.out.println("concat() : " + (str2.concat(str5)));
System.out.println("str2 : " + str2);
// concat() : javaoracle
// str2 : java
/* indexOf() : 문자열에서 특정 문자를 탐색하여 처음일치하는
* 인덱스 위치를 정수형으로 반환한다.
* 단, 일치하는 문자가 없는 경우 -1을 반환한다.
* */
String indexOf = "java oracle";
System.out.println("indexOf('a') : " + indexOf.indexOf('a'));
System.out.println("indexOf('z') : " + indexOf.indexOf('z'));
// indexOf('a') : 1
// indexOf('z') : -1 // 인덱스로 존재할 수 없는 정수값
/* lastIndexOf() : 문자열 탐색을 뒤에서부터 하고
* 처음 일치하는 위치의 인덱스를 반환한다.
* 단, 일치하는 문자가 없는 경우 -1을 반환한다.
* */
System.out.println("lastIndexOf('a') : " + indexOf.lastIndexOf('a'));
System.out.println("lastIndexOf('z') : " + indexOf.lastIndexOf('z'));
// lastIndexOf('a') : 7
// lastIndexOf('z') : -1
/* trim() : 문자열 앞 뒤에 공백을 제거한 문자열을 반환한다. */
String trimStr = " java ";
System.out.println("trimStr : #" + trimStr + "#");
System.out.println("trim() : #" + trimStr.trim() + "#");
/* 원본에 영향을 주지는 않는다. */
System.out.println("trimStr : #" + trimStr + "#");
// trimStr : # java #
// trim() : #java#
// trimStr : # java #
/* toLowerCase() : 모든 문자를 소문자로 변환시킨다.
* toUpperCase() : 모든 문자를 대문자로 변환시킨다.
* 원본에는 영향을 주지 않는다.
* */
String caseStr = "JavaOracle";
System.out.println("toLowerCase() : " + caseStr.toLowerCase());
System.out.println("toUpperCase() : " + caseStr.toUpperCase());
System.out.println("caseStr : " + caseStr);
// toLowerCase() : javaoracle
// toUpperCase() : JAVAORACLE
// caseStr : JavaOracle
/* substring() : 문자열의 일부를 잘라내어 새로운 문자열을 반환한다.
* 원본에는 영향을 주지 않는다.
* */
String javaoracle = "javaoracle";
System.out.println("substring(3,6) : " + javaoracle.substring(3,6));
System.out.println("substring(3) : " + javaoracle.substring(3));
System.out.println("javaoracle : " + javaoracle);
// substring(3,6) : aor // 인덱스 3,4,5 까지 추출한다는 의미
// substring(3) : aoracle // 시작인덱스 3부터 문자열의 끝까지 추출
// javaoracle : javaoracle
/* replace() : 문자열에서 대체할 문자열로 기존 문자열을 변경해서 반환한다.
* 원본에는 영향을 주지 않는다. */
System.out.println("replace() : " + javaoracle.replace("java", "python"));
System.out.println("javaoracle : " + javaoracle);
// replace() : pythonoracle // 자바를 파이썬으로 변경한다는 의미
// javaoracle : javaoracle
/* length() : 문자열의 길이를 정수형으로 반환한다. */
System.out.println("length() : " + javaoracle.length());
System.out.println("빈문자열길이 : " + ("".length()));
// length() : 10
// 빈문자열길이 : 0
/* isEmpty() : 문자열의 길이가 0이면 true를 반환, 아니면 false를 반환
* 길이가 0인 문자열은 null과는 다르다. */
System.out.println("isEmpty() : " + "".isEmpty());
System.out.println("isEmpty() : " + "abc".isEmpty());
// isEmpty() : true
// isEmpty() : false
}
}
Application2
public class Application2 {
public static void main(String[] args) {
/* 문자열 객체 사용하는 방법 */
/* "" 리터럴 형태 : 동일한 값을 가지는 인스턴스를
* 단일 인스턴스로 관리한다. (singleton)
* 즉, 같은 문자열에 대해선 더이상 인스턴스를 생성하지않는다.
*
* new String("문자열") : 매번 새로운 인스턴스를 생성한다.
*
* 즉 같은 리터럴이어도 생성 방식이 다르다.
* */
String str1 = "java";
String str2 = "java";
String str3 = new String("java");
String str4 = new String("java");
/* 리터럴 형대로 만든 문자열 인스턴스는 동일한 값을 가지는 인스턴스는 하나의
* 인스턴스로 관리한다.
* 따라서 주소값을 비교하는 == 연산으로 비교시 서로 동일한 주소를 비교하여
* true를 반환한다. */
System.out.println(" str1 == str2 " + ( str1 == str2 ));
// 리터럴이 같은경우 더이상 인스턴스를 생성하지않는다. 주소값이 같다고 간주한다.
/* new로 새로운 인스턴스를 생성하게 되면 기존 인스턴스를 두고 새로운 인스턴스를
* 할당했기 때문에 == 연산으로 비교 시 서로 다른 주소값을 가지게 되므로
* false 를 반환한다. */
System.out.println(" str2 == str3 " + ( str2 == str3 ));
/* 동일한 방식으로 인스턴스를 생성하고 값 또한 같더라도
* 새로운 인스턴스를 생성하는 방식은 서로 다른 주소를 가지기 때문에
* false를 반환한다. */
System.out.println(" str3 == str4 " + ( str3 == str4 ));
// str1 == str2 true
// str2 == str3 false
// str3 == str4 false
/* 하지만 4개의 문자열은 모두 동일한 hashCode 값을 가진다.
* 동일한 문자열은 동일한 hashCode값을 반환하도록 재정의 되어있기 때문이다. */
System.out.println("str1의 hashCode : " + str1.hashCode());
System.out.println("str2의 hashCode : " + str1.hashCode());
System.out.println("str3의 hashCode : " + str1.hashCode());
System.out.println("str4의 hashCode : " + str1.hashCode());
// str1의 hashCode : 3254818
// str2의 hashCode : 3254818
// str3의 hashCode : 3254818
// str4의 hashCode : 3254818
/* 실제 인스턴스는 다르지만 같은 값을 가지면 동일하다고 간주하도록
* 재정의가 되어있다. 같은 문자열일 경우 동일한 객체를 참조한다. */
/* 문자열은 불변이라는 특징을 가진다.
* 기존 문자열에 + 연산자를 수행하는 경우 문자열을 수정하는 것이 아닌
* 새로운 문자열을 할당하게 된다.
* 한번 할당된 문자열은 더이상의 변경이나 수정이 불가능하며
* 새로 힙 영역에 만들어 참조하는 수 밖에 없다.
* */
str2 += "oracle";
/* str 과 str2는 동일한 인스턴스였지만 수정 후에는 다른 인스턴스가
* 된 것을 알 수 있다. */
System.out.println("str == str2 : " + (str1 == str2));
// str == str2 : false
/* equals() : String 클래스의 equals메소드는 인스턴스 비교(주소값)가 아닌
* 문자열값을 비교하여 동일한 값을 가지는 경우 true, 다른 값을 가지는 경우
* false를 반환하도록 Object의 equals() 메소드를 재정의 해 두었다.
* 주소값 비교가 아닌 문자열 비교를 위해선 equals()를 써야한다.
* 인스턴스 생성방식과는 상관없이 문자열만을 비교한다.
* */
System.out.println("str1.equals(str3) : " + str1.equals(str3));
System.out.println("str1.equals(str4) : " + str1.equals(str4));
// str1.equals(str3) : true
// str1.equals(str4) : true
/* 참고로 Scanner 의nextLine() 등을 이용해 문자열을 읽어온 경우
* substring으로 잘라내기 해서 새로운 문자열을 생성 후 반환하기 때문에
* new String() 으로 인스턴스를 생성한 것과 동일한 것으로 볼 수 있다.
* 따라서 Scanner로 입력받은 문자열을 비교할 때에는 equals()를 써야한다.
* 구분하기 힘들면 그냥 문자열은 다 equals()로 비교하는 것이 가장 좋은 방법이다.
* */
}
}
import java.util.StringTokenizer;
public class Application3 {
public static void main(String[] args) {
/* split()과 StringTokenizer */
/* 각 문자열의 의미는 사번/이름/주소/부서 를 의미한다. */
String emp1 = "100/홍길동/서울/영업부"; // 모든 값 존재
String emp2 = "200/유관순//총무부"; // 주소 없음
String emp3 = "300/이순신/경기도/"; // 부서 없음
/* 먼저 split을 이용해서 3명의 문자열을 정보별로 분리한다. */
String[] empArr1 = emp1.split("/");
String[] empArr2 = emp2.split("/");
String[] empArr3 = emp3.split("/");
for (int i = 0; i < empArr1.length; i++) {
System.out.println("empArr1 [" + i + "] : " + empArr1[i]);
} //정상출력
for (int i = 0; i < empArr2.length; i++) {
System.out.println("empArr2 [" + i + "] : " + empArr2[i]);
} // 중간 값 빈 문자열
for (int i = 0; i < empArr3.length; i++) {
System.out.println("empArr3 [" + i + "] : " + empArr3[i]);
} // 마지막 값 출력 안됨
// empArr1 [0] : 100
// empArr1 [1] : 홍길동
// empArr1 [2] : 서울
// empArr1 [3] : 영업부
// empArr2 [0] : 200
// empArr2 [1] : 유관순
// empArr2 [2] : // 슬래쉬가 두번 들어가서 비워둔 부분
// empArr2 [3] : 총무부
// empArr3 [0] : 300
// empArr3 [1] : 이순신
// empArr3 [2] : 경기도
/* 한계점 : 값을 비워두자 인덱스가 하나 적게 출력 */
/* 마지막 구분자 사이에 값이 존재하지 않는 경우 이후 값도 추출하고 싶을때
* 몇 개의 토큰으로 분리 할 것인지 한계치를 두번째 인자로 넣어줄 수 있다.
* 이때 음수를 넣게 되면 마지막 구분자 뒤의 값이 존재하지 않는 경우
* 빈 문자열로 토큰을 생성한다.
* */
String[] empArr4 = emp3.split("/", -1);
// 뒤의 리밋 숫자대로 '줄 수'로 출력 돼 나온다.
for (int i = 0; i < empArr4.length; i++) {
System.out.println("empArr4 [" + i + "] : " + empArr4[i]);
}
/* 반면 StringTokenizer의 경우 공백으로 존재하는 값을 무시해버린다. */
StringTokenizer st1 = new StringTokenizer(emp1, "/");
StringTokenizer st2 = new StringTokenizer(emp2, "/");
StringTokenizer st3 = new StringTokenizer(emp3, "/");
// java.util 에 있으며 임포트 해준다.
while (st1.hasMoreTokens()) {
System.out.println("st1 : " + st1.nextToken());
}
while (st2.hasMoreTokens()) {
System.out.println("st2 : " + st2.nextToken());
}
while (st3.hasMoreTokens()) {
System.out.println("st3 : " + st3.nextToken());
}
/* nextToken()으로 토큰을 꺼내면 해당 StringTokenizer의
* 토큰을 재사용하는 것이 불가능하다. */
while (st1.hasMoreTokens()) {
System.out.println("st1 : " + st1.nextToken());
}
/* nextToken으로 인해 토큰은 다음 커서로 이동하고
이전 토큰은 출력된다.
*/
/* 공백에 대해서는, 중복된 반복문은 무시된 결과를 확인할 수 있다. */
// st1 : 100
// st1 : 홍길동
// st1 : 서울
// st1 : 영업부
// st2 : 200
// st2 : 유관순
// st2 : 총무부
// st3 : 300
// st3 : 이순신
// st3 : 경기도
/* split()과 StringTokenizer의 차이점 */
/* split은 정규표현식 이용(문자열 패턴),
* StringTokenizer는 구분자 문자열 이용 */
String colorStr = "red*orange#blue/yellow green";
/* "*#/ " 이라는 문자열이 구분자로 존재하지 않아서 에러 발생함 */
// String[] colors = colorStr.split("*#/ ");
/* 대괄호로 묶은 문자열은 문자열이 아닌 각 문자들의 패턴으로 볼 수 있다.
* 따라서 순서 상관없이 존재하는 문자들을 이용해서 구분자로 사용할 수 있다.
* */
String[] colors = colorStr.split("[*#/ ]");
for (int i = 0; i < colors.length; i++) {
System.out.println("colors[" + i + "] : " + colors[i]);
}
// colors[0] : red
// colors[1] : orange
// colors[2] : blue
// colors[3] : yellow
// colors[4] : green
/* StringTokenizer의 두번째 인자 문자열 자체는 연속 된 문자열이
* 아닌 하나하나를 구분자로 이용하겠다는 의미이다. */
StringTokenizer colorStringTokenizer = new StringTokenizer(colorStr, "*#/ ");
while (colorStringTokenizer.hasMoreTokens()) {
System.out.println(colorStringTokenizer.nextToken());
}
// red
// orange
// blue
// yellow
// green
}
}
이스케이프(escape) 문자
문자열 내에서 사용하는 문자 중 특수문자를 표현하거나 특수기능을 사용할 때 사용하는 문자
특수문자
문자 리터럴
비고
tab
\t
정해진 공간만큼 띄어쓰기
new line
\n
출력하고 다음 라인으로 옮김
역슬래시
\\
특수문자 사용지 백슬래시넣고 특수문자를 넣어야함
작은 따옴표
\'
큰 따옴표
\"
유니코드
\u
유니코드 표시할 때 사용
Application4
public class Application4 {
public static void main(String[] args) {
/* 개행문자 */
System.out.println("안녕하세요. \n저는 홍길동입니다.");
// 안녕하세요.
// 저는 홍길동입니다.
/* 탭문자 */
System.out.println("안녕하세요. \t저는 홍길동입니다.");
// 안녕하세요. 저는 홍길동입니다.
/* 홑따옴표 문자 */
System.out.println("안녕하세요. 저는 '홍길동'입니다."); // 이스케이스 쓰지않아도 됨
// System.out.println('''); // 홑따옴표 문자와 문자리터럴 기호가 중복됨
System.out.println('\''); // 이스케이프 문자를 사용해서 구분해 주어야함
// 안녕하세요. 저는 '홍길동'입니다.
// '
/* 쌍따옴표 문자 */
//System.out.println("안녕하세요. 저는 "홍길동"입니다.");
// 문자열 리터럴과 기호가 중복 됨
System.out.println("안녕하세요. 저는 \"홍길동\"입니다.");
// 안녕하세요. 저는 "홍길동"입니다.
/* 역슬래쉬 문자 사용 */
System.out.println("안녕하세요. 저는 \\홍길동\\입니다.");
// 역슬래쉬를 하나만 작성할 시 이스케이프 문자의 예약문자와 겹친다.
// 안녕하세요. 저는 \홍길동\입니다.
/* 이러한 특수문자나 특수한 기능을 표현하는 문자를 이스케이프 문자라고 한다. */
/* split시 이스케이프 문자를 사용해야하는 특수한 문자도 존재한다. */
/* 이스케이프 문자 사용 안하는 특수문자
* ~ ` ! @ # & % - _ = ; : ' \ " , < > /
*
* 이스케이프 문자 사용 하는 특수문자
* $ ^ * ( ) + | { } [ ] . ?
*
* */
String str = "java$oracle$jdbc";
String[] sarr = str.split("\\$");
for (String s : sarr) {
System.out.println(s);
}
//split("$") 사용시 다음과같이 출력된다. java/oracle/jdbc
// split("\\$") 사용시 정상적으로 잘 구분되어 출력된다.
// java
// oracle
// jdbc
}
}
StringBuilder와 StringBuffer
StringBuilder와 StringBuffer란?
String과 유사하지만 String이 불변이라면 StringBuilder나 StringBuffer는 가변이라는 차이점이 있다
StringBuilder와 StringBuffer는 기능적으론 거의 유사하다.
StringBuilder : 스레드 동기화 기능을 제공하지 않음
StringBuffer : 스레드 동기화 기능 제공 (스레드 동기화 유무의 차이 말고는 두 클래스가 의미하는 바가 동일함)
제공하는 메소드
capacity()
용량(현재 버퍼의 크기)을 int 값으로 반환 (문자열 길이 + 16이 기본 용량)
append()
인자로 전달 된 값을 문자열로 변환 후 기존 문자열의 마지막에 추가
delete()
시작 인덱스와 종료 인덱스를 이용해서 문자열에서 원하는 부분의 문자열 제거
insert()
인자로 전달된 값을 문자열로 변환 후 지정한 인덱스 위치에 추가
reverse()
문자열 인덱스 순번을 역순으로 재배열
Application1
public class Application1 {
public static void main(String[] args) {
/* String과 StringBuilder */
/* String :
* 불변이라는 특성을 가지고 있다.
* 문자열에 + 연산으로 합치기 하는 경우, 기존 인스턴스를 수정하는 것이 아닌
* 새로운 인스턴스를 반환한다.
* 따라서 문자열 변경이 자주 일어나는 경우 성능 면에서 좋지않다.
* (매번 새로운 인스턴스를 만들어야 하기 때문)
* 하지만 변하지 않는 문자열을 자주 읽어들이는 경우에는 좋은 성능을 기대할 수 있다.
*
* StringBuilder :
* 가변이라는 특성을 가지고 있다.
* 문자열에 append() 메소드를 이용하여 합치기 하는 경우
* 기존 인스턴스를 수정하기 때문에 새로운 인스턴스를 생성하지 않는다.
* 따라서 잦은 문자열 변경이 일어나는 경우 String보다 성능이 높다.
* (새로 만드는게 아닌 기존것을 수정하기 때문)
*
* 단, JKD 1.5 버전부터 문자열의 + 연산이 StringBuilder의 append()로 컴파일 된다.
* 따라서 성능에 큰 차이를 보이지는 않는다.
* 하지만 반복문 에서 문자열의 + 연산을 수행하는 경우
* StringBuilder 인스턴스를 반복루프시 루프 마다 생성하기 때문에
* 역시 성능에는 좋지않은 영향을 준다.
*
*
* */
/* 원본 코드 */
// String str1 = "java";
// String str2 = "oracle";
//
// String str3 = str1 + str2;
// String str4 = "";
//
// for (int i = 0; i < 10; i++) {
// str4 += str1;
// }
/* JKD 1.4이하
* - 새로운 인스턴스를 매번 생성해서 할당 */
/* JKD 1.5이상 */
// String str1 = "java";
// String str2 = "oracle";
//
// String str3 = new StringBuilder(str1).append(str2).toString();
// String str4 = "";
//
//
// for (int i = 0; i < 10; i++) {
// str4 += new StringBuilder(str4).append(str1).toString();
// }
//
/* StringBuilder 인스턴스 생성 */
StringBuilder sb = new StringBuilder("java");
/* toString이 오버라이딩 되어 있다. */
System.out.println("sb : " + sb/*.toString()*/ );
System.out.println("sb의 hashCode : " + sb.hashCode() );
/* hashCode는 오버라이딩 되어 있지 않다.
* 즉, 동일한 값을 가지는 경우 같은 해쉬코드를 반환하는 것이 아닌,
* 인스턴스가 동일해야 같은 해쉬코드를 반환한다.
* */
// sb : java
// sb의 hashCode : 1101288798
/* 문자열 수정 */
sb.append("oracle");
System.out.println("sb : " + sb);
System.out.println("sb의 hashCode : " + sb.hashCode() );
/* hashCode를 다시 출력하면 기존 인스턴스와 동일한 것을 확인할 수 있다.
* 즉, 문자열을 변경했다고 해서 새로운 인스턴스가 생성된 것은 아니다. */
// sb : javaoracle
// sb의 hashCode : 1101288798
// 주소값이 똑같다는 의미? : 문자열을 추가했지만 새로운인스턴스가 생기지 않았다는 것
}
}
Application2
public class Application2 {
public static void main(String[] args) {
/* StringBuilder의 자주 사용되는 메소드 */
/* StringBuilder 기본 생성자로 인스턴스 생성 */
StringBuilder sb1 = new StringBuilder();
/* capacity() : 용량 (현재 버퍼의 크기)을 정수형으로
* 반환하는 메소드 (문자열 길이 + 16이 기본 용량) */
System.out.println(sb1.capacity());
// 예) 인자값으로 java 전달시 20 출력
/* append() : 인자로 전달 된 값을 문자열로 변환 후
* 기존 문자열의 마지막에 추가한다.
* 기본 용량을 초과하는 경우 :
* (기존 문자열 +1 ) * 2를 하여 용량을 확장 시킨다.
* */
for (int i = 0; i < 50; i++) {
sb1.append(i);
System.out.println("sb1 : " + sb1);
System.out.println("capacity : " + sb1.capacity());
System.out.println("hashCode : " + sb1.hashCode());
// 출력시 동일 인스턴스임을 알 수 있다.
// 스트링빌더는 처음부터 끝까지
// '동일한 인스턴스 안에서' 변화하고 있음을 관찰할 수 있다.
}
/* 새로운 StringBuilder 인스턴스 생성 */
StringBuilder sb2 = new StringBuilder("javaoracle");
/* delete() : 시작인덱스와 종료인덱스를 이용해서 문자열에서 원하는
* 부분의 문자'열'을 제거한다.
* deleteCharAt : 문자열 인덱스를 이용해서 문자 딱 하나를(at) 제거한다.
* 둘 다 원본에 영향을 미친다.
*
* */
System.out.println("delete() : " + sb2.delete(2, 5));
System.out.println("delete() : " + sb2.deleteCharAt(0));
System.out.println("sb2 : " + sb2);
// delete() : jaracle
// delete() : aracle
/* insert() : 인자로 전달 된 값을 문자열로 변환 후 지정한
* 인덱스 위치에 추가한다.
* 원본에 영향을 미친다.
* */
System.out.println("insert() : " + sb2.insert(1, "vao"));
// 인덱스 1자리에 넣겠다는 의미
// insert() : avaoracle
System.out.println("insert() : " + sb2.insert(0, "j"));
// insert() : javaoracle
System.out.println("insert() : " + sb2.insert(sb2.length(), "jdbc"));
// insert() : javaoraclejdbc
System.out.println("sb2 : " + sb2);
// sb2 : javaoraclejdbc
/* reverse() : 문자열 인덱스 순번을 역순으로 재배열한다.
* 원본에 영향을 미친다.
* */
System.out.println("reverse() : " + sb2.reverse());
System.out.println("sb2 : " + sb2);
// reverse() : cbdjelcaroavaj
// sb2 : cbdjelcaroavaj
/* String 클래스와 동일한 메소드도 있다.
* charAt(), indexOf(), lastIndexOf(), length(), replace(),
* substring(),
* */
}
}
Wrapper
Primitive Data Type을 인스턴스화 해주는 클래스
Primitive Data Type
Wrapper Class
boolean
Boolean
byte
Byte
char
Character
short
Short
int
Integer
long
Long
float
Float
double
Double
Wrapper : String을 기본자료형으로 바꿀 시
byte b = Byte.parseByte("1");
short s = Short.parseShort("2");
int i = Integer.parseInt("3");
long l = Long.parseLong("4");
float f = Float.parseFloat("0.1");
double d = Double.parseDouble("0.2");
boolean bool = Boolean.parseBoolean("true");
char c = "abc".charAt(0);
Wrapper : 기본자료형을 String으로 바꿀 시
String b = Byte.valueOf((byte)1).toString();
String s = Short.valueOf((short)2).toString();
String i = Integer.valueOf(3).toString();
String l = Long.valueOf(4L).toString();
String f = Float.valueOf(0.1f).toString();
String d = Double.valueOf(0.2).toString();
String bool = Boolean.valueOf(true).toString();
String ch = Character.valueOf('a').toString();
상위(부모) 타입으로 하위(자식)타입의 객체를 사용할 수 있음. Parent p = new Child( );
다형성의 특징
여러 타입의 객체를 하나의 타입으로 관리할 수 있으므로 유지보수성과 생산성이 증가 됨
상속 관계에 있는 모든 객체는 동일한 메시지(=메소드)를 수신할 수 있음
확장성이 좋은 코드 작성 가능
결합도를 낮춰 유지보수성을 증가시킬 수 있음
동적바인딩
동적바인딩이란?
컴파일 당시에는 해당 타입의 메소드와 연결되어 있다가, 런타임 당시 실제 객체가 가진 오버라이딩 된 메소드로 바인딩이바뀌어 동작하는 것을 의미
동적바인딩 성립 조건
상속 관계로 이루어져 다형성이 적용된 경우, 메소드 오버라이딩이 되어 있어야 함
Instanceof 연산자
Instanceof 연산자란?
현재 레퍼런스 변수가 어떤 클래스 타입의 객체 주소를 참조하고 있는지를 확인할 때 사용 클래스 타입이 맞으면 true, 맞지 않으면 false를 반환
if (레퍼런스instanceof클래스타입) {
true일때 처리할 내용, 해당 클래스 타입으로 down casting
클래스 형변환
클래스 형변환은 업캐스팅과 다운캐스팅으로 구분할 수 있음
업캐스팅(up-casting)이란?
상위(부모) 타입으로 형변환하는 것, 자동형변환, 묵시적 형변환이라고 한다.
묵시적 형변환 : up-casting의 경우 묵시적 형변환이 적용 됨 (코드구현시 형변환 생략 가능)
다운캐스팅(down-casting)
하위(자식) 타입으로 형변환, 강제형변환, 명시적 형변환이라고 한다.
메소드를 호출하기전 먼저 형변환이 이루어 져야 하기 때문에 소괄호를 매소드 호출 이전에 둘어줘야 한다.
코드예시 : class Application
public class Application {
public static void main(String[] args) {
/* 다형성
* 다형성이란 [하나의 인스턴스가 여러가지 타입을 가질 수 있는 것]을 의미한다.
* 그렇기 때문에 하나의 타입으로 여러타입의 인스턴스를 처리할 수 있기도 하고
* 하나의 메소드 호출로 객체별로 각각 다른 방법으로 동작하게 할 수도 있다.
*
* */
System.out.println("Animal 생성--------------------");
Animal animal = new Animal();
animal.eat();
animal.run();
animal.cry();
// Animal 생성--------------------
// 동물이 먹이를 먹습니다.
// 동물이 달려갑니다.
// 동물이 울음소리를 냅니다.
System.out.println("Rabbit 생성--------------------");
Rabbit rabbit = new Rabbit();
rabbit.eat();
rabbit.run();
rabbit.cry();
rabbit.jump();
// Rabbit 생성--------------------
// 토끼가 풀을 뜯어먹고 있습니다.
// 토끼가 달려갑니다. 깡총~ 깡총~
// 토끼가 울음소리를 냅니다. 끼익~ 끼익~
// 토끼가 점프합니다. 점프!!!
System.out.println("Tiger 생성--------------------");
Tiger tiger = new Tiger();
tiger.eat();
tiger.run();
tiger.cry();
tiger.bite();
// Tiger 생성--------------------
// 호랑이가 고기를 뜯어 먹습니다.
// 호랑이는 웬만해서 달리지 않습니다. 어슬렁~ 어슬렁~
// 호랑이가 울부짖습니다. 어흐응~!!!!
// 호랑이가 물어뜯습니다. 앙~
/* Rabbit은 Rabbit타입이기도 하면서 Animal타입이기도 함
* Tiger은 Tiger타입이기도 하면서 Animal타입이기도 함
* 자식객체를 부모타입으로 참조할 수 있다.
* */
Animal a1 = new Rabbit();
Animal a2 = new Tiger();
/* 하지만 반대로 Animal은 Animal일뿐 Rabbit, Tiger가 될 수 없다.
* 그래서 반대로 작성할 시 에러가 발생한다. */
// Rabbit r = new Animal();
// Tiger t = new Animal();
System.out.println("동적 바인딩 확인 --------------------");
/* 컴파일 당시에는 해당 타입의 메소드와 연결되어 있다가 (애니멀)
* 런타임 당시 실제 객체가 가진 오버라이딩 된 메소드로 (토끼, 호랑이)
* 바인딩이 바뀌어 동작한다. */
a1.cry(); //Animal.cry로 연결되어 있다. "동물이 울음소리를 냅니다."
a2.cry(); //Animal.cry로 연결되어 있다. "동물이 울음소리를 냅니다."
// 동적 바인딩 확인 --------------------
// 토끼가 울음소리를 냅니다. 끼익~ 끼익~ // Rabbit.cry로 연결돼 있다.
// 호랑이가 울부짖습니다. 어흐응~!!!! // Tiger.cry로 연결돼 있다.
/* 하나의 메소드 호출로 각기 다른 객체의 다른 메소드를 동작시키게 한다. */
/* 현재 레퍼런스 변수의 타입은 Animal 이기 때문에
* Rabbit과 Tiger가 가지고있는 고유한 기능을 동작시키지 못한다.
* */
// a1.jump(); //The method jump() is undefined for the type Animal
// a2.bite(); //The method jump() is undefined for the type Animal
System.out.println("클래스 형변환 확인 --------------------");
/* 객체별로 고유한 기능을 동작시키기 위해서는 레퍼런스 변수를 형변환하여
* Rabbit과 Tiger로 변경해야 메소드 호출이 가능하다.
* class type casting : 클래스 형변환
* 타입 형변환시 실제 인스턴스와 타입이 일치하지 않는 경우에는
* ClassCastException이 발생할 수 있다.
*
* */
((Rabbit)a1).jump();
// 타입캐스팅이 먼저일어난 후 그 안의 기능을 수행해야하기 때문에
// 소괄호를 먼저 적용한 후 .로 메소드를 호출해야 한다.
((Tiger)a2).bite();
// 클래스 형변환 확인 --------------------
// 토끼가 점프합니다. 점프!!!
// 호랑이가 물어뜯습니다. 앙~
/* 타입 형변환을 잘못하는 경우
* 컴파일시에는 문제가 되지 않지만 런타임시 Exception이 발생한다. */
// ((Tiger)a1).bite();
// 토끼 인스턴스(a1)는 호랑이(Tiger)가 될 수 없다.
// 에러이유: java.lang.ClassCastException 발생
/* 레퍼런스 변수가 참조하는 실제 인스턴스가 원하는 타입과 맞는지
* 비교하는 연산자 instanceof */
System.out.println("instanceof 확인 --------------------");
System.out.println("a1이 Tiger타입인지 확인 : " + (a1 instanceof Tiger));
System.out.println("a1이 Rabbit타입인지 확인 : " + (a1 instanceof Rabbit));
System.out.println("a2이 Tiger타입인지 확인 : " + (a2 instanceof Tiger));
System.out.println("a2이 Rabbit타입인지 확인 : " + (a2 instanceof Rabbit));
// instanceof 확인 --------------------
// a1이 Tiger타입인지 확인 : false
// a1이 Rabbit타입인지 확인 : true
// a2이 Tiger타입인지 확인 : true
// a2이 Rabbit타입인지 확인 : false
/* 상속받은 타입도 함께 가지고 있다. (다형성) */
System.out.println("a1이 Animal타입인지 확인 : " + (a1 instanceof Animal));
System.out.println("a2이 Animal타입인지 확인 : " + (a2 instanceof Animal));
// a1이 Animal타입인지 확인 : true
// a2이 Animal타입인지 확인 : true
/* 모든 클래스는 Object의 후손이다. */
System.out.println("a1이 Object타입인지 확인 : " + (a1 instanceof Object));
System.out.println("a2이 Object타입인지 확인 : " + (a2 instanceof Object));
// a1이 Object타입인지 확인 : true
// a2이 Object타입인지 확인 : true
/* instanceof연산자를 이용해서 해당 타입이 맞는 경우에만 클래서 형변환을 적용한다. */
if (a1 instanceof Rabbit) {
((Rabbit)a1).jump();
}
if (a2 instanceof Tiger) {
((Tiger)a2).bite();
}
/* 클래스 형 변환은 up-casting과 down-casting으로 구분할 수 있다.
* up-casting : 상위 타입으로 형 변환
* down-casting : 하위 타입으로 형 변환
*
*
* */
/* 묵시적 형 변환 */
/* up-casting의 경우 묵시적 형변환이 적용된다. */
Animal animal1 = (Animal) new Rabbit(); // up-casting 명시적 형변환
Animal animal2 = new Rabbit(); // up-casting 묵시적 형변환
Rabbit rabbit1 = (Rabbit) animal1; // down-casting 명시적 형변환
// Rabbit rabbit2 = animal2; // down-casting 묵시적 형변환 불가
/* 효과 : 코드를 통일시키고 animal이라는 같은 방식으로
* rabbit과 tiger를 다뤄줄 수 있다.
* 반대의 경우에는 형변환을 이용하면 되며,
* 형변환에는 업캐스팅과 다운캐스팅이 있고
* 업캐스팅은 명시, 묵시적 형변환이 가능하고
* 다운 캐스팅은 명시적형변환만이 가능하다.
* 형변환을 확인하는 연산자로는 instanceof 메소드가 있다.
*/
}
}
class Animal
public class Animal {
/* 동물은 기본적으로 먹는 행동과 뛰는 행동, 우는 행동을 할 수 있다. */
public void eat() {
System.out.println("동물이 먹이를 먹습니다.");
}
public void run() {
System.out.println("동물이 달려갑니다.");
}
public void cry () {
System.out.println("동물이 울음소리를 냅니다.");
}
}
class Rabbit extends Animal
public class Rabbit extends Animal {
/* 동물이 하는 행동을 조금 구체화 하여 행동할 수 있도록
* Rabbit 클래스에 Animal클래스의 메소드를 오버라이딩 한다. */
@Override
public void eat() {
System.out.println("토끼가 풀을 뜯어먹고 있습니다.");
}
@Override
public void run() {
System.out.println("토끼가 달려갑니다. 깡총~ 깡총~");
}
@Override
public void cry() {
System.out.println("토끼가 울음소리를 냅니다. 끼익~ 끼익~ ");
}
/* 추가적으로 토끼는 점프를 뛸 수 있다. */
public void jump () {
System.out.println("토끼가 점프합니다. 점프!!!");
}
}
class Tiger extends Animal
public class Tiger extends Animal {
@Override
public void eat() {
System.out.println("호랑이가 고기를 뜯어 먹습니다.");
}
@Override
public void run() {
System.out.println("호랑이는 웬만해서 달리지 않습니다. 어슬렁~ 어슬렁~");
}
@Override
public void cry() {
System.out.println("호랑이가 울부짖습니다. 어흐응~!!!! ");
}
/* 호랑이는 추가적으로 물어뜯기를 할 수 있다. */
public void bite() {
System.out.println("호랑이가 물어뜯습니다. 앙~");
}
}
다형성과 객체배열
다형성과 객체배열을 이용하면 여러 인스턴스를 하나의 레퍼런스 변수로 연속 처리할 수 있음
car[i]는 동적바인딩이 되어 자식객체들의 메소드가 실행된다.
Application2
public class Application2 {
public static void main(String[] args) {
/* 다형성과 객체배열을 이용해서 여러 인스턴스를 연속 처리할 수 있다. */
/* 상위 타입의 '레퍼런스 배열'을 만들고 각 인덱스에 인스턴스들을 생성해서 대입한다. */
Animal[] animals = new Animal[5]; // 레퍼런스배열
animals[0] = new Rabbit();
animals[1] = new Tiger();
animals[2] = new Rabbit();
animals[3] = new Tiger();
animals[4] = new Rabbit();
/* Animal클래스가 가지는 메소드를 오버라이딩한 메소드 호출시 동적바인딩을 이용할 수 있다. */
for (int i = 0; i<animals.length; i++) {
animals[i].cry();
// 컴파일시에는 animal (ctrl+annimal클릭) 정적 바인딩 되어있음
// 실제 실행시에는 동적바인딩이 되어 출력된다.
// 토끼가 울음소리를 냅니다. 끼익~ 끼익~
// 호랑이가 울부짖습니다. 어흐응~!!!!
// 토끼가 울음소리를 냅니다. 끼익~ 끼익~
// 호랑이가 울부짖습니다. 어흐응~!!!!
// 토끼가 울음소리를 냅니다. 끼익~ 끼익~
}
/* 각 클래스별 고유한 메소드를 동작시키기 위해서는 down-casting을
* 명시적으로 해주어야 하는데 에러를 방지하기 위해서 instanceof 연산자를 이용해야 한다.
* */
for (int i = 0; i<animals.length; i++) {
if (animals[i] instanceof Rabbit) {
((Rabbit)animals[i]).jump();
} else if (animals[i] instanceof Tiger) {
((Tiger)animals[i]).bite();
} else {
System.out.println("호랑이나 토끼가 아닙니다.");
}
}
// 토끼가 점프합니다. 점프!!!
// 호랑이가 물어뜯습니다. 앙~
// 토끼가 점프합니다. 점프!!!
// 호랑이가 물어뜯습니다. 앙~
// 토끼가 점프합니다. 점프!!!
}
}
매개변수에 쓰이는 다형성
묵시적 형변환을 이용해서 매개변수가 다양한 자식클래스 자료형을 받아줄 수 있음
코드를 훨씬 줄일 수 있다.
* buy 메소드에 생성자를 매개변수로 보낸다는 것
=자식인스턴스를 부모 레퍼런스로 참조
* 자식인스턴스(new Sonata)를 부모메소드의 매개변수(Car c)로 넘김
= 매개변수의 다형성
Car c = new Sonata( );
Car c = new Avante( );
Car c = new Grandure( );
class Application3
public class Application3 {
public static void main(String[] args) {
/* 매개변수에도 다형성을 활용할 수 있다. */
/* 아래에 메소드 하나를 추가한다. */
Application3 app3 = new Application3();
/* animal 타입의 토끼와 호랑이 인스턴스를 만들어서 먹이를 준다. */
Animal animal1 = new Rabbit(); // 동일한 타입이기 때문에 가능함
Animal animal2 = new Tiger();
app3.feed(animal1);
app3.feed(animal2);
// Application3의 feed 메소드를 호출하며 Rabbit/tiger 의 참조변수 전달
// 토끼가 풀을 뜯어먹고 있습니다.
// 호랑이가 고기를 뜯어 먹습니다.
/* Rabbit/Tiger 타입의 토끼와 호랑이 인스턴스를 만들어서 먹이를 준다. */
Rabbit animal3 = new Rabbit();
Tiger animal4 = new Tiger();
app3.feed((Animal)animal3); // 명시적 형변환
app3.feed((Animal)animal4); // 명시적 형변환
app3.feed(animal3); // 묵시적 형변환
app3.feed(animal4); // 묵시적 형변환
// 토끼가 풀을 뜯어먹고 있습니다.
// 호랑이가 고기를 뜯어 먹습니다.
// 토끼가 풀을 뜯어먹고 있습니다.
// 호랑이가 고기를 뜯어 먹습니다.
/* 인스턴스를 생성하여 바로 묵시적으로 형변환을 이용해 전달할 수도 있다. */
app3.feed(new Rabbit()); //new Rabbit() = animal3
app3.feed(new Tiger()); //new Tiger() =animal4
/* 다형성을 적용하지 않았다면 호랑이에게 먹이를 주는 메소드와 토끼에게 먹이를 주는
* 메소드를 별도로 작성해야 했을 것이다. */
}
/**
* <pre>
* 동물에게 먹이를 주기 위한 용도의 메소드
* </pre>
*
* @param animal 먹이를 줄 동물
*
* */
public void feed(Animal animal) {
animal.eat();
}
}
리턴 타입에 쓰이는 다형성
묵시적 형변환을 이용해서 메소드에서 리턴되는 다양한 자식클래스 자료형을 받아줄 수 있음
Application4
public class Application4 {
public static void main(String[] args) {
/* 리턴타입에도 다형성을 적용할 수 있다. */
Application4 app4 = new Application4();
// static 메소드를 만들지 않았으므로 객체 생성
Animal randomAnimal = app4.getRandomAnimal();
// getRandomAnimal 메소드를 호출하며 그것을 Animal타입변수에 담음
// 왜? getRandomAnimal()에서 반환되는 것은 Animal(부모타입) 을 상속한
// Rabbit() or Tiger() (자식타입) 이기 때문 // 묵시적 형변환 발생
randomAnimal.cry();
// Animal 타입에 담긴 randomAnimal 속
// randomAnimal = Rabbit or Tiger 자식타입 클래스 내 cry 메소드 호출
// 동적 바인딩되어 Animal 의 cry는 출력되지 않음
/* 다형성을 적용하지않고 변환 받으려면 호랑이를 리턴받는 메소드와
* 토끼를 리턴받는 메소드를 따로 만들어야 한다. */
}
public Animal getRandomAnimal() {
int random = (int) (Math.random() * 2);
//0또는 1만을 반환, Math.random은 double 타입이므로 강제형변환
return random == 0 ? new Rabbit() : new Tiger();
// 랜덤값이 0이면 토끼를, 1이면 호랑이를 리턴값 Animal 타입으로 반환
}
}
추상클래스
추상메소드를 0개 이상 포함하는 클래스
추상메소드를 0개 이상 포함하는 클래스 = 미완성된 클래스
추상클래스로는 인스턴스를 생성할 수 없음
추상클래스를 사용하려면 추상클래스를 상속받은 하위 클래스를 이용해서 인스턴스를 생성 (다형성을 활용)
추상클래스의 사용 목적
추상클래스의 추상메소드는 오버라이딩에 대한 강제성이 부여 됨
원하는 필수 기능을 정의하여 강제성을 부여해 개발 시 일관된 인터페이스를 제공
즉, 추상클래스의 목적은 인스턴스 생성이아닌 가이드라인을 제공하는데에 있음
Animal animal = new Animal( ); (X) Animal a1 = new Tiger( ); (O) Animal a2 = new Rabbit( ); (O)
추상메소드
메소드의 선언부만 있고 구현부가 없는 메소드
반드시 예약어 자리에 abstract 키워드를 메소드 헤드에 작성 하여야 함
접근제한자와 반환형 사이에 abstract를 넣는다.
추상메소드를 0개 이상 포함해야한다 -> 0개 이상의 의미
보통은 추상메소드를 1개라도 포함하면 추상클래스라도 명시해줘야하지만, 때로는 추상메소드가 0개 이더라도 가이드라인이 목적이라면 abstract을 의도적으로 붙여줄 수도 있다. abstract class (추상클래스)
class Application
public class Application {
public static void main(String[] args) {
/* 추상클래스는 인스턴스 생성이 불가능하다. */
// Product product = new Product();
// 추상클래스인 Product는 객체를 생성할 수 없다.
/* 추상클래스를 상속받은 객체를 이용해야 한다.. */
SmartPhone smartPhone = new SmartPhone();
/* SmartPhonedms SmartPhone 타입이기도 하지만 Product타입이기도하다. */
System.out.println(smartPhone instanceof SmartPhone); // true
System.out.println(smartPhone instanceof Product); // true
/* 따라서 다형성을 적용해서 추상클래스를 레퍼런스 타입으로 활용할 수 있다. */
Product product = new SmartPhone();
// 객체생성은 안되지만 자식객체가 참조하는 변수는 생성이 가능하다.
/* 작성한 메소드 세가지 모두 호출 */
/* 동적 바인딩에 의해 SmartPhone의 메소드가 호출된다. */
product.absMethod();
// Product 클래스의 absMethod를 오버라이딩한 메소드 호출함...
/* 추상 클래스가 가진 메소드도 호출할 수 있다. */
product.nonStaticMethod();
/* static 메소드는 그냥 사용이 가능하다. (인스턴스 생성불필요) */
// 클래스명.메소드명
Product.staticMethod();
/* 추상클래스를 사용하는 이유? 오버라이딩에대한 강제화, 필수기능 강제구현
* 추상클래스의 추상 메소드는 "오버라이딩에 대한 강제성"이 부여된다.
* 따라서 여러 클래스들을 그룹화하여
* 가져야만하는 필수기능을 정의하여 -> 강제성을 부여해
* 개발 시 일관된 인터페이스(GUI)를 제공할 수 있다.
* 이 뒤에 배우는 인터페이스와는 다른 의미이다 (주의)
*
* 기능을 이용할 수 있는 방법이자 통로 : 인터페이스
* GUI : Graphic User Interface
* - 사용자에게 편리를 주기위해 그래픽으로 작성된 인터페이스
* CLI : cmd를 떠올리면 된다.
*
*
* 하지만 다른 클래스를 상속받고있는 클래스를 작성할 시에는
* 추상클래스를 추가로 상속받을 수 없다.
* 그래서 추상클래스보다 더 강제성이 강한 인터페이스(interface)라는
* 매커니즘을 제공하고 있다.
*
* */
}
}
abstract class Product
public abstract class Product {
/* 추상클래스는 필드를 가질 수 있다. */
/* 필드선언 */
private int nonStaticField;
private static int staticField;
/* 추상클래스는 생성자도 가질 수 있다.
* 하지만 직접적으로 인스턴스를 생성할 수는 없다. */
public Product() {}
/* 추상클래스는 일반적인 메소드를 가질 수 있다. */
public void nonStaticMethod( ) {
System.out.println("Product 클래스의 nonStaticMethod 호출함...");
}
public static void staticMethod( ) {
System.out.println("Product 클래스의 staticMethod 호출함...");
}
/* 미완성 클래스인 이유 : 미완성 메소드를 가지고있기 때문 */
/* -> 나를 상속해서 완성해주세요 */
/* 추가적으로 미완성 메소드(추상메소드) 또한 만들 수 있다.
* 추상 메소드가 0개인 경우 선택적으로 클래스에 abstract 키워드
* 작성하여 인스턴스 생성을 막을 수 있다.
* */
public abstract void absMethod();
// 접근제한자 abstract 반환 메소드명 {} 구현부없음
// 추상메소드가 하나라도 있다면 클래스앞엔 반드시 abstract을 붙여줘야한다.
// 그렇지 않으면 에러가 발생
}
class SmartPhone extends Product
public class SmartPhone extends Product {
// 컴파일 에러 구문 :
// The type SmartPhone must implement the inherited
// abstract method Product.absMethod()
// 해결방법 : 미완성된 추상메소드 구현
/* SmartPhone 클래스는 Product 클래스를 상속받아서 구현한다.
* 추상클래스가 가지는 추상메소드를 반드시 오버라이딩 해야한다. (강제성 부여)
* extends 키워드로 클래스를 상속 받을 때 두 개 이상의 클래스는 상속하지
* 못한다. (모호성 방지)
* */
@Override
public void absMethod() {
System.out.println("Product 클래스의 absMethod를 오버라이딩한 메소드 호출함...");
}
/* 추가적으로 메소드도 작성할 수 있다. */
public void printSmartPhone() {
System.out.println("SmartPhone 클래스의 printSmartPhone 메소드 호출함...");
}
}
인터페이스
인터페이스란?
추상메소드와 상수 필드만 가질 수 있는 클래스의 변형체
바꿔말하면 일반적인 메소드와 일반적인 필드를 가질 수 없다.
사용 목적
1. 표준화 : 추상클래스와 비슷하게 필요한 기능을 공통화 해서 강제성을 부여할 목적 2. 다중 상속 : 자바의 단일상속의 단점을 극복
인터페이스는 클래스 다이어그램에서 점선으로 표현된다. (상속관계는 실선)
추상클래스와 인터페이스
- 단, 인터페이스와 인터페이스 간의 상속에 있어서는 extends 키워드를 쓴다. - 추상 클래스는 명시적으로 abstract를 작성해 줘야하지만 인터페이스는 작성하지않아도 자동으로 추가된다. - 둘 다 참조타입으로서 다형성의 이점들을 활용 해 볼 수 있다.
Application
public class Application {
public static void main(String[] args) {
/* 인터페이스
* 여기서 다루게 되는 인터페이스는 자바의 클래스와 유사한 형태지만
* 추상 메소드와 상수필드만 가질 수 있는 클래스의 변형체를 말한다.
* 추상클래스보다 더 강한 강제성을 지닌다.
* */
/* 인스턴스를 생성하지 못하고, 생성자 자체가 존재하지 않는다. */
// InterProduct interProduct = new InterProduct();
/* 레퍼런스 타입으로만 사용이 가능하다. */
interProduct InterProduct = new Product();
/* 인터페이스의 추상메소드 오버라이딩한 메소드로 동적 바인딩에 의해 호출됨 */
InterProduct.nonStaticMethod();
// 정적으로는 interProduct의 추상메소드로 연결된다.
InterProduct.absMethod();
// 정적으로는 interProduct의 추상메소드로 연결된다.
// 동적바인딩으로 인해 Product의 추상메소드로 연결된다.
// InterProduct의 nonStaticMethod 오버라이딩한 메소드 호출됨...
// InterProduct의 absMethod 오버라이딩한 메소드 호출됨...
/* 오버라이딩 하지 않으면 인터페이스의 default 메소드로 호출됨 */
InterProduct.dafaultMethod();
// 정적으로는 interProduct의 추상메소드로 연결된다.
/* static 메소드는 인터페이스명.메소드명();으로 호출한다. */
interProduct.staticMethod();
/* 상수 필드(public static final) 접근도
* 인터페이스명.필드명으로 접근한다. */
System.out.println(InterProduct.MAX_NUM);
System.out.println(InterProduct.MIN_NUM);
}
}
public interface interProduct extends java.io.Serializable /*, java.util.Comparator*/ {
/* 인터페이스 생성 -> NEW -> interface */
/* 인터페이스 끼리 상속 받을 때에는 extends 키워드를 이용하며
* 이때도 여러 인터페이스를 다중상속 받을 수 있다. */
/* 인터페이스는 일반 필드는 가질 수 없으나 상수필드는 가질 수 있다.
* public static final 제어자 조합을 상수필드라고 부른다.
* 반드시 선언과 동시에 초기화 해줘야 한다.
*
* */
public static final int MAX_NUM = 100;
/* 상수필드만을 가질 수 있기 때문에 모든 필드는 묵시적으로
* public static final이다. 즉, 써주지 않아도 자동으로 처리된다.
* */
int MIN_NUM = 10;
// = public static final int MIN_NUM = 10;
/* 인터페이스는 생성자를 가질 수 없다. */
// public InterProduct() {}
/* 인터페이스는 구현부( {} )가 있는 non-static 메소드를 가질 수 없다.
* (추상 메소드는 구현부가 없어야 한다.)
* */
// public void nonStaticMethod(){}
/* 추상메소드만 작성이 가능하다. */
public abstract void nonStaticMethod();
/* 인터페이스 안에 작성한 메소드는 묵시적으로
* public abstract 의 의미를 가진다.
* 따라서 인터페이스의 메소드를 오버라이딩 하는 경우
* 반드시 접근 제한자를 public 으로 해야 오버라이딩이 가능하다.
* 인터페이스는 내용을 정의하는게 아니라 규칙을 정하는 공간이므로
* 접근이 자유로워야 한다.
* */
void absMethod();
// = public abstract void absMethod();
/* 하지만 static 메소드는 작성이 가능하다. (jdk 1.8에 추가된 기능)
* static 메소드는 정적인 메모리 영역에 할당되는 것이기 때문에
* 객체가 될 수 없는 인터페이스가 객체가 되어야하는 상황과 아무런
* 관련이 없다.
* */
public static void staticMethod() {
System.out.println("InterProduct 클래스의 staticMethod 호출됨...");
}
/* 또한 default 키워드를 사용하면 non static method 메소드도
* 작성이 가능하다. (jdk 1.8에 추가된 기능)
* 원론적으로 인터페이스는 몸체없는 추상메소드만 작성하기 위한공간이다.
* static 메소드와 default 메소드는 예외적인 부분이 첨가된 것이다.
* 예외가 필요한 이유는 자바 API에서 다룰 것이다.
* */
public default void dafaultMethod() {
System.out.println("InterProduct 클래스의 dafaultMethod 호출됨...");
}
}
class Product extends java.lang.Object implements interProduct, java.io.Serializable
public class Product extends java.lang.Object implements interProduct, java.io.Serializable {
/* InterProduct cannot be resolved to a type
*
* 클래스에서 인터페이스를 상속 받을 때에는 implements 키워드를 사용한다.
* - extends는 클래스를 상속 받을 때 사용 하였고
* - 인터페이스끼리 상속 받을 때에는 extends 키워드를 이용했다.
*
* 또한 인터페이스를 여러개를 상속받을 수 있으며,
* extends로 다른 클래스를 상속 받는 경우에도 그것과 별개로 인터페이스도
* 추가상속이 가능하다.
* 단, extends 키워드를 앞에 작성하고 implements를 뒤에 작성한다.
*
* */
/* InterProduct를 상속받으면 오버라이딩 해야 하는 메소드의 강제성이 부여되기 때문에
* 인터페이스에 작성한 추상 메소드를 전부 오버라이딩 해야한다. */
@Override
public void absMethod() {
System.out.println("InterProduct의 absMethod 오버라이딩한 메소드 호출됨...");
}
@Override
public void nonStaticMethod() {
System.out.println("InterProduct의 nonStaticMethod 오버라이딩한 메소드 호출됨...");
}
/* 스태틱 메소드는 오버라이딩 할 수 없다.
* 인터페이스 안에서 구현 완료했기 때문 */
// @Override
// public static void staticMethod() {
// System.out.println("InterProduct 클래스의 staticMethod 호출됨...");
//
// }
/* Default메소드는 인터페이스 안에서만 작성 가능하다. */
// @Override
// public default void dafaultMethod() {
// System.out.println("InterProduct 클래스의 dafaultMethod 호출됨...");
//
// } // Default methods are allowed only in interfaces.
/* 하지만 위의 메소드에서 Default 키워드를 제외하면 오버라이딩 가능하다. */
public void dafaultMethod() {
System.out.println("InterProduct 클래스의 dafaultMethod() 호출함");
}
}
Circle is a Shape. -> Circle 클래스는 하나의(일종의) Shape 클래스이다.
자식클래스는 일종의/하나의 부모클래스이다. -> 재사용성이 늘어나며 상속의 단점으로 여겨지는 부분이 상쇄될 수 있음
class Application
public class Application {
public static void main(String[] args) {
Car car = new Car();
car.soundHorn();
car.run();
car.soundHorn();
car.stop();
car.soundHorn();
// Car 클래스의 기본 생성자 호출됨...
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 자동차가 달립니다.
// 빵!빵!
// 자동차가 멈춥니다.
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
/* 소방차, 레이싱 카는 모두 자동차이다.
* 하지만 모든 자동차는 소방차 혹은 레이싱카가 아니다.
*
* FireCar is a Car(o) / Car is a FireCar(x)
* RacingCar is a Car(o) / Car is a RacingCar(x)
*
* 이러한 관계가 성립되는 것을 IS-A관계라고 한다.
* IS-A 관계가 성립이 되는 경우에 상속을 사용할 수 있다.
*
* */
FireCar fireCar = new FireCar();
/* 정말 car의 기능들을 쓸 수 있는 지 확인 */
fireCar.soundHorn();
fireCar.run();
fireCar.soundHorn();
fireCar.stop();
fireCar.soundHorn();
// Car 클래스의 기본 생성자 호출됨...
// FireCar 클래스의 기본 생성자 호출됨...
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 자동차가 달립니다.
// 빵!빵!
// 자동차가 멈춥니다.
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
/* FireCar 클래스에는 아무것도 작성하지 않았지만 Car 클래스가 가진 메소드를
* 전부 사용할 수 있다. 하지만 private 멤버는 접근이 불가능하다.
* 아래 예시는 private이라 접근 불가
* */
// fireCar.runningStatus;
// fireCar.isRunning();
// 오버라이딩 이후 콘솔 출력
// Car 클래스의 기본 생성자 호출됨...
// FireCar 클래스의 기본 생성자 호출됨...
// 소방차가 앞으로 갈 수 없습니다~ 비키세요~
// 자동차가 달립니다.
// 빠아아아앙ㅇ~~!!! 빠앙ㅇ아아아아앙~~~!!!!!!
// 자동차가 멈춥니다.
// 소방차가 앞으로 갈 수 없습니다~ 비키세요~
fireCar.sprayWater();
// car.sprayWater();
// fireCar에만 있기 때문에 부모클래스에서는 사용할 수 없는 기능이다.
/* 이렇게 상속은 부모가 가진 멤버를 사용하면서 확장까지 가능하도록 해 두었다.
* car인스턴스로는 fireCar 가 가진 기능을 사용할 수 없다.
* 자식은 부모 멤버에 접근해서 자신의 것처럼 사용 가능하지만, 반대의 경우는 허용하지 않는다.
* */
RacingCar racingCar = new RacingCar();
racingCar.soundHorn();
racingCar.run();
racingCar.soundHorn();
racingCar.stop();
racingCar.soundHorn();
// Car 클래스의 기본 생성자 호출됨... // 컴파일러가 묵시적으로 호출
// RacingCar 클래스 기본 생성자 호출됨...
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 자동차가 달립니다.
// 빵!빵!
// 자동차가 멈춥니다.
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 부모클래스의 기본 생성자는 자식 클래스의 메소드 생성시 가장먼저 호출이 된다.
// 명시적으로 쓰지않더라도 컴파일러가 묵시적/자동적으로 추가하기 때문에
// 자동으로 출력된다.
}
}
class Car
public class Car {
/* 자동차의 달리는 상태를 확인할 수 있는 필드 */
private boolean runningStatus;
/* 기본생성자 */
public Car() {
System.out.println("Car 클래스의 기본 생성자 호출됨...");
}
/* 자동차의 기본적인 달리는 기능 */
public void run() {
runningStatus = true;
System.out.println("자동차가 달립니다.");
}
/* 자동차의 기본적인 경적을 울리는 기능 */
public void soundHorn() {
/* 자동차는 주행 중인 상태일 때 기본적인 경적을 울리는 기능을 수행할 수 있다. */
if (isRunning()) {
System.out.println("빵!빵!");
} else {
System.out.println("주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.");
}
}
//private boolean isRunning() {
protected boolean isRunning() {
/*자동차의 주행중 상태를 반환하는 기능을 수행*/
return runningStatus;
}
/* 자동차의 기본적인 멈추는 기능 */
public void stop() {
runningStatus = false;
System.out.println("자동차가 멈춥니다.");
}
}
class FireCar extends Car
public class FireCar extends Car{
/* Car 클래스를 부모클래스로 FireCar 클래스를 자식 클래스로 사용할 것이다.
* 클래스 선언부에 extends Car 를 추가해서 Car 클래스를 상속 받을 것이다.
* public class FireCar extends Car
*
* 1. 그러면 FireCar 클래스는 부모클래스인 Car가 가지고 있는 모든 멤버를 상속 받는다.
* 2. 하지만 부모클래스의 생성자는 상속받지 못한다.
* 3. 또한 부모가 가지고있는 private 멤버는 접근이 불가능하다.
*
* 자바에서 상속은 기본적으로 부모가 가진 멤버를 자식이 사용할 수 있는 것이지만
* 더 나아가서 부모클래스의 확장(extend)이라는 개념을 가진다.
* 추가적인 자신의 멤버 작성이 가능하며 메소드 재정의(overriding)라는 기술을 이용해서
* 부모가 가진 메소드를 재정의 하는 것도 가능하다.
*
* */
/* 기본 생성자 */
public FireCar() {
/* 모든 생성자에는 맨 첫줄에 super() 클래스를 컴파일러가 자동추가한다.
* 부모의 기본 생성자를 호출하는 구문이다.
* 해당 생성자가 호출 될 시 가장 먼저 Car 클래스 호출 내용이 출력 될 것이다.
* 명시적, 묵시적(안써도 컴파일러가 자동추가) 전부 가능하다. */
super();
System.out.println("FireCar 클래스의 기본 생성자 호출됨...");
}
/* 오버라이딩 */
/* 소방차의 경적 소리가 너무 작으므로 조금 더 크게 키워보자
* 경적 소리를 내는 용도의 메소드 선언부는 그대로 두되 소방차 쪽에서 재정의 해 볼 것이다. */
/* @Override 어노테이션
* JDK 1.5 부터 추가된 문법으로 오버라이딩 성립 요건을 체크하여 성립되지않는 경우
* 컴파일 에러를 발생시킨다. (오버라이딩이 정상적으로 작성된 것인지를 체크해주는 기능)
* 에러 : 철자가 틀리는 등
* */
@Override //부모가 가진 기능을 자식이 고쳐서 사용하고 싶을때 재정의하는 것
public void soundHorn() {
/* 부모가 가지고 있는 멤버인 isRunning() 메소드를 사용해야
* 주행중인 상태를 확인할 수 있으므로 메소드 접근 제한자를
* private -> protected 로 변경*/
if(isRunning()) {
System.out.println("빠아아아앙ㅇ~~!!! 빠앙ㅇ아아아아앙~~~!!!!!!");
} else {
System.out.println("소방차가 앞으로 갈 수 없습니다~ 비키세요~");
}
}
/* 부모클래스에는 없지만 자식클래스에는 있는 기능 추가
* 물을 뿌리는 기능을 제공 */
public void sprayWater() {
System.out.println("불난 곳을 발견했습니다. 물을 뿌립니다 =======>>>");
}
}
class RacingCar extends Car
public class RacingCar extends Car {
/* 기본 생성자 */
public RacingCar() {
System.out.println("RacingCar 클래스 기본 생성자 호출됨...");
}
/* 부모로부터 한꺼번에 상속받은 기능이지만
* 레이싱카의 적합하지않은 기능인 경적소리, 달리는 기능등을 수정하고싶다. */
@Override
public void soundHorn() {
/* 레이싱카는 경적을 울리지 않는다. */
System.out.println("레이싱 카는 경적을 울리지 않습니다. (조용...)");
}
@Override
public void run() {
System.out.println("레이싱카가 전속력으로 질주합니다!!!!!!!!!!");
}
// 오버라이딩 후 결과값 출력
// Car 클래스의 기본 생성자 호출됨...
// RacingCar 클래스 기본 생성자 호출됨...
// 레이싱 카는 경적을 울리지 않습니다. (조용...)
// 레이싱카가 전속력으로 질주합니다!!!!!!!!!!
// 레이싱 카는 경적을 울리지 않습니다. (조용...)
// 자동차가 멈춥니다.
// 레이싱 카는 경적을 울리지 않습니다. (조용...)
}
super와 super()
super
super( )와 소괄호가 안붙었다는 차이점이있으므로 구분해서 기억해야한다.
부모클래스의 인스턴스 주소를 보관하는 레퍼런스 변수
자식클래스 내의 모든 생성자와 메소드 내에서 부모클래스의 레퍼런스 변수를 사용할 수 있음
super( )
부모 생성자를 호출하는 구문으로 매개변수의 타입, 개수, 순서가 일치하는 부모의 생성자를 호출하게 됨
this( )는 해당 클래스의 생성자를 호출하는 구문이다.
super( )는 부모클래스가 가지는 private 생성자를 제외한 나머지 생성자를 호출하는 구문이다.
class Application
public class Application {
public static void main(String[] args) {
/* ProductDTO 기본 생성자로 인스턴스 생성 후 정보 출력 */
ProductDTO product1 = new ProductDTO();
System.out.println(product1.getInformation());
/* ProductDTO의 모든 필드를 초기화하는 생성자로 인스턴스 생성 후 정보 출력 */
ProductDTO product2 = new ProductDTO("S-01234", "삼성", "갤럭시Z폴드1", 2398000, new java.util.Date());
System.out.println(product2.getInformation());
/* ComputerDTO 기본 생성자로 인스턴스 생성 후 정보 출력 */
ComputerDTO computer1 = new ComputerDTO();
System.out.println(computer1.getInformation());
/* ComputerDTO의 모든 필드를 초기화하는 생성자로 인스턴스 생성 후 정보 출력 */
ComputerDTO computer2 = new ComputerDTO("퀼컴 스냅드래곤", 512, 12, "안드로이드");
System.out.println(computer2.getInformation());
/* ComputerDTO의 부모 필드로 함께 초기화하는 생성자로 인스턴스 생성 후 정보 출력 */
ComputerDTO computer3 = new ComputerDTO("S-01234", "삼성", "갤럭시Z폴드1", 2398000, new java.util.Date(),
"퀼컴 스냅드래곤", 512, 12, "안드로이드");
System.out.println(computer3.getInformation());
/* 항상 super()로 부모생성자를 먼저 호출해야 한다. */
}
}
class ProductDTO
import java.util.Date;
public class ProductDTO {
/* 관리하려는 상품의 속성을 추상화해서 DTO 클래스를 만든다. */
private String code; // 상품코드
private String brand; // 제조사
private String name; // 상품명
private int price; // 가격
private java.util.Date manufacturingDate; // 제조일자
/* 기본 생성자 */
public ProductDTO() {
System.out.println("ProductDTO 클래스의 기본 생성자 호출함...");
}
/* 모든 필드를 초기화 하는 생성자 */
public ProductDTO(String code, String brand, String name, int price, Date manufacturingDate) {
/* 아무 클래스도 상속받지 않았는데 super() 가 호출되는 이유
* java.lang.Object 클래스의 생성자가 호출 된 것이다.
* 모든 클래스는 Object 클래스의 후손이기 때문이다. */
super();
// extends 를 선언하지않았는데 생겼다. 무엇을 의미하는 것일까?
// Every class has {@code Object} as a superclass.
// ProductDTO extends Object 라 작성하지않더라도 컨파일러가 자동으로 입력해준다.
// 모든 클래스는 오브젝트 클래스의 자식/후손 이다.
this.code = code;
this.brand = brand;
this.name = name;
this.price = price;
this.manufacturingDate = manufacturingDate;
System.out.println("ProductDTO 클래스의 매개변수 있는 생성자 호출함...");
}
/* getters/setters 작성하기 */
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public java.util.Date getManufacturingDate() {
return manufacturingDate;
}
public void setManufacturingDate(java.util.Date manufacturingDate) {
this.manufacturingDate = manufacturingDate;
}
/* 모든 필드 값을 문자열로 반환하는 메소드 */
public String getInformation() {
return "ProductDTO [code=" +code + ", brand=" + brand + ", name=" + name
+ ", price=" + price + ", manufacturingDate=" + manufacturingDate + "]";
}
}
class ComputerDTO extends ProductDTO
import java.util.Date;
public class ComputerDTO extends ProductDTO{
/* ComputerDTO 클래스는 하나의 상품이다. (IS-A)
* ProductDTO클래스를 상속받아 작성할 것이다.
* (단, 생성자는 상속받지않으며 private에는 직접접근 불가)
* */
/* ComputerDTO가 가지는 추가적인 속성을 필드로 작성한다. */
private String cpu;
private int hdd;
private int ram;
private String operationSystem;
/* 기본 생성자 */
public ComputerDTO() {
System.out.println("ComputerDTO클래스의 기본 생성자 호출함...");
}
/* 자식클래스의 기본 생성자를 생략하면 부모 클래스의 기본생성자 호출에 오류를 만든다.
* 자식클래스의 기본생성자는 필수로 작성한다. */
/* 모든 필드를 초기화 하는 생성자 */
public ComputerDTO(String cpu, int hdd, int ram, String operationSystem) {
/* 부모 클래스의 기본 생성자 호출 */
super();
this.cpu = cpu;
this.hdd = hdd;
this.ram = ram;
this.operationSystem = operationSystem;
System.out.println("ComputerDTO클래스의 모든 필드를 초기화 하는 생성자 호출함...");
}
// 컴퓨터와 관련된 값들은 초기화 하지만, 부모클래스 쪽의 생성자들은 초기화되지않은
// 기본값만을 가지고 있을 것이다.
/* 부모의 필드도 모두 초기화하는 생성자 */
/* 우클릭 - source - Generate Constructor from Super class */
public ComputerDTO(String code, String brand, String name, int price, Date manufacturingDate,
String cpu, int hdd, int ram, String operationSystem) {
/* 부모의 모든 필드를 초기화하는 생성자로 ProductDTO 클래스가 가진 필드를 초기화 할 값 전달
* 반드시 첫 줄에 생성자를 호출해야 오류가 나지않는다. */
super(code, brand, name, price, manufacturingDate);
// this(cpu, hdd, ram, operationSystem);
// 생성자 안에서 다른 생성자를 호출할때는 무조건 윗줄에 작성해야 하기에 컴파일 에러가 생긴다.
/* 위와 같이 작성하면 this()로 다시한번 생성자를 호출하게 되기 때문에
* 생성자 두 번 호출은 허용되지 않는다.
* */
this.cpu = cpu;
this.hdd = hdd;
this.ram = ram;
this.operationSystem = operationSystem;
System.out.println("ComputerDTO 클래스의 부모필드도 초기화하는 생성자 호출함...");
}
/* getters/setters 작성하기 */
/* 부모 필드의 메소드에 대해서는 자신의 것처럼 사용 가능하므로 따로 작성할 필요가 없다.
* 자식 클래스에 추가된 필드에 대해서만 작성한다. */
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getHdd() {
return hdd;
}
public void setHdd(int hdd) {
this.hdd = hdd;
}
public int getRam() {
return ram;
}
public void setRam(int ram) {
this.ram = ram;
}
public String getOperationSystem() {
return operationSystem;
}
public void setOperationSystem(String operationSystem) {
this.operationSystem = operationSystem;
}
/* 모든 필드값을 문자열로 반환하는 메소드 */
@Override
public String getInformation() {
/* 부모 클래스에서 작성한 getter를 이용해서 부모 필드가
* 가지고 있는 값도 한번에 문자열 합치기를 한다.
* 부모가 가진 멤버는 super.와 this. 둘다 사용이 가능하다.
* 하지만 코드의 의미를 명확히 하기 위해 super. 를 사용하는 것이 좋다.
* */
// return "ComputerDTO ["
// + "code=" + super.getCode()
// + ", brand=" + super.getBrand()
// + ", name=" + super.getName()
// + ", price=" + super.getPrice()
// + ", manufacturingDate=" + super.getManufacturingDate()
//super.getInformation() 한줄로 단축할 수도 있다.
//
// + ", cpu=" + this.cpu
// + ", hdd=" + this.hdd
// + ", ram=" + this.ram
// + ", operationSystem=" + this.operationSystem
// + "]";
//
/* super.getInformation() : 정상적으로 부모의 메소드 호출
* this.getInformation() : 재귀 호출이 일어나며 StackOverflowError 발생
* getInformation() : this. 이 자동 추가되어 재귀호출이 일어남
* java.lang.StackOverflowError
* */
/* 부모의 getInformation 을 호출하여 코드 단축하기 */
return super.getInformation() //컴퓨터의 getInformation과 구분해야 한다.
+ "ComputerDTO ["
+ ", cpu=" + this.cpu
+ ", hdd=" + this.hdd
+ ", ram=" + this.ram
+ ", operationSystem=" + this.operationSystem
+ "]";
// this.code로는 작성하지 못한다. 왜냐? private이기 떄문
}
}
상속에서 부모생성자가 먼저 호출이 되어야지만 자식 생성자가 호출될 수 있다.
오버라이딩
오버라이딩(Overriding)이란?
부모클래스에서 상속받은 메소드를 자식클래스가 재정의하여 사용하는 것
성립조건
1. 메소드 이름 동일
2. 메소드 리턴 타입 동일
3. 매개변수의 타입, 개수, 순서가 동일
4. private 메소드는 오버라이딩 불가능
5. final 키워드가 사용된 메소드는 오버라이딩 불가능
6. 접근제한자는 부모 메소드와 같거나 더 넓은 범위여야 함
7. 예외처리는 같은 예외이거나 더 구체적(하위)인 예외를 처리해야 함
오버라이딩과 오버로딩
오버로딩은 매개변수만이 영향을 미친다.
오버라이딩 테스트 : class SuperClass
/* 부모클래스를 의미 */
public class SuperClass {
/* 오버라이딩 성립 요건에 대해서 알아보자
* 오버라이딩이란?
* 부모클래스(SuperClass)에서 상속받은 메소드를 자식클래스(SubClass)
* 에서 재정의하여 사용하는 것이다. */
/* 오버라이팅 테스트를 위한 기준이 되는 메소드 */
public void method(int num) {}
private void privateMethod() {}
public final void finalMethod() {}
protected void protectedMethod() {}
}
오버라이딩 테스트 : class SubClass
/* 자식클래스를 의미 */
public class SubClass extends SuperClass {
/* extends SuperClass를 추가해야 오버라이딩 가능하다. */
/* 부모 클래스에서 작성한 메소드를 그대로 가져오면 정상적으로 오버라이딩 되었다.
* 1. 메소드 이름 변경 하면 에러발생
* 2. 메소드 리턴 타입 변경 하면 에러발생
* 3. 매개변수 갯수나 타입 변경시 에러 발생
*
* */
@Override //어노테이션
public void method(int num) {}
// 1. The method method1(int) of type SubClass must
// override or implement a supertype method
// 2. The return type is incompatible with SuperClass.method(int)
/* 4. private 메소드는 오버라이딩 불가 */
// @Override
// private void privateMethod() {}
// The method privateMethod() of type
// SubClass must override or implement a supertype method
/* 5. final 메소드는 오버라이딩 불가 */
// @Override
// public final void finalMethod() {}
// Cannot override the final method from SuperClass
// 파이널 메소드는 재정의할 수 없다.
// final은 어디에 붙을 수 있을까?
// : 지역변수, 필드, 메소드(오버라이딩불가의미), 클래스(상속 불가를 의미)
// The type SubClass cannot subclass the final class SuperClass
/* 6. 부모메소드 접근 제한자와 같거나 더 넓은 범위로만 오버라이딩 가능
* 더 좁은 범위의 접근제한자로 변경 불가능하다. */
// @Override
// void protectedMethod() {}
// 넓이 큰 순위대로 public > protected > default > private
// @ Override
// protected void protectedMethod() {} // 같은 범위로 가능
@ Override
public void protectedMethod() {} // 더 넒은 범위로 가능
/* 클래스에도 final 키워드를 넣을 수 있는데
* final 키워드를 추가하면 상속 불가의 의미를 가진다.
* */
}
✅ 클래스의 상속에 대해 이해할 수 있다. ✅ 상속의 목적에 대해 이해할 수 있다. ✅ 상속의 장단점에 대해 이해할 수 있다. ✅ 상속의 특징에 대해 이해할 수 있다. ✅ super에 대해 이해할 수 있다. ✅ 오버라이딩에 대해 이해할 수 있다. ✅ 오버로딩과 오버라이딩의 차이에 대해 이해할 수 있다.