학습할 것
- 프리미티브 타입 종류와 값의 범위 그리고 기본 값
- 프리미티브 타입과 레퍼런스 타입
- 리터럴
- 변수 선언 및 초기화하는 방법
- 변수의 스코프와 라이프타임
- 타입 변환, 캐스팅 그리고 타입 프로모션
- 1차 및 2차 배열 선언하기
- 타입 추론, var
1. 프리미티브 타입 종류와 범위 그리고 기본 값
프리미티브 타입은 기본 타입으로, 정수형 타입, 실수형 타입, 문자형 타입, 논리형 타입으로 나눌 수 있다.
기본형은 모두 8가지 타입이 있다.
프리미티브 타입의 설명은 다음 표에 정리하였다.
프리미티브형에는 기본값이 존재해 NULL값이 존재하지 않음.
실제 값은 스택 메모리에 저장되며 저장 가능한 범위를 넘게 값을 저장하면 컴파일 에러가 발생한다.
프리미티브 타입 저장 공간
프리미티브 타입 범위, 크기, 기본값
분류 | 자료형 | 저장 가능한 값의 범위 | 기본값 | 크기 | |
bit | byte | ||||
논리형 | boolean | false, true | false | 8 | 1 |
문자형 | char | '\u0000' ~ '\uffff' | '\u0000' | 16 | 2 |
정수형 | byte | -128 ~ 127 (= -2^{7} ~ 2^{7}-1) | 0 | 8 | 1 |
short | -32,768 ~ 32,767 (= -2^{15} ~ 2^{15}-1) | 0 | 16 | 2 | |
int | -2^{31} ~ 2^{31}-1 | 0 | 32 | 4 | |
long | -2^{63} ~ 2^{63}-1 | 0L | 64 | 8 | |
실수형 | float | -3.4E38 ~ -1.4E-45, 1.4E-45 ~ 3.4E38 | 0.0F | 32 | 4 |
double | (1.7 * 10 -308 ) ~ (1.7 * 10 308 )의 근사값 | 0.0 | 64 | 8 |
참조: ORACLE JAVA DOCS
표현 범위를 계산하는 방법은 메모리 크기와 연관이 있다.
첫번째 비트에 부호를 나타내고 나머지 비트(n-1개)에 수를 표현한다. 양수에 0을 포함하여 양수의 표현 범위는 2^{n} -1 이 된다.
실수형에서 부호, 지수, 가수로 나누어 저장한다(총 32bit = 4byte)
실수형은 같은 크기임에도 큰 범위를 표현할 수 있지만 실제 저장된 값과 오차가 날 수 있다.
그래서 정밀도가 중요한데, 정밀도는 높을수록 오차 범위가 줄어든다.
위의 실수 자료형(float)는 가수부분이 23bit이다.
정규화를 통해 24bit까지 표현이 가능하며 2^{24}는 10^{7} < 2^{24} < 10^{8} 이므로 float의 정밀도는 7이며 7자리가 된다.
마찬가지로 double은 가수 부분이 52비트이며 double의 정밀도는 15자리가 된다.
즉, float의 정밀도는 7자리로 10진수로 7자리 수를 오차없이 저장할 수 있으며, double은 15자리로 10진수로 15자리 수를 오차없이 저장할 수 있다는 뜻이다.
boolean 타입은 true, false를 판단하기 때문에 1bit로 충분하지만 java가 데이터를 다루는 범위는 최소 1byte이기 때문에 1byte를 할당한다.
The char data type is a single 16-bit Unicode character. - orcle java docs
char 타입은 UTF-16 (유니코드)를 사용하기 때문에 2byte가 필요하며 2byte를 할당한다.
유니코드란?
세계 각 국의 언어를 통일된 방법으로 표현할 수 있게 제안된 국제적인 코드 규약이다.
컴퓨터가 미국에서 개발되어져 영어를 바탕으로 정의되어 있다.
영어는 26자의 알파벳과 몇 가지 특수 문자를 표현하기에 1 byte로 충분했기 때문에문자가 1 byte로 표현되고 있지만, 동양 3국의 한글, 한자 또는 일어 등과 같은 문자는 1 byte로는 표현이 불가능하기에
2 byte로 문자를 표현하는 유니코드가 만들어 졌다.
출처: https://gyugyu.tistory.com/6 [GYU's DEV STORY:티스토리]
처음에 char크기를 왜 2byte로 쓰는지에 대해 의문이 들었는데 다음 링크에서 답을 얻을 수 있었다.
https://stackoverflow.com/questions/21124310/size-of-a-char-in-a-byte-array
https://kunststube.net/encoding/
C에서는 char의 크기는 1byte인데 java에서는 2byte를 쓴다.
위에서 설명한 유니코드를 저장하기 위함이다.
2. 프리미티브 타입과 레퍼런스 타입
자료형은 primitive type(기본형), reference type(참조형) 으로 나눌 수 있다.
기본형(Primitive Type): 논리형(boolean), 문자형(char), 정수형(byte, short, int, long), 실수형(float, double) 계산을 위 한 실제 값을 저장한다. 메모리에 고정된 크기로 저장하고 변수에 데이터의 값을 저장한다.
참조형(reference type): 변수를 선언할 때 크기가 정해져 있지 않고 변수가 할당될 때 객체의 주소를 저장한다. 기본적으로 Java.lang.Object를 상속받을경우 참조형이 된다.
즉, 기본형을 제외하고는 참조형이라 생각해도 된다.
3. 상수 & 리터럴
상수(constant)
변수와 마찬가지로 값을 저장할 수 있는 공간이다. 그러나 변수와 다르게 한번 값을 저장하면 다른 값으로 변경할 수 없다. 상수를 선언하는 방법은 변수 선언하는 방법과 동일한데 변수 타입앞에 키워드 'final'을 붙여주면 된다.
final MAX_SPEED = 10;
상수는 선언과 동시에 반드시 초기화 해야 한다. 이후 상수값의 변경을 할 수 없다.
리터럴(Literal)
데이터 그 자체를 의미한다.
원래 12, 123, 3.14, "A" 와 같은 값들이 상수 인데, 자바 프로그래밍에서는 상수를 값을 한번 저장하면 변경 할 수 없는 공간으로 정의하였기 때문에 이와 구분하기 위해 리터럴 이라는 용어를 사용한다.
즉,
변수(variable): 하나의 값을 저장하기 위한 공간
상수(constant): 값을 한번만 저장할 수 있는 공간
리터럴(literal): 그 자체로 값을 의미하는 것
4. 변수 선언 및 초기화하는 방법
변수(Variable)
값을 저장할 수 있는 메모리상의 공간
[타입]+ [optional 변수예약어] + 변수명 = 데이터.
변수 선언
변수를 사용하기 위해 변수를 선언해야 한다. 위의 그림과 같이 선언한다.
변수 타입(datatype): 변수에 저장될 값이 어떤 타입인지 지정
변수 이름(variable name): 변수에 붙힌 이름. 변수가 값을 저장할 수 있는 메모리 공간을 의미함.
변수가 선언되면 변수타입에 해당하는 크기만큼 메모리에 용량이 할당된다.
변수 네이밍 컨벤션
참고로 변수명을 짓는 네이밍 컨벤션이 있는데 대표적으로 이와 같으며 자세한 내용을 확인하고 싶으면 링크된 곳을 확인
- 소문자로 시작합니다.
- _나 $가 허용될지라도, 이 두 문자열로 시작하지 않습니다.
- 달러사인은 사용하지 않는게 좋습니다. (컨벤션상)
- 짧지만 의미 있게 작성해야합니다. (사용의도가 나타내게 작성해야합니다)
- 임시로 저장용으로 사용하는것을 제외하고는 한 문자열의 변수는 피합시다. (i,j,k..등)
- 두번째 단어부터는 그 단어의 첫번째 문자는 구분을 위해 대문자로 써줍니다.
- 만약에 상수를 저장한다면, 모든 문자를 대문자로 작성하고, 문자사이는 underscore(_)로 채웁니다.
- 위는 여기를 인용함 -
더 자세한 내용은 다음을 참고 => 캠퍼스 핵데이 Java 코딩 컨벤션
변수 초기화
변수를 선언하면 메모리에 변수의 저장공간이 있지만, 이 공간에 어떤 값이 저장되어있는지 알 수 없다.
그러므로 초기화를 해주어야 한다. 변수에 값을 저장할 때 대입연산자 = 를 사용한다.
클래스 변수와 인스턴스 변수는 초기화를 하지 않아도 변수의 타입에 맞게 기본값이 할당된다.
그러나 지역변수는 사용하기 전에 초기화 하지 않으면, 컴파일러가 오류를 발생시킨다.
5. 변수의 스코프와 라이프타임
스코프는 범위 라는 의미이다. 변수의 스코프는 키워드와 선언된 블럭 위치에 따라서 달라진다.
변수의 종류
변수의 종류는 스코프로 크게 3가지로 구분할 수 있다.
class Car {
static int modelOutput; // 클래스 변수
String modelName; // 인스턴스 변수
void method() {
int something = 10; // 지역 변수
}
}
메서드 안에 선언된 변수는 지역 변수라고 한다.
클래스 내부에 선언되며 메서드 밖에서 선언된 변수를 멤버 변수라고 한다.
멤버 변수에 static이 붙어있으면 클래스 변수, 붙어있지 않으면 인스턴스 변수이다.
1. 지역변수
블록이나 메서드 또는 생성자 내에 정의된 변수
- 변수는 블록에 들어갈 때 생성되거나 블록을 빠져나온 후 또는 함수가 호출된 후 함수에서 값이 반환될 때 소멸된다.
- 변수의 스코프(범위)는 변수가 선언된 블록 내에서만 존재하며 해당 블록내에서만 엑세스 할 수 있음을 의미한다.
- 지역변수를 사용하기 위해서 명시적으로 초기화를 해주어야 한다.
import java.io.*;
class GFG {
public static void main(String[] args)
{
int var = 10; // 지역변수 선언
// 지역변수는 해당 메서드 내에서만 사용할 수 있다
System.out.println("Local Variable: " + var);
}
}
// Output=> Local Variable: 10
// reference: https://www.geeksforgeeks.org/variables-in-java/
2. 인스턴스 변수
메서드, 생성자 블록 밖에 생성되며 클래스 내부에 생성되는 변수, 비정적 변수이다.(static이 붙지 않음)
- 인스턴스 변수는 클래스에서 선언되기 때문에 클래스의 객체가 생성될 때 변수가 생성되고 객체가 소멸될 때 변수도 같이 소멸된다.(즉, 인스턴스 변수는 객체를 생성해야만 접근할 수 있다.)
- 지역변수와 달리 인스턴스 변수는 접근 지정자(Access Control Modifiers)를 쓸 수 있다. (클래스변수도 마찬가지) (public, private, protected...)
- 인스턴스 변수는 초기화 하지 않아도 기본값이 지정된다.
import java.io.*;
class GFG {
public String geek; // 인스턴스 변수 선언
public GFG()
{ // Default Constructor
this.geek = "Shubham Jain"; // initializing Instance Variable
}
//Main Method
public static void main(String[] args)
{
// 객체 생성
GFG name = new GFG();
// Displaying O/P
System.out.println("Geek name is: " + name.geek);
}
}
// output=> Geek name is: Shubham Jain
3. 클래스 변수
- 인스턴스 변수와 유사하게 선언되지만, 메서드, 생성자 블록 외부의 클래스 내에서 static 키워드를 사용하여 선언됨.
- static 키워드를 붙일경우 한 클래스의 모든 인스턴스가 같은 값을 공유함
- 클래스 변수는 프로그램 실행 시작시 생성되고 실행 종료시 자동으로 소멸된다.
- 클래스 변수는 초기화를 따로 하지 않으면 기본값이 할당된다.
- 객체를 생성하지 않고 바로 접근할 수 있다.
import java.io.*;
class GFG {
public static String geek = "Shubham Jain"; //Declared static variable
public static void main (String[] args) {
//geek variable can be accessed withod object creation
//Displaying O/P
//GFG.geek --> using the static variable
System.out.println("Geek Name is : "+GFG.geek);
}
}
// output=> Geek Name is : Shubham Jain
정리
- 클래스 영역에 위치한 변수 중에서 static 키워드를 가지는 변수를 클래스 변수(static variable)
- 클래스 영역에 위치한 변수 중 static 키워드를 가지지 않는 변수는 인스턴스 변수(instance variable)
- 메소드나 생성자, 초기화 블록 내에 위치한 변수를 지역 변수(local variable)
변수 | 생성 시기 | 소멸 시기 | 저장 메모리 | 사용 방법 |
클래스 변수 | 클래스가 메모리에 올라갈 때 | 프로그램이 종료될 때 | 메소드 영역 | 클래스이름.변수이름 |
인스턴스 변수 | 인스턴스가 생성될 때 | 인스턴스가 소멸할 때 | 힙 영역 | 인스턴스이름.변수이름 |
지역 변수 | 블록 내에서 변수의 선언문이 실행될 때 | 블록을 벗어날 때 | 스택 영역 | 변수이름 |
- TCP School 인용 -
6. 타입 변환, 캐스팅 그리고 타입 프로모션
타입 변환
변수 또는 상수 타입을 다른 타입으로 변환하는 것
자바에서는 boolean형을 제외한 나머지 기본 타입간의 타입 변환을 자유롭게 수행할 수 있습니다.
타입간 각각이 가지는 크기가 다르기 때문에 큰 타입에서 작은 타입으로 형변환 할때 값의 손실이 발생할 수 있다.
타입 변환의 종류는 크게 두가지 방식으로 나눈다.
- 묵시적 타입 변환(자동 타입 변환)
- 명시적 타입 변환(강제 타입 변환)
자동 타입 변환 (묵시적 타입 변환)
형변환을 생략할 수 있으며 컴파일러가 생략된 형변환을 자동으로 추가한다.
byte a = 10;
int b = a;
다음 코드에서 1byte인 byte타입을 4byte인 int타입에 넣기 때문에 문법의 오류가 발생하지 않은 채 형변환이 자동으로 이루어진다.
자동 형변환이 이루어지는 순서는 다음과 같다.
double > float > long > int > short > byte
표현범위가 좁은 타입에서 넓은 타입으로 형변환 할때 값 손실이 없어 두 타입 중 표현 범위가 넓은 쪽으로 형변환 된다.
import java.io.*;
// Main Class
class GFG {
// Main driver method
public static void main(String[] args)
{
// Declaring and initializing variables to values
// but to different data types
long a = 3;
byte b = 2;
double c = 2.0;
// Type Conversion
// As long and byte data types are converted to
// double return type
double final_datatype = (a + b + c);
// Printing the sum of all three initialized values
System.out.print(final_datatype);
}
}
// output=> 7.0
만약 표현범위가 큰 타입에서 좁은 타입으로 형변환 할때 명식적으로 표기하지 않으면 컴파일 에러가 발생한다.
import java.io.*;
// Main class
public class GFG {
// Main driver method
public static void main(String[] args)
{
// Declaring and initializing variables to values
// with different return type
long a = 3;
byte b = 2;
double c = 2.0;
// Type Conversion
int final_datatype = (a + b + c);
// Print statement
System.out.print(final_datatype);
}
}
/* output
prog.java:9: error: incompatible types: possible lossy conversion from double to int
int final_datatype = (a + b + c);
^
1 error
*/
명시적 타입 변환(강제 타입변환)
앞서 묵시적 타입 변환의 예시에서 보았듯이 표현범위가 큰 타입을 작은 타입으로 표기하지 않을 때 컴파일 에러가 발생한다. 자바에서 다음과 같이 명시적 타입 변환을 수행할 수 있다.
(변환할타입) 변환할데이터
변환 시키고자 하는 데이터의 앞에 괄호를 넣고 그 괄호 안에 타입을 적으면 된다.
int num1 = 1, num2 = 4;
double result1 = num1 / num2;
double result2 = (double) num1 / num2;
System.out.println(result1);
System.out.println(result2);
/* output
0.0
0.25
*/
//reference: http://www.tcpschool.com/java/java_datatype_typeConversion
7. 1차 및 2차 배열 선언하기
배열(array)은 같은 타입의 변수들로 이루어진 유한 집합으로 정의할 수 있다.
배열을 구성하는 각각의 값을 배열 요소(element)라고 하며,
배열에서의 위치를 가리키는 숫자를 인덱스(index)라고 한다.
자바에서 인덱스는 항상 0부터 시작한다.
배열은 1차원부터 n차원 배열까지 생성할 수 있다.
1차원 배열 선언과 생성
1차원 배열은 다음 문법에 따라 선언한다.
타입[] 배열이름;
또는
타입 배열이름[];
예시를 들면 다음과 같다
int[] score;
String[] name;
OR
int score[];
String name[];
Although the first declaration establishes that intArray is an array variable, no actual array exists. It merely tells the compiler that this variable (intArray) will hold an array of the integer type. To link intArray with an actual, physical array of integers, you must allocate one using new and assign it to intArray.
- https://www.geeksforgeeks.org/arrays-in-java/?ref=gcse -
비록 score 또는 name이 배열 변수임을 설정하지만 실제로는 배열이 존재하지 않는다.
즉, 변수(score, name)이 정수 또는 String 유형의 배열을 보유할 것을 컴파일러에게 알려줄 뿐이다.
변수(score, name)을 실제 물리적 정수, 문자열 배열과 연결하려면 new를 사용하여 score, name에 할당해야 한다.
배열은 다음과 같이 생성하여 사용한다
타입[] 변수이름 = new 타입[길이];
new 연산자를 사용해 배열의 타입과 길이를 지정하면 메모리에 해당 길이만큼 영역을 확보한다.
이 때 생성된 배열의 길이는 정적이므로 길이를 변경할 수 없으면 새로운 길이의 배열이 필요하면 다른 하나의 배열을 생성해야 한다.
2차원 배열의 생성
2차원 배열은 다음과 같이 생성할 수 있다.
int[][] twoDimensionalArray = new int[2][2]{{1, 2}, {3, 4}};
배열의 초기화
자바에서는 변수와 마찬가지로 배열도 선언과 동시에 초기화 할 수 있다.
// 1
타입[] 배열이름 = {배열요소1, 배열요소2, ...};
// 2
타입[] 배열이름 = new 타입[]{배열요소1, 배열요소2, ...};
다음 두 경우로 배열을 사용할 때 두번째 방법만 사용할 수 있다.
1. 배열의 선언과 초기화를 따로 진행해야 할 경우
2. 메소드의 인수로 배열을 전달하면서 초기화해야 할 경우
예시를 살펴보자
int[] grade1 = {70, 90, 80}; // 배열의 선언과 동시에 초기화할 수 있음.
int[] grade2 = new int[]{70, 90, 80}; // 배열의 선언과 동시에 초기화할 수 있음.
int[] grade3;
// grade3 = {70, 90, 80}; // 이미 선언된 배열을 이 방법으로 초기화하면 오류가 발생함.
int[] grade4;
grade4 = new int[]{70, 90, 80}; // 이미 선언된 배열은 이 방법으로만 초기화할 수 있음.
8. 타입 추론, var
개인적으로 이해가 잘 되지 않아 해당 소단원은 이곳을 참조함.
타입추론
- 정적 타이핑을 지원하는 언어에서, 타입이 정해지지 않은 변수에 대해서 컴파일러가 변수의 타입을 스스로 찾아낼 수 있도록 하는 기능
- java 10이상부터 도입됨
- 타입을 명시하지 않아도 되며, 코드량을 줄이고 코드의 가독성을 높일 수 있다.
컴파일러가 지원해 주는 기능으로서, 컴파일 시간에 타입을 추론하는 기능
즉, 개발자가 변수의 타입을 명시적으로 적어주지 않고도, 컴파일러가 알아서 이 변수의 타입을 대입된 리터럴로 추론하는 것이다.
public static void main(String[] args) {
String str = "Hello world";
}
를 var를 사용하여 타입을 선언하지 않을 수 있다.
public static void main(String[] args) {
var str = "Hello world";
if(str instanceof String){
System.out.println("str 변수의 타입은 String입니다.");
}
여기서 str변수의 타입은 따로 명시하지 않았다. 대신 str 변수 앞에 var라고 키워드를 선언 해주었다.
컴파일러가 오른쪽에 있는 초기화값 리터럴로 타입을 추론한다.
"str변수의 타입은 String이 입니다" 가 출력될 것이다.
Var를 쓸 때 주의점이 있는데 var는 초기화값이 있는 지역변수로만 선언 가능하다.
var는 멤버변수, 메소드의 파라미터, 리턴 타입으로 사용 불가능하다.
그렇다면 var를 왜 사용하는 것일까?
(타입 추론 var가 나온 배경은 다음 공식 문서를 읽어보자 JDK7부터 JDK10까지 설명됨)
다음 코드를 살펴보자
URL url = new URL("http://www.oracle.com/");
URLConnection conn = url.openConnection();
Reader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
다음 예제에 표시된 것과 같은 코드는 중복되어 코드를 읽기 어렵게 만든다.
Type Inference Recommendations
Type inference definitely reduces the amount of time taken to write Java code but what about readability? Developers spend about 10x as much time reading source code as they do writing it, so you should definitely be optimising for ease of reading over ease of writing. The extent to which var improves this will always be subjective, it’s inevitable that some people will hate var and some love it. You should always be focussing on what helps your team-mates read your code so if they are happy reading code with var in then you should use it, otherwise not.
Sometimes including explicit types can impede readability. For example when looping over the entryset of a Map, you need to regurgitate the type parameters on the Map.Entry object.
- Java 10 Local Variable Type Inference by Raoul-Gabriel Urma and Richard Warburton -
코드의 길이가 길어짐에 따라 가독성을 높일 수 있어 var를 사용한다.
var를 어떻게 사용할까?
var로 적을 시에 협업하는 개발자가 타입을 유추할 수 없어 var데이터 타입을 적용하면 자연스럽게 네이밍에 신경쓰게 된다.
var meg = 'Hello World!";
public void Test(){
var testName = getTestName();
}
public Name getTestName(){
return new Name("객체입니다.", MessageType.SOME);
}
다른 클래스 파일에 두개 이상의 함수를 조회하는 경우 위 코드와 같이 타입을 유추하기 힘들다.
네이밍을 잘하면 커버할 수 있다고 생각하겠지만 getter/setter함수 외에는 한계가 있어보이고 네이밍을 잘 하더라도 헷갈리는 경우가 있다. 그렇다면 어디서 var를 쓰는것이 적절한가?
'Language > Java-Weekly-study' 카테고리의 다른 글
[Java] W06: 자바 상속 (0) | 2022.06.26 |
---|---|
[Java] W05: 자바 클래스 (0) | 2022.06.19 |
[Java] W04: 자바 제어문 (0) | 2022.06.11 |
[Java] W03: 자바 연산자 (0) | 2022.06.03 |
[Java] W01: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가 (0) | 2022.05.21 |