//데이터를 추가하거나 제거할 떄 3가지 배열의 인덱스에 대해 수정이 필요함.
String[] studentNames = {"학생1", "학생2", "학생3"};
int[] studentAges = {15, 16, 20};
int[] studentGrades = {90, 80, 70};
데이터마다 변수를 선언하는 것의 비효율성을 느껴 배열이 등장했다.
하지만 배열 또한 여러 관련된 데이터가 분산되어 있다면 데이터의 추가, 삭제, 수정 등에서 불편함이 있다. (불안정하기도 하고)
-> (이름, 나이, 점수) 이런 식으로 관련된 데이터를 하나로 묶어서 효율적으로 관리해보자! (클래스의 등장)
(+ Swift였다면 Struct를 만들어야지~라고 했겠지만 자바에서는 모두 클래스다.)
클래스 선언
클래스는 객체 생성을 위한 설계도이다.
클래스는 객체의 속성(변수)와 기능(메서드)를 정의한다.
public class Student {
String name;
String age;
int grade;
}
클래스명은 대문자 카멜케이스,
내부 변수들은 '멤버 변수' 또는 '필드'라고 부른다. (Swift의 property로 이해해도 될 듯)
클래스는 사용자 정의 타입이다.(Custom type)
int, String과 같은 기본 타입과 같이 타입은 데이터의 종류를 나타낸다.
클래스를 통해 개발자가 필요한 타입을 마음대로 정의할 수 있다!
사용자가 필요에 의해 정의한 설계도(클래스)를 가지고
필요한 만큼 실제 메모리에 찍어내는 것이다. (객체 == 인스턴스)
클래스를 기반으로(클래스에는 어떤 데이터가 들어가야 하는지 정의되어 있으니)
실제 메모리에 객체를 찍어내기 때문에 붕어빵 틀이라는 비유를 많이들 하는 것이다.
public class FishBread {
int dough;
int redBean;
int custard;
}
붕어빵을 만들려면 반죽도 필요하고, 팥도 필요하고, 슈크림도 필요하다.
위와 같은 것은 int와 같은 기본 타입으로는 구현이 불가능하다.
객체 생성(Object)
클래스에서 속성과 기능이 정의되었다면, 정의에 맞게 메모리에 할당된 실체를 객체라고 한다.
클래스는 재활용 가능하기 때문에 객체를 찍어낼 때마다 다 다른 객체이다. (같은 붕어빵 틀에서 다른 여러 붕어빵이 나온다.)
FishBread myFish = new FishBread();
myFish.dough = 3;
myFish.redBean = 5;
myFish.custard = 5;
myFish 변수는 FishBread 타입을 받을 수 있는 변수이다.
객체를 사용하려면 클래스를 통해 인스턴스를 생성해야 한다.
new ClassName()
- 새로운 객체를 생성한다는 뜻
괄호는 Swift의 생성자(initializer)와 같이 ClassName.init()에서 init이 생략됐다고 보면 된다.
생성자 - 기본 초기화 메서드
객체가 생성되면서 메모리상의 객체에 접근할 수 있는 참조값(주소)이 반환되는데, 이 값이 myFish에 들어간다.
(변수에 담지 않으면 메모리에 둥둥 떠다녀서 접근하지를 못할테니!)
이제 변수를 통해 객체에 접근할 수 있다.
그럼 내부에 데이터(멤버 변수)를 활용하려면? .(dot) 문법을 사용하면 된다.
객체 vs 인스턴스
결국 클래스에서 찍어서 메모리에 올렸다는 것이 중요하지만,
인스턴스는 '특정 클래스로부터 생성된 객체'를 의미한다. '특정 클래스'의 의미가 더 강조되었다고 보면 된다.
모든 객체는 당연히 인스턴스이다. 위 예시코드에서 myFish는 객체이고, FishBread라는 클래스의 인스턴스이다.
배열 Array
클래스 인스턴스를 배열에 보관하는 경우가 많다.
그런데 배열을 생성할 때도 new 키워드를 사용했었다. 어? 배열도 객체를 찍어내는거였나?
그렇다. 자바에서는 배열도 클래스로 이루어져 있다.
public class Array<T> implements java.io.Serializable, java.lang.Cloneable
제네릭 타입을 통해 배열에 저장되는 요소의 유형이 자유롭고(int, char 등)
Serializable과 Cloneable 인터페이스를 채택하고 있다.
간단하게 배열은 클래스로 정의되어 있기 때문에 새로운 배열을 생성할 때 클래스와 동일한 절차를 수행한다는 것만 이해하자.
클래스 객체를 찍어내고 배열에 담아보자 !
public class Student {
String name;
int age;
int score;
}
Student student1 = new Student();
student1.name = "김터피";
student1.age = 15;
student1.score = 90;
Student student2 = new Student();
student2.name = "박커피";
student2.age = 19;
student2.score = 99;
Student 클래스를 정의했고, 인스턴스를 두 개 찍어냈다.
여기서 핵심은 student1과 student2에는 객체 자체가 저장되어 있는 것이 아니라,
객체에 접근할 수 있는 주소(참조값)이 들어있다는 것이다. (객체 자체는 메모리의 힙에 있을 것)
자 이제 학생들을 한 번에 관리하기 위해 배열을 만든다.
Student[] students = new Student[2];
2명의 학생을 관리하기 때문에 Student 타입을 담을 수 있는 사이즈 2의 배열을 생성한다.
여기서 students에는 null null로 초기화된 배열의 참조값이 들어가 있다. (아직 학생이 추가되지 않았다.)
학생을 추가해준다.
students[0] = student1;
students[1] = student2;
위에 오렌지 색으로 적어둔 것처럼 student1과 student2는 객체 자체가 아니라 해당 객체의 참조값이다.
따라서 students배열에는 student1과 student2의 참조 값이 '복사'되어서 들어간다.
[null, null] -> [student1 참조값, student2 참조값]이 되었다고 보면 된다.
Student[] students = new Student[2] { student1, student2 };
Student[] students = { student1, student2 };
위와 같이 깔끔하게 초기화 해줄 수 있다.
System.out.printf("이름: %s, 나이: %d, 점수: %d\n",
students[0].name, students[0].age, students[0].score);
결국 지금 students 배열은 클래스들의 주소 모음 역할을 하는 것이다.
위 프린트문에서 students[0].name만 살펴보자면
students는 배열의 참조값을 가지고 있으니 @30f39991 라고 치면,
@30f39991 배열의 0번째 값에는 student1의 참조값인 @452b3a41이 들어있기 때문에
@30f39991@452b3a41.name 처럼 타고타고 가서 "김터피"가 나오는 것이다.
배열을 사용했기 때문에 인덱스를 통해 루프를 돌리는 것도 가능하다.
for (int i =0; i < students.length; i++) {
System.out.printf("이름: %s, 나이: %d, 점수: %d\n", students[i].name, students[i].age, students[i].score);
}
//enhanced for iter
for (Student man : students) {
System.out.printf("이름: %s, 나이: %d, 점수: %d\n", man.name, man.age, man.score);
}
학습 출처
'JAVA > Java Intermediate(OOP)' 카테고리의 다른 글
5. 접근 제어자와 캡슐화 (0) | 2024.03.03 |
---|---|
4. 생성자 Construct (0) | 2024.03.02 |
3. 절차 지향 vs 객체 지향(OOP) (0) | 2024.02.23 |
2. 기본형과 참조형 (Primitive, Reference), NullPointerException (0) | 2024.02.12 |
0. OOP의 세계로 (0) | 2024.02.10 |