자바 데이터 타입, 변수 그리고 배열

dev_314·2022년 9월 26일
0

| 백기선님의 라이브 스터디를 참고하여 작성한 게시물입니다.

자바의 데이터

참고: The Java™ Tutorials Primitive Data Types

자바는 statically-typed 언어다. 즉, 모든 변수들은 사용되기 전에 선언되어야 한다.
선언은 변수의 타입과 이름을 명명하는 작업을 포함한다.

int gear = 1;

이렇게 선언하는 것은 프로그램에게 초기값이 1인 Integar literal을 보관하는 gear라는 이름의 int 타입의 필드가 존재한다는 것을 알려주는 것이다.

변수의 데이터 타입은 그 변수가 보관하는 값과, 그 값이 수행할 수 있는 operation에 따라 결정된다.

자바의 primitive type

primitive type은 자바가 예약어(reserved keyword)로 미리 결정한 타입들을 의미한다. Primitive value들은 다른 Primitive value들과 상태를 공유하지 않는다.

primitive type의 변수를 선언할 때는 new 키워드를 사용하지 않는다.
primitive type은 class로 생성된 객체가 아니다. -> primitive type들은 언어에 내장된 특별한 데이터 타입

Java가 데이터를 다루는 최소 범위는 1 Byte이다. 고로 primitive type 변수들은 1Byte로 나누어 떨어지는 크기를 갖는다.

자바는 8개의 primitive type을 지원한다.

byte

2의 보수를 사용하는 8bit의 데이터 타입 -> -128 ~ 127의 값 & 1byte 크기
0을 초기값으로 가짐

short

2의 보수를 사용하는 16bit의 데이터 타입 -> -32,768 ~ 32,767의 값 & 2byte 크기
0을 초기값으로 가짐

int

2의 보수를 사용하는 32bit의 데이터 타입 -> -2^31 ~ 2^31-1의 값 & 4byte 크기
0을 초기값으로 가짐

long

2의 보수를 사용하는 64bit의 데이터 타입 -> -2^63 ~ 2^63-1의 값 & 8byte 크기
0L을 초기값으로 가짐

float

single-precision 32-bit IEEE 754 floating point을 구현한 데이터 타입 -> 오차 발생 가능
4byte 크기

정확한 연산이 중요한 경우에는 java.math.BigDecimal class를 사용

0.0f을 초기값으로 가짐

double

double-precision 64-bit IEEE 754 floating point을 구현한 데이터 타입. -> 오차 발생 가능
8byte 크기

0.0d을 초기값으로 가짐

boolean

true, false 두 가지 값만 가질 수 있는 데이터 타입 -> 1bit 정보 저장용
1byte 크기
(1bit 정보 저장 != 데이터 타입 사이즈가 1bit인건 아님)

false를 초기값으로 가짐

char

유니코드를 저장하는 16bit의 데이터 타입(0 ~ 2 ^ 16)
2byte 크기

\u0000을 초기값으로 가짐

primitive type의 default value

  • 선언되었으나 값이 할당되지 않은 primitive type의 변수들은 컴파일러에 의해 데이터 타입에 따라 0, null, false 등의 값을 기본값을 가진다.

  • 이러한 기본값에 의존해 코드를 작성하는 건 좋지 않다.

  • 지역 변수들은 compiler가 초기화(기본값으로 할당)하지 않는다.
    - 초기화되지 않은 변수에 접근하면 compile-time error가 발생한다.

그 외

JVM의 피연산자 스택이 피연산자를 4 Byte 단위로 저장 하기 때문에 int보다 작은 자료형의 값을 계산시 int 형으로 형변환 되서 연산이 수행된다.

String

스트링 타입은 참조형에 해당하지만 new 키워드 없이 사용할 수 있다

String myString = "myString";
String yourString = new String("yourString");

Literals

literal은 고정된 값을 표현하는 소스 코드 -> 별도의 연산 작업 없이 바로 코드를 통해 들어남

literal을 primitive type의 변수에 할당할 수 있음

boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;

Integer Literals

integer literal은 끝부분에 L or l이 붙어있으면 long타입, 그렇지 않으면 int 타입으로 결정된다.

l말고 L붙이기를 권장한다 ('1'과 구분이 잘된다)

byte, short, int 숫자형 타입들의 값은 int literal로 생성된다.
int형의 범위를 벗어난 long 타입은 long literal로 생성된다.

Integer literals들은 Decimal, Hexadecimal, Binary를 표현할 수 있다.

Java SE 7이후 부터는 Binary 전용 Int Literal을 사용할 수 있다.
Java SE 7이후 부터는 _(underscore)를 사용해서 Integer literal의 가독성을 높일 수 있다
underscore는 digit 사이에만 위치할 수 있다

// The number 26, in decimal
int decVal = 26;
//  The number 26, in hexadecimal
int hexVal = 0x1a;
// The number 26, in binary
int binVal = 0b11010;

// _를 사용해 가독성 높이기
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi =  3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;

Floating-Point Literals

float, double 타입에 사용되는 리터럴
F or f로 끝나면 float 타입, 아무것도 없거나 D or d로 끝나면 double 타입으로 결정된다.

E or e을 사용하여 scientific notation을 표현할 수 있다.

double d1 = 123.4;
// same value as d1, but in scientific notation
double d2 = 1.234e2;
float f1  = 123.4f;

Character and String Literals

UTF-16 Unicode 인코딩 방식을 사용한다. 그러므로 char, String Literal은 Unicode (UTF-16)를 포함한다.
에디터, 파일 시스템이 지원하면, 코드에 직접 유니코드를 사용할 수 있는데, 불가능한 경우에는 직접 "Unicode escape(EX: \u0108)"을 사용해야 한다.

char literal은 'single quotes', String literal은 "double quotes"로 표현해야 한다.

char literal은 빈 칸을 사용할 수 없다

char myChar = ''; // compile error
String myString = ""; // no error

자바는 몇 가지 특별한 escape sequence를 제공함

\b (backspace)
\t (tab)
\n (line feed)
\f (form feed)
\r (carriage return)
\" (double quote)
\' (single quote)
\\ (backslash)

null Literal

모든 종류의 reference type변수의 값으로 사용할 수 있는 리터럴 -> primitive type에는 사용 불가능

class Literal

참고: 클래스 리터럴, 타입 토큰, 수퍼 타입 토큰

Finally, there's also a special kind of literal called a class literal, formed by taking a type name and appending ".class"; for example, String.class. This refers to the object (of type Class) that represents the type itself.

Type Casting & Promotion

리터럴과 변수의 타입이 다른 경우, 명시적(Type Casting) 또는 묵시적(Type Promotion)으로 타입을 맞춰줄 수 있다.
변환하려는 변수의 타입에 따라 값 손실이 발생할 수 있다.


int i = 12;
double d = (double) i; // 작은 사이즈 -> 큰 사이즈 = 값 손실 X
double d = i; // float 타입 변수에 integer literal을 대입 -> integer literal 뒤에 f 없음 => 기본값으로 double로 인식 ==> (type) 생략 가능 

double d = 12.3; // double은 8byte
int i = (int) d; // int는 4byte -> 값 손실 발생 => i == 12

특정 상황에서 컴파일러를 통해 묵시적 형변환이 발생한다.

float f = 1234; // 컴파일러가 int를 자동으로 float로 변환
float f = (float)1234; // 위 내용을 컴파일러가 다음처럼 해석함 

byte b = 1234; // Error : byte의 표현 범위를 넘어감 (-128 ~ 127)
char ch = (char) 1000; // not Error : 명시적 형 변환

// 서로 다른 타입간의 연산에서는 두 타입 중 더 큰 크기의 타입 기준으로 변환이 발생
int i = 3; 
double d = 1.0 + i; // 4.0 (not Error : 자동 형변환 발생)

자바의 reference type

참고: What's the difference between primitive and reference types?

primitive type을 제외한 모든 타입이 reference type에 해당함

primitive type변수와 다르게 크기가 정해져 있지 않다.

primitive vs reference

primitive type과 reference type은 여러 모습이 다르다.
그중 가장 큰 차이점은 primitive type 변수는 실제 값을 저장하고, reference type 변수는 인스턴스의 주소를 저장한다.는 점이다.

Let’s assume that a class Person is already defined. If you create an int variable a, and an object reference variable person, they will store their values in memory as shown in figure 2.13.

primitive type 변수 a, reference type 변수 person이 있다면,

int a = 77;
Person person = new Person();

다음과 같은 모습으로 저장된다.
사진 출처: https://stackoverflow.com/questions/8790809/whats-the-difference-between-primitive-and-reference-types/32049775#32049775

변수

변수를 3가지로 구분할 수 있다.

Instance Variable

인스턴스에 각각 할당된 변수를 의미

Class Variable

클래스에 할당된 변수를 의미

클래스 변수는 모든 인스턴스들이 공유한다.

Local Variable

local varaibel = 모든 변수 - instance variable - class variable

class Student {
	String id; // instance variable
    int grade; // instance variable
    Major major; // instance variable
    
    static String profession = "student"; // class variable
    
    Student(String id, int grade, Major major) {
    	this.id = id;
        this.grad= grad;
        this.major = major;
    }
    
    void printMessage(String message) {
    	// message는 local variable
    	System.out.println("Hello " + message + ", I'm " + this.grade);
    }
}

선언, 초기화

변수의 종류에 따라 여러 가지 선언, 초기화 방법이 존재한다.

constructor

package variable;

public class MyVariable {
    static int classVariable;
    int instanceVariable;

    public MyVariable(int a) {
        MyVariable.classVariable++; // 생성자에서 class variable도 변경할 수 있다.
        this.instanceVariable = a;
    }

    public static void main(String[] args) {
        System.out.println(MyVariable.classVariable); // 아직 값 할당 X -> 기본값 0
        MyVariable myVariable = new MyVariable(10); // instance, class variable 할당
        System.out.println(MyVariable.classVariable); // 1
        System.out.println(myVariable.instanceVariable); // 10
    }
}

생성자가 인스턴스를 만드는 것이 아니다.생성자는 단순히 값을 할당하는 역할을 수행할 뿐
new 키워드가 인스턴스를 생성하고, 변수에 인스턴스의 주소를 할당한다.
인스턴스는 heap 영역에 위치한다.
변수는 stack 영역에 위치한다.

  1. instance, class variable은 값을 할당하지 않아도 사용할 수 있다. 값을 명시적으로 할당하지 않으면 컴파일러가 자동으로 초기값을 설정해준다.
  2. local variable은 무조건 값 할당이 명시적으로 이뤄져야 한다.

explicit initialization

변수 선언과 동시에 값을 할당하는 방식

int x = 1;

Initialization Block

코드로 직접 보자

class Card {
	static int count;
    int number;
    String shape;
	static { // Class Variable 초기화를 위한 class Initialization block
		// 클래스가 메모리에 로딩될 때 한 번만 수행됨
        count = 1;
	}
	
	{ // Instance Variable 초기화를 위한 Instance Initialization block
		// Instance가 생성될 때마다 수행됨
        // Constructor보다 먼저 수행된다
        number++;
        shape = "heart";
	}

	Card() {
    	// 생성자
    }
}

여러 종류의 생성자에서 동일한 값 할당이 발생할 경우, 초기화 블록을 통해 리팩토링 가능

정리

  1. local variable은 무조건 값 할당이 이뤄져야 사용할 수 있다. 안 그러면 컴파일 에러 발생
  2. instance variable은 다음과 같은 순서로 값 할당이 이뤄진다.
    1. 기본값 할당
    2. 명시적 초기화
    3. 인스턴스 초기화 블록
    4. 생성자
  3. class variable은 다음과 같은 순서로 값 할당이 이뤄진다.
    1. 기본값 할당
    2. 명시적 초기화
    3. 클래스 초기화 블록

lifetime & scope

instance variable

  • 인스턴스 생성시에 생성 & 인스턴스가 GC로 인해 heap에서 제거될 때 까지 유효
  • 접근 제한자의 영향을 받음

class variable

  • 바이트코드(.class)가 classLoader를 통해 메모리에 올라올 때 생성, 응용 프로그램 종료까지 존재
  • 접근 제한자의 영향을 받음

local variable

  • 메소드 실행 직전에 생성 & 메소드 실행 종료시에 제거
  • 메소드 내부에 국한된 스코프

var와 타입 추론

참고: [Java-11]타입추론 var

모든 변수는 명시적으로 타입을 명시해야 한다.
그런데 자바10 부터 자바에서도 타입 추론을 사용할 수 있다.

// 기존
String myString = "hello";

var myString2 = "hello";

System.out.println(myString2 instanceof String); // true

단, var는 초기화 값이 존재하는 local variable에만 사용할 수 있다.
다른 언어들의 타입 추론과 달리, 자바의 var는 한 번 타입이 정해지면 다른 타입으로 변경될 수 없다.
이는 컴파일 당시에 var의 구체적인 타입이 결정되기 때문이다.
즉, 런타임에서 타입을 추론하는 과정이 없으므로 성능에는 큰 영향이 없다.

배열

선언

type[] 배열이름 형식으로 배열 변수를 선언한다.

byte[] anArrayOfBytes;
short[] anArrayOfShorts;
long[] anArrayOfLongs;
float[] anArrayOfFloats;
double[] anArrayOfDoubles;
boolean[] anArrayOfBooleans;
char[] anArrayOfChars;
String[] anArrayOfStrings;
// 다음과 같은 형식으로도 배열을 선언할 수 있으나 비권장(convention)
float anArrayOfFloats[];

생성, 초기화

new 키워드를 통해 배열 인스턴스를 생성한다.
자바에서 배열은 객체로 취급된다

// 크기가 10인 바이트 타입 값들을 저장하는 배열 변수 선언
byte[] anArrayOfBytes = new byte[10];

초기화되지 않은 배열 변수에 접근하면 컴파일 애러가 발생한다

ArrayDemo.java:4: Variable anArrayOfBytes may not have been initialized.

2차원 배열

int[][] myInts = {
	{1,2,3},
    {4,5,6},
    {7,8,9}
};

byte[][] myBytes = new byte[5][10] // { {10개의 default value값의 literal}, {}, {}, {}, {}}

그 외

다음과 같이 배열 인스턴스 생성과 값 할당을 동시에 수행할 수 있다.

int[] anArray = { 
    100, 200, 300,
    400, 500, 600, 
    700, 800, 900, 1000
};

배열의 내장 속성인 length를 통해 배열의 크기를 알 수 있다.

System.out.println(anArray.length);

System.arraycopy 메소드를 통해 배열 복사가 가능하다

package array;

public class MyArray {
    public static void main(String[] args) {
        int[] myInts = {1, 2, 3, 4};

        int[] yourInts = new int[4];
		
        // 복사 대상 배열, 복사 시작 인덱스, 복사 결과 배열, 복사 시작 인덱스, 복사 개수
        System.arraycopy(myInts, 0, yourInts, 0, 4);

        for (int yourInt : yourInts) {
            System.out.println(yourInt);
            // 1
            // 2
            // 3
            // 4
        }

        System.out.println(myInts); // [I@4617c264
        System.out.println(yourInts); // [I@36baf30c
    }
}

참고: Why is array indexing in Java start with 0?

자바는 C언어를 따라 zero-base indexing을 사용한다
그러므로 다음이 성립한다.
1. array 주소 = array[0] 주소
2. array[n] 주소 = array 시작 주소 + n * 데이터 타입 크기

profile
블로그 이전했습니다 https://dev314.tistory.com/

0개의 댓글