[ Java 기초 ] 자바 데이터 타입, 변수 그리고 배열

황승환·2021년 12월 22일
0

Java 기초

목록 보기
2/6
post-thumbnail

Goal

자바의 프리미티브 타입, 변수 그리고 배열을 사용하는 방법 익히기

Study

Primitive type(기본형 타입)

  • 8가지 기본형 타입을 미리 정의하여 제공한다.
  • 기본값이 있기 때문에 Null이 존재하지 않는다. 만약 기본형 타입에 Null을 넣고 싶다면 래퍼 클래스를 활용한다.
  • 실제 값을 저장하는 공간으로 Stack 메모리에 저장된다.
  • 만약 컴파일 시점에 담을 수 있는 크기를 벗어나면 에러를 발생시키는 컴파일 에러가 발생한다. 주로 문법상의 에러가 많다.

논리형

  • boolean
    -> 할당되는 메모리 크기: 1byte
    -> 기본값: false
    -> 데이터 표현 범위: true, false

정수형

  • byte
    -> 할당되는 메모리 크기: 1byte
    -> 기본값: 0
    -> 데이터 표현 범위: -128 ~ 127
  • short
    -> 할당되는 메모리 크기: 2byte
    -> 기본값: 0
    -> 데이터 표현 범위: -32,768 ~ 32,767
  • int(기본)
    -> 할당되는 메모리 크기: 4byte
    -> 기본값: 0
    -> 데이터 표현 범위: -2,147,483,648 ~ 2,147,483,647
  • long
    -> 할당되는 메모리 크기: 8byte
    -> 기본값: 0L
    -> 데이터 표현 범위: -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

실수형

  • float
    -> 할당되는 메모리 크기: 4byte
    -> 기본값: 0.0F
    -> 데이터 표현 범위: (3.4 x 10^(-38)) ~ (3.4 x 10^(38))의 근사값
  • double(기본)
    -> 할당되는 메모리 크기: 8byte
    -> 기본값: 0.0
    -> 데이터 표현 범위: (1.7 x 10^(-308)) ~ (1.7 x 10^(308))의 근사값

문자형

  • char
    -> 할당되는 메모리 크기: 2byte(유니코드)
    -> 기본값: '\u0000'
    -> 데이터 표현 범위: 0 ~ 65,535

Reference type(참조형 타입)

  • 기본형 타입을 제외한 타입들이 모두 참조형 타입이다.
  • 빈 객체를 의미하는 Null이 존재한다.
  • 값이 저장되는 곳의 주소값을 저장하는 공간으로 Heap 메모리에 저장된다.
  • 문법상으로는 에러가 없지만 실행시켰을 때 에러가 나는 런타임 에러가 발생한다. 예를 들어 객체나 배열을 Null 값으로 받으면 NullPointException이 발생하므로 변수값을 넣어야 한다.

참조형 타입

  • 배열(Array)
    -> ex)
int[] arr = new int[10];

-> 기본값: Null
-> 할당되는 메모리 크기: 4byte (객체의 주소값)

  • 열거(Enumeration)
    -> 기본값: Null
    -> 할당되는 메모리 크기: 4byte (객체의 주소값)
  • 클래스(Class)
    -> ex)
String str = "test";
Student xx0hn = new Student();

-> 기본값: Null
-> 할당되는 메모리 크기: 4byte (객체의 주소값)

  • 인터페이스(Interface)
    -> 기본값: Null
    -> 할당되는 메모리 크기: 4byte (객체의 주소값)

Literal(리터럴)

프로그램에서 직접 표현한 값으로 소스 코드의 고정된 값을 대표하는 용어이다. 종류로는 정수, 실수, 문자, 논리, 문자열 리터럴이 있다.

정수 리터럴

  • 10진수: int a = 15; => 10진수 값으로 15 출력
  • 8진수: int b = 015; => 10진수 값으로 13 출력
  • 16진수: int c = 0x15; => 10진수 값으로 21 출력
  • 2진수: int d = 0b0101; => 10진수 값으로 5 출력

정수 리터럴은 int형으로 컴파일되고 long 타입 리터럴의 경우 숫자 뒤에 L 또는 l을 붙여 표시한다.

실수 리터럴

소수점 형태나 지수 형태로 표현한 값으로 실수 타입 리터럴은 double타입으로 컴파일 된다.

double f = 0.1234;
double g = 1234E-4;

(1234 x 10^(-4)이므로 0.1234와 같다.
숫자 뒤에 f(float)이나 d(double)을 명시적으로 붙이기도 한다. float의 경우에는 반드시 f를 붙여줘야 하고, double의 경우에는 생략 가능하다.

문자 리터럴

단일 인용부호('_')로 문자를 표현한다.

char a = 'H';
char b = "한";
char c = \uae00;(유니코드값) //(\u다음에 4자리 16진수로, 2바이트의 유니코드)

문자열 리터럴

문자열은 기본형 타입이 아니다. ("_")로 문자열을 표현한다.

String lter = "JAVA;"
lter + 26 = "Lter26;"

논리 타입 리터럴 + 외 리터럴

boolean 타입 변수에 치환하거나 조건문에 이용한다.

boolean a = true;
boolean b = 10 >0; //true로 저장됨
boolean c = 0; // boolean타입으론 1,0을 참, 거짓으로 사용 불가함

null 리터럴은 레퍼런스에 대입해서 사용한다. 기본 타입에는 사용이 불가하고 String 같은 경우에는 사용 가능하다.

int a = null; //error
String str = null;
str = "JAVA";

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

변수의 선언과 초기화를 알아보기 전에 변수의 종류에 대해 간단하게 정리해보았다.

변수의 종류

변수는 선언하는 곳마다 부르는 이름이 달라진다.

  • 필드, 멤버변수, 전역변수
    클래스에서 생성한 변수이다. 클래스 내에서 사용 가능하다.
  • 지역변수
    메소드 안에서 지역적으로 선언한 변수이다. 메소드 내에서 사용 가능하다.

변수 선언

변수를 선언하는 방법은 매우 간단하다. 데이터 타입을 선언하고 바로 뒤에 변수 이름을 넣으면 된다.

int var1;

여러개의 변수를 한번에 선언하는 것도 다음과 같이 가능하다.

int var1, var2, var3;

변수 초기화

명시적 초기화

일반적으로 초기화를 할 때는 변수를 선언하고 바로 뒤에 '=' 대입 연산자를 사용하여 변수에 데이터를 넣었다.

int var1 = 1000;

블록 초기화

블록 초기화는 말 그대로 초기화를 할 수 있는 블록을 하나 생성하는 것이다.

public class Study{
	static int var = 10;
    int hello = 3;
	{ // 일반적인 초기화 블록
		System.out.println("hello -> 5 초기화");
    	hello = 5;
	}
	static { // 정적 초기화 블록
		System.out.println("var -> 10 초기화");
    	var = 10;
	}
}

이렇게 블록으로 나눠 초기화 하는 이유는 명시적 초기화의 한계가 있기 때문이다. 필드에서 바로 초기화를 시키면 복잡한 로직으로 값을 변수에 넣는데에 제한이 있기 때문에 블록 방식을 사용한다.

  • 일반적인 초기화 블록: Class가 new를 통해 인스턴스를 생성하는 순간 초기화가 진행
  • 정적 초기화 블록: JVM이 Class Loader로 Loading 시점에 초기화 진행
public static void main(String[] args){
	System.out.println("인스턴스 생성");
	Study st = new Study();
}

main 메소드를 실행시키면 결과는 다음과 같이 출력된다.

정적 변수 var -> 10 초기화
인스턴스 생성
멤버 변수 hello -> 5 초기화

정적 초기화 블록이 인스턴스 생성 이전에 먼저 초기화되고 인스턴스 생성 후에 일반적인 초기화 블록이 초기화되는 것을 확인할 수 있다.

변수의 스코프(Scope)

변수의 스코프는 변수를 사용할 수 있는 범위를 의미한다. 쉽게 생각하면 변수가 선언된 블럭 안에서 영역이 끝나기 전까지 어디서든 사용이 가능하다.

public class ValableScopeExam{
	int globalScope = 10;   // 인스턴스 변수 
	public void scopeTest(int value){   
		int localScope = 10;
		System.out.println(globalScope);
		System.out.println(localScpe);
		System.out.println(value);
	}
}
  • 클래스의 속성으로 선언된 변수 globalScope의 사용범위는 클래스 전체이다.
  • 매개변수로 선언된 int value는 블럭 바깥에 존재하기는 하지만, 메소드 선언부에 존재하므로 사용범위는 해당 메소드 블럭이다.
  • 메소드 블럭 내에서 선언된 localScope변수의 사용범위는 메소드 블럭 내이다.

main 메소드에서 사용하기

public class VariableScopeExam {
	int globalScope = 10; 
	public void scopeTest(int value){
		int localScope = 20;            
		System.out.println(globalScope);
		System.out.println(localScope);
		System.out.println(value);
	}   
	public static void main(String[] args) {
		System.out.println(globalScope);  //오류
		System.out.println(localScope);   //오류
		System.out.println(value);        //오류  
	}   
}
  • 같은 클래스 안에 있어도 globalScope변수를 사용할 수 없다.
  • main은 static한 메소드이다. static한 메소드에서는 static하지 않은 필드를 사용할 수 없다.

Static

  • 같은 클래스 내에 있어도 해당 변수들을 사용할 수 없다.
  • main 메소드는 static이라는 키워드로 메소드가 정의되어 있다. 이런 메서드를 static한 메소드라고 한다.
  • static한 필드(필드 앞에 static이 붙음)나 static한 메소드는 Class가 인스턴스화 되지 않아도 사용할 수 있다.
public class VariableScopeExam {
	int globalScope = 10;
    static int staticVal = 7;
    public void scopeTest(int value){
    	int localScope = 20;
    }
    public static void main(String[] args) {
    	System.out.println(staticVal); // 사용 가능
    }
}

static한 변수는 공유된다.

  • static하게 선언된 변수는 값을 저장할 수 있는 공간이 하나만 생성된다. 그러므로 인스턴스가 여러개 생성되어도 static한 변수는 하나이다.
ValableScopeExam v1 = new ValableScopeExam();
ValableScopeExam v2 = new ValableScopeExam();
v1.globalScope = 20;
v2.globalScope = 30;
System.out.println(v1.globalScope); // 20 출력
System.out.println(v2.globalScope); // 30 출력
v1.staticVal = 10;
v2.staticVal = 20;
System.out.println(v1.staticVal); // 20 출력
System.out.println(v2.staticVal); // 20 출력
  • globalScope같은 변수(필드)는 인스턴스가 생성될 때 생성되기 때문에 인스턴스 변수라고 한다.
  • staticVal같은 static한 필드를 클래스 변수라고 한다.
  • 클래스 변수는 '레퍼런스.변수명'으로 사용하기 보다는 '클래스명.변수명'으로 사용하는 것이 더 바람직하다.
    ex) VariableScopeExam.staticVal

변수의 라이프타임(LifeTime)

모든 변수는 생명주기라는 것을 가지게 된다. 생명주기란 변수가 생성되고 죽을 때까지를 말한다.

public class Study {
	int var1; // 인스턴스 변수, 필드, 전역 변수
    static int var2; // 클래스 변수, 정적 변수
    void hard(int var3){ // 매개 변수, 파라미터
    	int var4 = 10; // 지역 변수
    }
}

각 변수들의 생명주기에 대해 알아보았다.

  • 인스턴스 변수 (var1):
    객체가 생성될 때, 변수가 생성된다. 현재 Study 클래스를 static main 메소드나 다른 클래스에서 인스턴스 생성할 때에 생성된다. 그리고 인스턴스는 참조가 없을 때 Garbage Collecter가 객체를 지우는데 이때 인스턴스 변수도 같이 소멸된다.
  • 클래스 변수 (var2):
    클래스가 처음 호출되면서 생성되고 자바 어플리케이션이 종료되는 시점에 같이 소멸된다.
  • 매개 변수 (var3):
    hard 메소드가 호출될 때 생성되고 hard 메소드가 종료될 때 소멸된다.
  • 지역 변수 (var4):
    해당 블록이 시작하는 시점에서 생성되고 블록이 끝나는 시점에서 소멸된다.

타입 변환(Type conversion)

하나의 타입을 다른 타입으로 바꾸는 것을 타입 변환이라고 한다. 자바에서는 boolean형을 제외한 나머지 기본 타입 간의 타입 변환을 자유롭게 수행할 수 있다.

자바에서 다른 타입끼리의 연산은 우선 피연산자들을 모두 같은 타입으로 만든 후에 수행된다. 메모리에 할당받은 바이트의 크기가 상대적으로 작은 타입에서 큰 타입으로의 타입 변환은 생략 가능하다.

하지만 메모리에 할당받은 바이트의 크기가 큰 타입에서 작은 타입으로의 타입 변환은 데이터의 손실을 발생시킨다. 따라서 상대적으로 바이트의 크기가 작은 타입으로 타입 변환을 할 경우 자바 컴파일러는 오류를 발생시킨다.

타입 변환의 종류

자바의 타입 변환은 크게 두 가지로 분류된다.

  • 묵시적 타입 변환(자동 타입 변환)
  • 명시적 타입 변환(강제 타입 변환)

묵시적 타입 변환(자동 타입 변환, implicit conversion)

묵시적 타입 변환이란 대입 연산이나 산술 연산에서 컴파일러가 자동으로 수행해주는 타입 변환을 가리킨다. 자바는 데이터의 손실이 발생하지 않거나, 데이터의 손실이 최소화되는 방향으로 묵시적 타입 변환을 진행한다. 또한 자바는 데이터의 손실이 발생하는 대입 연산은 허용하지 않는다.

double num1 = 10;
//double num2 = 3.14;
double num3 = 7.0f + 3.14;
System.out.println(num1); // 10.0 출력
System.out.println(num3); // 10.14 출력
  • num1의 경우 int형 데이터를 double형 변수에 대입한다. 이때 int형 데이터가 double형으로 자동 타입 변환된다.
  • 주석 처리된 num2의 경우 double형 데이터를 int형 변수에 대입한다. 이때 int형 변수가 표현할 수 있는 범위보다 더 큰 double형 데이터를 대입하므로 데이터의 손실이 발생한다. 따라서 이 대입 연산은 자바 컴파일러가 오류를 발생시키며 거부한다.
  • num3의 경우 float형 데이터와 double형 데이터의 산술 연산을 수행한다. 이때 데이터의 손실이 최소화되도록 float형 데이터가 double형 데이터로 자동 타입 변환된다.

자바 컴파일러가 자동으로 수행하는 타입 변환은 위와 같이 데이터의 손실을 최소화 하는 방향으로 진행된다.
byte -> short & char -> int -> long -> float -> double

명시적 타입 변환(강제 타입 변환, explicit conversion)

명시적 타입 변환이란 사용자가 타입 캐스트 연산자(())를 사용하여 강제적으로 수행하는 타입 변환을 가리킨다.

자바에서는 다음과 같이 명시적 타입 변환을 수행할 수 있다.
(변환할 타입)변환할 데이터

변환하고자 하는 데이터의 앞에 괄호를 넣고, 그 괄호 안에 변환할 타입을 적으면 된다. 자바에서는 이 괄호를 타입 캐스트(type cast) 연산자 라고 한다.

int num1 = 1, num2 = 4;
double result1 = num1 / num2;
double result2 = (double)num1 / num2;
System.out.println(result1); // 0.0 출력
System.out.println(result2); // 0.25 출력
  • result1의 경우 나눗셈 연산의 결과로 0이 반환된다. 자바에서 산술 연산을 수행하고 얻는 결과값의 타입은 언제나 피연산자의 타입과 일치해야 한다. 즉 int형 데이터끼리의 산술 연산에 대한 결과값은 언제나 int형 데이터이다. 이 경우 자동으로 int형으로 변환되어 0이 반환된다.
  • result2의 경우 피연산자 중 하나의 타입을 double형으로 강제 타입 변환하였다. 이렇게 피연산자 중 하나의 타입이 double형이 되면 나눗셈 연산을 위해 나머지 다른 피연산자도 double형으로 자동 타입 변환된다. double형으로 결과가 반환되므로 0.25가 반환된다.

타입 캐스팅(Type Casting) & 타입 프로모션(Type Promotion)

타입 변환에서 타입 캐스트 연산자가 등장했다. 명시적 타입 변환 시 (변환할 타입)변환할 데이터 형태로 데이터의 타입을 변환하는데에 사용하는 연산자이다.

타입 프로모션은 묵시작 타입 변환을 의미한다. 앞서 설명한 바와 같이 크기가 더 작은 자료형을 더 큰 자료형에 대입할 때, 자동으로 작은 자료형이 큰 자료형으로 변환되는 현상이다.

클래스의 타입 캐스팅 & 타입 프로모션

기본형 타입의 경우 모두 타입 캐스팅이 가능하지만 클래스 타입의 경우 조금 다르다. 클래스 타입들은 기본적으로 타입 캐스팅이 불가능하다. 조금만 생각해 보아도 여러 자료형이 묶여있는 클래스 타입이 서로 호환될 일은 없어 보인다. 그러나 상속 관계에서의 클래스 타입은 타입 캐스팅이 가능하다.

  • Up-Casting
    부모 클래스는 명시적인 타입 캐스팅 없이 자식과 연결할 수 있다. 이는 타입 프로모션이라고 할 수도 있다.
  • Down-Casting
    자식 클래스는 명시적인 타입 캐스팅이 있다면 부모와 연결할 수 있다.
  • 상속 관계가 아니라면 타입 캐스팅은 불가능하다.

1차 및 2차 배열 선언

1차 배열 선언

int []array = new int[10];

위와 같이 배열을 선언할 경우 0이 10개 들어간 1차원 배열이 초기화된다.

int []array = new int[5];
array[0] = 1;
array[1] = 2;
array[2] = 3;
array[3] = 4;
array[4] = 5;

위와 같이 배열의 값을 따로 정의할 수도 있다.

2차 배열 선언

int [][]array = new int[10][10]
int [][]array = new int[5][5];
array[0][0] = 1;
array[0][1] = 1;
array[1][0] = 2;
array[1][1] = 2;
...

타입 추론

자바에서는 타입 추론을 지원한다. 타입 추론이란 말 그대로 개발자가 변수의 타입을 명시적으로 적어주지 않고도 컴파일러가 알아서 이 변수의 타입을 대입된 리터럴로 추론하는 것이다.

public static void main(String[] args) {
	var str = "Hello world";
    if(str instanceof String) {
    	System.out.println("str은 String입니다.");
    }
}

위의 코드를 실행하면 자바의 타입 추론 기능으로 str을 String으로 인식하여 "str은 String입니다"가 출력된다.

var 사용법

var는 멤버변수 또는 메소드의 파라미터, 리턴 타입으로 사용이 불가능하다. 그리고 무조건 선언할 때 초기화 값이 있어야 한다. 정리하면 var은 초기화 값이 있는 지역변수로만 선언이 가능하다.

var != 키워드

var는 키워드가 아니다. 즉, 어떤 타입도 아니고 클래스에서 사용할 수 있는 예약어가 아니라는 뜻이다. 그래서 int를 변수 이름으로 만들 수는 없지만 var라는 문자를 변수로 사용할 수 있다. 이런 메커니즘을 둔 이유는 컴파일러가 바이트 코드로 변환하는 과정에서 var은 타입이 명시되어지기 때문이다.

var의 런타임 오버헤드

var은 런타임 오버헤드가 없다. 컴파일 시점에서 var을 초기화된 값을 보고 추론하여 바이트 코드에 명시적으로 자료형을 선언한다. 결정이 되어 있는 상태이기 때문에 타입 추론 변수를 읽을 때마다 타입 추론 연산을 하지 않는다. 그래서 var로 선언된 변수는 중간에 타입이 변경되지 않는다.

var의 쓰임

var을 사용할 일이 별로 없다고 느껴질 수도 있지만 다음과 같은 경우에서는 유용하다.

Study<String> hard = s -> System.out.println("s = " + s);
Study<string> hard = (var s) -> System.out.println("s = " + s);

이렇게 변수 앞에 var을 넣을 수 있다. 이렇게만 봐서는 똑같다고 느껴지지만

Study<String> hard = (@Nonnull var s) -> System.out.println("s = " + s);

이렇게 키워드 앞에서만 사용할 수 있는 어노테이션을 사용할 수 있게 된다.

profile
꾸준함을 꿈꾸는 SW 전공 학부생의 개발 일기

0개의 댓글