뭐라도 끄적이는 BLOG

Java Variables와 Data Types 본문

Java/Java 기본

Java Variables와 Data Types

Drawhale 2023. 6. 30. 12:53

Java의 변수는 값을 저장할 수 있는 메모리 한 부분이다. 메모리의 크기와 사용법을 정의하기 위해 미리 정의해 놓은 Data Type(자료형)이 있다. Java의 자료형은 크게 primitive type과 reference type 2가지가 있다.

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

primitive type은 일정 길이의 메모리에 저장된 정보의 크기과 해석방법을 알려주고 직접 초기 값을 메모리에 넣어주는 자료형들 이다.

구분 Type Bits 기본값 Range of Values
정수형 byte 8 bits 0 -2^7 ~ 2^7-1 (-128 ~ 127)
short 16 bits 0 -2^15 ~ 2^15-1 (-32768 ~ 32767)
int 32bits 0 -2^31 ~ 2^31-1 (-2147483648 ~ 2147483647)
long 64 bits 0L -2^63 ~ 2^63-1 (-9223372036854775808 ~ 9223372036854775807)
실수형 float 32 bits 0.0F 0x0.000002P-126f ~ 0x1.fffffeP+127f
double 64 bits 0.0L 0x0.0000000000001P-1022 ~ 0x1.fffffffffffffP+1023  
문자형 char 16 bits '\u0000' \u0000 ~ \uffff (0 ~ 2^15-1) * 자바에서 unsgined로 동작하는 자료형
논리형 boolean 1 bit false true, false

자료형에는 부호를 표현할 수 있는 signed와 부호를 사용하지 않고 양의 수만 사용하는 unsigned가 있다. unsigned는 부호 비트를 사용하지 않으면 표현할 수 있는 양의 수 값이 signed 값들보다 더 늘어나게 된다.

java 8 이전에는 unsigned는 전혀 없었지만 이후에는 간접적으로 만들 수 있다. primitive 타입에 직접적으로 생긴것은 아니고 Wrapper 클래스를 이용하여 사용할 수 있다.

int unsigned = Integer.parseUnsignedInt("3000000000");
System.out.println(Integer.toUnsignedString(unsigned));

실수의 경우 부호, 지수, 가수 영역으로 구성되는 부동 소수점 방식을 사용한다. IEEE 754에 해당 실수를 어떻게 나타낼지 명시되어 있다.

사진 출처 :  ko.wikipedia.org/wiki/IEEE_754

예시로 -118.625를 IEEE 754(32비트 단정밀도)로 어떻게 표현되는지 확인해보자.

  1. 우선 음수이므로 부호비트는 1
  2. 부호를 뺀 절대값을 이진법으로 나타내면 1110110.101(2)
  3. 소수점을 왼쪽으로 이동시켜 왼쪽에는 1만 남도록 만든다. (1110110.101 = 1.110110101 * 2^6) 이것을 정규화된 부동소수점 수라고 한다.
  4. 가수부는 소수점의 오른쪽 부분으로 110110101이다. 부족한 비트 수 부분은 0으로 채워 23비트로 만든다.
  5. 지수는 위 결과로 6가 되고여기에 Bias를 더해야 한다. 32비트 IEEE754형식에서 Bias는 127이므로 133이되고 이를 이진법으로 변환하면 10000101이 된다.

Bias 는 지수값에서 -와 +값을 나누는 기준이라고 생각하면 된다. 127이 지수가 0임을 나타낸다. 예시로 지수부가 127이면 가수부에 2^0을 곱하여 표현하고 126이면 2^-1을 곱하게 된다.

java에서 기본적으로 실수리터럴을 사용하면 double 형식이다. 그렇기 때문에 float형식의 실수를 표현하기 위해서는 마지막에 f를 붙여 float형식의 실수라고 알려주어야 한다. 하지만 float형식은 현재 사용하지 않는 것을 권장한다. 부동 소수점 형식은 정확한 실수값이 아닌 근사값을 저장하는 방식이기 때문에 32비트인 float는 오차가 생길 확률이 크기 때문이다.

public class Main {
	public static void main(String[] args) {
		float flt = 2147483648.0f;
		System.out.println("flt : " + flt);
		System.out.println("flt - 20 : " + (flt - 20));
		System.out.println("flt - 20 : " + (flt - 64));
	}
}

부동 소수점 오차로인한 표기 오류

위 코드에서 flt와 flt - 20을 출력해보면 같은 값이 나온다. 이는 숫자가 커지면서 생기는 오차율에 의해 -20을 무시해버려 이런 현상이 일어나는 것이다. 65를 뺏을때부터 차이가 나는데 이또한 정확하지 않은것을 볼 수 있다.

정확한 수학연산은 BigDecimal과 같은 클래스를 사용해야 한다. 특히 사용자의 돈과 관련된 연산은 실수형으로 하면 절대 안된다.

문자형의 경우 자바는 유니코드를 지원 하기위해 16bit를 사용한다.

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

Java Data Type
ㄴ Primitive Type
    ㄴ Boolean Type(boolean)
    ㄴ Numeric Type
        ㄴ Integral Type
            ㄴ Integer Type(short, int, long)
            ㄴ Floating Point Type(float, double)
        ㄴ Character Type(char)
ㄴ Reference Type
    ㄴ Annotation
    ㄴ Array
    ㄴ Class
    ㄴ Enumeration
    ㄴ Interface

참조형은 기본적으로 java.lang.Object를 상속받은 클래스들 이다. 참조형에는 Annotation, Array, Class, Enumeration, Interface가 있다.

Primitive Type Reference Type
boolean, char, byte, short, int등 이미 정해진 유형 사용자가 무제한적으로 유형을 선언
메모리에 저장되는 값이 실제값 저장되어 있는 값은 메모리의 주소
동일한 유형의 다른 변수에 값이 복사됨 다른 참조유형에 할당되면 동일한 객체를 가리킴
메소드에 전달되면 사본만 전달됨. 메서드에서는 Primitive Type의 원본에 접근할 수 없음. 메서드는 복사된 값을 변경 가능 객체가 메소드에 전단되면 메소드는 주소값을 전달받게되어 객체의 내용을 변경할 수 있음. 객체의 주소는 변경불가

Reference Type의 실제값은 JVM의 Runtime Data Area에 저장되어 있다. 그중 Stack과 Heap 영역에 저장되어 있다.

Primitive Type과 Reference Type은 Wrapper Class를 이용해 auto boxing과 unboxing으로 자동으로 변환 될 수 있다.

리터럴

리터럴은 실제값 그 자체라고 볼 수 있다. 소스코드에서 고정된 값을 대표하는 용어이며 리터럴은 변수 초기화에 사용된다.

public class Main {
	public static void main(String[] args) {
		int val = 10;
		long val2 = 100L;
		String str = "Hello";
	}
}

위 코드의 변수 선언 및 초기화에서 우항에 있는 값을 리터럴이라고 한다.

리터럴 예

정수형 리터럴

  • 100               // 일반적인 10진수
  • 0xAA             // 16진수
  • 010               // 8진수
  • 0b1010          // 2진수
  • 100L             // long형 정수

실수형 리터럴

  • 100.1f
  • 100.1
  • 1.0E-1f;

문자, 문자열 리터럴

  • 'C'
  • "Java"

논리 리터럴

  • true
  • false

정수 및 실수형 리터럴에는 가시성을 위해 숫자와 숫자 사이 '_'를 넣어도 된다. ( java 7부터 가능)

ex) 1_000, 1_000.0_1f

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

public class Main {
	public static void main(String[] args) {
		int val = 10;
		long val2;
		val2 = 20L;
	}
}

변수는 선언과 동시에 초기화 할 수 있으며 선언후 사용하기전 값을 할당 할 수 도 있다.

public class Main {
	public static void main(String[] args) {
		final int val = 10;
	}
}

final 키워드를 사용하면 상수를 선언할 수 있다. 상수는 선언과 동시에 초기화 되어야 하며 이후 값을 변경할 수 없다.

변수의 종류와 특징

종류 선언 위치 생성시기
클래스 변수 (class variable) 클래스 영역 클래스가 메모리에 올라갈 때
인스턴스 변수 (instance variable) 인스턴스가 생성 되었을 때
지역 변수 (local variable) 클래스 영역 이외 변수 선언문이 수행 되었을 때

1. 클래스 변수 (class variable)

멤버변수에 static 키워드를 붙힐 경우 클래스 변수가 되며 한 클래스의 모든 인스턴스가 값을 공유한다. 클래스 변수는 인스턴스를 생성하지 않아도 클래스가 메모리에 올라 갔을때 생성되기 때문에 생성된 인스턴스가 없어도 클래스 이름만으로 언제든 바로 접근해서 사용할 수 있다.

2. 인스턴스 변수 (instance variable)

클래스 영역에 선언되며 클래스의 인스턴스를 생성할 때 만들어 진다. 각 인스턴스마다 별도의 공간을 가지므로 각각의 인스턴스 변수는 서로 다른 값을 가질 수 있다.

3. 지역 변수 (local variable)

메소드 내에서 선언되어 메소드 내에서만 사용되고 메소드 종료와 함께 소멸 된다.

※ 이후 클래스파트에서 더 상세히 설명하겠습니다.

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

public class Main {
	public static void main(String[] args) {
		int val = 10;
		System.out.println(val);
		{
			long val2 = 20;
			System.out.println(val2);
		}
		//System.out.println(val2);
	}
}

스코프는 변수에 접근할 수 있는 범위를 말한다. Java에서는 블록 스코프를 사용한다. 중괄호의 열고 닫는 한 지역을 블록이라고 하며 블록 스코프 내에서 선언된 변수는 스코프가 끝나면 사용할 수 없다. 이렇게 변수가 선언되고 메모리에서 살아있는 기간을 라이프 타임이라고 한다.

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

일반적으로 크기가 작은 자료형은 크기가 큰 자료형에 값을 담을 수 있다. 즉 int를 long 변수에 담을 수 있다. 이과정을 타입 프로모션이라고 한다. 반대로 크기가 큰 자료형에서 작은 자료형에 데이터를 넣을 수도 있으며 이과정을 타입 캐스팅이라고 한다. 하지만 이과정중 원본 데이터의 손실 위험이 있기 때문에 조심해야한다. 

  • 타입 프로모션: 작은 데이터 타입에서 큰 데이터 타입으로 형변환
  • 타입 캐스팅: 큰 데이터 타입에서 작은 데이터 타입으로 형변환

1차 및 2차 배열 선언하기

배열은 동일한 자료형을 가진 연속된 메모리 공간으로 이루어진 자료구조를 말한다. 같은 자료형을 가진 변수들이 여러개 필요할 때 사용된다.

public class Main {
	public static void main(String[] args) {
		int[] arr = new int[100];
	}
}

배열은 단순 1차원 배열외에도 2, 3, 4차등 다차원 배열을 만들어 사용할 수 도 있다. 2차원 배열은 행렬, 3차원 배열은 게임에서 사용하는 3차원 좌표를 나타낼때 사용될 수 있다.

public class Main {
	public static void main(String[] args) {
		int[][] arr = new int[100][100];
	}
}

java는 몇차원 배열을 만들것인지 먼저 선언후 크기를 지정하여 인스턴스를 생성한다.

타입 추론, var

타입 추론(Type inference)이란 값을 보고 컴파일러가 데이터 타입이 무엇인지 추론한다는 것을 의미한다. java 10 버전부터 타입추론을 사용할 수 있게되었다. var는 로컬 변수로 선언해야 하며 컴파일러가 타입을 유추할 수 있도록 선언과 동시에 값을 할당해야 한다.

public class Main {
	public static void main(String[] args) {
		var integer = 100;
		var str = "Hello";
	}
}

해당 코드는 컴파일과정에서 각각 int형과 String형으로 타입추론이 이루어진다. var 사용시 개발자가 쉽게 타입을 유추할 수 없기 때문에 변수 네이밍에 신경을 쓰는것이 좋다.

    int[] array = new int[100];
    for (var type: array) {
           
    }

java11부터는 람다에도 var를 사용할 수 있게 되었다. var는 이후 배울 제네릭에 아주 유용하게 사용되고 있다.


※참고자료

jdm.kr/blog/213

ko.wikipedia.org/wiki/IEEE_754

www.oreilly.com/library/view/java-8-pocket/9781491901083/ch04.html

 

반응형

'Java > Java 기본' 카테고리의 다른 글

Java 제어문  (0) 2023.07.01
Java 연산자  (0) 2023.07.01
Garbage Collector의 종류 (Serial, Parallel, CMS, G1, Z)  (0) 2023.06.29
JVM 구성 요소  (0) 2023.06.29
Java Bytecode  (0) 2023.06.29