즐거운 자바(2)

지환·2023년 8월 3일
0

JAVA

목록 보기
2/39
post-thumbnail

출처 | 인프런 즐거운 자바

추상 클래스

  • 추상 클래스는 인스턴스가 될 수 없다.
  • 추상 클래스를 상속받는 자손이 인스턴스가 된다.
  • abstract 키워드를 사용하여 클래스를 정의한다.
  • 추상 클래스는 보통 1개 이상의 추상 메소드를 가진다.(추상 메소드가 없어도 오류가 발생하진 않는다.)
  • public abstract class 클래스명 {...}
  1. 추상 클래스는 생성자를 가질 수 있다.
  2. 생성자도 가질 수 있다. + 변수 선언도 가능하다.
  3. 추상 클래스의 구현체 일 떈 extends 사용

ex)

템플릿 메소드 패턴으로 배우는 추상 클래스

  • 어떤 순서를 가지고 있다. 이런 메소드를 템플릿 메소드랴고 한다.

템플릿 메소드 정리 블로그 [참고]

https://gmlwjd9405.github.io/2018/07/13/template-method-pattern.html

String 클래스는 왜 final인가?

결과

  • new라고 새로운 객체가 생성된다.

str1==str2는 같은 곳을 참조하느냐? -> yes

str3 == str4는 같은 곳을 참조하느냐? -> no

  • toUppercase를 추가하고 난 후


  • 불변 클래스이기 때문에 변하지 않는다.
  • 세 번째 있는 값을 잘라서 출력 한 다음 그 다음에도 str1를 출력하게 된다면 똑같은 hello가 나올까?

  • String과 비슷하지만 내부가 변하는 StringBuffer 클래스도 존재한다.

접근제한자

인터페이스

  • 무슨 기능을 만들어야 할까? vs 구현부터 하기
  • 어떤 기능을 만들어야 할지부터 고민한다. 만들어야 할 기능들을 관련된 것끼리 묶은 후 이름을 지어준다.

인터페이스 작성 문법

  • 인터페이스이름은 Upper CamelCase로 작성된다.

  • interface도 확장자가 .java 파일로 작성한다.

  • 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메소드는 public abstract이어야한다.(JAVA 7까지는) final, abstract를 생략하면 자동으로 붙는다.

  • java8 부터는 디폴드 메서드와 정적 메서드도 선언이 가능하다.

  1. 선언부와 생성부의 타입이 다를 수 있다. - 다형성의 전제조건
  2. 생성부의 이름으로 결정된다.

로또만들기 게임 예제

LottoMachine


// 1~45까지 써있는 Ball를 로또 기계에 넣는다.
// 로또 기계에 있는 Ball들을 섞는다.
// 섞인 Ball중에서 6개를 꺼낸다.

public interface LottoMachine {

    int MAX_BALL_COUNT = 45;
    // 해당 interface 변수들은 모두 public static하다. 즉, 메모리에 올라가있다.
    int RETURN_BALL_COUNT = 6;
    public abstract void setBalls(Ball[] balls); //배열이라고 하는것은 여러개를 의미한다. 45개를 받는다.
    // Ball[] Ball 여러개를 받겠다라는의미
    public abstract void mix(); // 자기가 가지고 있는 Ball들을 섞는다.
    public abstract Ball[] getBalls(); // 6개의 Ball를 반환한다.

}
//인터페이스가 가지고 있는 것들은 추상메소드다.
//즉, interface는 껍데기이다. 프로그램을 만들기전에 이런 기능을 가지고 있어야 될 것 같아 라고 정의만 하는 것
//장점 : 메소드 구현을 안 해도 Todolist처럼 이용가능하다.

LottoMachineImpl

public class LottoMachineImpl implements LottoMachine{

    private Ball[] balls;
    @Override
    public void setBalls(Ball[] balls) {
        this.balls = balls;

    }

    @Override
    public void mix() {
        for(int i = 0; i < 10000; i++)
        {
            int x1 = (int)Math.random() * 45;
            int x2 = (int)Math.random() * 45;
            if(x1 != x2)
            {
                Ball tmp = balls[x1]; // 값을 치환할 떄는 type의 임시변수가 필요하다. swap
                balls[x1] = balls[x2];
                balls[x2] = tmp;
            }
        }

    }

    @Override
    public Ball[] getBalls() {
        Ball[] result = new Ball[6]; //6개를 참조할 수 있는 배열
        for(int i = 0; i<6; i++)
        {
            result[i] = balls[i];
        }
        return result;
    }
}

//인터페이스를 구현하게 되면 반드시 인터페이스가 가지고 있는 메소드를 오버라이딩할 필요가 있다.
//위에 메소드를 생성시킨 것은 로또머신이 가지고 있는 3가지 메서드가 구현된다.

Ball

public class Ball {
    // 불변 객체
    public int getNumber() {
        return number;
    }

    private int number; //ball은 값을 가져야한다. ball 값 자체가 변하면 안 된다.
    public Ball(int number)
    {
        this.number = number;

    }


}

LottoMachineMain

public class LottoMachineMain {
    public static void main(String[] args)
    {
        //Ball b1 =new Ball(1);
        // ...
        //Ball b45 =new Ball(45);

        //변수가 45개 필요한 상황
        Ball[] balls = new Ball[45]; // 인스턴스를 생성한게 아니라 45개를 참조할수있는 변수를 45개를 만든것이다.
        //balls[0] = new Ball(1);
        //balls[1] = new Ball(2);
        //...
        //balls[44] = new Ball(45); 이런식으로 들어간다.
        for(int i = 0; i < 45; i++)
        {
            balls[i] = new Ball(i + 1);
        }

        LottoMachine lottoMachine = new LottoMachineImpl();
        // 이 코드가 실행됐다는 것은 로또머신이 생성된것이다.
        lottoMachine.setBalls(balls);
        lottoMachine.mix();
        Ball[] result = lottoMachine.getBalls();

        for(int i = 0; i < result.length; i++)
        {
            System.out.println(result[i].getNumber());
        }
    }
}

배열 설명

  • balls는 Ball[]에대한 배열을 참조한다.
  • Ball[] balls = new Ball[45] 이것은 생성자가 아니라 배열을 생성한것이다.
  • balls[0] = new Ball(1); 이라고 참조하게 하는 것이다. 처음은 NULL 값이다.
  • 45개의 배열을 초기화하는것이다.

인터페이스 작성 문법

  • 인터페이스 이름은 Upper CamelCase로 작성한다.

  • interface도 확장자가 .java 파일로 작성한다.

  • 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메소드는 public abstract이어야 한다. final, abstract를 생략하면 자동으로 붙는다.

  • java8 부터는 디폴트, 메서드와 정적 메서드도 선언이 가능하다.

인터페이스의 default method(JDK 8에 추가된 메소드)

  • A라는 사용자가 메소드가 3개 선언된 interface를 작성한 후 외부에 공개를 하였다.(라이브러리로 제공)

  • 여러 사용자들이 해당 인터페이스를 이용해 구현을 하였다.

  • A라는 사용자는 인터페이스에 1개의 메소드가 추가로 더 있는게 좋다고 판단하였다. 그래서 메소드를 추가 하였다.

  • 여러 사용자들은 라이브러리가 업데이트 된 줄 알고 업데이트 하였다. 무슨 일이 벌어졌을까? -> 에러 폭탄

인터페이스의 static method(JDK 8에 추가된 메소드)

  • 인터페이스를 구현한 클래스가 없어도 사용가능한 static method

팩토리 메소드 패턴

  • 복잡한 생산 과정을 숨기고, 완성된 인스턴스만 반환한다.

BeanFactory

package FactoryMethod;

public class BeanFactory {
    //   2. 자기 자신 인스턴스를 참조하는 static한 필드를 선언한다.
    private static BeanFactory instance = new BeanFactory();
    //  1. private 생성자를 만든다. 외부에서 인스턴스를 생성하지 못한다.
    private BeanFactory()
    {
    }
// 3. 2번에서 생성한 인스턴스를 반환하는 static한 메소드를 만든다.

    public static BeanFactory getInstance()
    {
        return instance;
    }

    public Bus getBus()
    {
        return new Bus(); //
    }
}

BeanFactoryMain

package FactoryMethod;

public class BeanFactoryMain {
    public static void main(String[] args){
        BeanFactory b1 = BeanFactory.getInstance();
        BeanFactory b2 = BeanFactory.getInstance();

        if(b1 == b2)
        {
            System.out.println("b1 == b2");
        }

        Bus bf1 = b1.getBus(); //객체생성을 대신해주는걸 팩토리라고 한다.
        Bus bf2 = b1.getBus();
    }

}

Bus

package FactoryMethod;

public class Bus extends  Car{
    public void a()
    {
        System.out.println("a");
    }
    public void b()
    {
        System.out.println("b");

    }
    public void c()
    {
        System.out.println("c");
    }

}

SuperCar

package FactoryMethod;

public class SuperCar  extends  Car {
    public void a()
    {
        System.out.println("Super Car a!!!");
    }
}

클래스 로더를 이용한 인스턴스 생성하기

import java.lang.reflect.Method;

상황
1. a() 메소드를 가지고 있는 클래스가 있다.
2. 이 클래스 이름이 아직 무엇인지 모른다.
3. 나중에 이 클래스 이름을 알려주겠다. 라는 상황
4. a() 메소드를 실행할 수 있도록 코드를 작성해라

package FactoryMethod;
import java.lang.reflect.Method;

public class ClassLoaderMain {
    public static void main(String[] args) throws Exception
    {
        
        //Bus b1 = new Bus();
        //b1.a(); // a 출력 -> b1를 모르는 상황 이 때 클래스로더를 사용


        String className = "FactoryMethod.Bus"; // Bus ==>SuperCar ==> Car사용 가능
        Class clazz = Class.forName(className); //클래스는 ClassPath에서 찾는다.
        //className에 해당하는 이름을 ClassPath에서 찾는다. 그리고 그 클래스 정보를 clazz에 잠조하도록 넣는다.
        Object o = clazz.newInstance(); //clazz의 정보를 가지고 인스턴스를 만들어라 라는 뜻
        //Object o = new Bus 와 동일하다.
        Car b = (Car)o;
        b.a(); // a 가 나온다.
        //Car가 부모이기 때문에 FactoryMethod.SuperCar가 Bus건 Car건 상관이 없다.
        Method m = clazz.getDeclaredMethod("a", null);
        // a() 메소드 정보를 가지고 있는 Method를 반환하라.
        m.invoke(o, null); // Object o가 참조하는 객체의 m 메소드를 실행해라.




//        // ----version1-------------------------------------------
//        Method[ ] declaredMethods = clazz.getDeclaredMethods();
//        //이 clazz가 가지고 있는 정보들을 Method[ ] declaredMethods 여러개로 리턴해준다.
//       for(Method m : declaredMethods) // 그 정보를 하나씩 꺼내서 배열의 정보를 하나씩 출력하면 a b c가 나온다.
//        {
//           /System.out.println(m.getName());
//        }


    }
}

이름 없는 클래스

  • new 생성자(){...}
  • 생성자 뒤에 중괄호가 나오고 코드를 오버라이딩하여 구현한다.

package FactoryMethod;

public class CarExam {
    public static void main(String[] args)
    {
        Car c1 = new Car(); // Car가 추상클래스여서 안돼 -> Car c1 = new Bus() 는 가능 (상속받고 있어서){
            @Override
            public void a() {
            System.out.println("asdasdasdas");
        }
        c1.a();

        }

    }
}
profile
아는만큼보인다.

0개의 댓글