1228 - 0103
1228
예외처리(exception handling)
프로그램 오류
오작동을 하거나 비정상적으로 종료되는 경우 이러한 결과를 초래하는 원인을 프로그램 오류 또는 에러라고 한다.
에러의 종류
- 컴파일 에러 - 컴파일 시에 발생하는 에러
- 런타임 에러 - 실행 시에 발생하는 에러
- 논리적 에러 - 실행은 되지만, 의도와 다르게 동작하는 것
- 자바는 실행 시 발생할 수 있는 프로그램 오류를 ‘에러’와 ‘예외’, 두 가지로 구분하였다.
에러 - 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
예외 - 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
예외 클래스의 계층구조
- 자바에서는 실행 시 발생할 수 있는 오류(Exception과 Error)를 클래스로 정의하였다.
- Exception과 Error클래스 역시 Object 클래스의 자손이다.
-
모든 예외의 최고 조상은 Exception클래스이며, 예외 클래스들은 두 그룹으로 나눠질 수 있다.
- Exception클래스와 그 자손들
- RuntimeException클래스와 그 자손들
- Exception 클래스들은 주로 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
- 배열의 범위를 벗어남 - ArrayIndexOutOfBoundsException
- 값이 null인 참조변수의 멤버를 호출 - NullPointerException
- 클래스간의 형변환 실수 - ClassCastException
- 정수를 0으로 나눔 - ArithmeticException 등
- RuntimeException클래스들은 주로 프로그래머의 실수에 의해서 발생할 수 있는 예외
- 존재하지 않은 파일의 이름 입력 - FileNotFoundException
- 실수로 클래스의 이름을 잘못 적음 - ClassNotFoundException
- 입력한 데이터 형식이 잘못된 경우 - DataFormatException 등
예외처리하기 - try-catch문
실행도중 발생하는 에러는 어쩔 수 없지만, 예외는 프로그래머가 이에 대한 처리를 미리 해주어야 한다.
예외처리
정의 - 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것
목적 - 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것
에러와 예외는 모두 실행 시(runtime) 발생하는 오류이다.
- 발생한 예외를 처리하지 못하면, 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외는 JVM의 ‘예외처리기’가 받아서 에러의 원인을 화면에 출력
try-catch문
try {
//예외가 발생할 가능성이 있는 문장
} catch (Exception1 e1) {
//Exception1이 발생 시, 처리하기 위한 문장
} catch (Exception2 e2) {
//Exception2이 발생 시, 처리하기 위한 문장
}
- 하나의 try 블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch블럭이 올 수 있으며, 이 중 발생한 예외의 종류와 일치하는 단 한 개의 catch블럭만 수행
- 일치하는 catch블럭이 없으면 예외는 처리되지 않는다.
- if문과 달리, try블럭이나 catch블럭 내에 포함된 문장이 하나뿐이어도 괄호 생략 불가
- try블럭 또는 catch블럭에 또 다른 try-catch문이 포함될 수 있다.
- 그러나 catch블럭 내에 또 하나의 try-catch문이 포함된 경우, 같은 이름의 참조변수를 사용해서는 안 된다.
try-catch문에서의 흐름
- try블럭 내에서 예외가 발생한 경우,
- 발생한 예외와 일치하는 catch블럭이 있는지 확인
- 일치하는 catch블럭을 찾게 되면, catch블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져나가서 그 다음 문장을 계속해서 수행
- try블럭 내에서 예외가 발생하지 않은 경우,
- catch블럭을 거치지 않고 전체 try-catch문을 빠져나가서 수행을 계속
try블럭에서 예외가 발생하면, 예외가 발생한 위치 이후에 있는 try블럭의 문장들은 수행되지 않으므로, try블럭에 포함시킬 코드의 범위를 잘 선택해야 한다.
예외의 발생과 catch블럭
- 예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어 진다.
- 첫 번째 catch블럭부터 차례로 내려가면서 catch블럭의 괄호()내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof연산자를 이용해서 검사결과가 true인 catch블럭을 만날 때까지 검사한다.
- 검사결과가 true인 catch블럭을 찾게 되면 블럭에 있는 문장들을 모두 수행하고 try-catch문을 빠져나간다.
모든 예외 클래스는 Exception클래스의 자손이므로, catch블럭의 괄호()에 Exception클래스 타입의 참조변수를 선언해 놓으면 어떤 종류의 예외가 발생하더라도 이 catch블럭에 의해서 처리된다.
printStackTrace()와 getMessage()
예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨있고, getMessage()와 printStackTrace()를 통해서 이 정보들을 얻을 수 있다.
printStackTrace() - 예외발생 당시의 호출스택에 있었던 메서드의 정보와 예외 메시지를 화면에 출력
getmessage() - 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
멀티 catch블럭
| _JDK 1.7부터 여러 catch블럭을 ‘ | ‘기호를 이용해서, 하나의 catch블럭으로 합칠 수 있게 되었으며, 이를 ‘멀티 catch블럭’이라 한다._ |
try {
} catch (ExceptionA | ExceptionB) {
e.printStackTrace();
}
|기호로 연결할 수 있는 예외 클래스의 개수에는 제한이 없다.- 연결된 예외 클래스가 조상과 자손의 관계에 있다면 컴파일 에러가 발생한다.
-> 불필요한 코드는 제거하라는 의미 - 멀티 catch는 하나의 catch블럭으로 여러 예외를 처리하는 것이기 때문에, 멀티 catch블럭 내에서는 실제로 어떤 예외가 발생한 것인지 알 수 없다. 그래서 예외 클래스들의 공통 분모인 조상 예외 클래스에 선언된 멤버만 사용할 수 있다.
예외 발생시키기
- 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스를 만든 다음
Exception e = new Exception("고의로 발생시켰음"); - 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;
- Exception인스턴스를 생성할 때, 생성자에 String을 넣어 주면, Exception인스턴스에 메시지로 저장된다.
- Exception클래스들이 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일조차 되지 않는다.
- RuntimeException클래스들에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는다.
-> 만일 예외처리를 필수로 해야 한다면, 참조 변수와 배열이 사용되는 모든 곳에 예외처리를 해줘야 할 것이다.
컴파일러가 예외처리를 확인하지 않는 RuntimeException클래스들은 ‘unchecked예외’라고 부르고, 예외처리를 확인하는 Exception클래스들은 ‘checked예외’라고 부른다.
1229
HTTP 기본
HTTP - HyperText Transfer Protocol
- HTTP 메시지에 모든 것을 전송
- HTML, Text, Image, 음성, 영상, 파일, JSON, XML (API), 등 거의 모든 형태의 데이터 전송 가능
- 서버간에 데이터를 주고 받을 때도 대부분 HTTP 사용
HTTP 역사
- HTTP/1.1: 가장 많이 사용하는 버전
- RFC2068(1997) -> RFC2616(1999) -> RFC7230~7235(2014)
- HTTP/2 2015년: 성능 개선
- HTTP/3 진행중: TCP 대신에 UDP 사용, 성능 개선
HTTP 특징
- 클라이언트 서버 구조
- 무상태 프로토콜(stateless), 비연결성
- HTTP 메시지
- 단순함, 확장 가능
클라이언트 서버 구조
- Request Response 구조
- 클라이언트는 서버에 요청을 보내고, 응답을 대기
- 서버가 요청에 대한 결과를 만들어서 응답
-> 클라이언트와 서버를 분리하여 클라이언트 측에서는 UI와 사용성, 서버 측에서는 비즈니스 로직과 데이터 로직을 담당하여 독립적으로 진화할 수 있다.
무상태 프로토콜
스테이스리스(Stateless)
- 서버가 클라이언트의 상태를 보존X
- 장점: 서버 확장성 높음(스케일 아웃)
- 단점: 클라이언트가 추가 데이터 전송
Stateful, Stateless 차이
상태 유지 - Stateful
- 서버에서 클라이언트의 상태를 유지하여 항상 같은 서버가 유지되어야 한다.
- 클라이언트 A는 계속 서버 1과 통신해야 하기 때문에 서버를 막 늘릴 수 없다.
- 중간에 서버가 장애 난다면 유지했던 상태가 모두 사라지므로 처음부터 다시 요청해야 한다.
무상태 - Stateless
- 클라이언트가 서버에 요청할 때 데이터를 담아서 보내기 때문에 아무 서버나 호출해도 된다.
- 서버에서 상태를 유지하지 않기 때문에 중간에 서버가 장애 나더라도 다른 서버에 다시 요청하여 응답을 받으면 된다.
- 스케일 아웃 - 수평 확장에 유리
Stateless 실무 한계
- 모든 것을 무상태로 설계 할 수 있는 경우도 있고 없는 경우도 있다.
- 무상태
- ex) 로그인이 필요 없는 단순한 서비스 소개 화면
- 상태 유지
- ex) 로그인
- 로그인한 사용자의 경우 로그인 했다는 상태를 서버에 유지
- 일반적으로 브라우저 쿠키와 서버 세션등을 사용해서 상태 유지
- 상태 유지는 최소한만 사용
- 정말 같은 시간에 딱 맞추어 발생하는 대용량 트래픽 ex)선착순 1000명 이벤트와 같은 수만명이 동시에 요청하는 업무에서는 어떻게든 머리를 쥐어짜서 stateless하게 설계해야 한다.
비 연결성(connectionless)
- 연결을 유지하는 모델은 클라이언트가 놀고 있어도 서버가 연결을 유지해서, 서버 자원이 소모된다.
- 연결을 유지하지 않는 모델은 한 번 클라이언트와 서버가 요청/응답을 마치면 TCP/IP 연결을 종료한다.
-> 서버는 연결 유지X, 최소한의 자원 유지
비 연결성 특징
- HTTP는 기본이 연결을 유지하지 않는 모델
- 일반적으로 초 단위의 이하의 빠른 속도로 응답
- 1시간 동안 수천명이 서비스를 사용해도 실제 서버에서 동시에 처리하는 요청은 수십개 이하로 매우 작음
- 웹 브라우저에서 계속 연속해서 검색 버튼을 누르지는 않는다.
- 서버 자원을 매우 효율적으로 사용할 수 있다.
비 연결성 한계와 극복
- TCP/IP 연결을 새로 맺어야 함 - 3 way handshake 시간 추가
- 웹 브라우저로 사이트를 요청하면 HTML 뿐만 아니라 자바스크립트, css, 추가 이미지 등 수 많은 자원이 함께 다운로드
- HTTP 지속 연결(Persistent Connections)로 문제 해결
-> HTTP 초기에는 HTML, 자바스크립트, 이미지를 요청하게 되면 각각 연결 및 종료를 따로 해서 시간이 낭비되었는데, HTTP 지속 연결을 통해 연결 및 종료는 한 번만 하고 그 사이에 HTML, 자바스크립트, 이미지를 요청하고 응답받게 되었다.
HTTP 메시지
HTTP 메시지 구조
start-line 시작 라인
header헤더
empty line 공백 라인 (CRLF)
message body
시작 라인
요청 메시지
request-line = method SP(공백) request-target SP HTTP-version CRLF(엔터)
- HTTP 메서드
- 종류: GET, POST, PUT, DELETE…
- 서버가 수행해야 할 동작 지정
- 요청 대상
- absolute-path[?query] (절대경로[?쿼리])
- 절대경로= “/”로 시작하는 경로
- HTTP 버전
응답 메시지
status-line = HTTP-version SP status-code SP reason-phrase CRLF
- HTTP 버전
- HTTP 상태 코드: 요청 성공, 실패를 나타냄
- 200: 성공
- 400: 클라이언트 요청 오류
- 500: 서버 내부 오류
- 이유 문구: 사람이 이해할 수 있는 짧은 상태 코드 설명 글
HTTP 헤더
header-field = field-name”:” OWS field-value OWS (OWS:띄워쓰기 허용)
- field-name은 대소문자 구분 없음
- HTTP 전송에 필요한 모든 부가정보 (metadata)
ex) 메시지 바디의 내용, 메시지 바디의 크기, 압축, 인증, 요청 클라이언트 정보, 캐시 관리 정보, 서버 애플리케이션 정보 등 - 필요시 임의의 헤더 추가 가능
-> 약속된 클라이언트와 서버만 이해
HTTP 메시지 바디
- 실제 전송할 데이터
- HTML 문서, 이미지, 영상, JSON 등등 byte로 표현할 수 있는 모든 데이터 전송 가능
1230
Object클래스
Object클래스는 모든 클래스의 최고 조상이기 때문에 Object클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다.
Object클래스는 멤버변수는 없고 오직 11개의 메서드만 가지고 있다. 이 메서드들은 모든 인스턴스가 가져야 할 기본적인 것들이며, 이 중 6개는 쓰레드와 관련된 것들이다.
equals(Object obj)
매개변수로 객체의 참조변수를 받아서 비교하여 그 결과를 boolean값으로 알려주는 역할
public boolean equals(Object obj) {
return (this == obj);
}
- 두 객체의 같고 다름을 참조변수의 값으로 판단한다. 그렇기 때문에 서로 다른 두 객체를 equals메서드로 비교하면 항상 false를 결과로 얻는다.
- Object클래스로부터 상속받은 equals메서드는 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능밖에 할 수 없다.
- equals메서드로 클래스의 인스턴스가 가지고 있는 값을 비교하려면 해당 클래스에서 equals메서드를 오버라이딩 하여 주소가 아닌 객체에 저장된 내용을 비교하도록 변경해야 한다.
class Person {
long id;
public boolean equals(Object obj) {
if (obj instanceof Person) {
return id == ((Person)obj).id; //obj가 Object타입이므로 id를 참조하기 위해서 형변환이 필요
else
return false; //타입이 Person이 아니면 비교할 필요도 없다.
}
}
- String클래스도 Object클래스의 equals메서드를 그대로 사용하는 것이 아니라 이처럼 오버라이딩을 통해서 String인스턴스가 갖는 문자열 값을 비교하도록 되어있다.
-> 그래서 같은 내용의 문자열을 같는 두 String인스턴스에 equals메서드를 사용하면 항상 true값을 얻는 것
hashCode()
해싱 기법에 사용되는 ‘해시함수’를 구현한 것
- Object클래스에 정의된 hashCode메서드는 객체의 주소값을 해시코드를 만들어 반환한다.
- 클래스의 인스턴스변수 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals메서드 뿐 아니라 hashCode메서드도 오버라이딩해야 한다.
-> 같은 객체라면 hashCode메서드를 호출했을 때의 결과값인 해시코드도 같아야 하기 때문 - String클래스는 문자열의 내용이 같으면, 동일한 해시코드를 반환하도록 hashCode메서드가 오버라이딩되어 있기 때문에, 문자열의 내용이 같은 str1과 str2에 대해 hashCode()를 호출하면 항상 동일한 해시코드값을 얻는다.
- 반면에 System.identityHashCode(Object x)는 Object클래스의 hashCode메서드처럼 객체의 주소값으로 해시코드를 생성하기 때문에 모든 객체에 대해 항상 다른 해시코드값을 반환할 것을 보장한다.
toString()
인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의한 것. 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻
// Object클래스에 정의된 toString()
public String toString() {
return getclass().getName() + "@" + Integer.tohexString(hashCode());
-
toString()을 오버라이딩하지 않는다면, 클래스이름에 16진수의 해시코드를 얻게 될 것
- String클래스의 toString()은 String인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩되어 있다.
- Date클래스의 toString()은 Date인스턴스가 갖고 있는 날짜와 시간을 문자열로 변환하도록 오버라이딩되어 있다.
-> toString()은 일반적으로 인스턴스나 클래스에 대한 정보 또는 인스턴스 변수의 값을 문자열로 변환하여 반환하도록 오버라이딩 하는 것이 보통이다. - Object클래스에 정의된 toString()의 접근 제어자가 public이므로 오버라이딩 한 toString()의 접근제어자도 public으로 해야 한다.
clone()
자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다.
- Object클래스에 정의된 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.
- 배열의 경우, 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에 복제된 인스턴스의 작업이 원래의 인스턴스에 영향을 미치게 된다.
- 이런 경우 clone메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야 한다.
- clone()을 사용하려면, 먼저 복제할 클래스가 Cloneable인터페이스를 구현해야하고, clone()을 오버라이딩하면서 접근 제어자를 protected에서 public으로 변경한다. 그래야 상속관계가 없는 다른 클래스에서 clone()을 호출 할 수 있다.
- Cloneable인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미
-> 데이터를 보호하기 위함
class Point implements Cloneable { // 1. Cloneable인터페이스를 구현한다.
public Object clone() { // 2. 접근 제어자를 Protected에서 public으로 변경
Object obj = null;
try {
obj = super.clone(); // 3. try-catch내에서 조상클래스의 clone()을 호출
} catch (CloneNotSupportedException e) {}
return obj;
}
}
공변 반환타입
JDK1.5부터 추가되었는데, 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것
class Point implements Cloneable {
public Point clone() { // 반환 타입을 자손 클래스 타입으로 변경
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {}
return (Point)obj; // return 문에서도 Point타입으로 형변환한다.
}
}
- ‘공변 반환타입’을 사용하면, 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어서 번거로운 형변환이 줄어든다.
Point copy = (Point)original.clone();->Point copy = original.clone();
int[] arr = {1, 2, 3, 4, 5};
int[] arrClone = arr.clone();
- 배열도 객체이기 때문에 Object클래스를 상속받으며, 동시에 Cloneable 인터페이스와 Serialiazable인터페이스가 구현되어 있다.
- public으로 오버라이딩하였기 때문에 호출 가능하고, 원본과 같은 타입을 반환하므로 형변환이 필요 없다.
- 배열을 복사할 때, 같은 길이의 새로운 배열을 생성한 다음에 System.arraycopy()를 이용해서 내용을 복사하지만, 이처럼 clone()을 이용해서 간단하게 복사할 수 도 있다.
얕은 복사와 깊은 복사
clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지는 않는다.
얕은 복사(shallow copy)
- 원본을 변경하면 복사본도 영향을 받는다.
- 객체배열을 clone()으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라고 보기 어렵다.
깊은 복사(deep copy)
- 원본이 참조하고 있는 객체까지 복제하는 것
- 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.
class Point {
int x;
int y;
Point (int x, int y) {
this.x = x;
this.y = y;
}
class Circle implements Cloneable {
Point p;
double r;
public Circle clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {}
Circle c = (Circle)obj; //깊은 복사를 하기 위한 코드
c.p = new Point(this.p.x, this.p.y);
return c;
}
- Object클래스의 clone()은 원본 객체가 가지고 있는 값만 그대로 복사한다. 즉 얕은 복사를 한다.
- 깊은 복사는 아래 3줄을 추가하여, 복제된 객체가 새로운 Point인스턴스를 참조하도록 했다.
-> 원본이 참조하고 있는 객체까지 복사
getClass()
자신이 속한 클래스의 Class객체를 반환하는 메서드
- Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.
- 클래스 파일이 클래스 로더에 의해서 메모리에 올라갈 때 자동으로 생성된다.
- 파일 형태로 저장되어 있는 클래스를 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체이다.
클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.
Class객체를 얻는 방법
클래스의 정보가 필요할 때, 먼저 Class객체에 대한 참조를 얻어 와야 하는데, 방법이 여러 가지가 있다.
Class cObj = new Card().getClass(); // 생성된 객체로 부터 얻는 방법Class cObj = Card.class; // 클래스 리터럴(*.class)로 부터 얻는 방법Class cObj = Class.forName("Card"); // 클래스 이름으로 부터 얻는 방법
-> Class 객체를 이용하면 클래스에 대한 모든 정보를 얻을 수 있기 때문에 Class 객체를 통해 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.
1231
2020년을 마무리 하며…
2020년은 일도 많았고, 정신없이 지나가버렸다.
코로나 사태가 발생하기 전에 유럽여행도 두 번이나 다녀왔기에 미련없이 학업에만 집중할 수 있었다.
인턴
- 6개월간의 인턴생활을 통해 기업과 프로젝트에 대해서 많은 것을 경험하고 배웠다.
- 휴학을 하고 진행한 것이 아니라 학점을 인정받고 진행한 학점연계형 인턴이어서 더 시간을 보람차게 보냈다고 생각한다.
- 현직에서 근무하는 개발자들과 같이 프로젝트를 진행하며 시야를 넓히는 계기가 되었다.
정보처리기사 자격증 취득
- 인턴을 진행하면서 취득한 자격증이라 나에게 더 큰 결실로 다가오는 성과이다.
- 매주 평일마다 9시부터 6시까지 근무를 하고 와서 저녁시간을 쪼개어 자격증 시험 준비를 하였다.
- 시간을 쪼개어 활용하는 습관을 내 몸이 기억하게 되었다.
우아한테크코스 도전, 그리고 실패
- 전부터 눈여겨 보고 있던 우아한 형제들에서 진행하는 우테코에 지원했다.
- 간절한 마음으로 자기소개서를 작성하고, 코딩테스트를 준비했지만 아쉽게도 떨어졌다.
- 부족한 노력을 글로 포장하기에는 한계가 있었나보다. 이 때의 실패가 기름이 되어 나를 더 불타게 만들었다.
GIT 블로그 시작
- ‘개발자스러움’이 무엇일까 고민하다가 처음으로 시도하게 되었다.
- 매일 커밋하는 습관을 들이고, 커밋을 하기 위해 공부를 함으로써 좋은 순환이 될 것이라 생각했다.
- 내가 공부한 내용을 다시 한 번 정리하면서 복습하고, 다시 찾을 수 있도록 기록해두었다.
혼자하는 study, 함께하는 study
- 최근에 와서 정보처리기사 자격증을 취득하고, 인턴생활도 종강과 함께 마무리하면서 새로운 도전을 하기로 했다.
- 매일 혼자 공부하고, 주 3회씩 함께 공부하는 그룹을 만들어서 정보를 공유한다.
- 아직 시작한 지 오래되지는 않았지만 결과가 성공적인 것 같다.
7월부터 마음을 다잡고 시작했기에 생각보다 적을 내용이 많지 않았다. 2021년에는 이번 도전을 계기로 더 나아갈 수 있는 개발자가 되어야겠다고 다짐한다.
0101
2021년 목표
- TIL 꾸준히 기록하며 성장하는 개발자 되기
- 자바와 스프링 공부하고, 이를 사용하여 의미 있는 프로젝트 완성하기
- 코딩테스트 준비하기
- 원하는 기업에 지원해보고 그에 맞는 인재로 성장하기 위해 노력하기
올해는 온전히 더 좋은 개발자가 되기 위한 한 해를 보내야겠다고 다짐했다. 다른 것들을 뒤로 하고 한 길만 파는 만큼 2021년을 돌아보며 후회없도록 하겠다.
0102
HTTP 메서드
API URI 설계
어떤 것이 좋은 URI 설계일까?
-> 가장 중요한 것은 리소스 식별이다.
- 회원을 등록하고 수정하고 조회하는게 리소스가 아니라 회원이라는 개념 자체가 리소스다.
-> 회원 리소스를 URI에 매핑 - URI는 리소스를 판별하는데 써야한다.
- 리소스와 해당 리소스를 대상으로 하는 행위를 분리
- 리소스: 회원
- 행위: 조회, 등록, 삭제, 변경
- 행위는 어떻게 구분? -> 메서드
HTTP 메서드
HTTP 메서드 종류 - 주요 메서드
- GET - 리소스 조회
- POST - 요청 데이터 처리, 주로 등록에 사용
- PUT - 리소스를 대체, 해당 리소스가 없으면 생성
- PATCH - 리소스 부분 변경
- DELETE - 리소스 삭제
기타 메서드
- HEAD - GET과 동일하지만 메시지 부분을 제외하고, 상태 줄과 헤더만 반환
- OPTIONS - 대상 리소스에 대한 통신 가능 옵션을 설명(주로 CORS에서 사용)
- CONNECT - 대상 자원으로 식별되는 서버에 대한 터널을 설정
- TRACE - 대상 리소스에 대한 경로를 따라 메시지 루프백 테스트를 수행
GET
- 리소스 조회
- 서버에 전달하고 싶은 데이터는 query(쿼리 스트링)를 통해서 전달
- 메시지 바디를 사용해서 데이터를 전달할 수 있지만, 지원하지 않는 곳이 많아서 권장하지 않음
POST
- 요청 데이터 처리
- 메시지 바디를 통해 서버로 요청 데이터 전달
- 서버는 요청 데이터를 처리
- 주로 전달된 데이터로 신규 리소스 등록, 프로세스 처리에 사용
- 예를 들어 /members로 들어오면 신규멤버로 등록하는 식으로 미리 약속한다.
요청 데이터를 어떻게 처리한다는 뜻일까?
- 예를 들어 POST는 다음과 같은 기능에 사용
- HTML 폼에 입력 된 필드와 같은 데이터 블록을 데이터 처리 프로세스에 제공
- 게시판, 블로그에 메시지 게시
- 서버가 아직 식별하지 않은 새 리소스 생성
- 기존 자원에 데이터 추가
이 리소스 URI에 POST 요쳥이 오면 요청 데이터를 어떻게 처리할지 리소스마다 따로 정해야 함(정해진 것 x)
- 새 리소스 생성(등록)
- 서버가 아직 식별하지 않은 새 리소스 생성
- 요청 데이터 처리
- 단순히 데이터를 생성하건, 변경하는 것을 넘어서 프로세스를 처리해야 하는 경우
- ex) 주문에서 결제완료 -> 배달시작 -> 배달완료 처럼 단순히 값 변경을 넘어 프로세스 상태가 변경되는 경우
- POST의 결과로 새로운 리소스가 생성되지 않을 수도 있음
- 어쩔 수 없이 리소스의 행위를 포함시키는 경우가 생기기도 한다.
- ex) POST /orders/{orderId}/start-delivery (컨트롤 URI)
- 다른 메서드로 처리하기 애매한 경우
- ex) JSON으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우
PUT
- 리소스를 대체 (리소스가 있으면 대체, 없으면 생성)
- 클라이언트가 리소스를 식별
- 클라이언트가 리소스 위치를 알고 URI 지정
- ex) PUT /members/100
- 리소스를 수정하는게 아니라 완전히 덮어버린다.
PATCH
- 리소스 부분 변경
- patch가 지원되지 않는 서버도 있는데, 그럴 경우 POST 메서드를 사용한다.
DELETE
- 리소스 제거
HTTP 메서드의 속성
- 안전(Safe Methods)
- 멱등(Idempotent Methods)
- 캐시가능(Cacheable Methods)
안전
- 호출해도 리소스를 변경하지 않는다.
- 주요 메서드 중에서는 GET만 리소스를 변경하지 않고 조회만함으로 안전하다.
멱등
- 한 번 호출하든 여러 번 호출하든 결과가 똑같다.
- GET, PUT, DELETE는 멱등
- POST는 두 번 호출하면 같은 결제가 중복해서 발생할 수 있으므로 멱등이 아니다.
- 멱등은 외부 요인으로 중간에 리소스가 변경되는 것 까지는 고려하지 않는다.
멱등 개념이 필요한 이유?
- 자동 복구 메커니즘
- 서버가 TIMEOUT 등으로 정상 응답을 못주었을 때, 클라이언트가 같은 요청을 다시 해도 되는지 판단 근거
캐시가능
응답 결과 리소스를 캐시해서 사용해도 되는가?
- 스펙상 GET, POST, HEAD, PATCH 캐시 가능
- 실제로는 GET, HEAD 정도만 캐시로 사용
-> POST, PATCH는 본문 내용까지 캐시 키로 고려해야 하는데, 구현이 쉽지 않음
0103
java.lang 패키지
java.lang패키지는 자바 프로그래밍에 가장 기본이 되는 클래스들을 포함하고 있다. 그래서 java.lang패키지의 클래스들은 import문 없이 사용 가능하다.
Object클래스
Object클래스는 모든 클래스의 최고 조상이기 때문에 Object클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다.
Object클래스는 멤버변수는 없고 오직 11개의 메서드만 가지고 있다. 이 메서드들은 모든 인스턴스가 가져야 할 기본적인 것들이며, 이 중 6개는 쓰레드와 관련된 것들이다.
equals(Object obj)
매개변수로 객체의 참조변수를 받아서 비교하여 그 결과를 boolean값으로 알려주는 역할
public boolean equals(Object obj) {
return (this == obj);
}
- 두 객체의 같고 다름을 참조변수의 값으로 판단한다. 그렇기 때문에 서로 다른 두 객체를 equals메서드로 비교하면 항상 false를 결과로 얻는다.
- Object클래스로부터 상속받은 equals메서드는 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능밖에 할 수 없다.
- equals메서드로 클래스의 인스턴스가 가지고 있는 값을 비교하려면 해당 클래스에서 equals메서드를 오버라이딩 하여 주소가 아닌 객체에 저장된 내용을 비교하도록 변경해야 한다.
class Person {
long id;
public boolean equals(Object obj) {
if (obj instanceof Person) {
return id == ((Person)obj).id; //obj가 Object타입이므로 id를 참조하기 위해서 형변환이 필요
else
return false; //타입이 Person이 아니면 비교할 필요도 없다.
}
}
- String클래스도 Object클래스의 equals메서드를 그대로 사용하는 것이 아니라 이처럼 오버라이딩을 통해서 String인스턴스가 갖는 문자열 값을 비교하도록 되어있다.
-> 그래서 같은 내용의 문자열을 같는 두 String인스턴스에 equals메서드를 사용하면 항상 true값을 얻는 것
hashCode()
해싱 기법에 사용되는 ‘해시함수’를 구현한 것
- Object클래스에 정의된 hashCode메서드는 객체의 주소값을 해시코드를 만들어 반환한다.
- 클래스의 인스턴스변수 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals메서드 뿐 아니라 hashCode메서드도 오버라이딩해야 한다.
-> 같은 객체라면 hashCode메서드를 호출했을 때의 결과값인 해시코드도 같아야 하기 때문 - String클래스는 문자열의 내용이 같으면, 동일한 해시코드를 반환하도록 hashCode메서드가 오버라이딩되어 있기 때문에, 문자열의 내용이 같은 str1과 str2에 대해 hashCode()를 호출하면 항상 동일한 해시코드값을 얻는다.
- 반면에 System.identityHashCode(Object x)는 Object클래스의 hashCode메서드처럼 객체의 주소값으로 해시코드를 생성하기 때문에 모든 객체에 대해 항상 다른 해시코드값을 반환할 것을 보장한다.
toString()
인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의한 것. 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻
// Object클래스에 정의된 toString()
public String toString() {
return getclass().getName() + "@" + Integer.tohexString(hashCode());
-
toString()을 오버라이딩하지 않는다면, 클래스이름에 16진수의 해시코드를 얻게 될 것
- String클래스의 toString()은 String인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩되어 있다.
- Date클래스의 toString()은 Date인스턴스가 갖고 있는 날짜와 시간을 문자열로 변환하도록 오버라이딩되어 있다.
-> toString()은 일반적으로 인스턴스나 클래스에 대한 정보 또는 인스턴스 변수의 값을 문자열로 변환하여 반환하도록 오버라이딩 하는 것이 보통이다. - Object클래스에 정의된 toString()의 접근 제어자가 public이므로 오버라이딩 한 toString()의 접근제어자도 public으로 해야 한다.
clone()
자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다.
- Object클래스에 정의된 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.
- 배열의 경우, 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에 복제된 인스턴스의 작업이 원래의 인스턴스에 영향을 미치게 된다.
- 이런 경우 clone메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야 한다.
- clone()을 사용하려면, 먼저 복제할 클래스가 Cloneable인터페이스를 구현해야하고, clone()을 오버라이딩하면서 접근 제어자를 protected에서 public으로 변경한다. 그래야 상속관계가 없는 다른 클래스에서 clone()을 호출 할 수 있다.
- Cloneable인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미
-> 데이터를 보호하기 위함
class Point implements Cloneable { // 1. Cloneable인터페이스를 구현한다.
public Object clone() { // 2. 접근 제어자를 Protected에서 public으로 변경
Object obj = null;
try {
obj = super.clone(); // 3. try-catch내에서 조상클래스의 clone()을 호출
} catch (CloneNotSupportedException e) {}
return obj;
}
}
공변 반환타입
JDK1.5부터 추가되었는데, 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것
class Point implements Cloneable {
public Point clone() { // 반환 타입을 자손 클래스 타입으로 변경
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {}
return (Point)obj; // return 문에서도 Point타입으로 형변환한다.
}
}
- ‘공변 반환타입’을 사용하면, 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어서 번거로운 형변환이 줄어든다.
Point copy = (Point)original.clone();->Point copy = original.clone();
int[] arr = {1, 2, 3, 4, 5};
int[] arrClone = arr.clone();
- 배열도 객체이기 때문에 Object클래스를 상속받으며, 동시에 Cloneable 인터페이스와 Serialiazable인터페이스가 구현되어 있다.
- public으로 오버라이딩하였기 때문에 호출 가능하고, 원본과 같은 타입을 반환하므로 형변환이 필요 없다.
- 배열을 복사할 때, 같은 길이의 새로운 배열을 생성한 다음에 System.arraycopy()를 이용해서 내용을 복사하지만, 이처럼 clone()을 이용해서 간단하게 복사할 수 도 있다.
얕은 복사와 깊은 복사
clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지는 않는다.
얕은 복사(shallow copy)
- 원본을 변경하면 복사본도 영향을 받는다.
- 객체배열을 clone()으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라고 보기 어렵다.
깊은 복사(deep copy)
- 원본이 참조하고 있는 객체까지 복제하는 것
- 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.
class Point {
int x;
int y;
Point (int x, int y) {
this.x = x;
this.y = y;
}
class Circle implements Cloneable {
Point p;
double r;
public Circle clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {}
Circle c = (Circle)obj; //깊은 복사를 하기 위한 코드
c.p = new Point(this.p.x, this.p.y);
return c;
}
- Object클래스의 clone()은 원본 객체가 가지고 있는 값만 그대로 복사한다. 즉 얕은 복사를 한다.
- 깊은 복사는 아래 3줄을 추가하여, 복제된 객체가 새로운 Point인스턴스를 참조하도록 했다.
-> 원본이 참조하고 있는 객체까지 복사
getClass()
자신이 속한 클래스의 Class객체를 반환하는 메서드
- Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.
- 클래스 파일이 클래스 로더에 의해서 메모리에 올라갈 때 자동으로 생성된다.
- 파일 형태로 저장되어 있는 클래스를 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체이다.
클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.
Class객체를 얻는 방법
클래스의 정보가 필요할 때, 먼저 Class객체에 대한 참조를 얻어 와야 하는데, 방법이 여러 가지가 있다.
Class cObj = new Card().getClass(); // 생성된 객체로 부터 얻는 방법Class cObj = Card.class; // 클래스 리터럴(*.class)로 부터 얻는 방법Class cObj = Class.forName("Card"); // 클래스 이름으로 부터 얻는 방법
-> Class 객체를 이용하면 클래스에 대한 모든 정보를 얻을 수 있기 때문에 Class 객체를 통해 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.