Timer / TimerTask 클래스

Timer클래스란?

말 그대로 정말 타이머(Timer)의 역할을 수행하는 클래스이다.

schedule이라는 내부 메서드를 통해 특성시간, 기간마다 인자로 받은 TimerTask인스턴스의 run메서드 로직을 수행하여 특정 시간마다 반복적인 로직을 실행하게끔하거나 하는 식으로 원하는 로직을 수행시킬 수 있다.

TimerTask 클래스란?

Timer클래스의 schedule함수로 작업 스케줄링을 관리할 때 들어가는 추상 클래스로,

Runnable클래스에서 run함수만 따와 별도의 스레드로 함수를 구동시키는 원리인 것같다.

사용 예제

우선 Timer클래스에 들어가면 생성자부터 schedule함수의 재정의에 따라 각각의 로직을 가지고있는데, 그것은 생략하겠다.

해당 예제에서 다루는 것은 Timer클래스의 생성, TimerTask의 정의, 이후 schedule에 함수에 들어간 TimerTask의 정상 작동 확인에 대해 다루겠다.

예제 1

ackage org.example;

import java.util.Timer;
import java.util.TimerTask;

//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
    public static void main(String[] args) {

        Timer timer = new Timer(); // 타이머 객체생성
        TimerTask timerTask = new TimerTask() { 
        // 추상 클래스이므로 인스턴스 생성시에 가진 메서드를 구현해야함 
            @Override
            public void run() {
                System.out.println("시간이 지났습니다~"); // 작동 로직
                timer.cancel(); // 타이머 종료
            }
        };
        
        // 구동 후 5초 뒤에 timerTask의 로직을 구동한다.
        timer.schedule(timerTask,5000); 
    }
}

아주 간단한 예제로 앱을 실행하고 5초뒤에 프린팅이 찍히게끔 되어있다.

TimerTask의 로직에 timer를 종료하는 cancel()를 사용하여 한번만 구동하게끔해놓았다.

왼쪽의 실행 시간을 보면 5초 후 구동이 완료되었고, 이후에 추가로 작동하지않고 종료됨을 알 수 있다.

그럼 타이머는 객체생성부터 작동될까, schedule()부터 구동될까?

객체 생성시부터 시간이 계산되는 것 같다. Timer의 클래스를 들어가보면

    public Timer() {
        this("Timer-" + serialNumber());
    }

    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

이렇게 두개의 생성자가 있는데, 위 코드에서 기본생성자로 생성시 아래있는 타이머 생성자가 실행되면서 스레드가 시작됨으로 봤을때 생성자체가 실행된다고 . 볼수 있다.

특정 로직에만 실행시키기

그러면 특정 로직을 탈 때에 특정시간마다 실행을 시키려면, TimerTask만 미리 정의하거나 임의의 커스텀 클래스로 제작하여 빼고, 특정 로직을 탈때 타이머 인스턴스를 생성하면 되겠다.

아래는 해당 예제에 대한 코드이다.

우선 TimerTask를 커스터마이징했다.

public class CustomTimerTask extends TimerTask {
    Timer timer;

    public CustomTimerTask(Timer timer){
        this.timer = timer;
    }

    @Override
    public void run() {
        System.out.println("타이머 로직이 실행됩니다.");
        timer.cancel();
    }
}
public class Main {
    public static void main(String[] args) {
        // 특정 로직을 실행하는지에 대한 bool 값
        boolean isTimerExecute = false;

        if (isTimerExecute) {
            Timer timer = new Timer();
            TimerTask timerTask = new CustomTimerTask(timer);
            timer.schedule(timerTask,5000);
        }else{
            System.out.println("타이머를 타야하는 로직이 아닙니다.");
        }

        
    }
}

이렇게 한 후 실행해보겠다.

특정 로직이 아니기에 불과 224ms만에 실행되었고, 그대로 앱은 종료되었다. isTimerExecute 변수값을 true로 바꾼 후, 타이머 로직이 실행되는지 테스트해보자

기존처럼 5초뒤에 TimerTask에 실행이 한번 되었고, 추가 작동없이 타이머가 종료됨을 알 수 있다.

이렇게 Timer클래스와 TimerTask에 대해 알아보았다. 해당 클래스에 대한 역할은 Spring Framework같은 경우 스프링 스케줄러나 스프링 배치등 여러 대체 구현 기능이 되어있지만, 간단한 경우에는 해당 클래스로 커스터마이징을 해서 사용해도될 거 같다.

 

Spring boot란?

- Spring Boot란 Spring Framework 프로젝트 생성시 처음하면 매우매우 복잡하고 절대로 쉽게 할 수 없는 초기 설정 및 라이브러리 세팅을 간단하게 만들어주는 프레임워크(FrameWork)입니다.

 

- 학원에서 STS를 통해 스프링 레거시 프로젝트(Spring Legacy Project)를 할 때 매우 오래걸린 초기세팅이 여기선 단 몇번의 클릭으로 끝나는 것을 보고 많이 놀랐던 것 같습니다. 이제 레거시 프로젝트와 스프링 부트 프로젝트의 세팅을 비교해보겠습니다.

 

Spring Boot 프로젝트 생성

우선 IDE는 스프링 부트(Spring boot)를 다루실 때 많이들 사용하시는 IntelliJ를 기준으로 말씀드리겠습니다.

제가 아는 스프링 부트 생성 방법은 두가지로

- intelliJ에서 직접 스프링 부트 프로젝트 생성하기

- Spring.start.io에서 만들어 IntelliJ에서 임포트하기

입니다. IntelliJ에서 생성하는 방법 먼저 보여드리겠습니다.

우선 프로젝트 새로 만들기에서 프로젝트를 클릭합니다.

프로젝트.. 클릭

그럼 다음 화면이 나타나게 되는데 왼쪽 제너레이터(Generator)에서 Spring Initializr를 누르면 됩니다.

스프링 이니셜라이즈 선택

그러면 해당 화면에서 프로젝트명, 저장 경로, 사용언어 및 빌드툴,JDK버젼을 선택하고 다음을 눌러줍니다.
빌드 툴은 보통 Gradle을 추천드리는데, Maven보다 훨씬 빌드속도가 빠르고 잠시 후 보여드리겠지만 라이브러리 추가시의 가독성 역시 훨씬 뛰어납니다.

각종 옵션 추가

다음을 누르면 아래의 사진 처럼 화면이 나옵니다. 초기에 추가해놓을 옵션들과 부트의 버젼을 선택합니다.

이 부분에서 기본 Spring과 많은 차이가납니다. MVNRepository에서 하나하나 찾고 맞는 버젼에 따라 가져와 입력하는 것이 아닌, 기능만 선택하면 해당 라이브러리를 자동으로 세팅해주는 것입니다.

부트 버젼과 기능 선택

대표적으로 몇가지 말씀드리면 Developer Tools는 개발의 편의성을 위한 툴을 의미합니다. 대표적으로 Lombok은 엔티티등을 작성할 때 어노테이션 하나만으로 수많은 반복코드를 작성하지 않아도 되도록해줍니다.

Web은 말 그대로 Web에 관한 기능들입니다. SpringWeb부터 Spring session등.... 세부적인 것들은 검색하셔서 필요한 기능을 추가하시면 됩니다.

그 이외에도 view단과 서버단을 연결하는 Template Engines, 보안 기능의 Security, 관계형 DB 관련 기능인 SQL등등 필요한 것들을 이 화면에서 추가한 뒤 생성을 누르면 됩니다. 저는 해당 글을 위해 기본적인 것들만 추가한 사진을 보여드리겠습니다.

추가한 기능은 오른쪽 하단에 나옵니다.

보시면 Spring Web과 Lombok, MySQL과 통신할때 사용하는 MySQL Driver그리고 Spring Data JPA등 6개의 기능을 넣었습니다. 그럼 생성해볼까요?

프로젝트가 벌써 생성됬다고?!

보시면 빠르게 프로젝트가 생성되었습니다. 이제 Maven의 Pom.xml과 같은 기능인 build.gradle에서 정상적으로 기능이 추가가 되었는지 확인하겠습니다.

정말로 추가가되다니...

보시면 JPA와 JDBC, SpringWeb,lombok등 제가 추가한 기능들이 모두 추가되어 빌드까지 되어있는 상태를 확인할 수 있습니다. 또한 Gradle은 Maven과 다르게 하나의 기능당 거의 1줄만 사용하고있는데요. 빌드 속도도 물론 빠르지만 가독성도 우수해 기능이 많아져도 비교적 관리가 편할 것입니다.

 

이번엔 Spring.io에서 생성하는 것을 다루겠습니다. import하는 것만 다르기 때문에 빠르게 짚고 넘어가겠습니다.

우선 해당 링크에 접속합니다.

https://start.spring.io/

그러면 아래 사진과 같은 화면이 나옵니다.

부트 버젼, 언어, 빌드툴과 프로젝트 명을 선택한 후 추가할 라이브러리는 Dependencies옆 버튼을 클릭합니다.

 

Dependencies 추가

여기서 IntelliJ에서 추가했던 것처럼 lombok, Spring DevTools등을 추가하신 후

GENERATE 버튼을 클릭하면 해당 프로젝트가 다운됩니다. 이후 다운 받은 파일을 IDE에서 임포트해서 열면 동일하게 생성된 프로젝트를 확인할 수 있습니다.

 

이렇게 오늘은 Spring Boot 프로젝트를 생성하는 법을 다뤄보았는데요, 많이 편리해진건 사실이지만 최근에 부트 프로젝트를 이리저리 건드려보니 귀찮더라도 결국 부딪히면서 Spring이나 JAVA에 대한 이해도를 높인 후 편의기능을 사용하는 것이 맞다는 생각이 듭니다. 이상으로 포스팅을 마치겠습니다.

이 문제는 입력에 4<n<1000 범위 안에 있는 4의 배수가 입력으로 들어옵니다. 또한 출력은 다음 사진과 같이 입력의 수를 4로 나누었을때 나오는 몫만큼 long을 출력하고 마지막에 int를 붙여야합니다.

출력 예시 -- 백준

그럼 우선 입력을 받기전 어떤 식으로 문제를 풀어야할지 생각해보아야합니다.

첫 번째로 입력된 값을 저장하는 변수가 있어야합니다.

 

두 번째로 for문의 작동 횟수를 조절해주는 값을 구해서 변수로 이용하면 편리할 것입니다.

세 번째로 출력할 값을 저장하는 문자열 변수가 존재해야합니다.

네 번째로 long이라는 값이 반복되기 때문에 for문을 이용하여 변수에 반복적으로 값을 삽입합니다.

 

순서대로 한번 작문해보겠습니다.

 

첫 번째로 입력값을 받고 그 값을 변수에 저장하겠습니다.

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);// 값을 받는 객체
int n = sc.nextInt();//바이트의 값이 들어온다;
}

스캐너 객체를 통해 몇 바이트를 계산할 것인지 정수형 데이터로 입력을 받고, 그 값을 정수형 변수에 저장해놓습니다.

 

두 번째로 입력한 값을 4로 나누어 long 을 몇번 출력해야 하는지 숫자를 계산하여 변수에 저장합니다. 이 변수는 후에 반복(for)문에서 사용 될 것입니다.

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);// 값을 받는 객체
int n = sc.nextInt();//바이트의 값이 들어온다;

int num = n / 4;// long을 찍어야하는 횟수를 계산하여 저장한다.
}

int num을 통해 n에 값이 20이 들어온다면 5가 들어올 것입니다. 그럼 이 변수를 for문에 삽입하여 for문이 5번 작동하도록 할 수 있을 것입니다.

 

세 번째로 출력해야하는 문자열 값을 저장하는 변수를 만들어줍니다.

import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);// 값을 받는 객체
int n = sc.nextInt();//바이트의 값이 들어온다;

int num = n / 4;// long을 찍어야하는 횟수를 계산하여 저장한다.

String word = ""; // 문장을 저장하는 변수를 가진다.
}

String word를 선언하고 우선 공백을 값으로 지정해줍니다. 이 변수가 마지막에 정답으로 출력될 것입니다.

 

네 번째로 for문을 통해 계산된 횟수만큼 word변수에 long을 추가합니다.

import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);// 값을 받는 객체
int n = sc.nextInt();//바이트의 값이 들어온다;

int num = n / 4;// long을 찍어야하는 횟수를 계산하여 저장한다.

String word = ""; // 문장을 저장하는 변수를 가진다.

for (int i = 0; i < num; i++) {
word += "long ";// for문을 돌면서 4비트마다 long을 하나씩 저장한다.
}
}

해당 for문을 통해 num에 저장된 수만큼 long이 word변수에 추가되어 저장될 것입니다. 이제 거의 마지막 단계입니다.

하지만 위 출력 예시를 잘 보시면 long과 long, long과 int사이엔 모두 띄어쓰기가 한칸 씩 존재합니다. 따라서 for문에서 값을 삽입할 때 long 뒤에 반드시 띄어쓰기 한칸을 넣어주셔야 요구하는 답이 나오게됩니다.

 

이젠 마지막으로 for문뒤에 int를 삽입하고 그 값을 출력합니다.

import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);// 값을 받는 객체
int n = sc.nextInt();//바이트의 값이 들어온다;

int num = n / 4;// long을 찍어야하는 횟수를 계산하여 저장한다.

String word = ""; // 문장을 저장하는 변수를 가진다.

for (int i = 0; i < num; i++) {
word += "long ";// for문을 돌면서 4비트마다 long을 하나씩 저장한다.
}

word += "int"; //문장의 마지막에 int를 붙인다.
System.out.println(word);//제대로 출력이 되는지 확인한다.
      }
}

한번 콘솔에서 20을 찍고 long이 5번 출력되고 int가 정확하게 출력되는지 테스트해보겠습니다.

테스트 콘솔 창

보시면 값을 20을 입력했을 때 long이 5번 출력되고, 마지막에 int가 정상적으로 출력되는 것을 알 수 있습니다.

 

부연설명

20의 값을 콘솔에 입력하면 변수 n에는 20이 저장되고, 변수 num에는 20을 4로 나눈 몫인 5가 저장됩니다.

그럼 for문에서 i는 0부터 num보다 작을 때까지만 실행하게 되어있기 때문에 0, 1, 2, 3, 4 총 5회가 동작되면서 

문자열 변수 word에는 "long "이 총 5번 더해지고 for문의 동작이 종료됩니다. 이후 변수 word에 int가 마지막에 출력됨으로서 모든 값을 다 구한 이후 System.out.println을 통해 출력하여 답변을 제출합니다.

 

위 코드를 제출하여 정답을 맞춘 사진입니다.

아마 buffer를 이용하면 좀 더 빠르게 방법을 구현할 수 있다고 하는데 다음에 한번 해봐야겠습니다.

모자란 글 봐주셔서 감사합니다.

'백준' 카테고리의 다른 글

백준 2439번 - 별 찍기-2  (0) 2023.02.02
백준 11022번 A+B-8  (0) 2023.01.03

람다식이란?

메서드를 하나의 식으로 표현한 것으로 실제 로직을 정해두지 않은 익명 객체의 함수를 객체의 선언과 동시에 식을 세우면서 가독성을 높이고, 그때마다 필요한 로직으로 좀 더 자유로운 코딩을 가능하게 해줍니다.

 

익명 객체?

보통 JS나 다른문법에서는 lambda식을 익명 함수라 칭하는 경우가 많습니다. 하지만 익명 객체라 뜻하는 것은 JAVA에서는 메서드가 단독 존재하는 것이 아니라, 항상 어떤 객체안에 메서드가 존재하기때문에 익명함수를 만들기 위해서는 익명 클래스를 먼저 선언한 후 클래스 안에 익명 함수를 선언해야 하기 때문입니다.

 

예제

우선 기존에 존재하는 메서드를 람다식으로 표현하는 예제를 먼저 보겠습니다.

public class lambda {
    public static void main(String[] args) {
	//배열
	int[] a = {1,2,3,4,5};
        
        // 람다식 표현
        Arrays.stream(a).forEach( item -> System.out.println(item));

        System.out.println();
        
        // 기존 표현
        for(int item : a){
            System.out.println(item);
        }
    }
}

위 코드에서 출력하는 결과는 두 식 모두 같습니다. 지금 당장은 수행하는 로직이 한줄밖에 되지않아 차이가 없어보이지만, 두줄에서 세줄만 되어도 가독성 차이가 심해집니다. 이제 익명 클래스를 선언해 보겠습니다.

 

익명 클래스

익명 클래스는 함수형 인터페이스를 통해 설명 드리겠습니다. 함수형 인터페이스는 @FunctionalInterface 어노테이션을 통해 선언하며, 하나의 익명함수만을 가질 수 있습니다.

 

우선 첫 번째로 새로운 인터페이스를 생성합니다.

인터페이스 생성

생성한 인터페이스에 @FunctionalInterface 어노테이션을 붙입니다.

빨간줄이 뜨는 이유는 아직 타깃 메서드를 작성하지 않아서 입니다.

이제 사용할 메서드를 선언합니다.

에러가 사라졌다!

 

하지만 위에서 설명했듯이 메서드를 두개 이상 선언하면 에러가 발생합니다.

두개이상 작성하면 에러!

 

이제 다시 calc메서드만 남기고 다른 클래스에서 사용해보겠습니다.

public class lambda {
    public static void main(String[] args) {
        int x = 10, y = 11;
        
        // 익명 클래스 선언 및 람다식을 통한 재정의
        NewLambda lambda = (int a,int b) -> { return a+b;};

		// 익명함수 작동
        int c = lambda.calc(x,y);
        System.out.println(c);
    }
}

위 처럼 익명 클래스는 객체를 선언 할 때마다 수행 로직을 변경하여 사용할 수 있습니다. 또한 람다식을 사용하여 좀 더 간결한 코드로 보이도록 할 수 있었습니다.

 

익명 객체를 사용하면 여러 객체를 선언함으로써 인터페이스처럼 상황에 맞는 로직을 수행할 수 있습니다. 이번엔 빼기연산을 수행하는 메서드를 추가하겠습니다.

public class lambda {
    public static void main(String[] args) {
        int x = 10, y = 11;
        NewLambda lambda =(int a,int b) -> {return a+b;};
	//새로운 익명 객체 추가
        NewLambda minus = (int a, int b) -> {return a-b;};
	// -연산 메서드 실행 후 리턴값 반환
        int min = minus.calc(y,x);

        System.out.println(min);
    }
}

이 처럼 객체를 선언함과 동시에 익명함수를 새롭게 정의하여 다른 로직을 객체별로 수행하게 만들었습니다. 하지만 너무 남발하면 오히려 가독성이 떨어지는 부분이 생기므로 주의해야합니다.

 

오늘 람다식에 대하여 작성하려했지만 아직 많은 부분이 부족하여 글을 쓰다보니 익명 클래스에 대한 내용이 더 많은 것 같습니다. 읽어주셔서 감사합니다.

 

 

-- 07/14 익명클래스의 설명에 함수형인터페이스와 설명이 섞어 혼동되게 적혔습니다. 수정 완료한 상태이고 잘못된 정보를 공유드려 죄송합니다.

+ Recent posts