변수에는 크기가 정해진 기본 자료형(int
, double
, boolean
, char
등)으로 선언하는 변수가 있고, 클래스 자료형(String
, 직접 만든 클래스 등)으로 선언하는 참조 자료형 변수가 있다.
기본 자료형 변수에는 자료형에 맞는 실제 값이 저장된다. 예를 들어 int i = 10;
로 선언된 변수 i
가 있다면 int
형의 크기인 4바이트의 공간이 할당되고, 그 공간에 정수 10
이 저장되는 것이다.
하지만 참조형 변수는 이와 달리 주소값이 저장된다. 예를 들어 Person jwoo = new Person();
처럼 클래스 Person 자료형 변수가 있다면, 이 변수에는 인스턴스의 주소값이 저장된다. 그래서 변수를 출력해보면 메모리 주소가 나오는 것을 알 수 있다.
package person;
public class BirthDay {
int day;
int month;
int year;
}
package person;
public class Person {
String name;
int id;
BirthDay birthDay = new BirthDay();
}
package person;
public class PersonTest {
public static void main(String[] args) {
Person jwoo = new Person();
jwoo.name = "jwoo";
jwoo.id = 20175966;
jwoo.birthDay.day = 7;
jwoo.birthDay.month = 6;
jwoo.birthDay.year = 1997;
System.out.println(jwoo);
System.out.println(jwoo.name);
System.out.println(jwoo.id);
System.out.println(jwoo.birthDay.day + "/" + jwoo.birthDay.month + "/" + jwoo.birthDay.year);
}
}
person.Person@c2e1f26
jwoo
20175966
7/6/1997
this
는 생성된 인스턴스 스스로를 가리키는 예약어로, this
의 쓰임새는 크게 3가지로 나눌 수 있다.
this
this
를 출력해주는 함수 만들기package thistest;
import date.MyDate;
public class Birth {
String name;
MyDate birthDay;
public Birth(String name) {
this.name = name;
}
public void printThis() {
System.out.println(this);
}
public void printName() {
System.out.println("name : " + name);
System.out.println("this.name : " + this.name);
}
}
package thistest;
public class ThisTest {
public static void main(String[] args) {
Birth birthDay = new Birth("jwoo");
System.out.println(birthDay);
birthDay.printThis();
System.out.println(birthDay.name);
birthDay.printName();
}
}
thistest.Birth@41975e01
thistest.Birth@41975e01
jwoo
name : jwoo
this.name : jwoo
main()
함수에서 생성자로 새로운 인스턴스를 생성하고, 그 인스턴스를 가르키는 참조변수 birthday
를 출력한 값과 printThis()
메서드를 통해 출력된 값은 동일하다.
그리고 printName()
을 통해 인스턴수 변수 name
과 this.name
출력값 또한 동일하며, this.name
은 birthday.name
와 같은 의미임을 확인해볼 수 있다.
this
this()
넣어보기package thistest;
import date.MyDate;
public class Birth {
String name;
MyDate birthDay;
public Birth() {
this("jwoo");
}
public Birth(String name) {
this.name = name;
}
public Birth(String name, MyDate birthDay) {
this.name = name;
this.birthDay = birthDay;
}
}
package thistest;
public class ThisTest {
public static void main(String[] args) {
Birth birthDay1 = new Birth();
Birth birthDay2 = new Birth("jiwon");
System.out.println(birthDay1.name);
System.out.println(birthDay2.name);
}
}
jwoo
jiwon
생성자가 여러개인 상황에서는 this()
을 사용하여 다른 생성자를 호출할 수 있다. 위와 같은 경우 this("jwoo")
를 사용하면, Birth(String name)
를 호출한 것이다.
다만 this()
생성자를 사용할 때는 this()
가 가장 첫줄에 와야한다. this()
를 통해 생성자를 호출해야 비로소 클래스가 생성되는 것이므로, 그 이전에 클래스에서 어떤 행동을 한다는 것은 불가능하다.
this
this
를 반환하는 함수 만들기package thistest;
import date.MyDate;
public class Birth {
String name;
MyDate birthDay;
public Birth returnThis() {
return(this);
}
public void printThis() {
System.out.println(this);
}
}
package thistest;
public class ThisTest {
public static void main(String[] args) {
Birth birthDay = new Birth();
System.out.println(birthDay);
System.out.println(birthDay.returnThis());
birthDay.printThis();
}
}
thisTest.Birth@c2e1f26
thisTest.Birth@c2e1f26
thisTest.Birth@c2e1f26
main()
함수에서 생성자로 새로운 인스턴스를 생성하고, 그 인스턴스를 가르키는 참조변수 birthday
와 this
를 반환해주는 returnThis
메소드의 리턴값을 출력해서 비교해보면 동일한 값이 출력된다.
메서드 내에서 this
를 출력하는 printThis
함수 또한 같은 메모리 주소를 출력한다. this
가 생성된 인스턴스의 자신을 가리킨다는 것을 확인해볼 수 있다.
static
과 함께 선언된 변수는 클래스의 일반 멤버 변수(인스턴스 변수)와 달리 heap
영역이 아니라 static
영역(데이터)에 존재하게 된다. static
변수는 프로그램이 로드됨과 동시에 메모리를 할당받게 되므로, 위치한 클래스의 인스턴스가 생성되지 않아도 사용할 수 있다.
같은 클래스로부터 생성된 인스턴스가 공유하는 변수를 클래스 변수, static 변수 혹은 정적 변수라고 한다. 다른 멤버 변수(인스턴스 변수)처럼 클래스 내부에 선언하지만, 앞에 static
예약어를 붙인다.
클래스 전반에서 공통으로 사용하는 변수로, 객체가 아닌 클래스에 고정되어 있는 변수라는 의미다. 즉, 특정 인스턴스로부터 접근하여 변수의 값을 바꾸면 다른 인스턴스에서 접근해도 똑같이 바뀌어 있다.
인스턴스 변수는 생성자에 의해 인스턴스가 생성될 때마다 힙 메모리 공간에 새로 생성되지만, 클래스 변수는 힙 메모리 영역이 아닌 데이터 영역에, 프로그램 실행시 딱 한 번 생성된다.
그러므로 인스턴스 생성 여부와 상관없이 클래스명을 참조하여 사용할 수 있다. 인스턴스가 생성되었다면 인스턴스명으로도 참조할 수는 있지만 권장하진 않는다.
인스턴스 변수를 위한 메서드를 인스턴스 메서드라고 부른다. 인스턴스 메서드는 인스턴스가 생성될 때 만들어져, 인스턴스를 생성한 이후에 이용할 수 있다.
이처럼 클래스 변수를 활용하기 위한 메서드 또한 존재하는데, 이를 클래스 함수라 부른다. 클래스 변수처럼 함수 이름 앞에 static
예약어를 붙인다.
인스턴스 메서드와 달리 인스턴스를 생성하지 않고도 사용가능하며, 인스턴스 생성 여부와 상관없이 클래스명을 참조하여 사용한다.
클래스 함수 내에서는 인스턴스 변수를 활용하면 오류가 발생하니 주의해야한다. 클래스 함수는 인스턴스 생성 여부와 관련없이 사용할 수 있지만 인스턴스 변수는 인스턴스 생성 후에 접근할 수 있기 때문이다. (반대로 인스턴스 메서드에서 클래스 변수를 사용하는 건 문제가 되지 않는다.)
싱글톤 패턴은 프로그램에서 인스턴스를 딱 하나만 구현하는 디자인 패텬이다. 이전에 공부한 접근제어자와 static을 활용하여 싱글톤 패턴을 구현할 수 있다.
SingleTone
구현package constructor;
public class SingleTone {
private static SingleTone singletone = new SingleTone();
private SingleTone() {};
public static SingleTone getInstance() {
if (singletone == null) {
singletone = new SingleTone();
}
return singletone;
}
}
우선, 생성자를 private
으로 선언한다. private
이므로 이 생성자는 클래스 내에서만 활용할 수 있다. 즉 외부에서는 생성자를 통해 새로운 인스턴스를 만들 수 없는 것이다. 그러므로 클래스 내에서 인스턴스를 생성하고, 이 인스턴스를 클래스 변수가 가리키게 한다. 그러면 이 인스턴스는 프로그램 내에서 SingleTone 클래스의 유일한 인스턴스가 된다.
package constructor;
public class ConstructorTest {
public static void main(String[] args) {
SingleTone single1 = SingleTone.getInstance();
SingleTone single2 = SingleTone.getInstance();
System.out.println(single1);
System.out.println(single2);
System.out.println(single1 == single2);
System.out.println(single1.equals(single2));
}
}
constructor.SingleTone@1ee0005
constructor.SingleTone@1ee0005
true
true
외부 클래스에서는 SingleTone 클래스의 인스턴스를 생성할 수 없으므로, public으로 선언한 getInstance()
함수를 통해 인스턴스의 메모리 주소를 확인해본다. single1
과 single2
에 모두 같은 메모리 주소가 저장되어있음을 알 수 있다. 즉, SingleTone 클래스의 인스턴스는 유일하다.