본문 바로가기

자바

[JAVA] 컴퓨터 프로그래밍 - Class 추가

클래스 내부의 클래스 ( Nested Class )

클래스 내부에 클래스를 또 선언할 수 있으며, Nested Class(중첩 클래스) 라고 부름

Nested Class는 두 가지로 갈리는데,

안쪽 클래스가 일반 클래스 라면 Inner Class라고 부르며
안쪽 클래스가 Static 클래스 라면 Static Nested Class라고 부른다.

Outer Class와 다른 점은 Inner Class는 접근제어자를 4가지(public, private, protected,(없음)) 사용 가능하다는 것.

class OuterClass{
    class InnerClass{}
    static class StaticNestedClass{} 
}

Inner Class

static이 아닌 클래스로, 바깥쪽 클래스의 인스턴스 변수/메소드, 스태틱 변수/메소드에 접근이 가능하다
사용 시에 아우터 클래스의 인스턴스가 있어야 하며, 아우터 클래스의 인스턴스 하위로 들어감.(인스턴스 변수처럼)

class OuterClass{

    private int a;
    private static int b;

    class InnerClass{
        public void method(){ //  #1 일반 메소드
            a = 1; // ✅ 인스턴스 변수 접근 가능
            b = 2; // ✅ 스태틱 변수 접근 가능
        }
        public static void staticMethod(){} // ❌ 선언 불가
        /* 스태틱은 클래스에 종속되어야함.
           근데? 이너클래스는 아우터클래스의 인스턴스가 있어야 쓸 수 있음
           -> 클래스에 종속이 아니라 인스턴스에 종속
           ❌ 선언 불가
        */
    }
}

// 만약 이걸 호출하려면?

OuterClass out = new OuterClass();

OuterClass.InnerClass instance = outer.new InnerClass(); 
//선언시에 반드시 OuterClass의 인스턴스가 있어야 함.
//outer.new InnerClass(); 문법에 유의

instance.method()

Static Nested Class

static으로 선언된 클래스임. 바깥쪽 스태틱 변수/메소드 에는 접근이 가능하지만, 인스턴스 변수/메소드 에는 접근이 불가능하다.

class OuterClass{

    private int a;
    private static int b;

    static class StaticNestedClass{
        public void method(){ //   #1 일반 메소드
            a = 1; // ❌ 인스턴스 변수 접근 불가
            b = 2; // ✅ 스태틱 변수 접근 가능
        }

        public static void staticMethod(){} //  #2 스태틱 메소드
    }
}

// 만약 이걸 호출하려면?

//#1 일반 메소드

OuterClass.StaticNestedClass instance = new OuterClass.StaticNestedClass(); 
//안쪽 클래스가 스태틱이라 OuterClass.StaticNestedClass로 부를 수 있음.

instance.method() 
//하지만 스태틱 메소드가 아니라 인스턴스 메소드이기 때문에 instance 만들고 instance.method()로 접근해야함.

//#2 스태틱 메소드

OuterClass.StaticNestedClass.staticMethod();
//안쪽 클래스도 스태틱, 메소드도 스태틱이라 가능.

아니 이걸 왜씀?

  • 논리적으로 관련된 클래스를 묶을 수 있음
  • 캡슐화 강화... -> 뭔 말임?
    • 내부 클래스는 외부 클래스의 private에 접근 가능하니까 외부 클래스 구조를 노출시키기 싫으면 내부 클래스만 호출해서 외부 private 값을 수정하도록 할 수 있음
      예시)외부 클래스의 변수는 숨기면서 내부 클래스를 불러서 값은 수정할 수 있음.
    • public class Hidden(){ private int hiddenNum; // <-- 숨기고자 하는 변수 public class InnerClass{ // 외부에서 private 접근을 위한 이너 클래스 public void innerMethod(){ hiddenNum += 1; // <-- 이너 클래스는 외부의 private에 접근 가능 } } }

-> 게터 / 세터랑 비슷한 느낌인듯.

  • 코드의 가독성과 유지보수성 향상. 논리적으로 관련 코드가 묶이니까 가독성이 오름

Shadowing

만약 외부의 변수와 동일한 이름 의 변수가 내부에 생기면 호출 시에 더 가까운 변수를 가져와서 바깥쪽 변수가 가려지는 현상

ex1)

public class Box{
    public int num = 10;   // shadowing 당할 변수
    public void method(int num){  // 이름이 똑같음 (!) -> 외부 num을 가림.
        num // <-- (매개변수 num이 불러와짐 이 메소드에서 매개변수 num이 인스턴스 변수 num보다 더 가깝기 떄문)
    }
}

ex2) 실제로 겪었던 사례 ( 상속 )

class Parent{
    protected int a;
    protected int b;
    Parent(int a, int b){ // parent의 생성자
        this.a = a; //섀도잉 방지를 위해서 this. 으로 매겨변수와 인스턴스 변수를 구분해줌
        this.b = b;
    }

class Child extends Parent{
    private int a;
    private int b;
    Child(int a, int b){
        super(a,b); //parent 생성자로 a, b에 지정해주려고 했음
    }
    // 문제) 이후로 a랑 b 호출할 때마다 값이 없다고 나옴,
}
}

위 코드가 고장났던 이유는 super()로 a와 b에 저장한 값은 child 중 parent의 부분에 저장되는 것인데
-> protected a, protected b에 저장됨
근데 내가 private로 child에 또 a와 b를 선언해서 호출할 때마다 더 가까운 변수 ( private a, private b )를 가져왔던것.
그래서 지정되지 않은 변수를 불러댔으니 값이 나오질 않았다.
-> shadowing 문제임

원래는 super을 하면 그냥 부모의 constructer를 가져와서 쓰는 줄 알고 child에도 a와 b가 있어야 생성자가 제대로 돌아갈 줄 알았으나,
부모 부분이 상속에 의해 자동으로 생성되므로 a, b를 child에 생성할 필요가 없었다.

메소드 안의 클래스 ( Local Class )

말 그대로 메소드에 클래스를 집어넣은 것이다.
선언한 블럭 안에서만 사용할 수 있음 -> 따라서 접근 제어자를 쓸 수 없다 ( abstract, final 제외 )
외부 클래스의 모든 요소에 접근할 수 있음 (Inner Class랑 동일함)
단 이 클래스는 정의된 블록(메소드) 안에서만 호출할 수 있음

익명 클래스 ( Anonymous Class )

이름이 없는 클래스로, 메소드 내부에도 쓸 수 있고,
선언과 동시에 인스턴스를 만들어 낸다. -> new 키워드로 시작함.
한 번만 사용할 수 있음 (정의와 생성을 동시에 하기 때문)

단 특정 클래스를 상속하거나 특정 인터페이스의 구현체여야 함.
-> 혼자서는 사용 불가능. 익명 클래스의 틀이 존재해야함 (상속이던 구현이던)

이때 익명 클래스에서 새로 만든 메소드는 외부에서 호출이 불가능하다
ex)

interface Animal{
    void bark();
}

Animal dog = new Animal(){
    public void bark(){} // 원래 있던 메소드

    public void eat(){} // 새로 만든 메소드

}; // 세미콜론으로 끝나야 한다 (인스턴스 생성이니까)

dog.bark() // 가능
dog.eat() // 불가능
// dog는 Animal 타입인데, Animal에는 eat이 정의되어 있지 않음

간단히 구현만 해서 짧게 쓴다는 느낌으로 부른다.

람다식 ( Lambda Expressions )

인수(변수) -> (실행할 부분)

ex)
x -> System.out.println(x)
x를 받아서 출력함 (x의 타입은 생략해도 무관)

위와 동일한 코드
public void prnt(int x){
    System.out,println(x);
}
이걸 간략하게 쓴 거라고 보면 된다.

람다식으로는 메서드 간단하게 쓰기라고 생각하면 됨.
다만 자바에서는 구현의 경우에만 사용 가능함. 틀이 되는 인터페이스가 필요하다.
틀이 되는 인터페이스는 추상 메서드가 하나여야 함 (함수형 인터헤이스)

타입 추론

자바에서는 대충 타입 맞춰 놓으면 알아서 연결해준다.
ex) 오버로딩된 메서드 호출했을때, 매개변수에 맞춰서 호출함

Method Reference

:: 키워드로 메소드를 불러내는 것. 메소드를 람다식처럼 전달하게 해줌

종류 형태
static 메소드 참조 클래스명 :: 메소드명
인스턴스 메소드 참조 클래스명.인스턴스명 :: 메소드명
클래스 인스턴스 메소드 참조 클래스명 :: 메소드명 ( 인스턴스 역할을 함 )
생성자 참조 클래스명 :: new

클래스 인스턴스 메소드 <- 이거 머임?

일단 메소드를 정의해주되 인자를 넘겨줘야함

Function<String, Integer> f = String::length;
int len = f.apply("hello"); 

f에 담긴 메소드에 인자가 들어감
"hello".length() 를 실행하는 것

Enum

특수 데이터 타입임. 클래스 같은건데 딕셔너리 느낌.
ex) Drink Enum이 콜라 사이다 환타를 가진다고 했을때,

public enum Drink {
    Coke(2300),
    Sprite(2200),
    Fanta(2380);
    // Drink 타입의 객체들


    private int price;

    Drink(int price){ // 생성자
         this.price = price;
    }

    public int getPrice(){
        return this.price;
    }
}

Drink.Coke.getPrice(); // 2300.