본문 바로가기

Java

로아랑 서버구축을 위한 공부 4일차

이것이 자바다(책) Chap11 ~ Chap14의 7까지

 

기억에 남는 부분 메모

1. 에러, 예외 처리

try {
    Class.forName(null);
    System.out.println("존재");
} catch (ClassNotFoundException e) {
    System.out.println(e.toString());
} catch (NullPointerException e) {
    System.out.println(e.toString());
}

 

- try 구문을 통해 예외가 발생할 수 있는 코드를 실행

- catch 구문을 통해 예외 발생시 실행할 코드를 작성

- finally는 성공하든 실패하든 무조건 실행

- 여러 예외가 발생할 수 있는 상황에서는 다중 캐치가 가능하며 가장 먼저 캐치된 예외 구문으로 넘어간다

try {
    Class.forName(null);
    System.out.println("존재");
} catch (ClassNotFoundException | NullPointerException e) {
    System.out.println(e.toString());
}

 

- 여러개의 예외를 하나의 캐치 구문에 작성 가능

try {
    Class.forName(null);
    System.out.println("존재");
} catch (NullPointerException e) {
    System.out.println("예상한 null 에러");
} catch (ClassNotFoundException e) {
    System.out.println("예상한 NotFound 에러");
} catch (Exception e) {
    System.out.println("예상하지 못한 에러 발생");
}

예상되는 특정 에러를 먼저 캐치해 구체적으로 코드를 작성하고

마지막 캐치 구문에 예상하지 못한 에러에 대한 대응책을 작성하는 식으로 사용 가능

혹은 에러에 대한 처리가 모두 같다면 아예 Exeption만 사용 가능

public static void printLength(String str) throws Exception {
    try(MyResource res = new MyResource(null)) {
        System.out.println(res.read1().length());
    }
}

이렇게 하면 예외 구분 안하고 모든 예외를 다 던질 수 있음

 

2. 리소스 자동 닫기

- 리소스를 열었을 때 실행이 완료되거나 에러 캐치 됐을 때 리소스를 닫아주는 코드를 작성해야함

- Java7 이후에는 자동 닫기 기능이 생겼는데 이는 리소스를 읽는 객체가 AutoCloseable을 구현하면 됨

public class MyResource implements AutoCloseable {
    private String name;
    MyResource(String name) {
        this.name = name;
    }

    public String read1() {
        return this.name;
    }

    @Override
    public void close() throws Exception {
        System.out.println("닫아벌여~");
    }
}

- 내부에 읽는 기능을 작성하고 close라는 메서드를 Override하고 닫는 기능을 작성하면 된다

try(MyResource res = new MyResource(null)) {
    System.out.println(res.read1().length());
} catch (Exception e) {
    System.out.println(e.toString());
}

 

그럼 사용하는 곳에서 이렇게 try뒤 괄호에 작성해서 사용하면 성공하든 실패하든 위에서 오버라이드 한 close메서드가 호출된다.

 

3. 사용자 정의 에러

public class TestException extends Exception {
    TestException() {

    }

    TestException(String message) {
        super(message);
    }
}

이렇게 Exeption을 상속받아 객체를 만들고

try(MyResource res = new MyResource(null)) {
    System.out.println(res.read1().length());
} catch (Exception e) {
    throw new TestException("니나노 에러 발생~");
}

캐치된 곳에서 해당 Exception을 throw해주면 됨

 

4. 동등 비교 하기 위해서는 hashCode비교 -> equals()로 비교를 하는데 이 때 그 객체의 hashCode와 equals를 재정의 해서 상황에 맞는 식을 만들어야함

public class Student {
    private int no;
    private String name;
    Student(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public String getName() {
        return name;
    }

    @Override
    public int hashCode() {
        int hashCode = no + name.hashCode();
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Student target) {
            if (no == target.getNo() && name.equals(target.getName())) {
                return true;
            }
        }

        return false;
    }
}

 

근데 복잡한 기능이 없는 그냥DTO 같은 경우는 class말고 record 객체로 만들면

public record Student(int no, String name) {}

이렇게 간단하게 만들 수 있음

-> hashCode, equals등을 재정의한 코드가 자동으로 추가됨

 

5. lombok

근데 4번 하는게 귀찮다

그러면 lombok 라이브러리 설치해서

class 객체 앞에 @Data만 붙여주면 다해줌

 

6. Math

- Math.abs() : 절대값

- Math.ceil() : 올림값

- Math.floor() : 버림값

- Math.max() : 최대값(두 값중에)

- Math.min() : 최소값(두 값 중에

- Math.random() : 랜덤값

- Math.round() : 반올림값 -> 얘는 근데 특이하게 리턴값이 long임

 

7. Date, LocalDate

Date now = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd hh:mm:ss");
System.out.println(formatter.format(now));

 

근데 LocalDate가 더 쓰기 쉽고 Date에 사용하는 메서드들(Ex. getDay)이 대부분 deprecated됨

 

LocalDate last = LocalDate.of(2024, 12, 31);
LocalDate now = LocalDate.now();
System.out.println(now.until(last, ChronoUnit.DAYS));

첫 줄, 두 번째 줄 처럼 날짜 생성하고 마지막 줄 처럼 비교 가능

 

9. DecimalFormat

DecimalFormat df = new DecimalFormat("#,###");
int a = 10000;
System.out.println(df.format(a));

 

10. 제네릭

public class Box <T>{
    private T contents;
    Box(T contents) {
        this.contents = contents;
    }
    
    public T getContents() {
        return this.contents;
    }
}

 

- 제네릭 선언

Box<String> box1 = new Box<>("안녕");
String box1Contents = box1.getContents();

Box<Integer> box2 = new Box<>(123);
int box2Contents = box2.getContents();

System.out.println(box1Contents);
System.out.println(box2Contents);

생성 시점에 제네릭 전달해서 선언하면 바로 그 타입으로 쓸 수 있음

 

- interface, record, generic 사용한 예시 코드

public interface Rentable<T> {
    T rent();
}

record Home(String name, int price) {}
class HomeAgency implements Rentable<Home> {
    @Override
    public Home rent() {
        return new Home("집", 500000);
    }
}

record Car(String model, int price) {}

class CarAgency implements Rentable<Car> {
    @Override
    public Car rent() {
        return new Car("아반떼", 100000);
    }
}

 

- 제네릭 타입 제한

public class limit <T extends Number> {
    T num;
    limit(T num) {
        this.num = num;
    }
}

extends를 통해 타입을 제한 할 수 있음(즉, Number의 자식 타입만 T로 들어올 수 있다)

-> Integer, Long, Double 다 되지만 String은 안됨

 

11. 스레드

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("다른 스레드가 실행할 코드");
    }
});

thread.start();

 

이런식으로 다른 스레드로 보내서 병렬적으로 처리

Java또한 메인스레드는 한번에 하나의 일밖에 처리를 못하기 때문에 비프음을 내면서 프린트를 찍으려면 둘 중 하나의 작업은 다른 스레드에서 실행해야함

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        for(int i = 0; i < 5; i++) {
            System.out.println("삐");
            try {
                Thread.sleep(500);
            } catch (Exception e) {
                System.out.println(e.toString());
            }
        }
    }
});
thread.setName("thread - print(삐)");
thread.start();

Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i = 0; i < 5; i++) {
    toolkit.beep();
    try {
        Thread.sleep(500);
    } catch (Exception e) {
        System.out.println(e.toString());
    }
}

- 예시는 "삐" 를 프린트 하는 구문을 다른 스레드에서 실행 시키면서 비프음을 메인스레드에서 내는 코드

- thread.setName을 통해 해당 스레드의 이름을 설정하고 디버깅 과정에서 어떤 스레드인지 확인 가능

 

12 join()

SumThread a = new SumThread();
a.start();
try {
    a.join();
} catch (Exception e) {}
System.out.println(a.getSum());

SumThread의 run은 1~100까지 더하는 기능인데 이걸 바로 프린트 하면 더하기가 진행되기 전 값인 0이 프린트됨

join() 해당 스레드의 작업이 완료되기까지 기다리는 역할을 하며 SumThread a의 작업이 완료되기까지 기다린 후 프린트를 하면 정상값 출력됨

 

13. synchronized

- 시나리오

User1과 User2가 검색을 하는데 둘 다 검색을 하고 검색 직후에 count가 몇 인지 호출을 했다

상식적으로

User1이 검색 -> count 호출시 1개

User2가 검색 -> count 호출시 2개 가 나와야 하지만

count를 가져오는 기능이 좀 오래걸려서 둘 다 2개가 나오는 현상이 발생했다

이런 문제를 해결하기 위해 동기화 작업을 해야하는데

간단하게 메서드에 synchronized 키워드를 붙여주면 된다

그럼 해당 객체에 여러개의 스레드가 synchronized 키워드가 붙은 메서드에 접근을 하게 된다면 이전 synchronized 메서드의 작업이 끝나기 전까지 synchronized키워드가 붙은 어떤 메서드도 실행하지 않고 기다린다.

public class SearchCounter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        try {
            Thread.sleep(500);
        return this.count;
        } catch (Exception e) {
            return this.count;
        }
    }
}

class Searcher {
    public static void main(String[] args) {
        SearchCounter counter = new SearchCounter();

        //User1
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.increment();
                System.out.println("User1이 요청한 count : " + counter.getCount());
            }
        });
        threadA.start();

        //User2
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.increment();
                System.out.println("User2가 요청한 count : " + counter.getCount());
            }
        });

        threadB.start();
    }
}