ch07

김민지·2023년 1월 16일
0

자바의정석

목록 보기
6/8
post-custom-banner

객체지향프로그래밍

상속

  • 기존클래스들의 멤버변수, 메서드 모든정보를 다 알고있는 클래스를 또 하나 정의하여 추가적인 변수/메서들르 정의하는것

포함

  • a를 가지고 있는 관계면 a를 포함관계에 집어넣는다. 즉, 멤버변수에 정의한다. 그런데 a가 A이다. 이런 is a 관계이면 a를 상속관계에 집어넣는다. extends를 사용함을 의미한다

오버라이딩

  • 부모메서드를 자식에서 재정의하는것

부모 static메서드 오버라이딩

  • 불가능하다. 만약 된다고 해도 이건 오버라이딩이 아니라 각 클래스에 별개의 static메서드를 정의한것이다

오버로딩

  • 매개변수로 구분되는, 이름이 같은 메서드를 중복정의하는것

다중상속

  • 예를들어 A클래스에도 a라는 멤버변수가 있고 B클래스에도 a라는 멤버변수가있을때 이 둘을 상속받는 C에서 a두개를 구분할 수 없다
  • 관계들이 복잡해진다
  • 복합적인 기능을 가지는 클래스 쉽게 작성가능
  • 단일상속일 경우 다중상속보다 클래스의 관계가 명확해진다
  • 다중상속을 사용할경우 상속말고 포함개념을 사용하여 멤버변수로 등록후 사용하자

object

  • A라는 클래스를 만들었다고하자. 모든 클래스의 조상은 object이기때문에 extends object라는 코드를 컴파일러가 자동으로 추가해준다

super

  • super()는 부모클래스 생성자를 뜻하며 자식생성자의 첫줄에서 실행되어야한다
    왜냐하면 자식에서 부모의 변수/메서드 무언가를 사용할수도있기때문
  • 모든클래스는 object로부터상속받는다. 이때문에 일단 생성자의첫줄은 부모를 호출하는거일수밖에없다
    만약에 생성자를호출하는 코드가 아니더라도 컴파일러가 자동으로 인자가 없는 생성자를 삽입해준다

패키지

  • 클래스들의 묶음

  • 클래스의 실제이름은 패키지까지 포함한다

  • 클래스는 물리적으로 하나의 .class 즉 클래스파일인것과 같이 패키지는 물리적으로는 하나의 디렉터리를 의미한다

  • 하나의 소스파일에는 첫번째 문장으로 단 한번의 패키지 선언만 허용 package 패키지명;

  • 모든 클래스는 반드시 하나의 패키지에 속해야한다

  • 만약 패키지를 지정하지 않으면 이름없는 패키지에 속하게된다

import

  • 다른패키지의 클래스를 사용하려면 아주 긴 패키지명.클래스명을 사용하여 매번 클래스를 사용할때 긴 이름을 사용해야한다.
  • import 를사용하면 컴파일러에게 패키지명을 알려주는거와 다름없어서
    알아서 클래스명앞에 패키지명을 붙여준다
import java.util.*;
import java.text.*;
//import java.*; 
  • 마지막 문장이 1+2 번쨰 문장을 대신해주진않는다.
  • 즉, 하위패키지의 클래스까지 포함해주는게 아니라 하나의 패키지내에있는 클래스들만 가능하다

static import

  • static멤버를 호출할때 클래스 이름을 생략할 수 있다
import static java.lang.Math.random;
//System.out.println(Math.random());
System.out.println(random());//Math 생략

제어자

  • 부가적인 의미를 부여하는 것
    public, protected, static, final ..

접근제어자를 사용하는 이유

  • 외부로부터 데이터의 접근을 적절히 제어해서 내부구현을 감추기 위해서
  • 내부구현이 다드러나는 코드는 외부와 내부의 결합도가 커져서 유지보수가 어렵다
  • 또한 사용하면안될곳에서도 사용이 일어날 수 있다

final

  • 클래스 final: 자식클래스 정의못함
  • 메서드 final: 오버라이딩 못함
  • 일반적으로 선언과 초기화를 동시에 해야하지만 인스턴스의 경우 생성자에서 초기화가 가능

abstract

  • 클래스 abstract: 추상메서드를 가지고있음을 의미
  • 메서드 abstract: 선언부만 작성하고 구현부는 작성하지 않았다라는것을 알림
  • 추상클래스는 아직 미완성된 클래스임으로 인스턴스를 생성할수없다
  • 다른클래스가 이 클래스를 상속받아서 원하는 일부의 메서드만 오버라이딩해도된다는 장점이있다. 만약 abstract이 없다면 구현내용을 비워도되는것을 매번 빈 구현을 다 정의해야한다는 번거로움이 있다.

왜 추상메서드를 정의?

  • 추상메서드 선언 (abstract선언) 대신에 빈몸통을주고 그것을 구현하라고하면? -> 컴파일에러안나기때문에 개발자가깜빡하고 넘어갈수있음
    구현을 강제하기 위해서 abstract선언이 포함된 추상메서드 선언을 해야함

추상클래스 vs 인터페이스

  • 추상 클래스는 그 추상 클래스를 상속받아서 기능을 이용하고, 확장시키는 데 있습니다. 반면에 인터페이스는 함수의 껍데기만 있는데, 그 이유는 그 함수의 구현을 강제하기 위해서 입니다
  • 추상클래스란? 상속을 통해서 자손 클래스에서 완성하도록 유도하는 클래스
  • 추상클래스는 IS - A "~이다".
    인터페이스는 HAS - A "~을 할 수 있는".
  • 이런식으로 추상클래스와 인터페이스를 구분하여 사용하게 되면 장점이 있다.
    A라는 개념에 속해서 만들어지는 기능과 A'라는 포괄적인 기능을 사용하기때문에 할 수 있는 기능을 분리해주기때문에 서로다른 개념에 속하고 같은 기능을 사용한다고할때 유지보수가 수월하게 코드로 구현할수있게된다.
    아래의 예제에서는 kevin과 tertile이 사람/동물로 다른 추상클래스를 가지지만 같은 인터페이스를 상속받으므로써 같은 동작을 수행할 수 있다

무엇을 언제 사용?

  • 일반메서드 + 추상메서드를 쓴다면 추상클래스를, 추상메서드만 사용한다면 인터페이스를 사용하자

정리

  • 추상클래스는 추상메서드를 가지고있는 클래스를 의미한다
    추상메서드란무엇일까? 추상메서드란 구현부가 비어잇고 abstact라는 키워드가 붙은 메서드를 의미한다
    추상클래스도 인터페이스의 역할을 할 수 있는데 둘을 구분하였다
    이이유는 둘의 역할이 애초에 다르기때문이다.
    추상클래스는 ~이다 의 관계일때 사용하고
    인터페이스는 ~라는것을 할 수잇다 라는 관계일때 사용한다
    이렇게 개념에 대한 구현과 기능에 대한 구현을 분리함으로써
    다른 개념에 속하더라도 같은 기능을 할 수 있도록 코드를짜는게 가능해진다

생성자의 접근제어자

  • 생성자에 접근제어자를 두면 싱글톤같은 패턴을 만들 수 있게 된다

싱글톤 패턴

왜 static class여야할까?

  • 지금 new Singletone으로 쓰고있지만 사실은
  1. Main 클래스 인스턴스 생성
  2. Main 클래스 인스턴스를 활용하여 싱글톤 인스턴스 만들기
    과정을 거쳐야한다
    그런데 바로 new를 통하여 생성하였으니 안되는것이다. 이를 해결하려면
  3. s변수의 static선언해제
  4. Singleton 클래스를 static선언하고 Main.singleton();사용
  5. main클래스 인스턴스를 만든 뒤에 해당 인스턴스로 싱글톤 인스턴스 만들기

정리

  • 싱글톤클래스가 inner 클래스예요
    이너클래스는 외부 클래스의 인스턴스가 생성된 이후에 생성돼요
    그래서 저렇게 static변수에 자기 인스턴스를 할당할수없어요
    왜냐하면 static 변수는 인스턴스 생성 이전에 할당돼야하니깐요
    만약 그러고 싶다면 main인스턴스를 static으로 하나만든다음에 그 인스턴스로 싱글톤 인스턴스를 만들거나
    class를 static으로 정의해서 main클래스 인스턴스 생성없이도 싱글톤클래스를 만들수있도록 해야해요
public class Main{
    //final 클래스-> 자식 클래스 정의못함
    //final을 붙여야 컴파일시점에 오류를 잡을 수 있음
    public final  class Singleton{
        //싱글톤 클래스가 내부클래스이기때문에 외부클래스 인스턴스를 생성해주든지
        //static으로 class를 선언하든지 해줘야함
        private static Main main = new Main();
        //static으로 변수를 선언해줘서 클래스의 인스턴스 생성없이도
        //그러니까 인스턴스 생성전에 클래스를 만드는것만으로도 인스턴스하나를 생성해서 가지고있어야함
        private static Singleton s = main.new Singleton();
        //외부로의 접근을 막는다 위코드에서 만든 인스턴스를 제외하고는 추가 인스턴스를 만들 수 없다
        private Singleton(){
                
        }
        //해당 메서드로만 인스턴스로의 접근이 가능하게하고 하나의 인스턴스를 매번 재활용해서 사용한다
        public static Singleton getInstance(){
            return s;
        }
    }
}

추가 알아둘 사항

  • static 메서드에 abstract붙일 수 없다.
    static메서드는 몸통이 있는 메서드에만 사용할 수 있기때문
  • 오버라이딩 시 부모메서드보다 더 넓은 범위의 접근제어자를 사용해야한다

다형성

  • 여러가지 형태를 가질 수 있는 능력
  • 부모타입의 참조변수로 자식인스턴스를 참조가능

참조변수타입vs 인스턴스타입

참조변수타입만큼 참조를 할 수 있고
인스턴스 타입만큼의 메모리가 확보된다
인스턴스타입을 자식만큼해도 참조변수를 부모로하면 부모만큼밖에 참조를 못한다

Parent p = new Child();//error 작은것 <- 큰것일때 error발생
Child c = new Parent();
Child c2 = (Child) p;//자식 참조변수가 자식 인스턴스가리킴
Parent p2 = (Parent) c;//부모 참조변수가 부모 인스턴스가리킴

상속에서의 형변환

  • 자식->부모: 형변환생략가능
  • 부모->자식: 형변환생략불가능 (더커질것이기때문에)
  • 형변환은 참조변수의 타입을 변환하는것이지 인스턴스를 변환하는것이 아니다
  • 형변환을 시킨다는것은 곧 내가 참조할 수있는 멤버의 개수를 조절하게 되는것이다
  • 참조변수가 인스턴스를 가리킬때를 주의해야한다 작은것으로 큰것을 가리킬수없다. 어차피 참조를 못할거기때문에 컴파일에러가 발생한다

멤버변수와 멤버메서드

  • 다음 코드에서(중복정의된 경우) 참조변수에 따라 멤버변수가 달라지는 반면 멤버메서드는 인스턴스에 따라 호출되는 메서드가 달라진다
  • 보통은 캡슐화를 통해 getter, setter를 사용하는데 이렇게 멤버변수에 직접접근하게되면 참조변수의 타입에다라 사용되는 멤버변수가 달라질 수 있어서 조심해야한다
public static vodi main(){
	Parent p = new Child();
    Child c = new Child();
    
    System.out.println(p.x);//100
    p.print();//"자식"
    System.out.println(c.x);//200
    c.print();//"자식"
}
class Parent{
	int x = 100;
    void print(){
    	System.out.println("부모");
    }
}
class Child{
	int x = 200;
    void print(){
    	System.out.println("자식");
    }
}

매개변수의 다형성

 public class Item{
        int price;
  }
    public class Tv extends Item{}
    public class Computer extends Item{}
    public class Person{
        int money = 1000;
        public int buyTv(Tv tv){
            return money-tv.price;
        }
        public int buyComputer(Computer computer){
            return money-computer.price;
        }
        //앞의 두 메서드를 하나의 메서드로 처리가능
        public int buy(Item item){
            return money-item.price;
        }
    }
  • 참조변수 하나로 자식인스턴스를 가리킬수있으니 자식인스턴스의 공통적인부분(부모클래스에 정의된 부분)은 다음과같이 공통코드로 대체가 가능하다
  • 이때문에 부모참조변수를 통해 서로다른인스턴스타입의 배열을 만드는것도 가능

인터페이스

  • 모든 멤버변수는 public static final 이다
  • 모든 메서드는 public abstract이다
  • 그리고 위의 제어자는 생략가능하다
  • 인터페이스 끼리의 상속은 implements가 아니라 extends를 사용한다
  • 인터페이스는 static상수만 정의할 수 있어서 부모의 멤버변수와 충돌할일이 거의없고 충돌해도 킄ㄹ래스이름붙여서 구분이 가능하다

다중상속을하고싶을때

  • 비중이높은쪽을 상속받고 나머지 한쪽은 인터페이스로도 정의한다
    그리고 멤버변수로 나머지한쪽에 대한 인스턴스를 만들어서
    인터페이스를 구현하는 메서드 내부에서 인스턴스로 내부 메서드들을 호출한다

인터페이스를 이용한 다형성

fightable을 구현한 fighter class가 있다고 가정하다

Fightable f = new Fighter();//가능
public void attach(Fightable f){...}//매개변수로도 사용가능
public Fightable method(){...}//리턴타입으로 정의하는것도 가능
//Fightable 을 구현한 구현체의 인스턴스를 넘겨주어야함

return타입이 인터페이스라는것은?

  • 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 의미

인터페이스의장점

  • 개발시간단축
    ㄴ 설계도를 바탕으로 각자 설계도에 맞는 코드를 짜면되니까 나눠서 업무를 하기에 좋다
  • 서로관계없는 클래스들에게 관계를 맺어줄 수 있다
    ㄴ 서로 상속관계도 아닌 클래스들에게 하나의 인터페이스를 구현하여 공통의 기능을 구현시킬 수 있다
  • 직접적인 관계를 간접적으로 만들 수 있다
class A{
	public void print(B b){
    	b.print();
    }
}
class B{
	public void print(){
    	System.out.println("BB");
    }
}
class A{
	//B와의 직접적인 연관이 끊어졌다
	public void print(I i){
    	i.print();
    }
}
class B implements I{
	public void(){
    	System.out.println("BB");
    }
}
//추가적인 클래스
class C implements I{
	public void(){
    	System.out.println("CC");
    }
}
  • B의 내용이 변경되어도 A에게 영향이 가지 않는 간접적인 관계가 되었다

디폴트 메서드

  • 인터페이스에 메서드를 추가하게 되면 해당 인터페이스를 구현한 모든 클래스에 대해 선언을 또 해주어야한다
  • 인터페이스에 추가메서드가 생길경우 디폴트 메서드로 정의해준다
    그러면 이를 모든 클래스에서 사용할 수 있다
  • 여러 인터페이스에서 디폴트 메서드 간의 충돌이 일어나면 구현클래스에서 해당 메서드를 오버라이딩해주어야한다(컴파일에러남)
  • 디폴트 메서드와 디폴트메서드가 포함된 인터페이스를 구현하는 클래스의 메서드간 충돌이 일어난다면 디폴트 메서드는 무시된다
 interface C {
        default void print(){
            System.out.println("C");
        }
 }
    interface B extends C{
        default void print(){
            System.out.println("B");
        }
    }
    static class Fighter implements B{

    }
    public static void main(String[] args) throws IOException {

        Fighter ff = new Fighter();
        ff.print();//"B"
    }
 interface C {
        default void print(){
            System.out.println("C");
        }
 }
    interface B extends C{
        default void print(){
            System.out.println("B");
        }
    }
    static class Fighter implements B{
    	//추가된 코드
		 public void print(){
            System.out.println("Fighter");
        }
    }
    public static void main(String[] args) throws IOException {

        Fighter ff = new Fighter();
        ff.print();//"Fighter"
    }

내부메서드

  • 두 클래스가 긴밀한 관계일때 사용
  • 내부클래스에서 외부클래스의 멤버에 쉽게 접근가능
  • 외부로부터 불필요한 클래스(내부클래스)를 감출 수 있다(캡슐화)

내부클래스의 종류

선언된 위치에 따라 구분할 수 있다

  • 인스턴스 클래스: 멤버변수 위치
  • 스태틱 클래스: static 멤버변수 위치
  • 지역 클래스
  • 익명클래스

static클래스의 내부클래스로 인스턴스 non-static이 가능할까?

  • 가능하다
import java.io.*;
import java.lang.reflect.Array;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.Arrays;

public class Main{
     static class outerClass{
        class innerClass{
            int x1;
            static int x2 = 2;
        }
        static class staticInnerClass{
            int x1 = 1;
            static int x2 = 2;
        }
        public static void print(){
            System.out.println(innerClass.x2);
        }

    }
    public static void main(String[] args) throws IOException {
        outerClass class1 = new outerClass();
        outerClass.innerClass class2 = class1.new innerClass();
        class2.x1 = 2;
        outerClass.innerClass class4 = class1.new innerClass();
        class4.x1 = 4;
        System.out.println(class2.x1);
        System.out.println(class4.x1);
    }
}

출력결과 2 와 4 가 나온다.

import java.io.*;
import java.lang.reflect.Array;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.Arrays;

public class Main{
     static class outerClass{
         int x;
        class innerClass{
            int x1;
            static int x2 = 2;
        }
        static class staticInnerClass{
            int x1 = 1;
            static int x2 = 2;
        }
        public static void print(){
            System.out.println(innerClass.x2);
        }

    }
    public static void main(String[] args) throws IOException {
        outerClass class1 = new outerClass();
        class1.x = 2;
        outerClass class2 = new outerClass();
        class2.x = 3;
        System.out.println(class1.x);
        System.out.println(class2.x);
    }
}

출력결과 2와 3이나온다
static의 내부에 있다고 무조건 다 static이 되는것이 아니다.
class단위에 static이 붙는다는것은 외부클래스 참조없이 만들수있다! 만을 의미하는것가탇
Main없이 new로 바로 outerClass를 생성할 수 있는 그정도의 의미를 갖고있는것같다

그래서 다음코드도 false가 나온다.
static class는 인스턴스가 생성되는영역인 heap에 생긴다
static영역에 생기지않는다

익명클래스

  • 이름이 없는 클래스로 이름이 없어서 생성자도 가질 수 없고, 상속받는 인터페이스 혹은 클래스 하나만 사용해서 만들 수 있다. 익명클래스는 이름이 없기때문에 컴파일을 하면 외부클래스명$숫자.class라는 이름으로 클래스파일이름이 결정된다

https://brunch.co.kr/@kd4/6
https://myjamong.tistory.com/150

profile
안녕하세요!
post-custom-banner

0개의 댓글