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의 인스턴스는 한 개이고,
- dataA와 dataB가 한개에 대한 동일한 참조값을 가지고 있다.
- 두 변수는 같은 객체 인스턴스를 참조한다.
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);
}
}
- 이때, 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);
}
}
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;
}
}