안녕하세요. 이번 포스팅은 한번도 포스팅해보지 않은 주제를 갖고와봤습니다.^^
요즘 스스로 기초 지식이 약하다는 판단이 들어, 기초를 다지기 위해 Java로 객체 지향 공부를 하게 되었습니다!!
⭐️ 자바에서 참조형을 제대로 이해하는 것은 정말 중요️ ⭐
자바의 타입은 크게 기본형과 참조형으로 나눌 수 있습니다.
😎: 기본형은 모두 소문자로 시작한다고 이해하면 편할듯!!
ex)int
,long
,double
,boolean
String
: 자바에서 String은 클래스이므로, 참조형으로 정의됨😎: 참조형은 모두 대문자로 시작한다고 이해하면 편할듯!!
ex)String
,객체
,배열
위에서 소개한 기본형과 참조형은 프로그래밍의 계산에서 어떻게 쓰이는지 알아봅시다.
기본형은 들어있는 값을 그대로 계산에 사용이 가능합니다.
int a = 10, b = 20;
int sum = a + b;
참조형은 들어있는 참조값
을 그대로 사용 불가합니다.
class Student {
int grade;
}
Student s1 = new Student(); // s1 = ref.Student@a09ee92 (s1에는 주소값이 존재)
Student s2 = new Student(); // s2 = ref.Student@30f39991 (s2에는 주소값이 존재)
System.out.println(s1 + s2); // error
Student는 타입이 기본형일까요 참조형일까요?
대문자로 시작하고, 자바가 기본으로 제공하는 데이터 타입이 아니기 때문에참조형
입니다!!
s1과 s2는 참조형 인스턴스인 Student라는 객체를 할당 받았습니다.
s1, s2에는 Student 객체의 참조(주소)값
이 존재합니다.
참조(주소)값은 자바 내 메모리에서 차지하는 주소지
를 뜻합니다.
주소지는 주소지일 뿐, 계산 할 수 있는 값이 아니기 때문에 멋대로 s1 + s2를 하게 되면 에러가 납니다...!
그렇다면 참조형
인스턴스는 어떻게 계산할까요?
주소지를 자세히 들여다봐야🧐 계산할 값이 나옵니다. 코드로 알아봅시다.
Student s1 = new Student();
s1.grade = 100; // 값 지정
Student s2 = new Student();
s2.grade = 90; // 값 지정
int sum = s1.grade + s2.grade; // 값을 꺼내 계산함 (190)
참조형 변수에 .(dot)
을 찍어보면 값 grade가 나옵니다. s1, s2의 grade에 각각 정수 값을 지정해줍니다.
그럼 비로소 기본형에서 계산했던대로 값 계산이 가능합니다.
💡 자바는 항상 변수의 값을 복사해서 대입한다.!!
자바에서 변수에 값을 대입하는 것은 변수에 들어있는 값을 복사해서 대입하는 것입니다.
복사해서 대입한다는 말이 무슨 말일까요? 🤔 기본형의 대입 방법부터 알아봅시다.
기본형은 변수에 들어있는 실제 사용하는 값
을 복사하여 대입합니다.
바로 코드로 보겠습니다!!
int a = 10;
int b = a; // b에 a의 값인 10을 복사해서 대입.
위의 코드를 아래와 같이 그릴 수 있을 것입니다.
a, b 값 할당
a, b가 메모리 내에서 차지하는 영역
😎: 서로 같은 값을 가지지만 다른 영역에 저장됩니다.
결과
기본형 변수에 값을 대입시, 서로 같은 값을 가지지만 각자 주소지가 다릅니다.
참조형은 객체의 위치를 가리키는참조(주소)값
을 복사하여 대입합니다.
바로 코드로 보겠습니다!!
Student s1 = new Student(); // s1 = ref.Student@a09ee92 (s1 주소값 예시)
Student s2 = s1; // s2에 s1의 주소값인 a09ee92 복사하여 대입
위의 코드를 아래와 같이 그릴 수 있을 것입니다.
s1, s2 값 할당
s1, s2가 메모리 내에서 차지하는 영역
😎: 서로 같은 영역에 저장되므로, 당연히 실제 사용하는 값도 같습니다.
결과
참조형 변수에 값을 대입 시, 서로 같은 값을 가지며 같은 주소지를 바라봅니다.
참조형 변수는 말 그대로 주소를 참조
하는 변수라는 것을 알아두세요! 😝
💡 자바는 항상 변수의 값을 복사해서 대입한다.!!
메서드를 호출할 때 사용하는 매개변수(파라미터)도 변수입니다. 따라서 메서드를 호출할 때 매개변수에 값을 전달하는 것도 값을 복사하여 전달하는 방식입니다.
메서드로 기본형 데이터를 전달하면 실제 값
이 복사되어 전달됩니다.
이 경우, 메서드 내부에서 매개변수(파라미터)의 값을 변경해도 호출자의 변수 값에는 영향이 없습니다.
바로 코드로 보겠습니다!! 아래 코드를 수행하면 어떻게 될까요?
public class changeNumberMain {
public static void main(String[] args) {
int a = 10;
System.out.println("메서드 호출 전: a = " + a);
changeNumber(a);
System.out.println("메서드 호출 후: a = " + a);
}
static void changeNumber(int x) {
x = 20;
}
}
한 줄 한 줄 그림으로 알아봅시다!!
int a = 10
changePrimitive(a)
changePrimitive(int x) { x = 20 }
changePrimitie의 파라미터인 x에 a를 대입하면, x = a가 될 것입니다.
x가 a의 실제 값
을 그대로 복사
합니다. x가 20이 될 뿐, a가 20이 되지 않습니다.
그래서 결과는?
메서드 호출 전: a = 10
메서드 호출 후: a = 10
왜⸌◦̈⃝⸍ʷʰʸˀ̣ˀ̣?
x는 기본형 타입이므로 a와는 값만 같을 뿐 참조값이 다르기 때문이죠!!
메서드로 참조형 데이터를 전달하면 참조값(주소값)
이 복사되어 전달됩니다.
이 경우, 메서드 내부에서 매개변수(파라미터)로 전달된 객체의 값을 변경하면 호출자의 객체도 변경됩니다.
바로 코드로 보겠습니다!! 아래 코드를 수행하면 어떻게 될까요?
Student s1 = new Student();
s1.grade = 100;
System.out.println("메서드 호출 전: s1.grade = " + s1.grade);
changeGrade(s1);
System.out.println("메서드 호출 후: s1.grade = " + s1.grade);
void changeGrade(Student studentS) {
studentS.grade = 90;
}
한 줄 한 줄 코드로 알아봅시다!!
Student s1 = new Student()
s1.grade = 100
changeGrade(s1)
changeGrade(Student studentS) { studentS.grade = 90 }
그래서 결과는?
a09ee92(주소값 예시)의 참조(주소)값에는 grade 90을 가지고 있으며, s1과 studentS가 둘다 동일한 참조(주소)값을 바라보고 있습니다!!!
자바에서 변수는 크게 멤버 변수와 지역 변수로 나눌 수 있습니다.
각각 무엇인지 알아봅시다!!
클래스
에 선언된 변수
바로 코드로 보겠습니다!!
public class Student {
String name; // 멤버 번수(필드)
int age; // 멤버 변수(필드)
int grade; // 멤버 번수(필드)
}
지역 변수는 메서드
에 선언된 변수를 말하며, 메서드가 끝나면 제거되는 변수
매개변수도 지역 변수의 한 종류
바로 코드로 보겠습니다!!
public class StudentMain {
public static void main(String[] args) {
int a = 1; // 지역 변수
Student student1; // 지역 변수
student1 = new Student();
Student student2 = new Student(); // 지역변수
}
}
멤버 변수(필드)와 지역 변수의 초기화가 어떻게 되는지 알아봅시다!
인스턴스를 생성할 때 자동으로 초기화
ex) int = 0
, boolean = false
, 참조형 = null
개발자가 초기값 직접 지정 가능합니다!
바로 코드로 보겠습니다!!
public class Student {
String name; // 초기화 안했으므로 null로 초기화 됨
int age; // 초기화 안했으므로 0으로 초기화 됨
int grade = 90; // 90으로 직접 초기화
}
개발자가 항상 직접 초기화해야합니다!
바로 코드로 보겠습니다!!
public class StudentMain {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name); // null
System.out.println(student.age); // 0
System.out.println(student.grade); // 90
}
}
위에서 멤버 번수(필드)의 참조형 변수는 null
로 초기화 한다고 하였습니다.
null
은 아직 가리키는 대상이 없다, 존재하지 않는, 혹은 없다는 뜻 입니다.
바로 코드로 보겠습니다!!
public class Data {
int value;
}
public class DataMain {
public static void main(String[] args) {
Data data = null; // 1.
data = new Data(); // 2.
data = null; // 3.
}
}
위와 같은 코드를 한 줄 한 줄 자세히 살펴보며 메모리에 어떻게 적재되는지 살펴봅시다! 🧐
Data data = null
Data의 타입을 갖는 data의 객체를 null로 초기화합니다.
data = new Data()
data의 타입을 가진 참조형 변수에 Data 인스턴스 지정합니다. data는 참조형 변수이므로, data에는 데이터에 접근하기 위한 Data 인스턴스의 참조(주소)값을 저장합니다. Data의 값으론 value가 있습니다. (value는 자동 초기화로 인해 0으로 초기화 됨)
data = null
(⭐️ data는 더이상 Data 인스턴스를 참조하지 않습니다. ⭐️)
위 3번에서 data는 더이상 Data 인스턴스를 참조하지 않는다고 하였습니다.
이 부분을 자세히 들여다보겠습니다. data에 null을 할당하면서 data는 Data의 인스턴스의 참조를 해제하였습니다. 이렇게 아무것도 참조하지 않게 되면 Data의 인스턴스는 갈 길을 잃게 됩니다...🥲 (data: 나 누구 참조해?🥲)
자바에서 객체는 해당 객체를 참조하는 곳이 있으면 JVM이 종료할 때까지 계속 생존합니다. 그런데 중간에 해당 객체를 참조하는 곳이 모두 사라지면 그때 JVM은 ⭐️필요 없는 객체로 판단하고 GC(Grabage Collection)를 사용해서 제거하게 됩니다.⭐️
그래서 ref.Data@a09ee92(참조값 예시) 참조값을 가지는 Data 인스턴스는 JVM의 GC에 의해 제거되었습다. 😋
참조형 변수에 . (dot)을 사용하면 해당 객체를 찾아갈 수 있습니다. 만약 참조형 변수가 null
을 가리킨다면 값이 없다는 뜻이므로, 찾아갈 수 있는 객체(인스턴스)가 없습니다.🥲 NullPointerException
은 null 에 . (dot)을 찍었을 때 발생합니다.
바로 코드로 보겠습니다!!
public class DataMain {
public static void main(String[] args) {
Data data = null;
System.out.println("data.value = " + data.value); // NullPointerException
}
}
이 경우 NullPointerException
이라는 예외가 발생하고, 프로그램이 종료됩니다. NullPointerException
은 이름 그대로 null 을 가리켜서(Pointer) 예외가 발생했다는 의미입니다. null
은 없다는 뜻이므로, 결국 주소가 없는 곳을 찾아갈 때 발생하는 예외입니다.
결과
Exception in thread "main" java.lang.NullPointerException: Cannot read field "value1" because "data" is null
at ref.initMain.main(initMain.java:8) // NullPointerException 발생
Process finished with exit code 1 // 프로그램 종료
다음 시간엔 객체 지향 프로그래밍에 대해 알아보겠습니다!! :)