AWS EC2(아마존 리눅스2) 생성과 Java 13 설치

스프링 부트와 AWS로 혼자 구현하는 웹 서비스의 나온 내용을 정리 해봤다. EC2는 자주 사용하지만 세세한 설정 등은 처음 본 것들도 있어서 공부한 보람이 있다.

책은 아마존 리눅스 1 이미지, JDK 8로 진행했으나 최근에 진행 중인 프로젝트가 아마존 리눅스 2, JDK 13이어서 책과는 다른 부분이 있으니 참고 바란다.

AWS EC2 인스턴스 생성

EC2 대시보드 → 인스턴스 시작


EC2 인스턴스 시작

이미지 선택


인스턴스 이미지 선택

Amazon Linx 2 AMI 선택

  • Centos 7 버전 내용과 호환
  • 아마존이 개발하고 있어 지원 받기 쉽다.
  • 레드햇 베이스이므로 레드햇 계열의 배포판을 다뤄본 사람일수록 사용하기 쉽다.
  • AWS의 각종 서비스와 상성이 좋다.
  • Amazon 독자적인 개발 리포지터리를 사용하고 있어 yum이 매우 빠르다.

인스턴스 유형 선택


인스턴스 유형 선택

  • t2.micro 선택

T 시리즈

  • 크레딧인란 CPU를 사용할 수 있는 포인트 개념 존재
  • 인스턴스 크기에 따라 정해진 비율로 CPU 크레딧을 계속 받으며 사용하지 않을 때 축적하고 사용할 때 크레딧을 계속 사용한다
  • 크레딧이 모두 사용되면 더이상 EC2를 사용할 수 없다.
  • 트래픽이 높은 서비스인 경우 다른 유형의 인스턴스를 사용한다.

인스턴스 세부 정보 구성


인스턴스 세부 정보 구성

  • 오토스케일링, 서브넷, VPC, 모니터링 등 용도에 맞게 세부적인 설정 가능
  • 지금은 크게 변경할 내용이 없다.

스토리지 추가


스토리지 추가

  • 프리 티어의 경우 30GB 까지 스토리지 사용 가능

태그 추가


태그 추가

  • 인스턴스에 태그 추가
  • 인스턴스가 많아질 경우 태그를 통해 관리가 용이하다.

보안 그룹 구성


보안 그룹 구성

  • 방화벽 설정
  • SSH 22 포트와 내 IP 규칙 추가
  • 웹 서비스 포트 Public 추가
  • HTTPS 포트 Public 추가

키 페어 생성


키 페어 생성

키 페어 등록

  • 다운로드 받은 키 페어를 등록하자
$ cd .ssh
$ mv ~/Downloads/springboot-aws-study-webservice.pem .
$ chmod 600 springboot-aws-study-webservice.pem
$ ssh-add springboot-aws-study-webservice.pem

인스턴스 대시보드에서 확인


인스턴스 대시보드 목록 확인

EIP 할당

  • AWS의 고정 IP 서비스 Elastic IP(EIP, 탄력적 IP)

  • 좌측 메뉴에서 탄력적 IP를 선택

 

  • 탄력적 IP 주소 할당 클릭

 

  • 할당 클릭

 

  • 이 탄력적 IP 주소 연결 또는 주소 Actions에서 탄력적 IP 주소 연결 선택

 

  • 생성한 EC2에 연결
  • 인스턴스와 프라이빗 주소의 드롭다운 목록에서 선택
  • 생성한 탄력적 IP를 EC2에 연결하지 않으면 비용이 발생하므로 바로 연결하는 것이 좋다.
  • 인스턴스가 없을 경우 탄력적 IP는 바로 삭제하는 것이 좋다.

인스턴스 정보 확인

인스턴스 정보 확인

  • 인스턴스 목록에서 인스턴스 선택 후 아래에 표시되는 세부 정보의 탄력적 IP에 할당된 모습 확인

터미널로 EC2 인스턴스 접속

pem키 등록

$ cd .ssh
$ mv ~/Downloads/springboot-aws-study-webservice.pem .
$ chmod 600 springboot-aws-study-webservice.pem
$ vim ~/.ssh/config

# Edit config                                                                                                                 Buffers
    # springboot-aws-study-webservice
  Host springboot-aws-study-webservice
      HostName [탄력적 IP]
        User ec2-user
      IdentityFile ~/.ssh/springboot-aws-study-webservice.pem

# config 저장 및 종료 :wq

# config 실행 권한 등록
$ chmod 700 ~/.ssh/config

SSH 호스트 Config

  • ssh [Host 이름]으로 바로 접속 가능하다.
ssh springboot-aws-study-webservice

접속 결과

인스턴스 아마존 리눅스 서버 설정

JDK 설치

  • openjdk13 설치를 진행한다.
  • 아마존 레포지토리에는 8버전까지 밖에 없어 13을 다운로드 한다.
$ curl -O https://download.java.net/java/GA/jdk13.0.2/d4173c853231432d94f001e99d882ca7/8/GPL/openjdk-13.0.2_linux-x64_bin.tar.gz

# 다운로드 하면서 아래와 비슷한 내용이 출력
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  186M  100  186M    0     0  10.0M      0  0:00:18  0:00:18 --:--:-- 9961k

# 다운로드 완료된 것 확인
$ ls
openjdk-13.0.2_linux-x64_bin.tar.gz

# 압축 풀기
$ tar -xvf openjdk-13_linux-x64_bin.tar.gz
$ ls
jdk-13.0.2  openjdk-13.0.2_linux-x64_bin.tar.gz

# /opt/ 디렉토리로 이동
$ sudo mv jdk-13.0.2 /opt
$ cd /opt
$ ls
aws  jdk-13.0.2  rh

# 환결변수 설정
$ cd /etc/profile.d
$ vim jdk13.sh

# Edit jdk13.sh
export JAVA_HOME=/opt/jdk-13.0.2
export PATH=$PATH:$JAVA_HOME/bin
## 저장 및 종료 :wa

# 설정 반영
$ source /etc/profile.d/jdk13.sh

# 확인
$ echo $JAVA_HOME
/opt/jdk-13.0.2

$ java --version
openjdk 13.0.2 2020-01-14
OpenJDK Runtime Environment (build 13.0.2+8)
OpenJDK 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)
  • [참고사항] curl -O 링크 참고 주소: 자바 릴리즈 아카이브 에서 JDK 13 최신 메이저 버전에서 Linux 운영체제의 tar.gz의 링크를 복사해와서 curl로 받아온다.

서버 시간대(타임존) 변경

  • 기본 타임존은 UTC (세계 표준시)로 우리나라와 9시간 차이가 난다.
  • 한국시간대 (KST)로 변경하자
# 현재 시간 확인
$ date
Sun Jun 28 13:25:53 UTC 2020

# 시간대 정보 변경
$ sudo rm /etc/localtime
$ sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime

# 심링크 확인
$ ll /etc/ | grep localtime
lrwxrwxrwx  1 root root       30 Jun 28 22:26 localtime -> /usr/share/zoneinfo/Asia/Seoul

# 현재 시간 다시 확인
$ date
Sun Jun 28 22:27:26 KST 2020

호스트네임 변경

  • 여러 서버를 관리할 때 IP만으로 어떤 서비스의 서버인지 확인이 어렵다.
  • Hostname을 설정하자
$ sudo hostnamectl set-hostname springboot-aws-study-webserivce
$ hostname
springboot-aws-study-webserivce

$ sudo systemctl restart network

$ sudo reboot

 

참고

  • 이동욱. 2019. 스프링 부트와 AWS로 혼자 구현하는 웹 서비스. 프리렉

'프로그래밍 > AWS' 카테고리의 다른 글

AWS RDS 생성과 기본 설정  (2) 2020.07.07

RestTemplate

RestTemplate은 HTTP 클라이언트 라이브러리를 통해 높은 수준의 API를 제공한다. REST 엔드포인트를 코드 한줄로 호출하기 쉽게 해준다. 오버로드된 메소드들은 다음과 같다.

 

RestTemplate methods

Method group Description
getForObject GET method를 통해 representation을 조회
getForEntity GET method를 통해 ResponseEntity를 조회 (status, headers, body)
headForHeaders HEAD method를 통해 리소스의 모든 헤더들을 조회
postForLocation POST method를 통해 새로운 리소스를 생성하고 응답에 Location 헤더를 리턴
postForObject POST method를 통해 새로운 리소스를 생성하고 응답에 representation을 리턴
postForEntity POST method를 통해 새로운 리소스를 생성하고 응답에 representation을 리턴
put PUT method를 통해 리소스를 새로 생성하거나 수정
patchForObject PATCH method를 통해 리소스를 수정하고 응답의 representation을 리턴.
주의할 것은 JDK HttpURLConnection은 PATCH 를 지원하지 않고, Apache HttpComponents와 다른 것들은 지원함
delete DELETE method를 통해 특정 URI의 리소스를 삭제
optionsForAllow ALLOW method를 통해 리소스가 허용하는 HTTP method들을 조회
exchange 위의 method들 보다 더 일반화되고, 덜 선택적인 버전으로 필요한 경우 추가적인 유연성을 제공함
HTTP 메서드, URL, 헤더 및 본문을 포함한 RequestEntity를 입력받아 ResponseEntity를 리턴
이러한 방법을 사용하면 Class 대신 ParameterizedTypeReference를 사용하여 제네릭 타입 응답을 지정할 수 있음
execute 요청을 수행하는 가장 일반화된 방법으로, 콜백 인터페이스를 통한 요청 준비 및 응답 추출에 대한 완벽한 제어가 가능

 

초기화

기본 생성자는 java.net.HttpURLConnection 라이브러리를 사용한다. ClientHttpRequestFactory를 사용하여 다른 HTTP 라이브러리로 바꿀 수 있다. 스프링은 Apache HttpComponents, Netty, OkHttp를 지원한다.

  • 예)
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

ClientHttpRequestFactory는 기본 HTTP 클라이언트 라이브러리(예: 자격 증명, 연결 풀링 등)에 대한 구성 옵션을 제공한다. 주의할 점은 java.net 패키지의 HTTP 구현체를 사용할 경우 401 상태를 가진 응답 객체에 접근할 때 익셉션이 발생할 수 있으므로, 이럴 경우 다른 HTTP 라이브러리 사용해야 한다.

 

URIs

대부분의 RestTemplate 메소드는 String vararg 또는 Map<String, String>을 통해 URI 템블릿과 URI 템플릿 변수를 허용한다.

/* String vararg */
String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

/* Map<String, String> */
Map<String, String> vars = Collections.singletonMap("hotel", "42");

String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

URI 템플릿은 자동으로 인코딩된다.

restTemplate.getForObject("http://example.com/hotel list", String.class);

// 요청 url: "http://example.com/hotel%20list"

RestTemplateuriTemplateHandler 속성을 사용하여 URI 인코딩 방법을 지정할 수 있다 또는 java.net.URI를 사용하여 이를 사용하는 RestTemplate 메소드에 인자로 전달할 수 있다.

 

Headers

exhcnage() 메소드를 사용하여 요청 헤더를 지정할 수 있다.

String uriTemplate = "http://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);

RequestEntity<Void> requestEntity = RequestEntity.get(uri)
        .header(("MyRequestHeader", "MyValue")
        .build();

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

ResponseEntity를 반환하는 RestTemplate 의 여러 메소드를 통해 응답 헤더를 얻을 수 있다.

 

Body

RestTemplate 메서드에 전달되고 반환되는 객체는 HttpMessageConverter의 도움으로 raw content로 변환된다.

POST 메서드에서 입력된 객체는 요청 body에 직렬화된다.

URI location = template.postForLocation("http://example.com/people", person);

요청의 Content-Type 헤더를 명시적으로 설정할 필요는 없다. 대부분의 경우 소스 객체 타입에 따라 호환되는 메시지 컨버터를 찾을 수 있고, 선택한 메시지 컨버터가 적절히 content-type을 설정한다. 필요한 경우 exchange 메서드를 사용하여 Content-Type 요청 헤더를 명시적으로 제공할 수 있으며, 이는 선택된 메시지 컨버터에 영향을 미칠 수 있다.

GET 메서드에서는 응답의 body가 출력 객체로 역직렬화된다.

Person person = restTemplate.getForObject("http://example.com/people/{id}", Person.class, 42);

요청의 Accept 헤더는 명시적으로 설정할 필요가 없다. 대부분의 경우 Accept 헤더를 예상 응답 유형에 따라 호환되는 메시지 컨버터를 찾을 수 있다. 이 컨버터는 Accept 헤더를 채우는데 도움이 된다. 필요한 경우 exchange 메서드를 사용하여 Accept헤더를 명시적으로 제공할 수 있다.

기본적으로 RestTemplate은 선택적인 변환 라이브러리가 있는지 확인하는데 도움이 되는 클래스패쓰 검사에 따라 모든 내장 메시지 컨버터들을 등록한다. 또, 명시적으로 메시지 컨버터를 설정할 수 있다.

 

Message Conversion

spring-web 모듈은 InputStreamOutputStream을 통해 HTTP 요청 및 응답의 body를 읽고 쓰는 역할을 맡은 HttpMessageConverter를 포함한다. HttpMessageConverter는 클라이언트 측(예, RestTemplate)과 서버 측 (예, Spring MVC REST controllers)에서 사용된다.

주요 미디어(MIME) 유형에 대한 구체적인 구현은 프레임워크에서 제공되며 기본적으로 클라이언트 측 RestTemplate 및 서버측 RequestMethodHandlerAdapter에 등록된다. (메시지 구성 참조)

HttpMessageConverter의 구현은 다음 섹션에 기술되어있다. 모든 컨버터들이 기본 미디어 타입을 사용하지만 supportedMediaTypes 빈 프로퍼티 설정으로 오버라이드 할 수 있다.

 

HttpMessageConverter 구현체 목록

MessageConverter Description
StringHttpMessageConverter HTTP 요청 및 응답으로부터 문자열(String)을 읽고 쓸 수 있는 구현체
기본적으로 모든 텍스트 미디어 타입들 (text/*)을 지원하며, text/plain Content-Type으로 작성한다.
FormHttpMessageConverter HTTP 요청 및 응답으로부터 form 데이터를 읽고 쓸 수 있는 구현체
기본적으로 application/x-www-form-urlencoded 미디어 타입을 지원한다.
Form 데이터는 MultiValueMap<String, String>읽고 작성된다.
ByteArrayHttpMessageConverter HTTP 요청 및 응답으로부터 바이트 배열을 읽고 쓸 수 있는 구현체
기본적으로 모든 미디어 타입 (*/*)을 지원하며, application/octet-stream Content-Type으로 작성한다.
이 컨버터는 supportedMediaTypes 속성을 설정하고 getContentType(byte[])를 오버라이드하여 재정의할 수 있다
MarshallingHttpMessageConverter org.springframework.oxm 패키지의 스프링의 마샬러(Marshaller)와 언마샬러(Unmarshaller) 추상화를 사용하여 XML을 읽고 쓸 수 있는 구현체
이 컨버터는 사용하기 전에 마샬러와 언마샬러를 필요로 한다.
이들은 생성자 또는 빈 프로퍼티를 통해 주입될 수 있다.
기본적으로 text/xmlapplication/xml을 지원한다
MappingJackson2HttpMessageConverter Jackson 라이브러리의 ObjectMapper를 사용하여 JSON을 읽고 쓸 수 있는 구현체
JSON 매핑은 필요에 따라 Jackson이 제공하는 어노테이션을 통해 커스터마이징 될 수 있다
추가 제어가 필요할 경우 특히, 특정 유형에 대해 커스텀 JSON 직렬화/역직렬화를 제공해야 하는 경우 ObjectMapper 속성을 통해 커스텀 ObjectMapper를 주입할 수 있다.
기본적으로 application/json을 지원한다
MappingJackson2XmlHttpMessageConverter Jackson XML의 XmlMapper를 사용하여 XML을 읽고 쓸 수 있는 구현체
Jackson이 제공하는 어노테이션을 또는 JAXB를 사용하여 필요에 따라 XML 매핑은 커스터마이징 될 수 있다
추가 제어가 필요한 경우, 특히 특정 유형에 대해 커스텀 XML 직렬화/역직렬화를 제공해야하는 경우 ObjectMapper 속성을 통해 커스텀 XmlMapper를 주입할 수 있다
기본적으로 application/xml을 지원한다
SourceHttpMessageConverter HTTP 요청 및 응답으로부터 javax.xml.transform.Source를 읽고 쓸 수 있는 구현체
DOMSource, SAXSource, StreamSource만 지원된다.
기본적으로 text/xmlapplication/xml를 지원한다
BufferedImageHttpMessageConverter HTTP 요청 및 응답으로부터 java.awt.image.BufferedImage를 읽고 쓸 수 있는 구현체
Java I/O API에서 지원하는 미디어 타입을 읽고 쓴다

 

Jackson JSON Views

객체 속성의 일부만 직렬화하도록 Jackson JSON View를 지정할 수 있다.

  • 예)
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);

RequestEntity<MappingJacksonValue> requestEntity =
    RequestEntity.post(new URI("http://example.com/user")).body(value);

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

 

Multipart

multipart 데이터를 전송하기 위해 multipart 내용을 대표하는 Objects 또는 내용과 헤더를 대표하는 HttpEntity를 값으로 가지는 MultiValueMap<String, ?>를 제공할 필요가 있다. MultipartBodyBuilder는 multipart 요청을 만들어주는 편리한 API를 제공한다.

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));

MultiValueMap<String, HttpEntity<?>> parts = builder.build();

대부분의 경우 각 part에 Content-Type를 지정할 필요가 없다. Content-Type은 직렬화 하도록 선택한 HttpMessageConverter 또는 파일 확장자에 기반한 Resource의 경우에 자동으로 결정된다. 필요한 경우 오버로드된 builder 메소드를 통해 각 part에 사용할 MediaType을 명시적으로 제공할 수 있다.

MultiValueMap이 준비되면, RestTemplate에 인자를 넘겨줄 수 있다.

MultipartBodyBuilder builder = ...;
template.postForObject("http://example.com/upload", builder.build(), Void.class);

MultiValueMap이 일반적인 form 데이터를 나타낼수 있는(예: application/x-www-form-urlencoded) String이 아닌 값을 가질 경우, Content-Typemultipart/form-data로 지정할 필요 없다. 이는 ㅇ항상 HttpEntity 래퍼를 보장하는 MultipartBodyBuilder를 사용하는 경우에 해당된다.

 

참고

 

매직 넘버 치환

  • 소스 코드에 특정한 숫자(매직 넘버(magic number))를 직접 작성하는 것은 나쁜 코딩 스타일

나쁜 예

public class MagicNumber {
    public static void main(String[] args) {
        MagicNumber magicNumber = new MagicNumber();
        MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
        // 0은 공격
        player.action(0);
        // 1은 방어
        player.action(1);
    }

    public class Player {

        private String name;

        public Player() {
        }

        public Player(String name) {
            this.name = name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void action(int command) {
            if (command == 0) {
                attack();
            } else if (command == 1) {
                defence();
            }
        }

        private void attack() {
            System.out.println(this.name + " is going to Attack!!");
        }

        private void defence() {
            System.out.println(this.name + " is going to Defence!!");
        }

    }
}

나쁜이유

  1. 매직 넘버의 의미를 알기 어렵다
    • 0과 1이 무슨 의미인가?
    • COMMAND_ATTACK과 같은 기호 상수를 쓰면 의미를 알기 쉽다
  2. 매직 넘버는 수정하기 어렵다
    • 0 또는 1 커맨드 숫자가 변경 될 경우 치환해야 하는데, 만약 많은 곳에서 동일한 커맨드를 쓴다면?
    • 손이 많이 가고, 틀리기 쉽고, 빼먹는 경우가 생길 수도 있다.

카탈로그

이름 매직 넘버를 기호 상수로 치환 (Replace Magic Number with Symbolic Constant)
상황 상수를 사용하고 있음
문제 • 매직 넘버는 알기 어려움
• 매직 넘버가 여러 곳에 있으면 변경하기 어려움
해법 매직 넘버를 기호 상수로 치환함
결과 장점
• 상수의 의미를 알기 쉬워짐
• 기호 상수의 값을 변경하면 상수를 사용하는 모든 곳이 변경됨
단점/주의 • 이해하기 어려운 이름을 사용하면 오해가 생길 수 있음
방법
  1. 기호 상수 선언하기
    • 1. 기호 상수 선언
    • 2. 매직 넘버를 기호 상수로 치환
    • 3. 기호 상수에 의존하는 다른 매직 넘버를 찾아서 기호 상수를 사용한 표현식으로 변환
    • 4. 컴파일
  2. 2. 테스트
    • 1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트
    • 2. 가능하다면 기호 상숫값을 변경한 후 컴파일해서 테스트 |
관련 항목 • 분류 코드를 클래스로 치환
• 분류 코드를 상태/전략 패턴으로 치환

리팩토링

기호 상수 선언하기

  1. 기호 상수 선언
  • public static final 클래스 필드 사용
  • 또는 enum 사용
  • 어떤 클래스 안에서만 사용할 기호 상수를 선언할 경우 private 접근지시자를 사용할 수 있다
public static final int COMMAND_ATTACK = 0;
public static final int COMMAND_DEFENCE = 1;
  1. 매직 넘버를 기호 상수로 치환
  • 0, 1과 같은 매직 넘버를 기호 상수로 치환
// if (command == 0) {
if (command == COMMAND_ATTACK) {

// else if (command == 1) {
} else if (command == COMMAND_DEFENCE) {


// player.action(0)
// player.aciton(1)
player.action(Player.COMMAND_ATTACK);
player.action(Player.COMMAND_DEFENCE);
  1. 기호 상수에 의존하는 다른 매직 넘버를 기호 상수를 사용한 표현식으로 변환
  • 상수 의존 관계는 상수 사이에 의존 관계가 있어 한 상수의 변경이 다른 상수에게도 영향을 미치는 경우를 말한다.
  • 이 때, 표현식으로 의존 관계를 표현해야 한다.
// public static final int MAX_INPUT_LENGTH = 100;
// public static final int WOR_ARE_LENGTH = 100 * 2;

public static final int MAX_INPUT_LENGTH = 100;
public static final int WOR_ARE_LENGTH = MAX_INPUT_LENGTH * 2;

테스트

  1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트

    • 테스트 결과는 리팩토링하기 전과 같아야 한다.
  2. 가능하다면 기호 상숫값을 변경한 후 컴팡리해서 테스트

    • 기호 상수의 값을 다른 값으로 변경한 후 테스트하면 빠드린 곳이 없는지 확인할 수 있다.
    • COMMAND_ATTACK 값을 0에서 1000으로 변경해도, 매직 넘버가 없다면 테스트가 성공하지만 매직 넘버가 있다면 테스트가 실패한다.

리팩토링 후

public class MagicNumber {
    public static void main(String[] args) {
        MagicNumber magicNumber = new MagicNumber();
        MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
        player.action(Player.COMMAND_ATTACK);
        player.action(Player.COMMAND_DEFENCE);
    }

    public class Player {

        public static final int COMMAND_ATTACK = 0;
        public static final int COMMAND_DEFENCE = 1;


        private String name;

        public Player() {
        }

        public Player(String name) {
            this.name = name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void action(int command) {
            if (command == COMMAND_ATTACK) {
                attack();
            } else if (command == COMMAND_DEFENCE) {
                defence();
            }
        }

        private void attack() {
            System.out.println(this.name + " is going to Attack!!");
        }

        private void defence() {
            System.out.println(this.name + " is going to Defence!!");
        }

    }
}
  • 기호 상수가 충분한 정보를 제공하므로 주석이 필요 없다.

더 나은 리팩토링

  • 기호 상수로 만든다고 해도 실제로는 상수 값이므로 매직 넘버를 직접 적어도 문제없이 컴파일 된다.
  • 실수가 생길 수 있다.
  • 커맨드 클래스 객체를 활용하거나 enum을 활용한다.

enum 활용

  • 자바 5부터 enum을 사용할 수 있다.
public class EnumMagicNumber {
    public static void main(String[] args) {
        EnumMagicNumber magicNumber = new EnumMagicNumber();
        Player player = new Player("Hoonmaro");
        player.action(Player.Command.ATTACK);
        player.action(Player.Command.DEFENCE);
    }
}

// Player 클래스 분리
public class Player {

    public enum Command {
        ATTACK,
        DEFENCE
    }

    private String name;

    public Player() {
    }

    public Player(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void action(Command command) {
        if (command == Command.ATTACK) {
            attack();
        } else if (command == Command.DEFENCE) {
            defence();
        }
    }

    private void attack() {
        System.out.println(this.name + " is going to Attack!!");
    }

    private void defence() {
        System.out.println(this.name + " is going to Defence!!");
    }

}

  • 기존 Player Inner 클래스를 분리하였다.
  • 증첩 enum 은 암묵적으로 static 클래스 이기 때문에 Inner 클래스 안에 선언할 수 없기 때문이다.
  • Java Language Spec 8.9 Enums 참고
  • enum을 활용하여 직관적으로 의미를 전달 할 수 있다.
  • 상수가 아니므로 잘못 사용된 곳에서 컴파일에서 경고가 발생하여 에러를 사전에 방지할 수 있다.

기호 상수가 적합하지 않은 경우

  • 배열 길이
    • 배열 길이는 배열 객체에 length라는 필드가 있다.
    • 기호 상수가 언제나 올바른 배열길이가 아닐 수도 있다.
  • 잘 알려진 값을 대체하는 기호 상수 (오히려 가독성이 떨어짐)

바이트 코드에 내장된 상수에 주의

  • 필드값이 컴파일할 때 정해지는 상수일 경우 리컴파일시 변경된 값으로 바뀌겠지만,
  • 이를 사용하는 다른 클래스에서는 리컴파일 하기 전에 이전 상수 값을 가지고 있으므로 문제가 발생한다.
  • 매직 넘버를 치환한 모든 소스코드를 리컴파일 해야 정상 작동 된다.

객체지향 프로그래밍

  • Object Oriented Programming, OOP *객체
    • 생활에서 일종의 물건
    • 속성(Attribute)와 행동(Action)을 가짐
  • OOP는 속성은 변수, 행동은 함수로 객체의 개념을 프로그램으로 표현

클래스, 객체, 인스턴스

  • 클래스는 객체의 구체적인 형태를 설계한 명령어의 집합, 템플릿 또는 설계도
  • 객체는 속성과 행위로 구성된 사물 또는 추상적 개념들의 정보를 표현한 것
  • 인스턴스는 클래스로부터 인스턴스화를 통해 구현된 객체

추상화 기법

  • 추상화 기법의 하나로 분류/인스턴스화(classification/instantiation) 개념 존재
  • 분류는 객체들을 공통적인 속성을 공유하는 개념으로 범주화
  • 인스턴스화는 추상화된 범주로부터 실재하는 객체를 만드는 과정을 의미
  • 인스턴스라는 말은 추상적인 개념과 구체적인 객체 사이의 관계에 초점에 맞춘 용어

프로그램에서의 클래스, 인스턴스

  • 추상화의 과정을 거쳐 속성과 행위를 가진 객체를 클래스로 표현하며, 클래스는 변수와 메서드를 가진다
  • 프로그램 언어별로 클래스를 만드는 문법을 사용하여 인스턴스를 생성하게 된다.
    • Java: ExampleClass exampleClass = new ExampleClass();
    • Python: example_class = ExampleClass()

문법

[접근지정자] [기타제어자] class 클래스명 [extends Super클래스] [implements 인터페이스…] {
	/* 필드(멤버변수) */
	/* 메서드(멤버함수) */
}
  • 멤버: 클래스 안에 선언된 것들

클래스명 작성 규칙

작성규칙
하나 이상의 문자로 이루어져야 한다 Car, SportsCar
첫 번째 글자는 숫자가 올 수 없다 Car, 3Car(x)
'$','_' 외의 특수 문자는 사용할 수 없다 $car, _Car, @Car(x), #Car(x)
자바 키워드(예약어)는 사용할 수 없다 int(x), for(x)
단일 단어 => 첫문자는 대문자 혼합 단어 => 각 단어 첫글자는 대문자

예제

  • Cat 클래스
public class Cat {
	
	/* 클래스 필드 (객체의 속성) */
	// 이름
	private String name;
	// 품종
	private String breed;
	// 나이
	private int age;
	
	/* 클래스 메서드 (객체의 행동) */
	public String cry() {
		return "냐옹";
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setBreed(String breed) {
		this.breed = breed;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public String getName() {
		return this.name;
	}
	
	public String getBreed() {
		return this.breed;
	}
	
	public int getAge() {
		return this.age;
	}
	
	// 모든 클래스의 부모인 Object 클래스의 toString 메서드 오버라이드
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("Cat{");
			sb.append("name='").append(name).append('\'');
			sb.append(", breed='").append(breed).append('\'');
			sb.append(", age='").append(age).append('\'');
			sb.append('}');
		return sb.toString();
	}
}
  • Cat 클래스 인스턴스화
public class CatClassTest {
	public static void main(String[] args) {
		// new 연산자를 통해 Cat 클래스를 인스턴스화
		Cat nabi = new Cat();
		nabi.setName("나비");
		nabi.setBreed("페르시안");
		nabi.setAge(2);
		System.out.println(nabi);
	}
}
  • 결과
Cat{name='나비', breed='페르시안', age='2'}

예제 설명

  • Cat 클래스를 만들고 클래스의 필드와 메서드를 선언
  • CatClassTest 클래스에서 Cat 클래스를 new 연산자를 통해 인스턴스화하여 인스턴스를 생성 후 nabi라는 변수에 레퍼런스 저장
  • 이 때 new 연산자를 통해 생성된 인스턴스는 힙(heap) 메모리 영역에 저장되며 nabi 변수는 메모리 레퍼런스(주소)를 가지고 있다.
  • setter(세터) 메서드를 통해 name, breed, age를 세팅 후 오버라이드한 toString 메서드를 통해 필드값들을 출력한다.

구성 멤버

필드(field)

  • 객체의 고유 데이터, 상태 정보를 저장하는 곳
  • 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재
  • 초기화 하지 않으면 각 자료형의 기본값으로 초기화 됨
  • 필드선언
    • 생성자 선언과 메소드 선언의 앞 뒤 어떤 곳에서도 필드 선언 가능
    • 단, 생성자와 메소드 내부에서는 선언될 수 없음 (생성자,메소드 내부의 변수는 지역변수)
  • 필드사용
    • 필드 값을 읽고 변경하는 작업
    • 클래스 내부에서는 단순히 필드 이름으로 읽고 변경
    • 클래스 외부에서는 클래스로부터 객체 생성 후 사용 (일반적으로 직접 접근 못하게 캡슐화)

생성자(Constructor)

  • new 연산자로 호출되는 특별한 중괄호 블록
  • 객체 생성 시 초기화 담당
  • 필드를 초기화하거나 메소드를 호출해서 객체를 사용할 준비
  • 클래스 이름으로 되어 있고 리턴 타입이 없다
  • 생성자 선언
    • 생성자를 선언 안해도 기본적으로 컴파일시 디폴트 생성자가 생성됨
    • 생성자는 메소드와 비슷한 모양을 가지나, 리턴 타잆이 없고 클래스 이름과 동일
    • 클래스에 생성자가 명시적으로 선언되어 있을 경우 반드시 선언된 생성자를 호출해서 객체를 생성
    • 하나라도 인자값을 가진 다른 생성자를 호출할 경우 디폴트 생성자는 자동으로 생성되지 않으므로 디폴트 생성자를 호출하기 위해선 디폴트 생성자도 명시적으로 선언해줘야 함
  • 필드 초기화
    • 필드를 선언할 때 초기값을 주면 동일한 클래스로부터 생성되는 객체들은 모두 같은 데이터를 가짐
    • 객체 생성 시점에 외부에서 제공되는 다양한 값들로 초기화 되어야 한다면 생성자에서 초기화 해야 함
  • 관례적으로 필드와 동일한 이름을 갖는 매개변수 사용
    • 이 경우 필드와 매개변수 이름이 동일하므로 생성자 내부에서 해당 필드에 접근할 수 없다
    • why? 동일한 이름의 매개 변수가 사용 우선순위가 높다. 따라서 this 를 사용한다.
    • this는 객체 자신의 참조
public class Cat {
	private String name;
	public setName(String name) {
		// 필드 name과 매개변수 name의 이름이 같다
		// name = name
		this.name = name
	}
	
}
  • 생성자 오버로딩
    • 매개변수를 달리하는 생성자를 여러 개 선언하는 것
    • 오버로딩 시 주의점은 매개 변수의 타입과 개수 그리고 선언된 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 오버로딩이라고 볼 수 없음
  • 다른 생성자 호출(this())
    • 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드 발생
    • 이 경우 필드 초기화한 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선
    • 생성자에서 다른 생성자 호출할 때 this() 코드 사용
    • this()는 자신의 다른 생성자를 호출하는 코드, 반드시 생성자 첫줄에서만 허용
public Cat(String name, String breed) {
        this.name = name;
        this.breed = breed;
}

public Cat(String name, String breed, int age) {
        this(name, breed);
        this.age = age;
}


메서드(Method)

  • 객체의 동작에 해당하는 중괄호 블록
  • 메서드 호출 => 중괄호 블록 내 모든 코드 실행
  • 용도
    • 필드 읽고 수정
    • 다른 객체 생성해서 다양한 기능 수행
    • 객체 간의 데이터 전달 수단
    • 이외 다양한 행동 구현
  • 메서드 선언
[접근제한자] [기타제어자] 반환자료형 메서드명(매개변수) {
    [return 리턴값;]
}
  • 메서드 선언부 = 메서드 시그너처

  • 리턴 타입

    • 메서드가 실행 후 리턴하는 값의 타입
    • 메서드 실행 후 결과를 호출한 곳에 넘겨줄 경우에는 리턴 값이 있어야 함
    • 리턴 값의 타입은 선언부의 반환자료형과 동일해야 함
    • 리턴 타입이 있다고 해서 반드시 리턴값을 변수에 저장할 필요 없음
    • void 타입의 경우 리턴문 없이 사용 가능
  • 매개 변수의 수를 모를 경우

    • 매개 변수를 배열 타입으로 선언
    • … 으로 선언후 리스트 나열
public int sum(int ... args) {
        return IntStream.of(args).sum();
}
  • 메서드 호출
  • 메서드는 클래스 내/외부의 호출에 의해 실행
    • 클래스 외부에서 호출할 경우 우선 클래스로부터 객체 생성
    • 클래스 참조변수 = new 클래스(매개값); * 참조변수.메서드(매개값); (리턴값 없거나, 받지 않을 경우) * 타입 변수 = 참조변수.메서드(매개값); (리턴값 받고 싶을 때)
  • 메서드 오버로딩
    • 클래스 내에 같은 이름의 메서드를 여러 개 선언하는 것
    • 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다 (시그니처가 달라야 한다)

이것이 자바다 리뷰

이것이 자바다

부제: 신용권의 Java 프로그래밍 정복
이것이 자바다
* 저자 : 신용권
* 출간 : 2015-01-06
* 페이지 : 1224 쪽
* ISBN : 9788968481475
* 물류코드 :2147


이것이자바다 상세정보




  ‘이것이 자바다’는 비전공자로 국비지원 학원에서 자바를 시작하면서 교재로 사용한 책이다. 학원을 다니기 전에 자바를 독학할 때 ‘자바의 정석(2판)’을 봤었는데, 고등학교 때 보던 수학의 정석의 악몽이 생각나던 스타일이었다. 그만큼 정석적으로 자바에 대해서 상세하게 잘 설명한 책이기도 하지만 책의 표지만큼 딱딱했던 느낌을 지울 수 없었다.
  그러나 ‘이것이 자바다’는 표지부터 세련되었다. 흰 바탕에 우주항공에서 나올 법한 스케치 그림과 파란색 타이틀은 시원하면서도 뭔가 있구나 싶었다. 아마 학원에서 공부를 막 시작했던 설렘을 담아서 그랬던 것일지도 모르겠다.
  처음 책을 보면 저자소개와 서문, 목차부터 보게된다. 저자 신용권씨는 20년이 넘은 경력을 가진 개발자고 삼성멀티캠퍼스 자바 전임교수를 거쳐 현재는 SK Planet에서 운영하는 T 아카데미에서 강사로 활동 중이시다. 실무 경험과 탄탄한 강사 커리어를 겸비하셔서 그런지 여러 노하우들이 책 속에 베어있다.
이 책은 2권으로 구성되어있는데 목차를 보면 1권에는 언어의 기본 문법과 자바의 핵심인 JVM 메모리 구조와 OOP를 구현하는 클래스, 인터페이스 등을 설명한다. 2권에서는 Advanced한 개념으로 컬렉션 프레임워크와 쓰레드, NIO 등이 나온다. 특히 자바의 정석에서는 없던 JavaFX와 자바8 문법도 설명해줘 더 좋았던 것 같다.

  책도 쉽게 읽히고 정리하기에도 편하게 개념을 설명해준다. 개인적으로 헤드퍼스트 시리즈는 이샇안 스토리를 엮으면서 개념을 정리하기 힘들었었는데 ‘이것이 자바다’는 아주 좋았다. 블로그 글도 대부분 이 책을 참고한 것이다. 특히, 참조 객체 파트로 들어가면서 육각형으로 표현된 메모리 주소를 통해 값이 아닌 주소를 참조하는 것에 대해 잘 설명하고 있다. 당시에는 잘 몰랐던 JVM 구조에 대해서 알 수 있었다. (얼마나 중요한 개념이었는지는 나중에야 알았다.)
  지금은 자바 개발자로 일하고 있지만, 무언가 부족함을 느낄 때 항상 책을 찾아보게 된다. 그 어떤 구글링을 통해 보는 글 보다 잘 정리되어 있다. (물론 더 깊게 알기 위해해선 구글링도 필요하다!)

  당시에는 이용하지 않았지만, 저자가 운영하는 카페에서 인터넷 강의도 있고 질문에 답변도 잘 달아주신다. 또, 이벤트를 통해 프로그래밍 콘테스트 같은 것도 하고 있어 혼자 공부하는 사람들에게도 동기부여가 되어 끝까지 공부할 수 있게 도와준다.


자바를 시작한다면 꼭 읽어보라고 권해주고 싶은 책이다. 읽고나면 아, 이것이 자바구나라고 탄성을 자아내는 책이다.

스프링의 소개

스프링이란?

  • 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크
  • EJB(Enterprise Java Beans)의 단점을 보완한 프레임 워크
  • DI(Dependency Injection, 의존성 주입)과 AOP(Aspect-Oriented Programing, 관점 지향 프로그래밍)이 지원되는 경량 컨테이너 & 프레임워크


  • 자바 플랫픔으로서 자바 애플리케이션을 개발하는데 필요한 하부 구조를 포괄적으로 제공한다.
  • 스프링이 하부 구조를 처리하므로 개발자는 애플리케이션 개발에 집중할 수 있다.

경량화

  • 스프링 자체가 가볍거나 작은 규모의 코드로 이루어진 것은 아니다.
  • 오히려 스프링은 20여개의 모듈로 세분화되고 복잡하고 방대한 코드를 가진 프레임워크이다.
  • 경량화가 특징인 이유는 기존 자바 엔터프라이즈 기술의 불필요한 복잡함에 반대되는 개념에서 시작되었다.
  • 주류 기술이었던 EJB는 고가의 무거운 자바 서버(WAS)가 필요했고, 다루기 힘든 설정파일 구조, 패키징, 불편한 배포 등이 단점이었다.
  • 반면, 스프링은 톰캣과 같은 단순한 서버환경에서도 동작하며, 단순한 개발환경으로도 엔터프라이즈 애플리케이션 개발하는데 충분하다.
  • 또 EJB 등의 기존 프레임워크에서 만들어진 코드에 비해 코드량이 적고 단순하기도 하다.
  • 즉, 기존에 비해 빠르고 간편하게 애플리케이션을 개발할 수 있어 생산성이 뛰어난 프레임워크다.

스프링 기능

  • 경량 컨테이너로서 자바 객체를 직접 관리
    • 각각의 객체 생성, 소멸과 같은 라이프 사이클을 관리하며 스프링으로부터 필요한 객체를 얻어올 수 있다.
  • POJO(Plain Old Java Object) 방식의 프레임워크
    • 일반적인 J2EE 프레임워크에 비해 구현을 위해 특정한 인터페이스를 구현하거나 상속을 받을 필요가 없어 기존에 존재하는 라이브러리 등을 지원하기에 용이하고 객체가 가볍다.
  • 제어 반전(또는 제어의 역전)(IoC: Inversion of Control)을 지원
    • 컨트롤의 제어권이 사용자가 아니라 프레임워크에 있어서 필요에 따라 스프링에서 사용자의 코드를 호출한다.
  • 의존성 주입(DI: Dependency Injection)을 지원
    • 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다.
  • 관점 지향 프로그래밍(AOP: Aspect-Oriented Programming)을 지원
    • 트랙잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.
  • 확장성이 높음
    • 스프링 프레임워크에 통합하기 위해 간단하게 기존 라이브러리를 감싸는 정도로 스프링에서 사용이 가능하다.
    • 수많은 라이브러리가 이미 스프링에서 지원되고 있고 스프링에서 사용되는 라이브러리를 별도로 분리하기도 용이하다.
  • 다른 프레임워크들의 통합

스프링의 구성

  • Spring Core
    • Core 컨테이너 기능
    • Spring 프레임워크의 근간이 되는 IoC (또는 DI) 기능을 지원하는 영역
    • BeanFactory를 기반으로 Bean 클래스들을 제어할 수 있는 기능을 지원한다.
  • Spring Context
    • Spring Core에서 지원하는 기능외에 추가적인 기능들과 좀 더 쉬운 개발이 가능하도록 지원한다.
    • 또, JNDI, EJB 등을 위한 Adaptor들을 포함하고 있다.
  • AOP
    • Spring 프레임워크에 AOP를 지원하는 기능이다.
  • DAO
    • JDBC 기반하의 DAO 개발을 좀 더 쉽고, 일관된 방법으로 개발하는 것이 가능하도록 지원한다.
  • ORM
    • Object-Relational Mapping 프레임워크인 Hibernate, MyBatis, JDO와의 결합을 지원하기 위한 기능이다.
  • Spring Web
    • Web Application 개발에 필요한 Web Application Context와 MultipartRequest 등의 기능을 지원한다.
  • Portlet
    • 포탈 서버에서 돌아가는 독립된 웹 애플리케이션 원격 지원 기능

스프링 창시자

  • 스프링의 창시자인 로드 존슨(Rod Johnson)
    • 2003년 『Expert One-on-One J2EE Design and Development』책을 출간하여 JE22 애플리케이션 설계와 독창적인 개발 전략을 다루었다.
    • “항상 프레임워크 기반으로 접근하라”를 강조하였다.

오픈소스

  • 스프링은 아파치 라이선스 2.0(Apache License Ver 2.0)을 따른다.
    • 영리 목적 및 비공개 프로젝트에 자유롭게 이용가능하다.
    • 스프링을 사용하고 원 저작자를 밝히고 라이선스 정보를 포함시키는 등의 의무사항을 지켜야 한다.
  • 오픈소스는 무료로 이용할 수 있고 소스코드가 모두 공개되어 수많은 다양한 환경의 개발자들이 사용하므로 테스트에 용이하고, 빠른 피드백을 받을 수 있는 장점이 있다.
  • 반면, 오픈소스 프로젝트 팀의 사정 상 개발이 여의치 않을 경우 프로젝트가 중단에 드랍되거나 버그 수정에 많은 시간이 소요될 수도 있다.
  • 특히 스프링은 애플리케이션 프레임워크로 그 규모가 일반 오픈 소스 프로젝트에 비해 크고 엔터프라이즈 개발자들에게는 중요한 프레임워크이므로 오픈 소스의 문제점을 안고 가기에는 부정적이었다.
  • 그래서 로드 존슨과 스프링 오픈소스화의 대표적 공로자인 유겐 흴러(Juergen Hoeller)와 자바 엔터프라이즈에서 최상급 개발자들이 스프링 소스(SpringSource)를 만들어 스프링 프레임워크의 실질적인 개발을 책임지고 있다.
  • 스프링 소스는 2009년 세계적인 IT업체인 VMware가 인수하여 더욱 안정적인 환경에서 개발되고 있다.


열거 타입(Enumeration Type)

  • 한정된 값만을 갖는 데이터 타입이다.

열거 타입 선언

  • 열거 타입의 이름을 정하고 열거 타입 이름으로 소스 파일(.java)을 생성해야 한다.
  • 열거 타입 이름은 관례적으로 첫 문자를 대문자로하고 나머지는 소문자로 구성한다.
  • 만약 여러 단어로 구성된 이름일 경우 각 단어의 첫 문자는 대문자로 작성하는 것이 관례이다.
  • 예) Week.java, MemberLevel.java
  • 형식
  • 
    public enum 열거타입이름 {
    	(열거 상수 선언)
    }
    
    
    
  • 열거 상수는 열거 타임의 값으로 사용되며 관례적으로 모두 대문자로 작성한다.
  • 만약 열거 상수가 여러 단어로 구성될 경우 단어 사이를 밑줄(_)로 연결한다.
  • 
    public enum Month {
    	JANUARY,
    	FEBRUARY,
    	MARCH,
    	APRIL,
    	MAY,
    	JUNE,
    	JULY,
    	.
    	.
    }
    
    
    
  • 열거 상수는 열거 객체로 생성된다. 해당 열거 타입의 열거 상수 개수만큼 객체가 열거 타입 객체가 생성된다.
  • 열거 타입 Month의 경우 JANUARY부터 DECEMBER까지 12개의 열거 상수는 힙 영역에 Month 객체로 생성된다.
  • 그리고 메소드 영역에 생성된 열거 상수가 해당 해당 Month 객체를 각각 참조한다.


열거 타입 변수

  • 열거 타입을 선언했다면 열거 타입은 하나의 데이터 타입이므로 변수를 선언하고 사용한다.
  • 형식
  • 열거타입 변수;
  • 
    Month thisMonth;
    Month birthMonth;
    
    
  • 열거 타입 변수를 선언 후 열거 상수를 저장할 수 있다. 이 때, 열거 상수는 반드시 열거타입.열거상수로 사용된다.
  • 열거 타입 변수는 null 값도 저장할 수 있다.
  • 형식
  • 열거타입 변수 = 열거타입.열거상수;
  • 
    Month thisMonth = Month.JUNE;
    
    
  • 열거 타입 변수 thisMonth는 스택 영역에 생성된다.
  • thisMonth에 저장되는 값은 Month.JUNE 열거 상수가 참조하는 객체의 번지이다.
  • 즉, 열거 상수 Month.JUNE와 thisMonth 변수는 서로 같은 Month 객체를 참조하게 된다.
  • 그러므로 thisMonth == Month.JUNE 상수의 비교 결과는 true가 된다.

열거 객체의 메소드

  • 열거 객체는 열거 상수의 문자열을 내부 데이터로 가지고 있다.
  • 열거 객체의 메소드들은 java.lang.Enum 클래스에 선언된 메소드인데, 모든 열거 타입은 컴파일 시에 Enum 클래스를 상속하게 되어 있어 열거 객체에서 해당 메소드들을 사용할 수 있다.


리턴 타입

메소드(매개 변수)

설명

String

name()

열거 객체의 문자열을 리턴

int

ordinal()

열거 객체의 순번(0부터 시작) 리턴

int

compareTo()

열거 객체를 비교해서 순번 차이를 리턴

열거 타입

valueOf(String name)

주어진 문자열의 열거 객체를 리턴

열거 배열

values()

모든 열거 객체들을 배열로 리턴


  • name()
    • 열거 객체가 가지고 있는 문자열을 리턴한다.
    • 리턴되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일하다.
    • 예)
    • 
      Month thisMonth = Month.JUNE;
      String month = thisMonth.name(); // month = "JUNE"
      
      
  • ordinal()
    • ordinal() 메소드는 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다.
    • 열거 객체의 순번은 열거 객체 타입을 정의할 때 주어진 순번이며 0번부터 시작한다.
    • 예)
    • 
      Month thisMonth = Month.JUNE;
      int ordinal = thisMonth.ordinal(); // ordinal = 5
      
      
  • compareTo()
    • 매개값으로 주어진 열거 객체를 기준으로 전후로 몇 번째 위치하는지를 비교한다.
    • 열거 객체가 매개값의 열거 객체보다 순번이 빠르다면 음수, 순번이 늦다면 양수가 리턴된다.
    • 예)
    • 
      Month month1 = Month.JUNE;
      Month month2 = Month.MARCH;
      int result1 = month2.compareTo(month1); // -3
      int result2 = month1.compareTo(month2); // 3
      
      
  • valueOf()
    • 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다.
    • 이 메소드는 외부로부터 문자열을 입력 받아 열거 객체로 변환할 때 유용하게 사용할 수 있다.
    • 예)
    • 
      Month thisMonth = Month.valueOf("JUNE");
      
      
  • values()
    • 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다.
    • 예)
    • 
      Month[] months = Month.values();
      for(Month month : months) {
      	System.out.println(month); // JANUARY, FEBRUARY, … DECEMBER
      }
      
      


배열

  • 정의: 같은 타입의데이터를 메모리상에 연속적으로 나열시키고, 각 데이터에 인덱스(index)를 부여해 놓은 자료 구조
  • 인덱스: 첨자값, [](대괄호) 사용
  • 배열은 참조 타입에 속한다.

배열의 선언


/* 대괄호의 위치 차이 */
int[] intArray; // 타입[] 변수(배열명);
int intArray[]; // 타입 변수(배열명)[];

/* null로 초기화 */
String[] stringArray = null;

/* 값 목록을 가진 배열 생성 */
String[] names = { "홍길동", "전지현", "설현", "수지" };
int[] scores = { 80, 90, 95, 99 };

/* new 연산자로 바로 배열 객체 생성 */
double[] doubleArray = new double[5]; // 배열의 길이를 지정한다. (이 때 인덱스는 0~4)

/* 컴파일 에러 */
String[] names = null;
// names = { "홍길동", "전지현", "설현", "수지 }; -> 배열 변수 선언 후 중괄호를 사용한 배열 생성은 에러 발생
names = new String[] { "홍길동", "전지현", "설현", "수지 };


  • 중괄호{}는 주어진 값들을 가진 배열 객체를 힙(heap) 메모리 영역에 생성하고, 배열 객체의 번지(주소)를 리턴한다.
  • 배열 변수는 리턴된 번지를 저장하여 참조한다.
  • 위의 예에서 "홍길동"은 names[0] "전지현"은 names[1], 80은 scores[0] 이다.

배열의 값 바꾸기


String[] names = { "홍길동", "전지현", "설현", "수지" };
System.out.println("names[0] > " + names[0]); // 홍길동
names[0] = "에일리";
System.out.println("names[0] > " + names[0]); //에일리

  • 배열의 값 바꾸기는 = (대입 연산자)를 사용한다.

배열 길이

  • 배열의 길이: 배열에 저장할 수 있는 전체 항목 수를 말한다.
  • 배열 객체의 length 필드(field)를 읽어야 한다.
  • 필드(field): 객체 내부의 데이터
  • 배열의 마지막 요소의 인덱스는 배열 길이-1이다.

int[]  intArray = {100, 200, 300};
int len = intArray.length;
System.out.println("배열 intArray의 길이는 > " + len);

참조 타입(reference type)

  • 참조 타입의 종류: 배열, 열거, 클래스, 인터페이스

  • 참조 타입 변수는 스택(stack) 영역에 생성되어 힙(heap) 메모리 영역의 번지(주소)를 값으로 갖는다.

  • 주소를 통해 객체를 참조한다는 뜻에서 참조 타입이라고 부른다.

참조 변수의 ==, != 연산

  • 참조 타입 변수들 간의 ==, != 연산은 동일한 객체를 참조하는지, 다른 객체를 참조하는지 알아볼 때 사용된다.

  • 서로 다른 객체를 참조하는 참조 타입 변수들간의 비교는 기본 타입과 다른 결과를 나타낸다.

  • 예)

int x = 3;
int y = 3;
String hello = new String("안녕하세요");
String hi = new String("안녕하세요");

if ( hello == hi ) {
    System.out.println("안녕하세요.");
} else if ( hello != hi) {
    System.out.println("안녕 못해요.");
}

if (x == y) {
   System.out.println("X와 y는 같습니다.");
} else if ( x != y) {
   System.out.println("x와 y는 다릅니다.");
}
  • 출력
  • 안녕 못해요.
    x와 y는 같습니다.
    

참조 타입 변수의 null

  • 참조 타입 변수가 null 값을 가질 경우 참조할 객체가 없으므로 객체는 힙 메모리 영역에 생성되지 않고, 변수만 스택 영역에서 생성되어 null 값을 가진다.
  • 따라서, null 값을 가진 참조 타입 변수의 ==, != 연산은 기본 타입과 동일하다.
  • 참조 타입 변수가 null 값을 가진 상황에서 프로그램을 실행할 경우 (혹은 프로그램 실행 중 참조 타입 변수가 null 값을 가질 경우) NullPointerException 예외가 발생한다.

String 타입

  • 문자열을 저장하는 참조 타입

  • 자바에서 String은 문자열 리터럴이 동일할 경우 객체를 공유하도록 되어 있다. (new 연산자로 새로운 객체를 생성하지 않고 = 대입 연산자로 같은 문자열을 저장할 경우)

  • String 객체의 equals() 메소드를 사용해서 객체에 상관없이 문자열을 비교할 수 있다.

  • 형식

    String x; // 기본값은 null
    x = "hello"; // 선언한 x에 문자열 값을 대입, " " (쌍따옴표) 사용
    String  y = "hello"; // 선언과 동시에 문자열 저장 x == y 는 true
    String  z = new String("hello"); // 새로운 객체 생성, y == z 는 false
    if ( y.equals(z) ) {
       System.out.println("true"); // true 출력
    }



JVM 구조

  • 실행될 클래스 파일을 메모리에 로드 후 초기화 작업 수행

  • 메소드와 클래스변수들을 해당 메모리 영역애 배치

  • 클래스로드가 끝난 후 JVM은 main 메소드를 찾아 지역변수, 객체변수, 참조변수를 스택에 쌓음
  • 다음 라인을 진행하면서 상황에 맞는 작업 수행(함수 호출, 객체 할당 등)

자바 메모리 구조



  • Class Loader: JVM내로 클래스를 로드하고 링크를 통해 배치하는 작업을 수행하는 모듈로써 런타임시 동적으로 클래스를 로드한다.
  • Execution Engine: Class Loader를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드를 실행한다. 이 때, Execution Engine은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다.
  • Garbage Collector: JVM은 Garbage Collector를 통해 메모리 관리 기능을 자동으로 수행한다. 애플리케이션이 생성한 객체의 생존 여부를 판단하여 더 이상 사용되지 않는 객체를 해제하는 방식으로 메모리를 자동 관리한다.
  • Runtime Data Areas: JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역이다. Class Loader에서 준비한 데이터들을 보관하는 저장소이다.

Runtime Data Areas


Runtime Data Areas & Heap Area



  • Method (Static) Area: JVM이 읽어들인 클래스와 인터페이스 대한 런타임 상수 풀, 멤버 변수(필드), 클래스 변수(Static 변수), 생성자와 메소드를 저장하는 공간이다.
  • Runtime Constant Pool
    • 메소드 영역에 포함되지만 독자적 중요성이 있다.
    • 클래스 파일 constant_pool 테이블에 해당하는 영역이다.
    • 클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 레퍼런스를 저장한다.
    • JVM은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리 상 주소를 찾아 참조한다
  • 메소드 영역/런타임 상수 풀의 사용기간 및 스레드 공유 범위
    • JVM 시작시 생성
    • 프로그램 종료 시까지
    • 명시적으로 null 선언 시
    • 구성 방식이나 GC 방법은 JVM 벤더마다 다를 수 있다.
    • 모든 스레드에서 공유한다.
  • Heap Area
    • JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역이다.
    • New 연산자로 생성된 객체 또는 객체(인스턴스)와 배열을 저장한다.
    • 힙 영역에 생성된 객체와 배열은 스택 영역의 변수나 다른 객체의 필드에서 참조한다.
    • 참조하는 변수나 필드가 없다면 의미 없는 객체가 되어 GC의 대상이 된다.
    • 힙 영역의 사용기간 및 스레드 공유 범위
      • 객체가 더 이상 사용되지 않거나 명시적으로 null 선언 시
      • GC(Garbage Collection) 대상
      • 구성 방식이나 GC 방법은 JVM 벤더마다 다를 수 있다.
      • 모든 스레드에서 공유한다.
  • Stack Area
    • 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 할당된다.
    • 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행한다.
    • 선입후출(FILO, First In Last Out) 구조로 push와 pop 기능 사용
    • 메소드 호출 시 생성되는 스레드 수행정보를 기록하는 Frame을 저장
    • 메소드 정보, 지역변수, 매개변수, 연산 중 발생하는 임시 데이터 저장
    • 기본(원시)타입 변수는 스택 영역에 직접 값을 가진다.
    • 참조타임 변수는 힙 영역이나 메소드 영역의 객체 주소를 가진다.
  • PC Register
    • 현재 수행 중인 JVM 명령 주소를 갖는다.
    • 프로그램 실행은 CPU에서 인스트럭션(Instruction)을 수행.
    • CPU는 인스트럭션을 수행하는 동안 필요한 정보를 CPU 내 기억장치인 레지스터에 저장한다.
    • 연산 결곽값을 메모리에 전달하기 전 저장하는 CPU 내의 기억장치
  • Native Method Stack Area
    • 자바 외 언어로 작성된 네이티브 코드를 위한 Stack이다.
    • 즉, JNI(Java Native Interface)를 통해 호출되는 C/C++ 등의 코드를 수행하기 위한 스택이다.
    • 네이티브 메소드의 매개변수, 지역변수 등을 바이트 코드로 저장한다.

Heap Area

  • Young Generation: 이 영역은 자바 객체가 생성되자마자 저장되고, 생긴지 얼마 안되는 객체가 저장되는 공간이다. 시간이 지나 우선순위가 낮아지면 Old 영역으로 옮겨진다. 이 영역에서 객체가 사라질 때 Minor GC가 발생한다.
  • Old(Tenured) Generation: Young Generation 영역에서 저장되었던 객체 중에 오래된 객체가 이동되어 저장되는 영역이다. 이 영역에서 객체가 사라질 때 Major GC(Full GC)가 발생한다.

None Heap Area

  • Permanent Generation(Java 7 이전): 클래스 로더에 의해 로드되는 클래스, 메소드 등에 대한 메타 정보가 저장되는 영역으로 JVM에 의해 사용된다. 리플렉션을 사용하여 동적으로 클래스가 로딩되는 경우에 사용된다. 내부적으로 리플렉션 기능을 자주 사용하는 Spring Framework를 이용할 경우 이 영역에 대한 고려가 필요하다. 런타임시 사이즈를 조절할 수 없어 OutOfMemoryError: PermGen Space error 가 발생하는 메모리 영역이다. JVM 실행시 PermSize와 MaxPermSize 옵션을 사용한다.
  • Metaspace(Java 8 이후): Permanent Generation이 Metaspace로 변경 되었다. 기능은 비슷하지만, 주요 차이점은 동적으로 사이즈를 바꿀 수 있다. JVM 옵션도 PermGem관련하여 사라지고, Metaspace 관련하여 MetaspaceSize 및 MaxMEtaspaceSize 옵션이 새로 생겼다.

참고자료


수정이력

  • 2019-05-07: Permanent Generation None Heap Area로 재분류 및 설명 추가, Meataspace 추가

+ Recent posts