[2주차] 자바 데이터 타입, 변수, 그리고 배열

목표

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

학습할 것

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

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

정수형

  • 부호를 가지고 있으며, 소수 부분이 없는 수
  • 타입이 표현할 수 있는 범위를 벗어난 데이터를 저장하면, 오버플로우(overflow)가 발생
    • 전혀 다른 값이 저장될 수 있음
  • 기본값: 0 (long: 0L)
타입크기 (byte)데이터 표현 범위
byte1-128 ~ 127
short2-215 ~ (215 - 1)
-32,768 ~ 32,767
int4-231 ~ (231 - 1)
-32,768 ~ 32,767
long8-263 ~ (263 - 1)
-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

실수형

  • 소수부나 지수부가 있는 수
  • 정수보다 훨씬 더 넓은 표현 범위를 가짐
  • 기본값
    • float: 0.0F(or f)
    • double: 0.0(D or d)
타입지수 길이가수 길이유효 자릿수
float823소수 부분 6자리까지 오차없이 표현
double1152소수 부분 15자리까지 오차없이 표현

타입크기 (byte)데이터 표현 범위리터럴 타입 접미사
float4(3.4 X 10-38) ~ (3.4 X 1038)F 또는 f
double8(1.7 X 10-308) ~ (1.7 X 10308)D 또는 d (생략 가능)

문자형

  • 작은 정수나 문자 하나를 표현
  • 유니코드(unicode)를 사용하여 문자 표현
  • 기본값: \u0000
타입크기 (byte)데이터 표현 범위
charunsigned 20 ~ 216
\u0000 ~ \uffff

논리형

  • 참(true)이나 거짓(false) 중 한 가지 값만을 가짐
  • 기본값: false
타입크기 (byte)데이터 표현 범위
boolean1true 또는 false

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

프리미티브 타입

  • 실제 데이터 값(리터럴)을 담는 타입
    • 값은 Stack 메모리에 저장
  • 기본값이 있음
  • 비객체 타입이므로 null이 없음
    • 기본형 타입에 null을 넣고 싶으면 레퍼런스 타입 사용
  • 문자열 -> 기본형으로 변환할 때 parseType 메서드 사용
    • 레퍼런스 타입의 valueOf 메서드 보다 조금 더 빠름

레퍼런스 타입

  • java.lang.Object를 상속 받으면 참조 타입
    • 기본 자료형 외에는 모두 참조 타입
  • 문자열 -> 기본형으로 변환할 때 valueOf 메서드 사용
  • 값이 저장되어 있는 곳의 주소값을 저장
    • 주소값은 Heap 메모리에 저장
    • 4byte 의 같은 크기를 가짐
  • Wrapper Class
    • 8개의 기본 타입에 해당하는 데이터를 객체로 포장해 주는 클래스
    • void의 래퍼 클래스인 Void도 존재

리터럴

변수에 저장될 수 있는 데이터 자체를 의미

정수 리터럴

  • 정수형 숫자
  • 정수형 숫자가 L 또는 l로 끝나는 형태
    • l은 숫자 1과 구분이 어려우므로 L 을 사용하는 것이 좋음
  • byte, short, int, long 타입은 int 리터럴로 생성
  • int 범위를 벗어나는 값은 long 리터럴로 생성

실수 리터럴

  • double: 실수형 숫자 (+ D 또는 d로 끝날 경우)
  • float: 실수형 숫자가 F 또는 f로 끝나는 형태
double d1 = 123.4;
// 지수 표기법
double d2 = 1.234e2;
float f1 = 123.4f;

숫자 리터럴에서의 밑줄문자(_, underscore) 사용

  • 자바7부터 추가된 기능
  • 숫자 리터럴을 읽기 쉽게 해줌
  • 자릿수가 긴 리터럴을 표현할 때 유용
  • 숫자 사이에만 밑줄을 넣을 수 있음
  • 연속 입력 가능

다음 위치에는 넣을 수 없음

  • 숫자의 시작 또는 끝
  • 부동 소수점 리터럴에서 소수점에 붙은 경우
  • 뒤에 붙는 F, L, D등의 문자 앞 뒤에 붙은 경우
  • 문자열에서 문자가 올 것으로 예상되는 위치
void 밑줄_사용한_숫자_표현() {
    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;
}

문자 및 문자열 리터럴

  • 유니코드로 표현
  • 직접 해당 문자를 코드에 입력 가능
  • 유니코드 이스케이프('\u0000') 사용 가능

char 리터럴: 작은 따옴표(') 사용
String 리터럴: 큰 따옴표(") 사용

이스케이프 문자: \ (백슬래시)

  • \b(백 스페이스)
  • \t(탭)
  • \n(라인 피드)
  • \f(폼 피드)
  • \r(캐리지 리턴)
  • \"
  • \'
  • \\

불린 리터럴

  • true
  • false

그 외 리터럴

  • null
  • Class
    • <TypeName>.class 형태로 표현
    • Class 타입 자체를 나타내는 객체의 참조를 반환

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

// 변수 선언
// <변수 타입> <변수명>;
int a;
char b;

 
// 변수 선언 및 초기화
// <변수 타입> <변수명> = 초기화할 값;
int a = 1;
long b = 10L;
boolean c = true;
float f = 3.14f;
double d = 8.8;
String str = "Java";

인스턴스 변수

  • 클래스 내부에 static 키워드 없이 선언 된 필드
  • 인스턴스 별로 다른 값을 가짐

클래스 변수 (static)

  • static 키워드로 선언 된 필드
  • 모든 인스턴스가 값을 공유
  • 여러 개의 인스턴스가 생성되더라도 static 변수는 하나만 생성

로컬 변수

  • 메서드 내부에 선언되고 사용되는 임시 변수
  • 인스턴스 변수 선언과 유사 (ex: int count = 0;)
  • 로컬 변수는 선언 된 메서드 내부에서만 사용할 수 있음
  • 다른 클래스에서 접근 불가

매개 변수

  • 메서드의 인자로 전달되는 변수

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

Instance Variable

스코프: static 메서드를 제외한 클래스 전체
라이프타임: 인스턴스가 메모리에 있는 동안

  • 클래스 내부에 선언되었지만 메서드 및 블록 외부에 선언 된 변수
  • 클래스 내에 선언한 변수는 클래스 내 어디서나 접근 가능
  • private 변수의 경우 클래스 외부에서 접근 불가
  • public 변수의 경우 생성된 인스턴스를 통해 외부에서 접근 가능
public class ClassScopeExample {
    private Integer amount = 0;
    public Integer qty = 0;
    
    public void exampleMethod() {
        amount++;
    }
    public void anotherExampleMethod() {
        Integer anotherAmount = amount + 4;
    }
}

// 클래스 외부
ClassScopeExample classScope = new ClassScopeExample();
classScope.qty = 1;  // classScope.amount 는 접근 불가

Class Variable (static)

스코프: 클래스 전체
라이프타임: 프로그램 실행부터 종료까지

  • 클래스 내부, 모든 블록 외부에서 선언되고 static으로 선언
  • static 변수는 instantName.variableName 으로 사용하기 보다는ClassName.StaticVariableName 으로 사용
String name = ClassScopeExample.NAME;

Local Variable

스코프: 변수가 선언된 블록 내부
라이프타임: 변수가 선언된 블록을 벗어날 때까지

  • 인스턴스 또는 클래스 변수가 아닌 모든 변수
  • Method Vriable
    • 메소드 내에서만 접근 가능
    • 다른 메소드에서 정의한 변수에 접근하려고 하면 컴파일 에러 발생
    • 함수 호출 시 스택(Stack) 메모리에 생성
public class MethodScopeExample {
    public void methodA() {
        Integer area = 2;
    }
    public void methodB() {
        // compiler error, area cannot be resolved to a variable
        area = area + 2;
    }
}
  • Loop Variable
    • Loop 내에서만 접근 가능
    • Loop 밖에서 접근하려고 하면 컴파일 에러 발생
public class LoopScopeExample {
    List<String> listOfNames = Arrays.asList("Joe", "Susan", "Pattrick");
    public void iterationOfNames() {
        String allNames = "";
        for (String name : listOfNames) {
            allNames = allNames + " " + name;
        }
        // compiler error, name cannot be resolved to a variable
        String lastNameUsed = name;
    }
}

// iterationOfNames 메소드에 name이라는 메소드 변수는
// 루프 내부에서만 사용할 수 있으며 외부에서는 사용 불가
  • Bracket Scope
    • 괄호 안에서만 접근
    • 괄호 밖에서 접근하려고 하면 컴파일 에러 발생
public class BracketScopeExample {    
    public void mathOperationExample() {
        Integer sum = 0;
        {
            Integer number = 2;
            sum = sum + number;
        }
        // compiler error, number cannot be solved as a variable
        number++;
    }
}

// 변수 number는 괄호 안에서만 유효
  • Scopes and Variable Shadowing
public class NestedScopesExample {
    String title = "Java Study";
    public void printTitle() {
        System.out.println(title); // Java Study
        String title = "Max";
        System.out.println(title); // Max
    }
}

// title 클래스 변수에 액세스하려면 this.title과 같은 this 접두사를 사용하는 것이 좋음

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

타입 변환

  • 변수나 리터럴의 타입을 다른 타입으로 변환하는 것
    • e.g., int 타입의 리터럴과 float 타입의 리터럴을 연산하기 위해서는 int 타입이든, float 타입이든 하나의 타입으로 일치시킨 후 연산해야 함

타입 캐스팅

  • 명시적 형 변환: 큰 데이터 타입에서 작은 데이터 타입으로 형 변환
  • 값 손실이 일어날 수 있음
    • e.g., 4byte int 300 > casting > 1byte byte 44
      • int는 32bit, byte는 8bit의 크기를 갖고 있음
      • int 300 의 2진수 표현: 00000000 00000000 00000001 00101100
      • int 300의 2진수 표현에서 변환할 타입의 크기의 bit만 남기고 나머지는 잘려나감
      • i.e., 2진수 표현한 300을 byte로 캐스팅했을 때: 00101100
      • byte로 캐스팅된 2진수의 값을 10진수로 변환하면: 44라는 값이 나옴
    • 정수형 > 실수형 변환 시 소수점 이하 값은 버림 처리
  • 프리미티브 타입에서 boolean을 제외한 나머지 타입들은 서로 형변환 가능
  • 프리미티브 타입과 레퍼런스 타입 간의 형변환 불가능
// 타입 캐스팅
double d = 123.45
int score = (int) d

타입 프로모션

  • 묵시적 형 변환: 작은 데이터 타입에서 큰 데이터 타입으로 형 변환
  • 정수 리터럴 > 문자 리터럴 변환 불가

e.g.,

// byte 타입 변수 byteValue에 정수 리터럴 65 저장
byte byteValue = 65;

// char 타입 변수 charValue에 byteValue 값 저장
char charValue = byteValue;  // 컴파일 에러 발생

byte: 1byte / char: 2byte
크기만 봐서는 byte > char 자동 형 변환이 가능할 것 같지만 char 타입은 unsigned이며 데이터 표현 범위는 0~65535로 음수 저장 불가.
따라서 char 타입은 음수 저장이 불가능 하기 때문에 음수가 저장될 수 있는 byte 타입에서 char 타입으로 자동 형변환(Promotion) 할 수 없음

// 타입 프로모션
int score = 100
double d = score

1차 및 2차 배열 선언하기

1차원 배열(One Dimensional Array)

선언

// 타입[] 배열이름;
Integer[] integers;
String[] strArray;

// 타입 배열이름[];
Double example[];
Char chars[];

두 가지 방법 모두 사용 가능하지만 첫 번째 방법만 사용 권장

생성

// 배열이름 = new 타입[배열길이];
integers = new Integer[3];
chars = new Char[5];

선언과 동시에 초기화

// 타입[] 배열이름 = {배열요소1, 배열요소2, ...};
Byte[] bytes = {1, 2, 3, ...};

// 타입[] 배열이름 = new 타입[]{배열요소1, 배열요소2, ...};
String[] strings = new String[]{"apple", "banana", "carrot", ...};

위의 두 방식은 같은 결과를 반환
다음의 경우 두 번째 방법만 사용 가능
1. 배열 선언과 초기화를 따로 진행해야 할 경우
2. 메소드의 인수로 배열을 전달하면서 초기화해야 할 경우


int[] grade1 = {70, 90, 80};          // 배열의 선언과 동시에 초기화할 수 있음.
int[] grade2 = new int[]{70, 90, 80}; // 배열의 선언과 동시에 초기화할 수 있음.
int[] grade3;
// grade3 = {70, 90, 80};             // 이미 선언된 배열을 이 방법으로 초기화하면 오류가 발생함.
int[] grade4;
grade4 = new int[]{70, 90, 80};       // 이미 선언된 배열은 이 방법으로만 초기화할 수 있음.

2차원 배열(Two Dimensional Array)

  • 배열의 요소로 1차원 배열을 가지는 배열
  • 자바에서는 2차원 배열을 나타내는 타입을 따로 제공하지 않음
  • 대신 1차원 배열의 배열 요소로 또 다른 1차원 배열을 사용하여 2차원 배열을 나타낼 수 있음
// 타입[][] 배열이름;
String[][] strings;

// 타입 배열이름[][];
Float floats[][];

// 타입[] 배열이름[];
Integer[] integers[];


선언과 동시에 초기화

/*
타입 배열이름[열의길이][행의길이] = {
    {배열요소[0][0], 배열요소[0][1], ...},
    {배열요소[1][0], 배열요소[1][1], ...},
    {배열요소[2][0], 배열요소[2][1], ...},
    ...
};
*/

int[][] arr = {
    {10, 20, 30},
    {40, 50, 60}
};

가변 배열(Dynamic Array)

  • 행마다 다른 길이의 배열을 저장할 수 있는 배열
  • 2차원 배열을 생성할 때 열의 길이를 명시하지 않으면 행마다 다른 길이의 배열을 요소로 저장할 수 있음
int[][] arr = new int[3][];
arr[0] = new int[2];
arr[1] = new int[4];
arr[2] = new int[1];

선언과 동시에 초기화

int[][] arr = {
    {10, 20},
    {10, 20, 30, 40},
    {10}
};

타입 추론, var

타입추론

  • 코드 작성 당시 타입이 정해지지 않았지만, 컴파일러가 그 타입을 유추하는 것

var

  • 자바 10부터 추가된 타입 추론 키워드
  • 지역 변수로만 사용할 수 있고 선언과 동시에 초기화 필수
  • var를 사용할 때 런타임 오버헤드 없음
  • 변수의 유형은 컴파일 시간에 유추되며 나중에 변경할 수 없음
  • 초기화 된 지역 변수에만 사용 가능
  • 멤버 변수, 메서드 매개 변수, 반환 형식 등에 사용 불가
  • 컴파일러가 형식을 유추할 수 없으면 초기화 필요
  • var 를 함수나 변수 이름으로 사용할 수 있음 -> 이전 버전과의 호환성을 보장함
// ~ Java 9
String message = "Good bye, Java 9";

// Java 10 ~
@Test
public void whenVarInitWithString_thenGetStringTypeVar() {
    var message = "Hello, Java 10";
    assertTrue(message instanceof String);
}

// var 를 함수나 변수 이름으로 사용할 수 있음 -> 이전 버전과의 호환성을 보장함
public void varIsNotAKeyword() {
    // Integer var = 3;
    var test = 3;
}

boilerplate 코드를 작성하지 않아도 됨

Map<Integer, String> map = new HashMap<>();

위 코드를 다음과 같이 다시 작성할 수 있음

var idToNameMap = new HashMap<Integer, String>();

변수의 타입보다 변수 이름에 집중할 수 있음 -> 변수 네이밍이 잘 돼 있으면 소스 코드 파악에 더 좋을 수 있음

var를 사용할 수 없는 경우

// 초기화하지 않을 경우
var n; // error: cannot use 'var' on variable without initializer

// null 로 초기화 하는 경우
var emptyList = null; // error: variable initializer is 'null'

// 지역 변수가 아닌 경우
public var = "hello"; // error: 'var' is not allowed here

// 람다표현식에서는 명시적 타입이 필요하므로 사용할 수 없음
var p = (String s) -> s.length() > 10; // error: lambda expression needs an explicit target-type

// 배열 초기화에도 마찬가지로 명시적 타입이 필요하므로 사용할 수 없음
var arr = { 1, 2, 3 }; // error: array initializer needs an explicit target-type

코드의 가독성이 떨어질 수 있음

var result = obj.prcoess();

위 코드는 var 를 사용하는 데 문제는 없지만 process() 의 리턴 타입을 이해하기 어려워져 코드의 가독성이 떨어짐

예상과 다른 결과가 발생할 수 있음

  • Java 7에 도입된 다이아몬드 연산자와 함께 사용하는 경우:
var empList = new ArrayList<>();

empListList<Employee> empList = new ArrayList<>(); 처럼 사용하고 싶다면 명시적으로 타입을 적어줘야함

var empList = new ArrayList<Employee>();
  • 익명 클래스 인스턴스와 함께 var 를 사용하는 경우:
@Test
public void whenVarInitWithAnonymous_thenGetAnonymousType() {
    var obj = new Object() {};
    assertFalse(obj.getClass().equals(Object.class));
}

이제 다른 Objectobj 에 할당하려고 하면 컴파일 오류 발생함

obj = new Object(); // error: Object cannot be converted to <anonymous Object>

추론된 유형의 obj 객체가 아니기 때문


참조


각주

  • 오버헤드(overhead)
    • 기존 A메서드 소요시간 10초. A메서드에 안정성을 고려하여 작업한 B메서드 소요시간 15초 -> 오버헤드 5초 라고 함
    • B메서드를 개선한 C메서드의 소요시간 12초 -> 오버헤드가 3초 단축되었다고 함

좋은 웹페이지 즐겨찾기