본문 바로가기
IT/김영한의실전JAVA-기본_inFlearn

2. 기본형과 참조형 - 기본형vs참조형1(시작) / 기본형vs참조형2(변수 대입) / 기본형vs참조형3(메서드 호출) / 참조형과 메서드 호출(활용) / 변수와 초기화 / null / NullPointerException / 문제와 풀이

by for-learn 2025. 2. 6.

2-1. 기본형 vs 참조형1 - 시작

변수의 데이터 타입

  • 기본형(Primitive Type):
    • int, long, double, boolean 처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입
    • 기본형은 숫자 10, 20과 같이 실제 사용하는 값을 변수에 담고, 해당 값을 바로 사용할 수 있다.
    • 기본형을 제외한 나머지는 모두 참조형이다.
    • 기본형은 소문자로 시작.
  • 참조형(Reference Type):
    • Student student1, int[ ] students 와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터 타입
    • 참조형은 실제 객체의 위치(참조, 주소)를 저장한다. 객체와 배열이 있다.
      • 객체는 . (dot)을 통해서 메모리 상에 생성된 객체를 찾아 사용한다.
      • 배열은 [ ]를 통해서 메모리 상에 생성된 배열을 찾아 사용한다. 
    • 클래스는 대문자로 시작한다. => 모두 참조형
    • String은 사실 클래스이면서, 참조형이다. 그런데 기본형처럼 문자값을 바로 대입 가능하다. => 뒤에서 추가 설명

기본형 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; //연산 가능

 

2-2. 기본형 vs 참조형2 - 변수 대입

자바는 항상 변수의 값을 복사해서 대입한다. (기본형, 참조형 모두)

 

기본형 대입

int a = 10;
int b = a;
  • a와 b는 서로 다르다.

참조형 대입

Student s1 = new Student(); // x001
Student s2 = s1; // x001 참조값을 복사해서 대입
  • s1과 s2는 같다.

VarChange1.java (기본형과 변수 대입)

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);
    }
}

 

Data.java (클래스)

package ref;

public class Data {
    int value;
}

VarChange2.java (참조형과 변수 대입)

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);
    }
}

  • 현재 메모리 상 Data의 인스턴스는 한 개이고,
  • dataAdataB가 한개에 대한 동일한 참조값을 가지고 있다. 
  • 두 변수는 같은 객체 인스턴스를 참조한다. 

 

2-3. 기본형 vs 참조형3 - 메서드 호출

메서드를 호출할 때 사용하는 매개변수(파라미터)도 결국 변수일 뿐이다.

따라서 메서드를 호출할 때 매개변수에 값을 전달하는 것도 앞서 설명한 내용과 같이 값을 복사해서 전달한다.

MethodChange1.java (기본형과 메서드 호출)

package ref;

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

    static void changePrimitive(int x) {
        x = 20;
    }
}
  • 실행 결과를 미리 예측해보자

MethodChange2.java (참조형과 메서드 호출)

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;
    }
}
  • 실행 결과를 미리 예측해보자

 

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

자바에서 메서드의 매개변수(파라미터)는 항상 값에 의해 전달됨.

이 값이 실제 값이냐, 참조(메모리 주소)값이냐에 따라 동작이 다름

  • 기본형: 메서드로 기본형 데이터를 전달하면, 해당 값이 복사되어 전달된다.
    메서드 내부 매개변수(파라미터) 값을 변경해도, 호출자의 변수 값에는 영향이 없다. 
  • 참조형: 메서드로 참조형 데이터를 전달하면, 참조값이 복사되어 전달된다.
    메서드 내부 매개변수(파라미터)로 전달된 객체의 멤버 변수를 변경하면, 호출자의 객체도 변경된다. 

 

2-4. 참조형 메서드 호출 - 활용

이전 객체의 멤버변수에 값을 하나하나 대입했던 중복 부분을 메서드를 통해 줄일 수 있다. 

Student.java (클래스)

package ref;

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

Method1.java (메서드에 객체 전달)

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 student, String name, int age, int grade) {
        student.name = name;
        student.age = age;
        student.grade = grade;
    }

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

 

위 코드에서 일부 중복이 있다.

Student student1 = new Student();
initStudent(student1, "학생1", 15, 90);
Student student2 = new Student();
initStudent(student2, "학생2", 16, 80);

Method2.java (메서드에서 객체 반환)

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 student1) {
        System.out.println("이름:" + student1.name + " 나이:" + student1.age + " 성적:" + student1.grade);
    }
}

 

2-5. 변수와 초기화

변수의 종류

  • 멤버 변수(필드): 클래스에 선언
  • 지역 변수: 메서드에 선언, 매개변수도 지역 변수의 한 종류

멤버 변수, 필드 예시

public class Student {
   String name;
   int age;
   int grade;
} // name, age, grade는 멤버 변수

 

 

지역 변수 예시

public class ClassStart3 {
    public static void main(String[] args) {
        Student student1;
        student1 = new Student();
        Student student2 = new Student();
    }
} // student1, student2는 지역 변수

public class MethodChange1 {
    public static void main(String[] args) {
        int a = 10;
        System.out.println("메서드 호출 전: a = " + a);
        changePrimitive(a);
        System.out.println("메서드 호출 후: a = " + a);
    }
    public static void changePrimitive(int x) {
        x = 20;
    }
} // a, x(매개변수)는 지역 변수

 

변수의 값 초기화

  • 멤버 변수: 자동 초기화 => 인스턴스의 멤버 변수는 인스턴스를 생성할 때 자동으로 초기화된다.
    • 숫자( int ) = 0 , boolean = false , 참조형 = null ( null 값은 참조할 대상이 없다는 뜻으로 사용 된다.)
    • 개발자가 초기값을 직접 지정할 수 있다.
  • 지역 변수: 수동 초기화 => 지역 변수는 항상 직접 초기화해야 한다.

InitData.java (클래스)

package ref;

public class InitData {
    int value1; // 초기화 하지 않음
    int value2 = 10; // 10으로 초기화
}

InitMain.java (멤버 변수의 초기화)

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);
    }
}

 

2-6. null

null은 값이 존재하지 않는, 없다는 뜻

택배 보낼 제품은 없지만, 보낼 주소지(참조형 변수)가 결정되지 않았을 때 => null

NullMain1.java (null 값 할당)

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);
    }
}

Data 타입을 받을 수 있는 참조형 변수 data에 null 값을 할당 후, 새로운 Data 객체를 생성해 참조값을 data 변수에 할당
참조형 변수 data에 다시 null 값을 할당해 data 변수는 앞서 만든 Data 인스턴스를 참조하지 않는다.

  • 이때, x001 인스턴스를 참조하는 변수가 없어, 이 인스턴스는 사용되지 않고 메모리 용량만 차지한다. 
  • C와 같은 과거 프로그래밍 언어는 개발자가 직접 명령어로 인스턴스를 메모리에서 제거해야 한다. 
    인스턴스 삭제를 누락하면 메모리에 사용하지 않는 객체가 가득해져서 메모리 부족 오류가 발생한다.
  • 자바는 이런 과정을 자동으로 처리해준다.
    아무도 참조하지 않는 인스턴스가 있으면 JVM의 GC(가비지 컬렉션)가 해당 인스턴스를 더이상 사용하지 않는다고 판단해 자동으로 메모리에서 제거한다. => 자바의 장점
객체는 해당 객체를 참조하는 곳이 있으면, JVM이 종료할 때 까지 계속 생존
중간에 해당 객체를 참조하는 곳이 모두 사라지면,
그때 JVM은 필요 없는 객체로 판단다고 GC(가비지 컬렉션)를 사용해서 제거
=> 필요 없는 객체가 어느정도 모이면 GC가 한번에 제거

 

2-7. NullPointerException

주소지 없이 택배를 발송하면 어떤 문제가 발생할가?

참조값 없이 객체를 찾아가면 어떤 문제가 발생할까?

NullPointerException이라는 예외 발생 => null을 가리킬때(Pointer), 발생하는 예외(Exception)

주소가 없는 곳을 찾아갈 때 발생하는 예외

 

객체를 참조할 때는 . (dot)을 사용

NullPointerException 은 이처럼 null . (dot)을 찍었을 때 발생

NullMain2.java

package ref;

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

NullMain3.java (멤버 변수와 null)

package ref;

public class NullMain3 {
    public static void main(String[] args) {
        BigData bigData = new BigData();
        System.out.println("bigData.count=" + bigData.count); // 숫자는 초기값이 0 이다.
        System.out.println("bigData.data=" + bigData.data); // 참조형은 초기값이 null 이다.

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

 

NullMain4.java (멤버 변수의 null 해결)

package ref;

public class NullMain4 {
    public static void main(String[] args) {
        BigData bigData = new BigData();
        bigData.data = new Data();
        System.out.println("bigData.count=" + bigData.count); // 숫자는 초기값이 0 이다.
        System.out.println("bigData.data=" + bigData.data);

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

참조형 변수 data의 값이 null 일 때와 참조값 일 때

 

2-8. 문제와 풀이

ProductOrder.java (클래스)

package ref.ex;

public class ProductOrder {
    String productName;
    int price;
    int quantity;
}

ProductOrder.java

package ref.ex;

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

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

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

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

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

 

ProductOrderMain3.java (Scanner로 입력받기)

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 count = scanner.nextInt();
        scanner.nextLine(); // \n처리

        ProductOrder[] orders = new ProductOrder[count];

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

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

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

            System.out.print("수량: ");
            int quantity = scanner.nextInt();
            scanner.nextLine(); // \n처리

            orders[i] = createOrder(productName, price, quantity);
        }

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

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

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

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