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

janjanee·2022년 8월 1일
0
post-thumbnail

2020.12.27 작성글 이전

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

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

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

값(data)이 저장될 공간의 크기와 저장형식을 정의한 것을 자로형(data type)이라고 한다.

자료형은 크게 기본형과 참조형 두 가지로 나뉜다.

프리미티브 타입(primitive type)기본형(원시 타입)을 의미한다.

분류자료형크기저장 가능한 값의 범위기본값
논리형boolean1 bytetrue, falsefalse
문자형char2 byte'\u0000' ~ '\uffff' (0~216-1, 0~65535)'\u0000'
정수형byte1 byte-128 ~ 127 (-27 ~ 27 -1)0
정수형short2 byte-32768 ~ 32767 (-215 ~ 215 -1)0
정수형int4 byte-2147483648 ~ 2147483647 (-231 ~ 231 -1)0
정수형long8 byte-9223372036854775808 ~ 9223372036854775807 (-263 ~ 263 -1)0L
실수형float4 byte(3.4 x 10-38) ~ (3.4 x 1038)의 근사값0.0F
실수형double8 byte(1.7 x 10-308) ~ (1.7 x 10308)의 근사값0.0
  • 문자형인 char는 문자를 내부적으로 정수(유니코드)로 저장하기 때문에 정수형과 별반 다르지 않으며, 정수형 또는 실수형과 연산도 가능
  • boolean을 제외한 나머지 7개의 기본형은 서로 연산과 변환이 가능
  • 자료형의 크기와 기본값 정도는 외워두자
  • Java 8부터 Integer, Long에 unsigned 관련 메소드 지원

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

  • 기본형(Primitive type)

    -> 실제 값(data)를 저장한다.

  • 참조형(Reference type)

    -> 어떤 값이 저장되어 있는 주소(memory address)를 값으로 갖는다.

    • 변수의 타입이 기본형이 아닌 것들은 모두 참조변수이다.

      ex) 배열, 열거, 클래스, 인터페이스

    • null을 가질 수 있다.

메모리 주소를 값으로 갖는다는 것은 어떤 의미일까?

기본형 변수와 달리 참조형 변수는 메모리 주소 -> 200번지를 값으로 갖는다.
200 번지를 참조하고 있다고 생각하면 된다.

// 기본형
int age = 29;
int price = 10000;

// 참조형
String name = "잔잔이";

위의 코드 처럼 기본형(int) age, price 변수와 참조형(String) name 변수가 선언되었을 때
메모리 상에서 각 변수들이 갖는 값은 아래의 그림과 같다.

기본형 age와 price는 직접 값을 스택영역에 저장하고 있지만,
String 객체는 힙 영역에 생성되며 name 변수는 그 객체의 주소값인 200번지를 참조값으로 가진다.

2-3. 리터럴

변수(variable) : 하나의 값을 저장하기 위한 공간
상수(constant) : 값을 한번만 저장할 수 있는 공간 리터럴(literal) : 그 자체로 값을 의미하는 것

리터럴의 타입과 접미사

정수형과 실수형에는 여러 타입이 존재하므로, 리터럴에 접미사를 붙여서 타입을 구분한다.

정수형 리터럴

int octNum = 010;   // 8진수 10, 10진수로 8
int hexNum = 0x10;  // 16진수 10, 10진수로 16 
int binNum = 0b10;  // 2진수 10, 10진수로 2
long longNum = 100L;    // long 리터럴 L

long 타입의 리터럴에 접미사 'l' 또는 'L'을 붙이고, 접미사가 없으면 int 타입의 리터럴이다.
byte와 short 타입의 변수에도 int 타입의 리터럴을 사용한다.

10진수 외에도 2, 8, 16진수로 표현된 리터럴을 변수에 저장할 수 있으며, 16진수라는 것을 표시하기 위해
리터럴 앞에 접두사 '0x' 또는 '0X'를, 8진수의 경우에는 '0'을 붙인다. 2진수 리터럴은 JDK 1.7부터 추가되었다.

long big = 100_000_000_000L;    // long big = 100000000000L;
long hex = 0xFFFF_FFFF_FFFF_FFFFL;  // long hex = 0xFFFFFFFFFFFFFFFFL;

JDK 1.7 부터 리터럴의 중간에 구분자 '_'를 넣을 수 있게 되어서 큰 숫자를 편하게 읽을 수 있다.

실수형 리터럴

float pi = 3.14f;
double rate = 1.618d;

실수형에서는 float 타입의 리터럴에 접미사 'f' 또는 'F'를 붙이고, double 타입의 리터럴에는 접미사 'd' 또는 'D'를 붙인다.

float pi = 3.14;    // ERROR. float타입 변수에 double 타입 리터럴 저장 불가능
double rate = 1.618;    // OK. 접미사 d 생략가능

실수형에서는 double이 기본 자료형이기 때문에 접미사 'd'는 생략이 가능하다.
즉, 실수형 리터럴인데 접미사가 없으면 double 타입 리터럴이다.

리터럴의 접두사와 접미사는 대소문자를 구별하지 않으므로, 어떤걸 사용해도 좋으나
long 타입의 경우 'l'은 숫자 '1'과 헷갈리므로 되도록 'L'을 사용하도록 한다.

double doubleNum1 = 10.   //  10.0
double doubleNum2 = .10   //  0.10
float floatNum1 = 10f   //  10.0f
float floatNum2 = 3.14e3f   //  3140.0f
double doubleNum3 = 1e1     //  10.0
double doubleNum4 = 1e-3    //  0.001

리터럴에 소수점이나 10의 제곱을 나타내는 'E' 또는 'e', 그리고 접미사 f, F, d, D를 포함하고 있으면
실수형 리터럴로 간주한다.

타입의 불일치

리터럴 타입은 저장될 변수와 일치하는것이 보통이지만, 타입이 다르더라도 저장범위가 넓은 타입에
좁은 타입의 값 저장은 허용된다.

int i = 'A';    // OK. 문자 'A'의 유니코드인 65가 int에 저장된다.
long l = 123;   // OK. int보다 long 타입의 범위가 더 넓다.
double d = 3.14f;   // OK. float 보다 double 타입의 범위가 더 넓다.

int i = 0x123456789;    //  ERROR. int 타입의 범위를 넘는 값 저장.
float f = 3.14;         //  ERROR. float 타입보다 double 타입의 범위가 넓다.

문자 리터럴과 문자열 리터럴

'A'와 같이 작은 따옴표로 문자 하나를 감싼 것을 문자 리터럴 이라고 한다. 두 문자 이상은 큰 따옴표로 감싸야 하며 문자열 리터럴이라고 한다.

char ch = 'J';
String name = "Java";

char 타입은 단 하나의 문자만 저장할 수 있기때문에, 여러 문자를 사용하기 위해서는 String 타입을 사용해야 한다.
문자열 리터럴은 empty string 즉, 빈 문자열 ""을 허용하지만 문자 리터럴은 반드시 안에 하나의 문자가 있어야한다.

String str = "";    // OK
char ch = '';   // ERROR. ''안에 반드시 하나의 문자 필요
char ch = ' ';  // OK. 공백문자(blank)로 변수 ch 초기화

2-4. 변수 선언 및 초기화 방법

변수란? 단 하나의 값을 저장할 수 있는 메모리 공간

하나의 값만 저장할 수 있으므로, 새로운 값을 저장하면 기존의 값은 사라진다.

변수는 클래스변수, 인스턴스변수, 지역변수 총 세 종류가 있다. 변수의 종류를 결정짓는 요소는 변수의 선언된 위치이므로 변수가 어느 영역에 선언되었는지를 확인하는 것이 중요하다.

  • 멤버변수
    • static이 붙은 클래스변수
    • static이 붙지않은 인스턴스변수
  • 지역변수
class Variables {

  int iv;         // 인스턴스변수
  static int cv;  // 클래스변수(static변수, 공유변수)

  void method() {
    int lv = 0;   // 지역변수
  }
}

iv와 cv는 클래스 영역에 선언되어있으므로 멤버변수이다. cv는 static 키워드와 함꼐 쓰여서 클래스변수이며
iv는 인스턴스 변수이다. lv는 메서드 내부인 메서드 영역에 선언되어있으므로 지역변수 이다.

변수 선언

int money;  // money라는 이름의 변수를 선언

변수를 사용하기 위해 우선, 변수를 선언해야 하며, 선언 방법은 다음과 같다.

'변수타입' '변수명'
  • 변수타입 : 변수에 저장될 값이 어떤 타입인지 지정
  • 변수명 : 변수에 붙인 이름이다.

변수 초기화

변수의 초기화란, 변수를 사용하기 전에 처음으로 값을 저장하는 것

int money = 10000;  // 변수 money를 선언하고 10000으로 초기화 한다.

int money;
money = 10000;   // 선언 후 초기화 위와 같은 코드이다.

변수 선언 이후 변수를 사용할 수 있으며, 그 전에 반드시 변수를 초기화해야 한다.
변수에 값을 대입할 때는 '=' 대입연산자를 이용한다. 오른쪽 값을 왼쪽(변수)에 저장한다는 뜻이다.

int money = 5000;
int age;
age = 20;
Compiled from "Hello.java"
public class Hello {
  public Hello();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: sipush        5000
       3: istore_1
       4: bipush        20
       6: istore_2
       7: return
}

위의 작성한 코드의 바이트코드를 살펴보면 선언과 초기화를 동시에 하는 방식과 선언 후 초기화를 하는
방식은 차이가 없다.

멤버변수의 초기화는 지역변수와 달리 여러 가지 방법이 있다.

  1. 명시적 초기화(explicit initialization)
  2. 생성자 (constructor)
  3. 초기화 블럭 (initialization block)
    • 인스턴스 초기화 블럭 : 인스턴스변수를 초기화 하는데 사용
    • 클래스 초기화 블럭 : 클래스변수를 초기화 하는데 사용

1.명시적 초기화

  • 변수를 선언과 동시에 초기화 하는 방법
  • 가장 기본적이며 간단한 초기화 방법
  • 여러 초기화 방법 중 가장 우선적으로 고려
class Person {
  int age = 40;             // 기본형(primitive type) 변수의 초기화
  Friend f = new Friend();  // 참조형(reference type) 변수의 초기화
}

명시적 초기화는 간단하고 명료하지만, 조금 더 복잡한 초기화가 필요할 때 '초기화 블럭(initialization block)' 또는 생성자를 사용해야한다.

2.초기화 블럭(initialization block)

클래스 초기화 블럭 클래스변수의 복잡한 초기화에 사용 인스턴스 초기화 블럭 인스턴스변수의 복잡한 초기화에 사용

인스턴스 초기화 블럭은 단순히 클래스 내부에 블럭 {} 코드를 작성하며,
클래스 초기화 블럭은 위와 같고 static을 붙여준다.

class InitBlock {

  static {
    // 클래스 초기화 블럭
  }

  {
    // 인스턴스 초기화 블럭
  }
}
  • 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩 될 때, 한 번만 수행된다.

  • 인스턴스 초기화 블럭은 인스턴스가 생성될 때 마다 실행된다.

    💡 생성자 보다 인스턴스 초기화 블럭이 먼저! 수행된다.

인스턴스 변수의 초기화는 주로 생성자를 사용한다. 단 모든 생성자에서 공통으로 수행해야 할 코드가 있을 때,
인스턴스 초기화 블럭을 사용한다.

다음은 예제코드이다.

Student () {
  count++;
  number = count;
  name = "janjan";
  class = "happy";
}

Student (String name, String class) {
  count++;
  number = count;
  this.name = name;
  this.class = class;
}
// 인스턴스 초기화 블럭
{
  count++;
  number = count;
}

Student () {
  name = "janjan";
  class = "happy";
}

Student (String name, String class) {
  this.name = name;
  this.class = class;
}

위의 코드에서 생성자 중복 코드를 인스턴스 초기화 블럭으로 작성한다.

멤버변수의 초기화 시기와 순서

클래스변수의 초기화 시점 클래스가 처음 로딩될 때 단 한번 초기화
인스턴스변수의 초기화 시점 인스턴스가 생성될 때마다 각 인스턴스별로 초기화
클래스변수의 초기화 순서 기본값 -> 명시적초기화 -> 클래스 초기화 블럭
인스턴스변수의 초기화 순서 기본값 -> 명시적초기화 -> 인스턴스 초기화 블럭 -> 생성자

class InitTest {

  static int cv = 1;
  int iv = 1;

  static { cv = 2; }
  { iv = 2; }

  InitTest () {
    iv = 3;
  }
}

위의 코드는 다음과 같은 순서로 초기화가 이루어진다.

  • 클래스변수 초기화 (1~3) -> 클래스가 처음 메모리에 로딩될 때 차례로 수행
  • 인스턴스변수 초기화(4~7) -> 인스턴스를 생성할 때 차례대로 수행
  • 클래스변수는 항상 인스턴스변수보다 먼저 생성되고 초기화된다.

2-5. 변수의 스코프와 라이프타임

변수의 종류선언위치생성시기
클래스 변수(class variable)클래스영역클래스가 메모리에 올라갈 때
인스턴스 변수(instance variable)클래스 영역인스턴스가 생성되었을 때
지역변수(local variable)클래스 영역 이외의 영역(메서드, 생성자, 초기화 블럭 내부변수 선언문이 수행되었을 때

각각을 더 자세히 살펴보자.

1.인스턴스 변수(instance variable)

클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어지며 인스턴스를 참조하는 변수가 더 이상 없을 때 GC에 의해 인스턴스와 함께 사라진다.

인스턴스변수의 값을 읽어오거나 저장하기 위해서는 반드시 인스턴스를 생성해야한다.
인스턴스는 독립적인 저장공간이므로 서로 다른 값을 가질 수 있다.
인스턴스마다 고유한 상태를 유지해야하는 속성이 있다면, 인스턴스변수로 선언한다.

2.클래스변수 (class variable)

클래스변수는 인스턴스변수 앞에 static 키워드를 붙이면된다. 클래스변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다.
한 클래스의 모든 인스턴스들이 공통적인 값을 가져야 한다면 클래스변수로 선언해야 한다.

클래스가메모리에 로딩 될 때, 생성되어 프로그램이 종료될 때 까지 유지되며, public을 앞에 붙이면 같은 프로그램 내에 어디서나 접근 가능한 전역변수(global variable의 성격을 갖는다.

3.지역변수(local variable)

메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 더 이상 사용할 수 없다.
for, while 문의 블럭 내에 선언된 지역변수는, 지역변수가 선언된 블럭 {} 내에서만 사용 가능하며, 블럭{}을 벗어나면 소멸되어 사용할 수 없게된다. 또한, 서로 다른 메서드라면 같은 이름의 지역변수를 선언해도 된다.

2-6. 타입 변환, 캐스팅 그리고 타입 프로모션

프로그램을 작성하다 보면 같은 타입뿐만 아니라 서로 다른 타입간의 연산을 해야하는 경우가 있다.
이럴 때는 연산을 수행하기 전 타입을 일치시켜야 하는데 이것을 형변환 이라고 한다.

형변환(casting)이란, 변수 또는 상수의 타입을 다른 타입으로 변환하는 것

(타입)피연산자

형변환 방법은 형변환하고자 하는 변수나 리터럴의 앞에 변환하려고 하는 타입을 괄호와 함께 써준다.
괄호()는 '캐스트 연산자' 또는 '형변환 연산자'라고 한다.

double d = 99.9;
int score = (int)d;   // double 타입의 변수를 int 타입으로 형변환

타입 캐스팅(강제 형변환)

강제적으로 큰 데이터 타입을 작은 데이터 타입으로 쪼개어서 저장하는 것을 의미한다.
타입 캐스팅은 캐스팅 연산자인 () 괄호를 사용해야 한다.
큰 데이터 타입 -> 작은 데이터 타입으로 형변환 하기 때문에 경우에 따라, '값 손실'이 발생한다.

int intValue = 103029770;
byte byteValue = (byte) intValue;     // 강제 형변환(캐스팅)

이 경우 int 값은 보존되지 않는다. 103029770의 4 byte 중

00000110 00100100 00011100 00001010

마지막 0001010 1 byte가 byteValue에 저장된다. 따라서 값 손실이 발생한다.

int intValue = 10;
byte byteValue = (byte)intValue;

이 경우는 어떨까? intValue 값인 10은
00000000 00000000 00000000 00001010

이므로 1 byte로 10을 표현할 수 있기 때문에 값 손실이 일어나지 않고 형변환 할 수 있다.

타입 프로모션 (자동 형변환)

프로그램 실행 도중 자동적으로 형변환이 일어나는 것을 의미한다.

기존의 값을 최대한 보존할 수 있는 타입으로 자동 형변환
즉, 데이터의 표현범위가 좁은 타입에서 넓은 타입으로 형변환 하는 경우에는
값 손실이 없으므로 자동 형변환이 발생한다.

byte byteValue = 10;
int intValue = byteValue;   // 자동 형변환

byte타입이 더 좁은 타입이므로 int 타입으로의 자동 형변환이 발생한다.

2-7. 1차 및 2차 배열 선언하기

👉 배열이란? 같은 타입의 여러 변수를 하나의 묶음으로 다루는 것

중요한 것은 같은 타입! 이라는 점이다.

학생들의 점수를 나타내기 위해

int score1, score2, score3, score4, score5;

5개의 변수를 선언하는 것 보다

int[] score = new int[5];   // 5개의 int 값을 저장할 수 있는 배열 선언

변수 대신 배열을 이용하면 간단히 처리할 수 있다.

배열 선언과 생성

선언방법예시
타입[] 변수이름;int[] score; String[] name;
타입 변수이름[];int score[]; String name[];

배열을 선언 후에는 생성을 해야한다. 배열을 선언 했다는 것은 참조변수를 위한 공간만 만들어졌을 뿐,
값을 저장한 공간은 만들어지지 않은 상태이다.

값을 저장할 공간을 만들기 위해 배열을 생성해보자.

타입[] 변수이름;            //  배열 선언(배열을 다루기 위한 참조변수 선언)
변수이름 = new 타입[길이];   //  배열 생성(실제 저장공간 생성)
int [] score;
score = new int[5];

배열의 선언과 생성을 한 코드이다. int 타입의 배열 참조변수 score 선언 후, int 타입의 값 5개를 저장할 수 있는 배열을 생성한다.

보통은 한 줄로 선언과 생성을 동시에 처리한다. 코드는 다음과 같다.

int[] score = new int[5];

배열은 생성과 동시에 자동적으로 자신의 타입에 해당하는 기본값으로 초기화되므로 따로 초기화하지 않아도 되지만, 원하는 값을 저장할 수도 있다.

int [] score = new int[]{ 50, 60, 70, 88, 99 };
int [] score = { 50, 60, 70, 88, 99 };      // new int[]를 생략할 수 있음

2차원 배열의 선언과 인덱스

2차원 배열은 테이블 형태의 데이터를 담는다고 생각하면 된다. 예를 들어, 3행 4열의 데이터를 담는다고 해보면 다음과 같이 생성한다.

int[][] score = new int[3][4];
intintintint
intintintint
intintintint

12개의 int 값을 저장할 수 있는 공간이 생긴다.

만약 2행 3열에 데이터를 넣고싶다면 이렇게 하면 된다.

score[1][2] = 100;

2차원 배열의 초기화도 1차원 배열과 비슷하다.

int [][] arr = {
  {1, 2, 3},
  {4, 5, 6}
};

2-8. 타입 추론, var

👉 타입추론 : 타입이 정해지지 않은 변수에 대해서 자바 컴파일러가 변수의 타입을 추론하는 것

var

  • java 10 버전부터 지원되는 타입명이다. int와 같은 예약된 타입 (키워드 아님)
  • 초기화 값이 반드시 있어야 하고, 지역변수 일 때만 사용가능하다.
  • <> 다아이몬드 연산자에서 사용할 수 없다.
  • null이 들어갈 수 없다.

다음은 사용 예제 코드 입니다.

// 1
var name = "janjan";

// 2
Map<Integer, String> map = new HashMap<>();
var idToNameMap = new HashMap<Integer, String>();

다음은 문제가 발생하는 상황 예제 코드입니다.

var n; // 초기화 되지 않음.
var emptyList = null; // null로 초기화해도 동작하지 않음
public var = hello; // 지역변수가 아닌경우 사용 불가능
var arr = {1, 2, 3}; // 배열일 경우에도 타입이 명시적으로 필요
var arrayList = new ArrayList<>(); // <> 연산자 안에 타입 명시적으로 작성 필요

주의할 점으로는 타입이 드러나지 않는 경우에는 다른 개발자들의 코드 가독성을 헤칠 수 있다는점,
파이프 라인이 긴 스트림에서 사용할 때는 예상 결과와 다를 수 있다는 점을 주의하자.

References

profile
얍얍 개발 펀치

0개의 댓글