OAS는 RESTful API에 대한 정보들을 정의하여 소스 코드에 접근하지 않아도 서비스의 기능을 검색, 확인하고 사용할 수 있게끔 지원해주는 표준 인터페이스이다.
OAS 3는 2017년 7월에 발표된 spec이며, 많은 개선이 이루어졌다고 한다. 해당 spec은 JSON, YAML을 통하여 작성할 수 있다. documentation generation tools를 사용하여서 해당 API의 기능, 테스트 코드 등을 사용자(클라이언트 개발자) 입장에서 쉽게 알 수 있게 해 준다.
(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'
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는 콘솔을 이용해서 데이터에 대해 별도의 관리를 해야 함으로 테스트 단계에서 필요 없는 복잡함을 가지게끔 하기도 하고, 프로젝트를 사용할 수 있는 여러 환경에서 다른 설정값이 존재할 수 있기에 신경을 써야 한다.
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 관련 자동 구성 어노테이션이었는데,
디렉터리 내부의 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();
}
}
모든 값, 범위를 기반으로 판단하는 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);
}
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()에서 사용된다.
// 모든 테스트가 실행되기 이전에 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");
}
@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();
}
}
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);
}
}
}
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);
}
}
}
// 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 Type과 Heap에서 참조되는 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의 참조 값을 할당하기 위해 사용한다.
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+"일 입니다.";
}
어떠한 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을 사용할 수 있다. )
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)
성능 상의 차이가 있을 수도 있다.
Boxing과 UnBoxing의 오버헤드
Heap 영역에서의 생성 후 참조 값의 연결 과정. (Constant Pool 과의 참조도 있을 수 있다.)
// 변수를 초기화할 때 사용되는 값들도 모두 리터럴이다.
int numberA = 10;
double numberB = 10.11D;
String str = "문자열 리터럴";
변수 선언 및 초기화하는 방법
흐름
타입에 맞는 메모리 공간을 확보하고 (byte b;)
타입에 맞는 리터럴을 저장한다. (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 Bytecode 란 Java의 Execution Engine를 통해 수행될 수 있는 상태의 코드를 말한다. (기계어보다 추상적이다.)
1번의 컴파일 단계를 더 거침으로써, 작성한 코드를 JVM이 설치된 모든 기기에서 실행 가능하다. (JVM에 종속적)
실행하는 방법
>Java source // source.class
자바 코드를 실행하기 위해선 .java 파일 (즉 SourceFile)를 컴파일하여 그 기기에 맞는 .class (Bytecode)로 변환해주어야 하며, 해당 파일을 인터프리터와 JIT 컴파일러를 통해 해석하여 프로그램을 실행하게 된다.
자바 프로그램의 기본적인 실행 절차.
해당 Java 파일을 Java Compiler가 Class 파일로 (Bytecode) 변환
클래스 로더가 JRE 환경을 조성한 후 Application 클래스 로더는 해당 클래스를 PATH를 이용해서 로드 (JVM 실행)
로드된 바이트 코드를 실행 엔진으로 넘기게 되고, 실행 엔진 내부의 인터프리터에서 내부 클래스를 행단위로 읽어 들인다.
(Static Obj의 경우에는 실행 엔진 이전에 클래스 로더의 초기화 단계에서 생성, 그 외의 정보는 PERM으로)
인터프리터 내부의 Counter가 만족되면 JIT 컴파일러를 호출하여 실행하게 된다. (중복되는 코드는 캐싱된 코드를 사용한다.)
Class 파일을 기계어로 컴파일하고 런타임 메모리에 올리게 된다.
Main Method에 일정한(기본적으로 빈 배열 등) 인자 값을 넘기고 실행한다.
Main Method 내부에서 필요한 객체가 있다면 해당 객체를 생성하기 위해 PERM Gen의 Class 정보를 읽는다.
리플렉션을 통해 클래스 이름을 가지고 타입을 특정하고 해당 클래스의 메서드, 변수, 생성자 등을 읽어서 생성한 뒤에 Heap 영역에 띄운다.
해당 객체를 지역 변수를 통해 참조하거나 다른 객체의 인스턴스 변수가 참조하여 사용한다.
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 가 포함되어 있다.