Hayden's Archive
[자바/ Java] 명시적 생성자 / setter & getter / 캡슐화 / 접근 제어자 / 참조변수와 toString() 본문
[자바/ Java] 명시적 생성자 / setter & getter / 캡슐화 / 접근 제어자 / 참조변수와 toString()
_hayden 2020. 4. 13. 23:47생성자 전반, 기본 생성자 관련 포스팅 : https://hayden-archive.tistory.com/60?category=775409
명시적 생성자(Explicit Constructor)
현실세계에 있는 라운드티, 점퍼, 반팔티를 인스턴스로 만들고 싶다. |
package oop.constructor;
public class Shirt {
public String maker; // 필드로 생성하는 순간 null이라는 기본값을 가짐.(묵시적 초기화)
public boolean longSleeved;
public char color;
public Shirt(){} // Default Constructor(기본 생성자)
public Shirt(String m, boolean longs, char c){
// Explicit Constructor(명시적 생성자)
maker = m; // 명시적 생성자가 하는 일 = 필드 초기화(Field Initialization)
longSleeved = longs;
color = c;
}
public String getDetails() { // Worker Method = 구현부가 있는 메소드.
return maker+", "+longSleeved+", "+color;
}
}
Shirt 타입으로 만든다! Shirt 라운드티 = new Shirt();Shirt 점퍼 = new Shirt(); Shirt 반팔티 = new Shirt(); Clothing 타입으로는 만들 수 있음. 하지만 Animal 타입으로는 만들 수 없음. Clothing 타입이 shirt 타입보다 더 넓은 범주. 현업에 나가면 더 상세한 Shirt 타입으로 만들게 된다. Shirt 타입, Clothing 타입 - 이러한 것들이 추상화(Abstraction)에 해당됨. 상속(Inheritance)과 관련됨. Shirt(); 는 메소드가 아니라 생성자(Constructor)! 메소드는 소문자로 시작한다. 생성자는 메소드는 아니지만 하는 일은 메소드와 같다. |
package oop.constructor.test;
/*
* 객체가 생성될 때 생성자 안에 인자값을 넣어주지 않으면
* 명시적인 값을 가지지 않는 디폴트 객체로 생성된다.
* ::
* 생성자를 통해서 값을 입력 +
* 객체가 생성됨과 동시에 값을 가지도록 코드를 작성하겠음.
*/
import oop.constructor.Shirt;
public class ShirtTest {
public static void main(String[] args) {
Shirt sh = new Shirt(); // 기본 생성자로 sh라는 객체 생성
System.out.println(sh.maker); // null 값이 출력됨.
// 명시적 값으로 할당하겠음.
Shirt roundT = new Shirt("유니클로", true, 'B'); // Calling. 생성자 호출
Shirt jumpa = new Shirt("베네똥", true, 'B');
Shirt banpalT = new Shirt("aaa", false, 'W');
// 서로 다른 셔츠객체가 만들어졌다면 Heap 영역에 객체 3개가 로드되어 있을 것이고
// 각각의 위치는 서로 다르기 때문에 참조변수의 주소값들은 서로 다르게 나와야 한다
System.out.println(roundT);
System.out.println(jumpa);
System.out.println(banpalT);
// 주소값들이 출력되고 객체가 만들어졌다는 게 확인됨
System.out.println("++++++++++++++++++++++++++++++++++");
System.out.println(roundT.getDetails());
System.out.println(jumpa.getDetails());
System.out.println(banpalT.getDetails());
// 여기서 roundT, jumpa, banpalT는 각각 다른 주소값을 가진
// 참조변수(Reference Variable)
}
}
Shirt roundT = new Shirt("유니클로", true, 'B'); roundT.getDetails(); ===> 여기에서 roundT를 참조변수(Reference Variable)라고 한다. 참조변수는 생성된 객체의 주소값을 가지고 있다. |
- 참조변수는 필드 사용은 값 할당으로 하고, 메소드 사용은 메소드 호출(Method Calling)으로 한다.
roundT.maker="나이키"; //값 할당
System.out.println(roundT.getDetails()); //메소드 호출. METHOD Calling
>>> 정리 1 <<<
- 객체 생성할 때 타입이 있어야 하고, new라는 키워드가 있어야 하고, 생성자가 호출되어야 한다.
- 객체를 생성하면 객체의 멤버(필드, 메소드)가 가상 메모리 JVM 안에 있는 Heap이라는 공간에 올라간다. Heap에는 반드시 new라는 키워드를 통해서 생성된 객체만 올라간다.
- 객체 생성의 결론 -> Stack에 주소값 할당! 저장된 객체의 위치값이 지정된다.
>>> 정리 2 <<<
- 생성자가 하는 일은 필드 초기화(Field Initialization)이다.(X) - null값을 가지는 기본 생성자도 있음.
- ★명시적 생성자가 하는 일은 필드 초기화(Field Initialization)이다.(O)
- 생성자는 메소드 중에서 set메소드와 기능이 같다.(아래 setter 참고) 애초부터 값을 넣어서 만드는 것.
setter()와 getter()
public class Programmer {
public String name;
public int age;
public float salary;
public int bonus;
public void setProgrammer(String n, int a, float s, int b) {
name = n;
age = a;
salary = s;
bonus = b;
}
// setPorgrammer()가 setter에 해당. 값을 주입하는 기능
public String getProgrammer() {
return name + "\t" + age + "\t" + salary + "\t" + bonus;
}
// getProgrammer()가 getter에 해당. 값을 가져오는 기능
}
public class ProgrammerTest {
public static void main(String[] args) {
Programmer pro = new Programmer();
System.out.println("정보를 출력합니다");
pro.setProgrammer("Amily", 30, 200.0f, 10);
System.out.println(pro.getProgrammer());
}
}
- 무조건 setter와 getter의 이름을 set, get으로 지을 필요는 없음. 내가 작성하려는 주제와 관련하여 set, get보다 더 어울리는 이름이 있다면 그 이름으로 정해주는 게 좋다.
- 예를 들어 주입할 Setter를 setProducts -> buyProducts로 짓는다면 더 현실세계와 가깝게 직관적으로 지을 수 있다.
- 변수명은 충분히 유추할 수 있는 이름으로. 자세하면서도 심플하게. 둘 중 하나를 선택해야 한다면 자세하게! PM이 하는 가장 중요한 일이 식별자(Identifier를 지정하는 것!
- 변수명 앞에 붙이는 this의 쓰임새 ( 생성자 앞에 붙이는 this 관련 포스팅 : https://hayden-archive.tistory.com/66 )
public int month;
public void setMonth(int month){
month = month;
// 필드 이름과 로컬 변수의 이름이 같으면 컴퓨터가 혼동함.
}
public int month;
public void setMonth(int m){
month = m;
// 하지만 그렇다고 이렇게 m으로 쓰면 알아보기 어려움.
}
public int month;
public void setMonth(int month){
this.month = month;
// 그러므로 필드와 로컬변수의 이름이 같을 때 구분하기 위해 this를 붙임.
}
- 로컬변수와 필드의 이름이 같을 때 구분하기 위해 필드 앞에 this를 지정한다.
- this는 해당 클래스 자기 자신을 가리킴. 해당 클래스 주소값을 참조하는 역할. 이 클래스 객체 자체의 레퍼런스값을 가져옴. ===> 따라서 위 코드의 this.month는 이 클래스 안에 있는 필드 month를 가리킴.
심화 참고 : https://blog.naver.com/jktk1/221551306370
캡슐화 Encapsulation
public class MyDate {
public int month;
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
}
public class MyDateTest {
public static void main(String[] args) {
MyDate md = new MyDate();
md.month = 13;
// MyDate 클래스의 필드 month에 직접적으로 값 13을 할당.
// 이러면 잘못된 값이 쉽게 들어갈 수 있음.
}
}
- 안에 있는 데이터를 보호하기 위해 캡슐을 씌움. 데이터는 하나의 클래스 안에 있음. 이 클래스에 접근하지 못하도록 보호막을 씌움. 바깥에서 필드에 직접적으로 접근하지 못하도록(그러면 에러날 수 있으니까) 하는 게 Encapsulation!
- 그러려면 필드 앞에 private라는 접근 지정자를 붙이면 됨.(pulic을 private로 바꾸면 됨.)
public class MyDate {
private int month;
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
}
public class MyDateTest1 {
public static void main(String[] args) {
MyDate md = new MyDate();
md.setMonth(13);
// setter를 통해 값 13을 주입.
md.month = 13; // 이제 이 코드는 에러가 뜨게 된다.
// 그래서 MyDate 클래스에 있는 필드 month 앞에 private 붙여주면
// 필드가 not visible하다고 하며 접근 막힘
}
}
- 중요한 건 필드 앞에만 private을 붙임! 메소드 앞에 붙여버리면 기능을 구현하지 못한다.
- 앞으로 class 안에 들어가는 field 앞에는 무조건 private 붙일 것.
- field에 값을 주입하는 통로는 2개 뿐이다. 생성자와 setter
- 값을 주입하는 setter와 생성자 차이를 굳이 따져본다면...
접근 제어자 / 접근 제한자 (Access Modifier)
참고 : https://hunit.tistory.com/162
- 접근 제어자(Access Modifier) : 접근 제어자는 public, private, protected로 총 3가지가 있다. (default는 그냥 아무것도 안 붙이는 것. 엄밀히 따지면 접근제어자는 아니다)
private 현재 클래스에서만 접근 |
default(아무것도 붙이지 않음) 현재 패키지에서만 접근 |
protected 같은 패키지에서만 접근(보통 때는 default랑 똑같다), 하지만 상속 관계일 때는 public으로 접근 |
public import만 하면 다 접근 가능 |
- 접근 제어자가 아닌 나머지를 Usage Modifier라고 보면 된다. ex) abstract, static, final
- static final로 쓰든 final static으로 쓰든 순서 바꿔도 둘 다 되는 이유 : 둘 다 Usage Modifier에 해당한다.
참조변수와 toString()
package object.test;
class ReferenceVariable{
private String notAddr = "Getter를 써야 이 값이 출력됨";
String notAddr2 = "필드 앞에 private를 안 붙였으므로 직접 접근 가능";
public String getNotAddr() {
return notAddr;
}
}
public class ReferenceVariableTest {
public static void main(String[] args) {
ReferenceVariable rv = new ReferenceVariable();
System.out.println("1번\n" + rv); // 주소값 출력
System.out.println("2번\n" + rv.toString()); // 주소값 출력
System.out.println("3번\n" + rv.getNotAddr());
System.out.println("4번\n" + rv.notAddr2);
}
}
1번) object.test 패키지에 있는 ReferenceVariable 클래스의 Full Name은 object.test.ReferenceVariable이다. 이렇게 패키지명을 포함한 클래스 전체 이름을 FQCN(Full Qualified Class Name)이라고 하는데, 이렇게 써야 프로그램이 인식한다. 콘솔창에 출력되는 object.test.ReferenceVariable@7852e922에서 object.test.ReferenceVariable 뒤에 있는 7852e922는 클래스의 주소값이라고 할 수 있다. 따라서 참조변수는 주소값을 가지고 있고, 참조변수를 있는 그대로 출력하면 주소값이 출력된다. |
2번) 2번은 1번과 출력 결과가 똑같다. toString()은 주소를 문자열로 반환하는 함수인데, 참조변수 뒤에는 무조건 .toString()이 생략되어 있다. 그러므로 toString으로 호출하든 안 하든 똑같이 주소값이 문자열로 반환된다. ===> 결론1 : 자바에서는 객체의 주소값을 출력하면 문자열로 출력된다. |
3번) 객체의 필드값을 가져오려면 Getter를 이용해서 가져오면 된다. |
4번) 필드 앞에 접근 제어자 private에 붙이지 않았다면 현재 같은 패키지 안에 있는 클래스이므로 참조변수.필드명으로 직접 접근할 수 있다. ==> 하지만 필드명 앞에는 항상 private를 붙여서 Encapsulation을 하는 게 좋다.(값을 주입할 때는 생성자나 Setter / 값을 가지고 올 때는 Getter) |