사용하는 값을 변수에 직접 넣을 수 있는 기
본형, 그리고 이전에 본 Student student1 과 같이 객체가 저장된 메모리의 위치를 가리키는 참조값을 넣을 수 있는 참조형으로 분류할 수 있다.
기본형은 연산이 가능하지만 참조형은 연산이 불가능하다.
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; //연산 가능
기본형을 제외한 나머지는 모두 참조형이다.
- 기본형은 모두 소문자로 시작한다. int, long, double,boolean...
- 클래스는 대문자로 시작한다. 클래스는 모두 참조형이다.
대원칙: 자바는 항상 변수의 값을 복사해서 대입한다
int a = 10;
int b = a;
Student s1 = new Student();
Student s2 = s1;
=>참조형의 경우 실제 사용하는 객체가 아니라 객체의 위치를 가리키는 참조값만 복사된다. 쉽게 이야기해서 실제 건물이 복사가 되는 것이 아니라 건물의 위치인 주소만 복사되는 것이다. 따라서 같은 건물을 찾아갈 수 있는 방법이 하나 늘어날 뿐이다.
<VarChange1.java>
public class VarChange1 {
public static void main(String[] args) {
int a = 10;
int b = a;
System.out.println("a = " + a); //10
System.out.println("b = " + b); //10
//a 변경
a = 20;
System.out.println("변경 a = 20");
System.out.println("a = " + a); //20
System.out.println("b = " + b); //10
//b 변경
b = 30;
System.out.println("변경 b = 30");
System.out.println("a = " + a); //20
System.out.println("b = " + b); //30
}
}
<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); //10
System.out.println("dataB.value = " + dataB.value); //10
//dataA 변경
dataA.value = 20;
System.out.println("변경 dataA.value = 20");
System.out.println("dataA.value = " + dataA.value); //20
System.out.println("dataB.value = " + dataB.value); //20
//dataB 변경
dataB.value = 30;
System.out.println("변경 dataB.value = 30");
System.out.println("dataA.value = " + dataA.value); //30
System.out.println("dataB.value = " + dataB.value); //30
}
}
참조값이 같다.
dataA와 dataB는 같은 참조값을 가지게 되고, 두 변수는 같은 객체 인스턴스를 참조하게된다.
<MethodChange1.java>
package ref;
public class MethodChange1 {
public static void main(String[] args) {
int a = 10;
System.out.println("메서드 호출 전: a = " + a); //10
changePrimitive(a);
System.out.println("메서드 호출 후: a = " + a); //10
}
static void changePrimitive(int x) {
x = 20;
}
}
1.
a , x 각각 숫자 10 을 가지고 있다.
2.
x 의 값만 20 으로 변경되고, a 의 값은 10 으로 유지
<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); // 10
changeReference(dataA);
System.out.println("메서드 호출 후: dataA.value = " + dataA.value);// 20
}
static void changeReference(Data dataX) {
dataX.value = 20;
}
}
dataX 에 변수 dataA 의 값을 전달
Data dataX = dataA
변수 dataA 는 참조값 x001 을 가지고 있으므로 참조값을 복사해서 전달했다. 따라서 변수 dataA , dataX 둘다 같은 참조값인 x001 을 가지게 된다.
이제 dataX 를 통해서도 x001 에 있는 Data 인스턴스에 접근할 수 있다.
<Method1.java>
package ref;
public class Method1 {
public static void main(String[] args) {
Student student1 = new Student();
initStudent(student1,"학생1", 16, 85);
Student student2 = new Student();
initStudent(student2, "학생2",19, 90);
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);
}
}
주의
```java
package ref;
import class1.Student;
public class Method1 {
...
}
학생정보를 입력하는 부분이 중복으로 나오는데 이부분도 하나로 합쳐보자.
<Method2.java>
package ref;
public class Method2 {
public static void main(String[] args) {
Student student1 = createStudent("학생1", 16, 85);
Student student2 = createStudent( "학생2",19, 90);
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);
}
}
createStudent() 는 생성한 Student 인스턴스의 참조값을 반환한다. 이렇게 반환된 참조값을 student1 변수에 저장했다. 앞으로는 student1 을 통해 Student 인스턴스를 사용할 수 있다.
<NullMain1.java>
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;
// data 변수는 앞서 만든 Data 인스턴스를 더는 참조하지 않는다.
System.out.println("3. data = "+data);
}
}
data 에 null 을 할당하면 생성한 x001 Data 인스턴스를 더는 아무도 참조하지 않는다. 이렇게 아무도 참조하지 않게 되면 x001 이라는 참조값을 다시 구할 방법이 없다. 따라서 해당 인스턴스에 다시 접근할 방법이 없다.
-> 아무도 참조하지 않는 인스턴스는 메모리 낭비
-> 자바는 낭비되는 메모리를 자동으로 삭제해준다.
null 을 가리키다(Pointer)인데, 이때 발생하는 예외(Exception)다.
null 은 없다는 뜻이므로 결국 주소가 없는 곳을 찾아갈 때 발생하는 예외이다.
객체를 참조할 때는 . (dot)을 사용한다. 이렇게 하면 참조값을 사용해서 해당 객체를 찾아갈 수 있다. 그런데 참조값이 null 이라면 값이 없다는 뜻이므로, 찾아갈 수 있는 객체(인스턴스)가 없다. NullPointerException 은 이처럼 null 에 . (dot)을 찍었을 때 발생한다.
<NullMain2.java>
package ref;
public class NullMain2 {
public static void main(String[] args) {
Data data = null;
data.value = 10; //=null.value = 10
System.out.println("data = "+data.value);
}
}
=> java.lang.NullPointerException 이 발생
<NullMain3.java>
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);
//null 값에 .(dot)을 찍으면 예외가 발생한다.
}
}
=> null 에러
<NullMain4.java>
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);
}
}
<ProductOrderMain2.java>
package ref.ex;
public class ProductOrderMain2 {
public static void main(String[] args) {
int total;
ProductOrder[] productOrders = {
createOrder("사과",2000,3),
createOrder("바나나",1000,7)
};
printOrder(productOrders);
total= getTotal(productOrders);
System.out.println("총 금액 : "+total);
}
static ProductOrder createOrder(String name, int price, int quantity){
ProductOrder productOrder = new ProductOrder();
productOrder.name = name;
productOrder.price = price;
productOrder.quantity = quantity;
return productOrder;
}
static void printOrder(ProductOrder[] orders){
for (ProductOrder order :orders){
System.out.println("이름 : "+order.name +" 가격 : "+ order.price+ " 수량 : "+order.quantity);
}
}
public static int getTotal(ProductOrder[] orders){
int total =0;
for(ProductOrder order: orders){
total += order.price*order.quantity;
}
return total;
}
}
<ProductOrderMain3.java>
package ref.ex;
import java.util.Scanner;
public class ProductOrderMain3 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("입력할 주문의 개수를 입력하세요:");
int count = input.nextInt();
input.nextLine();
ProductOrder[] productOrders = new ProductOrder[count];
for(int i =0; i<count;i++){
System.out.println((i+1)+"번째 주문 정보를 입력하세요.");
System.out.print("상품명: " );
String name = input.nextLine();
System.out.print("가격: ");
int price = input.nextInt();
System.out.print("수량: ");
int quantity = input.nextInt();
input.nextLine(); // 입력 버퍼를 비우기 위한 코드
productOrders[i]=createOrder(name, price, quantity);
}
printOrder(productOrders);
int total= getTotal(productOrders);
System.out.println("총 금액 : "+total);
}
static ProductOrder createOrder(String name, int price, int quantity){
ProductOrder productOrder = new ProductOrder();
productOrder.name = name;
productOrder.price = price;
productOrder.quantity = quantity;
return productOrder;
}
static void printOrder(ProductOrder[] orders){
for (ProductOrder order :orders){
System.out.println("이름 : "+order.name +" 가격 : "+ order.price+ " 수량 : "+order.quantity);
}
}
public static int getTotal(ProductOrder[] orders){
int total =0;
for(ProductOrder order: orders){
total += order.price*order.quantity;
}
return total;
}
}