정보은닉, 캡슐화의 장점
→ 모든 클래스와 멤버의 접근성을 가능한 좁혀야 함
클래스 멤버 접근 제한자
해당 클래스의 공개 api 하나 설정후, 나머지는 모두 private 처리(package-private)
하지만 serializable 구현시 그 필드는 모두 공개 될 수 있다
상위클래스의 메서드를 재정으 할 때는 그 접근 수준을 상위 클래스보다 좁게 설정할 수 없다 → 리스코프 치환 원칙 위배 되기 때문(상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야 한다)
public 클래스의 인스턴스 필드는 public이 아니여야 한다 → 불변식을 보장할수 없기 때문, 스레드 안전하지 않음
package com.springandjava.Test.effectivejava.chapter4;
public class ex16 {
private double x;
private double y;
public ex16(double x, double y){
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
}
패키지 바깥에서 접근할 수 있는 클래스라면 접근제를 제공해야 한다
불변클래스 : 인스턴스 내부 값을 수정할 수 없는 클래스
클래스를 불변으로 만드는 방법
package com.springandjava.Test.effectivejava.chapter4.ex17;
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im){
this.re = re;
this.im = im;
}
public double realPart(){
return re;
}
public double imaginaryPart(){
return im;
}
public Complex plus(Complex c){
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c){
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c){
return new Complex(re*c.re - im*c.im, re*c.im + im*c.re);
}
public Complex dividedBy(Complex c){
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im*c.im) / tmp, (im * c.re - re*c.im)/tmp);
}
@Override
public boolean equals(Object o){
if (o == this)
return true;
if (!(o instanceof Complex))
return false;
Complex c = (Complex) o;
return Double.compare(c.re, re) == 0 && Double.compare(c.im, im) == 0;
}
@Override
public int hashCode(){
return 31 * Double.hashCode(re) + Double.hashCode(im);
}
@Override
public String toString(){
return "(" + re + " + " + im +"i)";
}
}
plus, minus, times diviedBy → 매서드는 인스턴스 자신을 수정하지 않고 새로운 complex인스턴스를 만들어 반환함
불변 객체는 근본적으로 스레드 안전하기 때문에 따로 동기화할 필요가 없다
→스레드 안전(멀티 스레드 환경에서 한 스레드에 동시 접근해도 문제가 없는 것)
불변 객체는 자유롭게 공유할 수 있고, 내부 데이터를 공유할 수 있다.
불변클래스를 만드는 또다른 방법중 하나 → 모든 생성자를 private 혹은 package-private으로 만들고 public 정적 팩터리를 제공함
public final class Complex {
private final double re;
private final double im;
// public Complex(double re, double im){
// this.re = re;
// this.im = im;
// }
private Complex(double re, double im){
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im){
return new Complex(re, im);
}
정적 팩토리 메소드 : 메서드를 통해서 객체를 생성하는 것을 정적 팩토리 메서드라고 한다.(아이템 1)
상속은 메서드 호출과 달리 캡슐화를 깨트린다
기존 클래스를 확장하는 대신 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 하자
컴포지션(composition) : 기존 클래스가 새로운 클래스의 구성 요소로 쓰인다는 뜻에서 이러한 설계를 컴포지션 이라고 한다
포워딩(forwarding) : 기존 클래스의 대응하는 메서드를 호출해 그 결과를 반환
포워딩메소드(forwarding method) : 새 클래스의 메서드들의 전달 메서드
→ 이렇게 함으로서 기존 클래스에 영향을 받지 않는다.
상속용 클래스는 재정의할 수 있는 메소드를 문서로 남겨야 한다
→ @implSpec 태그
주의 할점!!
public class Super{
public Super(){
overrideMe();
}
public void overrideMe(){
}
}
package com.springandjava.Test.effectivejava.chapter4.ex19;
import java.time.Instant;
public final class Sub extends Super{
private final Instant instant;
Sub(){
instant = Instant.now();
}
@Override
public void overrideMe(){
System.out.println(instant);
}
public static void main(String[] args){
Sub sub = new Sub();
sub.overrideMe();
}
}
여기서는 overridemer가 재 정의되는데,
하위 클래스 생성자가 인스턴스 필드를 초기화 하기 전에 overrideme를 호출함 따라서 첫번째는 null을 출력
(상위 클래스 생성자는 하위 클래스 생성자가 인스턴스 필드를 초기화 하기전에 미리 호출함) → 즉 초기화가 안된 상태에서 호출하려다보니 nullpointException이 나옴
따라서 상속용 클래스가 아니면 상속하지 못하도록 구현하는게 좋다(final로 선언하거나 생성자 모두를 외부에 접근할수 없도록)
인터페이스는 믹스인 정의에 안성 맞춤이다
믹스인 : 클래스가 구현할 수 있는 타입으로 클래스 원래의 주된 타입외에도 특정 행위를 제공한다고 선언해줄 수 있다. 즉 이와 같이 대상의 메인 타입 외에도 추가로 선택적 기능을 혼합(mixed in)한다고 함
인터페이스로는 걔층구조가 없는 프레임워크를 만들 수 있다. (추상클래스는 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위타입이되어야 하기 때문)
탬플릿 메서드 패턴 : 인터페이스와 골격 구현 클래스를 함께 제공하는 식 → 인터페이스와 추상클래스의 장점을 모두 취하는 방식
static List<Integer> intArrayAsList(int[] a) {
Objects.requireNonNull(a);
return new AbstractList<>() {
@Override
public Integer get(int i) {
return a[i];
}
@Override public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val; //오토언박싱 autoboxing이란 래퍼(Wrapper) 클래스의 객체로 변환하는 것을 말합니다.
return oldVal; //오토박싱
}
@Override public int size(){
return a.length;
}
}
}
래퍼클래스란 기본 타입의 데이터를 객체로 취급해야 하는 경우가 있는데 기본 타입의 데이터를 그대로 사용할수는 없고 데이터를 객체로 변환해야 하는데 해당하는 데이터들을 객체로 포장해주는 것
자바 collection 인터페이스에 추가된 removeIf 메서드
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean result = false;
for (Iterator<E> it = Iterator(); it.hasNext(); ) {
if (filter.test(it.next())) {
it.remove();
result = true;
}
}
return result;
}
Predicate는 Type T 인자를 받고 boolean을 리턴하는 함수형 인터페이스
|<? super E> 는 부모와 현재 형태의 element만 받는 다는 뜻
반복자를 이용해 순회하면서 predicate를 호출하고, true 반환시 remove 메서드를 호출해 해당 원소 제거
상수인터페이스 와 같은 안티패턴 사용 금지
public interface Ex22 {
static final double A = 22;
static final double B = 23;
static final double C = 24;
}
차라리 상수 전용 클래스를 생성해라
public class EX22class {
//인스턴스화 방지
private EX22class(){
}
public static final double A = 22;
public static final double B = 23;
public static final double C = 24;
}
Tagged 구조
package com.springandjava.Test.effectivejava.chapter4.ex23;
import org.hibernate.AssertionFailure;
public class tagged {
enum Shape { RECTANGLE, CIRCLE}
final Shape shape;
double length;
double width;
double radius;
tagged(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
tagged(double length, double width){
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape){
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
계층 구조로 작성(추상화클래스를 이용해서)
abstract class hiarchy {
abstract double area();
}
class Circle extends hiarchy {
final double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * (radius * radius);
}
}
class Rectangle extends hiarchy{
final double length;
final double width;
Rectangle(double length, double width){
this.length = length;
this.width = width;
}
@Override
double area(){
return length*width;
}
}
중첩 클래스 (nested class) : 클래스 안에 정의된 클래스, 자신을 감싼 바깥 클래스에서만 쓰여야 되며 그외에 사용되면 탑레벨로 빼야함
중첩클래스의 종류
정적 멤버클래스, 멤버 클래스(비정적), 익명클래스, 지역클래스
여기서 정적 멤버클래스를 제외하면 나머지는 모두 내부 클래스이다.
비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결된다. 따라서 this를 사용해서 바깥 인스턴스 메서드를 호출하거나 참조할 수 있다. (클래스명.this)
따라서 바깥 인스턴스와 독립적으로 존재할 수 있다면 정적멤버클래스로 만들어야 한다
비정적 멤버 클래스는 바깥 인스턴스 없이 생성 불가
어뎁터를 정의할때 자주 쓰인다 → 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게하는 뷰로 사용
—> 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들어라
선언과 동시에 인스턴스가 만들어진다.
비정적문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있다.
여러 인터페이스를 구현할 수 없고, 인터페이스 구현동시에 다른 클래스를 상속할 수 없다.
주로 람다 처리에 사용
비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할수 있고 정적 멤버는 갖을 수 없다
public class main {
public static void main(String[] args) {
System.out.println(Utensil.NAME + Dessert.NAME);
}
}
class Utensil{
static final String NAME = "pan";
}
class Dessert{
static final String NAME = "cake";
}
하지만 이와 같은 파일이 다른 파일에 똑같이 작성되어 있다면?
→ 컴파일러에게 먼저 전달되는 파일로 컴파일 될 것이다
→ 해결법 : 탑레벨 클래스를 서로 분리(utensil, dessert)
public class main {
public static void main(String[] args) {
System.out.println(Utensil.NAME + Dessert.NAME);
}
private static class Utensil{
static final String NAME = "pan";
}
private static class Dessert{
static final String NAME = "cake";
}
}
이렇게 정적 클래스로 바꾸고 한파일에 작성해도 된다