본문 바로가기
Study/Live-Study

[Live-Study] 자바 데이터 타입, 변수 그리고 배열

by 검프 2021. 1. 27.

목표

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

학습할 것

  • 프리미티브 타입 종류와 값의 범위 그리고 기본 값
  • 프리미티브 타입과 레퍼런스 타입
  • 리터럴
  • 변수 선언 및 초기화하는 방법
  • 변수의 스코프와 라이프타임
  • 타입 변환, 캐스팅 그리고 타입 프로모션
  • 1차 및 2차 배열 선언하기
  • 타입 추론, var

2주차가 시작되었습니다. 정리도 우선이지만 쉽게 이해할 수 있게 정리해보는 것을 목표로 했습니다.

 

프리미티브 타입 종류와 값의 범위 그리고 기본 값

타입(Data type)이란 해당 데이터가 메모리에 어떻게 저장되고, 프로그램에서 어떻게 처리되어야 하는지를 명시적으로 알려주는 것입니다. 자바에서 타입은 크게 기본형(프리미티브) 타입참조형(래퍼런스) 타입이 있습니다.

프리미티브 타입은 메모리 공간(스택)에 직접 값(실제 값)을 담습니다.

총 8가지의 기본형 타입(Primitive type)을 미리 정의하여 제공합니다.

기본 값이 있기 때문에 Null이 존재하지 않습니다. 만약 기본형 타입에 Null을 넣고 싶다면 래퍼 클래스를 활용합니다.

컴파일 타임 에러가 주로 발생합니다.(데이터 표현범위를 넘어선 값을 저장하려할 때)

  타입 할당되는 메모리 크기 기본값 데이터의 표현 범위
논리형 boolean 1 byte false true, false
정수형 byte 1 byte 0 128 ~ 127
  short 2 byte 0 32,768 ~ 32,767
  int(기본) 4 byte 0 2,147,483,648 ~ 2,147,483,647
  long 8 byte 0L -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
실수형 float 4 byte 0.0F 3.4 X 10-38) ~ (3.4 X 1038) 의 근사값
  doule(기본) 8 byte 0.0 (1.7 X 10-308) ~ (1.7 X 10308) 의 근사값
문자형 char 2 byte (유니코드) '\u0000' 0 ~ 65,535

 

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

레퍼런스 타입의 변수는 스택에 생성되지만, 다른 곳을 참조하는 주솟 값(Heap 영역에 있는 객체를)을 담습니다. 즉, 변수는 스택 영역, 객체는 힙 영역에 생성

기본형 타입을 제외한 타입들이 모두 래퍼런스 타입(참조형 타입)입니다.

빈 객체를 의미하는 Null이 존재합니다.

런타임 에러가 주로 발생합니다.(객체나 배열을 Null값으로 받을 시 NullPointException 발생)

타입 예시 기본값 할당되는 메모리 크기
배열(Array) int[] arr = new int[5]; Null 4byte ( 객체의 주솟값 )
열거(Enumeration) public enum Season {} "" ""
클래스(Class) String str = "test";
Student stu = new Student
"" ""
인터페이스(Interface) public interface Car {} "" ""

 

리터럴

프로그램에서 직접 표현한 값,

쉽게 얘기해서 소스 코드(변수에 넣는 데이터)의 고정된 값을 의미하는 용어입니다.

아래의 예제에서

int a = 1;

int 앞에 a는 변수이고, 여기서의 1은 리터럴 을 의미합니다.

즉, 1과 같이 변하지 않는 데이터를 리터럴이라고 합니다.

리터럴의 종류는 정수, 실수, 논리, 문자, 문자열 리터럴 등이 있습니다.

정수형 리터럴

정수형 리터럴은 일반적인 숫자 즉 정수 데이터를 의미합니다

  • 10진수 리터럴
    • 10진수 값으로 10을 출력
  • int a = 15;
  • 8진수 리터럴
    • 10진수 값으로 13 출력
  • int b = 015;
  • 16진수 리터럴
    • 10진수 값으로 21 출력
  • int c = ox15;
  • 2진수 리터럴
    • 10진수 값으로 5 출력
  • int d = 0b0101;

모든 정수형 데이터가 기본적으로 int형이기 때문에 long 데이터 자료형에 정확한 long 리터럴을 지정하기 위해서는 숫자 뒤에 알파벳 l(또는 L)를 추가 해줘야 합니다.

실수(부동 소수점) 리터럴

소수점을 가진 실수형 데이터를 의미합니다

double a = 1.234; // 일반적인 실수 표현 방식
double b = 6.02E23; // 지수를 이용한 표현 방식 
float c = 1.234F; // float형 표현 방식

모든 실수형 데이터는 기본적으로 double이기 때문에 float 데이터 자료형에 정확한 float 리터럴을 지정하기 위해서는 숫자뒤에 알파벳 f(또는 F)를 추가 해줘야 합니다.

논리 리터럴

참(true)과 거짓(false)을 표현할때 사용하는 논리데이터를 의미합니다.

boolean a = true;
boolean b = 10 > 0;

1, 0 으로 참 거짓을 표현할 수 없습니다.

문자 리터럴

자바의 모든 문자들은 Unicode를 사용합니다. 정수로 변환될 수 있으며, 더하고 빼는 거와 같은 연산도 가능합니다. 정수 형태가 아닌 문자 리터럴을 표시하고자 할 때는 단일 인용 부호(' ')를 사용합니다. 유니코드나 직접 입력이 불가능한 문자들에 대해서는 역슬래쉬( \ )를 이용하여 표시할 수 있습니다.

char a = 'L';
char b = "삶";
char c = \uae00;(유니코드값)

https://user-images.githubusercontent.com/48986787/104325771-56e98a80-552c-11eb-859e-c3d274ef4093.png

문자열 리터럴

문자열 리터럴을 표시하고자 할 때는 이중 인용 부호("")를 사용합니다.

String str = "안녕?";

문자열 리터럴은 기본(프리미티브) 타입이 아닙니다.

null 리터럴

레퍼런스 타입에 대입해서 사용합니다. (기본 타입에는 사용불가)

int a = null; // 에러가 난다. 
String str = null;
str = "JAVA";

 

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

변수 선언

변수를 선언한다는 것은 스택의 공간을 사용하겠다는 의미로 해석할 수 있습니다.

int a;

위의 코드가 변수를 선언한 것인데, 해석을 하면 스택에 int 타입(4byte)의 공간을 확보하고, 그 공간의 이름을 a로 지정한다는 것입니다.

기본형 타입은 쉽게 생각할 수 있지만, 레퍼런스 타입은 어떨까요?

class Student{
    int age;
    String name;
}

위와 같은 클래스가 있다고 가정할 때,

Student student; 

위와 같이 변수를 선언하면, 이는 스택에 변수(객체가 생성될 주솟값을 담을 공간)가 저장된다고 볼 수 있습니다.

초기화 방법

int a;
System.out.print(a); // 에러 발생
a = 10;
System.out.print(a); // 10 출력

변수 a가 선언되고, 초기화 되기 전까지는 변수에 쓰레기 값이 들어가게 됩니다. 이때 사용하려 하면

17:37 java: variable a might not have been initialized

의 에러가 발생합니다.

a = 10과 같이 초기화를 진행하면 변수에 원하는 값(10)이 들어가게 됩니다.

레퍼런스 타입도 마찬가지로 아래와 같이 초기화를 진행하면 앞서 살펴봤듯이 스택에 변수가 객체는 힙에 저장될 것입니다.

Student student;
System.out.print(student); // 에러 발생
student = new Student();
System.out.print(student); // 객체의 주솟값 출력 

 

변수의 스코프(범위, 영역)와 라이프타임

변수의 스코프변수를 사용할 수 있는 영역의 범위를 뜻합니다. 즉, 변수에 대한 접근과 변수가 존재할 수 있는 영역을 의미합니다.

중괄호 {}로 영역을 설정하면, 이 영역에 관한 스코프를 형성하게 됩니다.
선언된 영역, 즉 {}()를 벗어나면 소멸됩니다.

라이프 타임은 변수가 메모리에 얼마나 존재할 수 있는지를 의미합니다.

변수의 스코프와 라이프타임을 설정하는 것은 "변수가 정의되는 방법과 위치입니다."

좀더 단순히 말하면, 변수의 스코프에 대한 일반적인 컨셉은 선언된 블록(중괄호 {})내에서만 엑세스 할 수 있다는 것입니다.

변수의 종류와 스코프

  1. Instance Variables(인스턴스 변수)
  2. Class Variables(클래스 변수)
  3. Local Variables(지역 변수)

변수의 종류에 따른 스코프와 라이프타임을 살펴보겠습니다.

Instance Variables(인스턴스 변수)

클래스 안에서 선언되지만, 클래스 안의 method나 block(중괄호) 외부에 선언되는 변수

Scope: static method를 제외한 클래스 전체

Lifetime: 클래스를 인스턴스화한 객체가 메모리에서 사라질 때 까지

Class Variables(클래스 변수)

클래스 안에서 static으로 선언되고, 클래스 안의 method나 block(중괄호) 외부에 선언되는 변수

Scope: 클래스 전체

Lifetime: 프로그램이 종료될 때 까지

Local Variables(지역 변수)

인스턴스, 클래스 변수가 아닌 모든 변수

Scope: 변수가 선언된 block(중괄호)

Lifetime: 프로그램 control이 변수가 선언된 block을 떠날 때 까지

public class scope_and_lifetime {
    int num1, num2;   //Instance Variables
    static int result;  //Class Variable
    int add(int a, int b){  //Local Variables
                int c = 0; //local Variables
        num1 = a;
        num2 = b;
        return a+b;
    }
    public static void main(String args[]){
        scope_and_lifetime ob = new scope_and_lifetime();
        result = ob.add(10, 20);
        System.out.println("Sum = " + result);
    }
}

중첩된 스코프

외부 block의 모든 변수는 내부 block에서 액세스 할 수 있지만 내부 block 내의 변수는 외부 block에서 액세스 할 수 없습니다.

public class scope_and_lifetime {
    public static void main(String args[]){
        int a = 10;
        //외부 Block
        {
            //내부 Block
            int x = a;
            {
                int y = x;
            }
        }
        System.out.println(y);    //에러 발생
    }
}

 

요약

https://user-images.githubusercontent.com/48986787/104427120-3968ff00-55c6-11eb-99fc-6cf46cc4a4a6.png

 

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

한 데이터 유형의 값을 다른 데이터 유형에 할당하면 두 유형이 서로 호환되지 않을 수 있습니다. 서로 호환되는 경우 Java는 자동 타입 변환을 수행하고, 그렇지 않으면 강제 타입 변환(캐스팅)을 수행합니다.

자바에는 두 종류의 타임 변환이 있습니다.

  • 자동 타입 변환 (묵시적)
  • 강제 타입 변환 (명시적), 캐스팅

자동 타입 변환 (묵시적)

프로그램 실행 도중에 자동으로 타입 변환이 일어남.

작은 크기를 가지는 타입이 큰 크기를 가지는 타입에 저장될 때 발생합니다.

ex) 큰 크기 타입 = 작은 크기 타입

타입별 크기 순서

byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)

float은 표현 범위가 더 크기 때문에 더 큰 타입으로 들어갑니다.

char 타입이 int 타입으로 변환되면 유니코드 값이 들어갑니다.

하지만, 음수가 저장 될 수 있는 byte, int 등의 타입은 char 타입으로 자동 타입 변환 될 수 없습니다.

char charVal = 'A';
int intVal = charVal; // 65저장 됨
byte byteTest = 65;
char charTest = byteTest;    // 컴파일 에러
charTest = (char)byteTest    // 강제 타입 변환은 가능
class Test 
{ 
    public static void main(String[] args) 
    { 
        int i = 100;  

        // 자동 타입 변환
        long l = i;  

        // 자동 타입 변환 
        float f = l;  
        System.out.println("Int value "+i);    // Int value 100
        System.out.println("Long value "+l);    // Long value 100
        System.out.println("Float value "+f);     // Float value 100.0
    } 
}

강제 타입 변환 (명시적), 캐스팅

큰 크기 타입은 작은 타입으로 자동 타입 변환을 할 수 없습니다.

하지만 강제로(캐스팅) 작은 타입에 저장할 수 있습니다.

ex) 작은 크기 타입 = (작은 크기 타입)큰 크기 타입

class Test 
{ 
    public static void main(String[] args) 
    { 
        double d = 100.04;  

        // 강제 타입 변환
        long l = (long)d; 

        // 강제 타입 변환
        int i = (int)l;  
        System.out.println("Double value "+d);    // Double vlaue 100.04

        // 소수점 이하 값이 사라짐
        System.out.println("Long value "+l);      // Long value 100

        // 소수점 이하 값이 사라짐
        System.out.println("Int value "+i);      // Int value 100
    }  
}

원래 값은 보존되지 못합니다. 다만 큰 크기 타입이 작은크기 타입의 크기 내의 값을 가지면 값이 보존됩니다.

그렇기 때문에 값의 손실이 일어나지 않는지 주의가 필요합니다.

연산식에서의 타입 프로모션(승격)

연산을 수행하는 동안 중간 값이 피연산자 범위를 초가 할 수 있으므로 크기가 큰 타입으로 자동 타입 변환된 후 연산을 수행합니다. 이를 타입 프로모션(승격)이라 합니다.

타입 프로모션이 수행되기위한 조건은

  1. byte, short char 연산자를 자동으로 int로 프로모션(승격)합니다.
  2. 한 연산자가 long, float 또는 double이면 전체 표현식이 long, float 또는 double로 프로모션(승격) 됩니다.
class Test 
{  
    public static void main(String args[])  
    {  
        byte b = 42;  
        char c = 'a';  
        short s = 1024; 
        int i = 50000; 
        float f = 5.67f; 
        double d = .1234; 

        // 연산식
        double result = (f * b) + (i / c) - (d * s); 

        // double로 타입 프로모션 됨
        System.out.println("result = " + result);    //Result = 626.7784146484375
    }  
}

연산식에서의 타입 캐스팅

연산을 수행하는 동안, 결과는 자동적으로 크기가 큰 타입으로 저장됩니다. 하지만 그 결과를 크기가 작은 타입으로 저쟝하려하면 컴파일 타임 오류가 발생함으로, 결과를 타입 캐스팅 해야합니다.

class Test 
{  
    public static void main(String args[])  
    {  
        int a = 50;  

        // double에서 int로 타입 캐스팅을 진행함 
        a = (int)(a * 2,0);  
        System.out.println(a);    // 100
    } 
}

1차 및 2차 배열 선언하기

배열이란 동일한 타입의 데이터를 연속된 공간에 저장하여 하나의 변수에 묶어서 관리하기 위한 자료구조입니다.

배열의 선언 및 초기화

배열을 선언할 때 참조변수만 먼저 선언하여 크기 및 값을 이후에 초기화 하는 방법과 최초 선언시 배열의 크기 및 값을 할당해주는 방법이 있습니다.

배열을 선언하기 위해서 []를 사용합니다.

1차 배열의 선언

int[] arr1;
int arr2[];

1차 배열의 초기화

int[] arr1;
arr1 = new int[5];

int[] arr2 = new int[10];    // 선언과 초기화를 함께 

arr2는 "int 타입의 연속된 10개의 데이터를 저장하는 공간을 arr2라는 이름으로 정의 하겠다"로 해석할 수 있습니다.

1차 배열의 값 할당

int[] arr1 = new int[3];    // 선언과 초기화를 함께 
arr1[0] = 1;   // 배열에 값 할당
arr1[1] = 2;
arr1[2] = 3;

위의 코드는

int[] arr3 = {1, 2, 3};    // 선언과 값을 할당

와 동일합니다.

또한 메소드의 파라미터로 바로 사용될 경우

someMethod(new int[]{1,2,3});

위와 같이 사용할 수 있습니다.

2차 배열의 선언

1차배열과 유사하므로 사용 예만 보도록 하겠습니다.

int[][] arr1;    // 2차 배열 선언
arr1 = new int[2][2];    // 2차배열 초기화

int[][] arr2 = new int[2][2];    // 선언과 초기화를 함께
int[][] arr3 = {{1, 2, 3}, {4, 5, 6}};    // 선언과 값을 할당

someMethod(new int[][]{{1, 2, 3}, {4, 5, 6}});

 

타입 추론(Type Inference), var

타입 추론(Type Inference)란 코드 작성 당시 타입이 정해지지 않았지만, 컴파일러가 그 타입을 유추하고 결정하는 것입니다.

타입추론과 제네릭 메서드(Generic Methods)

public class BoxDemo {

  public static <U> void addBox(U u, java.util.List<Box<U>> boxes) {
    Box<U> box = new Box<>();
    box.set(u);
    boxes.add(box);
  }

  public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
    int counter = 0;
    for (Box<U> box: boxes) {
      U boxContents = box.get();
      System.out.println("Box #" + counter + " contains [" +
             boxContents.toString() + "]");
      counter++;
    }
  }

  public static void main(String[] args) {
    java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
      new java.util.ArrayList<>();
    BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
    BoxDemo.outputBoxes(listOfIntegerBoxes);
  }
}

#outputs
Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]

제너릭 메서드인 addBox는 U라는 이름의 파라미터를 선언합니다().

일반적으로 Java 컴파일러는 제네릭 메소드 호출시, 파라미터(매개변수)를 유추할 수 있습니다. 따라서 대부분의 경우 파라미터를 지정할 필요가 없습니다.

예를 들어, 제네릭 메소드인 addBox를 호출하려면 아래와 같이 파라미터를 지정할 수 있습니다.

BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);

또한 이를 생략하면, 메서드의 인수에서 변수가 정수임을 자동으로 추론합니다.

BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);

타입추론과 제네릭 클래스(Generic Classes)

생성자 호출시에 필요한 아규먼트(전달인자)를 다이아몬드 연산자(<>)을 사용함으로 생략할 수 있습니다.

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

위와 같이 아규먼트(전달인자)를 선언할 수 있지만

Map<String, List<String>> myMap = new HashMap<>();

위와 같이, 파라미터가 있는 생성자의 형식을 빈 파라미터(매개변수) 집합(<>)으로 대체할 수 있습니다.

타입추론과 제네릭, 비 제네릭 클래스의 제네릭 생성자(Generic Constructors)

생성자는 제네릭, 비 제네릭 클래스 모두에서 제네릭(고유한 파라미터를 선언함)이 될 수 잇습니다.

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

인스턴스화는 아래와 같이 할 수 있습니다.

new MyClass<Integer>("")

위의 문장은 MyClass유형의 인스턴스를 만듬을 의미합니다. 즉, MyClass의 X에 대해 Integer 형식을 명시적으로 지정한 것입니다.

MyClass 생성자에는 파라미터 T가 포함되어 있습니다. 그렇기 때문에, 위의 문장 처럼 인스턴스화 시, T에 대해 String 형식을 유추합니다. (전달인자가 ""이므로, 이 생성자의 실제 파라미터는 String이기 때문)

MyClass<Integer> myObject = new MyClass<>("");

위와 같은 선언을 통해 전달인자를 생략 할 수 있습니다. 이 예제에서 컴파일러는 제네릭 클래스 MyClass의 파라미터 X에 대해 Integer형식을 유추하고, 제네릭 클래스 생성자의 파라미터 T에 대해 String을 유추합니다.

Var

Java 10이상 부터 지원

이 키워드는 local variable이면서 선언과 동시에 initializer가 필수적으로 요구됩니다.

자바 9이하 버전에서는 아래와 같이 데이터 타입을 지정해야합니다.

String message = "Good bye, Java 9";

하지만 자바 10 이상부터는

데이터 타입을 지정하지 않아도, Java 컴파일러가 오른쪽에 있는 데이터 유형에 따라 타입을 유추합니다.

var message = "Hello, Java 10";

또한 아래와 같이 쓸 수 있습니다.

// 자바 9 이하
Map<Integer, String> map = new HashMap<>();

// 자바 10 이상
var idToNameMap = new HashMap<Integer, String>();

Var을 잘못 쓴 경우

  1. 초기화 없이 사용하는경우
  2. var n; // error: cannot use 'var' on variable without initializer
  3. null로 초기화 하려는 경우
  4. var emptyList = null; // error: variable initializer is 'null'
  5. 지역변수가 아닌경우
  6. public var = "hello"; // error: 'var' is not allowed here
  7. 람다 표현식에서 사용하려는 경우( Lambda 표현식에는 명시적인 타입이 필요하므로 var를 사용할 수 없습니다 )
  8. var p = (String s) -> s.length() > 10; // error: lambda expression needs an explicit target-type
  9. 배열 초기화에 사용하려는 경우( 위와 같이 명시적인 타입 필요)
  10. var arr = { 1, 2, 3 }; // error: array initializer needs an explicit target-type
  11. 자바 7부터 지원한 다이아몬드 연산자(<>)만 쓸경우
  12. var messages = new ArrayList<>();

등이 있습니다.

 

Refer.

댓글