Open API Specfication 3?

OAS는 RESTful API에 대한 정보들을 정의하여 소스 코드에 접근하지 않아도 서비스의 기능을 검색, 확인하고 사용할 수 있게끔 지원해주는 표준 인터페이스이다.

 

OAS 3는 2017년 7월에 발표된 spec이며, 많은 개선이 이루어졌다고 한다. 해당 spec은 JSON, YAML을 통하여 작성할 수 있다. documentation generation tools를 사용하여서 해당 API의 기능, 테스트 코드 등을 사용자(클라이언트 개발자) 입장에서 쉽게 알 수 있게 해 준다.

 

스프링에서 사용할 수 있는 OAS 라이브러리

 

Format

  • 키 값에 대해 대소문자를 구분하며 특별한 경우에는 구분하지 않는다고 명시해야 한다.

  • spec의 schema는 고정된 필드 이름(식별자와)과 문자열, 패턴 값을 가진다.

      #식별자 / 문자열 or 패턴 값
      "field": [ 1, 2, 3 ]
  • (Json) 사용되는 Tag는 JSON Schema ruleset에 정의된 것들만 사용해야 한다.

  • YAML에서 사용되는 키 값은 YAML Failsafe schema ruleset를 무조건 따라야 한다.

  • API는 YAML 또는 JSON 형식으로 문서에 정의될 수 있지만, API 요청 및 응답 본문과 콘텐츠가 JSON 또는 YAML 일 필요는 없다.

 

Data Type

OAS의 타입은 JSON Schema Specification Wright Draft 00의 정의된 유형을 기반으로 한다.

  • Integer → int 32
  • → int 64
  • number → float
  • → double
  • string
  • → byte
  • → binary
  • → date
  • → date-time
  • → password
  • boolean

 

Rich Text Formatting

OAS는 description필드 전체에서 CommonMark라는 기본적인 마크 다운 형식을 지원한다.

  • 어떠한 텍스트 서식들을 렌더링 하는 경우에는 최소한의 마크다운 구문을 지원해야 한다.

 

Relative References in URLs

URL의 기본적인 속성은 모두 RFC3986에 정의된 상대 참조를 사용한다.

  • Server Object 형식을 사용하거나 Reference Object 형식을 사용한다.

      # Server Object
      # url, desciption, variables
      # Server에 대한 값을 나타내는 Object이다. (client에 제공되는)
      {
        "url": "https://development.gigantic-server.com/v1",
        "description": "Development server"
      }
      # 복수의 Server를 나타내는 경우
      {
        "servers": [
          {
            "url": "https://development.gigantic-server.com/v1",
            "description": "Development server"
          },
          {
            "url": "https://staging.gigantic-server.com/v1",
            "description": "Staging server"
          },
          {
            "url": "https://api.gigantic-server.com/v1",
            "description": "Production server"
          }
        ]
      }
      # port의 대한 값이나, default 설정, BasePath 등의 정보도 제공 가능하다.
      "port": {
                "enum": [
                  "8443",
                  "443"
                ],
                "default": "8443"
              },
              "basePath": {
                "default": "v2"
              }
    
      # Reference Object
      # $ref 
      # spec의 다른 component를 내부, 외부에서 참조하게끔 도와주는 Object 이다.
      {
          "$ref": "#/components/schemas/Pet"
      }

 

Example Object

요청, 응답, 매개 변수에 대한 예시를 보여준다.

# 요청 본문
requestBody:
  content:
    'application/json':
      schema:
        $ref: '#/components/schemas/Address'
      examples: 
        foo:
          summary: A foo example
          value: {"foo": "bar"}
        bar:
          summary: A bar example
          value: {"bar": "baz"}
    'application/xml':
      examples: 
        xmlExample:
          summary: This is an example in XML
          externalValue: 'http://example.org/examples/address-example.xml'
    'text/plain':
      examples:
        textExample: 
          summary: This is a text example
          externalValue: 'http://foo.bar/examples/address-example.txt'

# 응답 본문
responses:
  '200':
    description: your car appointment has been booked
    content: 
      application/json:
        schema:
          $ref: '#/components/schemas/SuccessResponse'
        examples:
          confirmation-success:
            $ref: '#/components/examples/confirmation-success'

# 매개 변수 
parameters:
  - name: 'zipCode'
    in: 'query'
    schema:
      type: 'string'
      format: 'zip-code'
    examples:
      zip-example: 
        $ref: '#/components/examples/zip-example'

 

Another Obejcts

  • 그 외에도 수많은 객체들이 존재한다.
  • Server Variable Object - 서버의 변수 정보를 나타낸다.
  • License Object - Lincense 정보를 나타낸다.
  • Component Object - OAS에서 제공하는 재사용 가능한 객체들의 집합이다.
  • Contact Object - API와 관련된 연락처 정보를 나타낸다
  • Info Object - API에 대한 메타 데이터를 제공한다
  • Path Item Object
  • Operation Object
  • External Documentation Object
  • Parameter Object
  • Request Body Object
  • Media Type Object
  • Encoding Object
  • Responses Object
  • Response Object
  • Callback Object

 

springdoc-openapi. 사용법

 

Gradle 의존성

implementation("org.springdoc:springdoc-openapi-ui:1.4.6")

 

Java Configuration 추가하기

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .components(new Components())
                .info(new Info().title("XXX Application API").description(
                        "XXX Application Descrptions.."));
    }
}

 

Sample Controller

@RestController
@RequestMapplng(path = "/posts")
@Tag(name = "Domain API Names", description = "API Description")
public class HelloController {

        @Operation(summary = "Find User by id", description = "ID serach by ID = {id} format"
                            ,tags = {"Domain API Names"})
        @ApiResponse(responseCode = "200", desription = "Status Code Description",
                            , content = (array = @ArraySchema(schema = @Schema(implementation = Post.class)))
        @GetMapping(value = "/{postId}", produces = { "application/json", "application/xml" })
        public ResponseEntity<List<Contact>> findById(
                @Parameter(description="Id of the Post for search.") @Pathvariable Long postId){
                Post postsById = PostService.findById(postId);
                return ResponseEntity.status(HttpStatus.OK).body(postsById);
        }
}

 

Open API Annotation

 

@Tag ( spring fox = @Api )

  • name : 컨트롤러의 도메인 이름 (PostController의 경우 Post)
  • description : Name에 대한 설명

@Operation ( spring fox = @ApiOperation)

  • summary : 해당 메서드의 동작 정보 요약
  • description : 해당 동작의 설명
  • tags : 상단에 정의된 Tag의 name과 동일하게 주는 값

@ApiResponse

  • responseCode : 성공시 전달할 HTTP Status Code
  • desription : 해당 Code 에 대한 설명
  • content : 응답되는 컨텐츠 유형 설정
    • array : 단일이 아닌 복수의 자원 응답이 있을 경우 사용

@ArraySchema

  • schema : 복수 자원 응답시 내부에 저장되는 리소스 설정

@Schema ( spring fox = @ApiModel, @ApiModelProperty)

  • implementation : 반환되는 DTO, Entity 를 .class 형식으로 연동 (구조 상속)

@Parameter ( spring fox = @ApiIgnore, @ApiImplicitParam, @ApiParam)

  • description : 해당 URL에 대한 Pathvariable, Query Parameter 설명

 

참고, 코드 출처

H2의 Local, Server 개념

 

Embedded 모드

H2 DB를 시스템의 메인 메모리에서 (JVM 위에서) 구동시키는 방식으로 application이 종료된다면 저장, 수정된 Data가 손실(휘발) 된다. 즉 기본적으로는 영속적이지 않은 방식이다.

→ 데이터에 대한 영속성을 제공하는 방법은 존재한다.

 

메인 메모리에 DB를 띄워놓고 해당 DB를 사용하는 Application의 스레드로 데이터에 바로 접근함으로써 데이터 읽기, 쓰기에 대한 성능을 향상할 수 있으므로 유용하게 사용할 수 있으며, 데이터 캐싱 DB에 대해서도 H2를 고려할 수 있다고 한다.

 

하지만 JVM에서 데이터 연산에 사용되는 쓰레드를 인터럽트 하지 않을 수 있기에, IO 수행 시에 I/O Handler가 닫힘으로써 데이터베이스의 손상을 일으킬 수 있다.

 

Server 모드

해당 이미지는 하나의 시스템에서 서버 모드를 사용하는 경우이다.

별도의 프로세스(JVM)를 통해 DB를 동작시켜 데이터베이스를 영속적으로 사용하는 방법이다.

 

local 환경에서는 localhost의 9092포트를 통해 DB 콘솔에 접근할 수 있으며, 별도의 서버 위에서 동작시킬 경우에 여러 Application을 해당 데이터베이스에 동시적으로 연결할 수 있다.

 

서버 모드도 내부적으로는 Embedded 모드와 동일한 실행방식을 가지지만, 모든 데이터의 처리 흐름이 TCP/IP를 통하여 전송되기 때문에 Embedded 모드보다 상대적으로 느릴 수밖에 없다.

 

H2 사용 방법

처음에 H2 데이터베이스를 사용하기 위하여선

spring:
  datasource:
    url: jdbc:h2:~/testdb
    //..

해당 주소를 입력하고 한번 구동시켜야 한다. 이는 해당 DB의 파일을 생성하는 방법이다.

→ 기본적으로 사용자 디렉터리에 database 파일이 생성되게 된다.

 

 

Embedded - YAML 설정

spring:
  datasource:
    url: jdbc:h2:mem:testdb # in-memory 주소 설정
    username: sa # 기본 계정명 password 를 추가하지 않는다면 기본적으로 없음으로 구동한다.
    driver-class-name: org.h2.Driver 
  h2:
    console:
      enabled: true # in-memory 방식을 사용하는 경우 H2 콘솔을 사용할 것인지의 유무이다.

해당 모드는 기본적으로 데이터가 저장되어 있지 않기 때문에 SQL파일을 통한 DDL이 선행되어야 한다. 스프링 부트에서는 com.domain.main.resource 밑에 schema.sql 과 data.sql 파일이 있다면 자동으로 해당 파일들을 이용하여 DB를 구성하게 된다.

 

 

Server - YAML 설정

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/test # Tcp, Server 모드 주소 설정
    username: sa
    driver-class-name: org.h2.Driver 

 

Local Mode vs Server Mode (주관적인 생각)

어떤 것을 사용해야 할까?

Local Mode의 장점으로는

  • 메모리로 바로 접근함으로써 빠른 데이터(쿼리) 처리
  • (스프링 부트가 지원하는) sql 파일을 통한 쉬운 DDL, DML 적용

정도가 있다고 생각한다.

 

단점으로는

  • JVM 시스템의 컨트롤로 인한 데이터베이스 손실 ( 쿼리, 연산 중 I/O Handler 종료)
  • Application의 쓰레드와 자원을 사용함으로써 해당 Application의 상태에 영향을 받는다.

정도가 있는데, 해당 문제들은 테스트 DB로만 사용할 때에는 큰 영향이 없다고 생각된다.

 

 

Server Mode의 장점으로는

  • 제한이 없는 Application과 DB의 커넥션 (TCP/IP를 사용함으로써 흐름 제어가 가능한 듯하다.)
  • PageStore 엔진의 동기화를 이용하여 멀리 스레딩을 지원함으로 데이터 정합성을 지킬 수 있다.

정도가 있는 것 같고,

 

단점으로는

  • TCP/IP를 이용하여 데이터의 흐름을 가지기 때문에 상대적으로 커넥션을 맺고 데이터를 전송하는 등의 상대적인 성능 오버헤드가 존재한다.
  • 외부 서버에서 제공하고 연동 설정을 지정하지 않는다면, 환경에 따라서 해당 모드를 사용하는 프로젝트에 대해 별도의 H2 설정과 데이터베이스에 대한 데이터 (DDL, DML 등)가 요구된다.

정도가 있는 것 같다.

 

결론

사실 프로젝트 진행 중에 빠르게 결과를 받고, 쉽게 관리하기 위하여서는 (DDL을 변경하여 쉽게 구조를 적용한다던지 등 ) In-Memory 방식이 매우 간편하다.

 

Server mode는 콘솔을 이용해서 데이터에 대해 별도의 관리를 해야 함으로 테스트 단계에서 필요 없는 복잡함을 가지게끔 하기도 하고, 프로젝트를 사용할 수 있는 여러 환경에서 다른 설정값이 존재할 수 있기에 신경을 써야 한다.

 

그러니까..! 테스트에는 In-Memory를 사용하자!

 

참고 자료

 

Post Entity, DTO 들을 만들고 생성 테스트를 진행한 뒤에 Post Controller와 Service를 생성하고 PostMapper에 Mapper 어노테이션 작성과 Mapper.xml 작성을 완료한 후 Teliend API를 통한 테스트를 진행하였다.

 

그런데.. POST 요청 보냈더니 이러한 에러가 발생하였다.

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.somaeja.post.mapper.PostMapper.save
at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.binding.MapperProxy.lambda$cachedInvoker$0(MapperProxy.java:115) ~[mybatis-3.5.5.jar:3.5.5]
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705) ~[na:na]
at org.apache.ibatis.binding.MapperProxy.cachedInvoker(MapperProxy.java:102) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85) ~[mybatis-3.5.5.jar:3.5.5]
at com.sun.proxy.$Proxy54.save(Unknown Source) ~[na:na]
at com.somaeja.post.service.PostService.savePost(PostService.java:20) ~[classes/:na]
...
...
...

2시간 정도 고생을 하다보니 설정된 코드에서 문제점을 발견하였다...

 

 

문제점

@EnalbeAutoConfiguration <- 문제점 
@MapperScan(basePackages = "com.somaeja")
public class PersistenceConfig {
        <- 문제점 
}

 

문제점 파악.

 

@EnableAutoConfiguration 은 Spring Boot 관련 자동 구성 어노테이션이었는데,

Dependencies > spring-boot-autoconfigure > META-INF > spring.factories

디렉터리 내부의 Definition 들을 Bean으로 등록해 준다고 하여서 configuration 대신에 사용하였었다.

 

ContextLoad Test

@SpringBootTest
class MainApplicationTests {
    @Test
    void contextLoads() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PersistenceConfig .class);
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

ContextLoad 테스트를 진행하였을 때에도 Datasource, sqlSessionFactory, postMapper 빈이 등록되어 있어 문제가 없다고 판단하였지만, 사실은 Mybatis 설정이 적용되지 않은 sqlSessionFacroty와 Root Context (MainApplication.class)에 등록되지 않은 postMapper 이였다는 것을 알지 못하였고, 헤맬 수밖에 없었다.

 

 

문제 해결 방법.

@Configuration // @EnableAutoConfiguration 에서 Configuration 으로 변경
@MapperScan(basePackages = "com.somaeja")
public class PersistenceConfig {

    // Custom Initializer -> Mybatis MapperLocations 설정이 적용된 SqlSessionFactory 생성 
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);

        // 스프링에서 Xml 파싱을 통한 Bean 생성시 사용하는 PathMatchingResourcePatternResolver 를 사용
        // 하여 classpath: 패턴의 경로를 작성하고 Mapper Location에 설정하였다.
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactory.setMapperLocations(resolver.getResources("classpath:mybatis/mapper/*.xml"));
        return sqlSessionFactory.getObject();
    }

}

 

참고 문서

MyBatis - 마이바티스 3 | 소개

MyBatis - 마이바티스 3 | 매퍼 XML 파일

Mybatis-Spring-Boot-Starter 소개 문서 번역

'프로젝트' 카테고리의 다른 글

Mybatis의 IndexOutOfBoundsException?  (2) 2021.01.12
Spring Boot Gradle Test 실패?  (0) 2020.12.31
[Somaeja : 소매자] 01. 프로젝트 개요  (0) 2020.11.15

 

4주 차 시작합니다!

 

선택문 (switch)?

주어진 조건 값의 결과에 따라 프로그램이 다른 명령을 수행하도록 하는 일종의 조건문이다.

  • 모든 값, 범위를 기반으로 판단하는 if 문과 달리 정수 값이나 열거된 값 또는 문자, 문자열만을 사용할 수 있다.

  • 컴파일러를 통해 실행 경로를 설정하는 점프 테이블이라는 것이 만들어지게 되어서 많은 조건을 비교하여야 할 때, if else 보다 더 빠른 성능을 보이게 된다.

    → case의 수가 5개 이상이라면, 성능 차이가 보이기 시작한다.

  • if else에 비하여서 좋은 가독성을 가지고 있다.

switch 문

public static String monthCheck(int num){
        int days = 0;
        switch (num) {
            case 1 :
            case 3 :
            case 5 :
            case 7 :
            case 8 :
            case 10 :
            case 12 :
                days = 31;
                break;
            case 4 :
            case 6 :
            case 9 :
            case 11 :
                days = 30;
                break;
            case 2 :
                days = 28;
                break;
            default:
                days = -1;
        };
        return "입력하신 달은 "+days+"일 입니다.";
    }

 

if else 문 (비교 용)

public static String monthCheck(int num){
        int days = 0;
        if (num == 1 || num == 3 || num == 5 || num == 7 || num == 8 || num == 10 || num == 12){
            days = 31;
        } else if (num == 4 || num == 6 || num == 9 || num == 11){
            days = 30;
        } else if (num == 2){
            days = 28;
        } else {
            days = -1;
        }
        return "입력하신 달은 "+days+"일 입니다.";
    }

 

반복문?

어떠한 명령을 일정한 횟수만큼 반복하여 수행하도록 하는 명령문이다.

1부터 100까지를 더하는 코드를 여러 방법으로 작성해보았다.

 

 

while 문

  • 조건 식이 맞는 경우 실행되는 반복문이다. (bool 값을 통해 반복한다.)

구조

while(조건식){
        //do something
}

// 무한 루프 : 사용시 조건 검증과 break; 을 통해 탈출해야한다.
while(true){
        //do something
}
public static void whiles(){
    int total = 0;
    int loopCount = 1;
    while(loopCount <= 100){
        total += loopCount;
        ++loopCount;
    }
    System.out.println(total);
}

 

 

do / while 문

  • while 문과 비슷하지만 조건 식이 만족되지 않더라도 무조건 한 번은 실행되는 반복문이다.

구조

do{
        //do something
}while(조건식);

// 무한 루프 : 사용시 조건 검증과 break; 을 통해 탈출해야한다.
do{
        //do something
}while(true);

코드

public static void doWhiles(){
    int total = 0;
    int loopCount = 1;
    do {
        total += loopCount;
        ++loopCount;
    }while(loopCount <= 100);
    System.out.println(total);
}

 

 

for 문

  • 반복된 횟수가 고정된 경우 사용하거나, index 위치나 값이 필요한 경우 사용한다.

구조

for(조건식){ 
        //do something
}

// 무한 루프 : 사용시 조건 검증과 break; 을 통해 탈출해야한다.
// 해당 구문은 디컴파일 시 While(true)로 변경되어 있다.
for(;;){ 
        //do something
}

코드

public static void fors(){
    int total = 0;
    for (int loopCount = 0; loopCount <= 100; loopCount++) {
        total += loopCount;
    }
    System.out.println(total);
}

 

 

labeled for 문

  • 각각의 for 문에 대하여서 라벨을 부여함으로써 중첩 for문에서 조건에 따라 중단하거나 계속하게끔 사용하기 좋다.
  • 내부에 중첩된 for 문에 대해서도 라벨 부여가 가능하다.

구조

label:
        for(조건식){
                //do something
        }

혹은

label:
        for(조건식){
                //do something
                for(조건식){
                        //do something
                        break label;
                        // 혹은 continue;
                }
        }

코드

public static void labeledFors(){
    int total = 0;

        // 무한 루프
    loop1: for (;;){
        for (int inLoop=0;inLoop<=1000;inLoop++){
            total += inLoop;

                        // 100번을 반복하게 되면 loop1을 탈출한다. 
            if(inLoop==100) {
                break loop1;  
            }
        }
    }
    System.out.println(total);
}

 

 

Enhanced for 문 (for each)

  • index 값이 아닌 요소를 통한 값의 순회를 진행한다.
  • 가변적인 컬렉션의 값을 처리하기에 좋다.

구조

for(Type variable : collection<Type>, Type[] array){
        //do something
}

코드

public static void enhancedFors(){
    int total = 0;
    int[] numArr = {10,20,30,40,50,60,70,80,90,100};
    for (int num : numArr){
        total += num;
    }
    System.out.println(total);
}

 

 

HashTable, Vector, Stack의 Enumeration

  • Collection Framework가 만들어지기 전에 사용되던 인터페이스이다.

  • 순차 접근 시 컬랙션 객체의 내부가 수정되더라도 이를 무시하고, 끝까지 동작한다

    → Enumeration을 이용해서는 내부 요소를 수정할 방법이 없다. (읽기만 가능하다.)

    → 해당 컬랙션 내부의 수정 메서드를 이용하여야 한다.

Enumeration<String> enumeration = vector.elements();

// 읽어올 요소가 존재한다면, True 요소가 없다면 false를 반환한다.
enumeration.hasMoreElements()

// 다음 요소를 읽어올 때 사용한다.
enumeration.nextElement()

 

 

Collection의 Iterator()

Collection Framework 에서 사용되는 Enumeration을 보완한 인터페이스이다.

→ 요소를 제거하는 기능을 포함하였다. (remove)

  • Fail Fast Iterator (ArrayList, HashMap)

    • 순차적 접근이 끝나기 전에 객체에 변경이 일어날 경우 예외를 반환한다.

      → 동일한 자원의 수정 발생 시 데이터의 일관성을 보장하기 위함이다.

    • 내부적으로 변경, 수정된 횟수를 나타내는 modCount 필드가 있다.

      • 하나 이상의 요소가 제거된 경우

      • 하나 이상의 요소가 추가된 경우

      • 컬렉션이 다른 컬렉션으로 대체되었을 때

      • 컬렉션이 정렬될 때 등

        → 구조가 변경될 때마다 카운터가 증가하게 된다..

        → 데이터를 순회하는 동안 expectedModCount에 modCount 값을 저장하고 비교하며,

        → 두 개의 값이 달라질 경우 ConcurrentModificationException()를 발생시킨다.

        → next(), remove(), add(), remove(), forEachRemaining()에서 사용된다.

        → HashTable에도 expectedModCount, modCount를 사용한다.

  • Fail Safe Iterator (CopyOnWriteArrayList, ConcurrentHashMap)

    • Enumeration와 같이 내부가 수정되더라도 예외가 발생하지 않는다.

    • 사실은 컬렉션의 원본이 아닌 복제본을 순회하기 때문이다.

      → 복제된 컬렉션을 관리하기 위해 추가적인 메모리를 사용한다.

 

과제 0. JUnit 5 학습하세요.

 

JUnit 5를 만들게 된 이유? (JUnit 4의 단점)

  • IDE에 대한 강한 결합도를 해결하기 위하여

  • 부족했던 확장성의 해결

    → 하나의 @Runwith에 여러 Rule을 사용하여 확장하고, Runwith들이 결합되지 못했다.

  • 하나의 Jar에 포함되어 있던 코드를 분리하였다. (하나의 Jar의 큰 책임)

이전 버전의 문제점을 해결하며 좀 더 단순하고, 확장성 있게 만들기 위해 그런 선택을 하였다고 한다.

그 외에도 외부 라이브러리를 사용해야 했던 Parameter Test 등의 부가 기능을 공식적으로 지원한다.

//넘겨지는 Source의 수 만큼 테스트를 반복한다.

@ParameterizedTest(name = "{index} {displayName} SourceParam = {0}")
@ValueSource(strings = {"1", "2", "3"})
@NullAndEmptySource // CsvSource 등..
void repeatParameterTest(String Param) {
    System.out.println("Param = " + Param);
}

// 넘겨지는 정수 값 만큼 테스트 반복.
@RepeatedTest(10)
void repeatTest(String Param) {
    System.out.println("test");
}

혹시 Junit 4를 사용하고 있다면, Parameter Test가 필요한 경우

Pragmatists/JUnitParams

해당 라이브러리를 사용하면 된다.

 

그 외에도

 

접근 제어자 변경

  • public 접근 제어자가 필요했던 4 버전과 달리 5 버전은 패키지 범위에서 실행이 가능하다.

 

좋아진 확장성

  • Extension 하나로 여러 규칙을 통합, 조합 가능하다.

    → @ExtendWith, @RegisterExtension, Java ServiceLoader

 

분리된 Jar (모듈)

  • Junit 4의 Jar→Vintage(4 버전과의 호환성), Jupiter(5 버전 모듈), Platform(Extension, 실행, 관리 등)

 

다양해진 assert 방식

  • assertThat → assertThrows, assertEquals, assertTimeout, assertNotNull 등..

 

한 번에 여러 test를 실행 가능

  • 기존에 하나의 테스트가 실패하면 더 이상 진행되지 않는 Junit 4의 문제점을 해결

      assertAll(
              () -> assertNotNull(),
              () -> assertEquals(),
              () -> assertTrue(),
              () -> assertTimeout(),
      );

등이 변경되었다.

 

 

Junit 5의 Test Cycle

        // 모든 테스트가 실행되기 이전에 1번 호출된다.
    @BeforeAll
    public static void beforeAll() {
        System.out.println("AppTest.beforeAll");
    }

    // 모든 테스트가 실행된 이후에 1번 호출된다.
    @AfterAll
    public static void afterAll() {
        System.out.println("AppTest.afterAll");
    }

    // 각각의 테스트가 실행되기 이전에 호출된다.
    @BeforeEach
    public void beforeEach() {
        System.out.println("AppTest.beforeEach");
    }

    // 각각의 테스트가 실행된 이후에 호출된다.
    @AfterEach
    public void afterEach() {
        System.out.println  ("AppTest.afterEach");
    }

 

 

JUnit 5의 Dynamic Test

  • Junit 5부터는 테스트가 런타임 환경으로 생성되고 수행이 가능하게 되었다.
  • 이를 통해 외부 자원을 활용하고, 랜덤 데이터를 생성하여 활용할 수 있다.
@TestFactory
Stream<DynamicNode> dynamicTests() {
        return Stream.of("AAA","BBB","CCC","DDD")
                        .map(text -> dynamicTest("AAAEEETTT", () -> assertTrue(text.contains("AAA")));
}

 

 

Junit 5의 Tagging, Filtering

  • @Tag 주석을 통해 테스트 클래스 및 메서드에 태그를 지정할 수 있다.
@Tag("fast")
@Tag("User Test")...

 

 

Junit 5의 Test 순서 설정

  • @Order(정수 값) : 넘겨진 정수 값을 우선순위로 설정한다.

    → test_1 → test_2 → test_3...

@TestMethodOrder(OrderAnnotation.class)
class OrderedTest {

----------------------------

@Test
@Order(1)
void test_1() {
    // do someThing
}

@Test
@Order(2)
void test_2() {
    // do someThing
}

@Test
@Order(3)
void test_3() {
    // do someThing
}

 

 

JUnit 5의 Test 이름 표시

  • @DisplayName("test name")

    • DisplayNameGenerator 보다 우선적으로 적용된다.

    • method 위에 작성한다.

      @Test
      @DisplayName("AAA Test")
      void Arrange_Act_Assert() {
            // Arrange : 생성
            // Act : 조작
            // Assert : 결과 비교
      }
  • @DisplayNameGenerator

    • Class 내부의 모든 메서드에 적용된다.

    • Class 위에 작성한다.

      // 모든 밑줄 문자를 공백으로 바꾸는 static class
      @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
      
      // 표준 표시 생성 static class
      @DisplayNameGeneration(DisplayNameGenerator.Standard.class)

 

Junit 5의 테스트에 대한 환경 변수 설정

  • @EnabledIfEnvironmentVariable : 지정된 환경 변수 값이 지정된 정규식과 일치하는 경우 활성화
  • @EnabledIfSystemProperty : JVM 시스템에 따라서 테스트를 활성화
  • @EnabledOnJre : 지정된 JRE (Java Runtime Environment) 버전에서만 활성화
  • @EnabledForJreRange : min으로 지정된 jre와 max로 지정된 jre 버전 사이에서 활성화
  • @EnabledOnOs : 지정된 운영 체제에서만 활성화
@EnabledIfEnvironmentVariable(named = "GDMSESSION", matches = "ubuntu") // ununtu 서버에서 실행
@EnabledIfSystemProperty(named = "java.vm.vendor", matches = "Oracle.*") // Oracle jvm에서 실행
@EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_13) // 8 ~ 13 버전의 자바에서 실행
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9}) // 8, 9 버전의 자바에서만 실행
@EnabledOnOs({OS.WINDOWS, OS.LINUX}) // windows와 linux에서 실행

 

 

Junit5 Assertions

// assertEquals(A, B);
assertEquals("user", user.getUserName());

// assertTrue(A, B);
assertTrue("L", firstName.startsWith(user.getFirstName().charAt(0));

Exception exception = assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());

// 시간 제한
assertTimeout(Duration.ofSeconds(1), () -> new user("Lob"));

// 시간이 초과되면 테스트 실패
assertTimeoutPreemptively(ofSeconds(1), () -> { Thread.sleep(5000); });

 

과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.

package assignment.gitapi;

import org.kohsuke.github.*;

import java.io.IOException;
import java.util.*;

public class Application {

    private final String token = "tokenString";
    private GitHub github;

    public static void main(String[] args){

        Application app = new Application();

        try {
            app.run();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void run() throws IOException {

        // 연결
        connectGitApi();

        GHRepository ghRepository = github.getRepository("whiteship/live-study");
        // 참여자 이름, 참여 횟수
        Map<String, Integer> participant = new HashMap<>();
        // 모든 이슈를 가져온다.
        List<GHIssue> issues = ghRepository.getIssues(GHIssueState.ALL);

        //Find Issue
        for (GHIssue issue : issues) {
            // 각각의 Issue 에 존재하는 comment 들을 저장.
            List<GHIssueComment> commentList = issue.getComments();

            // 혹시 모를 유저 중복을 제거하기 위한 Set
            Set<String> nameList = new HashSet<>();

            addParticipantInSet(commentList, nameList);

            // 참여자 명단에서 비교한다.
            for (String s : nameList) {
                hasParticipantInSet(participant, s);
            }
        }
        printParticipantRate(participant);
    }

    private void hasParticipantInSet(Map<String, Integer> participant, String s) {
        if (!participant.containsKey(s)){
            participant.put(s, 1);
        } else {
            Integer integer = participant.get(s);
            participant.put(s, ++integer);
        }
    }

    private void addParticipantInSet(List<GHIssueComment> commentList, Set<String> name) throws IOException {
        for (GHIssueComment ghIssueComment : commentList) {
            name.add(ghIssueComment.getUser().getLogin());
        }
    }

    private void printParticipantRate(Map<String, Integer> participant) {
        participant.forEach((key, value)-> {
            double percent = (double) (value * 100) / 18;
            System.out.println(key+"  :  "+String.format("%.2f", percent)+"%");
        });
    }

    private void connectGitApi() throws IOException {
        github = new GitHubBuilder().withOAuthToken(token).build();
    }
}

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

 

과제 2. LinkedList를 구현하세요.

package assignment.linkedlist;

public class ListNode {

    private int elements;

    private ListNode next = null;

    public ListNode(int data) {
        elements = data;
    }

    public ListNode add(ListNode newElement){
        if (this.next == null){
            this.next = newElement;
            return this;
        }

        ListNode nextNode = this.next;
        while (nextNode.next != null){
            nextNode = nextNode.next;
        }

        nextNode.next = newElement;

        return this;
    }

    public ListNode add(ListNode head, ListNode nodeToAdd, int position){
        ListNode nextNode = head;

        for (int loop = 0; loop < position-1; loop++) {
            if (nextNode.next == null){ break; }
            nextNode = nextNode.next;
        }

        ListNode tmp = nextNode.next;
        nextNode.next = nodeToAdd;
        nodeToAdd.next = tmp;

        return this;
    }

    public ListNode remove(ListNode head, int positionToRemove){
        ListNode nextNode = head;

        for (int loop = 0; loop < positionToRemove-1; loop++) {
            nextNode = nextNode.next;
        }
        // 현재 시점의 nextNode 에서 next 가 지워져야할 node
        ListNode tmp = nextNode.next;
        nextNode.next = tmp.next;
        tmp = null;

        return this;
    }

    public boolean contains(ListNode head, ListNode nodeToCheck){
        ListNode nextNode = head;

        while (nextNode.next != null){
            if (nextNode.elements == nodeToCheck.elements){
                return true;
            }
            nextNode = nextNode.next;
        }

        return false;
    }

    public void printForEach(){
        ListNode nextNode = this;

        while (nextNode != null){
            System.out.println(nextNode.elements);
            nextNode = nextNode.next;
        }
    }

    public int size(){
        ListNode nextNode = this;

        int size = 0;
        while (nextNode != null){
            ++size;
            nextNode = nextNode.next;
        }

        return size;
    }
}

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

테스트

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

 

과제 3. Stack을 구현하세요.

package assignment.stack;

public class StackNode {

    private int[] elements;
    private int head = 0;
    private int size = 16;
    private int modifyCount = 0;

    public StackNode() {
        elements = new int[size];
    }

    public StackNode(int size) {
        elements = new int[this.size = size];
        System.out.println(this.size);
    }

    public boolean push(int data){
        if (modifyCount >= size){ return false; }
        elements[modifyCount] = data;
        head = modifyCount;
        ++modifyCount;
        return true;
    }

    public int pop(){
        if (head < 0) { return -1; }
        int res = elements[head];
        elements[head] = -1;
        head--;
        return res;
    }

    public void print(){
        for (int index : elements){
            if (index == 0 || index == -1){
                System.out.println("is Empty");
                break;
            }
            System.out.println(index);
        }
    }
}

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

테스트

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

 

과제 4. 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.

package assignment.stack;

import assignment.linkedlist.ListNode;

import java.util.List;

public class ListStack {

    private ListNode node = null;
    private ListNode head;

    public void push(int data){
        if (node == null){
            node = new ListNode(data);
            head = node;
        } else {
            ListNode nextNode = node.next;
            while (nextNode.next != null){
                nextNode = nextNode.next;
            }
            nextNode.next = new ListNode(data);
            head = nextNode.next;
        }
    }

    public int pop(){
        ListNode nextNode = node;
        ListNode preNode = head;

        if (node.next == null){ node = null; }

        while (nextNode.next != null){
            preNode = nextNode;
            nextNode = nextNode.next;
        }

        int result = head.elements;
        head = preNode;
        preNode.next = null;

        return result;
    }

    public void print(){
        if (node == null){
            System.out.println("is empty");
        } else if (node.next == null){
            System.out.println(node.elements);
        } else{
            while (node.next != null){
                System.out.println(node.elements);
            }
        }

    }

    public static class ListNode{
        private int elements;
        private ListNode next = null;

        public ListNode(int data){
            elements = data;
        }
    }

}

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

테스트

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

 

(optional) 과제 5. Queue를 구현하세요.

배열 사용

package assignment.queue;

public class ArrayQueue {

    private int[] elements;
    private int size = 16;
    private final int head = 0;
    private int modifyCount = 0;

    public ArrayQueue() {
        elements = new int[size];
    }

    public ArrayQueue(int size) {
        elements = new int[size];
    }

    public boolean offer(int data){
        if (modifyCount >= size){ return false; }
        if (data < 0){ return false; }

        elements[modifyCount] = data;
        ++modifyCount;
        return true;
    }

    public int poll(){
        int res = elements[head];
        int modify = 0;
        for (int loop = 1; loop < modifyCount; loop++) {
            elements[loop-1] = elements[loop];
            modify = loop;
        }
        elements[modify] = -1;
        modifyCount = modify;
        return res;
    }

    public int size() {
        int size = 0;
        for (int index : elements){
            if (index == -1){ break; }
            if (index == 0){ break; }
            ++size;
        }
        return size;
    }

    public void print() {
        for (int index : elements){
            if (index == -1){ break; }
            if (index == 0){ break; }
            System.out.println(index);
        }
    }
}

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

테스트

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

 

ListNode 사용

package assignment.queue;

import assignment.stack.ListStack;

public class ListQueue{

    private ListNode node = null;

    private ListNode head;

    public ListQueue() {
    }

    public ListQueue(int element) {
        node = new ListNode(element);
        head = node;
    }

    public void offer(int data){
        if (node == null){
            node = new ListNode(data);
            head = node;
        } else {
            ListNode nextNode = node;
            while (nextNode.next != null){
                nextNode = nextNode.next;
            }
            nextNode.next = new ListNode(data);
        }
    }

    public int poll(){
        int result = head.elements;

        ListNode nextNode = head.next;
        head = null;
        head = nextNode;

        return result;
    }

    public int size() {
        int size = 0;
        ListNode nextNode = head;
        while (nextNode != null){
            ++size;
            nextNode = nextNode.next;
        }
        return size;
    }

    public void print() {
        if (head == null){
            System.out.println("is empty");
        } else if (head.next == null){
            System.out.println(node.elements);
        } else {
            ListNode nextNode = head;
            while (nextNode != null){
                System.out.println(nextNode.elements);
                nextNode = nextNode.next;
            }
        }

    }

    public static class ListNode{
        private int elements;
        private ListNode next = null;

        public ListNode(int data){
            elements = data;
        }
    }
}

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

테스트

Lob-dev/DashBoardDemo

 

Lob-dev/DashBoardDemo

Java Live Study assignment test(week 4). Contribute to Lob-dev/DashBoardDemo development by creating an account on GitHub.

github.com

 

참고 자료

Fail Fast and Fail Safe Iterators in Java - GeeksforGeeks

 

Fail Fast and Fail Safe Iterators in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

Difference between Iterator and Enumeration in Java

 

Difference between Iterator and Enumeration in Java

Difference between Iterator and Enumeration in Java Iterator and Enumeration both are the cursors to traverse and access an element from the collection. They both belong to the collection framework. Enumeration was added in JDK1.0 and Iterator in the JDK.1

www.tutorialspoint.com

Loops in Java | Java For Loop - Javatpoint

 

Loops in Java | Java For Loop - Javatpoint

Loops in Java | Java for loop with java while loop, java for loop, java do-while loop, java for loop example, java for loop programs, labeled for loop, for each loop or advanced for loop, java infinite for loop example, java simple for loop, nested for loo

www.javatpoint.com

JUnit 5 User Guide

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model will not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and cus

junit.org

if(kakao)2020

 

if(kakao)2020

오늘도 카카오는 일상을 바꾸는 중

if.kakao.com

 

3주 차 시작합니다!

연산이란?

주어진 정보를 통해 일정한 규칙에 따라 어떤 값이나 결과를 구하는 과정을 의미한다.

 

연산자란?

연산을 진행하는 동안 사용되는 기호를 말한다.

연산 대상의 수나 연산 방식에 따라서 명칭이 나뉘게 된다.

 

피연산자란?

연산될 대상을 말하며. 변수, 상수, 리터럴 등을 의미한다.

 

단항 연산자 (Unary Operator)

연산이 수행될 피연산자가 1개인 경우 해당 연산자를 단항 연산자라고 한다.

  • 전위 증감, 후위 증감 연산자
  • 단항으로써 사용하는 +, - 연산자 (부호 연산자)
  • 비트 반전 ~ 연산자

이항 연산자 (Binary Operator)

연산이 수행될 피연산자가 2개인 경우 해당 연산자를 이항 연산자라고 한다.

  • 산술 연산자
  • 비트 연산자
  • 관계 연산자
  • 대입 연산자
  • 논리 연산자

삼항 연산자 (Ternary Operator)

연산이 수행될 피연산자가 3개인 경우 해당 연산자를 삼항 연산자라고 한다.

  • 조건 연산자 ( 조건식? True의 경우 반환될 식 : False의 경우 일시 반환될 식 )

 

산술 연산자 (Arithmetic Operator)

산술 연산자는 수치 계산을 실시하는 연산자를 말한다.

  • A + B : 덧셈 연산자 (정수형, 실수형 리터럴뿐만 아니라 문자열 연산에도 사용 가능하다.)

      // 기본적인 사용법
          int numA = 10;
          int numB = 10;
    
          System.out.println(numA + numB); // 20
    
          int i = numA + numB;
        System.out.println(i);
    • 문자열 더하기 연산

      String strA = "hello";
      strA += " World";
      System.out.println(strA); // hello world
    • 값의 범위가 다른 Type 간의 연산 (공통)

      // long과 int를 더하는 경우에는?
      long longA = 10l;
      int numC = 10;
      
      System.out.println(longA+numC);
      
      // 실제로는 int 가 long으로 Type Conversion이 된 것을 알 수 있다. - 2주차 내용
      System.out.println(longA + (long)numC);
      
      // long과 double를 더하는 경우에는?
      long longA = 10;
      double doubleA = 10.0;
      
      System.out.println(longA+doubleA);
      
      // 실제로는 long 이 double로 Numeric Promotion이 된 것을 알 수 있다.
      System.out.println((double)longA + doubleA);
      
  • A - B : 뺄셈 연산자

      int numA = 10;
      int numB = 10;
    
      System.out.println(numA - numB); 
    
      int j = numA - numB; 
      System.out.println(j); // 0 출력
  • A / B : 나눗셈 연산자

    나눗셈 연산과 나머지 연산의 경우 피연산자로 0을 사용하면 Exception이 발생한다.

      int numA = 10;
      int numB = 10;
    
      System.out.println(numA / numB); 
    
      int k = numA / numB;
      System.out.println("k = " + k); // 1 출력 
  • A % B : 나머지 연산자

      int numA = 11;
      int numB = 2;
    
      System.out.println(numA % numB); 
    
      int l = numA % numB;
      System.out.println("l = " + l); // 1 출력
  • A * B : 곱하기 연산자

      int numA = 11;
      int numB = 2;
    
      System.out.println(numA * numB); 
    
      int h = numA % numB;
      System.out.println("h = " + h); // 22 출력
  • ++N, --N : 전위 증감 연산자

      int numC = 10;
      numC = --numC;
      System.out.println("numC = " + numC);
    
      int numD = 10;
      numD = ++numD;
      System.out.println("numD = " + numD);
    
      // Decompile
      // 전위 증감 연산자는 내부적으로 A = A +- 1; 의 형식을 취하는 것을 알 수 있다.
    
      int numC = 10;
      int numC = numC - 1;
      System.out.println("numC = " + numC);
    
      int numD = 10;
      int numD = numD + 1;
      System.out.println("numD = " + numD);
    
  • N++, N-- : 후위 증감 연산자

      int numE = 10;
      System.out.println(numE--);
      System.out.println("numE = " + numE);
    
      int numF = 10;
      System.out.println(numF--);
      System.out.println("numF = " + numF);
    
      // Decompile
      // 우선 임시변수를 만들어서 해당 값을 복사한뒤 전달하고 반영된 값을 그 이후에 제공한다.
    
      int numE = 10;
      byte var10001 = numE; // 임시변수에 값 복사
      int numE = numE - 1; // 변수 연산
      System.out.println(var10001); // 임시변수 출력
      System.out.println("numE = " + numE); // 연산된 변수를 제공
    
      int numF = 10;
      var10001 = numF; 
      int numF = numF - 1;
      System.out.println(var10001);
      System.out.println("numF = " + numF);
    • Wrapper와 Primitive 간의 산술 연산

    • Wrapper Class* (Integer, Long, Double)와 Primitive Type 연산 시에는 Wrapper Class 피연산자가 Unboxing 되어 연산이 진행되고 다시 Boxing이 되는 등에 성능상 오버헤드가 발생한다.

      int numB = 10;       
      
      Integer numA = numB; //autoboxing
      
      numA = numA + numB; //unboxing 후 연산 -> 결과 반영 전 다시 autoboxing 진행

      그리고 Operand Stack에서 관리되는 Primitive TypeHeap에서 참조되는 Wrapper Type은 값에 접근하는 시간에 차이가 존재한다. (일반적으로 Wrapper의 Access 시간이 길다.)

    • Primitive 도 일정 값 이상의 경우, Constant Pool이라는 Stack 외부에 존재하는 공간에 저장된다.*

    • JVM 설정 중에 Primitive 값의 크기에 대한 설정이 있다고 한다.

    • 즉 프로그램의 환경적인 부분에 따라서 수치나 연산 시간 등이 달라질 수 있다.

 

비트 연산자?

  • A & B : AND 연산 ( A와 B의 비트에 대해서 AND 연산을 수행한다.)

      //양쪽의 비트가 1이면 1, 0이면 0, 0 과 1 인 경우에는 0을 나타낸다.
    
      int num = 15; // 1111
      int nums = 6; // 0110
                    // 0110 : AND 연산
    
      int i = num & nums;
      System.out.println("i = " + i); // 6
  • A | B : OR 연산 ( A와 B의 비트에 대해서 OR 연산을 수행한다.)

      //하나의 비트가 1이면 1, 양쪽의 비트가 0이면 0이다.
    
      int num = 15; // 1111
      int nums = 6; // 0110
                    // 1111 : OR 연산
    
      int i = num | nums;
      System.out.println("i = " + i); // 15
  • A ^ B : XOR 연산 ( A와 B의 비트에 대해서 XOR 연산을 수행한다.)

      //하나의 비트가 1이면 1, 양쪽의 비트가 0이면 0, 양쪽의 비트가 1이면 0이다.
    
      int num = 15; // 1111
      int nums = 6; // 0110
                    // 1001 : XOR 연산
    
      int i = num ^ nums;
      System.out.println("i = " + i); // 9
  • A << N : 시프트 연산자 ( A의 비트를 좌측으로 N만큼 이동한다.)

      //비트를 좌측으로 N만큼 이동하고 우측에서 N만큼의 영역을 0으로 채우게된다.
    
      int num = 15; // 1111
      int nums = 2;
    
      int i = num << nums; // 111100 = 좌측으로 비트를 2칸씩 이동.
      System.out.println("i = " + i);
  • A >> N : 시프트 연산자 ( A의 비트를 우측으로 N만큼 이동한다.)

      //비트를 우측으로 N만큼 이동하고 좌측에서 N만큼의 영역을 0으로 채우게된다.
    
      int num = 15; // 1111
      int nums = 2;
    
      int i = num >> nums; // 0011 = 우측으로 비트를 2칸씩 이동.
      System.out.println("i = " + i);
  • A >>> N : 부호가 없는 시프트 연산자 ( A의 2진수를 우측으로 비트를 N만큼 이동한다.)

      int num = -15; // 11111111111111111111111111110001
      int nums = 2;
    
      // 부호를 신경쓰지 않고 모든 비트 값들을 오른쪽으로 이동시킨 후에 왼쪽의 빈 공간은 모두 
      // 0 으로 채운다.
    
      int i = num >>> nums; 
      System.out.println("i = " + i); // 1073741820 = 00111111111111111111111111111100
    
      int j = -15 >> nums;         
      System.out.println("j = " + j); // -4 : 11111111111111111111111111111100 
  • ~A : 반전 연산자 ( A 내부의 비트 값을 반전시킨다.)

      int num = -15; // 11111111111111111111111111110001
    
      System.out.println(~num);   // 14 : 00000000000000000000000000001110

 

관계 연산자?

  • A == B : 동일성 비교 ( A와 B가 같다면?)

      int numA = 10;
      int numB = 10;
    
      boolean isSame = numA == numB; // boolean 값을 반환.
    
      if (isSame){
          System.out.println(isSame); // true
      }
    
      혹은
    
      if (numA == numB){
          System.out.println(isSame); // true
      }
  • A != B : 동일하지 않은 경우 ( A와 B가 같지 않다면?)

      int numA = 10;
      int numB = 9;
    
      boolean isSame = numA != numB;
    
      if (isSame){
          System.out.println(isSame); //true
      }
    
      또는
    
      if (numA != numB){
          System.out.println(isSame); //true
      }
    
      또는
    
      boolean isSame = numA == numB;
    
      if (!isSame){
          System.out.println(isSame); //false 
      }
  • A > B : 대소 관계 비교 ( A가 B보다 크다면?)

      int numA = 10;
      int numB = 9;
    
      boolean isSame = numA > numB;
    
      if (isSame){
         System.out.println(isSame); //true
      }
    
      if (numA > numB){
         System.out.println(isSame); //true
      }
  • A >= B : 대소관계 비교 ( A가 B보다 크거나 같다면?)

      int numA = 10;
      int numB = 10;
    
      boolean isSame = numA >= numB;
    
      if (isSame){
         System.out.println(isSame); //true
      }
    
      if (numA >= numB){
         System.out.println(isSame); //true
      }
  • A < B : 대소관계 비교 ( A가 B보다 작다면?)

      int numA = 9;
      int numB = 10;
    
      boolean isSame = numA < numB;
    
      if (isSame){ 
         System.out.println(isSame); //true
      }
    
      if (numA < numB){
         System.out.println(isSame); //true
      }
  • A <= B : 대소관계 비교 ( A가 B보다 작거나 같다면?)

      int numA = 10;
      int numB = 10;
    
      boolean isSame = numA <= numB;
    
      if (isSame){
         System.out.println(isSame); //true
      }
    
      if (numA <= numB){
         System.out.println(isSame); //true
      }

 

논리 연산자?

  • 조건식 1 && 조건식2 : 조건식1과 조건식 2를 모두 만족한다면?

      int numA = 10;
      int numB = 10;
      int numC = 10;
      int numD = 10;
    
      boolean bool = numA == numB && numC < numD; //false
    
      if (bool){
         System.out.println(bool);
      }else {
           **System.out.println(bool);
      }
    
      if (numA == numB && numC == numD){
         System.out.println(numA == numB && numC == numD); //true
      }
  • 조건식1 || 조건식2 : 조건식1 혹은 조건식2를 만족한다면?

      int numA = 10;
      int numB = 10;
      int numC = 10;
      int numD = 10;
    
      boolean bool = numA == numB || numC < numD; //true
    
      if (bool){
         System.out.println(bool); 
      }else {
         System.out.println(bool);
      }
    
      if (numA == numB || numC == numD){
         System.out.println(numA == numB && numC == numD); //true
      }
  • ! : 논리 반전 연산자 : != 같지않다면?, !< 작지않다면?

      int num = 10;
      String conn = null;
    
      boolean bool = num != 9;
    
      if (bool){
         System.out.println(bool);
      }else {
         System.out.println(bool);
      }
    
      if (conn != null){
         System.out.println("true");
      }else {
         System.out.println("false");
      }
  • JDK Assertion (JDK 1.4+)

    이전에 작성된 코드에서 사용된 assert라는 변수들과의 하위 호환성을 위해 기본적으로 비활성화되어 있다.

    ```java
    assert service != null; // true -> 아무 일도 발생하지 않는다.
                                                // false -> AssertionError (Unchecked Exception) 발생
    
    혹은
    
    assert service != null : "Service is Null"; // false 시 추가적으로 메세지를 제공한다.

 

instanceof

  • A instanceof B : (공변) 타입 검증 연산자, A가 B의 타입 혹은 하위 구현체인지 판단한다.
    • Reference Type 만 사용 가능하다.
public static void main(String[] args) {
    InstanceofExample example = new InstanceofExample();
    example.run();
}

public void run() {
    SomeClass someClass = new SomeClass();
    SomeClasz someClasz = new SomeClasz();
    SomeClassChild someClassChild = new SomeClassChild();

    typeCheck(someClass);      // is SomeClass
    typeCheck(someClasz);      // is SomeClasz
    typeCheck(someClassChild); // is SomeClass
}

public void typeCheck(Object obj){
    if (obj instanceof SomeClass){
        System.out.println("is SomeClass");
    }else if (obj instanceof SomeClasz){
        System.out.println("is SomeClasz");
    }
}

class SomeClass {

}
class SomeClassChild extends SomeClass{

}
class SomeClasz {

}

 

대입 연산자? (assignment(=) operator)

Primitive 변수에 값을 할당하기 위해 사용하거나 Reference의 참조 값을 할당하기 위해 사용한다.

  • 그 외에도 이항 연산자와 결합하여 사용할 수 있다.
int num = 4;
int variable;

variable = num; // variable = 4;
System.out.println("= 연산 : " + variable); // = 연산 : 4

variable += num; // variable = 4 + 4
System.out.println("+= 연산 : " + variable); // += 연산 : 8

variable -= num; // variable = 8 - 4
System.out.println("-= 연산 : " + variable); // -= 연산 : 4

variable *= num; // variable = 4 * 4
System.out.println("*= 연산 : " + variable); // *= 연산 : 16

variable /= num; // variable = 16 / 4
System.out.println("/= 연산 : " + variable); // /= 연산 : 4

variable %= num; // variable = 4 % 4 
System.out.println("%= 연산 : " + variable); // %= 연산 : 0

variable = 10;
variable &= 2; // variable = 10 & 2
System.out.println("&= 연산 : " + variable); // &= 연산 : 2

variable = 10;
variable |= 2; // variable = 10 | 2
System.out.println("|= 연산 : " + variable); // |= 연산 : 10

variable = 10;
variable ^= 2; // variable = 10 ^ 2
System.out.println("^= 연산 : " + variable); // ^= 연산 : 8

variable = 10;
variable <<= 2; // variable = 10 << 2
System.out.println("<<= 연산 : " + variable); // <<= 연산 : 40

variable = 10;
variable >>= 2; // variable = 10 >> 2
System.out.println(">>= 연산 : " + variable); // >>= 연산 : 2

variable = 10;
variable >>>= 2; // variable = 10 >>> 2
System.out.println(">>>= 연산 : " + variable); // >>>= 연산 : 2

 

Arrow operator? ( (parameter list) -> lambda body )

Java 8에 추가된 lambda 식을 지원하기 위해 사용되는 연산자이다.

// 이러했던 구문을
Runnable runnable = new Runnable() {
   @Override
   public void run() {
       System.out.println("Hello!");
   }
};

// 이렇게 축약할 수 있다.

// 하나의 식, 로직을 가지고 있다면 { } block 과 return도 생략 가능하다.
Runnable runnable = () -> System.out.println("Hello!");

// 그렇지 않다면 이런식으로 작성해야한다.
Runnable runnable = () -> {
    System.out.println("Hello!");
    helloLogic();
**};**

 

3항 연산자? ( Ternary Operator : 변수 = (조건)? true 인 경우 : false 인 경우)

삼항 연산자는 피연산자 3개를 사용하며, if-then-else 명령문의 축약형이라고 할 수 있다.

int num = 10;
String result;

if(num == 10){
        result = "true";
        System.out.println("result = " + result);
}else {
        result = "false;
        System.out.println("result = " + result);
}

result = (num == 10) ? "true" : "false";
System.out.println("result = " + result);

 

연산자 우선순위?

수식 내에 여러 연산자가 함께 등장할 때, 어느 연산자가 먼저 처리될 것인가를 결정합니다.

( ) 등으로 우선순위, 결합 방향에 따른 연산자의 처리 순서를 변경할 수도 있습니다.

 

연산자의 결합 규칙?

수식 내에 우선순위가 같은 연산자가 둘 이상 있을 때, 먼저 어느 연산을 수행할 것인가를 결정합니다.

int numA = 5;
int numB = 2;
int numC = 2;

// +, - 의 우선 순위가 동일하다.
// 결합 방향에 따라서 왼쪽부터 오른쪽으로 진행하게 된다.
int res = numA + numB - numC;

 

Java 13의 switch 연산자?

기존 switch 문법으로 만든 요일 반환식 (윤년을 앞선 로직에서 체크했다고 가정.)

public static String monthCheck(int num){
        int days = 0;
        switch (num) {
            case 1 :
            case 3 :
            case 5 :
            case 7 :
            case 8 :
            case 10 :
            case 12 :
                days = 31;
                break;
            case 4 :
            case 6 :
            case 9 :
            case 11 :
                days = 30;
                break;
            case 2 :
                days = 28;
                break;
            default:
                days = -1;
        };
        return "입력하신 달은 "+days+"일 입니다.";
    }

Java 12에서 switch는

  • case 라벨을 쉼표로 구분하여 사용하게끔 문법을 지원하게 되었다.
  • break을 통해 값을 반환할 수 있게끔 되었다.
public static String monthCheck(int num){
        int days = switch (num) {
            case 1, 3, 5, 7, 8, 10, 12 :
                break 31;
            case 4, 6, 9, 11 :
                break 30;
            case 2 :
                break 28;
            default:
                break -1;
        };
        return "입력하신 달은 "+days+"일 입니다.";
    }

혹은 이와 같이 람다 식을 사용할 수 있게 되었다.

public static String monthCheck(int num){
        int days = switch (num) {
            case 1, 3, 5, 7, 8, 10, 12 -> 31;
            case 4, 6, 9, 11 -> 30;
            case 2 -> 28;
            default -> -1;
        };
        return "입력하신 달은 "+days+"일 입니다.";
    }

Java 13에서 switch는

  • 기존에 사용하던 break이라는 키워드를 yield로 대체(확장) 하게 되었다.
public static String monthCheck(int num){
        int days = switch (num) {
            case 1, 3, 5, 7, 8, 10, 12 :
                yield 31;
            case 4, 6, 9, 11 :
                yield 30;
            case 2 :
                yield 28;
            default:
                yield -1;
        };
        return "입력하신 달은 "+days+"일 입니다.";
    }

또는

public static String monthCheck(int num){
        int days = switch (num) {
            case 1, 3, 5, 7, 8, 10, 12 -> 31;
            case 4, 6, 9, 11 -> 30;
            case 2 -> 28;
            default -> {
                                System.out.println("잘못 입력했습니다.");
                                yield -1;
                        }
        };
        return "입력하신 달은 "+days+"일 입니다.";
    }

이렇게 표현할 수 있다.

 

Java 12 에서는 해당 기능을 미리보기 기능으로 제공하였고, (활성화 필요)

Java 13 에서는 해당 식을 확장하였으며,

Java 14 에서는 해당 기능을 표준으로 제공하게 되었다.

 

 

참고 자료

Primitive Type 종류와 값의 범위 그리고 기본 값

Primitive Variable 이란?

어떠한 Instance의 참조 값을 가지지 않고, 지정된 Type에 맞는 리터럴을 저장한 변수를 말한다.

객체를 다루지 않기 때문에 Null을 저장할 수 없으며, 같은 타입의 변수들이 동일한 값을 다룰 때에는 Operand Stack이나 Constant Pool에 리터럴을 저장해두었다가 꺼내어 사용한다.

**// 정수 타입
// JDK 7 까지는 int와 long에 대해서 Unsigned 를 지원하지 않았지만,
// JDK 8 부터는 int와 long에 대하여서 지원하기 시작하였다.**

// 1 byte (8 bit)
// -128 (-2^7)  ~  127 (2^7–1).
// 기본 값은 0. (공통적으로 Method, Loop Scope 등에 선언된 Primitive 변수는 초기화 해야한다.)
byte variableB;

// 2 byte (16 bit)
// -32,768(-2^15)  ~  32,767(2^15–1).
// 기본 값은 0.
short variableS;

// 4 byte (32 bit) 
// 2,147,483,648 (-2^31)  ~  2,147,483,647 (2^31-1)\
// int 를 통해 8, 16 진수를 표현 가능하다. 0 시작하면 8진수, 0x 로 시작하면 16진수 이다.
// (JDK 8+) 부호 없는 int를 만들기 위해서는 Integer.parseUnsignedInt 를 사용해야 한다.
// 기본 값은 0. 
int variableI;

// 8 byte (64 bit)
// -9,223,372,036,854,775,808 (-2^63)  ~  9,223,372,036,854,775,807 (2^63–1)
// (JDK 8+) 부호 없는 long를 만들기 위해서는 Long.parseUnsignedLong 를 사용해야 한다.
// 기본 값은 0.
long variableL;


**// 실수 (부동소수점 방식) 타입**

// 4 byte (32 bit)
// 1.40239846 x 10^-45  ~  3.40282347 x 10^38
// 기본 값은 0.0f
float variableF;

// 8 byte (64 bit)
// 4.9406564584124654 x 10-324  ~  1.7976931348623157 x 10308
// 기본 값은 0.0D
double variableD;


**// 논리 타입**

// 표시 용량은 1 bit (내부적으로는 1 byte 내부 7 비트에 값을 채우고 하나의 bit 만 사용한다.) 
// true , false
// 기본 값은 false
boolean bool;


**// 문자 타입**

// 2 Byte (16 bit)
// 유니코드를 표현하는 변수이며, 값의 범위는 0  ~  65,535 까지이다.
// 기본 값은 /u0000 (Unicode Character 'NULL')
char variableC

 

Primitive Type과 Reference Type

Reference Type?

Reference Type 은 Primitive Type을 제외한 모든 타입을 포함한다.

참조 값을 통해 관리하기 때문에 Reference Type이라고 한다. ( Null을 사용할 수 있다. )

Wrapper Class?

Primitive Type을 인스턴스로 다룰 수 있게끔 도와주는 것들을 말한다.

값을 조작하기 위해 작성된 Util Method를 사용하기 위해서 이용하는 경우도 많다.

  • Min, Max_value 상수, valueOf, parsexxx, toString 등..

Boxing과 Unboxing을 통해 Primitive Type을 감싸거나 다시 Primitive로 변환할 수 있다.

//대응 클래스
byte Byte      char Character
short Short    float Float
int Integer    double Double
long Long      boolean Boolean


//Wrapper Class 선언 예시
Integer num = new Integer(10);
            = Integer.valueOf(10);//혹은 primitive 변수



// Integer의 valueOf 메서드는 Boxing 할 값이-127~128 범위 값인 경우 기존에 있는 객체를 
// 재사용할 수 있게끔 작성되어 있다. 
// IntegerCache 클래스에 -127~128 만큼의 범위를 가지는 별도의 배열을 만들고 
// 해당 valueOf 로직에서 검증하여 중복된 경우 기존에 선언된 객체를 반환한다.
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
}

Primitive Type과 Reference Type의 차이점?

  • Reference 변수를 통해서 생성된 객체에 접근하고 조작하고 값을 받아올 수 있다.

  • Null을 다룰 수 있다. (NPE 가 발생될 수 있다.)

    • Null 을 가지는 Wrapper와 Primitive Type 연산 시 NPE 가 발생한다.
  • Generic을 사용할 수 있다.

  • 별도의 식별 값을 가진다. (Hashcode)

  • 성능 상의 차이가 있을 수도 있다.

 

그러면 Wrapper Class 를 사용하지 말라는 걸까?

  • Front 단의 데이터를 검증해야 한다거나, DB Table에서 Null을 다루고 있다면, 해당 데이터를 받아오는 객체에서 검증을 진행해야 하는 것이 맞다.
    • DB Table에서 Null 을 다룰 때 변수를 Primitive로 선언하여 사용한다면 0이 저장될 것이고 의도치 않은 결과가 발생할 수 있다고 생각한다.
  • Collection이나 Generic 은 Primitive를 지원하지 않는다. (결국 Wrapper를 사용해야 한다.)

 

리터럴

리터럴이란? wiki

어떤 변수에 의해 저장되는, 값 자체로써 변화하지 않는 것들을 의미한다.

// 변수를 초기화할 때 사용되는 값들도 모두 리터럴이다.
int numberA = 10;
double numberB = 10.11D;
String str = "문자열 리터럴";

변수 선언 및 초기화하는 방법

흐름

  1. 타입에 맞는 메모리 공간을 확보하고 (byte b;)
  2. 타입에 맞는 리터럴을 저장한다. (byte b = 100;)
// 정수 리터럴의 _ 은 숫자를 구별하기 위해 사용할 수 있게 지원해준다. 
// 100_000 = 100,000
// 맨 앞이나 뒤에 _ 를 작성할 경우 컴파일 에러가 발생한다.

byte variableB; 
variableB = 100;

short variableS;
variableS = 30_000;

// 10진수
int variableI = 100_000;
// 8진수
int variableI = 010; // 8
// 16진수
int variableI = 0xa; // 10
// 2진수
int variableI = 0b11; // 3

long variableL = 100_000_000_000; // 혹은 100_000_000_000l;

float variableF = 3.14f;

double variableD = 3.14; // 3.14D; 기본적으로 Double 로 판단하여 선언해준다.

char variableC = 'c';

boolean bool = true;

 

Variable Scope와 Life Cycle?

Class Scope

public class someClass {
        // 해당 Class 의 Bracket 내부에서만 사용이 가능하다.
        // 외부에서는 해당 Class 의 getter, setter 를 통해서 해당 변수를 접근할 수 있다.
        private int age = 32;
        -------------------------------------------
}

Method Scope

public class someClass {

        public static void main(String[] args) {
        VariableScope variableScope = new VariableScope();
        variableScope.someMethodA();
        System.out.println(variableScope.someMethodB());
    }        

        public void someMethodA() {
                // 해당 Method 의 Bracket 내부에서만 사용이 가능하다.
                int count = 0;
                System.out.println("count = " + count);
        }

        public int someMethodB() {
                //Compile Error 발생
                //cannot find symbol 
                    //symbol:   variable count
                    //location: class noteThree.VariableScope
                count += 1;
                return count;
        }
}

Loop Scope

public class someClass {

        public static void main(String[] args) {
        VariableScope variableScope = new VariableScope();
        variableScope.min(new int[]{10,20,30,40,50});
    }

    public void min(int[] args) {
        int min = 21700000;

        for (int arg : args) {
            // int arg 는 해당 Loop 내부에서만 사용이 가능하다.
            if (min > arg) { min = arg; }
            System.out.println("arg = " + arg);
        }

        //cannot find symbol.. symbol: variable arg...
        System.out.println("arg = " + arg);

        System.out.println("min = " + min);
    }
}

Bracket Scope

public class someClass {

        public static void main(String[] args) {
        VariableScope variableScope = new VariableScope();
        variableScope.someMethod();

    }

        public void someMethod() {
        int numberA = 0;

        {
                        // 해당 변수는 자신을 감싸는 Bracket 영역에서만 사용 가능하다.
            int numberB = 1;
            numberA += numberB;
        }

        //cannot find symbol.. symbol: variable arg...
                System.out.println(numberB);

        System.out.println("numberA = " + numberA); // 1
    }
}

Variable Shadowing

public class someClass {

        public static void main(String[] args) {
        VariableScope variableScope = new VariableScope();
        variableScope.someMethodA();
        variableScope.someMethodB();

    }
        //Class Scope Variable
    private int count = 10;

    public void someMethodA(){
                // Instance Variable 인 count 를 접근하여 사용한다.
        System.out.println("count = " + count);
    }

        // Class Scope > Method Scope
    public void someMethodB(){
                // Method 내부에 Instance Variable 인 count와 동일한 이름을 가진 변수가 존재한다.
                // 동일한 이름의 변수가 하위 Scope에서 존재한다면 하위 Scope의 변수가 상위 Scope의
                // 변수를 가리게(Shadowing) 된다. 
        int count = 1;
        System.out.println("count = " + count);
    }

}

Variable Life Cycle

  • Static Variable (Class Member Variable)

    static 키워드가 붙은 필드 변수를 말하며, 전역적으로 접근이 가능한 녀석이다.

    인스턴스 생성 이전에 미리 로드되어 생성되고 Heap 영역에서 관리된다. (JDK 8+)

    더 이상 해당 Class (Static Obejct)가 참조되지 않는 경우 GC 된다.

    (JDK 7 이전에선 Application 의 Life Cycle이 종료될 때까지 유지된다.)

  • Instance Member

    class scope 에서 작성된 변수를 말한다. 인스턴스의 참조 값을 통해서 접근과 조작이 가능하다.

    인스턴스 생성시에 메모리 공간을 가지고 기본 값으로 초기화되거나 생성자를 통해 주입된다.

    더 이상 해당 Instance가 참조되지 않는 경우 GC 된다.

  • Local Variable

    Method, Loop Scope 등에서 생성되어 사용되는 지역 변수들을 말한다.

    지역 변수가 포함된 Scope가 값을 반환하는 등, 로직이 끝난다면 사라진다.

 

Type Conversions, Casting, Type Promotion

Type Conversions

  • Widening Conversion

    Type 간의 호환성이 존재하고 변환될 타입이 이전에 지니고 있던 타입보다 큰 경우 자동으로 변환되는 것을 말한다.

    (int → long , float → double...)

  • Narrowing Conversion

    대상 Type들이 같은 리터럴을 다루지만, 변환될 타입이 이전에 지니고 있던 타입보다 작은 경우 Casting을 통해 변환할 수 있는 것을 말한다. 내부의 값에 따라 OverFlow가 발생할 수 있다.

    (int → short, double → float)

  • String Conversion

    Wrapper Class의. toString()을 통해 변환되는 것을 말하거나 혹은 parse를 통한 경우를 말한다.

      Integer numA = 100;
    
      String str = num.toString();
    
      int nunB = integer.parseInt(str);
  • Boxing/Unboxing Conversion (JDK 5+)

    Wrapper Class 가 Primitive Type 과의 연산이나 값 저장이 이루어지는 경우에 컴파일러에서 자동적으로 변환해주는 것을 말한다.

      int numA = 10;
      Integer numB = 10; // Integer.valueOf(10) _ 오토박싱
    
      int result = numA + numB; // numB.intValue() - int 10 _ 언박싱
  • Numeric Promotions

    이진 연산 시에 변수들의 크기가 호환되지 않는 경우, 호환성을 위해 지원하는 규칙들을 말한다.

    • 하나의 변수가 Double이면, 다른 변수도 Double로 변경된다.
    • 하나의 변수가 float이면, 다른 변수도 float으로 변경된다.
    • 하나의 변수가 long이면, 다른 변수도 long으로 변경된다.
    • 위의 상황을 만족시키지 않는 경우 Int로 간주한다.

Casting

어떠한 타입을 다른 타입으로 명시적인 변환을 하는 것을 Casting이라고 한다.

  • Primitive Type 간의 Type 변환
  • 구현, 상속 관계의 Class 간의 Type 변환

구현, 상속 관계의 Class 간의 Type 변환

public class Casting {
    public static void main(String[] args) {

        // Up Casting
        // 상위 클래스의 공통 메서드, 상태는 하위 클래스에서 사용 가능하다.
        // 하위 클래스의 재정의, 메서드, 상태는 상위 클래스에서 사용이 불가능하다.
        Parent child = new Child();
        child.name = "name";
        child.getDegree(); // Child의 메서드 접근 - 컴파일 에러

        // Down Casting
        Child childTwo = (Parent) childOne; 
        childTwo.getDegree(); // 사용 가능
    }
}

 

1차 및 2차 배열 선언하기

// 1차원 Int 배열 생성. 
// 정수 값을 넘김으로써 그 길이 만큼의 배열을 생성할 수 있다.

int[] numArrA = new int[**length**];

//혹은

int[] numArrA = {10,20,30,40,50};

int[][] numArrB = new int[length][length];

//혹은

int[][] numArrB = {{10,20}, {30,40}};

// for을 통한 초기화 방식
int[][] numArrB = new int[10][10];
for (int loop = 0; loop < numArrB.length; loop++) {
     for (int innerLoop = 0; innerLoop < numArrB[loop].length; innerLoop++) {
         numArrB[loop][innerLoop] = 10;
     }
}

//혹은

int[][] numArrC = new int[10][10];
for (int loop = 0; loop < numArrB.length; loop++) {
     Arrays.fill(numArrC[loop], 10);
}

// 출력 방식
for (int[] ints : numArrB) {
    System.out.println("ints = " + ints);
    for (int anInt : ints) {
        System.out.println("anInt = " + anInt);
    }
}

// 혹은

System.out.println("numArrB = " + Arrays.deepToString(numArrB));

 

Type Inference (타입 추론) , var

Type Inference 이란 정적인 언어에서 컴파일러가 컴파일 시점에서 변수를 초기화하는 리터럴을 판단하여 해당 타입으로 변환하는 것을 말한다.

자바는 JDK 10부터 Local Scope 내에서 var이라는 새로운 Type을 지원하게 되었다.

사용 시 주의점

  • 초깃값 (리터럴)이 없다면 컴파일 에러가 발생한다. var num;
  • null로 초기화하면 작동하지 않는다. var num = null;
  • local 변수가 아니라면 작동하지 않는다. public num = "String";
  • 람다 표현식에는 var 타입의 변수를 사용할 수 없다. var foo = (String s) → s.length() > 10;
  • 타입 없이 배열 초깃값을 넘겨도 작동하지 않는다. var arr = { 1, 2, 3 };
public void someMethod() {
    var str = "Hello world";
    assertTrue(str instanceof String);

        var num = 10;
    var numB = 10.10D;
    System.out.println(numB+num); // 20.1
}

 

참고한 자료.

Java Primitives versus Objects (https://www.baeldung.com/java-primitives-vs-objects)

Introduction to Java Primitives (https://www.baeldung.com/java-primitives#:~:text=The eight primitives defined in, objects and represent raw values.)

Java Primitive Conversions (https://www.baeldung.com/java-primitive-conversions)

Variable Scope in Java (https://www.baeldung.com/java-variable-scope)

Java 10 LocalVariable Type-Inference (https://www.baeldung.com/java-10-local-variable-type-inference)

자바의 신

Somaeja

모든 사람이 소매상이 될 수 있는 거래 플랫폼

 

지역 정보 기반의 물품 거래 플랫폼 RESTful API 서버 프로젝트입니다.

당근마켓과 유사한 서비스를 제공함으로써 사용자들은 설정된 지역 정보를 가지고 다른 사람들의 물품을 확인하고 구매 의사를 밝힐 수 있습니다.

 

Kakao Oven을 이용하여 프로토타입 설계를 진행하였으며, API 서버만 집중하여 개발을 진행합니다.



프로젝트의 대략적인 인프라 구조

 



프로젝트 설계 정보

 

프로젝트 UML

 

 

구조 변경 사항

  • AFFICHE

    • USER FK 를 가짐으로 USER의 NICKNAME 제외 (닉네임 변경시 처리가 불편하다.)

    • 11.25 테이블 네이밍 변경 AFFICHE → POST (이해하기 쉬운 익숙한 네이밍이 좋다)

    • 11.25 게시글 삭제 유무 DELETE_YN 이라는 Flag 를 추가

       히스토리 테이블 혹은 로그 테이블 생성하여 대체하자. (제거)

    • 테이블 이름 변경

  • AFFICHE INFO

    생성 이유 : 컨텐츠 사이즈의 따른 성능 부하 여부, 목록 조회시에 컨텐츠가 보이지 않는다.

    • AFFICHE Table에 컨텐츠를 두고 조회할 때 컨텐츠를 제외하는 방법이 있다.
    • 단순히 생성 이유 때문에 분리하는 것은 이득보다 관리하는 비용이 늘어날 수 있다.
    • AFFICHE TABLE 와 병합
  • LOCATION

    • PK, FK 를 통한 USER 와의 양방향 종속성(의존성) 제거
  • HASHTAG

    생성 이유 : AFFICHE 테이블에서 사용시 태그별 분할 문제점

    • 1 대 N 관계 해소를 위해 중간의 AFFICHE_HASHTAG 테이블 생성 (Toxi solution)

      → 과한 정규화, 복잡해질 수 있는 쿼리, 데이터의 고아 상태 발생 가능 (Scuttle으로 변경)

  • AFFICHE_LOCATION

    생성 이유 : POST가 여러 지역정보를 가지게 되면 다대다 관계가 됨으로 이를 분리하기 위해 작성

    • POST의 LOCATION 설정 정보를 하나만 제공하도록 요구사항을 변경함으로써 제거

 

 

 

프로젝트 개발 시 집중하는 부분

 

지속적인 코드 개선

  • 작성된 코드를 방치하는 것이 아니라 계속해서 개선합니다.
  • 가독성과 Depth 뿐만 아니라 호출 로직들도 개선하려 노력합니다.

 

멀티스레드 환경에서 안전한 코드 작성

  • 공유하는 자원을 최소화하고 최대한 모든 객체를 불변 객체로 작성합니다.



 

프로젝트 개발 시 준수 사항

 

Convention

 

Work Flow

  • Git Branch Strategy "Git-Flow"

 

Tech Stack

  • Spring Boot 2.3.5
  • JDK 11
  • Gradle
  • H2(Test, InMemory) + MySQL(Operational DB) + MyBatis
  • Naver Cloud Platform

1주 차 과제 : JVM 은 무엇이며, 자바 코드는 어떻게 실행하는 것인가.

JVM 이란 무엇인가

JVM (자바 가상 머신) 이란 Java와 Kotlin, Scala 등이 컴파일된 바이트코드를 실행하는 Virtual machine이다.

컴파일하는 방법 Java Compiler : wikipedia

Java Compiler 명령어를 사용하고 Source File의 Path를 넘겨주면 된다.

Directory\Study>Javac source.java  // File Path를 이용하여 컴파일한다. -> Directory\Study\source.java

바이트코드란 무엇인가? Bytecode : wikipedia

Java Bytecode 란 Java의 Execution Engine를 통해 수행될 수 있는 상태의 코드를 말한다. (기계어보다 추상적이다.)

1번의 컴파일 단계를 더 거침으로써, 작성한 코드를 JVM이 설치된 모든 기기에서 실행 가능하다. (JVM에 종속적)

실행하는 방법

>Java source  // source.class

자바 코드를 실행하기 위해선 .java 파일 (즉 SourceFile)를 컴파일하여 그 기기에 맞는 .class (Bytecode)로 변환해주어야 하며, 해당 파일을 인터프리터와 JIT 컴파일러를 통해 해석하여 프로그램을 실행하게 된다.

자바 프로그램의 기본적인 실행 절차.

  1. 해당 Java 파일을 Java Compiler가 Class 파일로 (Bytecode) 변환
  2. 클래스 로더가 JRE 환경을 조성한 후 Application 클래스 로더는 해당 클래스를 PATH를 이용해서 로드 (JVM 실행)
  3. 로드된 바이트 코드를 실행 엔진으로 넘기게 되고, 실행 엔진 내부의 인터프리터에서 내부 클래스를 행단위로 읽어 들인다.
  4. (Static Obj의 경우에는 실행 엔진 이전에 클래스 로더의 초기화 단계에서 생성, 그 외의 정보는 PERM으로)
  5. 인터프리터 내부의 Counter가 만족되면 JIT 컴파일러를 호출하여 실행하게 된다. (중복되는 코드는 캐싱된 코드를 사용한다.)
  6. Class 파일을 기계어로 컴파일하고 런타임 메모리에 올리게 된다.
  7. Main Method에 일정한(기본적으로 빈 배열 등) 인자 값을 넘기고 실행한다.
  8. Main Method 내부에서 필요한 객체가 있다면 해당 객체를 생성하기 위해 PERM Gen의 Class 정보를 읽는다.
  9. 리플렉션을 통해 클래스 이름을 가지고 타입을 특정하고 해당 클래스의 메서드, 변수, 생성자 등을 읽어서 생성한 뒤에 Heap 영역에 띄운다.
  10. 해당 객체를 지역 변수를 통해 참조하거나 다른 객체의 인스턴스 변수가 참조하여 사용한다.

JIT 컴파일러란 무엇이며, 어떻게 동작하는지

JIT 방식은 프로그램을 실행하는 동안 필요한 바이트코드를 기계어로 번역하는 기법을 말하며, JVM에선 해당 기법을 구현한 녀석을 JIT Compiler라고 한다.

JVM 구성 요소

Class Loader

  • Bootstrap Class Loader
    첫 번째로 동작하는 Class Loader이며, jre 실행 환경을 조성하는 등의 Java Library Class들을 로드하게 된다.
    Runtime, i18n Class... (Nativecode로 구성되어 있다.)

  • Extention Class Loader
    jdk/ jre 패키지 내부의 ext 디렉터리에 있는 Class들을 로드하게 된다.

  • Application Class Loader (System Class Loader)
    Class Path를 기반으로 실행할 Class를 로드하게 된다.

로드된 Class 들은 로딩 -> 링크 -> 초기화의 절차를 진행하게 된다.

링크, 초기화 이후에는 참조로 연결된 Class 들을 로드하는 절차가 추가될 수 있다.

 

Execution Engine

  • interpreter
    프로그램 실행을 위하여 Bytecode를 Nativecode로 변환하기 위해 행별로 해석하는 구현체.
    내부적으로 Counter를 지니고 있으며, 해당 행을 해석할 때마다 Counter의 수치가 증가한다.
    일정 수치를 만족하게 되면 JIT Compiler를 호출하여 Dynamic Loading, Compile을 수행하게끔 한다.

  • JIT Compiler
    interpreter의 요청에 따라 컴파일을 수행하는 구현체이다.
    성능 최적화를 위하여 코드를 캐싱하다가 중복 코드가 발견된다면 캐시 된 코드를 재사용한다

Runtime Data Areas

  • Permanent Generation
    Class, Interface, Package 등의 Meta 정보를 저장하고 String Pool 제외한 Constant Pool을 관리하는 공간이다.
    JDK 7 에선 String Pool 이 Heap으로 이동하였으며,
    JDK 8 에선 Static Object 이 Heap 으로 이동하고, OS Memory 상에서 위치하게 변경되었다.

  • Heap Area
    New로 생성되는 Instance 들과, Static Object, String Pool 이 관리되는 영역이다.
    GC를 통하여서 메모리 관리가 이루어지고, 다른 모든 영역에서 공유가 되는 영역이다.
    Young 영역, 2개의 Survivor 영역, Old 영역으로 이루어져 있다. (Hotspot JVM. JDK 8 기준)
    Young과 Survivor 영역에서 이루어지는 GC를 Minor GC라고 하고, Old 영역에서 이루어지는 것을 Major GC 라고 한다.
    JDK 9부터는 G1 GC라는 모듈이 Default로 설정되어 있으며, 영역별로 나누어져있지 않고 메모리 영역을 2천 개 이상의 작은 영역으로 분할하게 되어있다.

  • Stack Area
    Scope에 따른 크고 작은 Stack Frame과 지역 변수, 스레드 들의 Life Cycle이 진행되는 영역이다.
    Application이 실행되는 동안 Main Method Stack Frame 위에서 다른 Frame 이 생성되고 사라진다.
    스레드들은 각각의 Stack 공간(Runtime Stack)을 가지게 되고, 여러 자원을 공유할 수 있다. (가시성 문제가 발생할 수 있다.)

  • PC Register
    Thread 가 생성될 때 같이 생성되는 공간이다. Thread가 실행할 명령의 위치를 기록한다.

  • Native Method Stack
    자바 이외의 언어에서 제공되는 Method의 정보가 저장되는 공간이다.
    JNI(Native Method Interface)와 Library를 통해 표준에 가까운 방식으로 구현된다. (Thread Class 등)

JDK와 JRE의 차이

JRE : Java Runtime Environment
Java Application을 실행하기 위한 최소의 실행 환경을 제공하는 것.
JVM + Java API (핵심 라이브러리)가 포함되어 있다.

JDK : Java Development Kit
JRE에서 제공하는 실행 환경과 개발에 필요한 API, 명령어(메모리 확인, 배포 등), Compiler를 포함하고 있는 것.
JVM, Java API, Java Tool, Java Compiler 가 포함되어 있다.

< Reference >

https://ko.wikipedia.org/wiki/%EB%B0%94%EC%9D%B4%ED%8A%B8%EC%BD%94%EB%93%9C https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC
[JVM Performance Optimizing 및 성능 분석 사례]

+ Recent posts