클래스가 필요한 이유

자바에서 클래스와 객체라는 개념은 매우 중요하다.

자바에서 클래스를 사용하는 이유는 다양한 자료형과 구조체 등 여러 기능적인 요소들을 묶어서 관리할 수 있을 뿐만 아니라 데이터를 처리하는 기능까지 함께 관리할 수 있는 문법 요소이기 때문이다.

 

문제: 학생 정보 출력 프로그램 만들기

두 명의 학생 정보를 출력하는 프로그램을 작성해야 한다. 각 학생은 이름, 나이, 성적을 가지고 있다.

 

[1. 변수 사용]

package class1;

public class ClassStart1 {
    public static void main(String[] args) {
        String student1Name = "학생1";
        int student1Age = 15;
        int student1Grade = 90;

        String student2Name = "학생2";
        int student2Age = 16;
        int student2Grade = 80;

        System.out.println("이름: " + student1Name + " 나이: " + student1Age + " 성적: " + student1Grade);
        System.out.println("이름: " + student2Name + " 나이: " + student2Age + " 성적: " + student2Grade);
    }
}

실행 결과

학생 2명을 다루어야 하기 때문에 각각 다른 변수를 사용했다. 이 코드의 문제는 학생이 늘어날 때마다 변수를 추가로 선언해야 하고, 또 출력하는 코드도 추가해야 한다. 이를 해결하기 위해서는 배열을 사용하면 된다.

 

[2. 배열 사용]

package class1;

public class ClassStart2 {
    public static void main(String[] args) {
        String[] name = {"학생1", "학생2"};
        int[] age = {15, 16};
        int[] grade = {90, 80};

        for(int i = 0; i<name.length; i++){
            System.out.println("이름: " + name[i] + " 나이: " + age[i] + " 성적: " + grade[i]);
        }
    }
}

 

배열을 사용한 덕분에 학생이 추가되어도 배열에 학생의 데이터만 추가하면 된다.

그러나...

한 학생의 데이터가 name[ ], age[ ], grade[ ] 3개의 배열에 나누어져 있기 때문에 데이터를 변경할 때 매우 조심해서 작업해야 한다.

한 학생의 데이터를 관리하기 위해서는 3개의 배열의 인덱스 순서를 항상 정확하게 맞춰야 하는데, 이렇게 하면 특정 학생의 데이터를 변경할 때 실수할 가능성이 매우 높다.

 

→ 이름, 나이, 성적을 각각 따로 나누어서 관리하는 것은 사람이 관리하기 좋은 방식이 아니다.

사람이 관리하기 좋은 방식은 학생이라는 개념을 하나로 묶는 것이다. 그리고 각각의 학생별로 본인의 이름, 나이, 성적을 관리하는 것이다.

 


클래스 도입

클래스를 사용해서 학생이라는 개념을 만들고, 각각의 학생별로 본인의 이름, 나이, 성적을 관리하자.

※ 클래스는 관례상 대문자로 시작하고 낙타 표기법을 사용함

ex) Student, User, MemberService

package class1;

public class Student { // class 키워드를 사용해서 학생 클래스(Student)를 정의
    String name; // 이름 변수
    int age; // 나이 변수
    int grade; // 성적 변수
}

클래스에 소속된 변수(name, age, grade) → 멤버 변수 or 필드

  • 멤버 변수(Member Variable): 특정 클래스에 소속된 멤버들
  • 필드(Field): 데이터 항목. 데이터베이스, 엑셀 등에서 데이터 각각의 항목을 필드라고 부름

 

[3. 클래스 사용]

package class1;

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;

        System.out.println("이름:" + student1.name + " 나이:" + student1.age + " 성적:" + student1.grade);
        System.out.println("이름:" + student2.name + " 나이:" + student2.age + " 성적:" + student2.grade);
    }
}

실행 결과

 

클래스와 사용자 정의 타입

클래스를 사용하면 int, String과 같은 타입을 직접 만들 수 있다.

즉, 클래스를 통해서 사용자가 원하는 종류의 데이터 타입을 마음껏 정의할 수 있다.

int age; -- 정수 타입

String name; -- 문자 타입

Student student1; -- 학생(Student)이라는 타입

 

클래스와 객체(=인스턴스)
  • 클래스: 사용자가 직접 정의하는 사용자 정의 타입을 만들 수 있는 설계도 -- 붕어빵틀
  • 객체(=인스턴스): 실제 메모리에 만들어진 실체 -- 붕어빵

※ 객체 vs 인스턴스

(둘다 클래스에서 나온 실체라는 핵심 의미가 같기 때문에 보통 둘을 구분하지 않고 사용함)

  • 객체(Object): 클래스에서 정의한 속성과 기능을 가진 실체로서, 서로 독립적인 상태를 가진다. ex) student1은 학생1의 속성을 가지는 객체, student2는 학생2의 속성을 가지는 객체. student1과 student2는 같은 클래스에서 만들어졌지만, 서로 다른 객체이다.
  • 인스턴스(Instance): 특정 클래스로부터 생성된 객체. 인스턴스는 주로 객체가 어떤 클래스에 속해 있는지 강조할 때 사용하는 용어. ex) student1 객체는 Student 클래스의 인스턴스다.
  • 용어상 인스턴스는 객체보다 좀 더 관계에 초점을 맞춤. 특정 클래스와의 관계를 명확히 할 때 인스턴스 용어를 주로 사용함. ex) student1은 객체이지만, 이 객체가 Student 클래스로부터 생성되었다는 점을 명확히 하기 위해 student1을 Student의 인스턴스라고 부른다.

 

[코드 분석]

package class1;

public class Student { // 클래스(설계도)인 Student 생성
    String name;
    int age;
    int grade;
}
package class1;

public class ClassStart3 {
    public static void main(String[] args) {
        Student student1; // Student라는 변수 선언
        student1 = new Student(); // 메모리 어딘가에 객체(인스턴스) 생성 -> 참조값(주소) 반환[2f4d3709]
        student1.name = "학생1";
        student1.age = 15;
        student1.grade = 90;

        Student student2 = new Student(); // Student라는 변수 선언 -> 메모리 어딘가에 객체(인스턴스) 생성 -> 참조값(주소) 반환[4e50df2e]
        student2.name = "학생2";
        student2.age = 16;
        student2.grade = 80;

        System.out.println(student1);
        System.out.println(student2);

        System.out.println("이름:" + student1.name + " 나이:" + student1.age + " 성적:" + student1.grade);
        System.out.println("이름:" + student2.name + " 나이:" + student2.age + " 성적:" + student2.grade);
    }
}

실행 결과: 위에 2개는 참조값

  • 클래스: Student
  • 객체(=인스턴스): student1, student2

1. Student student1;

- Student 타입을 받을 수 있는 변수 선언

- int는 정수를, String은 문자를 담을 수 있듯이 Student는 Student 타입의 객체(인스턴스)를 받을 수 있다.

 

2. student1 = new Student( );

- new Student( ): new새로 생성한다는 의미, new Student( )는 Student 클래스 정보를 기반으로 새로운 객체(인스턴스)를 생성하라는 의미 → 메모리에 실제 Student 객체(인스턴스) 생성

- Student 클래스는 String name, int age, int grade 멤버 변수(=필드)를 가지고 있다. 이 변수를 사용하는 데 필요한 메모리 공간도 함께 확보한다.

메모리 어딘가에 생성됨

 

3. 참조값 보관

student1 = 2f4d3709;  // Student 인스턴스 참조값 보관

- 객체(인스턴스)를 생성하면 자바는 메모리 어딘가에 있는 이 객체에 접근할 수 있는 참조값(주소) (2f4d3709)을 반환한다.

- new 키워드를 통해 객체(인스턴스)가 생성되고 나면 참조값을 반환한다. 앞서 선언한 변수인 Student student1에 생성된 객체의 참조값 (2f4d3709)을 보관한다.

- Student student1 변수는 이제 메모리에 존재하는 실제 Student 객체(인스턴스)의 참조값을 가지고 있다.

이제 student1 변수를 통해 메모리에 있는 실제 객체를 접근(참조)하고 사용할 수 있다.

 

※ 참조값을 변수에 보관해야 하는 이유

객체를 생성하는 new Student( ) 코드 자체는 단순히 Student 클래스를 기반으로 메모리에 실체 객체를 만든 것일뿐, 코드 자체에는 아무 이름이 없다. 따라서 생성한 객체에 접근할 수 있는 방법이 필요하다. 이런 이유로 객체를 생성할 때 반환되는 참조값을 어딘가에 보관해두어야 한다. 앞서 Student student1 변수에 참조값(2f4d3709)을 저장해두었으므로 저장한 참조값(2f4d3709)을 통해서 실제 메모리에 존재하는 객체에 접근할 수 있다.

 

Student student1 = new Student(); //1. student1 변수 선언 → Student 객체 생성
Student student1 = 2f4d3709; //2. new Student()의 결과로 2f4d3709 참조값 반환
student1 = 2f4d3709; //3. 최종 결과

 

이후에 학생2(student2)까지 생성하면 Student 객체(인스턴스)가 메모리에 2개 생성된다. 각각 참조값이 다르므로 서로 구분할 수 있다.

student1 변수의 참조값: 2f4d3709

student2 변수의 참조값: 4e50df2e

 

 


객체 사용

클래스를 통해 생성한 객체를 사용하려면 먼저 메모리에 존재하는 객체에 접근해야 한다.

객체에 접근하려면 . (점, dot)을 사용하면 된다.

//객체 값 대입
student1.name = "학생1";
student1.age = 15;
student1.grade = 90;

//객체 값 사용
System.out.println("이름:" + student1.name + " 나이:" + student1.age + " 성적:" +
student1.grade);

 

객체에 값 대입

객체가 가지고 있는 멤버 변수(=필드) (name, age, grade)에 값을 대입하려면 먼저 객체에 접근해야 한다.

객체에 접근하려면 . (점, dot)을 사용하면 된다. . (점, dot)은 변수(student1)에 들어있는 참조값(2f4d3709)을 읽어서 메모리에 존재하는 객체에 접근한다.

student1.name="학생1" //1. student1 객체의 name 멤버 변수에 값 대입
2f4d3709.name="학생1" //2.변수에 있는 참조값을 통해 실제 객체에 접근, 해당 객체의 name 멤버 변수에 값 대입

 

student1 . (dot): student1 변수가 가지고 있는 참조값을 통해 실제 객체에 접근한다.

student1은 2f4d3709이라는 참조값을 가지고 있으므로 2f4d3709 위치에 있는 Student 객체에 접근한다.

 

2f4d3709.name = "학생1": 2f4d3709 객체가 있는 곳의 name 멤버 변수에 "학생1" 데이터가 저장된다.

 

객체 값 읽기

객체의 값을 읽는 것도 앞의 내용과 같다. . (점, dot) 키워드를 통해 참조값을 사용하여 객체에 접근한 다음에 원하는 작업을 하면 된다.

//1. 객체 값 읽기
System.out.println("이름:" + student1.name);
//2. 변수에 있는 참조값을 통해 실제 객체에 접근하고, name 멤버 변수에 접근한다.
System.out.println("이름:" + 2f4d3709.name);
//3. 객체의 멤버 변수의 값을 읽어옴
System.out.println("이름:" + "학생1");

2f4d3709에 있는 Student 인스턴스의 name 멤버 변수는 "학생1"이라는 값을 가지고 있다. 이 값을 읽어서 사용한다.

 

 

 


배열 도입 - 시작

코드에서 아쉬운 부분이 있는데, 바로 학생을 출력하는 부분이다.

System.out.println("이름:" + student1.name + " 나이:" + student1.age + " 성적:" + student1.grade);
System.out.println("이름:" + student2.name + " 나이:" + student2.age + " 성적:" + student2.grade);

새로운 학생이 추가될 때마다 출력하는 부분도 함께 추가해야 한다.

배열을 사용하여 해결해보자! (Student 타입을 사용하는 배열 도입)

package class1;

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;

        System.out.println("이름:" + students[0].name + " 나이:" + students[0].age + " 성적:" + students[0].grade);
        System.out.println("이름:" + students[1].name + " 나이:" + students[1].age + " 성적:" + students[1].grade);
    }
}

 

배열에 참조값 대입

Student를 담을 수 있는 배열을 생성하자.

Student[] students = new Student[2];

  • Student 변수를 2개 보관할 수 있는 사이즈 2짜리 배열을 만든다.
  • Student 타입의 변수는 Student 인스턴스의 참조값을 보관한다. Student 배열의 각각의 항목도 Student 타입의 변수일 뿐이기 때문에 Student 타입의 참조값을 보관한다.
  • 배열에는 아직 참조값을 대입하지 않았기 때문에 참조값이 없다는 의미의 null 값으로 초기화된다.

이제 배열에 객체를 보관하자.

students[0] = student1;
students[1] = student2;

 

자바에서 대입은 항상 변수에 들어 있는 값을 복사한다.

 

student1, student2에는 참조값이 보관되어 있다. 따라서 이 참조값이 배열에 저장된다.

= student1, student2에 보관된 참조값을 읽고 복사해서 배열에 대입한다.

students[0] = student1;
students[1] = student2;

//자바에서 대입은 항상 변수에 들어 있는 값을 복사한다.
students[0] = 2f4d3709;
students[1] = 4e50df2e;

오른쪽 변수인 student1, student2에는 참조값이 들어있다. 이 값을 복사해서 왼쪽에 있는 배열에 전달한다.

 

[배열에 참조값을 대입한 이후 배열 그림]

이제 배열은 x001, x002의 참조값을 가진다. 참조값을 가지고 있기 때문에 x001(학생1), x002(학생2) Student 인스턴스에 모두 접근할 수 있다.

자바에서 변수의 대입( = )은 모두 변수에 들어있는 값을 복사해서 전달하는 것이다. 이 경우 오른쪽 변수인 student1 , student2에는 참조값이 들어있다. 그래서 이 값을 복사해서 왼쪽에 있는 배열에 전달한다. 따라서 기존 student1 , student2 에 들어있던 참조값은 당연히 그대로 유지된다.

 

주의!

변수에는 인스턴스 자체가 들어있는 것이 아니다. 인스턴스의 위치를 가리키는 참조값이 들어있을 뿐이다. 따라서 대입(=)시에 인스턴스가 복사되는 것이 아니라 참조값만 복사된다.

 

배열에 들어있는 객체 사용

배열에 들어있는 객체를 사용하려면 먼저 배열에 접근하고, 그 다음에 객체에 접근하면 된다.

 

[학생1 예제]

System.out.println(students[0].name); //배열 접근 시작
System.out.println(x005[0].name); //[0]를 사용해서 x005 배열의 0번 요소에 접근
System.out.println(x001.name); //.(dot)을 사용해서 참조값으로 객체에 접근
System.out.println("학생1");

 

 


배열 도입 - 리펙토링

배열을 사용한 덕분에 출력해서 for문을 도입할 수 있게 되었다.

package class1;

public class ClassStart5 {
    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[] {student1, student2};
         // Student[] students = {student1, student2}; 도 가능
         
        // for문 적용
          for(int i = 0; i < students.length; i++){
            System.out.println("이름:" + students[i].name + " 나이:" + students[i].age + " 성적:" + students[i].grade);
            }

 

배열 선언 최적화

직접 정의한 Student 타입도 일반적인 변수와 동일하게 배열을 생성할 때 포함할 수 있다.

Student[] students = new Student[]{student1, student2};

 

다음과 같이 더 최적화할 수 있다.

Student[] students = {student1, student2};

 

for문 최적화

배열을 사용한 덕분에 for문을 사용해서 반복 작업을 깔끔하게 처리할 수 있다.

for(int i = 0; i < students.length; i++){
     System.out.println("이름:" + students[i].name + " 나이:" + students[i].age + " 성적:" + students[i].grade);
 }

 

한편, 반복 요소를 변수에 담아서 더 깔끔하게 처리할 수 있다.

students[ i ].이 자꾸 반복되기 때문에 이걸 Student s라는 변수에 담아두고 사용해도 된다.

for(int i = 0; i < students.length; i++){
    Student s = students[i];
    System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
}

 

이 경우에 향상된 for문(for each문)을 사용하는 것이 가장 깔끔하다.

for(Student s : students){
    System.out.println("이름:" + s.name + " 나이:" + s.age + " 성적:" + s.grade);
}

 

+ Recent posts