[자바 스터디 2주차] 자바 데이터 타입, 변수 그리고 배열

 

2주차 과제: 자바 데이터 타입, 변수 그리고 배열 · Issue #2 · whiteship/live-study

목표 자바의 프리미티브 타입, 변수 그리고 배열을 사용하는 방법을 익힙니다. 학습할 것 프리미티브 타입 종류와 값의 범위 그리고 기본 값 프리미티브 타입과 레퍼런스 타입 리터럴 변수 선언

github.com

목표

  • 자바의 프리미티브 타입, 변수 그리고 배열을 사용하는 방법을 익힙니다.

학습할 것

  • 프리미티브 타입 종류와 값의 범위 그리고 기본 값

  • 프리미티브 타입과 레퍼런스 타입

  • 리터럴

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

  • 변수의 스코프와 라이프타임

  • 타입 변환, 캐스팅 그리고 타입 프로모션

  • 1차 및 2차 배열 선언하기

  • 타입 추론, var

  • 라이브 피드백

프리미티브 타입 종류와 값의 범위 그리고 기본 값

프리미티브 타입(=기본형 타입=원시형 타입)이란 변수의 주소값에 값이 그 자체로 저장되는 데이터 타입이다.

기본형 타입은 단어 그대로 기본 값이 있기 때문에 NULL이 존재하지 않는다.

만약 NULL을 넣고 싶다면 기본 자료타입을 객체로 다루기 위해서 사용하는 Wrapper클래스를 사용하면 된다.

해당 데이터 타입은 값이 할당되면 Stack 영역에 값이 저장되며 사용하는 타입의 값의 범위를 벗어난 데이터를 넣으면 컴파일 에러가 발생한다. 

 

프리미티브 타입의 종류

※ 정수형 데이터 저장시 일반적으로 int를 쓰는 이유는?

JVM의 피연산자 스택이 피연산자를 4 Byte단위로 저장하기 때문에 크기가 4 Byte보다 작은 byte나 short의 값을 계산할 때는 4 Byte로 변환하여 연산이 수행된다고 한다.

그래서 자바에서는 정수형 연산을 int형으로만 진행한다.(short + short 할시 둘다 int형으로 변환하여 수행한다)

따라서 변환과정을 거치지 않는 int형을 쓰는게 훨씬 더 효율적이다.

 

boolean

: Java가 데이터를 다루는 최소 범위가 1 Byte이기 때문에 true와 false를 저장하는데 1bit면 충분하지만 어쩔수 없이 1 Byte를 사용한다.

 

char

: Java는 유니코드를 사용하여 모든 문자를 지원한다. Java에서 유일하게 제공하는 unsigned 형태이다.

 

 

자바는 문자코드를 유니코드로 사용한다. 왜 유니코드를 사용할까?

아스키코드를 이용해 다른 언어를 표현하기에는 7비트로는 부족했기 때문에

8비트로 확장한 아스키 코드를 사용하여 다른 언어 또한 지원하기 위해 그렇다고 한다.

(모든 언어 입력가능 : 자바의 특징중 하나)

 

floar과  double

float는 소수점 이하 7자리의 정밀도를 가지고 있고, double은 소수점 이하 15자리의 정밀도를 가지고 있다.

하지만, 방대한 실수형 데이터를 표현하는 경우라서 소수점을 연산할 때 작은 오차가 발생할 수 있다는 점을 유념해야 한다.

 

실수는 부호, 가수, 지수로 구성되며, 부동 소수점 방식을 사용한다.

부동소수점 방식을 사용하여 모든 가수를 0보다 크거나 같고 1보다 작은 값 범위의 값으로 만들고 원래 수를 표현하기 위해 10을 몇 번 거듭 제곱해야 하는지 지수로 표현한다.

 

즉, 1.234라는 값을 0.1234 * 10^1로 표현 한다는 것을 의미한다.

 

float타입은 부호(1비트) + 지수(8비트) + 가수(23비트) = 32비트를 사용하고

double타입은 부호(1비트) + 지수(11비트) + 가수(52비트) = 64비트를 사용한다.

 

※ 왜 int 범위가 ~21억 ~ +21억인가? 기타범위도 등등...

 

byte 자료형의 메모리 크기는 1byte이다.

즉 8비트이다. 8비트로 표현 가능한 값의 개수는 2의 8제곱이다.

2의 8제곱은 256인데 왜 표현범위가 0 ~ 255가 아니고 -128 ~ +127 까지 일까?

 

컴퓨터에서 음수를 표현하기 위해 MSB라는 것을 사용한다

MSB는 Most Significant Bit의 줄임 말로 최상위 비트를 뜻한다.

최상위 비트란 일반적으로 가장 왼쪽에 위치한 비트를 뜻한다.

 

8비트를 다음과 같이 표현할 수 있다고 가정해보자.

빨간동그라미친 가장 왼쪽에 나타난 비트를 MSB라고 부르고 부호 비트라고도 한다.

이 값이 1이면 음수, 0이면 양수라고 판단한다.

 

즉, 부호가 있는 자료형의 경우 1비트를 부호를 표현하기 위해 사용하기 때문에

현재 예시를 기준으로 -128 ~ 127까지의 값 표현 범위를 가진다.

양수는 0이 포함되기 때문에 128이 아니다.

만약 0 ~ 255까지 표현하고 싶다면, 다시 말해 부호 비트의 자리도 데이터로 취급하려 한다면 unsigned(부호가 없는)자료형을 사용하면 된다. 음수는 표현하지 못하는 대신 양수 표현 범위가 두배 늘어난다.

 

자바에는 unsigned타입의 자료형을 지원하지 않는다.

그래서 보통 표현 범위를 넘을 때 더 큰 자료형을 사용하고는 한다.

 

unsigned 사용법

 

※ 음수 2진수 표현법

1의 2진수

0000 0001

 

-1을 2진수로 표현하려면 2의보수를 사용

※ 2의보수는 비트를 반전(0->1, 1->0)시키고 1의자리에 +1하는것

 

-1 구하는법

0000 0001 --(비트반전)----> 1111 1110 ----(일의자리 +1)-----> 1111 1111

 

번외로 십진수 -127과 128 2진수 표현

-127은 이진수로 1000 0001이고, -128은 이진수로 1000 0000이 된다.

-128의 이진수는 언뜻 보기에 양수 128과 같아보인다

하지만 가장 왼쪽비트는 +,-을 표현하는 비트이기 때문에 양수 128이아닌 -128로 사용되는 것이다.

 

음수 2진수에서 음수 십진수를 구할려면 2의 보수와 반대방향으로 해주면된다.  

-1을 빼주고 비트 반전시킨뒤 10진수로 변환해주고 마이너스를 붙여준다.

 

Ex)

1000 0001 ---(일의자리 1 빼기)--> 1000 0000 ----(비트 반전)--> 0111 1111 ---(십진수 변환 후 -붙이기)-----> -127

 

프리미티브 타입과 레퍼런스 타입

프리미티브 타입은 위의 설명처럼 특정 데이터 값을 가지는 타입이다. 그럼 레퍼런스 타입은 뭘까? 

레퍼런스(reference)는 참조의 뜻을 가지고 있으며 이는 실제 값이 저장되어 있는 곳의 위치(=주소값)를 저장한다.

레퍼런스 타입의 종류로는 클래스, 인터페이스, 열거형, 배열 등 프리미티브 타입을 제외한 것들이다.

기본적으로 Java.Lang.Object를 상속받을 경우 참조형이 된다.

 

참조 타입 선언시 생성된 변수는 스택(Stack)영역에 생성되고, 객체는 힙(Heap) 영역에 생성된다.

 

출처 : https://m.blog.naver.com/PostView.nhn?blogId=jaeyoon_95&logNo=221187488789&proxyReferer=https:%2F%2Fwww.google.com%2F

자바에서 배열은 참조타입이다. 

int[] scores = {10, 20, 30} 

이걸 풀어쓰면 아래와 같다.

int[] scores = new int[]{10, 20, 30}

 

하나씩 살펴보자

1. scores라는 변수는 스택에 저장된다.  

2. 배열은 참조타입 즉, 객체이기 때문에 new int[]를 통해 힙영역에 생성해준다. 위처럼 3번지라고 하자.

3. 3번지에는 배열생성시 초기화한 데이터 10, 20, 30이 담긴다.

4. 생성한 힙영역의 주소를 scores 변수에 담아준다.

 

※ 추가

출처 : https://yaboong.github.io/java/2018/05/26/java-memory-management/

1. new 키워드를 통해 생성하려는 객체를 저장할 수 있는지 힙영역의 빈공간을 찾은 다음, 빈 List를 참조하는 listArgument라는 변수를 스택에 할당한다.

 

2. listArgument.add("yaboong")는 

listArgument.add(new String("yaboong"));과 같다고 한다. 이것 또한 new 키워드로 힙영역에 생성해주고 yaboong를 String이라는 객체에 데이터를 할당한다. 

※ String은 객체다. 

List내부의 인덱스에 하나씩 add()된 데이터가 존재하는 주소값을 가지게 된다. 

 

리터럴

리터럴은 실제로 저장되는 그 자체로 값을 의미하는 것이다. 다시한번 말하자면 데이터 그 자체가 리터럴이다.

 

'='의 오른쪽에 있는 값이 리터럴이다. 

원래는 상수라 불렀지만, 프로그래밍에서는 상수를 '값을 한 번 저장하면 변경할 수 없는 저장공간'으로 정의하였기 때문에 이와 구분하기 위해 리터럴이라 정의하였다.

※ final이 붙으면 상수가 된다.

 

정수 리터럴

ob10 : 접두문자ob(2진수)

o10   : 접두문자o(8진수)

10     : 접두문자 없음(10진수)

0x10     : 접두문자 0x(16진수)

l or L : 접미문자(Log 타입 리터럴)

 

이걸 이제알았다니....

 

실수 리터럴

- 실수 타입 리터럴은 double 타입으로 컴파일 되므로 

float 타입인 경우 명시적으로 f 또는 F를 명시해줘야 한다.

double 타입도 d나 D를 명시해줘도 되지만, 안해줘도 상관없다.

추가로 아래와 같은 표현들도 가능하다.

https://github.com/yeo311/java-study-with-whiteship/tree/main/week2

 

문자 리터럴

char a = 'a'; //작은 따옴표안에 표현

char a = '\u20AC'와 같이 유니코드로 입력 가능

 

특수문자 리터럴(이스케이프 시퀀스)

\b : 백스페이스

\t : 탭

\n : 개행

\f  : form feed

\r  : 캐리지 리턴

\"  : 쌍따옴표

\'  : 따옴표

\\ : 역슬래시

 

문자열 리터럴

String a= "abc";  //큰 따옴표로 표현

String은 프리미티브 타입이 아니고 레퍼런스 타입이다. 그럼에도 리터럴을 지원하는데 리터럴 방식으로 String에 값을 주면 Heap영역안의 String constant pool영역에 저장된다. 만약 리터럴 방식으로 동일한 값을 할당할 시 일반적인 레퍼런스 타입처럼 Heap에 객체를 생성하는게 아닌 String constant pool에 만들어진 기존의 리터럴을 참조하는 방식이다.

 

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

변수 선언

변수를 선언한다는 것은, 저장공간을 확보하겠다는 의미이다.

변수를 선언하기 위해선 변수타입과 변수이름을 지정해줘야 한다.

변수타입 변수명;

Ex) int a;

 

변수타입(변수에 저장될 값이 어떤타입인지?)

: 위에서 언급한 기본형타입(int, double, float ...)이 될수도 있고 참조타입이 될수도 있다.

 

변수명(변수에 붙힌 이름)

: 변수명은 식별자라고 하며, 식별자는 같은 영역(메서드 내 지역변수, 같은 클래스 내 인스턴스 변수)내에서 서로 구분될 수 있어야 한다. 그리고 변수의 명명규칙도 있다.

 

반드시 지켜야하는 규칙

1. 대소문자가 구분되며 길이에 제한이 없다.

  - True와 true는 서로 다른 것으로 간주된다.

2. 예약어를 사용해서는 안된다.

  - true는 예약어라서 사용할 수 없지만, True는 가능하다.

3. 숫자로 시작해서는 안된다.

4. 특수문자는 '_'와 '$'만을 허용한다. 

 

변수의 초기화

변수의 초기화란 변수를 사용하기 전 값을 할당하는 것을 뜻한다.

Ex)

int a = 100;

Stack은 자동초기화를 지원하지 않는다. 그러기 때문에 Stack에 선언한 변수에는 쓰레기 값이 채워진다.

선언당시 사용자가 값을 대입하지 않으면, 최초로 쓰레기 값이 대입된다.

=> 이래서 변수는 사용되기 전에 적절한 값으로 초기화 하는것이 좋은건가보다

 

※ 쓰레기 값이란?

메모리는 독점하는게 아닌 공유하는 자원이다.

Stack은 LIFO 구조를 가지고 있다. 객체의 메서드가 실행되면 메서드 사용을 위한 메모리가 Stack에 할당된다.(push) Stack안에 할당된 메소드는 지역변수들이 저장된다. 메소드 실행을 끝마치면 메소드를 반환한다.(pop) 이때, 반환한다는 것은 메모리를 지우는게 아니다.(메모리는 공유자원이다) 메소드가 더이상 참조되지 않는 것이므로 주소값을 지우는 것이다. 그럼으로, 메소드가 사용한 지역변수 등의 데이터를 그대로 남아있고 새로 호출된 메서드가 같은 메모리 공간을 할당받는다. 즉, 이전 메서드의 데이터를 쓰레기 값이라고 한다.

 

 

※ 지역변수는 사용하기 전 반드시 초기화를 해야한다.

Why? 

=> 일단 인스턴스 변수는 자동으로 기본값으로 초기화 해준다. 하지만 지역변수는 초기화하지 않고 사용시 컴파일 에러가 발생한다. 일단 자바설계자들이 자바 스펙을 정했다고 한다.  

http://docs.oracle.com/javase/specs/jls/se5.0/html/defAssign.html

"Each local variable (§14.4) and every blank final (§4.12.4) field (§8.3.1.2) must have a definitely assigned value when any access of its value occurs."

=> 각 지역 변수(제14.4조)와 모든 블랭크 파이널(제4.12.4조) 필드(제8.3.1.2조)는 해당 값에 대한 액세스가 발생할 때 확실히 할당된 값을 가져야 한다.

 

여러 사람들의 의견은 다음과 같다.

1. 지역변수는 메서드 내에서 일부 계산을 위해 쓰이는 경우가 많다. 따라서, 기본값을 적용하면 예상치 못한 값이 될 수 있으므로 컴파일러는 프로그래머가 변수에 액세스하기 전에 일부 값으로 초기화하도록 요청합니다. 

 

2. 클래스의 멤버변수는 사용되기 전에는 초기화 됐는지 컴파일러가 체크할 수 없지만, 로컬 변수는 컴파일러가 체크할 수 있으므로 명시적으로 초기화를 강제했다.

+ 컴파일러단에서 최대한 오류를 잡아내기 위해서다.

 

3. 기존 C언어에 비해 메모리를 자동으로 관리하는 장점을 가진 자바에서는 런타임 환경에서 메모리참조 오류를 가능한 막아야 한다. 그래서 컴파일환경에서 가능한한 초기화하는것을 강제한것이다.

로컬변수는 스택에 올라가므로 컴파일타임에 컴퓨터가 미리 로직에 대해 검증이 가능하여 컴파일 오류로 잡아낼 수 있다.

 

기타등등... 

 

예전에는 아 그렇구나 하고 넘긴것을 "왜?"라는 의심(?)을 가지고 공부하니 훨씬더 흥미롭고 재밌다!!

 

※ 

int a = 1; 한줄 짜리 코드가 바이트코드에서는 두줄이다.

0: iconst_1 상수 0을 stack에 푸시

1: istroe_1  push한 값을 로컬 변수 1에 저장

 

위 내용은 1주차 내용중 한부분이다.

int a = 1; 한줄을 선언했음에도 op코드에서는 두줄이다. 

이부분이 왜 중요하냐? 

위 소스코드를 예시로 들면 멀티쓰레드 돌릴 때

코드는 한줄인데 사실상 두줄이기 때문에

: number = number.add(BigDecimal.valueOf(0.1)); 

add를 A스레드에서 처리하고 B라는 스레드가 add를 한번더 처리하면

그다음 A라는 스레드가 number에 값을 할당하면 값이 이상해질 수 있음

 

※ 싱글톤예시

이렇게 코드를 짜면 인스턴스를 사용하지 않아도 만드는 문제가 있음

=> 만들어 놓고 사용하지 않으면 메모리 낭비

 

허나 이렇게 짜면 문제가 new Temp();를 할때 멀티스레드 환경에서 문제가 됨

=> 두개 이상의 스레드가 getInstance를 호출하여 스레드를 얻기 위한 경합을 벌이는 와중에 인스턴스가 2개이상 생성될 수 있음

=> 그래서 Lock을 잡고 만들어야 함

 

LazyHolder 기법

Temp 클래스의 getInstance() 메서드에서 LazyHolder.temp를 참조하는 순간 Class가 로딩되며 초기화가 진행된다. 

 

LazyHolder 기법이란 JVM에게 객체의 초기화를 떠넘기는 방식으로, 멀티스레드 환경에서도 객체의 단일성을 보장할 수 있다.

객체 생성을 담당할 내부클래스를 하나 정의하는데, 이것이 LazyHolder 다.
Temp클래스에는 LazyHolder 클래스의 변수가 없으므로, Temp클래스 로딩 시에

Temp클래스를 초기화하지 않지만, getInstance 메서드가 호출될 때 LazyHolder가 로딩되며 초기화가 진행된다.

클래스의 내부의 클래스는 외부의 클래스가 초기화될때 초기화되지 않고, 클래스의 static 변수는 클래스를 로딩할 때 초기화되는 것을 이용한 기법이다.

Class 를 로딩하고 초기화하는건 JVM 의 영역이고 Thread Safe 를 보장하기 때문에 volatile이나 synchronized 같은 키워드가 없어도 된다.

(synchronized 키워드를 사용하면 성능이 100배 이하로 떨어진다고 한다.)

 

변수의 스코프와 라이프타임

변수의 스코프란 변수들이 사용 가능한 범위를 뜻한다.

다시 말해서, 변수에 대한 접근과 변수가 존재할 수 있는 영역을 의미한다.

라이프타임은 어느 시점까지 변수가 살아있느냐를 의미한다.

변수는 스코프에 따라 3가지로 나눌 수 있다.

 

멤버 변수(인스턴스 변수)

클래스 영역에 선언되며 클래스의 인스턴스를 생성할 때 만들어진다. 

인스턴스 변수값을 사용하기 위해서는 인스턴스를 생성해야한다.

인스턴스 별로 별도의 저장공간을 확보함으로 인스턴스별로 다른값을 가질 수 있다.

Car car1 = new Car();

Car car2 = new Car();

=> 힙영역에 car1객체와 car2객체가 생성되어있다.

생성한 객체가 Heap영역에 살아있을때 까지 존재한다.

 

클래스 변수

멤버변수에 static 키워드가 붙혀진 변수로서, 모든 클래스에 값을 공유한다.

클래스가 메모리(Method 영역)에 올라갔을때 선언되기 때문에 인스턴스를 생성하지 않고 사용할 수 있다.

프로그램종료시까지 살아있다.

클래스가 처음 로딩될 때 단 한번 초기화 된다.

 

지역변수

메서드 내에 선언되는 변수이다.

메소드 종료와 함께 소멸된다.

 

출처 : https://itmining.tistory.com/20

초기화 순서

출처 : https://catsbi.oopy.io/6541026f-1e19-4117-8fef-aea145e4fc1b
출처 : https://catsbi.oopy.io/6541026f-1e19-4117-8fef-aea145e4fc1b

클래스 변수 초기화(1~3) : 클래스가 처음 메모리에 로딩될 때 차례대로 수행된다.

인스턴스변수 초기화(4~7) : 인스턴스를 생성할 때 차례대로 수행한다.

 

 

※static과 nonstatic 메모리 로딩시점

인스턴스 메서드는 인스턴스를 new해서 만든 다음부터 사용 가능

static은 클래스가 로딩하는 시점에 만들어지지만

위 코딩은 클래스 로딩하는 시점에 만들어지지도 않은 인스턴스의 메서드를 써서 컴파일에러

 

이미 로딩된 static 변수를 인스턴스의 메서드에서 사용 가능

 

※ 멤버 변수와 지역변수의 변수명이 같다면?

=> 지역변수를 선택한다.

 

※ for문의 ()는 상위 지역 변수의 영향을 받는다.

 

타입 변환, 캐스팅 그리고 타입 프로모션

코딩을 하다보면 서로 다른 타입간의 연산을 수행하는 경우가 있다. 자바에서는 동일한 데이터 타입끼리만 연산이 가능하기 때문에 타입변환을 통해 서로 일치시켜줘야 한다. 이럴 때 사용하는 방법이 캐스팅(강제 형변환)과 타입 프로모션(자동 형변환)이 있다. 

 

타입 프로모션(자동 형변환)

앞서, 연산시 타입을 일치시켜줘야 한다고 했다. 

double = double + int 형식이다.

자동형변환의 경우 자료형의 크기가 큰 방향으로 형변환이 일어나기 때문에 int 타입인 i가 (double)로 자동형변환이 되었다. 이렇게 하는 것이 값손실의 위험이 더 적어서 올바른 결과를 얻을 확률이 높다.

 

출처 : https://keep-cool.tistory.com/14

보통 자료형의 크기가 큰 것일수록 값의 표현범위가 크기 마련이지만, 실수형은 정수형과 값을 표현하는 방식이 다르기 때문에 같은 크기 일지라도 실수형이 더 크다?

 

※ (byte, short)와 char은 서로 형변환이 될 수 없다.

: char의 범위는 0~65536인 양수만 저장되고 byte와 short는 양수+음수까지 저장되므로 표현하는 값의 범위가 달라서 어느 쪽으로의 형변환도 값 손실이 발생할 수 있다.

 

 

캐스팅(명시적 형변환)

타입 프로모션이 진행되지 않는 상황에서도 캐스팅을 통해 형 변환을 할 수 있다.

byte b = 1000;은 byte 의 범위(-128 ~ 127)을 넘는 값인 1000을 저장할려고 했기 때문에 

'incompatible types: possible lossy conversion from int to byte' 에러가 발생한다.

byte b = (byte)1000; 는 프로그래머가 의도적으로 한것을 감지하고 에러를 발생시키지 않는다.

참고로 b는 -24가 출력된다. 1000은 byte의 크기를 벗어낫다. 

설명이 약간 어려운데 만약 1000이 아니라 128이였으면 -128이 출력되고 130이였으면 -126이 출력된다.

즉, byte의 범위를 마이너스부터 오버되는 숫자만큼 순차적으로 도는것이다. 

 

num2같은 경우는 5.44를 int로 캐스팅 했기 때문에 소수점자리수가 짤려나가고 정수형인 5만 저장된다.

 

1차 및 2차 배열 선언하기

배열의 길이는 최초 생성시 선언한 길이에서 정적이므로 생성 후 변경할 수 없다.

애초에 List타입을 생성하면 되지만 피치못할 경우엔 새로운 배열을 생성해준뒤

System.arraycopy()를 사용하여 기존배열의 내용을 새로운 배열로 복사해준다.

 

출처 : 자바의 정석 3판

 

1차원 배열 선언의 3가지 방법

(1) int arr[] = new int[3];

arr[0] = 1;

arr[1] = 2;

arr[2] = 3;

 

(2) int arr[] = new int[]{1, 2, 3}

=> 초기화 과정에서 길이를 알 수 있기 때문에 대괄호안의 숫자는 생략, 들어가면 컴파일 오류 발생

 

(3) int arr[] = {1, 2, 3}

=> new int[] 생략 가능

 

2차원 배열 선언의 3가지 방법

(1) int[][] arr = new int[1][2];

arr[0][0] = 1;

arr[0][1] = 2;

 

(2) int[][] arr = new int[3][3]{

  {11, 22, 33},

  {44, 55, 66},

  {77, 88, 99}

};

 

(3)

int[][] arr = {

  {11, 22, 33},

  {44, 55, 66},

  {77, 88, 99}

};

 

3차원 배열

int[][][] a = new int[2][2][3];

=> 총 8개

https://b-programmer.tistory.com/225

이 3차원 배열의 뜻은 2 * 2 이차원 배열이 개수가 총 2개라는 뜻.

 

타입 추론, var

타입 추론이란 정적 타이핑을 지원하는 언어에서, 타입이 정해지지 않은 변수에 대해서 컴파일러가 변수의 타입을 스스로 유추하는 것이다.(Type Interface)

즉, 타입을 명시하지 않아도 되며, 코드량을 줄이고 코드의 가독성을 높일 수 있다.

var은 자바10부터 생겼으며 자바11부터는 이를 통한 람다 타입 지원도 생겼다.

컴파일러는 개발자가 입력한 초기화 값을 통해 타입을 유추하는데 즉 var은 컴파일러가 유추할 수 있도록 반드시 데이터를 초기화 해야 한다.

주의사항

(1)이니셜라이져가 없이 var은 동작하지 않는다.

(2)자바7에서 나온 다이아몬드 연산자또한 var와 함께 사용을 못한다. (제네릭을 명시해줘야 함)

사용법

(1) var로 적을시에 협업하는 개발자가 쉽게 타입을 유추할 수 없기 때문에 var 데이터타입을 적용하면 자연스럽게 네이밍에 신경쓰게 된다. ???

다른 클래스 파일에 두개 이상의 함수를 조회해야 할 경우 위의 코드와 같이 타입을 유추하기 힘들 수 있다. 물론 네이밍을 잘하면 커버할 수 있는 단점이라고 생각하지만, 단순한 getter/setter함수외에는 한계가 있어 보이고, 네이밍을 잘하더라도 헷갈리는 경우가 많을꺼 같다. 

 

올바른 사용법

(1)forEach

이렇게 작성하면 IDE에서 var 키워드를 Hyumwoo 클래스로 인식할 수 있게 되고, 컴파일 시에도 var키워드를 Hyumwoo로 변환하게 된다. 타이밍이 정말 간결해지고 Object타입으로 미리 단정 지을 필요도 없다.

 

(2)람다(Java 11)

LTS(Long-term support, 첫번 째 LTS버전)인 자바 11은 람다 인자에도 var를 넣게 해주는데, 이게 왜 중요하나면, 일반 람다의 경우 파라미터 어노테이션을 못 집어넣는다. 만약 어노테이션을 넣고 싶으면 따로 메소드로 빼던가, 익명 클래스로 정의해야 했었다. 하지만 자바8부터 람다 인자는 타입 추론의 기초였던 게, 자바 11부터는 타입 추론의 유연성을 추가했다.

어노테이션의 장점만 생각해도 이건 유연성이 증대되는 효과가 있다.(초보인 내가봐도 좋아보인다)

 

(3)익명 클래스

일단 보통 가독성 때문에 대부분 개발자들은 아래와 같은 심플한 var사용을 지양하려고 한다. 왜냐면 IDE라면 모를까 임시로 자동완성을 지원하지 않는 에디터로 편집할 때 사람이 타입추론이 어려워서이다.

하지만 익명클래스는 정의가 거대하고 유차하기 쉬우며 선언한 다음에 변수가 바뀌는 일도 없으므로 var를 쓰기에는 안성 맞춤이다.

 

라이브 피드백

http://andresalmiray.com/maven-dependencies-pop-quiz-results

: maven 문제 재밌는거 많음

 

Ex) Maven을 쓰면 이정도는 반드시 알아야함

답은 28.2

 

maven vs gradle

복잡한 구조의 프로젝트 구조면 gradle이 빌드가 빠름

빌드를 스크립트 언어로 직접 짜고싶으면 gradle이 유리

 

But, 간단한 프로젝트면 maven 써도 속도차이 크지않음

백기선님은 maven을 좋아하고 인텔리제이서 maven을 사용하는게 안정적이라고 느낀다고 함

 

  • 그림을 그려서 공부해라(안이뻐도됨)
  • 변수 선언 및 초기화 부분에서도 바이트코드 열어보길 원하신것=> int a = 1;이 바이트코드로 2~3줄인것 등 확인
  • 프리미티브타입의 표현범위를 암기하라는 것이 아닌 왜 저범위인가? 라는것을 설명하길 원함
  • unsigned 사용법
int unsigned = Integer.parseUnsignedInt("22억") //문자열로 해야됨, int에는 담길수가 없음
Integer.toUnsignedString(unsigned)); //unsigned로 인식을 하겠다, String으로 
그냥 빅인티저 씁시다잉(까다로움~)
BigInteger bigInteger = BigInteger.valueOf(22000...L); //빅인티저는 Long을 넣어야됨
  • int 숫자 표현법
int number  = 1_000;
int number2 = 1_000_000;
//숫자 가시적 표현 증가
  • 타입추론(var)에서 제네릭까지 원한건 아니였지만 타입 추론을 듣는 순간 제네릭을 떠올린 사람이면 공부한 사람
  • float이나 double사용 시 부동소수점이 정확하지 않을 수 있음
float number = 0.1f;
for(int i = 0; i < 10; i++) {
	number += 0.1f;
}
//1.0000001 출력 
//이런게 문제
//float이나 double을 쓸때는 부동소수점이 정확하지 않다는걸 중요해야함
  • 돈계산하는 어플등에서는 float이나 double을 쓰면 안되고 BigDemical 등을 써야함
BigDecimal bigdecimal = BigDecimal.ZERO;
for(int i = 0; i < 10; i++) {
	bigdecimal = bigdecimal.add(BigDecimal.valueOf(0.1));
    //bigdecimal.add는 새로운 객체를 리턴하므로 받는 변수가 필요함
}
//number는 1.0 출력
  • 인텔리제이는 클래스파일도 자바코드로 해석해서 보여주기 때문에 클래스파일을 볼려면 터미널에서 직접 열어서 봐야함

BigDecimal number = BigDecimal.ZERO;
for(int i = 0; i < 10; i++) {
	number = number.add(BigDecimal.valueOf(0.1)); //주목!!
}
System.out.println(number);

멀티스레드 시 코드는 한줄(주목 부분)인데 바이트코드를 까보면 사실상 두줄임

Ex)

number.add를 A스레드에서 처리하고 B라는 스레드가 add를 한번 더한뒤

그다음 A라는 스레드가 number에 값을 할당하면 값이 변할 가능성이 큼

 

코드로 보기엔 한줄이라 멀티스레드가 안겹칠줄 알지만

사실상 코드는 2줄이기 때문에 멀티스레드시 값의 변화가 일어날 수 있음

=> 이부분 잘 이해가 안되네요 아시는분 댓글좀 ㅠ

 

  • 싱글톤 예시
  • static과 static이 아닌것들이 메모리에 올라가는 시점 중요
  • 인텔리제이 개인라이센스 버전을 회사에서 사용하는거 무방
  • But, 구매비용 보조가 있으면 안됨
  • 인텔리제이 대학생 1년무료버전을 실무에서 사용하면 절대 안됨
  • bean상속은 .xml 시절이야기(구시대)

Reference

yaboong.github.io/java/2018/05/26/java-memory-management/

자바의 정석 3판

nobodj.space/?p=2095

qastack.kr/programming/415687/why-are-local-variables-not-initialized-in-java

www.slipp.net/questions/162

catsbi.oopy.io/6541026f-1e19-4117-8fef-aea145e4fc1b

www.notion.so/2-00ffb2aeb41d450aa446675b8a9e91d5

blog.naver.com/swoh1227/222149491648

catsbi.oopy.io/6541026f-1e19-4117-8fef-aea145e4fc1b

jungwoon.github.io/java/2019/08/11/Singleton-Pattern-with-Multi-Thread/

blog.javarouka.me/2018/11/20/no-instance/

blog.naver.com/hsm622/222144931396

www.notion.so/2-00ffb2aeb41d450aa446675b8a9e91d5#e9a8a0ecfc32417e9cfb113a1720ee96