D+38-중첩클래스.멤버클래스,로컬클래스,호출,제한범위,변수상수 ,익명클래스.변수상수,스레드.동시성문제,시간지연,이름부여

Bku·2024년 2월 20일

학원 일기

목록 보기
36/67
post-thumbnail

중첩 클래스

중첩클래스 필드에 클래스를 만들수 있고 함수 안에 클래스를 넣을 수도 있다. 여러가지 클래스와 연관을 맺을때는 독립적인 클래스를 만드는 것이 좋지만 한개의 클래스와만 연관을 맺을 경우에는 중첩클래스를 사용하는 것이 유지보수에 도움이 되는 경우가 있다.

  • 1) 멤버 클래스 - 필드에 있는 클래스
  • 2) 로컬 클래스 - 함수에 있는 클래스

중첩 클래스 코드

public class Outer {
    // 중첩클래스 필드에 클래스를 만들수 있고 함수 안에 클래스를 넣을 수도 있다.
    // 1) 멤버 클래스 - 필드에 있는 클래스
    // 2) 로컬 클래스 - 함수에 있는 클래스

    public Outer() {
        System.out.println("Outer 클래스가 생성됨");
    }

    public class Attr{ // 클래스 안의 클래스. 그 중에서도 멤버클래스 그 중에서도 인스턴스 멤버 클래스 (객체를 생성해야 사용가능)
        int field1;

        public Attr() {
            System.out.println("Attr 중첩 클래스가 생성됨");
        }
        void method1(){
        }
    }
    static class Share{ // 클래스 안의 클래스. 그 중에서도 멤버클래스 그 중에서도 전역 멤버 클래스 (객체를 생성하지 않아도 사용가능)
        int field1;
        static int field2;
        void method1(){}
        static void method2(){}
    }
    void method(){
        class Local{ // 함수 안의 클래스 - 로컬 클래스
            public Local() {
                System.out.println("Local 클래스가 생성됨");
            }
            int field;
            void method1(){}
        }
    }
}

이렇게 클래스안에나 함수안에도 클래스를 만들 수 있다.

인스턴스 중첩 클래스 호출

미리 만든 중첩 클래스를 메인 클래스에서 호출해보자.

public class OuterApplication {
    public static void main(String[] args) {
        // 외곽 클래스 : Outer
        Outer outer = new Outer();
        
        // 중첩클래스
        Outer.Attr attr = outer.new Attr();
        
        attr.field1 = 3;
        attr.method1();

    }
}

인스턴스 중첩클래스 호출은 다음과 같이 하면 된다. 객체를 꼭 생성해야함

에러없이 잘 된다.

전역 중첩 클래스 호출

미리 만든 전역 중첩 클래스를 메인 클래스에서 호출해보자.

public class OuterApplication {
    public static void main(String[] args) {
        // 외곽 클래스 : Outer
        Outer outer = new Outer();
         // 전역 중텁 클래스
        Outer.Share.field2=3;
        Outer.Share.method2();
    }
}

전역 중첩클래스 호출은 다음과 같이 하면 된다. 객체를 꼭 생성하지 않아도 된다.

전역 중첩 클래스 호출

미리 만든 로컬 중첩 클래스를 메인 클래스에서 호출해보자.

public class OuterApplication {
    public static void main(String[] args) {
        // 외곽 클래스 : Outer
        Outer outer = new Outer();
       //함수(메소드) 중첩클래스 == 로컬 클래스
        outer.method(); // 함수안에 클래스 자동 생성
    }
}

전역 중첩클래스 호출은 다음과 같이 하면 된다. 객체를 꼭 생성하지 않아도 되고 자동으로 함수안에서 클래스가 생성된다.

중첩 클래스의 제한

중첩 클래스의 전역/일반 객체일때의 접근


전역 클래스의 인스턴스생성은 가능하지만 일반 클래스의 전역 클래스 생성은 불가하다.

중첩클래스의 전역/일반 사용제한 #2


전역은 전역의 속성과 함수만 사용 가능

중첩클래스의 변수 상수

외곽클래스의 매개변수나 속성을 내부클래스에서 사용하면 이것들이 상수로 변경된다. 왜냐하면 외부클래스의 지역변수는 실행이 끝나면 삭제된다. 그런데 내부에서 사라진 변수를 사용할 수 없어지니까 상수로 복사해서 사용한다.

그래서 var의 값을 바꾸면 내부클래스에서 var을 사용하지 못하게 된다.

this 예약어 범위

내부 클래스의 함수에서 내외부 클래스의 속성이나 함수의 이름이 같다면 어떻게 this로 구분할수 있을까?


내부 클래스의 함수에서 그냥 this를 사용하면 내부 클래스의 속성이 사용되고, 외부 클래스.this를 사용하면 외부클래스의 속성을 사용할 수 있다.

중첩 인터페이스

public class Button {
    static interface OnClickListener{
        void onClick();
    }
    OnClickListener listener;
    void setOnClickListener(OnClickListener listener){
        this.listener = listener;
    }
}

인터페이스도 클래스 안에 생성이 가능하다

익명 클래스

익명 클래스

익명클래스에 상속할 부모 클래스

public class Person {
    void wake(){
        System.out.println("7시에 일어납니다.");

    }
}

익명클래스 만들기

public class Anonymous {
    // 이름이 없는 클래스로 특정위치에서 한번만사용하는 일회용 클래스이다.
    //   TODO: 1) 속성(필드) : 아래 부모클래스의 함수를 재정의
        // 여기서 person의 함수를 재사용하고 싶은데 이번에만 사용할거라 상속받기는 귀찮다.
        // 이럴때 익명 클래스를 만들면된다.
        Person person = new Person(){ // 익명 클래스
            @Override
            void wake() {
                System.out.println("6시에 일어남");;
            }
        };
    //   TODO: 2) 함수안에서 사용하기
    void method1() {
    // (지역)변수 : 함수재정의 하고 싶음 => 자식클래스 정의하고 싶지않음
    //  => 익명클래스 사용을 고려
        Person person2 = new Person() {
            void walk() {
                System.out.println("산책합니다.");
            }
            //            함수재정의
            @Override
####             void wake() {
                System.out.println("5시에 일어납니다.");
                walk();
            }
        };
        person2.wake();                // 다형성에 의해 자식클래스의 함수가 실행됨
    }

    //   TODO: 3) 함수의 매개변수로 사용하기
    void method2(Person person) {
        person.wake();
    }


}

person객체를 만들고 바로뒤에 {}만 사용하여 이름없는 클래스를 만들고 그 안에 함수를 재정의해주면된다. 이렇게 하면 익명클래스가 Person을 상속한 것이다.(다형성을 이용한 것)

main클래스

ppublic class PersonApplication {
    public static void main(String[] args) {
        Anonymous anonymous = new Anonymous();
//        익명클래스의 함수 실행
        anonymous.person.wake();
        anonymous.method1();
        anonymous.method2(anonymous.person);
    }
}

결과


익명클래스로 재정의한 함수들이 실행된 것을 알 수있다. 원래 person의 wake를 실행하면 7시에 일어나야하지만, 재정의한 함수가 우선으로 실행되므로 6시에 일어남이 실행된다.

person의 함수를 재정의 해서 사용하고 싶은데, 한번만 사용할 거라 상속하기는 귀찮을때 익명 클래스를 사용할 수있다.

익명 인터페이스

부모 인터페이스

public interface RemoteControl {
    public void turnOn();
    public void turnOff();
}

익명 인터페이스 만들기

public class Anonymous {
    // 1) 속성 : 익명클래스 사용
    RemoteControl remoteControl
            = new RemoteControl() { // 원래 인터페이스 생성자를 만들 수 없지만 익명클래스를 만들경우는 가능하다
        @Override
        public void turnOn() {
            System.out.println("TV를 켭니다");
        }

        @Override
        public void turnOff() {
            System.out.println("TV를 끕니다");
        }
    };
    void method1(){ // 함수 안에서 익명인터페이스
        RemoteControl remoteControl2
                = new RemoteControl() {
            @Override
            public void turnOn() {
                System.out.println("오디오를 켭니다.");
            }

            @Override
            public void turnOff() {
                System.out.println("오디오를 끕니다.");
            }
        };
        remoteControl2.turnOn();
        remoteControl2.turnOff();
    }
    //    3) 함수 매개변수로 익명클래스 사용
    void method2(RemoteControl remoteControl) { // 인터페이스를 매개변수로 받으면 인터페이스는 가진것이 없지만 자식들이 오버라이팅을 해서 자식들의 함수를 실행 해당 함수에서 호출이 가능하다.
        remoteControl.turnOn();
        remoteControl.turnOff();
    }

}

인터페이스는 원래 생성자를 만들 수 없고 자식 클래스를 집어넣는 형식이다. 하지만 익명클래스 생성시 자식 클래스가 아닌 자신의 생성자를 넣고 만들 수 있다.

인터페이스의 함수를 재정의 하고싶지만 한번만 사용하고 싶을때 만들면된다.

main클래스

public class RemoteApplication {
    public static void main(String[] args) {
        Anonymous anonymous = new Anonymous();
        anonymous.remoteControl.turnOn();
        anonymous.method1();
        anonymous.remoteControl.turnOff();
        anonymous.method2(anonymous.remoteControl);

    }
}


결과가 잘 나온다.

익명 클래스의 변수 상수


이것도 중첩클래스와 마찬가지로 변수를 받았을때 상수로 변하게 된다. 이때 변수를 변경해주면 익명 함수에서는 해당 변수를 사용할 수 없게된다.

스레드

  • 프로세스 : 현재 실행중인 프로그램을 프로세스라고한다.
  • 멀티 프로세스 : 프로세스가 두개이상일 때 이걸 멀티 프로세스라고한다.
  • 스레드 : 프로세스 안에서 독립적으로 실행되는 것을 스레드라고한다.
  • 멀티 스레드 : 프로세스 안에서 독립적으로 2개 이상 실행되는 것을 멀티 스레드라고한다.

이렇게 여러가지 스레드가 동시에 일어난다면 코드가 씹히는 일이 생길 수 있다. 출처 https://okky.kr/articles/645810
이렇게 코드가 씹힐 수 있는데 이걸 스레드함수들로 방지할 수 있다.

인터페이스 상속 받아서 스레드 사용

public class BeepTask implements Runnable{
    // 1) 삐소리를 내는 스레드, 2) 화면에 띵 5번 출력하는 스레드


    @Override
    public void run() {
        // 1) 삐 소리내는 스레드
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for (int i = 0; i < 5; i++) {
            toolkit.beep();
            try {
                Thread.sleep(500); // 0.5초 지연
            }catch (Exception e){}
        }
    }
}

Runnable는 스레드가 작업을 실행할 때 사용하는 인터페이스이다. 자식함수에 run을 사용할 수 있게 해준다. 또 Thread에 들어가기위한 타입을 제공해준다.

main클래스

public class BeepTaskApplication {
    public static void main(String[] args) {
        // 1) 삐소리 스레드 출력
        Runnable runnable = new BeepTask();
        Thread thread = new Thread(runnable);
        thread.start(); // run을 실행하는 함수
        // 2) 띵 출력 실행 : 5번
        for (int i = 0; i < 5; i++) {
            System.out.println("띵");
            try {
                Thread.sleep(500);
            }catch (Exception e){}
        }
    }
}

start함수안에서 run함수가 실행되는데 start가 실행되면 스레드가 실행되는 것이다.

띵이 소리와 같이 0.5초 간경으로 같이 나온다. 중요한 것은 같이 나온다는 것이다. 코드가 위에서 아래로 내려오면서 삐소리가 먼저 나고 띵 출력이 나와야하는데 스레드 처리를 해주었기에 두개가 동시에 일어날 수 있다.

스레드 과정

스레드는 start함수에 의해 시작된다. 시작되면 main에서 start이하의 코드와 run함수 안에 있는 코드를 동시에 실행시키게 된다.

Thread클래스 상속하여 스레드

인터페이스가 아니라 Thread라는 함수를 상속받아 thread기능을 사용할 수 있다.

public class BeepThread extends Thread{
    @Override
    public void run() {
        // 1) 삐 소리내는 스레드
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for (int i = 0; i < 5; i++) {
            toolkit.beep();
            try {
                Thread.sleep(500); // 0.5초 지연
            }catch (Exception e){}
        }

    }
}

main클래스

public class BeepApplication {
    public static void main(String[] args) {
        Thread thread = new BeepThread();
          thread.start(); // run을 실행하는 함수, 이걸 실행하면 thread의 코드와 이 코드의 아래 실행문들이 동시에 실행되는 스레드 실행된다.

        for (int i = 0; i < 5; i++) {
            System.out.println("띵");
            try {
                Thread.sleep(500); // 전역함수라서 전역으로만 실행
            }catch (Exception e){}
        }
    }
}

인터페이스로 만들었을때와 같은 결과가 나온다.

스레드실행시 동시성 문제

스레드가 실행되면 여러개의 코드가 동시에 실행된다. 그래서 먼저 출력되야하는 데이터가 출력되기 전에 다른 데이터로 초기화 되면 초기화된 데이터로 출력이 되어 원하는 값을 못 가지게 될 수 있다.

값입력 받고 내보내기

public class Calculator {
    // user1과 user2에서 공통적으로 실행하는 함수가 있는 클래스 : setMemory()
    private int memory;

    public int getMemory() {
        return memory;
    }

    public void setMemory(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(2000);
        }catch (Exception e){}
        // 화면 출력 : memory 값
        System.out.println(this.memory);
    }
}

setter로 memory값을 받고 2초의 지연을 준 다음 이것을 출력하게 하는 setter를 만들었다.

입력받은 값 thread로 만들어주는 클래스 2개 만들기

public class User1 extends Thread {
    private Calculator calculator;

    public void setCalculator(Calculator calculator){
        this.setName("user1"); // 스레드에 이름 주는 함수 user1 스레드가 된다.
        this.calculator = calculator;
    }

    @Override
    public void run() {
        calculator.setMemory(50);
    }
}

setName으로 스레드의 이름을 설정해 줄 수 있다. 확인은 getName으로 하면된다.

public class User2 extends Thread{
    private Calculator calculator;

    public void setCalculator(Calculator calculator) {
        this.calculator = calculator;
        this.setName("user2");
    }

    @Override
    public void run() {
        calculator.setMemory(100); // 이 때 문제가 생길 수 있다.
        // user1과 2가 동일한 함수인 setter함수를 부르는데 값이 다를때 문제가 생길 수 있다.
        // 스레드는 동시에 진행되기에 setter에 값이 50 들어가고 100들어가고 하면 문제가 생김 순서가 뒤죽 박죽일 수 있어서

    }

user1에는 50을 넣고, user2에는 100을 넣었다. run을 통해 thread로 실행할 수 있게 해주었다.

main클래스로 결과 보기

public class CalculatorApplication {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        // 스레드 실행
        User1 user1 = new User1();
        user1.setCalculator(calculator);
        user1.start();

        User2 user2 = new User2();
        user2.setCalculator(calculator);
        user2.start();
    }
}

결과


user1에서 50이 아니라 100이 나온다. 이는 스레드실행되면 코드순서대로 실행이 되는게 아니라 스레드가 있는 코드들은 다 동시에 처리되기때문에 데이터 저장소를 공유한다면 데이터1이 들어오고 출력이 되기전에 데이터 2가 들어와 데이터 1은 출력이 안되게 된다. 그래서 user1d의 50이 들어왔지만 출력되기 전에 user2의 100이 들어오게 되서 결과는 100 100이 되었다.이를 동시성 문제라고 한다.

해결방법

동시에 사용하는 변수나 함수에 synchronized 예약어를 사용해서 작업이 다 끝날때까지 기다리고 그 다음 데이터가 들어오게 해준다. 여기서는 Calculater의 setter함수에 데이터를 같이 저장하므로 이 함수에 넣어주면된다.
Calculator 클래스

public class Calculator {
    // user1과 user2에서 공통적으로 실행하는 함수가 있는 클래스 : setMemory()
    private int memory;

    public int getMemory() {
        return memory;
    }

    public synchronized void setMemory(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(2000);
        }catch (Exception e){}
        // 화면 출력 : memory 값
        System.out.println(this.memory);
    }
}

결과


50이 씹히지 않고 잘 출력된다.

스레드환경을 만들어 여러가지 일을 한번에 처리할 수 있다. 또 여러 함수를 통해 시간지연 등의 처리도 할 수 있다. 하지만 스레드 환경에서는 우리가 예상치 못한 에러와 데이터 씹힘 현상이 잘 발생하기 때문에 예외처리와 synchronized 처리를 잘 해주어야한다.

profile
기억보단 기록

0개의 댓글