[Java] 클래스와 데이터, 기본형과 참조형, 생성자
클래스와 데이터
1. 클래스
클래스 : 객체를 생성하기 위한 설계도로 객체가 가져야 할 속성(변수)과 기능(메서드)를 정의한다.
public class ClassStart3 { public static void main(String[] args) { Student student1; student1 = new Student(); student1.name = "학생1"; student1.age = 15; student1.grade = 90; Student student2 = new Student(); student2.name = "학생2"; student2.age = 16; student2.grade = 80; } }
- 사용자가 원하는 종류의 데이터 타입을 마음껏 정의할 수 있다.
- 객체(Object) : 클래스를 사용해서 실제 메모리에 만들어진 실체
- 서로 독립적인 상태를 가진다.
- 인스턴스(Instance) : 특정 클래스로부터 생성된 객체
- 주로 객체가 어떤 클래스에 속해있는 지 강조할 때 사용한다.
- 예) student1 객체는 Student 클래스의 인스턴스이다.
- 멤버 변수(Member Variable) : 특정 클래스에 소속된 변수
- 필드(Field) : 특정 클래스에 소속된 변수
- 멤버 변수와 필드는 같은 뜻이다.
- 클래스는 관례상 대문자로 시작하고, 낙타 표기법을 사용한다.
- 예) Student, User, MemberService
변수 선언 → 객체 생성 → 참조값 보관
Student student1
: Student 타입을 받을 수 있는 변수를 선언한다.student1 = new Student()
: Student 클래스 정보를 기반으로 새로운 객체인 student1을 생성한다.- student1 변수는 메모리에 존재하는 실제 Student 객체의 참조값(x001)을 보관한다.
- 객체에 접근하기 위해 객체 생성 시 반환되는 참조값을 보관해두어야 한다.
2. 객체 사용
객체에 접근하기 이해
.
을 사용한다.student1.name = "학생1"; student1.age = 15; student1.grade = 90;
객체 값을 읽을 때도
.
을 통해 참조값을 사용해서 객체에 접근한 다음 원하는 작업을 하면 된다.//1. 객체 값 읽기 System.out.println("이름:" + student1.name); //2. 변수에 있는 참조값을 통해 실제 객체에 접근하고, name 멤버 변수에 접근한다. System.out.println("이름:" + x001.name); //3. 객체의 멤버 변수의 값을 읽어옴 System.out.println("이름:" + "학생1");
3. 배열 도입
배열 사용시 특정 타입을 연속한 데이터 구조로 묶어서 편리하게 관리할 수 있다.
public class ClassStart4 { public static void main(String[] args) { Student student1 = new Student(); student1.name = "학생1"; student1.age = 15; student1.grade = 90; Student student2 = new Student(); student2.name = "학생2"; student2.age = 16; student2.grade = 80; **Student[] students = new Student[2];** students[0] = student1; students[1] = student2; } }
Student[] students = new Student[2]
- Student 배열의 각 항목은 Student 타입의 참조값을 보관한다.
- 아직 참조값을 대입하지 않아서 null 값으로 초기화된다.
- 배열에 들어있는 객체 사용
- 먼저 배열에 접근 → 객체에 접근
배열 선언 최적화
// 방법 1 Student[] students = new Student[]{student1, student2}; // 방법 2 Student[] students = {student1, student2};
기본형과 참조형
1. 기본형 vs 참조형
- 기본형(Primitive Type) : int, long, double, boolean처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입
- 참조형(Reference Type): Student student1, int[] students와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입
- 기본형을 제외한 나머지는 모두 참조형이다.
- 기본형은 소문자로 시작한다.
- 클래스는 대문자로 시작한다. → 참조형
- String은 클래스로 참조형이지만, 기본형처럼 문자 값을 바로 대입할 수 있다. 문자는 매우 자주 다뤄 자바에서 특별히 편의 기능을 제공한다.
2. 변수 대입
- 기본형 : 실제 사용하는 값이 변수에 들어있어서 해당 값만 복사해서 대입한다.
- 참조형 : 실제 사용하는 객체가 아니라 객체의 위치를 가리키는 참조값만 복사된다.
- 예시 : dataA에 들어있는 참조값 x001을 복사해서 dataB에 대입한다.
Data dataA = new Data(); dataA.value = 10; Data dataB = dataA; System.out.println("dataA 참조값=" + dataA); // ref.Data@x001 System.out.println("dataB 참조값=" + dataB); // ref.Data@x001 System.out.println("dataA.value = " + dataA.value); // 10 System.out.println("dataB.value = " + dataB.value); // 10
3. 메서드 호출
- 기본형 : 메서드로 기본형 데이터 전달시, 해당 값이 복사되어 전달된다.
- 이 경우, 메서드 내부에서 매개변수의 값을 변경해도, 호출자의 변수 값에는 영향이 없다.
- 참조형 : 메서드로 참조형 데이터를 전달하면, 참조값이 복사되어 전달된다.
- 이 경우, 메서드 내부에서 매개변수로 전달된 객체의 멤버 변수를 변경하면, 호출자의 객체도 변경된다.
예시 : dataA와 dataX는 같은 참조값이 x001을 가지게 되어 dataX를 통해서 x001에 있는 Data 인스턴스에 접근할 수 있다.
public static void main(String[] args) { Data dataA = new Data(); dataA.value = 10; System.out.println("메서드 호출 전: dataA.value = " + dataA.value); // 10 changeReference(dataA); System.out.println("메서드 호출 후: dataA.value = " + dataA.value); // 20 } static void changeReference(Data dataX) { dataX.value = 20; }
4. 변수와 초기화
- 변수의 종류
- 멤버 변수(필드) : 클래스에 선언
- 지역 변수 : 메서드에 선언, 매개 변수도 지역 변수의 한 종류이다.
- 변수의 값 초기화
- 멤버 변수 : 인스턴스 생성 시 자동 초기화된다.
- int = 0, boolean = false, 참조형 = null
- null은 아직 값이 존재하지 않는다는 것
- 지역 변수 : 항상 직접 초기화 해야한다.
- 멤버 변수 : 인스턴스 생성 시 자동 초기화된다.
- 가비지 컬렉션(GC) : 아무도 참조하지 않는 인스턴스가 있으면 GC가 더이상 사용하지 않는 인스턴스라 판단하고 해당 인스턴스를 자동으로 메모리에서 제거해준다.
- 아무도 참조하지 않는 인스턴스는 사용되지 않고 메모리 용량만 차지한다.
- NullPointerException
- null에
.
을 찍었을 때, 참조할 주소가 존재하지 않기 때문에 발생하는 예외
- null에
객체 지향 프로그래밍
1. 절차 지향 프로그래밍 vs 객체 지향 프로그래밍
- 절차 지향 프로그래밍
- 프로그램의 흐름을 순차적으로 따르면 처리한다.
- “어떻게”를 중심으로 프로그래밍한다.
- 데이터와 해당 데이터에 대한 처리 방식이 분리되어 있다. → 유지보수하기 불편하다.
- 객체 지향 프로그래밍
- 실제 세계의 사물이나 사건을 객체로 복, 이러한 객체들 간의 상호작용을 중심으로 프로그래밍한다.
- “무엇을” 중심으로 프로그래밍한다.
- 데이터(속성)와 그 데이터에 대한 행동(메서드)이 하나의 ‘객체’안에 함께 포함되어 있다.
예시 : 데이터인 value와 해당 데이터를 사용하는 add() 메서드를 함께 클래스에 정의
public class ValueObject { int value; void add() { value++; System.out.println("숫자 증가 value=" + value); } }
public class ValueObjectMain { public static void main(String[] args) { ValueObject valueObject = new ValueObject(); valueObject.add(); valueObject.add(); valueObject.add(); System.out.println("최종 숫자=" + valueObject.value); } }
- 캡슐화
- 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 제공하는 것
- 객체 내부 코드가 변하는 경우, 다른 코드는 변경하지 않아도 된다.
생성자
1. 생성자가 필요한 이유
아래 코드에서는 초기값을 설정하는 부분이 계속 반복된다.
public static void main(String[] args) { MemberInit member1 = new MemberInit(); member1.name = "user1"; member1.age = 15; member1.grade = 90; MemberInit member2 = new MemberInit(); member2.name = "user2"; member2.age = 16; member2.grade = 80; MemberInit[] members = {member1, member2}; }
메서드를 사용해서 초기값 설정하는 부분의 반복을 제거해보자.
- 객체 지향 프로그래밍에서는 속성과 기능을 한 곳에 두는 것이 더 나은 방법이다. —> MemberInit이 초기값을 설정하는 기능을 제공하는 것이 바람직!
public static void main(String[] args) { MemberInit member1 = new MemberInit(); initMember(member1, "user1", 15, 90); MemberInit member2 = new MemberInit(); initMember(member2, "user2", 16, 80); MemberInit[] members = {member1, member2}; for (MemberInit s : members) { System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" +s.grade); } } static void initMeber(MemberInit member, String name, int age, int grade) { member.name = name; member.age = age; member.grade = grade; }
2. this
- 멤버 변수와 메서드의 매개 변수가 이름이 동일하면, 매개변수가 우선순위를 가진다.
멤버 변수에 접근하려면
this.
으로 인스턴스 자신의 참조값을 가리켜주어야 한다.public class MemberInit { String name; int age; int grade;//추가 void initMember(String name, int age, int grade) { **this**.name = name; **this**.age = age; **this**.grade = grade; } }
- 필드 이름과 매개변수 이름이 다르다면, this를 생략해도 된다.
name = name
→ 필드 이름과 매개변수 이름이 같을 때, name은 둘 다 매개변수를 가르킨다.
3. 생성자 도입
- 객체 지향 언어는 객체를 생성하자마자 즉시 초기값을 할당할 수 있도록 생성자라는 기능을 제공한다.
- 생성자의 이름은 클래스 이름과 같아야 하며, 첫 글자는 대문자로 시작한다.
생성자는 반환 타입이 없다.
public class MemberConstruct { String name; int age; int grade; **MemberConstruct(String name, int age, int grade) { this.name = name; this.age = age; this.grade = grade; }** }
- 생성자는 인스턴스를 생성하고 나서 즉시 호출된다.
new
명령어 다음에 생성자 이름과 매개변수에 맞추어 인수를 전달하면 된다.public static void main(String[] args) { MemberConstruct member1 = **new MemberConstruct("user1", 15, 90);** MemberConstruct member2 = **new MemberConstruct("user2", 16, 80);** } }
- 생성자 특징
- 중복 호출 제거
- 제약 - 생성자 호출 필수
- 생성자가 없으면 프로그램은 작동하지만, 데이터가 없는 상태로 동작하게 된다.
- 직접 정의한 생성자의 경우 반드시 호출되어야 하며, 호출되지 않으면 컴파일 오류가 발생한다.
- 생성자를 사용하면 필수값 입력을 보장할 수 있다.
4. 기본 생성자
- 기본 생성자 : 매개변수가 없는 생성자
- 클래스에 생성자가 없으면 자바 컴파일러는 매개변수가 없고, 작동하는 코드가 없는 기본 생성자를 자동으로 만들어준다.
생성자가 하나라도 있으면 자바는 기본 생성자를 만들지 않는다.
public class MemberDefault { String name; } public class MemberDefaultMain { public static void main(String[] args) { MemberDefault memberDefault = new MemberDefault(); } }
- 위의 경우 자바가 다음과 같은 기본 생성자를 만들어준다.
public class MemberDefault { String name; **//기본 생성자 public MemberDefault() { }** }
5. 오버로딩과 this()
- 생성자는 메서드 오버로딩처럼 매개변수만 다르게 해서 여러 생성자를 제공할 수 있다.
- grade가 꼭 필요한 경우 grade가 있는 생성자를 호출하고, 그렇지 않은 경우에는 grade가 없는 생성자를 호출하면 된다.
public class MemberConstruct { String name; int age; int grade; //추가 MemberConstruct(String name, int age) { this.name = name; this.age = age; this.grade = 50; } MemberConstruct(String name, int age, int grade) { this.name = name; this.age = age; this.grade = grade; } } public class ConstructMain2 { public static void main(String[] args) { MemberConstruct member1 = new MemberConstruct("user1", 15, 90); MemberConstruct member2 = new MemberConstruct("user2", 16); MemberConstruct[] members = {member1, member2}; } } }
this()
를 사용하여 생성자 내부에서 자신의 생성자를 호출할 수 있다.생성자 2개
public class MemberConstruct { String name; int age; int grade; MemberConstruct(String name, int age) { **this(name, age, 50); //변경** } MemberConstruct(String name, int age, int grade) { this.name = name; this.age = age; this.grade = grade; } }
김영한의 실전 자바 기본편을 참고하였습니다