Java

[Java] 배열

hyomee2 2024. 7. 11. 17:33

1. 배열

- 동일한 자료형, 순차적 자료 구조

- 선언 시 배열 길이 결정된다.

- heap 영역에 new 연산자를 통해 할당된다.

- 인덱스 연산자[]로 빠른 참조가능 (cf. Linked list는 빠르지 않다. 시간 복잡도 O(n))

- 물리적 위치와 논리적 위치가 동일 (실제 메모리에서 A[0]와 A[1]은 연속적이다. Linked list는 물리적 위치 != 논리적 위치)

- 자바에선 객체 배열을 구현한 ArrayList를 자주 사용한다. (Java util 패키지에서 제공) 

 

2. 배열 선언 및 초기화

(1) 배열 선언하기

: 스택에 배열의 주소를 보관할 레퍼런스 변수 공간을 만드는 것

- 선언한 레퍼런스 변수에 배열을 할당하여 대입할 수 있다.

  (발생한 주소가 레퍼런스 변수에 저장되고, 이것을 참조해서 사용하므로 참조 자료형이라 한다.)

int[] arr1 = new int[10];
int arr2[] = new int[10];
// new 연산자는 heap 영역에 공간을 할당하고 발생한 주소값을 반환한다.

 

(2) 배열 초기화하기

- 배열은 선언과 동시에 초기화된다. (정수: 0, 실수: 0.0, 논리: false, 문자: \u0000, 참조: null) (초기값 지정도 가능)

  cf. 스택에 할당되는 지역변수는 초기화해야지만 접근할 수 있지만, heap 영역에 할당될 경우 초기값이 설정된다.

int[] numbers = new int[] {10, 20, 30};  // 개수는 작성하지 않는다.

int[] numbers = {10, 20, 30};  // 선언과 초기화를 동시에 할 경우애는 new int[] 생략 가능

int[] ids;
ids = new int[] {10, 20, 30};  // 선언 후 배열을 생성하는 경우에는 new int[] 생략할 수 없다.

 

(3) 배열 접근하기

- heap 메모리는 이름이 아닌 주소로 접근하는 영역이다.

- stack에 저장된 주소로 heap에 할당된 배열을 찾아갈 수 있다.

System.out.println("numbers = " + numbers);

 

- hashCode(): 객체의 주소값을 10진수로 변환하여 생성한 객체의 고유 정수값 반환 메소드

System.out.println("numbers.hashCode() = " + numbers.hashCode());

 

(4) 배열 덮어쓰기

- 한 번 지정한 배열의 길이는 변경할 수 없으므로 새로운 배열을 생성하여 그 주소값을 레퍼런스 변수에 덮어쓸 수 있다.

numbers = new int[100];
System.out.println("수정된 numbers.hashCode() = " + numbers.hashCode()); // 위에서 출력한 hashCode와 다르다.

- cf. 한 번 할당된 배열은 지울 수 없는데, 참조되지 않는 배열은 시간이 지난 후 old 영역으로 이동하여 GC가 삭제한다.

- cf. 주소값을 잃어버린 배열은 재참조 불가하다.

iarr = null // 참조하는 주소값이 없음을 뜻한다.

/*null을 참조할 때 참조 연산자를 사용하면 java.lang.NullPointerException이 발생한다.
System.out.println("수정된 numbers.hashCode() = " + numbers.hashCode());
System.out.println("수정된 numbers.length = " + numbers.length); */

 

3. 배열의 길이 & 요소의 개수

- 배열을 선언하면 개수만큼 메모리가 할당되지만, 실제 요소(데이터)가 없는 경우도 있다.

- 배열의 length는 배열의 개수를 반환. (요소의 개수를 반환하는 것이님 )

double[] dArr = new double[5];

dArr[0] = 1.1;
dArr[1] = 2.1;
dArr[2] = 3.1;
// 이런 식으로 일부만 초기화할 수도 있다.

- 요소의 개수에 대한 정보가 필요하면 아래와 같이 count 변수를 이용할 수 있다.

double[] dArr = new double[5];
int count = 0;
dArr[0] = 1.1; count++; 
dArr[1] = 2.1; count++;
dArr[2] = 3.1; count++;

 

 

4. 배열에서 향상된 for문 사용하기

- 배열의 요소 n개를 0~n-1까지 순회할 때(즉, 배열 전체를 순회할 때) 사용가능

public class CharArrayTest {
	public static void main(String[] args) {
		char[] alphabets = new char[26];
		char ch = 'A';
		
		for (int i=0; i<alphabets.length; i++) {
			alphabets[i] = ch++;;  // alphabets[i]에 ch를 넣어주고 ch값이 증가
		}
		
		for (char alpha:alphabets) {
			System.out.println(alpha + "," + (int)alpha);
		}
	}

}

 

5. 배열 복사하기

- 깊은 복사: heap의 배열에 저장된 값을 복사

(1) 얕은 복사

: stack에 저장되어 있는 배열의 주소값만 복사.

- 두 레퍼런스 변수는 동일한 배열의 주소값을 갖고 있다.

- 하나의 레퍼런스 변수에 저장된 주소값을 가지고 배열의 내용을 수정하면,

  다른 레퍼런스 변수로 배열에 접근했을 때도 변경된 값이 반영돼 있다.(동일한 배열을 가리키고 있기 때문)

 

// <얕은 복사>
int[] originArr = {1, 2, 3, 4, 5};

// originArr에 저장된 배열의 주소를 copyArr에도 저장
int[] copyArr = originArr; 

// hashcode를 출력해보면 두 레퍼런스 변수는 동일한 주소를 갖고 있는 걸 확인할 수 있다.
System.out.println(originArr.hashCode());
System.out.println(copyArr.hashCode());

// 원본 배열과 복사본 배열의 값 출력 (두 배열은 동일한 값 갖고 있다.)
for (int i = 0; i < originArr.length; i++) {
	System.out.print(originArr[i] + " ");
}

for (int i = 0; i < copyArr.length; i++) {
	System.out.print(copyArr[i] + " ");
}

// 복사본 배열의 값을 변경한 뒤 원본 배열의 값을 확인해보면, 원본 배열에도 변경이 반영되어 있다.
// 현재 존재하는 배열은 하나 뿐이기 때문
copyArr[0] = 99;

for (int i = 0; i < originArr.length; i++) {
	System.out.print(originArr[i] + " ");
}

for (int i = 0;; i < copyArr.length; i++) {
	System.out.print(copyArr[i] + " ");
}
public static void main(String[] args) {
	String[] names = {"홍길동", "유관순", "이순신"};

	System.out.println(names.hashCode());

	// 배열을 매개변수로 전달받아 출력하는 메소드. 출력해보면 동일한 hashCode을 갖는 걸 볼 수 있다.
	// 이와 같이 다른 메소드에서 동일한 배열(객체)를 사용하도록 하고 싶은 경우 얕은 복사 이용
	print(names);
    
    String[] animals = getAnimals();
    
    System.out.println(animals.hashCode());
    
    print(animals);
}

public static void print(String[] sarr) {
	// 전달받은 배열의 hashcode 출력
    System.out.println("sarr의 hashcode : " + sarr.hashCode());
    
    // 전달받은 배열의 값 출력
    for (int i = 0; i < sarr.length; i++) {
    	System.out.print(sarr[i] + " ");
    }
    System.out.println();
}

public static String[] getAnimals() {
	String[] animals = new String[] {"낙타", "호랑이", "나무늘보"};
    
    // 얕은 복사 확인을 위한 hashcode 출력
    System.out.println(animals.hashCode());
    
    return animals;
}

 

(2) 깊은 복사

: heap의 배열에 저장된 값을 복사

- 서로 같은 값을 갖고 있긴 하지만, 두 배열은 서로 다른 배열이기 때문에 하나의 배열을 변경해도 다른 배열에 영향을 주지 않는다.

- 새롭게 할당한 힙 영역에 기존 배열의 값을 복사한 후 새롭게 생성된 배열의 주소값을 넘겨준다.

 

 1) 깊은 복사 1. for문을 이용한 동일한 인덱스의 값 복사

// 원본 배열
int[] originArr = new int[] {1, 2, 3, 4, 5};
print(originArr);

// 복사한 배열
int[] copyArr1 = new int[10];

for (int i = 0; i < originArr.length; i++)
	copyArr1[i] = originArr[i];
    
print(copyArr1);
// 원본 배열과 복사한 값은 같은 값을 갖고 나머지 인덱스는 다른 값, 다른 주소를 갖고 있다.

 

2) 깊은 복사 2. Object의 clone()을 이용한 복사

// 원본 배열
int[] originArr = new int[] {1, 2, 3, 4, 5};
print(originArr);

// 복사한 배열
int[] copyArr2 = originArr.clone();

print(copyArr2); // 동일한 길이, 동일한 값을 가지는 배열이 생성되어 복사되며, 다른 주소를 갖고 있다.

 

3) 깊은 복사 3.System의 arraycopy()를 이용한 복사

// 원본 배열
int[] originArr = new int[] {1, 2, 3, 4, 5};
print(originArr);

// 복사한 배열
int[] copyArr3 = new int[10];

// arraycopy(원본배열, 복사를 시작할 인덱스, 복사본 배열, 복사를 시작할 인덱스, 복사할 길이)
System.arraycopy(originArr, 0, copyArr3, 3, orginArr.length);
print(copyArr3);  // 복사한만큼의 값은 같지만 길이도 다르고 주소도 다르다.

 

4) 깊은 복사 4. Arrays의 copyOf()를 이용한 복사

// 원본 배열
int[] originArr = new int[] {1, 2, 3, 4, 5};
print(originArr);

// 복사한 배열
int[] copyArr4 = Arrays.copyOf(originArr, 7);
print(copyArr4);

- 위의 4가지 방법 중 가장 높은 성능을 보이는 것은 arraycopy() 메소드이고,

  가장 많이 사용되는 방식은 좀 더 유연한 방식인 copyOf() 메소드이다.

- clone()은 이전 배열과 같은 배열 밖에 만들 수 없다는 특징을 갖고, 

  그 외 3가지 방법은 복사하는 배열의 길이를 마음대로 조절할 수 있다는 특징을 갖는다. 

 

* 깊은 복사의 활용

// 같은 값을 갖는 두 배열 초기화
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = arr1.clone();

// arr1의 값을 10 누적 증가시키기
for (int i = 0; i < arr1.length; i++)
	arr1[i] += 10;
    
// arr1 값 출력
for (int i = 0; i < arr1.length; i++)
	System.out.print(arr1[i] + " ");

// arr2의 값도 10 누적 증가시키기
for (int i : arr2)
	i += 10;
    
// arr2 값 출력
for (int i = 0; i < arr2.length; i++)
	System.out.print(arr2[i] + " ");
// 증가되지 않고 있다. 향상된 for문에서 i는 배열의 각 요소를 복사한 값이기 때문에 실제 배열의 값을 변경할 순 없다.
// 변경이 아니라 접근이 목적이라면 향상된 for문을 이용할 수 있다.

 

6. 객체 배열

(1) 객체 배열 선언과 구현

- 객체 배열은, 선언 시 객체의 주소가 들어갈 메모리만 할당되고(NULL),

  각 요소 객체는 일일이 직접 생성해서 저장해줘야 한다.

package ch21;

public class Book {
	private String title;
	private String author;
	
	public Book() {}
	public Book(String title, String author) {
		this.title = title;
		this.author = author;
	}
	
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	
	public void showInfo() {
		System.out.println(title + ", " + author);
	}
}

 

public class BookTest {
	public static void main(String[] args) {
		Book[] library = new Book[5]; // 이렇게 하면 메모리만 잡힌거지, Book 객체들이 생성된 건 아님.
		
		for (int i=0; i<library.length; i++) {
			System.out.println(library[i]); // 출력하면 null 5개 나옴
		}
		
		// 이제 Book instance을 만들어보자.
		library[0] = new Book("태백산맥1", "조정래");
		library[1] = new Book("태백산맥2", "조정래");
		library[2] = new Book("태백산맥3", "조정래");
		library[3] = new Book("태백산맥4", "조정래");
		library[4] = new Book("태백산맥5", "조정래");
		
		for (Book book : library) {
			System.out.println(book);
			book.showInfo();
		}
	}
}

(2) 객체 배열 복사하기

 1) 얕은 복사

  - System.arrayCopy(src, srcPos, dest, destPos, length) 

  - 얕은 복사를 하게 되면 두 배열이 같은 객체를 가리키게 되고, 

    따라서 한 쪽 배열을 수정하면 같이 수정된다.

public class ObjectCopy {
	public static void main(String[] args) {
		Book[] library = new Book[5];
		Book[] copyLibrary = new Book[5];
		
		library[0] = new Book("태백산맥1", "조정래");
		library[1] = new Book("태백산맥2", "조정래");
		library[2] = new Book("태백산맥3", "조정래");
		library[3] = new Book("태백산맥4", "조정래");
		library[4] = new Book("태백산맥5", "조정래");
		
		System.arraycopy(library, 0, copyLibrary, 0, 5);

		System.out.println("======copy library=========");
		for( Book book : copyLibrary ) {
			book.showBookInfo();
		}
		
		library[0].setTitle("나목");
		library[0].setAuthor("박완서");
		
		System.out.println("======library=========");
		for( Book book : library) {
			book.showBookInfo();
		}
		
		System.out.println("======copy library=========");
		
		for( Book book : copyLibrary) {
			book.showBookInfo();
		}
	}
}

2) 깊은 복사

public class ObjectCopy2 {
	public static void main(String[] args) {
		Book[] library = new Book[5];
		Book[] copyLibaray = new Book[5];
		
		library[0] = new Book("태백산맥1", "조정래");
		library[1] = new Book("태백산맥2", "조정래");
		library[2] = new Book("태백산맥3", "조정래");
		library[3] = new Book("태백산맥4", "조정래");
		library[4] = new Book("태백산맥5", "조정래");
		
		copyLibaray[0] = new Book();
		copyLibaray[1] = new Book();
		copyLibaray[2] = new Book();
		copyLibaray[3] = new Book();
		copyLibaray[4] = new Book();
		
		for(int i = 0; i< library.length; i++) {
			copyLibaray[i].setTitle(library[i].getTitle());
			copyLibaray[i].setAuthor(library[i].getAuthor());
		}
		
		library[0].setTitle("나목");
		library[0].setAuthor("박완서");
	
		System.out.println("======library=========");
		for( Book book : library) {
			book.showBookInfo();
		}
		
		System.out.println("======copy library=========");
		for( Book book : copyLibaray) {
			book.showBookInfo();
		}
	}
}

 

 

7. 이차원 배열

int[][] arr = {{1,2,3}, {4,5,6}}

// 주소를 묶어서 관리할 배열의 크기는 반드시 지정해주어야 한다.
iarr = new int[3][];

// 아래는 주소를 묶어서 관리할 배열의 크기를 지정해주지 않아 에러가 발생하는 경우들이다.
// iarr = new int[][];
// iarr = new int[][4];
public class TwoDimensionTest {
	public static void main(String[] args) {
		int[][] arr = {{1, 2, 3}, {1, 2, 3, 4}};
		int i, j;
        
		for (i=0; i < arr.length; i++) {
			for (j=0; j < arr[i].length; j++) {
				System.out.println(arr[i][j] + ",");
			}
			System.out.println("\t" + arr[i].length);
		}
	}
}

 

 

8. ArrayList

- java.util 패키지에서 제공되는 클래스이다.

import java.util.ArrayList;
import ch21.Book;

public class ArrayListTest {
	public static void main(String[] args) {
		ArrayList<Book> library = new ArrayList<Book>();  // 이게 생성자
		// 근데 요즘에는 아래와 같이 작성해도 된다.
		ArrayList<Book> library = new ArrayList<>();
		// 개수를 지정해주지 않으면 10개를 잡고, 더 필요하면 늘어남
        
		library.add(new Book("태백산맥1", "조정래"));
		library.add(new Book("태백산맥2", "조정래"));
		library.add(new Book("태백산맥3", "조정래"));
		library.add(new Book("태백산맥4", "조정래"));
		library.add(new Book("태백산맥5", "조정래"));
		
		// size(): 요소의 개수
		for(int i =0; i<library.size(); i++) {
			library.get(i).showBookInfo();
		}
	}
}

 

9. 간단한 ArrayList 예제

- StudentTest 클래스를 참고하여 Student와 Subject 클래스를 작성했다.

 

Student.java

import java.util.ArrayList;

public class Student {
	int studentId;
	String studentName;
	
	Student(int studentId, String studentName){
		this.studentId = studentId;
		this.studentName = studentName;
	}
	
	ArrayList<Subject> subjectList = new ArrayList<Subject>();
	
	public void addSubject(String subjectName, int score) {
		subjectList.add(new Subject(subjectName, score));
	}
	
	public void showStudentInfo() {
		int total = 0;
		for (int i = 0; i < subjectList.size(); i++) {   // for(Subject subject : subjectList) 라고 해줄 수도 있다.
			System.out.println("학생 " + studentName + "의 " + subjectList.get(i).getSubjectName() + " 과목 성적은 " + subjectList.get(i).getScore() + "입니다.");
			total += subjectList.get(i).getScore();
		}
		System.out.println("학생 " + studentName + "의 총점은 " + total + " 입니다.");
	}
}

 

StudentTest.java

public class StudentTest {
	public static void main(String[] args) {
		Student studentLee = new Student(1001, "Lee");
		
		studentLee.addSubject("국어", 100);
		studentLee.addSubject("수학", 50);
		
		Student studentKim = new Student(1002, "Kim");
		
		studentKim.addSubject("국어", 70);
		studentKim.addSubject("수학", 85);
		studentKim.addSubject("영어", 100);
		
		studentLee.showStudentInfo();
		System.out.println("======================================");
		studentKim.showStudentInfo();
	}
}

 

Subject.java

public class Subject {
	private String subjectName;
	private int score;

	Subject() {}
	
	Subject(String subjectName, int score) {
		this.subjectName = subjectName;
		this.score = score;
	}

	public String getSubjectName() {
		return subjectName;
	}

	public void setSubjectName(String subjectName) {
		this.subjectName = subjectName;
	}

	public int getScore() {
		return score;
	}

	public void setScore(int score) {
		this.score = score;
	}
	
}