Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags more
Archives
Today
Total
관리 메뉴

ultra_dev

이것이 자바다 CH 5(참조타입) 본문

이것이 자바다

이것이 자바다 CH 5(참조타입)

ultra_dev 2023. 1. 13. 00:30

☑️ 참조타입 : 객체의 번지를 참조하는 타입(배열, 열거 , 클래스, 인터페이스)

객체 = 데이터(필드) + 메소드

기본타입 변수는 값 자체를 저장하고 있지만

참조타입 변수는 객체가 생성된 메모리 번지를 저장

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 객체를 참조한다.

Comments