관리 메뉴

나구리의 개발공부기록

기본형과 참조형, 기본형 vs 참조형, 참조형과 메서드 호출, 변수와 초기화, null, NullPointerException, 문제와 풀이 본문

인프런 - 실전 자바 로드맵/실전 자바 - 기본편

기본형과 참조형, 기본형 vs 참조형, 참조형과 메서드 호출, 변수와 초기화, null, NullPointerException, 문제와 풀이

소소한나구리 2024. 12. 21. 11:56

출처 : 인프런 - 김영한의 실전 자바 - 기본편 (유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


1. 기본형 vs 참조형

1) 시작

(1) 자바에서 참조형을 제대로 이해하는 것은 정말 중요함

  • 변수의 데이터 타입을 가장 크게 보면 사용하는 값을 변수에 직접 넣을 수 있는 기본형, 그리고 객체가 저장된 메모리의 위치를 가리키는 참조값을 넣을 수 있는 참조형으로 분류할 수 있음
  • 기본형(Primitive Type): int, long, double, boolean 처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입
  • 참조형(Reference Type): Student student1, int[] students와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입을 말하며 객체 또는 배열에 사용됨
  • 즉 기본형 변수에는 직접 사용할 수 있는 값이 들어가지만 참조형 변수에는 위치(참조값)이 들어가 있으며 참조형 변수를 통해서 뭔가를 하려면 참조값을 통해 해당 위치로 이동해야함

(2) 기본형 vs 참조형 - 기본

  • 기본형은 숫자 10, 20과 같이 실제 사용하는 값을 변수에 담을 수 있으므로 해당 값을 바로 사용할 수 있음
  • 참조형은 실제 사용하는 값을 변수에 담는 것이 아니라 이름 그대로 실제 객체의 위치(참조)를 저장하며 객체라면 .(dot)을 통해서, 배열이라면 [ ]을 통해서 메모리 상에 생성된 실제 인스턴스를 찾아가야 사용할 수 있음

(3) 기본형 vs 참조형 - 계산

  • 기본형은 들어있는 값을 그대로 계산에 사용할 수 있음
  • 그러나 참조형은 들어있는 참조값을 그대로 사용할 수 없고 참조값으로 실제 인스턴스의 기본형에 접근하면 연산을 할 수 있음
// 기본형은 연산이 가능
int a = 10, b = 20
int sum = a + b;

// 참조형은 연산이 불가능
Student s1 = new Student();
Student s2 = new Student();
s1 + s2 // 오류 발생

// .을 통해 객체의 기본형 멤버 변수에 접근하여 연산을 사용할 수 있음
Student s1 = new Student();
s1.grade = 100;
Student s2 = new Student();
s2.grade = 90;
int sum = s1.grade + s2.grade;	// 연산 가능

 

(4) 쉽게 이해하기

  • 기본형을 제외한 나머지는 모두 참조형임
  • 기본형은 소문자로 시작하며 int, long, double, boolean 모두 소문자로 시작함
  • 기본형은 자바가 기본으로 제공하는 데이터 타입이므로 개발자가 새로 정의할 수 없고 개발자는 참조형인 클래스만 직접 정의할 수 있음
  • 클래스는 대문자로 시작하며 클래스는 모두 참조형임

** 참고 - String

  • 자바에서 String은 특별한데, 기본형처럼 사용이 가능했던 String은 사실 클래스임
  • 문자는 매우 자주 다루기 때문에 자바에서 특별하게 편의 기능을 제공함
  • String에 대한 자세한 내용은 뒤에서 설명함

2) 변수 대입

(1) 대원칙: 자바는 항상 변수의 값을 복사해서 대입함

  • 자바에서 변수에 값을 대입하는 것은 변수에 들어 있는 값을 복사해서 대입하는 것임
  • 기본형, 참조형 모두 항상 변수에 있는 값을 복사해서 대입함
  • 기본형이면 변수에 들어 있는 실제 사용하는 값을 복사해서 대입하고 참조형이면 변수에 들어 있는 참조값을 복사해서 대입함
  • 기본형은 변수에 값을 대입하더라도 실제 사용하는 값이 변수에 바로 들어있기 때문에 해당 값만 복사해서 대입한다고 생각하면 쉽게 이해가 가능함
  • 그러나 참조형의 경우 실제 사용하는 객체가 아니라 객체의 위치를 가리키는 참조값만 복사됨
  • 즉, 실제 건물이 복사되는 것이 아니라 건물의 위치인 주소만 복사되는 것이며 같은 건물을 찾아갈 수 있는 방법이 하나 늘어날 뿐임

(2) VarChange1 - 기본형과 변수 대입

package ref;

public class VarChange1 {
    public static void main(String[] args) {
        int a = 10;
        int b = a;
        System.out.println("a = " + a);
        System.out.println("b = " + b);

        //a 변경
        a = 20;
        System.out.println("변경 a = 20");
        System.out.println("a = " + a);
        System.out.println("b = " + b);

        //b 변경
        b = 30;
        System.out.println("변경 b = 30");
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}

/* 출력 결과
a = 10
b = 10
변경 a = 20
a = 20
b = 10
변경 b = 30
a = 20
b = 30
*/
  • int b = a: 변수의 대입은 변수에 들어있는 값을 복사해서 대입하므로 변수 a에 들어있는 값 10을 복사해서 변수 b에 대입함, 변수 a 자체를  b에 대입하는 것이 아님
  • a = 20, b = 30: 변수a와 b에 각각 20과 30을 대입하여도 해당 변수의 값만 변경되고 다른 변수에는 아무런 영향을 주지 않음

(3) 참조형과 변수 대입 - VarChange2

  • 참조형 예시를 위해 int value라는 멤버 변수를 가지는 Data 클래스를 생성 후 진행
  • 실행해보면 실제 대입한 변수의 값뿐만 아니라 다른 변수의 값도 변경된 것처럼 출력이 되었음
package ref;

public class Data {
    int value;
}
package ref;

public class VarChange2 {
    public static void main(String[] args) {
        Data dataA = new Data();
        dataA.value = 10;
        Data dataB = dataA;

        System.out.println("dataA 참조값= " + dataA);
        System.out.println("dataB 참조값= " + dataB);
        System.out.println("dataA.value= " + dataA.value);
        System.out.println("dataB.value= " + dataB.value);

        // dataA 변경
        dataA.value = 20;
        System.out.println("변경 dataA.value = 20");
        System.out.println("dataA.value= " + dataA.value);
        System.out.println("dataB.value= " + dataB.value);

        // dataB 변경
        dataB.value = 30;
        System.out.println("변경 dataB.value = 30");
        System.out.println("dataA.value= " + dataA.value);
        System.out.println("dataB.value= " + dataB.value);
    }
}

/*
dataA 참조값= ref.Data@4517d9a3
dataB 참조값= ref.Data@4517d9a3
dataA.value= 10
dataB.value= 10
변경 dataA.value = 20
dataA.value= 20
dataB.value= 20
변경 dataB.value = 30
dataA.value= 30
dataB.value= 30
*/
  • Data dataB = dataA: 변수 dataA에는 참조값 x001이 들어가있고 변수 dataA에 들어있는 참조값 x001을 복사해서 변수 dataB에 대입함
  • 이제 dataA와 dataB에 들어이는 참조값은 같으므로 둘다 Data 인스턴스를 가리킴
  • dataA.value = 20: dataA가 가리키는 x001 인스턴스의 value값을 10에서 20으로 변경하면 dataA와 dataB가 같은 x001 인스턴스를 참조하기 때문에 dataA.value와 dataB.value는 둘다 같은값인 20을 출력하게 됨
  • dataB.value = 30을 실행했을 때에도 마찬가지로 동작하여 둘다 30을 출력하게 됨
  • 여기에서의 핵심은 Data dataB = dataA라고 했을 때 변수에 들어있는 값을 복사해서 사용한다는 점이며 그 값이 참조값일 경우 dataA와 dataB가 같은 참조값을 가지게 되고 두 변수는 같은 객체 인스턴스를 참조하게 됨

3) 메서드 호출

(1) 기본형과 메서드 호출 - MethodChange1

  • 메서드 호출에서도 대원칙은 동일함
  • 메서드를 호출할 때 사용하는 매개변수(파라미터)도 결국 변수일 뿐이며 메서드를 호출할 때 매개변수에 값을 전달하는 것도 값을 복사해서 전달함
package ref;

public class MethodChange1 {
    public static void main(String[] args) {
        int a = 10;
        System.out.println("메서드 호출 전: a = " + a);
        changePrimitive(10);
        System.out.println("메서드 호출 후: a = " + a);
    }

    static void changePrimitive(int x) {
        x = 20;
    }
}

/* 출력 결과
메서드 호출 전: a = 10
메서드 호출 후: a = 10
*/
  • changePrimitive(10): 메서드를 호출할 때 매개변수 x에 변수 a의 값을 전달하는데 int x = a와 같다고 해석할 수 있으므로 변수 a, x에 각각 숫자 10을 가지고 있음
  • 메서드 안에서 x = 20으로 새로운 값을 대입하고 결과적으로 x의 값만 20으로 변경되고 a의 값은 10으로 유지됨
  • 메서드 종료 후 값을 확인해보면 a는 10이 출력됨
  • 참고로 메서드가 종료되면 매개변수 x는 제거됨

(2) 참조형 메서드 호출 - MethodChange2

package ref;

public class MethodChange2 {
    public static void main(String[] args) {
        Data dataA = new Data();
        dataA.value = 10;
        System.out.println("메서드 호출 전: dataA.value = " + dataA.value);
        changeReference(dataA);
        System.out.println("메서드 호출 후: dataA.value = " + dataA.value);
    }

    static void changeReference(Data dataX) {
        dataX.value = 20;
    }
}

/* 출력 결과
메서드 호출 전: dataA.value = 10
메서드 호출 후: dataA.value = 20
*/
  • changeReference(Data dataX): 메서드를 호출할 때 매개변수 dataX dataA의 값을 전달하므로 이 코드는 Data dataX = dataA와 같다고 해석할 수 있음
  • 변수 dataA가 가진 참조값을 복사해서 dataX에 전달하고 dataA, dataX 둘다 같은 참조값을 가지게 되며 dataX를 통해서도 Data 인스턴스에 접근할 수 있음
  • 메서드 안에서 dataX.value = 20으로 새로운 값을 대입하면 참조값을 통해 Data 인스턴스에 접근하고 그안에 있는 value의 값을 20으로 변경함
  • dataA, dataX 모두 같은 인스턴스를 참조하기 때문에 dataA.value와 dataX.value는 둘다 20이라는 값을 가지게 되어 메서드 호출 전에는 10이였지만 메서드 호출 후에는 20으로 값이 변경되었음

(3) 기본형과 참조형의 메서드 호출

  • 자바에서 메서드의 매개변수는 항상 값에 의해 전달되는데 이 값이 실제 값이냐, 참조(메모리 주소)값이냐에 따라 동작이 달라짐
  • 기본형: 메서드로 기본형 데이터를 전달하면 해당 값이 복사되어 전달되고 메서드 내부에서 매개변수의 값을 변경해도 호출자의 값에는 영향이 없음
  • 참조형: 메서드로 참조형 데이터를 전달하면 참조값이 복사되어 전달되고 메서드 내부에서 매개변수로 전달된 객체의 멤버 변수를 변경하면 호출자의 객체도 변경됨

2. 참조형과 메서드 호출

1) 활용

(1) ClassStart3 다시 보기

  • 기존에 작성해 두었던 class1.ClassStart3 코드는 name, age, grade에 값을 할당하고 학생 정보를 출력하는 코드가 중복되어있음
  • 이런 중복은 메서드를 통해 손쉽게 제거할 수 있음
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);

        System.out.println("student1 = " + student1);
        System.out.println("student2 = " + student2);
    }
}

 

(2-1) 메서드에 객체 전달 - Student

  • 새롭게 ref 패키지에 Student 클래스를 생성
package ref;

public class Student {
    String name;
    int age;
    int grade;
}

 

(2-2) 메서드에 객체 전달 - Method1

  • 참조형은 메서드를 호출할 때 참조값을 전달하므로 메서드 내부에서 전달된 참조값을 통해 객체의 값을 변경하거나 값을 읽어서 사용할 수 있음
  • initStudent(Student student, ...): 전달한 학생 객체의 필드에 값을 설정
  • printStudent(Student student, ...): 전달한 학생 객체의 필드의 값을 읽어서 출력
  • student1이 참조하는 Student 인스턴스에 값을 편리하게 할당하기 위해 initStudent() 메서드를 만들었음
  • 이 메서드를 호출하면서 student1을 전달하면 student1의 참조값이 매개변수에 student에 전달되고 이 참조값을 통해 initStudent()메서드 안에서 student1이 참조하는 것과 동일한 Student 인스턴스에 접근하고 값을 변경할 수 있음
package ref;

public class Method1 {
    public static void main(String[] args) {
        Student student1 = new Student();
        initStudent(student1, "학생1", 15, 90);

        Student student2 = new Student();
        initStudent(student2, "학생2", 16, 80);

        printStudent(student1);
        printStudent(student2);
    }

    static void initStudent(Student student1, String name, int age, int grade) {
        student1.name = name;
        student1.age = age;
        student1.grade = grade;
    }

    static void printStudent(Student student) {
        System.out.println("이름: " + student.name + " 나이: " + student.age + " 성적: " + student.grade);
    }
}

/* 출력 결과
이름: 학생1 나이: 15 성적: 90
이름: 학생2 나이: 16 성적: 80
*/

 

(3) 메서드에서 객체 반환

  • 조금 더 나아가보면 방금 예제도 객체를 생성하고 초기값을 설정하는 부분이 중복이 되어있는데 이부분을 하나로 합칠 수 있음
  • createStudent()라는 메서드를 만들어서 객체를 생성하는 부분을 이 메서드안에 포함시켰으므로 이 메서드 하나로 객체의 생성과 초기값 설정을 모두 처리할 수 있게 됨
  • 그러나 메서드 안에서 객체를 생성했기 때문에 만들어진 객체를 메서드 밖에서 사용할 수 있게 돌려주어야만 메서드 밖에서 이 객체를 사용할 수 있음
  • 메서드의 반환(return) 기능을 사용해서 메서드의 반환타입을 Student로 하고 만들어진 객체의 참조값을 메서드 밖으로 반환하면 됨
  • 즉, 메서드 내부에서 인스턴스를 생성한 후 참조값을 메서드 외부로 반환하여 student1과 student2에 저장하고 이 참조값으로 생성된 인스턴스에 접근하고 사용할 수 있음
package ref;

public class Method2 {
    public static void main(String[] args) {
        Student student1 = createStudent("학생1", 15, 90);
        Student student2 = createStudent("학생2", 16, 80);

        printStudent(student1);
        printStudent(student2);
    }

    static Student createStudent(String name, int age, int grade) {
        Student student = new Student();
        student.name = name;
        student.age = age;
        student.grade = grade;
        return student;
    }

    static void printStudent(Student student) {
        System.out.println("이름: " + student.name + " 나이: " + student.age + " 성적: " + student.grade);
    }
}

3. 변수와 초기화

1) 변수의 종류

(1) 멤버 변수(필드)

  • 클래스에 선언
  • 위에서 만들었던 Student 클래스에 선언된 name, age, grade이 멤버 변수임

(2) 지역 변수

  • 메서드에 선언하며 매개변수도 지역 변수의 한 종류임
  • ClassStart3의 main메서드에 선언된 student1과 student2는 지역변수임
  • MethodChange1 main메서드에 선언된 a와 changePrimitive메서드의 매개변수 x는 지역 변수임
  • 지역 변수는 이름 그대로 특정 지역에서만 사용되는 변수라는 뜻으로 변수 x의 경우 changePrimitive()메서드의 블록에서만 사용되고 해당 메서드가 종료되면 제거됨
  • a메서드도 마찬가지로 main메서드의 블록에서만 사용되며 main메서드가 끝나면 제거됨
public class ClassStart3 {
    public static void main(String[] args) {
        Student student1;
        student1 = new Student();
        Student student2 = new Student();
    }
}

public class MethodChange1 {
    public static void main(String[] args) {
        int a = 10;
        System.out.println("메서드 호출 전: a = " + a);
        changePrimitive(10);
        System.out.println("메서드 호출 후: a = " + a);
    }

    static void changePrimitive(int x) {
        x = 20;
    }
}

2) 변수의 값 초기화

(1) 멤버 변수 초기화

  • 인스턴스의 멤버 변수는 인스턴스를 생성할 때 자동으로 초기화됨
  • 개발자가 초기값을 직접 지정할 수 있으며 지정하지 않으면 숫자는 0, boolean은 false, 참조형은 null로 초기화가 됨

(2) 지역 변수 

  • 지역 변수는 항상 직접 초기화해야 함

(3) 예제 

  • InitData 클래스의 멤버변수 value1에는 초기화를 하지않고 value2에는 10으로 초기화한 후 InitMain 클래스에 객체를 생성하여 출력해보면 초기화 하지 않았음에도 value1필드의 값이 0으로 출력되는 것을 확인할 수 있음
  • InitMain 클래스의 main 메서드에 지역변수 a을 선언만하고 출력하려고하면 컴파일 오류가 나는 것을 확인할 수 있음
package ref;

public class InitData {
    int value1;
    int value2; 
}

package ref;

public class InitMain {
    public static void main(String[] args) {
        InitData data = new InitData();
        System.out.println("value1 = " + data.value1);
        System.out.println("value2 = " + data.value2);

        int a;  // 지역변수
        // System.out.println("a = " + a); // 초기화 하지 않아서 컴파일 오류 발생
    }
}

/* 출력결과
value1 = 0
value2 = 10
*/

4. null

1) null

(1) 설명

  • 참조형 변수에는 항상 객체가 있는 위치를 가리키는 참조값이 들어가는데 아직 가리키는 대상이 없거나 가리키는 대상을 나중에 입력하고 싶을 때 null 값을 넣어 둘 수 있음
  • null은 값이 존재하지 않다, 없다는 뜻임

(2) null값 할당 - NullMain1

  • Data 타입을 받을 수 있는 참조형 변수 data를 만들고 null 값을 할당하면 data 변수에는 아직 가리키는 객체가 없다는 뜻임
  • 이후에 새로운 Data 객체를 생성해서 그 참조값을 data 변수에 할당하면 data 변수가 참조할 객체가 존재함
  • 다시 data에 null을 할당하면 data 변수는 앞서 만든 Data 인스턴스를 더는 참조하지 않음
package ref;

public class NullMain1 {
    public static void main(String[] args) {
        Data data = null;
        System.out.println("1. data = " + data);

        data = new Data();
        System.out.println("2. data = " + data);

        data = null;
        System.out.println("3. data = " + data);
    }
}

/*
1. data = null
2. data = ref.Data@4517d9a3
3. data = null
*/

 

(3) GC - 아무도 참조하지 않는 인스턴스의 최후

  • 위 예제서 마지막에 data에 null을 할당함으로 인해 Data 인스턴스를 더는 아무도 참조하지 않는데 이렇게 아무도 참조하지 않게되면 해당 인스턴스에 접근하기위한 참조값을 다시 구할 방법이 없음
  • 즉, 해당 인스턴스에 다시 접근할 방법이 없다는 뜻이며 이런 인스턴스는 사용되지 않고 메모리 용량만 차지할 뿐임
  • C와 같은 과거 프로그래밍 언어에서는 개발자가 직접 명령어를 사용하여 인스턴스를 메모리에서 제거해야 했음
  • 실수로 인스턴스 삭제를 누락하면 메모리에 사용하지 않는 객체가 가득해져서 메모리 부족 오류가 발생함
  • 자바는 이런 과정을 자동으로 처리해주는데 아무도 참조하지 않는 인스턴스가 있으면 JVM의 GC(가비지 컬렉션)가 더이상 사용하지 않는 인스턴스라 판단하고 해당 인스턴스를 자동으로 메모리에서 제거해줌 - 자바가 가진 큰 장점
  • 객체는 해당 객체를 참조하는 곳이 있으면 JVM이 종료할 때 까지 계속 생존하고 중간에 해당 객체를 참조하는 곳이 모두 사라지면 그때 JVM은 필요 없는 개체로 판단하고 GC를 사용하여 제거함

** 참고

  • 가비지 컬렉션보다 직접 개발자가 메모리 용량을 제거하는 것이 효율적이라고 생각할 수있지만 가비지 컬렉션은 이미 똑똑한 개발자들이 최적화를 진행하여 매우 똑똑하게 동작하므로 믿고 사용해도 문제가 없음

5. NullPointerException

1) null 예외

(1) 설명

  • 참조값 없이 객체를 찾아가면 NullPointerException이라는 예외가 발생하는데 개발자를 가장 많이 괴롭히는 예외임
  • 이름 그대로 null을 가리키다(Pointer)인데 이때 발생하는 예외(Exception)이며 null은 없다는 뜻이므로 결국 주소가 없는 곳을 찾아갈 때 발생하는 예외임
  • 택배를 보낼 때 주소지 없이 택배를 발송하려고하면 사람도 오류에 빠질 것..
  • 객체를 참조할 때는 .(dot)을 사용하여 변수에 저장된 참조값으로 실제 객체를 찾아갈 수 있는데 참조값이 null인 변수에 .(dot)을 찍게되면 찾아갈 수 있는 객체가 없으므로 NullPointerException이 발생함

(2) NullMain2

  • null값이 들어가 있는 참조형 변수 data에 data.value = 10을 입력하려고 시도하면 참조할 객체 인스턴스가 존재하지 않으므로 java.lang.NullPointerException이 발생하고 프로그램이 종료됨
  • 예외가 발생했기 때문에 그 다음 로직은 수행되지 않음
package ref;

public class NullMain2 {
    public static void main(String[] args) {
        Data data = null;
        data.value = 10;    // NullPointerException 예외 발생
        System.out.println("data = " + data);

    }
}

/* 출력결과 
Exception in thread "main" java.lang.NullPointerException: Cannot assign field "value" because "data" is null
	at ref.NullMain2.main(NullMain2.java:6)
*/

2) 멤버 변수와 null

(1) BigData

  • Data data와 int count를 가지고 있는 클래스를 생성
package ref;

public class BigData {
    Data data;
    int count;
}

 

(2) NullMain3

  • NullMain2처럼 지역 변수의 경우 null 문제를 파악하는 것이 어렵지는 않은데, 멤버 변수가 null인 경우에는 주의가 필요함
  • BigData를 생성하면 BigData의 인스턴스가 생성되고 이때 BigData인스턴스의 멤버 변수에 초기화가 일어나서 data 변수는 참조형이므로 null로 초기화가 되고 count 멤버 변수는 숫자이므로 0으로 초기화가 됨
  • bigData.count를 출력하면 0이 출력되고, bigData.data를 출력하면 참조값인 null 이출력됨
  • 그러나 bigData.data.value를 출력하면 data의 값이 null이므로 null에 .(dot)을 찍게 되고 따라서 참조할 곳이 없으므로 NullPointerException예외가 발생됨
package ref;

public class NullMain3 {
    public static void main(String[] args) {
        BigData bigData = new BigData();
        System.out.println("bigData.count = " + bigData.count);
        System.out.println("bigData.data = " + bigData.data);

        // NullPointerException 발생
        System.out.println("bigData.data.value = " + bigData.data.value);
    }
}
/* 실행 결과
bigData.count = 0
bigData.data = null
Exception in thread "main" java.lang.NullPointerException: Cannot read field "value" because "bigData.data" is null
	at ref.NullMain3.main(NullMain3.java:10)
*/

 

(3) NullMain4 - 문제 해결

  • 이런 문제를 해결하려면 Data 인스턴스를 만드록 BigData.data 멤버 변수에 참조값을 할당하면 됨
  • new Data()로 Data 객체가 생성되면서 해당 객체의 멤버 변수인 int value에 기본값인 0으로 초기화가 되어 에러가 발생하지 않음
package ref;

public class NullMain4 {
    public static void main(String[] args) {
        BigData bigData = new BigData();
        bigData.data = new Data();  // Data 객체를 생성하여 인스턴스 변수의 값들이 기본값으로 초기화 됨
        System.out.println("bigData.count = " + bigData.count);
        System.out.println("bigData.data = " + bigData.data);

        System.out.println("bigData.data.value = " + bigData.data.value);
    }
}

/* 출력결과
bigData.count = 0
bigData.data = ref.Data@2ff4acd0
bigData.data.value = 0
*/

6. 문제와 풀이

1) 상품 주문 시스템 개발 - 리펙토링

(1) 문제 설명 및 요구사항

  • 앞에서 만들었던 상품 주문 시스템을 리팩토링
  • ProductOrderMain2 클래스 안에 main() 메서드를 포함하여 여러 상품의 주문 정보를 배열로 관리하고 그 정보들을 출력하고 최종 금액을 계산하여 출력하고 아래 3개의 메서드를 포함해야함
  • static ProductOrder createOrder(String productName, int price, int quantity): ProductOrder를 생성하고 매개변수로 초기값을 설정, 생성한 ProductOrder를 반환
  • static void printOrders(ProductOrder[] orders): 배열을 받아서 배열에 들어있는 전체 ProductOrder의 상품명, 가격, 수량을 출력
  • static int getTotalAmount(ProductOrder[] orders): 배열을 받아서 배열에 들어있는 전체 ProductOrder의 총 결제 금액을 계산하고 계산 결과를 반환

(2) 예시 코드

더보기
더보기
package ref.ex;
public class ProductOrderMain2 {
    public static void main(String[] args) {
        // 여러 상품의 주문 정보를 담는 배열 생성
        // createOrder()를 여러번 사용해서 상품 주문 정보들을 생성하고 배열에 저장
        // getTotalAmount()를 사용해서 총 결제 금액 계산
        // 총 결제 금액 출력
    }
}

 

상품명: 두부, 가격: 2000, 수량: 2

상품명: 김치, 가격: 5000, 수량: 1

상품명: 콜라, 가격: 1500, 수량: 2

결제 금액: 12000

(3) 정답

더보기
더보기
package ref.ex;

public class ProductOrderMain2 {
    public static void main(String[] args) {
        ProductOrder order1 = createOrder("두부", 2000, 2);
        ProductOrder order2 = createOrder("김치", 5000, 1);
        ProductOrder order3 = createOrder("콜라", 1500, 2);

        ProductOrder[] productOrders = new ProductOrder[]{order1, order2, order3};

        printOrders(productOrders);

        int totalAmount = getTotalAmount(productOrders);
        System.out.println("총 결제 금액: " + totalAmount);
    }

    static ProductOrder createOrder(String productName, int price, int quantity) {
        ProductOrder order = new ProductOrder();
        order.productName = productName;
        order.price = price;
        order.quantity = quantity;
        return order;
    }

    static void printOrders(ProductOrder[] orders) {
        for (ProductOrder order : orders) {
            System.out.println("상품명: " + order.productName + ", 가격: " + order.price + ", 수량: " + order.quantity);
        }
    }

    static int getTotalAmount(ProductOrder[] orders) {
        int totalPrice = 0;
        for (ProductOrder order : orders) {
            totalPrice += order.price * order.quantity;
        }
        return totalPrice;
    }
}

 

상품명: 두부, 가격: 2000, 수량: 2
상품명: 김치, 가격: 5000, 수량: 1
상품명: 콜라, 가격: 1500, 수량: 2
총 결제 금액: 12000

2) 상품 주문 시스템 개발 - 사용자 입력

(1) 문제 설명 및 요구사항

  • 입력, 출력 예시를 참고하여 앞서 만든 상품 주문 시스템을 사용자 입력을 받도록 개선
  • 주문 수량과 상품 가격, 수량, 상품명을 입력을 받도록 변경하고 입력이 끝나면 등록한 상품과 총 결제 금액을 출력

(2) 입력, 출력 예시

더보기
더보기

입력할 주문의 개수를 입력하세요: 3

1번째 주문 정보를 입력하세요.

상품명: 두부

가격: 2000

수량: 2

2번째 주문 정보를 입력하세요.

상품명: 김치

가격: 5000

수량: 1

3번째 주문 정보를 입력하세요.

상품명: 콜라

가격: 1500

수량: 2

상품명: 두부, 가격: 2000, 수량: 2

상품명: 김치, 가격: 5000, 수량: 1

상품명: 콜라, 가격: 1500, 수량: 2

결제 금액: 12000

(3) 정답

더보기
더보기
package ref.ex;

import java.util.Scanner;

public class ProductOrderMain3 {
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        System.out.print("입력할 주문의 개수를 입력하세요: ");
        int inputOrderCount = scanner.nextInt();

        ProductOrder[] productOrders = new ProductOrder[inputOrderCount];

        int n = 0;
        while (n < inputOrderCount) {
            System.out.println((n + 1) + "번째 주문 정보를 입력하세요.");

            System.out.print("상품명: ");
            String productName = scanner.next();

            System.out.print("가격: ");
            int price = scanner.nextInt();

            System.out.print("수량: ");
            int quantity = scanner.nextInt();

            ProductOrder order = createOrder(productName, price, quantity);
            productOrders[n] = order;

            n++;
        }

        printOrders(productOrders);

        System.out.println("총 결제 금액: " + getTotalAmount(productOrders));
    }

    static ProductOrder createOrder(String productName, int price, int quantity) {
        ProductOrder order = new ProductOrder();
        order.productName = productName;
        order.price = price;
        order.quantity = quantity;
        return order;
    }

    static void printOrders(ProductOrder[] orders) {
        for (ProductOrder order : orders) {
            System.out.println("상품명: " + order.productName + ", 가격: " + order.price + ", 수량: " + order.quantity);
        }
    }

    static int getTotalAmount(ProductOrder[] orders) {
        int totalPrice = 0;
        for (ProductOrder order : orders) {
            totalPrice += order.price * order.quantity;
        }
        return totalPrice;
    }
}

 

입력할 주문의 개수를 입력하세요: 3
1번째 주문 정보를 입력하세요.
상품명: 두부
가격: 2000
수량: 2
2번째 주문 정보를 입력하세요.
상품명: 김치
가격: 5000
수량: 1
3번째 주문 정보를 입력하세요.
상품명: 콜라
가격: 1500
수량: 2
상품명: 두부, 가격: 2000, 수량: 2
상품명: 김치, 가격: 5000, 수량: 1
상품명: 콜라, 가격: 1500, 수량: 2
총 결제 금액: 12000


7. 정리

1) 자바의 대원칙

(1) 자바는 항상 변수의 값을 복사해서 대입함

  • 자바에서 변수에 값을 대입하는 것은 변수에 들어 있는 값을 복사해서 대입하며 기본형, 참조형 모두 동일함
  • 기본형이면 변수에 들어있는 실제 사용하는 값을 복사해서 대입하고 참조형이면 변수에 들어있는 참조값을 복사해서 대입함
  • 기본형이든 참조형이든 변수의 값을 대입하는 방식은 동일하지만 기본형과 참조형에 따라 동작하는 방식이 달라짐

2) 기본형 vs 참조형

(1) 기본

  • 자바의 데이터 타입은 크게 기본형과 참조형으로 나뉘며 기본형을 제외한 나머지 변수는 참조형이고 클래스와 배열을 다루는 변수도 참조형임
  • 기본형 변수는 값을 직접 저장하고 참조형 변수는 참조(주소)를 저장함
  • 기본형 변수는 산술 연산을 수행할 수 있지만 참조형 변수는 산술 연산을 수행할 수 없음
  • 기본형 변수는 null을 할당할 수 없지만 참조형 변수는 null을 할당할 수 있음

(2) 대입

  • 기본형은 사용하는 값을 복사해서 전달함
  • 참조형은 실제 인스턴스가 복사되는 것이 아니라 인스턴스를 가리키는 참조값을 복사해서 전달한다는 것이 중요하며 하나의 인스턴스를 여러곳에서 참조할 수 있음
  • 즉 기본형은 사용하는 값이 참조형은 참조값이 들어있으며 변수에 어떤 값이 들어있든 간에 그 값을 그대로 복사해서 전달함

(3) 메서드 호출

  • 메서드 호출시 기본형은 메서드 내부에서 매개변수(파라미터)의 값을 변경해도 호출자의 변수 값에는 영향이 없음
  • 그러나 참조형은 메서드 내부에서 매개변수(파라미터)로 전달된 객체의 멤버 변수를 변경하면 호출자의 객체에서도 변경됨