Post

[스터디 할래] week02. 자바 데이터 타입, 변수 그리고 배열

백기선님의 스터디 “스터디 할래”를 참고로 한 정리입니다.

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

학습할 것

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

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

논리형, 숫자형, 정수형, 실수형이 있으며 값을 할당할 때 변수의 주소 값에 값이 저장되는 데이터 타입이다. 해당 데이터 타입은 값이 할당되며 JVM의 Runtime Data Area 영역의 Stack 에 값이 저장된다.

분류타입기본값값의 범위값의 크기
정수형byte0-128 ~ 1271byte
 short0-32,768 ~ 32,7672byte
 int0-2,147.. ~ 2.147..4byte
 long0L- 9,223.. ~ 9.223..8byte
실수형float0.0f(3.4 X 10-38) ~ (3.4 X 10.38)8byte
 double0.0(1.7 X 10-308) ~ (1.7 X 10308)8byte
문자형char‘\u0000’‘\u0000’ ~ ‘\uffff’2byte
논리형booleanfalsetrue, false1byte


대략 21억이 넘는 값을 int에 담고 싶을 땐 어떻게 할까?

  • 자바8부터 unsigned int가 생겼다.
    1
    2
    
    int unsigned = Integer.parseUnsignedInt("2200000000");
    System.out.println(Integer.toUnsignedString(unsigned));
    
  • 혹은 BingInteger를 사용할 수도 있다.
    1
    
    BigInteger bigInteger = BigInteger.valueOf(220000000L);
    

float, double의 문제점

1
2
3
4
5
6
float number = 0f;
for(int i=0; i < 10; i++) {
    number += 0.1f;
}
System.out.println(number);
// 실제 결과 : 1.0000001
  • float과 double을 쓸 때 부동소수점이 정확하지 않으므로 정확한 계산이 필요한 경우 BigDecimal을 사용한다.
1
2
3
4
5
6
7
BigDecimal number = BigDecimal.ZERO;
for(int i=0; i < 10; i++) {
    number = number.add(BigDecimal.valudOf(0.1));   // 객체 내용이 바뀌는게 아니라 
                                                    // 새로운 객체를 리턴하므로 다시 받아줘야한다.
}
System.out.println(number);
// 실제 결과 : 1.0

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

프리미티브 타입

  • 기본형 타입 또는 원시 타입이라고 한다.
  • 정수, 실수, 문자, 논리, 리터럴 등의 실제 데이터 값을 저장한다.
  • 기본값이 있으므로 null이 존재하지 않는다.
  • 값의 범위를 벗어나면 컴파일 에러가 발생한다.

레퍼런스 타입

  • 참조 타입이라고 한다.
  • class, interface, enum, array, String 타입 등이 있으며 프리미티브 타입을 제외한 모든 타입은 참조형 타입이다.
  • null이 존재한다.
1
2
String name = "myname";
int age = 3;

레퍼런스 타입의 name 변수와 프리미티브 타입의 age 변수는 런타임 스택 영역에 생성된다. 그리고 레퍼런스 타입의 값인 주소값과 프리미티브 타입의 값인 3 또한 런타임 스택 영역에 저장된다.

이 때 레퍼런스 타입의 값인 주소 값이 가리키는 실제 값은 가비지 컬렉션 힙 영역에 객체가 생성되어 값 복사 시 실제 값이 아닌 주소값이 복사되기 때문에 주의해야한다.

값에 의한 복사인지 아닌지에 따라 얕은 복사와 깊은 복사로 나뉜다.

  • 얕은 복사 : 주소값을 복사하여 동일한 가비지 컬렉션 힙 영역의 객체를 참조한다.
  • 깊은 복사 : 프리미티브 타입에서의 값에 의한 복사처럼 똑같은 새로운 객체를 만들어 복사한다.

리터럴

  • 직접 입력된 데이터 자체를 의미한다. 변수에 넣는 변하지 않는 데이터이다.
  • 정수, 실수, 문자, 논리, 문자열 리터럴이 존재한다.
1
2
3
4
5
boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;

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

변수 선언과 초기화란?

변수를 선언하면 메모리에 변수의 저장 공간이 확보되어 있지만 이 공간에 어떠한 값이 저장되어 있을지는 알 수 없다. 그렇기 때문에 초기화를 해줘야한다.

1
2
3
4
5
6
7
8
9
// 변수 선언 (초기화 하지 않았으므로 쓰레기 값이 들어있다.)
int a; 
float b;
char c;

// 변수 초기화
a = 5;
b = 3.14f;
c= 'C';
  • 멤버 변수 (클래스 변수, 인스턴스 변수)와 배열의 초기화는 선택적으로 할 수 있으나 지역 변수는 초기화하지 않으면 에러가 발생한다. ``` int a; int num = a; // 에러 X

public boid method() { int b; int c = b; // 에러 O }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---------------------------------
# 변수의 스코프와 라이프타임

#### 변수의 스코프란?
변수에 접근하거나 접근할 수 있는 유효 범위를 뜻한다.
#### 변수의 라이프타임
변수가 메모리에 살아있는 기간을 뜻한다.

스코프에 따른 변수의 종류는 **클래스 변수(static 변수), 인스턴스 변수, 지역 변수** 3가지로 나뉜다. 이는 변수의 **선언 위치**에 따라 결정된다.

| 변수의 종류 | 선언 위치 | 생성시기 |
|-----|-----|-----|
| 인스턴스 변수 | 클래스 영역 | 인스턴스가 생성되었을 때 |
| 클래스 변수 | 클래스 영역 | 클래스가 메모리에 올라갈 때 |
| 지역 변수 | 메서드 영역 | 변수 선언문이 수행될 때 |

### 1. 인스턴스 변수 (instance variable)
: 클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다. 그러므로 인스턴스 변수 값을 읽어오거나 저장하기 위해서는 먼저 인스턴스를 생성해야한다.

인스턴스 별로 별도의 저장공간을 확보하기에 인스턴스별 다른 값을 가질 수 있다.

### 2. 클래스 변수 (class variable)
: 멤버 변수에 static 키워드를 붙일 경우 클래스 변수가 되며 한 클래스의 모든 인스턴스가 값을 공유한다. 클래스 변수는 인스턴스를 생성하지 않고 클래스가 메모리에 올라갔을 때 선언되기 때문에 인스턴스에서는 언제든 바로 접근해서 사용이 가능하다. 그러므로 어디서나 접근할 수 있는 전역변수(global variable)의 성격을 가진다.

public class Hello {

1
2
3
4
private static int number = 1000;
public static void main(String[] args) {
    System.out.println(Hello.number);
} }  ```

3. 지역 변수 (local variable)

: 메서드 내에 선언되어 메서드 내에서만 사용이 가능하며 메서드가 종료될 때 같이 소멸된다.

1
2
3
4
5
6
7
8
9
10
public class Hello {

    public static void main(String args[]) {
        for (int i=0; i<10; i++) {
            System.out.println(i);
        }
        System.out.println(i); // Checked Exception 발생. 
                               // (변수가 메서드 내에서만 유효하므로)
    }
} 

static 내에서 인스턴스에 관한 것을 참조할 수 없다.

  • static이 올라오는 시점은 클래스 로딩하는 시점이고 인스턴스나 메서드가 만들어지는 시점은 인스턴스를 new 해서 만든 이후이므로..
1
2
3
4
5
6
7
public class Hello {

    private static int number = helloNumber(); // 참조할 수 없다.
    private int helloNumber() {
        return 10;
    }
} 
  • 반대로는 가능하다.
    1
    2
    3
    4
    5
    6
    7
    
    public class Hello {
    
      private static int number = 100;
      private int helloNumber() {
          return App.number;
      }
    } 
    

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

타입 변환

  • 선언된 하나의 타입을 다른 타입으로 변환하는 것을 뜻한다.
  • 자동 형변환(작은 타입 -> 큰 타입), 명시적 형변환(큰 타입 -> 작은 타입)이 있다.

자동 형변환 (= 묵시적 형변환, 확장)

  • 메모리 크기가 작은 데이터 타입의 값을 더 큰 범위의 타입에 할당할 경우 동작한다.
    1
    2
    3
    4
    5
    
    void 자동_형변환(){ 
      int a = 100; 
      long b = a; 
      float c = b; 
    }
    

명시적 형변환 (= 축소)

  • 메모리 크기가 큰 데이터 타입의 값을 더 작은 범위의 타입에 할당하기 위해서는 명시적 현변환을 해주어야한다. ``` void 명시적_형변환(){ int a = 88; char b = ‘c’; b = a; // b의 타입이 더 작기 때문에 컴파일 에러 발생 b = (char)a; // 정상적인 형변환 가능

    a = b; // a는 b보다 타입의 범위가 크므로 형변환 필요X }

1
2
3
## 캐스팅
- 크키가 더 큰 자료형을 더 작은 자료형에 대입할 때 자료형을 명시해서 강제로 넣는 것을 뜻한다. 이 때 데이터 손실이 발생한다.

void 캐스팅() { float a = 3.14f; int b = (int)a; }

1
2
3
## 프로모션
- 크기가 더 작은 자료형을 더 큰 자료형에 대입할 때 자동으로 형변환되는 현상이다.

void 프로모션() { int a = 10; float b = a; }

1
2
3
4
5
6
7
8
---------------------------------
# 1차 및 2차 배열 선언하기

## 베열
- **선형 자료구조** 중 하나로 동일한 타입의 연관된 데이터를 메모리에 연속적으로 저장하여 하나의 변수에 묶어서 관리하기 위한 자료구조이다.

### 1차 배열 선언

// 크기 할당 X, 초기화 X 선언 int[] arr; int arr[];

// 크기 할당 O, 초기화 X 선언 int[] arr = new int[5]; String[] arr = new String[5];

// 크기 할당 O, 초기화 O 선언 int[] arr = {1,2,3,4,5}; int[] arr = new int[] {1,2,3,4,5};

1
### 2차 배열 선언

int[][] arr = new int[5][5];

1
2
3
4
5
6
7
8
9
---------------------------------
# 타입 추론, var

참고 : https://dev.to/composite/java-10-var-3o67

## 타입추론
- 변수의 타입을 명시적으로 작성하지 않아도 컴파일러가 알아서 변수의 타입을 대입된 리터럴로 추론하는 것을 뜻한다.

public static void main(String[] args) { var str = “Hello world”;

1
2
3
if (str instanceof String) {
    System.out.println("str의 타입은 String입니다");
} } ``` 위와 같이 str의 타입을 var로 선언을 하면 컴파일러가 리터럴을 추론하고 str가 String일 것이라고 판단한다.

var

  • var는 자바10부터 도입되었고 자바11부터는 이를 통한 람다 타입 지원도 생겼다.
  • 컴파일러는 개발자가 입력한 초기화 값을 통해 타입을 유추하기 때문에 반드시 데이터를 초기화 해야한다.
  • var는 초기값이 있는 지역 변수로만 선언이 가능하다. 즉 멤버 변수, 필드 선언, 메서드의 파라미터, 리턴 타입으로는 사용할 수 없다.
  • var에는 null이 들어갈 수 없다.
  • 람다 표현식에서는 명시적인 타입을 지정해야한다.
    • var p = (String s) -> System.out.println("s =" + s);

어디서 var를 쓰는 것이 적절할까?

1. forEach

1
2
3
4
5
Person person; 클래스가 가능하다고 가정

for (var person : personList) {
    // ...
}

이렇게 작성하게 되면 IDE에서 var 키워드를 Person 클래스로 인식할 수 있고 컴파일 시에도 var 키워드를 Person으로 변환하게 된다. 이로인해 Object 타입으로 미리 단정짓지 않아도 된다.

2. 람다식

1
2
Consumer<Person> person = (@Nonnull var person) -> {}
    // @Nonnull 어노테이션에 의해 person 널체크부터 하게된다.

자바 11부터는 람다 인자에도 var를 넣을 수 있는데 람다의 경우 파라미터 어노테이션을 못 넣기 때문에 만약 어노테이션을 넣고 싶으면 따로 메서드로 빼던가 익명 클래스로 정의해야 했었다. 그러나 var를 사용하므로써 타입 추론의 유연성이 생기게 된다.

3. 익명 클래스

보통은 타입 추론이 어렵기 때문에 var 사용을 지양하지만 익명 클래스를 사용하므로써 이를 보완할 수 있다. (라고 하지만 잘 모르겠다..)

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 타입 추론이 어려움 -->
var intVal = 20;
var strVal = "string";
var list = new ArrayList<Integer>();

<!-- 익명클래스 사용 -->
var supply = new Supplier<String>() {
    @Override
    public String get() {
        // 
    }
}
This post is licensed under CC BY 4.0 by the author.