[새싹] 자바 변수,배열 사용법 익히기

채상엽·2022년 5월 10일
0

Sproutt 2nd - Spring Study

목록 보기
29/32

layout: post
title: "자바 Primitive Type,변수,배열 사용법 익히기"
date: 2022-05-01T00:00:00-00:00
author: sangyeop
categories: Sproutt-2nd


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


정수형

타입기본값값의 범위
byte0-2^7- ~ 2^7-1
short0-2^15 ~ 2^15-1
int0-2^31 ~ 2^31-1
long0L-2^63 ~ 2^63 -1

실수형

타입기본값값의 범위
float0.0f음수 범위 : -3.4028235E+38 ~ -1.4E-45
양수범위 : 1.4E-45 ~ 3.4028235E+38
double0.0음수 범위 :-1.7976931348623157E+308 ~ -4.9E-324
양수 범위 : 4.9E-324 ~ 1.7976931348623157E+308

문자형

타입기본값값의 범위
char'\u0000'0 ~ 2^16-1

논리형

타입기본값값의 범위
booleanfalsefalse or true

Primitive Type / Reference Type


자바의 타입은 Primitive TypeReference Type 두 가지로 나뉘어 진다.

Primitive Type에는 byte,short,int,long,float,double,char,boolean 8가지가 존재하며, 그 외의 나머지 타입들은 모두 Reference Type으로 정의된다.

Reference Type에는 Integer,Double,Long,String 등등 여러가지 클래스들이 포함된다.

Primitive Type 메모리 저장 방법

int a = 10;

다음과 같은 int형 변수가 선언되었을때, Primitive Type의 경우에는 해당 메모리 자리에 10이라는 값이 그대로 할당이 된다.

a의 주소 값= 123
a의 주소 공간에 들어가는 값 = 10

Reference Type 메모리 저장 방법

String name = "채상엽";

다음과 같이 String형 변수가 선언되었을때, Reference Type의 경우에는 해당 메모리 자리에 "채상엽" 이라는 값이 아닌 "채상엽"이라는 값이 위치하는 주소값을 담는다.

그렇다면 다음과 같은 경우에는 어떻게 주소 공간을 차지할까?

String originalName = "채상엽";
String copyName = originalName;

"채상엽"의 주소 값 = 222
originalName의 주소 값 = 333
orginalName이 저장하는 값 = 222

originalName은 222라는 값을 메모리에 저장하고 있게 된다. 그리고 copyName은 다음과 같은 값을 갖는다.

copyName의 주소 값 = 444
copyName이 저장하는 값 = 222

실제 코드로 확인해보면 다음과 같이 같은 주소가 출력됨을 확인할 수 있다.

Car car1 = new Car();
Car car2 = car1;
Car car3 = car2;

System.out.println(car1);
System.out.println(car2);
System.out.println(car3);
image

위에서 언급했듯이 Primitive는 값 자체를 저장하고, Reference는 주소 값을 저장하기 때문에, 아래의 예제와 같은 경우에 그 차이를 더 잘 확인할 수 있다.

  • Primitive Type 값 변경
int a = 1000;
int b = a;

System.out.println("a의 값=" + a);
System.out.println("b의 값=" + b);

a=2000;

System.out.println("a의 값=" + a);
System.out.println("b의 값=" + b);
a의 값=1000
b의 값=1000
a의 값=2000
b의 값=1000
  • Reference Type 값 변경
Car car1 = new Car("포르쉐");
Car car2 = car1;

System.out.println("car1의 차 이름=" + car1.getName());
System.out.println("car2의 차 이름=" + car2.getName());

car2.setName("페라리");

System.out.println("car1의 차 이름=" + car1.getName());
System.out.println("car2의 차 이름=" + car2.getName());
car1의 차 이름=포르쉐
car2의 차 이름=포르쉐
car1의 차 이름=페라리
car2의 차 이름=페라리

Primitive Type의 경우에는 주소 값을 공유하지 않기 때문에, 어떤 변수의 값을 값으로 저장하고 있었다고 하더라도 해당 값을 변경하면 변경을 시도한 변수의 메모리가 가진 값만 변경이 된다.

그러나 Reference Type의 경우에는 값을 공유하는 변수 모두가 주소를 공유하고 있기 때문에, 한 객체의 값을 변경하면 해당 주소에 해당하는 객체를 공유하는 모든 변수의 값이 함께 변경된다.

동일성과 동등성

  • 동일성

    • 두 객체가 완전히 동일할 경우를 의미한다. 즉 두 객체의 해시코드(주소) 값을 비교하여 같음을 의미한다. == 연산자를 이용하여 비교한다.

    • Car car1 = new Car();
      Car car2 = car1;
    • car1car2는 위에서 설명 했듯이 Reference Type 이기 때문에 같은 주소 공간을 공유한다. 즉 같은 해시코드(주소) 값을 가지고 있기 때문에 동일성 검사에서 true를 반환한다.

  • 동등성

    • 두 객체가 같은 정보를 가지고 있는 경우를 의미한다.

    • String a1 = new String("테스트");
      String a2 = new String("테스트");
    • a1a2는 서로 다른 객체이기 때문에 동일성 검사(a1==a2)에서는 false가 반환된다.

    • 그러나 a1a2가 가지고 있는 정보(값)는 동등하기 때문에, 동등성 검사(a1.equals(a2))에서는 true가 반환된다.

자바가 Call By Reference가 아닌 Call By Value인 이유

자바는 Call By Value만 존재한다. 객체의 해시코드가 value로써 전달되어지고 객체 내의 상태값을 바꿀 수는 있지만, 전달되는 값이 포인터가 아닌 주소값이기 때문에 객체 자체를 바꿀 수는 없어서 Call By Reference는 아니다.

리터럴


변수에 할당할 수 있는 어떤 상수(변하지 않는 값)를 의미한다. 간단한 예를 들어보자면

int a = 1000;  // a는 상수 리터럴이다.

Integer Literal

정수 타입의 경우에는 4가지 방법으로 변수에 리터럴 값을 할당할 수 있다.

  • 10진수
  • 8진수
  • 16진수
  • 바이너리

Floating-point Literal

  • 10진수

Char Literal

  • Single quote

    char a = 'a';
  • Integer Literal을 이용한 표현법 (0~65535)

    10진수, 8진수, 16진수로 표현이 가능하며, 유니코드로 변환되어 출력된다.

    char a = 42;
  • Unicode 표현 ('\uxxxx')

    char a = '\u0000';
  • Escape 문자

    char a = '\n';

String Literal

  • Double quote

    String a = "Hello World";
  • Mulit Line

    String a = "Hello World\n" 
      +"Hello World\n"
      +"Hello World";

Boolean Literal

  • True or False

    boolean a = true // or false

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


변수 선언
컴퓨터는 모든 값을 bit(binary digits)를 이용해 저장한다. 한 개의 비트는 0 또는 1 두 가지 값을 표현할 수 있다. 변수 선언은 곧 변수를 생성하는 것을 의미한다. 자바에서 변수를 생성하기 위해서는 얼마나 많은 비트를 사용해야할지 알려주기 위해서 변수의 타입을 선언해주어야 한다.

int a;

변수 초기화
변수 초기화는 곧 변수에 값을 할당하는 것을 의미한다. 값이 초기화 되어 있지 않다면, Primitive Type 의 경우에는 각각의 default value가 할당되어 있고 Reference Type의 경우에는 default value로 null이 할당되어 있다.

int a; // 변수 선언
a = 2; // 변수 초기화

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


인스턴스 변수
블록과 메서드의 바깥이면서 클래스 내에 선언되어 있는 변수를 인스턴스 변수라고 부른다. 일반적인 인스턴스 변수의 범위는 static 메서드 내를 제외한 클래스 내 모든 범위에 해당한다. 인스턴스 변수의 수명은 해당 클래스 객체가 메모리에 남아 있을 동안 유지된다.

클래스 변수
클래스 내에 선언되어 있으면서 블록들의 바깥에 static으로 선언되어 있는 변수를 클래스 변수라고 부른다. 일반적인 클래스 변수의 범위는 클래스 내 모든 범위에 해당하고, 클래스 변수의 수명은 프로그램이 끝날때까지 또는 클래스가 메모리에 로드되어 있는 동안 유지된다.

로컬 변수
인스턴스 변수와 클래스 변수가 아닌 다른 나머지 변수들을 로컬 변수라고 부른다. 이 범위에는 메서드의 매개변수(parameter)도 포함이 된다. 로컬 변수의 범위는 해당 변수가 선언되어 있는 블록 내에 해당하고, 로컬 변수의 수명은 해당 변수가 선언되어 있는 블록이 종료되기 전까지 유지된다.

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


자바 언어로 작성된 모든 표현식은 추정할 수 있는 타입을 가지고 있다. 양쪽의 타입이 일치하지 않을 때, 컴파일 에러를 발생시킬수도 있지만, 어떠한 경우에 따라서는 자바는 편의상 프로그래머에게 타입을 명시적으로 변환하도록 요구하는 것이 아니라, 스스로 암시적 변환을 실행해서 사용가능한 타입으로 변환해준다.

S라는 타입에서 T타입으로 변환을 하기 위해서 S 타입의 표현식은 컴파일시에 S타입 대신 T타입이 있는 것처럼 처리할 수 있다. 어떤 경우에는 변환의 유효성을 확인하거나 표현식의 런타임 값을 새 유형인 T타입에 적합하게 바꿔주기 위해서 런타임시에 조치가 필요하다.

  • Type Obejct to Type Thread

    오브젝트 타입에서 쓰레드 타입으로 변경하는 경우 런타임 값이 쓰레드 클래스의 인스턴스인지 또는 해당 클래스의 서브클래스인지를 확인하는 과정이 필요하다. 만약 그렇지 않다면 예외를 던진다.

  • Type Thread to Type Object

    쓰레드 타입에서 오브젝트 타입 변환은 런타임시 별도 과정이 필요하지 않다. 쓰레드는 오브젝트의 하위 클래스이므로 이 경우에는 모두 변환이 가능하다.

  • Type int to Type long

    int에서 long으로 변환핳기 위해서는 32비트 정수 값을 64비트 long 표현으로 sign-extension이 필요하다. 이 과정에서 정보가 손실되지는 않는다.

  • Type double to Type long

    double에서 long으로 변환하기 위해서는 64비트 floating-point 값을 64비트 integer 값으로 변환하는 과정이 필요하다. 이 과정에서는 일부 정보가 손실될 수 있다.

Automatic Type Conversion

  • 작은 데이터 타입을 큰 데이터 타입으로 변환할때 사용된다.
Byte -> Short -> Int -> Long -> Float -> Double

Narrowing(Explicit) Conversion

  • 큰 데이터 타입을 작은 데이터 타입으로 변환할때 사용된다
  • Automatic Type Conversion이 호환되지 않을때 유용하게 사용된다.
Double -> Float -> Long -> Int -> Short -> Byte
double d = 123.1;
long l = (long)d;
int i = (int)l;
123.1
123
123

만약 long 타입에 할당된 값이 int의 범위를 벗어난 값이라면 예외가 던져진다.

Type Promotion

만약 여러개의 타입이 연산에 사용되는 표현식이 있다면, 최종 연산 결과의 타입은 표현식에 사용된 타입들 중 가장 넓은 범위의 타입으로 정해진다.

byte b = 1;
int i = 1;
long l = 1;

long result = b + i + l;

Explicit Type Casting

표현식 연산에서 연산의 결과는 자동으로 더 큰 데이터 타입으로 캐스팅된다.

byte b = 1;
b = (byte)(b+1);

만약 아래와 같다면 컴파일 에러가 발생한다. 그 이유는 결과로 받는 bbyte 타입인데 b+1을 실행하게 되면 1이 더 큰 데이터 타입인 int 타입이기 떄문에 Type Promotion에 의해 int형으로 반환되기 때문이다. byte로 받기위해서는 위와 같이 명시적으로 타입을 정의해주어야 한다.

byte b = 1;
b = b+1;

Casting 과 Conversion의 차이 : Conversion은 컴파일러에 의해 자동으로 수행되지만, Casting은 프로그래머가 명시적으로 선언해야한다는 차이점이 있다.

1차 및 2차 배열 선언하기


자바의 배열이란, 같은 타입의 값들을 포함하고 있는 변수들의 리스트를 의미하는것과 같다. 다차원의 경우 각 배열들은 하나의 행이나 열을 의미하게 되고 각각의 배열 내 요소들은 인덱스를 이용해 구분되어진다.

1차원 배열 선언방법

String[] names = {"채상엽", "홍동건", "김성혁", "최진영", "김현우"};
String[] names = new String[5]; // 배열의 크기를 5라고 선언해서 요소는 비어있는 배열을 생성하는 방법

[] 하나가 선언된 것은 1차원 배열임을 의미한다. 만약 2차원 배열일 경우 [][]가 선언되어진다. 중괄호 {} 안에는 배열 안에 넣고자하는 요소들이 선언되어지고. 해당 요소들을 꺼내 사용하기 위해서는 다음과 같이 사용할 수 있다.

String first = names[0]; // "채상엽"
String third = names[2]; // "김성혁"

각각의 인덱스 (0~4)는 각 배열 요소의 위치(주소 값)를 참조하고 있다.

2차원 배열 선언방법

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

위와 같이 배열이 2차원으로 중첩되어 있는 구조를 2차원 배열이라고 한다. 아래와 같이 인덱스를 이용해 값을 찾을 수 있다.

arr[0] // {1,2}
arr[0][1] // 2
arr[2] // {5,6}
arr[2][0] // 5

타입 추론, var


JAVA10 이상부터 지역변수의 타입 추정 기능을 제공하게 되었다. 예를 들어 이전까지는 문자열 변수를 선언할때 다음과 같이 사용하였다.

String name = "채상엽";

그러나 타입 추정을 이용하면 다음과 같이 변수를 선언할 수 있다

var name = "채상엽";

변수를 명시적으로 프로그래머가 선언하지 않고, 컴파일시에 컴파일러가 할당되는 값을 보고 타입을 추정한다. 주의 할점은 이러한 특징은 오로지 초기화된 지역 변수에서만 가능하다는 점이다. 멤버 변수나, 메서드의 매개변수, 반환 타입에서는 사용이 불가능하다.

응용으로는 다음과 같이도 사용할 수 있다.

Map<Long, User> map = new HashMap<>();
var map = new HashMap<>();

이러한 기능을 제공함으로써 프로그래머는 변수의 타입에 집중하기 보다는 변수의 이름에 집중할 수 있게 된다는 장점이 생긴다.

참고 자료

Primitive Type

Primitive Type vs Reference Type

Literals in Java

Java variables declaring

Java variables scope and lifetime

Type conversion and Type promotion

One,Two Dimensional Declaration

Java type inversion var

profile
프로게이머 연습생 출신 주니어 서버 개발자 채상엽입니다.

0개의 댓글