|
| 1 | +# Java의 접근제어자 |
| 2 | +public / private / default / protected<br> |
| 3 | +**변수, 메소드, 클래스**(편의상 객체라 하겠다... 이걸 뭐라고 해야하지? 아래에도 계속 객체라고 나올 텐데 어떤 의미에서 사용한 것인지 잘 구분해주세요) 선언 시 붙일 수 있고 해당 객체에 대한 접근을 제한하는데 사용된다. |
| 4 | + |
| 5 | +1. public |
| 6 | +* 어느 곳에서든 호출/접근이 가능함 |
| 7 | + * 접근에 제한을 둘 필요가 없는 객체에 사용 |
| 8 | +* @Transactional 어노테이션은 public 메소드에 달았을 때만 제 기능으로 동작함 |
| 9 | +```java |
| 10 | +@Transactional // 트랜잭션 정책 적용 안됨 |
| 11 | +private String method1() { // IntelliJ 에서는 컴파일 에러 발생 |
| 12 | + ... |
| 13 | +} |
| 14 | + |
| 15 | +@Transactional // 트랜잭션 정책 적용 |
| 16 | +public String method2() { |
| 17 | + ... |
| 18 | +} |
| 19 | +``` |
| 20 | + |
| 21 | + |
| 22 | +2. protected |
| 23 | +* 동일 패키지 또는 하위 클래스에서만 접근이 가능하다. |
| 24 | +* 보통은 public으로 해결 가능한 메소드이다. 그렇다면 protected는 언제 왜 사용하는가? |
| 25 | + * 하위 클래스에서 Override하여 바꿀 가능성을 열어두는 것 |
| 26 | + ```java |
| 27 | + //protected example |
| 28 | + public class Man { |
| 29 | + protected void attack(){ |
| 30 | + System.out.println("때리기"); |
| 31 | + } |
| 32 | + } |
| 33 | + |
| 34 | + public class Archer extends Man { |
| 35 | + protected void attack(){ |
| 36 | + System.out.println("활쏘기"); |
| 37 | + } |
| 38 | + } |
| 39 | + ``` |
| 40 | + * 위의 예제에서 abstract vs protected 차이점은 무엇일까?<br> |
| 41 | + * abstract는 선언 시에 구현을 하지 않고 상속받는 클래스에서 재정의를 필수적으로 요구함 |
| 42 | + * protected는 재정의를 요구하지 않음 |
| 43 | +* 다들 김영한 강의를 들으므로... Jpashop에서의 사용 예제(JPA 활용 1편 엔티티 클래스 개발2 14:52~) |
| 44 | +```java |
| 45 | +package jpabook.jpashop.domain; |
| 46 | + |
| 47 | +import lombok.Getter; |
| 48 | + |
| 49 | +import javax.persistence.Embeddable; |
| 50 | + |
| 51 | +@Embeddable |
| 52 | +@Getter |
| 53 | +public class Address { |
| 54 | + |
| 55 | + private String city; |
| 56 | + private String street; |
| 57 | + private String zipcode; |
| 58 | + |
| 59 | + protected Address() { |
| 60 | + } |
| 61 | + |
| 62 | + public Address(String city, String street, String zipcode){ |
| 63 | + this.city = city; |
| 64 | + this.street = street; |
| 65 | + this.zipcode = zipcode; |
| 66 | + } |
| 67 | +} |
| 68 | +``` |
| 69 | +city, street, zipcode라는 세 개의 정보를 Address라는 값 타입으로 만든 것이다.<br> |
| 70 | +값 타입은 변경 불가능하도록 설계되어야만 해서 setter를 제공하지 않는다.<br> |
| 71 | +기본 생성자는 꼭 있어야해서 만들었는데 public은 너무 개방적이고 JPA 스펙 상 protected를 권장한다는 의미로 이해했다<br> |
| 72 | +어차피 여기에서의 protected는 큰 의미도 없으므로(다른 누군가 Address 클래스를 상속 받을 일도 없음) 그냥 그른갑다~하고 넘어가자<br> |
| 73 | + |
| 74 | +3. default |
| 75 | +* 접근제어자를 생략하면 기본으로 설정된다. |
| 76 | +* 같은 패키지 내의 클래스에서 접근이 가능하다. |
| 77 | + |
| 78 | +4. private |
| 79 | +* 실질적으로 접근 권한을 '제한'하고 싶을때 주로, 대부분, 통상적으로 private을 쓴다. |
| 80 | +* 해당 클래스 내부에서만 사용할 수 있기 때문에 해당 로직(주로 값 변경)이 어딘가에서 무분별하게 호출되는 것을 막기 위해 사용한다. |
| 81 | + |
| 82 | + |
| 83 | +5. 접근 권한 총 정리 |
| 84 | + |
| 85 | +|종류|Class|Package|Lower Class|All| |
| 86 | +|---|---|---|---|---| |
| 87 | +|public|O|O|O|O| |
| 88 | +|protected|O|O|O|X| |
| 89 | +|default|O|O|X|X| |
| 90 | +|private|O|X|X|X| |
| 91 | + |
| 92 | +# static / final |
| 93 | + |
| 94 | +1. final |
| 95 | + |
| 96 | +final은 **단 한번** 할당될 수 있는 객체에 사용된다. 구현한 코드의 변경이 일어나지 않기를 원할 때 사용하자. |
| 97 | +* final로 선언된 변수를 수정하려는 코드가 있으면 에러가 발생한다. |
| 98 | + * 아래의 경우는 에러가 발생할까? |
| 99 | + ```java |
| 100 | + public static void main(){ |
| 101 | + final Item item = new Item(); |
| 102 | + |
| 103 | + Item.setName("MacBook Pro"); // 이 때 에러가 발생할까? |
| 104 | + } |
| 105 | + ``` |
| 106 | + 정답 : X; Item 객체가 immutable(불변)한 것은 아니다. 객체의 속성은 변경할 수 있다. |
| 107 | +* final 메소드의 경우 오버라이드가 불가능하다. |
| 108 | +* final 클래스의 경우 상속이 불가능하다. |
| 109 | + |
| 110 | +김영한 스프링 강의에서 final이 사용됐던 예제를 보자. |
| 111 | +```java |
| 112 | +@Repository |
| 113 | +@RequiredArgsConstructor |
| 114 | +public class ItemRepository { |
| 115 | + private final EntityManager em; |
| 116 | + ... |
| 117 | +} |
| 118 | +``` |
| 119 | +Repository 계층은 EntityManager의 의존성 주입을 받아야 한다.(받아야 할 것이다)<br> |
| 120 | +의존성 주입 방법은 생성자, setter, field 등이 있으나 가장 무난한 것은 생성자를 이용한 주입이다.<br> |
| 121 | +그런데 RequiredArgsConstructor 어노테이션은 클래스 내에 final로 선언된 필드의 생성자를 자동으로 생성해준다(Lombok의 힘이다!)<br> |
| 122 | +따라서 RequiredArgsConstructor 어노테이션을 넣어주고 final로 선언하면 편하게 의존성 주입을 할 수 있다. |
| 123 | + |
| 124 | + |
| 125 | +2. static |
| 126 | + |
| 127 | +'객체' 와 '클래스'의 차이점을 안다고 가정하겠다!<br> |
| 128 | +static으로 선언된 변수와 메소드는 객체가 아니라 클래스에 묶인다.<br> |
| 129 | +따라서!<br> |
| 130 | +* 객체를 여러 개 생성해도 클래스에 할당된 메모리 영역에 있으므로 모든 객체가 같은 변수값, 메소드를 **공유**할 수 있다. |
| 131 | +* 인스턴스화하지 않아도 static 메소드를 호출할 수 있다. |
| 132 | +* Garbage Collector의 관리 영역 밖에 있으므로 프로그램 종료 시까지 메모리에 할당된 상태를 유지한다. 그래서 static 변수, 메소드가 너무 많으면 시스템에 악영향이 있을 수 있다! |
| 133 | + |
| 134 | +static을 사용함으로써 메모리 상의 이득과 손해를 보는 경우를 정리하고 넘어가자. |
| 135 | + |
| 136 | +3. 싱글톤 패턴 |
| 137 | + |
| 138 | +static에 대해 알았다면 싱글톤 패턴이 무엇인지 알 수 있다.<br> |
| 139 | +싱글톤 : 단 하나의 객체만을 생성하도록 강제하는 패턴 |
| 140 | +```java |
| 141 | +class Singleton { |
| 142 | + private static Singleton one; |
| 143 | + private Singleton() { |
| 144 | + } |
| 145 | + |
| 146 | + public static Singleton getInstance() { |
| 147 | + if(one==null) { |
| 148 | + one = new Singleton(); |
| 149 | + } |
| 150 | + return one; |
| 151 | + } |
| 152 | +} |
| 153 | + |
| 154 | +public class Sample { |
| 155 | + public static void main(String[] args) { |
| 156 | + Singleton singleton1 = Singleton.getInstance(); |
| 157 | + Singleton singleton2 = Singleton.getInstance(); |
| 158 | + System.out.println(singleton1 == singleton2); // true 출력 |
| 159 | + } |
| 160 | +} |
| 161 | +``` |
| 162 | +* 장점/사용하는 경우 |
| 163 | + * 인스턴스를 **하나만** 만들도록 강제해야 하는 상황 |
| 164 | + * 두 번째 이용(singleton2)의 경우 이미 만들어진 인스턴스를 가져오기만 하므로 로딩 시간이 빠름 |
| 165 | +* 단점 |
| 166 | + * 멀티쓰레드 환경에서 동기화를 제대로 안하면 인스턴스가 두 개가 동시에 생성되어 싱글톤 패턴이 깨짐 |
| 167 | + * 싱글톤이 너무 잡다하게 많은 일, 데이터를 처리하게 되면 객체 지향 설계 원리를 벗어나게 됨 |
| 168 | + |
| 169 | +써야 할 때만(그걸 아는게 어렵지만..) 잘 쓰자! |
| 170 | + |
| 171 | + |
| 172 | +# 뇌절을 쳐보자 |
| 173 | +``` |
| 174 | +private 생성자를 가진 클래스를 스프링 빈으로 등록할 수 있을까? |
| 175 | +``` |
| 176 | +먼저 이 질문에 대한 답은 '리플렉션'이라는 개념을 알아야 이해할 수 있을 것 같은데 이는 추후로 넘기도록 하겠다.<br> |
| 177 | +내가 공부한 것은 접근제어자이므로 여기에 집중하면, 생성자를 private으로 만들면 new를 이용한 생성이 불가능하고 대신 정적 팩토리 메소드(static factory method)를 이용하여 생성해야 한다. |
| 178 | +## 정적 팩토리 메소드(static factory method) |
| 179 | +```java |
| 180 | +// LocalTime.class |
| 181 | +... |
| 182 | +public static LocalTime of(int hour, int minute) { |
| 183 | + ChronoField.HOUR_OF_DAY.checkValidValue((long)hour); |
| 184 | + if (minute == 0) { |
| 185 | + return HOURS[hour]; |
| 186 | + } else { |
| 187 | + ChronoField.MINUTE_OF_HOUR.checkValidValue((long)minute); |
| 188 | + return new LocalTime(hour, minute, 0, 0); |
| 189 | + } |
| 190 | +} |
| 191 | +... |
| 192 | + |
| 193 | +// hour, minutes을 인자로 받아서 9시 30분을 의미하는 LocalTime 객체를 반환한다. |
| 194 | +LocalTime openTime = LocalTime.of(9, 30); |
| 195 | +``` |
| 196 | +* 생성자를 통해 객체를 생성하는 것이 아닌, 어떤 메소드를 통해 객체를 생성하는 것을 정적 팩토리 메소드라고 한다. |
| 197 | +* 이를 사용하는 것을 **팩토리 패턴**이라고 한다. |
| 198 | +* 생성자와 정적 팩토리 메소드 모두 객체를 생성한다는 동일한 기능을 수행하는 것 같은데, 어떤 차이점이 있는 것인가? |
| 199 | + * 장점 |
| 200 | + 1. 이름을 가질 수 있다 : 객체의 생성 목적을 담아낼 수 있다 |
| 201 | + 2. 매번 새로운 객체를 생성하지 않아도 된다 |
| 202 | + 3. 하위 자료형 객체를 반환할 수 있다(2번과 동일한 맥락) |
| 203 | + 4. 객체 생성을 캡슐화할 수 있다. |
| 204 | + |
| 205 | + -> 좀 더 가독성있고 객체지향적 프로그래밍을 할 수 있게 도와주며, 해당 도메인에서 '객체 생성'이 중요한 역할일 경우 사용하면 좋다. |
| 206 | + |
| 207 | + * 단점 |
| 208 | + 1. 팩토리 메소드만 존재하는 객체는 상속이 불가능하다 |
| 209 | + |
| 210 | + |
| 211 | + |
| 212 | +## 다음에 공부하고 싶은 내용 |
| 213 | +* Reflection |
| 214 | +* Garbage Collector |
| 215 | + |
| 216 | + |
| 217 | +## 자료 출처 |
| 218 | +* https://hyerm-coding.tistory.com/m/95 |
| 219 | +* https://velog.io/@kmdngmn/Spring-Transactional-privatepublic-%EC%A0%91%EA%B7%BC%EC%A0%9C%EC%96%B4%EC%9E%90 |
| 220 | +* https://wikidocs.net/232 |
| 221 | +* https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=2feelus&logNo=220576845725 |
| 222 | +* https://groups.google.com/g/ksug/c/eg3vxl4D6Bs?pli=1 |
| 223 | +* https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/ |
| 224 | +* https://advenoh.tistory.com/13 |
| 225 | +* https://wikidocs.net/228 |
| 226 | +* https://coding-factory.tistory.com/524 |
0 commit comments