카카오 테크 캠퍼스 4주차

boseung·2023년 5월 6일
0

중첩 클래스(Nested Class)

클래스 간의 중첩 관계를 가진 클래스

  • 외부 클래스와 밀접한 관련을 가지고 있음

Nested Classes

내부 클래스(Inner Class)

  • 내부 클래스로서 일반 클래스 내부에 생성
  • Non Static Nested Class로 불리기도 한다
  • 클래스 생성 방식
public class Test {
    public static void main(String[] args) {
        Outer 변수이름1 = new Outer();
        Outer.Inner 변수이름2 = 변수이름1.new Inner();
    }
}
  • 내부 클래스는 외부 클래스의 자원을 직접 사용할 수 있다
  • 인스턴스 생성을 통해 내부 클래스가 외부 참조하는 방식으로 생성되므로 내부 클래스에서 static을 사용할 수 없다(static은 제일 처음 한번 만들어지는데 인스턴스 생성은 나중에 되므로)
class Outer{
    private int num = 10;
    static int sNum = 10;
    class Inner{
        int inNum = 100;
        //static int sInNum = 10; 불가능
        void inTest(){
            System.out.println(num);
            System.out.println(sNum);
            System.out.println(inNum);
//            System.out.println(sInNum);
        }
        //static void inMethod(){} 불가능
    }
}

정적 내부 클래스(Static Inner Class)

public class Test {
    public static void main(String[] args) {
        Outer.staticInner inter = new Outer.staticInner();
    }
}
  • static의 속성 그대로 외부 클래스의 인스턴스 변수 사용 불가능, 내부 클래스의 정적 메서드에서 인스턴스 변수 사용불가능
class Outer{
    int num = 10;
    static int sNum = 10;
    static class Inner{
        int inNum = 100;
        static int sInNum = 10;
        void inTest(){
//            System.out.println(num); 외부 클래스 인스턴스 변수 x
            System.out.println(sNum);
            System.out.println(inNum);
            System.out.println(sInNum);
        }
        static void inMethod(){
//            System.out.println(num); 외부 클래스 인스턴스 변수 x
//            System.out.println(inNum); 내부 클래스 인스턴스 변수 x
            System.out.println(sNum);
            System.out.println(sInNum);
        }
    }
}

정적 내부 클래스 메서드

지역 내부 클래스

  • 지역 변수와 같이 메서드 내부에 정의하여 사용하는 클래스
  • 메서드 호출이 끝나면 메서드에 사용된 지역변수의 유효성이 사라짐
  • 메서드 호출 이후에도 생성된 내부 클래스가 사용될 수 있으므로 메서드의 지역변수나 매개변수는 final로 선언됨
class Outer{
    int num = 10;
    static int sNum = 10;
    Runnable getRunnable(int i){
        int localNum = 10; // final로 선언되어서 변경x
        class MyRunnable implements Runnable{
            @Override
            public void run() {
                System.out.println(i);
                System.out.println(localNum);

                System.out.println(num);
                System.out.println(sNum);
            }
        }
	      return new MyRunnable();
    }
}

익명 내부 클래스

  • 이름이 없는 클래스
  • 클래스 이름을 생략하고 주로 하나의 인터페이스나 하나의 추상 클래스 구현하여 반환(보통 일회성 사용)
  • 인터페이스나 추상 클래스 자료형의 변수에 직접 대입하여 클래스 생성
class Outter2{

	Runnable runner = new Runnable() {
		@Override
		public void run() {
			System.out.println("Runnable 이 구현된 익명 클래스 변수");
			
		}
	};
}

Stream(스트림)

Stream과 함수형 인터페이스와 람다식의 관계

자바에서 함수형 프로그래밍을 지원하기 위해서 JDK8부터 함수형 인터페이스, 람다식, Stream API를 제공해주고 있다.

Stream 연산들은 매개변수로 함수형 인터페이스를 받도록 되어 있고, 람다식은 반환값으로 함수형 인터페이스를 반환하고 있다.(람다식 → 함수형 인터페이스 → 스트림)

따라서 Stream에 대해서 정확히 이해하기 위해서는 함수형 인터페이스와 람다식에 대해서 이해하고 있어야 한다.

함수형 프로그래밍

함수형 프로그래밍은 순수 함수로 구현하고 호출함으로써 외부 자료에 부수적인 영향을 미치지 않도록 구현하는 방식이다.

순수함수란 매개변수만을 이용하여 만드는 함수를 의미하고,

부수적인 영향은 Side Effect라고도 하는데, 원본 데이터에 영향을 미치지는 것을 의미한다.

원본 데이터를 변경하지 않고 내부의 반복만으로 작업을 처리하기 때문에 병렬처리가 가능하다는 장점을 가지고 있다.

람다식

함수를 하나의 식으로 표현한 것

함수를 람다식으로 표현하면 메서드의 이름이 필요없기 때문에 람다식은 익명함수의 한 종류라고 할 수 있다.

익명함수는 모두 일급 객체로 변수처럼 사용이 가능하며 매개변수로 전달이 가능하다는 특징이 있다.

보통 아래와 같은 형식을 가지고 있다.

(매개변수) -> {실행문;}

예를 들어서 두 수를 더하는 함수의 경우

int add(int x, int y){
	return x + y;
}

람다식으로 표현하면 아래와 같다.

(int x, int y) -> {return x + y;}

매개변수가 하나인 경우 자료형과 괄호가 생략 가능하다.

반면에 매개변수가 두 개인 경우 자료형과 괄호를 생략할 수 없다.

str -> {System.out.println(str);}
// x, y -> {System.out.println(x + y);}  오류

실행문이 한 문장인 경우 중괄호가 생략 가능하다.

반면에 실행문이 한 문장이라도 return문이 포함된다면 중괄호를 생략할 수 없다.

str -> System.out.println(str);
// str -> return str.length();  오류

함수형 인터페이스

함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로 단 하나의 추상 메서드를 가지고 있는 인터페이스

람다식을 통해 순수 함수를 선언할 수 있게 되었다. 자바에서는 객체지향 언어이기 때문에 순수 함수와 일반 함수를 다르게 취급하고 있다. 자바에서 이를 구분하기 위해서 함수형 인터페이스가 등장하게 되었다.

아래는 함수 인터페이스를 사용하는 예시이다.

// 함수 인터페이스 정의
@FunctionalInterface
interface LambdaFunction{
    int add(int a, int b);
}
// 함수 인터페이스 사용 예시
public class Test {
    public static void main(String[] args) {
       LambdaFunction lambdaFunction = (int a, int b) -> {
           return a + b;
       };
        System.out.println(lambdaFunction.add(1, 2));
    }
}

스트림

데이터의 흐름

스트림은 위에서 설명한 함수형 프로그래밍으로서 자료의 대상과 관계없이 동일한 연산을 수행하며, 한번 생성하고 사용한 스트림은 사용할 수 없다는 특징을 가지고 있다.

스트림 연산은 생성 연산, 중간 연산, 최종 연산으로 구분된다.

스트림의 생성 연산

Collection의 생성 스트림은 다음과 같다.

List<String> list = Array.asList("a", "b", "c");
Stream<String> listStream = list.stream();

배열의 생성 스트림은 다음과 같다.

Stream<String> stream = Stream.of("a", "b", "c");
Stream<String> stream = Stream.of(new String[]{"a", "b", "c"});
Stream<String> stream = Array.stream(new String[]{"a", "b", "c"});

원시 타입의 생성 스트림은 다음과 같다.

IntStream stream = IntStream.range(4, 10);

스트림의 중간 연산

filter는 조건에 맞는 데이터만을 걸러주는 연산이다.

Stream<String> stream = sList.stream().filter(s->s.length() >= 5)

map은 특정한 값으로 변환해주는 연산이다.

Stream<String> stream = names.stream().map(s -> s.toUpperCase());

스트림의 최종 연산

최댓값, 최솟값, 평균 연산 등은 Stream으로 주어지는 값이 비어 있을 경우를 대비해서 Optional을 반환한다.

OptionalInt max = IntStream.of().max();
OptionalInt min = IntStream.of(1, 2, 3).min();
double v = IntStream.of(1, 2, 3).average().orElse(0);

forEach는 요소들을 하나씩 꺼내는 연산이다.

customerList.stream().forEach(s->System.out.println(s));

구현 가능한 스트림 연산

reduce 연산은 정의된 연산이 아니라 직접 구현한 연산을 사용한다.

T reduce(T identify, BinaryOperator<T> accumulator)

첫번째 인자로는 초기 누적값을 사용하고, 두번째 인자에 직접 람다식으로 연산을 구현할 수 있다.

예를 들어서

Arrays.stream(ints).reduce(0, (a,b)-> a+b);

예제: 길이가 가장 긴 문자열 찾기

class CompareString implements BinaryOperator<String>{

    @Override
    public String apply(String s1, String s2) {
        if (s1.getBytes().length >= s2.getBytes().length) return s1;
        else return s2;

    }
}
public class Test {
    public static void main(String[] args) {
        String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};

        String s= Arrays.stream(greetings).reduce("", (s1, s2)->{
            if (s1.getBytes().length >= s2.getBytes().length) return s1;
            else return s2;
        });

        String ss = Arrays.stream(greetings).reduce(new CompareString()).get();
       
    }
}

Exception(예외처리)

프로그램에서의 에러는 발생 시점에 따라 나눌 수 있다.

Exception

컴파일 오류(Compile Error)의 경우 문법적 오류처럼 컴파일 시점에서 확인 가능한 에러를 의미한다. 이 에러의 경우 대부분 IDE에서 잡아준다.

실행 오류는(Runtime Error)의 경우 프로그램 실행 중 발생하는 에러로 실행 프로그램이 의도지 않은 행동을 하거나 프로그램이 중지되는 에러를 의미한다.

Throwable

실행 오류는 다시 시스템 에러와 예외로 나뉜다.

시스템 에러(Error)의 경우 가상머신에서 발생하는 에러로, 동적 메모리가 부족하거나 스택 메모리가 부족한 경우처럼 개발자가 처리할 수 없는 에러를 의미한다.

반면에 예외(Exception)의 경우 NullPointException처럼 프로그램에서 제어할 수 있는 에러를 의미한다.

예외 처리하기(try-catch)

자바에서는 예외처리를 위해 try-catch-fianlly문을 제공하고 있다.

public static void main(String[] args) {
        try{
            //예외 발생 가능성이 있는 곳
        } catch(처리할 예외타입 e){
            //예외가 발생했을 때, 예외처리
        } finally{
						//예외와 관계없이 항상 수행되는 부분
        }
    }

finally은 예외와 관계없이 항상 수행되기 때문에 try와 catch에서 공통으로 처리해야하는 리소스 해제 구현 등이 많이 이뤄진다.

특히 JDK 7부터 리소스 처리를 위해 try-with-resource문을 제공해주고 있다. 덕분에 close()를 명시적으로 호출하지 않아도 try{} 블록에서 열린 리소스는 정상적인 경우나 예외가 발생한 경우 모두 close() 처리된다.

class AutoCloseObj implements AutoCloseable{
    @Override
    public void close() throws Exception {
        System.out.println("close 처리");
    }
}
public class Test {
    public static void main(String[] args) {
        AutoCloseObj obj = new AutoCloseObj();
        try(obj){//AutoCloseObj 구현한 객체
            //예외 발생 가능성 및 리로스 오픈
        } catch(Exception e){
            //예외가 발생했을 때, 예외처리
        }
    }
}

예외 처리 미루기(Throws)

Throws 키워드를 이용해서 예외 처리를 메서드를 호출한 곳으로 미룰 수 있다.

무슨 말인지 예시를 통해 이해해보자

public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException{
		FileInputStream fis = new FileInputStream(fileName); //FileNotFoundException 발생
		Class c = Class.forName(className);  //ClassNotFoundException 발생
		return c;
	}

위의 메서드를 통해 두가지 예외가 발생한다.

여기서 해당 메서드에서 예외처리를 해주는 대신 throws를 이용해 메서드를 호출하는 시점에서 try-catch를 통해 예외처리하도록 만들 수 있다.

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        try {
            test.loadClass("a.txt", "java.lang.String");
						// main 메서드에서 호출했기 때문에 여기서 예외처리를 해준다.
        }catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException{
        FileInputStream fis = new FileInputStream(fileName); //FileNotFoundException 발생
        Class c = Class.forName(className);  //ClassNotFoundException 발생
        return c;
    }
}

여러 예외 처리

여러 개의 예외가 발생하는 경우 예외를 묶어서 하나로 처리할 수 있다.

try {
	test.loadClass("a.txt", "java.lang.String");
	} catch (FileNotFoundException | ClassNotFoundException e) {
		e.printStackTrace();
	}

각각의 예외를 따로 처리할 수도 있는데, 이때 만약 Exception 클래스를 활용하여 default 처리를 한다면 Exception 블록은 맨 마지막에 위치해야 한다.

try {
			test.loadClass("a.txt", "java.lang.String");
		}catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}catch (Exception e) {
			e.printStackTrace();
		}

사용자 정의 예외 처리

자바가 모든 예외 클래스를 제공해줄 수 없기 때문에 사용자가 직접 예외를 정의해서 사용해야 할 때도 있다. 이때 기존의 예외 클래스 중 가장 유사한 예외 클래스를 상속 받아서 사용자 정의 예외 클래스를 만들면 된다. 잘 모르면 Exception 클래스

그리고 if문을 만족할 때마다 메서드로 해당 클래스를 throws 해서 예외를 발생시키면 된다.

생성자에서 예외 처리를 해주어야 할 때도 있을텐데, 이때 throws를 하는 대신에 IllegalArgumentException을 이용하면 throws를 사용하지 않고 예외 처리를 다룰 수 있다.

예제: 패스워드에 대한 예외처리 하기

  1. 비밀번호는 null일 수 없습니다.
  2. 비밀번호의 길이는 5 이상입니다.
  3. 비밀번호는 문자로만 이루어져서는 안됩니다.

class PasswordException extends IllegalAccessException{
    public PasswordException(String message){
        super(message);
    }
}
public class Test {

    private String password;

    public String getPassword(){
        return password;
    }

    public void setPassword(String password) throws PasswordException{

        if(password == null){
            throw new PasswordException("비밀번호는 null 일 수 없습니다");
        }
        else if( password.length() < 5){
            throw new PasswordException("비밀번호는 5자 이상이어야 합니다.");
        }
        else if (password.matches("[a-zA-Z]+")){
            throw new PasswordException("비밀번호는 숫자나 특수문자를 포함해야 합니다.");
        }

        this.password = password;
    }

    public static void main(String[] args) {
				Test test = new Test();
        String password = null;
        try {
            test.setPassword(password);
            System.out.println("오류 없음1");
        } catch (PasswordException e) {
            System.out.println(e.getMessage());
        }
        password = "abcd";
        try {
            test.setPassword(password);
            System.out.println("오류 없음2");
        } catch (PasswordException e) {
            System.out.println(e.getMessage());
        }
        password = "abcde";
        try {
            test.setPassword(password);
            System.out.println("오류 없음3");
        } catch (PasswordException e) {
            System.out.println(e.getMessage());
        }
        password = "abcde#1";
        try {
            test.setPassword(password);
            System.out.println("오류 없음4");
        } catch (PasswordException e) {
            System.out.println(e.getMessage());
        }
    }
}

I/O Stream(입출력 스트림)

입력 스트림과 출력 스트림

입출력 스트림

입출력 장치와 자바 사이에 독립적으로 일관성 있는 입출력을 주고 받는 것을 입출력 스트림이라고 한다.

입출력 스트림은 아래와 같이 다양한 클래스가 있다.

종류예시
입력 스트림FileInputStream, FileReader, BufferedInputStream, BufferedReader
출력 스트림FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter

바이트 단위 스트림과 문자 단위 스트림

스트림

바이트 단위 스트림은 실행 파일이나 동영상, 음악파일을 주고 받을 때, 사용한다.

문자 단위 스트림은 바이트 단위로 문자를 처리하면 문자가 깨지기 때문에, 인코딩에 맞게 2바이트 이상으로 처리한다.

종류예시
바이트 스트림FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream
문자 스트림FileReader, FileWriter, BufferedReader, BufferedWriter

기반 스트림과 보조 스트림

기반, 보조 스트림

기반 스트림은 대상에 직접 자료를 읽고 쓰는 기능을 하는 스트림이다.

보조 스트림은 직접 읽고 쓰는 기능은 없는 추가적인 기능을 더해주는 스트림이다. 그래서 보조 스트림은 항상 기반 스트림이나 다른 보조 스트림을 생성자의 매개변수로 포함한다.

종류예시
기반 스트림FileInputStream, FileOutputStream, FileReader, FileWriter
보조 스트림InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream

표준 입출력 스트림

System.out은 System.out.println()으로 많이 접해본 표준 출력 스트림이다.

System.in은 반대로 표준 입력 스트림을 의미한다.

예를 들어

int d = System.in.read() // 한 바이트 읽기

실제로 사용할 때에는 try-catch문으로 예외 처리를 해주어야 한다.

public class SystemInTest {
	public static void main(String[] args) {
		System.out.println("알파벳 여러 개를 쓰고 [Enter]를 누르세요");
		int i;
		try {
			while( (i = System.in.read()) != '\n' ) { // Enter를 누르기 전까지 입력을 받는다.
				System.out.print((char)i); // char 캐스팅으로 문자로 변환해준다.
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

직렬화(Serialization)

Object또는 Data의 상태를 외부의 자바 시스템에서도 사용할 수 있도록 Byte형태로 바꾸어 파일 저장하거나 네트워크로 전송하고(serialization) 이를 다시 복원(deserializatio) 하는 방식

자바에서는 보조 스트림을 통해 직렬화를 제공한다.

Serializable 인터페이스를 구현한 객체를 통해 직렬화가 이뤄진다.

생성자설명
ObjectInputStream(InputStream in)InputStream을 생성자의 매개변수로 받아 ObjectInputStream을 생성합니다.
ObjectOutputStream(OutputStream out)OutputStream을 생성자의 매개변수로 받아 ObjectOutputStream을 생성합니다.

람다식과 함수형 인터페이스
예외처리

profile
Dev Log

0개의 댓글

관련 채용 정보