자바 데이터 타입, 변수 그리고 배열
자바 데이터 타입, 변수 그리고 배열
프리미티브 타입 종류와 값의 범위 그리고 기본 값
정수형 | 문자형 | 실수형 | 논리형 | |
---|---|---|---|---|
1바이트 | byte | - | - | boolean |
2바이트 | short | char | - | - |
4바이트 | int | - | float | - |
8바이트 | long | - | double | - |
Primitive type은 정수형, 문자형, 실수형, 논리형 크게 네 종류로 구성된다.
정수형
정수 자료형은 byte, short, int, long 네 가지 자료형으로 구성된다. 이 정수 자료형은 말 그대로 정수인 양수, 음수, 0을 나타내는데 사용된다. 이 자료형들의 값의 범위와 기본 값은 다음과 같다.
자료형 | 값의 범위 | 기본값 |
---|---|---|
byte | -2^7 ~ 2^7-1 | (btye) 0 |
short | -2^15 ~ 2^15-1 | (short) 0 |
int | -2^31 ~ 2^31-1 | 0 |
long | -2^63 ~ 2^63-1 | 0L |
long의 기본값을 0L 이라고 표현하는 이유는, 자료형이 long임을 명시적으로 알려주는 것이다.
각각의 정수형은 2진수로 저장되는데, 해당 자료형이 n비트라고 할 때 최상위 비트(MSB : Most Significant Bit) 부호비트와 (n-1)개 비트로 구성된다. 부호비트는 양수면 0이고 음수면 1로 표현된다. 한 비트 당 두 개의 숫자가 저장되기 때문에, 음수는 -2의 n-1승까지이며, 양수는 2의 n-1승에서 0을 포함하기 때문에 -1를 해준다.
각 자료형에 범위를 벗어나는 수를 대입할 경우 오버플로우가 발생하면서 컴파일 에러가 뜬다.
long num = 1234567890; // error
🤔 이와 같이 선언했을 때 오류가 나는 이유는? → 자바는 기본적으로 모든 정수 값을 int로 처리하기 때문이다
long num = 1234567890L; // correct
따라서 long형으로 처리하라고 컴파일러에게 명시적으로 알려주기 위해 숫자 뒤에 L을 붙인다.
문자형
자료형 | 값의 범위 | 기본값 |
---|---|---|
char | ‘\u0000’ ~ ‘uFFFF’ (0 ~ 65535) | ‘\u0000’ (null) |
컴퓨터는 0과 1로만 표현할 수 있기 때문에 문자 역시 0과 1로만 표현해야한다. 따라서 각각의 문자들을 특정 정수 값으로 표현하기 위한 코드 값을 모아두는데, 이를 ‘문자 세트’라고 한다. 문자들을 정해진 코드 값으로 변환하는 것을 ‘문자 인코딩’이라 하고, 다시 코드 값을 문자로 변환하는 것을 ‘문자 디코딩’이라고 한다.
-
아스키(ASCII)코드
가장 기본적인 문자 인코딩은 아스키(ASCII) 코드이다. 아스키코드는 영문자, 숫자, 특수 문자등이 포함된 문자 세트이다. 영문자는 대소문자, 특수문자, 기호를 포함하여도 1바이트로 표현할 수 있기 때문에, 아스키코드는 1바이트만 사용한다.
-
유니코드(unicode)
하지만 한글 등 다른 언어는 복잡하고 다양하기 때문에 1바이트만으로 모든 문자를 표현하기 어렵기 때문에 2바이트 이상을 사용하는데, 이 때 각 언어의 표준 인코딩을 정의한 것이 유니코드이다.유니코드의 1바이트는 아스키코드 값과 호환되고, 그 밖의 문자들은 2바이트나 그 이상의 조합으로 표현한다. 자바는 유니코드에 기반하여 문자를 표현하므로, char는 2바이트를 사용한다.
char c = 'A';
와 같이 선언을 하면, 문자 그대로 저장되는 것이 아닌 그 문자에 해당하는 정수 값이 저장된다.
출처 - https://www.w3resource.com/java-exercises/basic/java-basic-exercise-41.php
💡작은 따옴표와 큰 따옴표의 차이?
문자를 사용할 땐 항상 작은따옴표(’ ‘)를 사용해야하고, 문자를 여러 개 이은 문자열을 사용할 때는 큰따옴표(” “)를 사용해야한다. 예컨대 문자열 Hello는 큰따옴표를 감싸 “Hello”라고 표현하는 것이 많다. 그리고 문자열 끝에는 항상 널문자(‘\0’)가 있다. 따라서 문자와 문자열은 전혀 다른 값을 가진다. ‘A’는 정수값 65이고, “A”는 사실상 “A\0”이다. 자바에서는 문자열을 다룰 때 String 클래스를 사용한다.
실수형
자료형 | 값의 범위 | 기본값 |
---|---|---|
float | 1.4E-45 ~ 3.4028235E38 | 0.0f |
double | 4.9E-324 ~ 1.7976931348623157E308 | 0.0d |
float는 부호 1비트, 지수부 8비트, 가수부 23비트로 총 32비트를 사용한다. double은 부호 1비트, 지수부 11비트, 가수부 52비트로 총 64비트를 사용한다. 자바에서 실수는 double을 기본으로 사용하기 때문에 float을 사용할 땐 식별자 F 또는 f를 써주어야 한다.
float f = 3.14F;
논리형
자료형 | 값의 범위 | 기본값 |
---|---|---|
boolean | true / false | false |
어떤 변수의 참, 거짓을 나타내는데 사용된다.1바이트로 값을 저장하고, 참인 true와 거짓인 false인 두 값만 가진다.
프리미티브 타입과 레퍼런스 타입
자바에는 **기본 자료형(Primitive type)**과 참조 자료형(Reference type) 두 가지 종류의 자료형이 있다.
-
프리미티브 타입은 boolean type과 numeric type 두 개의 큰 종류로 나뉜다. numeric type은 integral type인 byte, short, int, long, char와 floating-point type인 float와 double로 구성된다.
-
레퍼런스 타입은 프리미티브 타입을 제외한 모든 타입을 말한다. 클래스 형, 인터페이스 형, 배열 형 등등 으로 구성된다. 정해져있는 것이 아닌, 사용자가 원한다면 직접 필요한 형태를 만들어 사용할 수 있다.
🤔 null type?
null type은 null에 대한 이름이 없는 표현이다. null type은 이름이 없기 때문에, null type 변수를 선언하거나 null로 형변환 하는 것은 불가능하다. 따라서 null type은 오직 null type 표현의 값으로써만 존재할 수 있고, 어느 레퍼런스 타입들에나 대입되거나 형변환될 수 있다.(다른 것으로) 출처 - https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.1
-
레퍼런스 타입의 값은 객체들의 참조값, 즉 주소값이다.
리터럴
리터럴은 프로그램에서 사용하는 모든 숫자, 문자, 논리값, 즉 ‘값’ 자체를 일컫는 말이다.
char c = 'A';
int num = 10;
final double PI = 3.14;
에서, ‘A’, 10, 3.14는 리터럴, 혹은 리터럴 상수이다. 리터럴은 프로그램이 시작할 때, 시스템에 같이 로딩되어, **JVM의 runtime data area의 상수 풀(runtime constant pool)**에 놓인다. (Runtime constant pool은 상수 자료형을 저장하고 참조하여 중복을 막는다.)
int num = 10;에서, 값 10이 어딘가에 존재해야 num 변수에 그 값을 복사할 수 있다. 즉 숫자가 변수에 대입되려면, 우선 숫자의 값이 메모리 어딘가에 쓰여있어야 하고, 그 값이 변수 메모리에 복사된다.
정수와 같이 상수 풀에서도 메모리의 기본 크기는 4바이트이다. 리터럴 10, ‘a’, 3.14 등은 int형(4바이트) 로 처리되는데, long num = 12345678900L; 은 4바이트로 처리할 수 없으므로, 8바이트로 처리 뒤 컴파일러에 알려주어야한다. 따라서 리터럴 뒤에 long형으로 저장되어야 한다는 의미로 식별자 l 또는 L을 써준다. 실수도 마찬가지로 모든 실수 리터럴이 8바이트인 double형으로 처리되므로, float를 사용할 땐 식별자 f나 F를 사용해야한다.
변수 선언 및 초기화하는 방법
-
변수 선언 방법
자료형 변수이름; 형태로 변수를 선언한다
int num;
-
변수 초기화 방법
변수에 처음 값을 대입하는 것을 초기화라고 한다.
num = 10;
선언과 동시에 초기화도 가능하다.
int num = 10;
-
변수 이름 제약사항
- 숫자로 시작할 수 없다. ex) 10day(x), 1abc (x)
- 영문자나 숫자를 사용할 수 있고, 특수문자 중에서는 $와 _만 사용가능하다.
- 자바에서 이미 사용중인 예약어는 사용할 수 없다.
변수의 스코프와 라이프타임
-
변수의 스코프
프로그램상에서 사용되는 변수들은 사용가능한 범위를 가지는데, 그 범위가 변수의 스코프이다.
public class ScopeTest { int globalScope = 10; public void Test(int value) { int localScope = 100; } }
- 클래스 속성으로 선언된 globalScope의 사용 범위는 클래스 전체이다.
- 매개변수 value는 메서드 선언부에 존재하므로 사용범위는 해당 메서드 블럭 내이다.
- localScope의 사용범위는 메서드 블럭 내이다.
- 메서드에서 생성된 변수는 메서드가 실행될 때 만들어지고, 메서드가 끝나면 삭제된다.
public class ScopeTest { int globalScope = 10; static int staticValue = 1000; public void Test(int value) { int localScope = 100; } public static void main(String[] args) { System.out.println(staticValue); System.out.println(globalScope); System.out.println(localVal); } }
- main에서는 같은 클래스 안에 있어도 globalScope를 사용할 수 없다 -> main은 static한 메서드이기 때문에!
- static한 메서드에서는 static하지 않은 필드를 사용할 수 없다!
-
static한 변수는 공유된다
- static이 붙은 멤버들은 프로그램이 시작될 때, 메모리에 우선 할당되기 때문에 new를 통한 객체 초기화가 필요없다. (인스턴스 생성 없이 바로 사용 가능)
- static 변수와 메서드는 정적 변수, 정적 메서드라고도 불린다.
- static 메서드는 객체의 생성없이 호출이 가능하지만 객체에서는 호출할 수 없다.
- static하게 선언된 변수는 값을 저장할 수 있는 공간이 하나만 생선된다. 따라서 인스턴스가 여러개 생겨도 static한 변수는 하나이다.
타입 변환, 캐스팅 그리고 타입 프로모션
-
타입 변환 (type conversion)
정수와 실수는 컴퓨터 내부에서 표현되는 방식이 다르다. 따라서 정수와 실수를 더하기 위해서는 하나의 자료형으로 통일한 뒤 연산을 해야한다. 이 때 발생하는 것이 타입 변환이다.
타입 변환은 크게 묵시적 타입 변환( = 자동 형 변환 = promotion) 과 명시적 타입 변환( = 강제 형 변환 = demotion) 두 가지로 구별할 수 있는데, 타입 변환의 기본 원칙은 다음과 같다.
- 바이트 크기가 작은 자료형에서 큰 자료형으로 형 변환은 자동으로 이루어진다.
- 덜 정밀한 자료형에서 더 정밀한 자료형으로 형 변환은 자동으로 이루어진다.
묵시적 타입 변환
묵시적 타입 변환은 자동 형 변환, 즉 타입 프로모션이라고도 한다.
-
묵시적 타입 변환이 이루어지는 관계
(출처 - Do it! 자바 프로그래밍 입문, 박은종)
byte bNum = 10;
int iNum = bNum;
bNum은 1바이트이고, iNum은 4바이트 이므로, 남은 3바이트에는 0으로 채워진다.
명시적 타입 변환
int iNum = 10;
byte bNum = (byte)iNum; // 괄호를 통해 명시
byte는 int보다 크기가 작기 때문에 자료 손실이 발생할 수 있으므로, 프로그래머가 변환할 자료형을 명시적으로 써주어야 한다. byte형이 표현할 수 있는 범위를 넘으면 자료손실이 발생한다.
캐스팅
캐스팅(Casting) 은 형변환이라 불리고, OOP의 Polymorphism에서 중요한 역할을 한다. 프리미티브 타입에서의 캐스팅은 주로 데이터 손실을 막기 위해 동작한다. 데이터가 큰 자료형에서 작은 자료형으로 캐스팅을 할 때 명시적으로 형변환을 하는 것이 그 이유이다. 레퍼런스 타입에서의 캐스팅은 서로 상속(extends)나 구현(implements) 관계에 있을 때만 일어난다. 레퍼런스 타입의 캐스팅은 업캐스팅과 다운캐스팅으로 나뉜다.
-
업캐스팅과 다운 캐스팅
class Animal { String name; int age; void bark() { System.out.println("동물이 짖는 소리"); } } class Dog extends Animal { String breed; @Override void bark() { System.out.println("왈왈"); } void giveHand() { System.out.println("손"); } } public class Main { public static void main(String args[]) { Animal animal_a = new Animal(); // 부모클래스 타입에 부모클래스 대입 Animal animal_b = new Dog(); // 업캐스팅 Dog dog_a = new Dog(); // 자식클래스 타입에 자식클래스 대입 Dog dog_b = new Animal(); // 컴파일 에러 (다운 캐스팅 x) animal_a.bark(); // Animal 클래스에서 동작합니다 animal_b.bark(); // 왈왈 dog_a.bark(); // 왈왈 dog_b.bark(); // 컴파일 에러 } }
위 코드에서, Animal은 Dog의 부모 클래스이고, Dog는 Animal을 상속받은 자식 클래스이다.
-
업캐스팅
- 업캐스팅은 자식 클래스의 객체가 부모 클래스의 객체로 캐스팅, 즉 형변환 되는 것을 말한다. 자식 클래스에서 부모 클래스로 업캐스팅 될 때, 업캐스팅된 객체는 자식 객체의 성질을 가지고 있는데, 이는 오버라이딩된 메소드가 실행된 것을 보면 알 수 있다. animal_b.bark() 는 오버라이딩 되었다.
- 하지만 Animal에는 없는 Dog의 필드인 breed와 메소드 giveHand()에는 접근을 할 수 없는데, 업캐스팅된 객체는 자식 객체만 가지고 있는 멤버에 접근할 수 없다
-
다운 캐스팅
- 다운 캐스팅은 반대로 부모클래스의 객체가 자식 클래스의 객체로 캐스팅, 즉 형 변환 되는 것이다.
Animal animal = new Animal(); animal.name = "bom"; animal.age = 10; Dog dog = animal; // 컴파일 에러 System.out.println(dog.breed); // 컴파일 에러
부모클래스인 Animal은 자식클래스인 Dog의 필드나 메소드가 없기 때문에 이를 필요로하는 Dog객체를 만들 수 없다. 따라서 다운캐스팅은 단순히 업캐스팅의 반대 개념이 아니라 **업캐스팅 되어 고유의 특성을 잃은 자식 클래스의 객체를 다시 복구시키는 것이다 **
Animal animal = new Animal(); animal.name = "bom"; Dog dog = (Dog)animal; // 다운캐스팅 dog.breed = "시고르자브종"; // 가능!!
- 다운캐스팅 할 때는 (캐스팅할 객체 타입) 캐스팅할 객체 형식으로 적어준다.
-
1차 및 2차 배열 선언하기
-
배열
배열을 선언하는 방법은 두 가지가 있다.
자료형[] 배열이름 = new 자료형[개수]; 자료형 배열이름[] = new 자료형[개수]; // example int[] arr = new int[10]; int arr[] = new int[10];
자바에서 배열을 선언하면, 그와 동시에 각 요소의 값이 초기화된다. 정수는 0, 실수는 0.0, 객체는 null로 초기화 되는데, 선언과 동시에 특정한 값으로 초기화 할 수도 있다. 초기화의 요소만큼 생성되므로 [] 안에 개수는 생략한다.
int[] arr = new int[] {1, 2, 3}; int[] arr = new int[3] {1, 2, 3}; // error!
new int 부분을 생략할 수도 있다.
int[] arr = {1, 2, 3};
-
2차원 배열
2차원 배열을 선언하는 방법은 다음과 같다.
타입 추론, var
타입 추론(Type Inference)
알지 못하는 타입들에 대해 컴파일러가 추론하는 과정을 타입 추론(type inference)라고 한다. Java 10 이전에서는 일반 변수에 대한 타입추론 (var)을 지원하지 않았기 때문에, 제네릭과 람다에 대해서만 타입 추론이 가능하였다.
public static final <T> List <T> emptyList() { ... };
List<String> names = Collections.emptyList(); // 컴파일러는 제네릭 타입이 String임 유추할 수 있다.
var
Java 10의 등장으로 일반 변수(local variable)에 대해서도 var 키워드를 추가하면서 타입 추론을 지원하였다. var는 선언과 동시에 초기화가 필수적이다.
컴파일러는 초기화된 값을 통해 타입을 추론한다.
var str = "hello";
하지만 다이아몬드 연산자와 함께 사용하면 컴파일러가 타입을 유추할 수 없기 때문에 컴파일 에러가 발생한다.
var str = new ArrayList<>();
출처
https://stepbystep1.tistory.com/23 [블로그]
https://docs.oracle.com/javase/specs/jls/se15/html/jls-4.html#jls-4.12.5 [공식문서]