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

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

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

이것이 자바다 리뷰

이것이 자바다

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


이것이자바다 상세정보




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

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

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


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

열거 타입(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 추가

반복문

  • 어떤 작업이 반복적으로 수행되도록 할 때 사용되는 구문

반복문의 종류

  • for문: 주로 반복횟수를 알고 있을 때 사용
  • 향상된 for문: 컬렉션과 배열을 처리할 때 더 효과적인 문법으로 JDK5.0 이후에 사용 가능
  • while문: 조건식으로 반복할 때 주로 사용
  • do-while문: 반복하기 전에 먼저 실행해야 하는 구문이 있을 때 사용

for문

  • for문 선언 형식
  • for( 초기식; 조건식; 증감식) {
      // 명령코딩
    }
    
  • 예제: 1부터 10까지의 합
  • public class ExFor {
    	public static void main (String[] args)
    	{
    		int i , sum = 0;   
            for ( i=1 ; i<=10 ;  i++) {
            	System.out.printf(i==10?"%d":"%d+",i);
    			sum+= i;    
    		}
            System.out.println("="+sum);
    	}
    }
    
    1+2+3+4+5+6+7+8+9+10=55
    

향상된 for문

  • 향상된 for문 선언 형식
  • for( 자료형  변수명 : 배열 또는 컬렉션 ) {
     // 명령코딩
    }
    
  • 예제: 배열에 담긴 국어 점수 출력
  • public class ExEnhancedFor
    {
    	public static void main (String[] args)
    	{
    		int [] kors = new int[5];
    		kors[0] = 100;
    		kors[1] = 90;
    		kors[2] = 77;
    		kors[3] = 23;
    		kors[4] = 88;
    		
    		for (int kor : kors) {
    			System.out.println(kor);
    		}
    	}
    }
    
    100
    90
    77
    23
    88
    

while문

  • while문 선언 형식
  • while( 조건식 ) {
     // 조건식이 참일 동안 반복될 명령 코딩
    }
    
  • 예제: 10부터 0까지의 합
  • public class ExWhile
    {
    	public static void main (String[] args)
    	{
    		int i = 10 , sum = 0;
    		while( i >= 0 ){
    			System.out.printf(i==0?"%d":"%d+",i);
    			sum += i; 
    			i--;
    		}
    		System.out.println("="+sum); 
    	}
    }
    
    10+9+8+7+6+5+4+3+2+1+0=55
    

do-while문

  • do-while문 선언 형식
  • do{
    // 먼저 한번 실행하고, 조건식이 참일 동안 반복할 명령 코딩
    }while( 조건식 );
    
  • 예제: 증가치에 따라 증가하는 항 값의 누적 합
  • public class ExDoWhile
    {
    	public static void main (String[] args) throws java.lang.Exception
    	{
    		int h = 1; // n 항의 누적 합
    		int n = 1; // 각 항: [1]+[2]+[4]+[7]+[11]+[16]+[22]
    		int k = 0; // 증가치:   1   2   3   4    5    6
     
    		do{
    			k+=1;
    			n += k;
    			if( n > 100) break;
    			h += n; // (누적)
    			System.out.printf("%d - %d - %d\n",n,k,h);
    		}while(true);	
    	}
    }
    
    2 - 1 - 3
    4 - 2 - 7
    7 - 3 - 14
    11 - 4 - 25
    16 - 5 - 41
    22 - 6 - 63
    29 - 7 - 92
    37 - 8 - 129
    46 - 9 - 175
    56 - 10 - 231
    67 - 11 - 298
    79 - 12 - 377
    92 - 13 - 469
    

변수

  • 하나의 값을 저장할 수 있는 메모리 공간

변수의 선언

  • 타입 변수이름;
  • 타입은 변수에 저장되는 값의 종류와 범위를 결정지으므로 어떤 값을 변수에 저장할지 생각한 후 타입을 결정해야 한다.
  • 변수 이름은 메모리 주소에 붙여진 이름이다.
  • 프로그램은 변수 이름을 통해 메모리 주소에 접근하고, 그곳에 값을 저장하거나 그곳에 값을 읽는다.
  • 변수 이름은 자바 언어에서 정한 명명규칙을 사용해야 하며, 예약어는 사용할 수 없다.
  • 개발자는 변수 이름을 보고, 변수가 어떤 값을 저장하고 있는지 쉽게 알 수 있도록 의미 잇는 변수 이름을 지어주는 것이 좋다.
 int age; // 정수값을 저장할 수 있는 age 변수 선언
double value; // 실수값을 저장할 수 있는 value 변수 선언


변수 명명 규칙

작성 규칙

번째 글자는 문자이거나 $, _ 이어야 하고 숫자로 시작할 없다(필수)

가능: price, $price, _companyName

불가: 1v, @speed, $#value

영어 대소문자가 구분된다(필수)

firstname firstName 다른 변수

문자는 영어 소문자로 시작하되, 다른 단어가 붙을 경우 문자를 대문자로 한다

maxSpeed, firstName, carBodyColor

문자 (길이) 제한은 없다

 

자바 예약어는 사용할 없다(필수)

 



자바 예약어

분류

예약어

기본 데이터 타입

boolean, byte, char, short, int, long, float, double

접근 지정자

private, protected, public

클래스와 관련된

class, abstract, interface, extends, implements, enum

객체와 관련된

new, instanceof, this, super, null

메소드와 관련된

void, return

제어문과 관련된

if, else, switch, case, default, for, do, while, break, continue

논리값

true, false

예외 처리와 관련된

try, catch, finally, throw, throws

기타

transient, volatile, package, import, synchronized, native, final, static, strictfp, assert



변수의 범위

  • 변수는 선언된 { } (블록) 안에서만 사용 가능하다.
  • 클래스 블록안에 바로 선언된 변수를 전역 변수라고 한다.
  • 메소드(함수) 내에 선언된 변수를 지역 변수(local variable)
  • 변수는 선언된 블록 안에서만 사용 할 수 있다.
  • 엄밀히 말하면, 전역 변수는 인스턴스 변수와 클래스 변수로 구분할 수 있는데, 이는 추후 클래스를 다룰 때 더 자세히 설명하겠다.

비트 연산자

  • 0과 1로 표현이 가능한 정수타입만 비트 연산이 가능하다.

  • 피연산자를 int 형으로 자동 형변환

비트 반전 연산자(~), 틸드 연산자

  • 정수 타입의 피연산자에만 사용
  • 피연연사즐 2진수로 표현했을 때 비트값인 0을 1로, 1은 0으로 반전
  • 부호가 반대인 새로운 값이 산출됨

비트 논리 연산자(&, |, ^)

  • 비트논리 AND 연산자(&): 두 비트 모두 1일 경우에만 연산 결과가 1
  • 비트논리 OR 연산자(|): 두 비트 중 하나라도 1이면 연산 결과 1
  • 비트논리 XOR 연산자(^): 두 비트 중 하나는 1이고 다른 하나가 0일 경우 연산 결과는 1

비트 이동 연산자(<<, >>, >>>), 쉬프트 연산자

  • a << b: 정수 a의 각 비트를 b만큼 왼쪽으로 이동(빈자리는 0으로 채워짐)
  • a >> b: 정수 a의 각 비트를 b만큼 오른쪽으로 이동(빈자리는 정수 a의 최상위 부호 비트와 같은 값으로 채워짐)
  • a>>>b: 정수 a의 각 비트를 b만큼 오른쪽으로 이동(빈자리는 0으로 채워짐)




+ Recent posts