목표 : 자바의 프리미티브 타입, 변수 그리고 배열을 사용하는 방법을 익힙니다.
1. 프리미티브 타입 종류와 값의 범위 그리고 기본 값
2. 프리미티브 타입과 레퍼런스 타입
3. 리터럴
4. 변수 선언 및 초기화하는 방법
5. 변수의 스코프와 라이프타임
6. 타입 변환, 캐스팅 그리고 타입 프로모션
7. 1차 및 2차 배열 선언하기
8. 타입 추론, var
9. 라이브 강의
10. 참고
프리미티브 타입 : 계산을 위해 실제 값을 저장하는 사전에 정의 된 정적 데이터 타입이다.
프리미티브 타입의 크기와 범위 그리고 기본값
데이터 타입 | 분류 | 크기 | 표현 가능 범위 | 기본값 |
---|---|---|---|---|
boolean | 논리형 | 1 바이트 | true, false | false |
char | 문자형 | 2 바이트 | 유니코드 문자 | '\u0000' |
byte | 정수형 | 1바이트 | -128 ~ 127 | 0 |
short | 정수형 | 2바이트 | -32,768 ~ 32,767 | 0 |
int | 정수형 | 4바이트 | -2,147,483,648 ~ 2,147,483,647 | 0 |
long | 정수형 | 8바이트 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 0L |
float | 실수형 | 4바이트 | +-(1.40 * 10 ^-45 ~ 3.40 * 10^38) | 0.0f |
double | 실수형 | 8바이트 | +-(4.94 * 10 ^-324 ~ 1.79 * 10^308) | 0.0d |
boolean flag = true;
char ch = 'A';
byte b = 1;
short s = 2;
int val = 4;
long big_val = 8L;
float f = 3.141592F;
double d = 3.141592653589793;
변수를 생성할 때 변수 이름과 같이 프리미티브 타입을 기술해야한다.
boolean
을 제외한 나머지 7가지 타입은 서로 연산과 변환이 가능하다.
기본값
- 전역 스코프에 한해 변수를 초기화하지 않았을 경우 들어가는 기본값
- 정확히 말하면 초기화되지 않은 필드는 컴파일러에 의해 적절한 기본값이 할당된다.
- 기본값은 일반적으로 데이터 유형에 따라 0 또는 null이 들어간다.
- 그러나 값을 표시하지 않아 혼란을 유발하게 하는 잘못된 프로그래밍 스타일이다.
쓰레기값
- 전역 스코프를 제외하고 변수를 초기화하지 않았을 경우 들어있는 값
- 메모리가 이전에 사용했던 값을 그대로 가지고 있다.
- 개발자가 의도한 값이 아니고 지워져야 하는 값으로
쓰레기값
이라고 부른다. - 쓰레기값을 처리하지 않으면 시스템은 거의 무조건 에러를 발생한다.
- 그러므로 우리는 변수 선언과 동시에 초기화 하는 습관을 길러야 한다.
자료형은 프리미티브 / 레퍼런스 이 2가지 타입으로 나뉜다.
프리미티브 타입 :
데이터 타입 | 크기 | 기본값 |
---|---|---|
boolean | 1 바이트 | false |
char | 2 바이트 | '\u0000' |
byte | 1바이트 | 0 |
short | 2바이트 | 0 |
int | 4바이트 | 0 |
long | 8바이트 | 0L |
float | 4바이트 | 0.0f |
double | 8바이트 | 0.0d |
- Access By Value
- 지정된 데이터 형식에 맞는 고정된 크기의 공간을 할당하고
데이터 값
을 저장한다. - 변수 선언, 초기화, 할당시 값이 저장된 메모리 영역에
직접적
으로 접근한다. - 즉, 변수에 새 값이 할당 될 때 변수에 할당된 메모리 블럭에 저장된 값을 바로 변경한다는 뜻이다.
- 변수와 데이터 값은 스택(Stack)영역에 저장된다.
- 지정된 데이터 형식이 있기 때문에 자료형이라는 용어를 사용한다.
레퍼런스 타입 :
타입 | 할당 메모리 | 기본값 |
---|---|---|
배열 (Array) | 4 bytes (객체의 주소값) | Null |
열거 (Enumeration) | 4 bytes (객체의 주소값) | Null |
클래스 (Class) | 4 bytes (객체의 주소값) | Null |
인터페이스 (Interface) | 4 bytes (객체의 주소값) | Null |
- Access By Reference
객체의 주소(4 byte 정수)
만큼 공간을 할당하고데이터 값
이 아닌메모리 주소
를 저장한다.- 변수에 저장된 메모리 주소를 '참조'하여 실제 대상을 조작하기에 레퍼런스 타입이라 부른다.
- 레퍼런스 타입을 자료형으로 가진 변수의 이름도
참조 변수
라고 부른다. - 기본 값으로 null 값이 들어가며 할당하지 않을 경우 런타임시에 NullPointException이 발생할 수 있다.
- 변수는 스택(Stack)영역에 저장되지만 주소값을 통해 힙(Heap)으로 부터 데이터를 참조한다.
- 참고로 참조 대상들은 힙(Heap) 영역에 존재하므로 GC 대상이 되어 삭제될 수 있다.
- 지정된 데이터 형식이 없기 때문에 타입이라는 용어를 사용한다.
- 프리미티브 타입 : 스택(stack) 영역에
변수
와데이터 값
이 저장된다. - 레퍼런스 타입 : 스택(stack) 영역에
변수
와주소값
이 저장된다.- 참조 변수들은 주소값을 통해 힙(Heap)에 존재하는 실제 데이터를 조작한다.
- 주소값은 대상의 가장 앞에 있는 메모리의 주소를 참조한다.
- 스택(stack)과 달리 참조 대상은 순서가 랜덤하게 들어있다.
프리미티브와 레퍼런스 복사 비교
- 프리미티브 타입 복사 :
- Deep Copy : 값을 복사한다.
- 값을 복사하기 때문에 특정 변수의 값을 변경해도 다른 변수에 영향을 미치지 않는다.
- 레퍼런스 타입 복사 :
- Shallow Copy : 주소값만 복사한다.
- 주소값만 복사하기 때문에 특정 변수의 참조 대상의 값을 바꾸면 다른 변수에도 영향을 미친다.
- 즉, 완전한 복사는 이루어지지 않은 것이다.
- 참고 사이트
String
- 우선, 이 부분에 대해서는 jaden94님의 블로그를 참조했습니다.
- 사실 우리가 자주 사용하는
String
문자열 자료형도레퍼런스 타입
이다. - Java는 예외적으로
java.lang.String
문자열에 대한 특수 지원을 제공한다.- String 타입은 literal 을 지원한다.
- 프리미티브 타입과 같이
String str = "ABCDEFG";
초기화를 하면 객체가 생성된다. - literal 방식으로 String 에 값을 주면
Heap 영역에서String constant pool
이라는 특수한 영역에 값이 저장된다. - 동일한 값을 쓰는 경우에 다른 일반적인 레퍼런스타입 처럼 Heap 에 또 올라가지 않고,
String constant pool 에 존재하는 값을 참조하는 방식으로 작동한다. - 이는 String의 특징과 Immutable instance의 특징의 결합으로 사용된 방법이다.
리터럴은 변수에 넣는 변하지 않는 데이터를 의미한다.
int year = 2020;
final int MAX_VALUE = 100;
// year : 변수
// MAX_VALUE : 상수
// 2020, 100 : 리터럴
- 현실에서는
상수
를 변하지 않는 데이터라고 표현한다. - Java에서
상수
는 변하지 않는 변수를 의미한다. - 그렇기 때문에 개발자들은
리터럴
이란 표현을 사용한다.
정수 리터럴 정수 리터럴은 10진법, 2진법, 8진법과 같은 다양한 표현 방식이 있다. 10진수 26을 다양한 리터럴로 표현해보자.
int decimal = 26; // 일반적인 형태 10진법
int ocatal = 032; // 제일 앞에 0 이 붙으면 8진법
int heaxaDecimal = 0x1a; // 0x가 붙으면 16진법
int binary = 0b11010; // 0b가 붙으면 2진법
JVM에서 설정한 정수 리터럴은 기본적으로 int 형이고, long 타입을 표현하려면 l,L을 접미사를 붙여야 한다.
실수 리터럴
JVM에서 설정한 실수 타입의 리터럴은 기본적으로 double 타입이고,
float 타입으로 표현하려면 f,F를 명시적으로 붙여야한다.
float f = 3.141592F;
double d = 3.141592653589793;
double dd = 1E-1; // 0.1
float
과double
은 정확한 값을 저장하는 것이 아닌 근사값을 저장하기에 다소 오차가 있다.- 단, 데이터 크기에 따라
float은 6자리까지
,double은 15자리까지
의 실수값을 보장한다. E
를 이용한 실수의 지수 표기법도 가능하다.
문자 리터럴
문자는 작은따옴표(''
)안에 표현할 수 있다
char a = 'a'; // a,
char
는 문자 타입이지만 사실 유니코드와 매칭된 숫자를 저장한다.- 위 결과
'a'
는 사실 97이라는 형태로 저장된다.
특수문자 | 문자 리터럴 |
---|---|
tab | \t |
backspace | \b |
form feed | \f |
new line | \n |
carriage return | \r |
역슬래쉬 | | |
작은따옴표 | ' |
큰따옴표 | " |
유니코드(16진수)문자 | \u유니코드 |
- 특수 문자 처리를 위한 리터럴도 존재한다.
- 이스케이프 문자 라고 부른다.
boolean 리터럴 true, false 로 표현할 수 있다.
boolean a = true;
boolean b = false;
리터럴간의 연산
System.out.println(1+2); // 출력 : 3, 자료형 : int
System.out.println(1+2L); // 출력 : 3, 자료형 : long
System.out.println(1+'a'); // 출력 : 98, 자료형 : int
System.out.println("123"+4); // 출력 : 1234, 자료형 : String
- 논리형을 제외한 리터럴끼리는 연산이 가능하다.
char
는 연산할 경우 숫자 형태로 연산이 진행되고- 나머지 리터럴은 더 큰 크기를 가진 리터럴의 형태로 오토 캐스팅 되어 연산이 된다.
- 문자열 같은 경우는 나머지 피연산자를 문자열로 치환후 문자를 결합한다.
리터럴에 대한 오해와 진실
int a = 10;
기존에 변수는 실제 데이터 값을 저장한다고 했다.
하지만, 이 같은 설명은 반은 맞고 반은 틀린 설명이다.
리터럴 또한 데이터이므로 메모리 어딘가에 저장되어 있다가 사용되어야 한다.
- 코드를 입력하면 변수를 메모리에 할당하고 리터럴을 메모리에 할당한다.
- 할당하는 과정에서는 랜덤한 메모리로 할당한다.
의문점 해설
public class Sample {
public static void main(String[] args) {
System.out.println(157); // 157은 어떤 데이터형일까?
int i = 10; // 접미사가 없다.
long l = 10L; // 접미사 L이 있다.
float f = 10.00001F; // 접미사 f가 있다.
double d = 10.000000000000001D; // 접미사 D가 있다.
long error = 10; // 에러 발생
}
}
종류 | 리터럴 | 접미사 |
---|---|---|
논리형 | false, true | 없음 |
정수형 | 123, 0b0101, 077, 0xFF, 100L | L |
실수형 | 3.14, 3.0e8, 1.4r, 0x1.0p-1 | F,D |
문자형 | 'A', '1', '\n' | 없음 |
문자열 | "ABC", "123", "A", "true" | 없음 |
- JVM은 기본 리터럴 타입에 대해서 정수형은 int로 실수는 double로 정의했다.
- 그렇기 때문에 만약 다른 리터럴 타입을 사용하고 싶다면 알맞는 접미사를 붙여야 한다.
- 참고로, int 와 double은 접미사를 붙이지 않아도 된다.
- 그 외의 자료형에 대해 접미사를 붙이지 않을 경우에는 에러가 발생하니 주의하자
- 리터럴은 메모리에 어딘가에 계속 상주해있다.
- 변수는 할당하려는 리터럴이 존재할 경우 자신에 메모리에 해당 값을 복사한다.
- 만약 할당하려는 리터럴이 존재하지 않을 경우는 JVM이 메모리를 새로 할당 후 복사한다.
==
동등 연산자는 같은 메모리를 참조하면 같으면 true, 다르면 false를 리턴한다.- 변수는 서로 다른 메모리에 존재하지만 저장된 값이 같으면 true를 리턴 한다.
여기서는 프리미티브 타입 변수 선언과 초기화에 대한 내용만 다룬다. 레퍼런서 타입 변수는 후에 클래스 주제에서 다루겠다.
변수 선언 : 자료형만큼의 메모리를 할당하고 이름을 붙이는 작업
int variable; // 변수 선언
// 전역이면 기본값, 지역이면 기존 메모리에 있던 쓰레기값이 들어있다.
- 자료형 크기 만큼의 메모리가 할당된다.
- 해당 메모리에 이름이 붙는다.
- 초기화를 진행하지 않았을 때 전역 스코프 변수라면 기본값이 들어간다.
- 초기화를 진행하지 않았을 때 지역 스코프 변수라면 쓰레기값이 들어간다.
변수 초기화 : 변수 선언과 동시에 값을 할당하는 작업
int variable = 10; // 변수 선언과 동시에 초기화
- 자료형 크기 만큼의 메모리가 할당된다.
- 해당 메모리에 이름이 붙는다.
- 값을 초기화 했으므로 메모리 공간에 지정된 값을 저장한다.
- 이 과정에서 메모리 어딘가에 값이 있으면 복사를 하고 없을 경우 새로 할당 후 넣는다.
- 물론, 초기화 후에도 새로운 값을 할당하는 것도 가능하다.
할당 : 변수에 값을 넣는 작업
int variable; // 변수 선언
vairable = 10; // 값 할당
int variable2 = 10; // 변수 선언과 동시에 초기화
vairable2 = 20; // 값 할당
- 변수에 데이터를 넣는 작업을
할당
이라고 부른다. - 앞서 말했듯이 값을 초기화해주지 않으면 기존 메모리에 존재하는 쓰레기값이 들어있다.
- 그러므로 할당을 통해 데이터를 넣는 작업을 해주는 것이 좋다.
- 물론, 초기화된 변수는 물론, 변수에 여러번 값을 할당하는 것도 가능하다.
상수 : 변하지 않는 변수로 값을 한 번 넣으면 바꿀 수 없다.
final int START_NUMBER = 1;
final int END_NUMBER;
END_NUMBER = 9; // 딱 1번은 인정
// START_NUMBER = 10; --> ERROR!
// END_NUMBER = 99; --> ERROR!
final
키워드를 붙이면 변하지 않는 모듈이 된다.(변수, 클래스, 메서드등)final 변수
는 일반적인 변수와 달리 값을 한 번 넣으면 바꿀수 없다.- 단, 변수 선언시 초기화를 진행하지 않았다면 딱 1번의 할당은 인정을 해준다.
- 값이 1번 들어간 이후에 추가적으로 할당을 하면 에러가 발생한다.
변수의 스코프 : 변수에 접근 할 수 있는 프로그램의 영역 또는 섹션을 나타낸다.
라이프타임 : 변수가 메모리에서 얼마나 오래 살아 있는지를 나타낸다.
변수의 범위와 수명을 정의하는 기준은 변수가 정의되는 방법과 위치
이다.
쉽게 말하면, 변수는 변수가 선언된 블록 내에서만 액세스 할 수 있다는 것이다.
public class scope_and_lifetime {
int num1, num2; // Instance Variables
static int result; // Class Variable
int add(int a, int b){ // Local Variables
num1 = a;
num2 = b;
return a+b;
}
public static void main(String args[]){
scope_and_lifetime ob = new scope_and_lifetime();
result = ob.add(10, 20);
System.out.println("Sum = " + result);
}
}
변수의 유형 및 범위 3가지 유형
- 클래스 변수
- 인스턴스 변수
- 지역 변수
클래스 볁수
public class Sample {
static int temp = 10;
}
- 클래스 내부, 블록의 외부에서 선언되고
static
으로 선언된 변수를클래스 변수
라고 한다. - 범위 : 클래스 전체에서 사용 가능 및 같은 클래스의 객체일 경우 값을 공유하면서 사용한다.
- 평생 : 프로그램 시작 후 프로그램이 끝날 때까지 존재한다.
- 클래스 변수는 해당 클래스 정보가 JVM에 읽히는 순간 메모리 공간에 할당되고 초기화된다.
- 그렇기에 생성자에서 초기화하는 일도 없고 1개만 존재하기에 다른 객체에서도 공유하면서 사용한다.
인스턴스 변수
public class Sample {
private int temp = 10;
}
- 클래스 내부에서 선언되었지만 메서드 및 블록 외부에서 선언 된 변수를
인스턴스 변수
라고 한다. - 범위 : static 메서드를 제외하고 클래스 전체에서 사용 가능하다.
- 수명 : 클래스를 인스턴스화한 객체가 메모리상에 사라지기 전까지 존재한다.
- static 메서드에서 사용 불가능한 이유는? :
- 인스턴스 변수 : 클래스가 인스턴스화하여 객체를 만들었을 때 메모리에 저장된다.
- 클래스 변수 : 프로그램을 시작했을시 클래스의 정보를 로드했을 때 메모리에 저장된다.
- 객체를 만들지 않았을 때 메모리에 존재하지 않는 인스턴스 변수를 사용하는 것은 문제가 있다.
- 자바에서는 이러한 문제를 컴파일타임에서 검사하여 에러를 내도록 했다.
지역 변수
public class Sample {
public int sampleMethod(int a, int b) {
int c = a+b;
return c;
}
}
- 인스턴스 또는 클래스 변수가 아닌 블록내에 존재하는 모든 변수를
지역 변수
라고 한다. - 메서드나 생성자에 존재하는 매개변수도
지역 변수
이다. - 범위 : 지역 변수가 선언된 특정 블록 내에서만 사용 가능하다.
- 수명 : 지역 변수가 선언된 블록을 떠날 때까지 존재한다.
지역 변수의 예시
// 잘못된 예시 - 인스턴스 변수의 값은 바뀌지 않지만 에러도 발생하지 않는다.
public class Sample {
private int var = 0;
public void setVar(int var) {
var = var;
}
}
// 좋은 예시 - 인스턴스 변수의 값이 바뀐다.
public class Sample {
private int var = 0;
public void setVar(int var) {
this.var = var;
}
}
- 메서드의 지역변수는 임시적인 데이터이므로 JVM스택(Stack) 영역에 저장된다.
- 클래스의 변수들은 힙(Heap)과 클래스(Class) 영역에 저장된다.
- 참고로, 인스턴스 변수는 힙(Heap)에, 클래스 변수는 클래스(Class) 영역에 저장된다.
- 메서드를 호출하면 기본적으로 JVM스택(Stack) 영역에 존재하는 데이터로 처리를 한다.
- 이 과정에서 변수 이름이 같으면 JVM스택(Stack) 영역에 존재하는 변수를 우선으로 처리한다.
- 이를 방지하고자 객체 자기 자신을 의미하는
this
키워드 를 사용한다. this.변수이름
으로 객체의 변수에 접근해 이름이 같아도 값을 할당하는 작업을 할 수 있다.- 참고로 여러 이름을 관리하는 것 보다는 이름을 통일해서 사용하는 것이 좋다.
타입 변환 :
캐스팅(casting) : 명시적으로 데이터 타입을 지정해 값을 해당하는 타입으로 바꾸는 것
프로모션(promotion) : 묵시적으로 데이터 타입을 지정하지 않고 값을 알맞는 타입으로 자동으로 바꾸는 것
타입 변환
int a = 10;
long b = 10L;
System.out.printpn(a+b); // 연산된 리터럴은 무슨 타입이지?
// 정답은 Long
int aa = 10;
byte bb = a; // 데이터 타입이 다르므로 컴파일 에러 발생
- Java에서 연산은
피연사자간의 타입이 같아야 된다.
라고 지정을 해놓았다. - 그렇기 때문에 연산을 위해 서로 다른 데이터 타입을 맞춰주는 작업을 해야한다.
캐스팅(casting)
int a = 10;
byte b = (byte)a;
System.out.printpn(b); // 10 출력
int aaa = 150;
byte bbb = (byte)aaa;
System.out.printpn(bbb); // -106 출력, Overflow 발생
- 캐스팅이란 명시적으로 데이터 타입을 변경하는 것을 의미한다.
- 즉, 내가 원하는 데이터 타입으로 값을 바꾸는 것이다.
- 현재 데이터 타입보다 작은 타입으로 변경을 해줄 때 사용된다.
- 단, 현재 데이터 타입보다 작은 타입으로 변경하면 오버플로우가 발생할 수 있다.
프로모션(promotion)
int a = 10;
System.out.println(3.14 + 10); // 13.14 출력, 리터럴 타입은 double
// '3.14 + 10' == '3.14 + (double)10'
int aa = 2_147_483_647;
long bb = aa; // '= aa' 는 '= (long)aa'가 된다.
System.out.printpn(bb); // 2147483647 출력
String oneToFourWord = "1234";
System.out.println(oneToFourWord+5); // 12345 출력, 리터럴 타입은 String
- 프로모션이란 묵시적으로 데이터 타입을 변경하는 것을 의미한다.
자동 형변환
이라고 불리기도 한다.- 우선순위에 따라 데이터를 자동으로 형변환 시켜준다.
- 작은 타입에서 큰 타입으로 변하는 것이기에 메모리의 손실은 거의 없다.
프로모션 우선순위
우선 순위 | 자료형 | 타입 | 크기 |
---|---|---|---|
1 | double | 실수 | 8byte |
2 | float | 실수 | 4byte |
3 | long | 정수 | 8byte |
4 | int | 정수 | 4byte |
5 | short, char | 정수, 문자 | 2byte |
6 | byte | 정수 | 1byte |
- 기본적으로
실수 > 정수, 문자
순으로 우선순위가 지정되어 있다. - 이외의 조건으로는 메모리 크기가 큰 순서대로 우선순위가 지정되어 있다.
- 서로 다른 자료형을 연산할 때 연산자 우선 순위가 큰 것으로 자동으로 변형된다.
배열 : 같은 타입의 여러 변수를 하나의 묶음으로 다루는 것이다.
- 배열은 레퍼런스 타입으로 배열 참조 변수가 생성된다.
new 데이터타입[크기]
만큼의 메모리를 할당하고 인스턴스를 생성한다.- 배열 인스턴스 안에 존재하는 length 인스턴스 변수에 크기 만큼의 값이 저장된다.
- 참조 변수는 배열로 할당된 메모리의 가장 앞에 있는 메모리를 참조한다.
배열의 선언
선언 방법 | 선언 예 |
---|---|
타입[]변수이름; |
int[]score; |
타입 변수이름[]; |
int score[]; |
int[] intArr = new int[크기]; // 타입[] 변수이름, 주로 이 방법을 사용한다.
String strArr[] = new String[크기]; // 타입 변수이름[];
- 배열의 선언 방법은 2가지이다.
- 주로 위에 있는 방법을 사용하며 밑에 있는 방법은 주로 C,C++에서 사용한다.
- 위에 있는 방법을 사용하는 이유는 가독성을 높이기 위해서이다.
배열의 초기화 및 할당
int[] score = new int[5];
score[0] = 50;
score[1] = 60;
score[2] = 70;
score[3] = 80;
score[4] = 90;
- 배열은 선언을 해주면 배열 크기가 생성된 것이지 값이 할당 된 것은 아니다.
- 그렇기 때문에 선언 후 값을 인덱스마다 하나씩 할당해주어야 한다.
- 아니면 반복적인 작업이기에 반복문을 사용해서 값을 할당해주어도 된다.
int[] score2 = int[5]{50, 60, 70, 80, 90};
int[] score3 = int[]{50, 60, 70, 80, 90}; // 크기 생략
- 선언 후 할당하는 것이 아닌
초기화
로 선언과 동시에 값을 넣어줄 수 있다. - 크기를 생략할 경우 크기가 고정되지 않은 가변적인 배열로 생성된다.
int[] score4 = {50, 60, 70, 80, 90};
초기화
로 작성시new 자료형[]
을 생략할 수 있다.new 자료형[]
를 생략할 경우 크기가 고정되지 않은 가변적인 배열로 생성된다.
int[] score5;
score5 = new int[5]{50, 60, 70, 80, 90};
score5 = new int[]{50, 60, 70, 80, 90}; // 가능
// score5 = {50, 60, 70, 80, 90}; // 불가능
- 배열 참조변수 선언 후 할당도
new 자료형[크기]
방식으로 해주면 된다. - 단
초기화
가 아닌할당
이므로{}
을 이용한 할당은 불가능하다.
public void method(int[] arr){
...
}
public void run(){
method(new int[]{50, 60, 70, 80, 90}); // 가능
method({50, 60, 70, 80, 90}); // 불가능
}
- 메서드 매개변수를 사용할 때는
new 자료형[]{}
으로 배열을 할당할 수 있다. - 단, 매개변수도 이미 변수가 선언된 형태이므로
{}
을 이용한 할당은 불가능하다.
2차원 배열의 선언
선언 방법 | 선언 예 |
---|---|
타입[][] 변수이름; |
int[][] score; |
타입 변수이름[][]; |
int score[][]; |
타입[]변수이름[]; |
int[]score[]; |
int[][] score = new int[3][5]; // 주로 이 방법을 사용
int score2[][] = new int[3][5];
int[] score3[] = new int[3][5];
- 2차원 배열은 1차원 배열과 비슷하지만
[]
이 하나 더 늘은 것이다. - 1차원과 마찬가지로 주로 데이터형 옆에
[][]
차원이 있는 방법을 쓴다.
2차원 배열의 초기화 및 할당
int[][] score = new int[3][3];
score[0][0] = 11;
score[0][1] = 22;
score[0][2] = 33;
score[1][0] = 44;
score[1][1] = 55;
score[1][2] = 66;
score[2][0] = 77;
score[2][1] = 88;
score[2][2] = 99;
- 2차원 배열도 선언을 해주면 배열 크기가 생성된 것이지 값이 할당 된 것은 아니다.
- 그렇기 때문에 선언 후 값을 인덱스마다 하나씩 할당해주어야 한다.
- 아니면 반복적인 작업이기에 반복문을 사용해서 값을 할당해주어도 된다.
int[][] score2 = int[3][3]{
{11, 22, 33},
{44, 55, 66},
{77, 88, 99}
};
int[][] score3 = int[][]{ // 크기 생략
{11, 22, 33},
{44, 55, 66},
{77, 88, 99}
};
int[][] score3 = int[][]{ // 크기 생략
{11},
{22, 33},
{44, 55, 66}
};
- 선언 후 할당하는 것이 아닌
초기화
로 선언과 동시에 값을 넣어줄 수 있다. - 크기를 생략할 경우 크기가 고정되지 않은 가변적인 배열로 생성된다.
- 가변적인 배열로 생성할 경우 각
행
마다 각각의열
의 크기를 조정할 수 있다.
int[][] score4 = {
{11, 22, 33},
{44, 55, 66},
{77, 88, 99}
};
int[][] score5 = {
{11},
{22, 33},
{44, 55, 66}
};
초기화
로 작성시new 자료형[][]
을 생략할 수 있다.new 자료형[][]
를 생략할 경우 크기가 고정되지 않은 가변적인 배열로 생성된다.new 자료형[][]
를 생략하는 경우도 각행
마다 각각의열
의 크기를 조정할 수 있다.
int[] score6;
score6 = new int[2][2]{
{11,22},
{33, 44}
};
score6 = new int[][]{ // 가능
{11,22},
{33, 44}
};
score6 = new int[][]{ // 가능
{11},
{22, 33}
};
/* 불가능
score6 = {
{11,22},
{33, 44}
};
*/
- 배열 참조변수 선언 후 할당도
new 자료형[크기][크기]
방식으로 해주면 된다. - 크기를 생략할 경우 앞선 내용처럼 각
행
마다 각각의열
의 크기를 조정할 수 있다. - 단
초기화
가 아닌할당
이므로{{}}
을 이용한 할당은 불가능하다.
public void method(int[][] arr){
...
}
public void run(){
method(new int[][]{{11,22}, {33, 44}}); // 가능
method({{11,22}, {33, 44}}); // 불가능
}
- 메서드 매개변수를 사용할 때는
new 자료형[][]{{}}
으로 배열을 할당할 수 있다. - 단, 매개변수도 이미 변수가 선언된 형태이므로
{{}}
을 이용한 할당은 불가능하다.
타입 추론 :
타입이 정해지지 않은 변수에 대해서 컴파일러가 변수의 타입을 스스로 찾아내는 기능
python
, javascript
, typescript
, go
, kotlin
등
현대에 주로 사용되는 대다수의 프로그래밍 언어들은 타입 추론을 지원한다.
타입 추론이란
개발자가 명시적으로 변수의 데이터 타입을 지정하지 않아도
컴파일러가 코드에 알맞는 타입을 찾아내어 해당 데이터 타입을 적용시키는 것이다.
장점으로 코드량을 좀더 줄이고, 코드의 가독성을 높일 수 있게 해준다.
var 타입 추론
int val1 = 10;
- 자바 10부터 타입 추론을 지원하는 var 자료형 추가되었다.
- 이 데이터 타입은
local variable
이면서 선언과 동시에initializer
가 필수적으로 요구된다. var
는 키워드가 아닌 자료형이며 함수 또는 변수 이름으로 사용하는 프로그램에 대한 하위 호환성을 보장한다.var
사용시 런타임 오버 헤드 가 없으며Java
를 동적 유형 언어로 만들지 않는다.- 변수의 유형은 여전히 컴파일 시간에 유추되며 나중에 변경할 수 없다.
var val2 = 10; // int
var val3 = 3.14; // double
var val4 = 2_147_483_648L; // Long
- 정수의 경우 기본 연산이
int
이므로int
로 추론될 가능성이 높다. - 실수의 경우 기본 연산이
double
이므로double
로 추론될 가능성이 높다.
var idToNameMap = new HashMap<Integer, String>();
클래스가 지정된 제네릭 객체
도var 타입 추론
을 할 수 있다.
@Test
public void whenVarInitWithString_thenGetStringTypeVar() {
var message = "Hello, Java 10";
assertTrue(message instanceof String);
}
- 스프링5 에서도 var 타입 추론이 가능해졌다.
var 타입 추론을 사용 못하는 경우
var n; // error: cannot use 'var' on variable without initializer
- 앞서 언급했듯이
var
는 초기화 없이는 작동하지 않는다.
var emptyList = null; // error: variable initializer is 'null'
- 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
- Lambda 표현식에는 명시적인 대상 유형이 필요하므로
var
를 사용할 수 없다.
var arr = { 1, 2, 3 }; // error: array initializer needs an explicit target-type
- 배열의 초기화도 사용하지 못한다.
var 타입 추론의 주의점
var name = new List<>();
비어있는 제네릭 연산자
은 컴파일 에러가 발생한다.
var result = obj.prcoess();
- 타입이 드러나있지 않으므로 타 개발자들이 코드를 이해하기 어려울 수 있다.
var x = emp.getProjects.stream()
.findFirst()
.map(String::length)
.orElse(0);
- 파이프 라인이 긴 스트림에서 사용할 때 예상과는 다른 결과로 이뤄질 수 있다.
@Test
public void whenVarInitWithAnonymous_thenGetAnonymousType() {
var obj = new Object() {};
assertFalse(obj.getClass().equals(Object.class));
obj = new Object(); // error: Object cannot be converted to <anonymous Object>
}
- 선언 할 수없는 유형에
var
를 사용하면 예기치 않은 오류가 발생할 수 있다. - 예를 들면, 익명 클래스 인스턴스와 함께 var를 사용하는 경우이다.
- Object가 obj의 추론 유형이 아니기 때문에 에러가 발생한다.
제네릭 타입 추론
// Collections.emptyList() 의 메소드 시그니쳐
// 필자가 실제로 구현한게 아니라 Collections에 아래와 같이 존재한다는 의미이다.
public static final <T> List<T> emptyList() { ... }
// 이런 메소드가 있다고 하자
static void processNames(List<String> names) {
for (String name : names) {
System.out.println("Hello " + name);
}
}
// 컴파일러는 제네릭 타입이 String 이라고 유추할 수 있음
List<String> names = Collections.emptyList();
// 컴파일러는 제네릭 타입이 Integer 라고 유추할 수 있음
List<Integer> integerList = new ArrayList<>();
List<Integer> integerList2 = new ArrayList(); // 이렇게도 가능하다.
// 이런 메소드가 있다고 하자
processNames(Collections.emptyList()); // ERROR in Java 7
processNames(Collections.emptyList()); // OK in Java 8
Java 7
이전에는 제네릭의 타입을 알 수 없었다.- 그렇기 때문에
Collections.emptyList()
의 결과로List<Object>
을 리턴했다. Java 8
이 되고나서타입 추론
기능이 강화되어 위 같이 사용할 수 있게 되었다.- 현재는 '제네릭 참조 변수'의 데이터 타입을 통해
타입 추론
을 할 수 있다.
람다 타입 추론
@FunctionalInterface
interface Printable{
void print(String s);
}
public class OneParamNoReturn {
public static void main(String[] args) {
Printable p;
p = (String s) -> { System.out.println(s); }; // 타입을 명시
p.print("Lambda parameter type is defined.");
p = (s) -> System.out.println(s); // 타입을 명시하지 않음
p.print("Lambda parameter type is not defined.");
}
}
- 람다에서는 함수형 인터페이스에 매개변수 타입이 기술되어 있으므로 가능하다.
- 함수형 인터페이스란 쉽게 말해, 인터페이스의 함수가 1개밖에 없는 것을 의미한다.
- 기본적으로 람다는 함수형 인터페이스를 사용할 경우 가능하다.
- 인터페이스에
@FunctionalInterface
를 주어 컴파일에게 함수형 인터페이스임을 미리 알리자. @FunctionalInterface
를 선언하면 인터페이스는 함수형 인터페이스로만 정의해야한다.- 함수형 인터페이스 함수의 시그니쳐가 정의되어 있기 때문에
컴파일러가 이 정보를 참고해서 람다에서 생략된 정보들을 추론할 수 있게 된다. - 즉, 추상 메서드의 시그니쳐를 통해 컴파일러는 람다에서 생략된 데이터 타입을 추론할 수 있다.
- 주의점은 데이터 타입이 명시적으로 드러나지 않아서 협업 개발에 혼란을 가중시킬 수 있다.
- 그렇기 때문에 람다에서 매개변수 타입을 명시하는 것은 일반적인 관례이다.
타입 추론 참고