이번 섹션에선 클래스(Class)와 객체(Object)라는 개념의 필요성과 발전 방식에 대해 학습한다.
이 강의는 2배속으로 듣고, 헷갈렸던 내용이나 중요하다고 말씀하신 내용만 정리해두려고 한다.
1. 클래스가 필요한 이유
변수와 배열을 사용해 여러 명의 학생 정보(이름, 나이, 성적)를 출력하는 프로그램을 만든다고 가정해 보자. 학생 수가 아주 적을 땐 각각 다른 변수를 선언하거나 배열에 저장해 출력해도 된다. 그러나 학생 수가 늘어날수록 개발자가 추가해야 할 코드가 많아지고, 데이터에 정확하게 접근해야 한다는 문제가 발생한다. 개발자가 관리하기 좋은 방식은 학생이라는 개념을 하나로 묶고, 각각의 학생 별로 이름과 나이, 성적을 관리하는 방식이다.
클래스 도입
위에서 말한 문제들은 클래스라는 개념을 도입해서 해결할 수 있다. class 키워드를 사용해 학생 클래스(Student)를 정의한다. 학생 클래스는 내부에 이름(name)과 나이(age), 성적(grade) 변수를 가진다. 이렇게 클래스에 정의한 변수들을 멤버 변수 또는 필드라고 한다.
- 클래스에 소속된 변수 = 멤버 변수 = 필드
- 멤버 변수(Member Variable): 특정 클래스에 소속된 멤버이자 변수
- 필드(Field): 데이터 항목을 가리키는 전통적인 용어로, DB나 엑셀 등에서도 사용함
- 클래스는 관례상 대문자로 시작하고, 낙타 표기법(Camel Case)을 사용한다.
- ex. MemberApiController
- 클래스를 통해 사용자가 원하는 종류의 데이터 타입을 마음껏 정의할 수 있다.
- 타입 = 데이터의 종류나 형태
- 클래스 = 사용자가 직접 정의하는 사용자 정의 타입을 만들기 위한 설계도
- 객체 또는 인스턴스 = 클래스를 사용해 실제 메모리에 만들어진 실체
public class Student {
String name;
int age;
int grade;
}
이제 위의 학생 클래스를 사용하는 코드를 작성하고 분석해 보자.
- Student 타입을 받을 수 있는 변수 s1을 선언한다.
- new 명령어를 통해 Student 클래스 정보(필드, 메서드)를 기반으로 새로운 객체를 생성한다. 객체를 생성하면 자바는 메모리 어딘가에 있는 이 객체에 접근할 수 있는 참조값(주소)을 반환한다.
- s1에 생성된 객체의 참조값을 보관한다. 따라서 s1을 통해 객체를 접근(참조)할 수 있다.
public class ClassStart {
public static void main(String[] args) {
Student s1 = new Student(); // 변수 선언 & 객체(인스턴스) 생성 & 참조값 보관
s1.name = "Kim";
s1.age = 17;
s1.grade = 90;
System.out.println("이름: " + s1.name + ", 나이: " + s1.age + ", 성적: " + s1.grade);
}
}
참고
new 명령어를 사용하면 단순히 Student 클래스를 기반으로 메모리에 실제 객체를 만든다. 따라서 생성된 객체에 접근하기 위해 객체를 생성할 때 반환되는 참조값을 변수에 저장해 둬야 한다. 참조값을 통해 실제 메모리에 존재하는 객체에 접근할 수 있다.
참조값을 확인하고 싶다면, 아래 코드처럼 객체를 담고 있는 변수를 출력해 보면 된다.
- @ 앞 = 패키지 + 클래스 정보
- @ 뒤 = 16진수 참조값
System.out.println(s1); // 결과: class1.Student@8f7a889d
클래스를 통해 생성한 객체를 사용하려면, 먼저 메모리에 존재하는 객체에 접근해야 한다. 객체에 접근하려면 .(점, dot)을 사용하면 된다. 이 키워드는 변수에 들어있는 참조값을 읽어서 메모리에 존재하는 객체에 접근한다.
2. 클래스, 객체, 인스턴스 정리
클래스(Class)
클래스는 객체를 생성하기 위한 '틀' 또는 '설계도'로, 객체가 가져야 할 속성(변수)과 기능(메서드)을 정의한다.
- 예를 들어 붕어빵 틀을 생각해 보자. 붕어빵 틀은 붕어빵이 완성된다면 어떻게 나올지 알려주는 틀일 뿐, 먹을 수 있는 붕어빵이 아니다.
- 붕어빵 틀 = 클래스
- 붕어빵 = 객체 또는 인스턴스
객체(Object) & 인스턴스(Instance)
객체는 클래스에서 정의한 속성과 기능을 가진 실체로, 서로 독립적인 상태를 가진다.
- 예를 들어 위의 코드에서 "Kim"의 속성을 갖는 Student 객체와 "Han"의 속성을 갖는 Student 객체가 있다면, 이 두 객체는 같은 클래스에서 만들어졌지만 서로 다른 객체이다.
인스턴스는 특정 클래스로부터 생성된 객체로, 주로 객체가 어떤 클래스에 속해 있는지 강조할 때 사용한다.
- 예를 들어 s1 객체는 'Student 클래스의 인스턴스'라고 표현할 수 있다.
객체 vs 인스턴스
모든 인스턴스는 객체이지만, 우리가 인스턴스라고 부르는 순간은 특정 클래스로부터 그 객체가 생성됐음을 강조하고 싶을 때이다. 그러나 둘 다 클래스에서 나온 실체라는 핵심 의미는 같기 때문에 보통 둘을 구분하지 않고 사용한다.
자바에서 대입(=)은 항상 모든 변수에 들어 있는 값을 복사해서 전달한다.
변수에는 인스턴스 자체가 들어있지 않고, 인스턴스의 위치를 가리키는 참조값이 들어 있다. 따라서 대입 시 인스턴스가 아닌 참조값만 복사된다.
참고
직접 정의한 Student 타입으로 일반적인 변수와 동일하게 배열을 생성할 수 있다.
Student[] students = new Student[]{s1, s2};
생성과 선언을 동시에 하는 경우엔 더 최적화할 수 있다.
Student[] students = {s1, s2};
3. 기본형 vs 참조형
개념
변수의 데이터 타입을 가장 크게 보면, 기본형과 참조형으로 분류할 수 있다.
- 기본형(Primitive Type)
- int, long, double, boolean처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입
- 숫자 10, 20과 같이 실제 사용하는 값을 변수에 담기 때문에 해당 값을 바로 사용할 수 있다.
- 들어있는 값을 그대로 계산에 사용할 수 있다. → 산술 연산 가능
- 소문자로 시작한다.
- 개발자가 직접 정의할 수 없다.
- null을 할당할 수 없다.
- 참조형(Reference Type)
- Student s1, int[] students처럼 데이터에 접근하기 위한 참조값(주소)을 저장하는 데이터 타입
- 이름 그대로 실제 객체의 위치(참조값, 주소)를 저장하며, 객체와 배열에 사용된다.
- 객체는 .(점, dot)을 통해, 배열은 []를 통해 메모리 상에 생성된 객체를 찾아가서 사용한다.
- 들어있는 참조값을 그대로 계산에 사용할 수 없다. → 산술 연산 불가능
- 대문자로 시작한다.
- 참조형인 클래스는 개발자가 직접 정의할 수 있다.
- null릏
참고
String도 사실 클래스이며, 참조형이다. 그런데 기본형처럼 문자 값을 바로 대입할 수 있다. 문자는 자주 다루기 때문에 자바에서 특별하게 편의 기능을 제공한다.
자바에서 대입(=)은 항상 모든 변수에 들어 있는 값을 복사해서 전달한다.
변수에는 인스턴스 자체가 들어있지 않고, 인스턴스의 위치를 가리키는 참조값이 들어 있다. 따라서 대입 시 인스턴스가 아닌 참조값만 복사된다.
참고
객체1에 객체2를 대입하면 객체2의 참조값을 복사해서 객체1에 사용한다. 따라서 두 객체는 같은 참조값을 갖게 되며 같은 객체 인스턴스를 참조하게 된다.
참고
메서드에 넘겨 값을 변경하고, 변경된 값을 반환받고 싶다면 참조형을 넘기거나 기본형의 참조값(&)을 넘기면 된다.
변수와 초기화
변수에는 두 종류가 있다.
- 멤버 변수(필드)
- 클래스에 선언
- 자동 초기화
- int(0), boolean(false), 참조형(null)
- 개발자가 초기값을 직접 지정할 수 있다.
- 지역 변수
- 메서드에 선언; 매개변수도 지역 변수의 한 종류이다.
- 수동 초기화
- 항상 직접 초기화해야 한다.
public class Student {
// 멤버 변수(필드) - name, age, grade
String name;
int age;
int grade;
}
///
public class ClassStart {
int count = 0;
public static void main(String[] args) {
// 지역 변수 - s1, s2
Student s1 = new Student();
Student s2 = new Student();
}
// 지역 변수(매개변수) - x
public static void changePrimitive(int x) {
x = 20;
}
}
null과 Garbage Collection
참조형 변수에서 아직 가리키는 대상이 없다면 null이라는 특별한 값을 넣어 둘 수 있다. 참고로 0이나 공백과는 다르다.
public class NullStart {
public static void main(String[] args) {
Data data = null; // (1) Data 타입 변수를 만들고 null 저장
data = new Data(); // (2) 위에서 선언한 변수에 새로운 객체의 참조값(x001) 저장
data = null; // (3) 새로운 객체의 참조값을 null로 덮어 씌움
}
}
a. Garbage Collection
위의 코드를 보면 data에 null을 할당한 뒤, 새로 생성한 객체의 참조값을 저장한다. 이후 다시 data에 null을 할당한다. 이렇게 되면 앞서 생성한 Data 인스턴스(x001)는 아무도 참조하지 않게 된다. 따라서 해당 인스턴스에 다시 접근할 방법이 없으며, 아무도 참조하지 않는 인스턴스는 사용되지 않고 메모리 용량만 차지할 뿐이다.
자바에선 아무도 참조하지 않는 인스턴스가 있으면, JVM의 GC(Garbage Collection)가 더 이상 사용하지 않는 인스턴스라 판단하고 해당 인스턴스를 자동으로 메모리에서 제거해준다.
- 객체는 해당 객체를 참조하는 곳이 있으면, JVM이 종료할 때까지 생존한다. 그런데 중간에 해당 객체를 참조하는 곳이 모두 사라지면 그때 JVM은 필요 없는 객체로 판단하고, GC를 사용해 제거한다.
지울 인스턴스들을 모아서 한꺼번에 삭제함 = 개발자가 하나하나 메모리에서 없애는 것보다 효율적으로 동작함
참고
JVM의 GC는 개발자가 더 이상 쓰지 않는 인스턴스를 하나하나 메모리에서 없애는 것보다 더 효율적으로 동작한다.
- 더 이상 쓰지 않는 인스턴스를 모아 한꺼번에 제거
b. NullPointerException
참조값 없이 객체를 찾아가면 NullPointerException 예외가 발생한다. 이름 그대로 null을 가리킬 때(pointer) 발생하는 예외다.
- 객체를 참조할 때는 .(점, dot)을 사용한다. 이렇게 하면 참조값을 사용해서 해당 객체를 찾아갈 수 있다. 그런데 참조값이 null이면 값이 없다는 뜻이므로, 찾아갈 수 있는 객체(인스턴스)가 없다.
- NullPointerException은 이처럼 null에 .(점, dot)을 찍었을 때 발생한다.