자바에서 참조형을 제대로 이해하는 것은 정말 중요하다.
변수의 데이터타입을 가장 크게 보면 기본형과 참조형으로 분류가능
기본형 변수에는 직접 사용할 수 있는 값이 들어있지만 참조형 변수에는 위치(참조값)가 들어가 있다. 참조형 변수를 통해서 뭔가 하려면 결국 참조값을 통해 해당 위치로 이동해야 한다.
기본형은 들어있는 값을 그대로 계산에 사용할 수 있다.
예) 더하고 빼고, 사용하고 등등, (숫자 같은 것들은 바로 계산할 수 있음)
참조형은 들어있는 참조값을 그대로 사용할 수 없다. 주소지만 가지고는 할 수 있는게 없다. 주소지에 가야 실체가 있다!
예) 더하고 빼고 사용하고 못함!! 참조값만 가지고는 계산 할 수 있는 것이 없음!
기본형을 제외한 나머지는 모두 참조형이다.
클래스는 대문자로 시작한다. ex) Student
String은 사실은 클래스이다. 따라서 참조형이다. 그런데 기본형처럼 문자 값을 바로 대입할 수 있다.
Why?
문자는 매우 자주 다루기 때문에 자바에서 특별하게 편의 기능을 제공한다.
대원칙: 자바는 항상 변수의 값을 복사해서 대입한다.

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

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

핵심정리
Data dataB = dataA라고 했을 때 변수에 들어있는 값을 복사해서 사용한다는 점이다. 이 값은 참조값이다. dataA와 dataB는 같은 참조값을 가지게 되고, 두 변수는 같은 객체 인스턴스를 참조하게 된다.
대원칙: 자바는 항상 변수의 값을 복사해서 대입한다.
기본형은 변수에 있는 실제 값을, 참조형은 변수에 들어 있는 참조값을 복사해서 대입
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;
}
}

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

다음 메서드 호출 코드를 보고 어떤 결과가 나올지 먼저 생각해보기
= 둘다 10으로 생각했다.
-> ChangeReference(dataA)
1. dataA의 참조값(x001)을 changeReference 메서드에 복사해서 대입한다.(둘의 참조값은 동일)
2. 참조값을 찾아가서 값을 20을 복사해서 대입한다.
3. x001의 값은 20으로 바뀐다.
4. 그래서 20 20으로 출력한다.
- 기본형: 메서드로 기본형 데이터를 전달하면, 해당 값이 복사되어 전달된다. 이 경우, 메서드 내부에서 매개변수(파라미터)의 값을 변경해도, 호출자의 변수 값에는 영향이 없다.
- 참조형: 메서드로 참조형 데이터를 전달하면, 참조값이 복사되어 전달된다. 이 경우, 메서드 내부에서 매개변수(파라미터)로 전달된 객체의 멤버 변수를 변경하면, 호출자의 객체도 변경된다
참조형 변수는 참조값(주소값)을 보관한다.

package ref;
public class Method1 {
public static void main(String[] args) {
Student student1 = new Student();
initStudent(student1,"학생1", 15,80);
Student student2 = new Student();
initStudent(student2,"학생2", 16,80);
/*student1.name = "학생1";
student1.age = 15;
student1.grade = 80;
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);*/
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 student) {
System.out.println("이름: " + student.name + " 나이:" + student.age + " 성적:" + student.grade);
}
}





멤버변수: 클래스에 선언

지역변수: 메서드에 선언, 매개변수도 지역변수의 한종류

package ref;
public class Data {
int value;
}
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);
}
}

아무도 참조하지 않는 인스턴스가 있으면 JVM의 GC(가비지 컬렉션)가 더이상 사용하지 않는 인스턴스라고 판단하고 해당 인스턴스를 자동으로 메모리에서 제거해준다.

package ref;
public class NullMain2 {
public static void main(String[] args) {
Data data = null;
data.value = 10;// NullPointerException 예외 발생
System.out.println("data = " + data.value);
}
}
null.value로 인식이 되어 찾아갈 수 없어서 NullPointerException 예외처리가 발생한다.
이렇게 null값이 대놓고 있어서 문제를 파악하기 쉽다. 하지만 멤버 변수가 null인 경우는 주의가 필요하다.
package ref;
public class Data {
int value;
}
package ref;
public class BigData {
Data data;
int count;
}
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);
}
}

정리
1. bigData의 data 멤버 변수는 참조형(Data data)이므로 null로 초기화된다. count 멤버 변수는 숫자(int count) 이므로 0으로 초기화된다.
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);
System.out.println("bigData.data=" + bigData.data);
System.out.println("bigData.data.value=" + bigData.data.value);
}
}
정리
bigData.data = new Data();을 넣어줌으로써 밑의 그림처럼 들어간다.

NullPointerException이 발생하면 null 값에 .(dot)을 찍었다고 생각하면 문제를 쉽게 찾을 수 있다.



몰라서 해설 보고 하다가 이해 안되는 부분

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("콜라", 2500, 2);
printOrders(orders);
int sum = getTotalAmount(orders);
System.out.println("총 결제 금액: " + sum);
}
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 sum = 0;
for (ProductOrder order : orders) {
sum += order.price * order.quantity;
}
return sum;
}
}
부분 코드
static void printOrders(ProductOrder[] orders){
for (ProductOrder order : orders) {
System.out.println("상품명: " + order.productName + ", 가격: " + order.price + ", 수량: " + order.quantity);
}
}
부분 코드
static int getTotalAmount(ProductOrder[] orders){
int sum = 0;
for (ProductOrder order : orders) {
sum += order.price * order.quantity;
}
return sum;
}
문제 설명
앞서 만든 상품 주문 시스템을 사용자 입력을 받도록 개선해보자.
요구 사항
아래 입력, 출력 예시를 참고해서 다음 사항을 적용하자.
주문 수량을 입력 받자
예) 입력할 주문의 개수를 입력하세요:
가격, 수량, 상품명을 입력 받자
입력시 상품 순서를 알 수 있게 "n번째 주문 정보를 입력하세요." 라는 메시지를 출력하세요.
입력이 끝나면 등록한 상품과 총 결제 금액을 출력하자.

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 n = scanner.nextInt();
ProductOrder[] orders = new ProductOrder[n];
for (int i =0; i < orders.length; i++){
System.out.println( i+1 +"번째 주문 정보를 입력하세요.");
scanner.nextLine();
System.out.print("상품명: ");
String productName = scanner.nextLine();
System.out.print("가격: ");
int price = scanner.nextInt();
System.out.print("수량: ");
int quantity = scanner.nextInt();
orders[i] = createOrder(productName, price, quantity);
}
printOrders(orders);
int sum = getTotalAmount(orders);
System.out.println("총 결제 금액: " + sum);
}
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 sum = 0;
for (ProductOrder order : orders) {
sum += order.price * order.quantity;
}
return sum;
}
}
이해 안됬던 부분
int n = scanner.nextInt();
scanner.nextLine();
ProductOrder[] orders = new ProductOrder[n];
for (int i =0; i < orders.length; 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();
nextInt()는 \n을 남기고 간다. 그러다가 nextLine()을 만나면 nextLine()은 엔터를 기준으로 넘겨버리기 때문에 바로 종료가 된다. 이런걸 방지하기 위해 int n 밑에 nextline()을 넣어서 버퍼를 비우고 for문을 들어가서 또 돌기전에 버퍼를 비우고 요런식으로 작동된다.