사용자 정보[이름, 나이] 데이터를 설계한 Person 클래스를 설계해보겠습니다.
public class Person {
private String name;
private int age;
public void show() {
System.out.println("name : " + name);
System.out.println("age : " + age);
System.out.println();
}
}
Person 클래스의 멤버변수 name 과 age 를 만들었고, 그 데이터를 출력하는 메소드 show() 를 만들었습니다. 보통 클래스의 멤버변수들의 접근제한자는 private
이고, 메소드는 public
이 권장됩니다. 멤버변수의 데이터를 건드릴수 있게되다 보면 오류가 날 가능성이 생기기 때문입니다. 그런데 이러다 보니 Main 클래스에서 객체의 정보를 설정해줄 수가 없게 되었습니다. 그래서 우리는 사람의 데이터를 입력하기 위해 Person 클래스에게 이야기합니다. 데이터를 입력해주는 기능을 해줘!
-------- Person.java
public class Person {
private String name;
private int age;
public void setMembers(String name, int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println("name : " + name);
System.out.println("age : " + age);
System.out.println();
}
}
--------- Main.java
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.setMembers("sam",20);
p.show();
}
}
데이터를 입력해주는 기능을 하는 setMembers()를 만들었습니다. 그리고 Person 클래스의 객체를 생성해준 뒤, 데이터를 전달해주어 객체의 정보를 저장합니다. 그러면 추가로 객체를 만들어볼까요?
Person p2 = new Person();
p2.setMembers("robin",25);
p2.show();
기존에 만들었던 메소드를 이용해 성공적으로 객체를 생성했습니다. 그런데, 이렇게 하다보니 항상 2줄의 코드를 작성해야하고, 데이터의 값을 넣어주는 기능을 하는 메소드도 만들어주어야 하니 좀 짜증이 납니다. 그래서 생성자(Constructor) 라는 개념이 나오게 됩니다.
- 메소드의 이름은 클래스의 이름과 같다.
- 리턴타입을 명시하지 않음.
규칙은 간단합니다. 그럼 이 규칙들을 적용해서 Person 클래스에 생성자를 만들어봅시다.
--------- Main.java
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.show()
}
}
--------- Person.java
public class Person {
private String name;
private int age;
public void show() {
System.out.println("name : " + name);
System.out.println("age : " + age);
System.out.println();
}
public Person() {
System.out.println("Person 객체 생성 ! ");
name = "익명";
age = 0;
}
}
생성자 메소드의 이름과 클래스의 이름이 동일하며, 리턴타입은 명시하지 않았습니다. 그리고 출력문 하나와 멤버변수의 초기값을 지정해주었습니다. 또한, 접근제한자를 지정할 수 있습니다. 여기서는 public 으로 지정해주었습니다. 자, 이렇게 생성자를 생성할 수 있습니다.
생성자도 메소드 라고 이야기했습니다. 그러면 혹시 매개변수를 이용할 수 있지 않을까요? 맞습니다. 생성자에 매개변수를 전달하는것도 가능합니다. 아 그러면 메소드에서 가능했던 오버로딩도 가능하지 않을까요? 그것도 가능합니다. 그것들에 대한 예제를 알아봅시다.
--------- Person.java
public class Person {
private String name;
private int age;
public void show() {
System.out.println("name : " + name);
System.out.println("age : " + age);
System.out.println();
}
public Person() {
name = "익명";
age = 0;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
생성자의 오버로딩과 생성자의 목적 중 하나는 멤버변수의 초기화에 있습니다. 그래서 this
키워드를 통해 매개변수로 전달받은 값을 멤버변수에 넣어주어 초기화를 완료했습니다. 또한 생성자 오버로딩을 통해 이름은 같고, 매개변수를 달리해서 사용자가 편리하게 이용할 수 있도록 만들었습니다. 추가적으로 생성자 오버로딩은 갯수의 제한이 없기 때문에 몇개라도 추가로 만들수 있다는 점도 알아둡시다.
다만, 주의할 점이 한가지 있습니다. 아까 생성자에는 접근제한자를 사용할 수 있다고 하였습니다. 그러다 보면 접근제한자의 잘못된 지정 때문에 객체생성이 불가한 상황이 생길 수 있습니다. 예를 들어, aaa 라는 패키지를 만들고 Test
라는 클래스를 만들었다고 합시다. 그리고 Test 의 생성자의 접근제한자를 default 로 설정하고 Main 클래스에서 Test 의 객체를 생성한다고 해봅시다. 다음 코드는 그 예제입니다.
---------- Test.java
package aaa;
public class Test {
Test(){
System.out.println("Test 객체가 생성!");
}
}
---------- Main.java
public class Main {
public static void main(String[] args) {
aaa.Test t = new aaa.Test(); // error!!
}
}
바로 에러가 납니다. 왜그럴까요? 바로 Test 클래스의 접근제한자 때문입니다. 객체를 생성할 때 생성자가 자동으로 호출이 됩니다. 그런데, 호출을 하려고 보니 접근제한자가 default 인 상황입니다. 그래서 자바에서는 오류를 띄웁니다.
this() 생성자는 멤버변수의 갯수가 많을 때, 값 대입을 매번하기 번거로울때 사용하는 문법입니다.
다음과 같은 코드가 있다고 생각해봅시다.
public class Second {
private int a,b,c,d,e;
public Second(int a,int b,int c,int d,int e) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
public Second() {
this(0,0,0,0,0); // Second 클래스의 생성자 호출!
}
}
생성자의 존재 이유 중 하나는 멤버변수의 초기화가 목적입니다. 그래서 명시적인 값 전달이 없을 경우에는 각 데이터의 default 값을 대입하는 코드를 작성해주어야 합니다. 그런데 위 코드처럼 혹은 그보다 더 많은 수의 멤버변수들이 있다면 값을 대입하는 과정이 아주아주 귀찮을것 같습니다. 그래서 this() 를 사용하여 Second 의 생성자를 호출해 번거로움을 해결하였습니다.
초기화가 단계별로 실행되며 실행구문의 순서와는 관계가 없습니다.
1. 기본값 초기화
2. 명시적 초기화
3. 초기화 블록
4. 생성자
public class InitialTest {
int a; // 1. 기본값 초기화
int b = 10; //2. 명시적 초기화
// 초기화 블럭 : 프로그래밍적으로 초기화를 할 수 있다는 장점이 있음.
{
int c = 30;
c++;
if(c<25) c--;
System.out.println("초기화블럭!");
}
// 생성자 메소드를 이용한 초기화 : 파라미터를 전달받아 초기화를 할 수 있다는 장점.
public InitialTest (){
System.out.println("생성자 메소드!!");
}
}
아래 그림과 코드를 잘 이해하는것을 시작으로 Static 을 이해해 봅시다.
----------- Test.java
public class Test {
public int a;
public static int b;
}
----------- Main.java
public class Main {
public static void main(String[] args) {
Test t1 = new Test();
Test t2 = new Test();
Test t3 = new Test();
}
}
위 코드를 도식화한게 위 그림입니다. Test
클래스에 멤버변수로 int a
와 static int b
를 설정했습니다. 그리고 Main
클래스에서 Test 클래스의 객체를 생성했습니다. 이때, 그림처럼 static 변수는 각 객체에 존재하지 않습니다. 그리고 변수 a 는 각 객체마다 존재합니다. 그래서 a 변수를 인스턴스 변수
라고 부르기도 하며, static 변수는 클래스에 1개가 존재하므로 클래스 변수
라고 부르기도 합니다. static 은 객체에 존재하지 않고 Test 클래스에 존재할 뿐입니다. 이 사실을 잘 기억해야 합니다.
단어 그대로 클래스 안에 클래스가 설계되면, 그 설계된 클래스를 inner class 라고 합니다. 그리고 이너클래스를 가진 클래스를 outer class 라고 합니다.
이너클래스의 특징에 대해 알아봅시다.
------------- Main.java
public class Main {
public static void main(String[] args) {
Nice nice; // error!!!
}
}
------------- Test.java
public class Test {
class Nice {
}
}
------------- Main.java
public class Main {
public static void main(String[] args) {
Test.Nice nice = new Test.Nice() // ERROR!!!
}
}
------------- Test.java
public class Test {
class Nice {
}
}
------------- Main.java
public class Main {
public static void main(String[] args) {
Test t = new Test();
Test.Nice nice = Test.makeObj();
}
}
------------- Test.java
public class Test {
Nice makeObj(){
return new Nice();
}
class Nice {
}
}
------------- Test.java
public class Test {
int a = 10;
String str = "hello";
void show() {
System.out.println("Hello");
}
class Nice {
a = 50;
str = "HI";
show();
}
}
inner class 가 존재하기 위해서는 outer class 가 존재해야 합니다. 그렇기 때문에 사용이 가능합니다.
------------- Test.java
public class Test {
k = 10; //ERROR!!!
class Nice {
int k = 10;
}
}
outer class 가 존재한다고 해서 inner class 가 반드시 존재하는것은 아니기 때문에 에러를 일으킵니다.
inner class 는 객체를 안전하게 만들기 위해 사용하는 기법입니다. 즉, 외부에서 아우터 객체 없이 마음대로 생성하지 못하도록 문법적으로 막아두는 기법입니다.
메소드 영역 안에 클래스를 설계하면 이 클래스를 Local class 라고 합니다. 로컬클래스는 설계된 지역 안에서만 인식이 가능한 클래스로, 지역클래스, 내장클래스, 내부클래스 라고도 부릅니다.
클래스가 설계된 메소드가 실행중 일때만, 잠시 1회용 처럼 사용하는 객체를 만들고 싶을때 로컬 클래스를 설계합니다. 나중에 배우게 될 익명 클래스 라는것을 사용할 때 많이 사용됩니다.