객체의 번지를 참조하는 타입
| 타입 명 | 특징 |
|---|---|
| 기본 타입 | 값 자체를 저장 |
| 참조 타입 | 객체가 생성된 메모리 번지를 저장 |
변수들은 모두 스택이라는 메모리 영역에 생성된다. 기본 타입 변수의 직접 값을 저장하고 있지만, 참조 타입 변수는 힙 메모리 영역의 String 객체 번지를 저장하고, 이 번지를 통해 String 객체를 참조한다.
int age=25;
double price=100.5;
String name="신용권";
String hobby="독서";

java 명령어로 JVM이 구동되면 JVM은 운영체제에서 할당받은 메모리 영역을 다음과 같이 구분해서 사용한다.

참조 타입 변수의 ==, != 연산자는 값이 아닌 번지를 비교한다. 번지가 같다면 동일한 객체를 참조하는 것이고, 다르다면 다른 객체를 참조하는 것이다.
객체를 비교하는 코드는 if문에서 많이 사용된다.
package ch05.sec03;
public class ReferenceVariableCompareExample {
public static void main(String[] args) {
int[] arr1; //배열 변수 arr1 선언
int[] arr2; //배열 변수 arr2 선언
int[] arr3; //배열 변수 arr3 선언
arr1 = new int[]{1, 2, 3}; //배열 { 1, 2, 3 }을 생성하고 arr1 변수에 대입
arr2 = new int[]{1, 2, 3}; //배열 { 1, 2, 3 }을 생성하고 arr2 변수에 대입
arr3 = arr2; //배열 변수 arr2의 값을 배열 변수 arr3에 대입
System.out.println(arr1 == arr2); // arr1과 arr2 변수가 같은 배열을 참조하는지 검사
System.out.println(arr2 == arr3); // arr2와 arr3 변수가 같은 배열을 참조하는지 검사
}
}
같은 값이 저장돼있는 것 같아도 저장돼있는 객체 번지의 주소가 다르다면 서로 다른 배열로 판단된다.
스택은 자동으로 초기화 되지 않는다, 계속 초기화 해주면 거기에서 오는 시간 사용이 너무 크기 때문! 초기화를 해주지 않으면 어떻게 값을 초기화해야 하나? 예전에 사용하던 쓰레기 값이 그대로 남아있다. 그렇기 때문에 지역변수는 반드시 초기화 해줘야 한다
힙 영역은 자동으로 0으로 초기화 해준다
참조 타입 변수는 아직 번지를 저장하고 있지 않다는 뜻
null로 초기화된 참조 변수는 스택 영역에 생성된다. 참조 타입 변수가 null 값을 가지는지 확인하려면 ==, != 연산을 수행하여 확인할 수 있다.
String refVar = null;
refVar == null; //결과: true
refVar != null; //결과: false
변수가 null인 상태에서 객체의 데이터나 method를 사용하려 할 때 발생하는 예외
참조 변수를 사용하면서 가장 많이 발생하는 예외 중 하나이다.
int[] intArray = null;
intArray[0] = 10; //NullPointerException
intArray가 참조하는 배열 객체가 없으므로 10을 저장할 수 없기 때문에 예외가 발생한다.
이런 오류가 발생할 확률이 높은 null 값을 왜 사용할까?
자바는 쓰레기 객체를 모니터링하여 참조가 없는 객체를 자동으로 처리해준다. 쓰레기 수집기가 주기적으로 메모리를 검사하여 참조가 없는 객체를 제거하고, 이를 통해 메모리의 낭비를 막고 효율적으로 메모리를 관리할 수 있다.
개발자는 더이상 사용하지 않을 값에 대해서 null로 저장을 함으로써 Garbage Collector가 객체를 삭제하도록 유도하는 것이다.
null은 위험하지만 메모리 해제와 상태 표현에 있어서 매우 중요한 역할을 하는 값이다.
자바에서 문자열은 String 객체로 생성된다.
String 객체의 문자열을 변경이 불가능한 특성을 가지고 있다.
숫자인데, 의미는 숫자가 아닌 주민등록번호 같은 것은 문자열로 처리해줘야 한다. 형식만 숫자이지 각 자릿수마다의 의미가 따로 있는 문자의 특성을 띈다.
자바는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 설계돼있다.
String name1="한지성";
String name2="한지성";

new 연산자로 직접 String 객체를 생성하여 사용할 수도 있다. new 연산자는 새로운 객체로 만드는 연산자로 객체 생성 연산자라고 한다.
String name1 = new String("한지성");
String name2 = new String("한지성");

package ch05.sec05_string;
public class EqualsExample {
public static void main(String[] args) {
String strVar1 = "한지성";
String strVar2 = "한지성";
if (strVar1 == strVar2) {
System.out.println("strVar1과 strVar2는 참조가 같음");
} else {
System.out.println("strVar1과 strVar2는 참조가 다름");
}
if (strVar1.equals(strVar2)) {
System.out.println("strVar1과 strVar2는 문자열이 같음");
}
String strVar3 = new String("한지성");
String strVar4 = new String("한지성");
if (strVar3 == strVar4) {
System.out.println("strVar3과 strVar4는 참조가 같음");
} else {
System.out.println("strVar3과 strVar4는 참조가 다름");
}
if (strVar3.equals(strVar4)) {
System.out.println("strVar3과 strVar4는 문자열이 같음");
}
}
}
문자열을 문자열 리터럴로 생성하느냐, new 연산자로 생성하느냐에 따라 비교 연산자의 결과가 달라질 수 있다. strVar1==strVar2은 true의 값이 나오지만 strVar3==strVar4는 서로 다른 객체이기 때문에 참조하는 주소의 번지가 달라 false 값이 나온다.
만약 내부 문자열만 동일한지 판단하고 싶다면 String 객체의 equals() method를 사용해서 판단해야 한다.
charAt()method는 매개값으로 주어진 인덱스의 문자를 리턴한다.
String day = "월화수목금 토일";
char charVal = day.charAt(6); //토
공백도 하나의 문자로 판단한다.
length() method는 문자열 안에 있는 문자의 개수를 리턴한다.
String day = "월화수목금 토일";
int length = day.length(); //8
replace() method는 문자열 내에서 특정 문자열을 다른 문자열로 대체할 수 있다. 기존 문자열은 그대로 두고, 대체한 새로운 문자열을 리턴한다. 기존 문자열에 변화는 없다.
package ch05.sec05_string;
public class ReplaceExample {
public static void main(String[] args) {
String oldStr = "자바 문자열은 불변입니다. 자바 문자열은 String입니다.";
String newStr = oldStr.replace("자바", "JAVA");
System.out.println(oldStr);
System.out.println(newStr);
}
}
해당 문자열 내에 대치되는 모든 문자열을 대체한다.
substring()method는 문자열에서 특정 위치의 문자열을 잘라내어 새로운 문자열을 반환한다.
| method | 설명 |
|---|---|
substring(int beginIdx) | beginIdx에서 끝까지 잘라내기 |
substring(int beginIdx, int endIdx) | beginIdx에서 endIdx-1까지 잘라내기 |
indexOf() method는 문자열에서 특정 문자열이 시작되는 인덱스를 반환한다.
만약에 주어진 문자열이 문자열에 포함되어 있지 않다면 indexOf()는 -1을 반환함으로써 해당 문자열을 찾을 수 없음을 반환한다.
contains()단순히 문자열이 포함되어 있는지를 판단한다. boolean 타입으로 포함하면 true, 포함하지 않으면 false를 리턴한다.
package ch05.sec05_string;
public class IndexOfContainsExample {
public static void main(String[] args) {
String subject = "자바 프로그래밍";
int location = subject.indexOf("프로그래밍");
System.out.println(location);
String substring = subject.substring(location);
System.out.println(substring);
location = subject.indexOf("자바");
if (location != -1) {
System.out.println("자바와 관련된 책이군요.");
} else {
System.out.println("자바와 관련 없는 책이군요.");
}
boolean result = subject.contains("자바");
if (result) {
System.out.println("자바와 관련된 책이군요.");
} else {
System.out.println("자바와 관련 없는 책이군요.");
}
}
}
split()method는 문자열이 특정 구분자를 사용하여 여러개의 문자열로 구성이 돼있을 경우, 이를 따로 분리해준다.
String board = "번호,제목,내용,성명";
String boardArr = board.split(",");

변수 하나에 하나의 값만 저장할 수 있다. 저장해야 할 값의 수가 많아지면 그만큼 많은 변수가 필요하게 된다. 이때 많은 변수를 일일히 관리하는 것은 매우 비효율적인 과정이다.
비슷한 특성을 가진 변수들을 관리하기 위해서 고안된 것이 배열이라는 개념이다.
연속된 공간에 값을 나열시키고, 각 값에 인덱스를 부여해 놓은 자료구조
int[] intArr;
double[] doubleArr;
String[] strArr;
int intArr[];
double doubleArr[];
String strArr[];
관례적으로 첫번째 방법을 표준 선언 방식으로 사용하고 있다.
타입[] 변수 = null;
배열 변수는 참조 변수이고, 배열은 객체이다. 배열은 힙 영역에 생성되고, 배열 변수는 힙 영역의 배열 주소를 저장한다. 참조할 배열이 아직 없다면 배열 변수도 null로 초기화해둘 수 있다.
타입[] 변수 = {값0, 값1, 값2, 값3, ...};
배열에 저장될 값의 목록이 있다면, 선언과 동시에 값을 넣어서 배열을 생성할 수 있다.
선언과 동시에 초기화를 진행해줘야 한다.
타입[] 변수;
변수 = {값0,값1,값2,값3, ...}; //컴파일 에러
만약 이렇게 선언된 배열 변수에 값 목록을 변수에 바로 대입한다면 컴파일 에러가 발생한다. 배열 변수를 선언한 시점과 값 목록이 대입되는 시점이 다르다면 new 타입[]을 중괄호 앞에 붙여줘야 한다.
변수 = new 타입[] {값0,값1,값2,값3, ...};
값의 목록은 없지만 향후 값들을 저장할 목적으로 배열을 미리 생성할 수 있다. new 연산자를 다음과 같이 사용하면 배열 객체를 생성시킨다.
타입[] 변수 = new 타입[길이];
타입[] 변수 = null;
변수 = new 타입[길이];
길이는 배열이 저장할 수 있는 항목 수이다.
new 연산자로 배열을 처음 생성하면 배열 항목은 기본값으로 초기화가 된다.
| 데이터 타입 | 초기값 | |
|---|---|---|
| 기본 타입 | byte[] | 0 |
char[] | '\u0000’ | |
short[] | 0 | |
int[] | 0 | |
long[] | 0L | |
float[] | 0.0F | |
double[] | 0.0 | |
boolean[] | false | |
| 참조 타입 | 클래스[] | null |
인터페이스[] | null |
코드에서 배열의 길이를 얻으려면 .연산자를 사용해서 참조하는 배열의 length 필드를 읽으면 된다.
배열변수.length;
length 필드는 읽기 전용이기 때문에 해당 필드의 값을 변경할 수는 없다.
for문을 사용할 때 조건식에 길이를 직접 넣는 것이 아닌 해당 필드를 이용해서 유동적으로 길이가 자동으로 잡히도록 해줘야 한다.
배열 항목에 또 다른 배열이 대입된 배열

각 차원의 배열은 하위 배열의 첫번째 인덱스 주소를 참조한다. 다차원 배열은 1차원 배열을 서로 연결한 구조이다.
arr[0]과 arr[1]는 단지 서로 다른 1차원 배열을 참조하고 있을 뿐이다. 물리적으로 연속적인 메모리에 존재할 필요는 없다. 자바에서 2차원 배열은 본질적으로 배열을 요소로 가지는 배열이다.
배열 변수 선언 시 타입 뒤에 대괄호 []를 차원의 수만큼 붙이고, 값 목록도 마찬가지로 차원의 수만큼 중괄호를 중첩시킨다.
int[][] scores={
{80,90,96},
{76,78}
};
scores.length //반의 수:2
scores[0].length //첫번째 반의 학생 수:3
scores[1].length //두번째 반의 학생 수:2
반의 개수는 1차원 배열의 길이와 동일하고, 각 반의 학생 수는 2차원 배열의 길이와 동일하기 때문에 length필드로 반의 개수와 학생 수를 알 수 있다.
각 배열 요소는 각각 독립된 객체이기 때문에 배열의 길이도 서로 다를 수 있다. 그래서 반복문을 돌릴 때는 반드시 바깥쪽 배열과 안쪽 배열의 길이를 조건문마다 판단하면서 처리해야 한다.
for (int i = 0; i < scores.length; i++) {
for (int j = 0; j < scores[i].length; j++) {
System.out.println(scores[i][j]);
}
}
타입[][] 변수 = new 타입[1차원수][2차원수];
new 타입 뒤에도 차원의 수만큼 대괄호를 작성한다.배열을 new로 선언하면 배열은 각 타입의 기본값으로 자동 초기화가 된다. 2차원 배열은 단순히 참조돼있는 것이기 때문에 길이가 달라도 전혀 문제가 되지 않는다.
참조를 실제 가리키고자 하는 값으로 바꾸고자 할 때, 참조 주소를 저장해야 할 곳에 또 다른 배열을 생성해주면 된다.
int[][] scores = new int[2][];
scores[0] = new int[3];
scores[1] = new int[2];
scores는 2개의 배열을 참조하고 있고, 각각은 서로 다른 길이의 배열을 가리키게 된다.
해당 형식을 사용해서 길이와 참조주소가 정해지게 된다.
배열에는 모든 타입이 저장될 수 있다. 배열의 값이 클래스 타입이라면 기본형타입과 구동방식은 똑같지만 값이 저장되는 것이 아니라 참조가 저장되는 것이다.
package ch05.sec08_arrref;
public class ArrayReferenceObjectExample {
public static void main(String[] args) {
String[] strArray = new String[3];
strArray[0] = "Java";
strArray[1] = "Java";
strArray[2] = new String("Java");
System.out.println(strArray[0] == strArray[1]); //true: 같은 객체 참조
System.out.println(strArray[0] == strArray[2]); //false: 다른 객체를 참조
System.out.println(strArray[0].equals(strArray[2])); //true: 문자열이 동일
}
}
배열은 한 번 생성하면 길이를 변경할 수 없는 특성을 가지고 있다. 그런데 코드를 진행하는 중에 배열의 크기를 변경해서 새로운 데이터를 저장해야 하는 상황이 올 수도 있다. 이때는 원하는 크기의 배열을 새로 만들고 이전 배열로부터 항목들을 복사해서 생성해야 한다.
for문을 이용해서 항목을 하나씩 읽고 새로운 배열에 저장한다.
package ch05.sec09_arrcopy;
public class ArrayCopyByForExample {
public static void main(String[] args) {
//길이 3인 배열
int[] oldIntArray = {1, 2, 3};
//길이 5인 배열을 새로 생성
int[] newIntArray = new int[5];
//배열 항목 복사
for (int i = 0; i < oldIntArray.length; i++) {
newIntArray[i] = oldIntArray[i];
}
//배열 항목 출력
for (int i = 0; i < newIntArray.length; i++) {
System.out.print(newIntArray[i] + ", ");
}
}
}
System의 arraycopy() method를 사용해서 한줄만에 배열 복사를 진행할 수 있다.
System.arraycopy(Object src, int srcPos, Object dest, int destPost, int length)
//길이 3인 배열
String[] oldStrArray = {"java", "array", "copy"};
//길이 5인 배열을 새로 생성
String[] newStrArray = new String[5];
//배열 항목 복사
System.arraycopy(oldStrArray, 0, newStrArray, 0, oldStrArray.length);
for(타입 변수: 배열){
실행문;
}
각 배열의 값이 궁금하여 처리하고 싶을 때 사용한다. 인덱스의 값을 알 수 없다.
main() method에서 매개변수로 왜 String[] args가 필요한가?
자바 프로그램은 기본적으로 main() method부터 실행된다. 이 때 String[] args를 통해서 외부에서 값을 넘길 수 있다. String[] args는 명령행 인자를 받아서 프로그램 안에서 활용할 수 있도록 해주는 역할을 한다.


한정된 값을 갖는 타입
열거 타입도 하나의 데이터 타입이므로 변수를 선언하고 사용해야 한다.
컴퓨터의 날짜 및 요일, 시간을 얻을 때 사용한다.
Calendar now=Calendar.getInstacne();
int year=now.get((Calendar.YEAR);
int month=now.get((Calendar.MONTH);
int day=now.get((Calendar.DAY_OF_MONTH);
int week=now.get((Calendar.DAY_OF_WEEK);
int minute=now.get((Calendar.MINUTE);
int seconde=now.get((Calendar.SECOND);