layout: post
title: "자바 Primitive Type,변수,배열 사용법 익히기"
date: 2022-05-01T00:00:00-00:00
author: sangyeop
categories: Sproutt-2nd
정수형
타입 | 기본값 | 값의 범위 |
---|---|---|
byte | 0 | -2^7- ~ 2^7-1 |
short | 0 | -2^15 ~ 2^15-1 |
int | 0 | -2^31 ~ 2^31-1 |
long | 0L | -2^63 ~ 2^63 -1 |
실수형
타입 | 기본값 | 값의 범위 |
---|---|---|
float | 0.0f | 음수 범위 : -3.4028235E+38 ~ -1.4E-45 양수범위 : 1.4E-45 ~ 3.4028235E+38 |
double | 0.0 | 음수 범위 :-1.7976931348623157E+308 ~ -4.9E-324 양수 범위 : 4.9E-324 ~ 1.7976931348623157E+308 |
문자형
타입 | 기본값 | 값의 범위 |
---|---|---|
char | '\u0000' | 0 ~ 2^16-1 |
논리형
타입 | 기본값 | 값의 범위 |
---|---|---|
boolean | false | false or true |
자바의 타입은 Primitive Type과 Reference Type 두 가지로 나뉘어 진다.
Primitive Type에는 byte
,short
,int
,long
,float
,double
,char
,boolean
8가지가 존재하며, 그 외의 나머지 타입들은 모두 Reference Type으로 정의된다.
Reference Type에는 Integer
,Double
,Long
,String
등등 여러가지 클래스들이 포함된다.
Primitive Type 메모리 저장 방법
int a = 10;
다음과 같은 int
형 변수가 선언되었을때, Primitive Type의 경우에는 해당 메모리 자리에 10이라는 값이 그대로 할당이 된다.
a의 주소 값= 123
a의 주소 공간에 들어가는 값 = 10
Reference Type 메모리 저장 방법
String name = "채상엽";
다음과 같이 String
형 변수가 선언되었을때, Reference Type의 경우에는 해당 메모리 자리에 "채상엽" 이라는 값이 아닌 "채상엽"이라는 값이 위치하는 주소값을 담는다.
그렇다면 다음과 같은 경우에는 어떻게 주소 공간을 차지할까?
String originalName = "채상엽";
String copyName = originalName;
"채상엽"의 주소 값 = 222
originalName의 주소 값 = 333
orginalName이 저장하는 값 = 222
originalName은 222라는 값을 메모리에 저장하고 있게 된다. 그리고 copyName은 다음과 같은 값을 갖는다.
copyName의 주소 값 = 444
copyName이 저장하는 값 = 222
실제 코드로 확인해보면 다음과 같이 같은 주소가 출력됨을 확인할 수 있다.
Car car1 = new Car();
Car car2 = car1;
Car car3 = car2;
System.out.println(car1);
System.out.println(car2);
System.out.println(car3);
위에서 언급했듯이 Primitive는 값 자체를 저장하고, Reference는 주소 값을 저장하기 때문에, 아래의 예제와 같은 경우에 그 차이를 더 잘 확인할 수 있다.
int a = 1000;
int b = a;
System.out.println("a의 값=" + a);
System.out.println("b의 값=" + b);
a=2000;
System.out.println("a의 값=" + a);
System.out.println("b의 값=" + b);
a의 값=1000
b의 값=1000
a의 값=2000
b의 값=1000
Car car1 = new Car("포르쉐");
Car car2 = car1;
System.out.println("car1의 차 이름=" + car1.getName());
System.out.println("car2의 차 이름=" + car2.getName());
car2.setName("페라리");
System.out.println("car1의 차 이름=" + car1.getName());
System.out.println("car2의 차 이름=" + car2.getName());
car1의 차 이름=포르쉐
car2의 차 이름=포르쉐
car1의 차 이름=페라리
car2의 차 이름=페라리
Primitive Type의 경우에는 주소 값을 공유하지 않기 때문에, 어떤 변수의 값을 값으로 저장하고 있었다고 하더라도 해당 값을 변경하면 변경을 시도한 변수의 메모리가 가진 값만 변경이 된다.
그러나 Reference Type의 경우에는 값을 공유하는 변수 모두가 주소를 공유하고 있기 때문에, 한 객체의 값을 변경하면 해당 주소에 해당하는 객체를 공유하는 모든 변수의 값이 함께 변경된다.
동일성과 동등성
동일성
두 객체가 완전히 동일할 경우를 의미한다. 즉 두 객체의 해시코드(주소) 값을 비교하여 같음을 의미한다. ==
연산자를 이용하여 비교한다.
Car car1 = new Car();
Car car2 = car1;
car1
과 car2
는 위에서 설명 했듯이 Reference Type 이기 때문에 같은 주소 공간을 공유한다. 즉 같은 해시코드(주소) 값을 가지고 있기 때문에 동일성 검사에서 true
를 반환한다.
동등성
두 객체가 같은 정보를 가지고 있는 경우를 의미한다.
String a1 = new String("테스트");
String a2 = new String("테스트");
a1
과 a2
는 서로 다른 객체이기 때문에 동일성 검사(a1==a2
)에서는 false
가 반환된다.
그러나 a1
과 a2
가 가지고 있는 정보(값)는 동등하기 때문에, 동등성 검사(a1.equals(a2)
)에서는 true
가 반환된다.
자바가 Call By Reference가 아닌 Call By Value인 이유
자바는 Call By Value만 존재한다. 객체의 해시코드가 value로써 전달되어지고 객체 내의 상태값을 바꿀 수는 있지만, 전달되는 값이 포인터가 아닌 주소값이기 때문에 객체 자체를 바꿀 수는 없어서 Call By Reference는 아니다.
변수에 할당할 수 있는 어떤 상수(변하지 않는 값)를 의미한다. 간단한 예를 들어보자면
int a = 1000; // a는 상수 리터럴이다.
Integer Literal
정수 타입의 경우에는 4가지 방법으로 변수에 리터럴 값을 할당할 수 있다.
Floating-point Literal
Char Literal
Single quote
char a = 'a';
Integer Literal을 이용한 표현법 (0~65535)
10진수, 8진수, 16진수로 표현이 가능하며, 유니코드로 변환되어 출력된다.
char a = 42;
Unicode 표현 ('\uxxxx')
char a = '\u0000';
Escape 문자
char a = '\n';
String Literal
Double quote
String a = "Hello World";
Mulit Line
String a = "Hello World\n"
+"Hello World\n"
+"Hello World";
Boolean Literal
True or False
boolean a = true // or false
변수 선언
컴퓨터는 모든 값을 bit(binary digits)를 이용해 저장한다. 한 개의 비트는 0 또는 1 두 가지 값을 표현할 수 있다. 변수 선언은 곧 변수를 생성하는 것을 의미한다. 자바에서 변수를 생성하기 위해서는 얼마나 많은 비트를 사용해야할지 알려주기 위해서 변수의 타입을 선언해주어야 한다.
int a;
변수 초기화
변수 초기화는 곧 변수에 값을 할당하는 것을 의미한다. 값이 초기화 되어 있지 않다면, Primitive Type 의 경우에는 각각의 default value가 할당되어 있고 Reference Type의 경우에는 default value로 null이 할당되어 있다.
int a; // 변수 선언
a = 2; // 변수 초기화
인스턴스 변수
블록과 메서드의 바깥이면서 클래스 내에 선언되어 있는 변수를 인스턴스 변수라고 부른다. 일반적인 인스턴스 변수의 범위는 static
메서드 내를 제외한 클래스 내 모든 범위에 해당한다. 인스턴스 변수의 수명은 해당 클래스 객체가 메모리에 남아 있을 동안 유지된다.
클래스 변수
클래스 내에 선언되어 있으면서 블록들의 바깥에 static
으로 선언되어 있는 변수를 클래스 변수라고 부른다. 일반적인 클래스 변수의 범위는 클래스 내 모든 범위에 해당하고, 클래스 변수의 수명은 프로그램이 끝날때까지 또는 클래스가 메모리에 로드되어 있는 동안 유지된다.
로컬 변수
인스턴스 변수와 클래스 변수가 아닌 다른 나머지 변수들을 로컬 변수라고 부른다. 이 범위에는 메서드의 매개변수(parameter)도 포함이 된다. 로컬 변수의 범위는 해당 변수가 선언되어 있는 블록 내에 해당하고, 로컬 변수의 수명은 해당 변수가 선언되어 있는 블록이 종료되기 전까지 유지된다.
자바 언어로 작성된 모든 표현식은 추정할 수 있는 타입을 가지고 있다. 양쪽의 타입이 일치하지 않을 때, 컴파일 에러를 발생시킬수도 있지만, 어떠한 경우에 따라서는 자바는 편의상 프로그래머에게 타입을 명시적으로 변환하도록 요구하는 것이 아니라, 스스로 암시적 변환을 실행해서 사용가능한 타입으로 변환해준다.
S라는 타입에서 T타입으로 변환을 하기 위해서 S 타입의 표현식은 컴파일시에 S타입 대신 T타입이 있는 것처럼 처리할 수 있다. 어떤 경우에는 변환의 유효성을 확인하거나 표현식의 런타임 값을 새 유형인 T타입에 적합하게 바꿔주기 위해서 런타임시에 조치가 필요하다.
Type Obejct to Type Thread
오브젝트 타입에서 쓰레드 타입으로 변경하는 경우 런타임 값이 쓰레드 클래스의 인스턴스인지 또는 해당 클래스의 서브클래스인지를 확인하는 과정이 필요하다. 만약 그렇지 않다면 예외를 던진다.
Type Thread to Type Object
쓰레드 타입에서 오브젝트 타입 변환은 런타임시 별도 과정이 필요하지 않다. 쓰레드는 오브젝트의 하위 클래스이므로 이 경우에는 모두 변환이 가능하다.
Type int to Type long
int에서 long으로 변환핳기 위해서는 32비트 정수 값을 64비트 long 표현으로 sign-extension이 필요하다. 이 과정에서 정보가 손실되지는 않는다.
Type double to Type long
double에서 long으로 변환하기 위해서는 64비트 floating-point 값을 64비트 integer 값으로 변환하는 과정이 필요하다. 이 과정에서는 일부 정보가 손실될 수 있다.
Automatic Type Conversion
Byte -> Short -> Int -> Long -> Float -> Double
Narrowing(Explicit) Conversion
Double -> Float -> Long -> Int -> Short -> Byte
double d = 123.1;
long l = (long)d;
int i = (int)l;
123.1
123
123
만약 long
타입에 할당된 값이 int
의 범위를 벗어난 값이라면 예외가 던져진다.
Type Promotion
만약 여러개의 타입이 연산에 사용되는 표현식이 있다면, 최종 연산 결과의 타입은 표현식에 사용된 타입들 중 가장 넓은 범위의 타입으로 정해진다.
byte b = 1;
int i = 1;
long l = 1;
long result = b + i + l;
Explicit Type Casting
표현식 연산에서 연산의 결과는 자동으로 더 큰 데이터 타입으로 캐스팅된다.
byte b = 1;
b = (byte)(b+1);
만약 아래와 같다면 컴파일 에러가 발생한다. 그 이유는 결과로 받는 b
는 byte
타입인데 b+1
을 실행하게 되면 1
이 더 큰 데이터 타입인 int
타입이기 떄문에 Type Promotion에 의해 int
형으로 반환되기 때문이다. byte
로 받기위해서는 위와 같이 명시적으로 타입을 정의해주어야 한다.
byte b = 1;
b = b+1;
Casting 과 Conversion의 차이 : Conversion은 컴파일러에 의해 자동으로 수행되지만, Casting은 프로그래머가 명시적으로 선언해야한다는 차이점이 있다.
자바의 배열이란, 같은 타입의 값들을 포함하고 있는 변수들의 리스트를 의미하는것과 같다. 다차원의 경우 각 배열들은 하나의 행이나 열을 의미하게 되고 각각의 배열 내 요소들은 인덱스를 이용해 구분되어진다.
1차원 배열 선언방법
String[] names = {"채상엽", "홍동건", "김성혁", "최진영", "김현우"};
String[] names = new String[5]; // 배열의 크기를 5라고 선언해서 요소는 비어있는 배열을 생성하는 방법
[]
하나가 선언된 것은 1차원 배열임을 의미한다. 만약 2차원 배열일 경우 [][]
가 선언되어진다. 중괄호 {}
안에는 배열 안에 넣고자하는 요소들이 선언되어지고. 해당 요소들을 꺼내 사용하기 위해서는 다음과 같이 사용할 수 있다.
String first = names[0]; // "채상엽"
String third = names[2]; // "김성혁"
각각의 인덱스 (0~4)는 각 배열 요소의 위치(주소 값)를 참조하고 있다.
2차원 배열 선언방법
int[][] arr = {
{1,2},
{3,4},
{5,6}
};
위와 같이 배열이 2차원으로 중첩되어 있는 구조를 2차원 배열이라고 한다. 아래와 같이 인덱스를 이용해 값을 찾을 수 있다.
arr[0] // {1,2}
arr[0][1] // 2
arr[2] // {5,6}
arr[2][0] // 5
JAVA10 이상부터 지역변수의 타입 추정 기능을 제공하게 되었다. 예를 들어 이전까지는 문자열 변수를 선언할때 다음과 같이 사용하였다.
String name = "채상엽";
그러나 타입 추정을 이용하면 다음과 같이 변수를 선언할 수 있다
var name = "채상엽";
변수를 명시적으로 프로그래머가 선언하지 않고, 컴파일시에 컴파일러가 할당되는 값을 보고 타입을 추정한다. 주의 할점은 이러한 특징은 오로지 초기화된 지역 변수에서만 가능하다는 점이다. 멤버 변수나, 메서드의 매개변수, 반환 타입에서는 사용이 불가능하다.
응용으로는 다음과 같이도 사용할 수 있다.
Map<Long, User> map = new HashMap<>();
var map = new HashMap<>();
이러한 기능을 제공함으로써 프로그래머는 변수의 타입에 집중하기 보다는 변수의 이름에 집중할 수 있게 된다는 장점이 생긴다.
참고 자료
Primitive Type
Primitive Type vs Reference Type
Literals in Java
Java variables declaring
Java variables scope and lifetime
Type conversion and Type promotion
One,Two Dimensional Declaration
- https://www.scientecheasy.com/2021/08/one-dimensional-array-in-java.html/
- https://www.geeksforgeeks.org/arrays-in-java/
Java type inversion var