본문 바로가기

생각정리/항해99

[프로그래밍 기초] Java문법 3일차

Java문법 종합반 4, 5 주차


예외처리

컴파일 에러 - 실행 자체가 안된다. 문법상으로 틀렸든 오타가 있든 수정을 해서 해결해야 된다.

런타임 에러 - 프로그램이 작동 중에 나오는 에러로 예외처리를 통해 대처한다.

throws를 통해 여러 개의 예외처리도 가능하다.

예외처리를 처음부터 확인하기 어려운데 실행하면서 에러가 발생하면 에러 코드를 보고 해당 에러가 왜 일어났는지와 에러에 대한 예외처리 클래스도 확인할 수 있다.

// 예외처리 클래스를 생성
public class OurBadException extends Exception { // 상위 예외처리에서 상속받는다.
    public OurBadException () { // 생성자
        super("위험한 행동을 하면 예외처리를 꼭 해야함"); 
    }
}

// 클래스를 통해서 체크를 한다.
public class OurClass {
    private final boolean just = true;

    public void thisMethodIsDangerous () throws OurBadException { // 만든 예외 처리 지정
        if (just) {
            throw new OurBadException(); // 확인을 위해 강제로 예외처리를 던저준다.
        }
    }
}

// try catch문을 통한 예외처리
public class StudyException {
    public static void main(String[] args) {
        OurClass ourClass = new OurClass();
		
        // 즉 try 블럭 내의 구문을 실행하다가 예외가 발생하면
        // 예외가 발생한 줄에서 바로 코드 실행을 멈추고
        // 여기 있는 catch 블럭 내의 코드가 실행됩니다.

        try { 
            // 1. 위험한 요소가 있는 부분을 try문에 넣어서 실행
            ourClass.thisMethodIsDangerous(); // 에러 불러오기
            System.out.println("트라이 끝"); // 위에서 에러가 나므로 실행안됨.
        } catch (OurBadException e) { // 해당 발생에러 
            // 2. 예외가 발생하면, catch문에서 대처를 한다.
            System.out.println(e.getMessage()); // 에러에 대한 메세지 출력 "위험한 행동을 하면 예외처리를 꼭 해야함"
            System.out.println("캐치 끝");
        } finally {
            // 3. 예외의 발생 여부와 상관없이, 실행시켜야 하는 코드가 들어갑니다.
            // 무조건 실행되는 코드가 들어가는 블럭입니다.
            System.out.println("우리는 방금 예외를 handling 했습니다!");
        }
        System.out.println("문장 끝");

    }
}

// 예외처리 회피
public class StudyException {
    public static void main(String[] args) throws OurBadException { // throws를 이용해 회피 가능 
        OurClass ourClass = new OurClass();

        ourClass.thisMethodIsDangerous();
    }
}

제네릭(Generic)

데이터 타입을 미리 임시로 지정을 해두고 사용하고 싶은 타입을 넣어서 사용해서 활용에 있어 자유롭다.

클래스와 메서드에만 사용이 가능하다.

멤버변수에 static을 사용할 수 없다.

타입 변수는 래퍼클래스 및 클래스만 올 수 있다.

와일드카드를 이용해 이름에 제한을 두지 않고 표현이 가능하다.

// 기본적 형태
public class Generic<T> {
    private T t;
    public T get() {return this.t;}
    public void set(T t) {this.t = t;}

Generic<String> stringGeneric = new Generic<>();

// 다수의 타입변수를 사용 할 수 있다.
public class Generic<T, U, E> {
    public E multiTypeMethod(T t, U u) { ... }
}

Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);

// 제네릭의 활용
class LandAnimal { public void crying() { System.out.println("육지동물"); } }
class Cat extends LandAnimal { public void crying() { System.out.println("냐옹냐옹"); } }
class Dog extends LandAnimal { public void crying() { System.out.println("멍멍"); } }
class Sparrow { public void crying() { System.out.println("짹짹"); } }

class AnimalList<T extends LandAnimal> { // extends 키워드를 이용해 특정 타입만 사용하도록 제한 가능.
    ArrayList<T> al = new ArrayList<T>();

    void add(T animal) { al.add(animal); }
    T get(int index) { return al.get(index); }
    boolean remove(T animal) { return al.remove(animal); }
    int size() { return al.size(); }
}

public class Generic01 {
    public static void main(String[] args) {
        AnimalList<LandAnimal> landAnimal = new AnimalList<>(); // Java SE 7부터 생략가능함.

        landAnimal.add(new LandAnimal());
        landAnimal.add(new Cat());
        landAnimal.add(new Dog());

        // landAnimal.add(new Sparrow()); // 오류가 발생함.

        for (int i = 0; i < landAnimal.size() ; i++) {
            landAnimal.get(i).crying();
        }
    }
}

// 와일드카드
<?>           // 타입 변수에 모든 타입을 사용할 수 있음.
<? extends T> // T 타입과 T 타입을 상속받는 자손 클래스 타입만을 사용할 수 있음.
<? super T>   // T 타입과 T 타입이 상속받은 조상 클래스 타입만을 사용할 수 있음.

프로세스와 스레드

프로세스(process)란 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것

스레드(thread)란 프로세스 내에서 실제로 작업을 수행하는 주체

스레드를 생성하는 방법

  • Runnable 인터페이스를 구현, Thread 클래스를 상속, main에 람다식으로 바로 적용
  • run() 메서드를 작성해 작업을 추가

멀티 스레드(multi thread) - 둘 이상의 스레드가 동시에 작업을 수행하는 것

데몬 스레드(deamon thread) - 보조적인 역할을 하는 스레드로 일반 스레드가 모두 종료되면 데몬 스레드의 역할이 남아 있어도 자동으로 종료된다.

다른 여러 기능들은 한번 다시 확인해 보면서 정리를 해야겠다.

스레드의 상태

// Thread 상속
public class ThreadWithClass extends Thread{
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName()); // 현재 실행 중인 스레드의 이름을 반환
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// Runnable 구현
public class ThreadWithRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()); // 현재 실행 중인 스레드의 이름을 반환
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 스레드 사용
public class Main {
    public static void main(String[] args) {
        ThreadWithClass thread1 = new ThreadWithClass();
        Thread thread2 = new Thread(new ThreadWithRunnable());

        thread1.start();
        thread2.start();
		
        // 람다식을 이용한 스레드 구현
        Runnable task = () -> {
            try {
                for (int i = 0; i < 10; i++) {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread3 = new Thread(task, "Thread3");
        thread3.start();
    }
}

람다

람다(lambda)는 익명 함수를 의미한다.

해당 인터페이스의 메서드가 1개 이상이면 람다 함수를 사용할 수 없다.

그래서 람다 함수로 사용할 인터페이스는 @FunctionalInterface 어노테이션을 사용해서 2개 이상의 메서드를 가지 못하도록 만들어 주는 것이 좋다고 한다.

interface Calculator {
    int sum(int a, int b);
}

class MyCalculator implements Calculator {
    public int sum(int a, int b) {
        return a+b;
    }
}

public class Sample {
    public static void main(String[] args) {
        MyCalculator mc = new MyCalculator();  // 일반적인 코드 사용
        // (매개변수목록) -> { 함수몸체 }
        //Calculator mc = (int a, int b) -> a +b;  // 람다를 적용한 코드
        //Calculator mc = Integer::sum; // 메서드를 사용할 때는 :: 기호로 축약해서 사용이 가능하다
        int result = mc.sum(3, 4);
        System.out.println(result);  // 7 출력
    }
}

스트림

원본 데이터를 변경하지 않는다.

일회성으로 작동이 된다.

스트림을 생성 -> 스트림 변환 -> 스트림 최종 값연산

import java.util.Arrays;
import java.util.Comparator;

public class Sample {
    public static void main(String[] args) {
        int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
        int[] result = Arrays.stream(data)  // IntStream을 생성한다.
                .boxed()  // IntStream을 Stream<Integer>로 변경한다.
                .filter((a) -> a % 2 == 0)  //  짝수만 뽑아낸다.
                .distinct()  // 중복을 제거한다.
                .sorted(Comparator.reverseOrder())  // 역순으로 정렬한다.
                .mapToInt(Integer::intValue)  // Stream<Integer>를 IntStream으로 변경한다.
                .toArray()  // int[] 배열로 반환한다.
                ;
    }
}

4,5 주차 내용들은 복잡한 부분들이 많아서 다른 강의도 보면서 좀 더 익숙해져야 할 것 같다.