ultra_dev
이것이 자바다 CH 5(참조타입) 본문
☑️ 참조타입 : 객체의 번지를 참조하는 타입(배열, 열거 , 클래스, 인터페이스)
객체 = 데이터(필드) + 메소드
기본타입 변수는 값 자체를 저장하고 있지만
참조타입 변수는 객체가 생성된 메모리 번지를 저장
ex)
int age = 15;
String name = “웅”;
Stack 영역
age 15
name 100
age는 15라는 값 자체를 가지고 있지만
name의 100은 -> 힙 영역의 100번지에 존재하는 String 객체 “웅”을 참조한다는 뜻임
☑️ JVM 구동 → 운영체제에서 메모리 할당 받음 → 이때 메모리는 메소드, 힙, 스택으로 구분
- 메소드 영역 클래스 별로 저장. 바이트 코드 파일(.class)의 내용이 저장되는 영역 ex)상수, 정적 필드, 메소드 코드 , 생성자 코드
- 힙 영역 객체가 생성되는 영역 힙 영역 내 객체의 번지는 메소드 영역과 스택 영역의 상수,변수에서 참조 가능
- 스택 영역 메소드 호출할 때 생기는 프레임이 저장되는 영역 호출과 관계되는 각종 변수 등이 저장되는 영역 스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸
☑️ == 연산은 변수의 값이 같은지 아닌지 판단
참조 타입 변수의 값은 객체의 번지이므로 참조 타입에서는 객체의 번지 수를 비교하는 것
ex)
int[] arr1 = new int[] {1,2,3}
int[] arr2 = new int[] {1,2,3}
내용물의 값은 동일하지만 서로 다른 배열 객체를 참조하므로 ==연산하면 false가 나옴
☑️ Null로 초기화된 참조 변수는 스택 영역에 생성됨
스택 영역
var1null
☑️ 가비지컬렉터
String name = “웅”;
name = null;
이런 식으로 만약 참조 타입에 null이나 다른 값을 대입하면 name은 힙 영역의 “웅” 과 연결이 끊김
이후 다른 곳 어디에서도 “웅” 객체를 참조를 하지 않는다면
해당 객체는 더 이상 쓸모 없게 됨
붕 떠버린 “웅” 객체는 가비지컬렉터(Garbage Collector)가 없애버림🥲
자바에서는 코드로 객체를 직접 제거하는 방법을 제공하지 않기 때문에
객체를 제거하려면 해당 객체의 모든 참조를 없애면 됨. 그러면 가비지컬렉터가 제거해줌
☑️ 문자열(String) 타입
자바는 문자열 리터럴(””로 묶은 문자 집합)이 동일하면 String 객체를 공유하도록 설계되어 있음!
Stirng name1 = “웅”;
String name2 = “웅”;
→ 서로 같은 객체를 공유함
하지만 new연산자를 사용하면 서로 다른 객체를 가지게 됨
String name1 = “웅”;
String name2 = new String(“웅”);
→ 서로 다른 객체
위에는 “웅”이라는 공통 String 객체
밑에는 별개의 새로운 String 객체
☑️ 문자열(String) 관련 메소드
- 특정 문자 위치 .charAt(인덱스)
- 특정 문자열 대체 .replace(”a”,”b”) → a를 b로 바꿈아예 새로운 문자열을 만드는 것
- 원본은 힙에 그대로 존재하고 새로 수정된 문자열을 추가로 만드는 것
- 이때 String 객체 문자열은 변경 불가능한 특성 때문에 원래 문자열의 수정본이 아니라
- 문자열 잘라내기.substring(인덱스a,인덱스b) →a부터 b-1 인덱스까지 잘라내기
- .substring(인덱스) → 해당 인덱스부터 끝까지 잘라내기
- 문자열 찾기 .indexOf(”문자열”) → 해당 문자열이 시작되는 인덱스를 리턴 만약 해당 문자열이 포함되어 있지 않으면 -1을 리턴
- 문자열 포함 여부 확인하려면 .contains(”문자열”) 사용 존재하면 true 아니면 false 리턴
- 문자열 분리(분리된 문자열로 구성된 배열(array)로 리턴) .split(””) → “”사이에 원하는 구분자 넣어줘서 그걸 기준으로 나누기 가능 ex) String[] arr = board.split(””);
☑️ .length() : String 길이
.length : 배열 길이
.size() : 컬렉션타입 길이(ArrayList 등)
- length 와 length() 의 차이점 length는 상수이고 length()는 메소드 배열에서 사용 가능한 length는 최초 배열이 생성 될 때 길이가 결정 되는 상수 String의 length() 메소드는 호출 될 때 (가변적) 문자의 길이를 결정하는 변수
☑️ new 연산자로 다차원 배열 생성
* 타입[][] 변수 = new 타입 [1차원수][2차원수]
int [][] scores = new int[2][3];
* 만약 배열 숫자가 공통적이지 않은 비정방형 배열이라면
int[][] scores = new int[2][];
scores[0] = new int[3];
scores[1] = new int[2];
-> 출력값(int형이니 초기값 0)
[[0, 0, 0], [0, 0]]
----------------------------------------------------------------------------------------
만약 비정방형 3차원 배열을 만드려면
int[][][] scores2 = new int[2][][];
scores2[0] = new int[3][];
scores2[1] = new int[2][];
//이런식으로 첫번째부터 채워넣어줘야지
//위에꺼 생략하고 바로 scores2[0][0] = new int[3]; 이런식으로 들어가면 널포인터오류 뜸
scores2[0][0] = new int[]{1, 2, 3};
scores2[0][1] = new int[]{1, 2, 3};
scores2[0][2] = new int[]{1, 2, 3};
scores2[1][0] = new int[]{1, 2};
scores2[1][1] = new int[]{1};
System.out.println(Arrays.deepToString(scores2));
출력값 : [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2], [1]]]
☑️ 배열 출력 Arrays.toString();
public class PrintArray {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr);
}
}
**출력값
[I@762efe5d -> 메모리의 주소값**
📌 배열 출력시
1)반복문 사용하거나
2)Arrays.toString() 이용하기
1-1) for 사용
public class PrintArray {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
}
}
1-2)for each문
public class PrintArray {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
for(int i : arr){
System.out.print(i);
}
}
}
**출력값
12345**
2) Arrays.toString() 이용하기
import java.util.Arrays;
public class PrintArray {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(Arrays.toString(arr));
}
}
**출력값
[1, 2, 3, 4, 5]**
☑️ 다차원 배열 출력 Arrays.deepToString();
다차원 배열은 Arrays.deepToString(); 사용하기
💥다차원 배열은 Array.toString() 하면 밑에처럼 주소값 뜨기 때문
int[][] scores = new int[2][];
scores[0] = new int[] {1,2,3};
scores[1] = new int[] {1,2};
System.out.println(<Arrays.ToString(scores));
**출력값
[[I@58ceff1, [I@7c30a502]**
System.out.println(Arrays.deepToString(scores));
**출력값
[[1, 2, 3], [1, 2]]**
☑️ 배열 복사
배열은 한번 생성하면 길이를 변경할 수 없으니 공간 더 필요하면
더 큰 길이 배열 만들고 이전 배열 복사해서 사용 가능
int[] oldIntArray = {1,2,3};
int[] newIntArray = new int[5];
for(int i=0; i<oldIntArray.length; i++) {
newIntArray[i] = oldIntArray[i];
}
-> for문 돌면서 복사
복사되지않은 4,5번 인덱스는 초기값으로 됨
ex)int[]면 {1,2,3,0,0}
⭐밑에 방식으로 간단히 복사 가능
❗ 간단히 복사하기 ❗(하지만 2차원 배열에서 얕은 복사 문제 생김!!)
☑️ System.arraycopy() 메소드
System.arraycopy(원본배열, 원본배열 복사 시작 인덱스,새 배열,새 배열 붙여넣기 시작 인덱스,복사 항목 수);
System.arraycopy(arr1,0,arr2,0,arr1.length);
arr1을 0번 인덱스부터 복사해서
arr2의 0번 인덱스부터 arr1.length 수만큼 붙여넣기
마찬가지로 남는 부분들은 초기값으로 설정됨
💥☢️
문제점! (얕은 복사시, 원본 값도 바뀌는 문제 생김)
깊은 복사(Deep Copy)는 '실제 값'을 새로운 메모리 공간에 복사하는 것을 의미하며, 복사된 배열이나 원본배열이 변경될 때 서로 간의 값은 바뀌지 않음
(복사값을 바꿔도 원본값 안바뀜)
얕은 복사(Shallow Copy)는 '주소 값'을 복사한다는 의미하며, 복사된 배열이나 원본배열이 변경될 때 서로 간의 값이 같이 변경됨
(복사값을 바꿨는데 원본값도 바뀜)
얕은 복사의 경우 주소 값을 복사하기 때문에, 참조하고 있는 실제값은 같음
→ 알고리즘 문제 풀 때 얕은 복사 하다가 원본값도 바껴서 오류 날 수 있으니 조심!!
❗얕은 복사,깊은 복사 (1차 배열과 2차 배열에서 깊은 복사 가능한 메소드가 다름)
📋<1차 배열>
- 1차 배열에서 얕은 복사
📌 (=)
int[] arr = {1,2,3};
int[] copy = arr;
copy[1] = 3;
->int[] arr = {1,3,3}
->int[] copy = {1,3,3}
즉, copy 배열 수정하면 arr배열도 수정 돼버리는 어이 없는 상황 발생
→ 얕은 복사는 주소 값을 복사하기 때문에 복사 값을 바꾸면 원본값도 바껴버림!!
- 1차 배열에서의 깊은 복사
📌 **.clone()**
<int[] arr = {1,2,3};
int[] copy = arr.clone();
copy[1] = 3;
-> int[] arr = {1,2,3}
-> int[] copy = {1,3,3}
📌 System.arraycopy()
int[] arr = {1,2,3};
int[] copy = new int[arr.length];
System.arraycopy(arr, 0, copy, 0, 3);
//원본배열, 원본배열 복사 시작 인덱스,새 배열,새 배열 붙여넣기 시작 인덱스,복사 항목 수
copy[1] = 3;
-> int[] arr = {1,2,3}
-> int[] copy = {1,3,3}
📌 for문으로 직접 돌아가며 찍기(어느 상황에서든 제일 확실)
int[] arr = {1,2,3};
int[] copy = new int[3];
for(int i=0; i<arr.length; i++) {
copy[i] = arr[i];
}
copy[1] = 3;
-> int[] arr = {1,2,3}
-> int[] copy = {1,3,3}
📋<2차 배열>
- 2차 배열에서의 얕은 복사 (💥for문 포함 안되면 전부 얕은 복사)
일어나는 이유 :
객체 배열의 경우 각각 주소값을 가지고 있기 때문에 깊은 복사가 안되는 것!!
arr[x][y]에서
arr[x]부분까지만 깊은 복사가 되고 arr[x][y] 부분은 깊은 복사가 되지 않음
왜냐하면 a[x]에는 y좌표를 가리키는 주소값만 있기 때문
같은 주소값을 복사하니 복사본을 바꾸면 원본도 바뀌는 얕은 복사 문제 발생
예시)
ㅁ(복사배열)
ㅣ
ㅁ(1차원)→ ㅁㅁㅁㅁㅁ(2차원)
ㅁ → ㅁㅁㅁㅁㅁ
만약 1차원 배열에서 메소드 이용하면 ㅁ(복사배열)은
ㅁ
ㅁ (1차원)부분과 내용이 같은 새로운 객체 만들어서 할당하니
새롭게 만든 복사 배열을 바꿔도 원본은 안바뀜
하지만 2차원 배열에서
ㅁ
ㅁ(1차원 배열)은 그저 ㅁㅁㅁㅁㅁ(2차원 배열)들의 주소값을 가리키는 용도일 뿐이기 때문에
메소드를 이용해서 복사하고 객체를 새롭게 할당해도
2차원 배열의 주소값을 그대로 복사한 것일 뿐!
복사본을 바꾸면 원본도 바뀌게 되는 것.
⭐결론:
따라서 2차원 배열을 복사하기 위해선 for문을 돌면서 직접 찍어야 함
(2중 for문 또는 System.arraycopy+for문)
- 2차 배열에서의 깊은 복사
📌 2중for문
int[][] arr = {{1,2,3},{4,5,6}};
int[][] copy = new int[arr.length][arr[0].length];
for(int i=0; i<arr.length; i++) {
for(int j=0; j<arr[i].length; j++){
copy[i][j] = arr[i][j];
}
}
copy[0][1] = 10;
-> arr [[1, 2, 3], [4, 5, 6]]
-> copy [[1, 10, 3], [4, 5, 6]]
📌 System.arraycopy + for문
int[][] arr = {{1,2,3},{4,5,6}};
int[][] copy = new int[arr.length][arr[0].length];
for(int i=0; i<copy.length; i++) {
System.arraycopy(arr[i], 0, copy[i], 0, arr[0].length);
//원본배열, 원본배열 복사 시작 인덱스,새 배열,새 배열 붙여넣기 시작 인덱스,복사 항목 수
}
copy[0][1] = 10;
-> arr [[1, 2, 3], [4, 5, 6]]
-> copy [[1, 10, 3], [4, 5, 6]]
☑️열거(Enum) 타입
- 한정된 데이터 값 가지는 타입 ex)권한은 USER, ADMIN / 계절은 봄,여름,가을,겨울
- 열거 상수 = 열거 타입으로 사용할 수 있는 한정된 값
- 대문자 알파벳으로 작성하며 여러 단어로 구성시 (_) 언더바 사용
- public enum LoginResult{ LOGIN_SUCCESSS, LOGIN_FAILED }
- 열거 타입도 하나의 데이터 타입이므로 변수 선언 후 사용해야 함, 열거타입
- public enum UserRoleEnum { USER, // 사용자 권한 ADMIN // 관리자 권한 } 의 경우 UserRoleEnum role; 이런식으로 먼저 선언 후 사용 열거 타입 변수에는 열거 상수 대입 가능. -> 열거타입.열거상수 UserRoleEnum role = UserRoleEnum.USER; if (signupRequestDto.isAdmin()) { if (!signupRequestDto.getAdminToken().equals(ADMIN_TOKEN)) { throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다."); } role = UserRoleEnum.ADMIN; }
☑️ 동일한 문자열 리터럴을 저장하는 변수는 동일한 String 객체를 참조한다.
'이것이 자바다' 카테고리의 다른 글
이것이 자바다 CH 7-1(상속) (0) | 2023.01.13 |
---|---|
이것이 자바다 CH 6-2(클래스) (0) | 2023.01.13 |
이것이 자바다 CH6-1(클래스) (0) | 2023.01.13 |
이것이 자바다 CH 3(연산자), CH 4(조건문과 반복문) (0) | 2023.01.13 |
이것이 자바다 CH 1, 2(변수와 타입) (1) | 2023.01.13 |