🔌 SPARTA/Courses

자바 개념 확장

eunjiom 2026. 1. 15. 21:01

➕  예외와 예외처리

1. 예외

  • 프로그램 실행 중 예상하지 못한 상황 발생
  • 의도적으로 예외 발생throw 키워드
  • 예외를 처리하지 않으면 비정상적 프로그램 종료 > 예외처리(try-catch)를 통해 프로그램이 안정적으로 실행

2. 예외 발생

 

1) 의도하지 않은 예외

  • 10 / 0 연산을 수행하면서 ArithmeticException (산술예외)가 발생
더보기
public class Main {
    public static void main(String[] args) {
        System.out.println("프로그램 시작");
        int result = 10 / 0; // ❌ 예외 발생 (ArithmeticException)
        System.out.println("이 문장은 실행되지 않음");
    }
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at chapter3.exception.Main.main(Main.java:8)

Process finished with exit code 1

2) 의도적인 예외 (throw)

  • 특정 조건에서 의도적으로 예외를 발생
더보기
  • 미성년자 접근금지
  •  age < 18  조건을 만족  > IllegalArgumentException 을 발생
public class Main {
    public static void main(String[] args) {
        int age = 10;
        if (age < 18) {
		        // ✅ 의도적으로 예외를 발생시키는 부분
            throw new IllegalArgumentException("미성년자는 접근할 수 없습니다!");
        }
        System.out.println("....");
    }
}

 

3. 예외 구조와 종류

 

1) 구조와 종류

  • RuntimeException - UncheckedException : 예외처리를 컴파일러가 확인x
  • Exception - CheckedException: 예외처리를 컴파일러가 확인 

2) 예외 전파

  • 메서드에서 발생한 예외가 해당 메서드 내에서 처리되지 않았을 때 메서드를 호출한 상위 메서드로 전달되는 과정
  • 로그램 시작 지점(main()) 까지 전파되고 끝내 처리되지 않으면 프로그램이 비정상 종료

3) RuntimeException - 언체크 예외 전파 

  • 컴파일러가 예외 처리를 강제하지 않는 예외(빨간줄x)
  • 처리되지 않은 예외는 계속 프로그램 시작 지점까지 전파
  • 끝내 예외가 처리되지 않으면 프로그램이 비정상적으로 종료
  • try-catch 활용
더보기
public class ExceptionPractice {
    public void callUncheckedException() {
        if (true) {
            System.out.println("언체크 예외 발생");
            throw new RuntimeException(); // ✅ 예외발생 
        }
    }
}
public class Main {
    public static void main(String[] args) {
        // 예외 실습 객체 인스턴스화
        ExceptionPractice exceptionPractice = new ExceptionPractice();

        // ✅ 언체크 예외 호출
        exceptionPractice.callUncheckedException();
        
        // ❌ 예외처리를 해주지 않았기 때문에 프로그램이 종료됩니다.
        System.out.println("이 줄은 실행되지 않습니다."); 
    }
}
public class Main {

    public static void main(String[] args) {
        ExceptionPractice exceptionPractice = new ExceptionPractice();
        
        // ✅ 상위로 전파된 예외처리
        try {
            exceptionPractice.callUncheckedException();

        } catch (RuntimeException e) { // ✅ 예외처리
            System.out.println("언체크 예외 처리");   
            
        } catch (Exception e) {
            System.out.println("체크 예외 처리");
        }

        System.out.println("프로그램 종료");
    }
}

4) Exception - 체크 예외 전파

  • 컴파일러가 예외 처리를 강제하는 예외
  • try-catch 활용
더보기
public class ExceptionPractice {

    public void callCheckedException() {
		    // ✅ try-catch 로 예외 처리
        try { 
            if (true) {
                System.out.println("체크예외 발생");
                throw new Exception();
            }
        } catch (Exception e) {
            System.out.println("예외 처리");
        }
    }
}
public class Main {
    public static void main(String[] args) {
        
        // 예외 실습 객체 인스턴스화
        ExceptionPractice exceptionPractice = new ExceptionPractice();

        // ✅ 체크예외 호출
        exceptionPractice.callCheckedException();
    }
}
  • throws 활용: 예외를 호출한 곳에서 처리
더보기
public class ExceptionPractice {

    public void callCheckedException() throws Exception { // ✅ throws 예외를 상위로 전파
        if (true) {
            System.out.println("체크예외 발생");
            throw new Exception();
        }
    }
}
package chapter3.exception;

public class Main {
    public static void main(String[] args) {

        // 예외 실습 객체 인스턴스화
        ExceptionPractice exceptionPractice = new ExceptionPractice();

        // 체크 예외 사용
        // ✅ 반드시 상위 메서드에서 try-catch 를 활용해 주어야합니다.
        try {
            exceptionPractice.callCheckedException();
        } catch (Exception e) {
            System.out.println("예외처리");
        }
    }
}

➕ Optinal

1. 개념

  • null 을 안전하게 다루게 해주는 객체
  • null : 값이 없음 또는 참조하지 않음
  • NullPointerException(NPE)을 방지 > 런타임 예외 : 처리해 주지 않으면 프로그램이 종료

2. 필요한 이유 (npe 발생)

  • Student 먼저 생성: 없는 경우 camp.getStudent()(null 반환메서드) 반환 시 npe 발생
더보기
public class Student {

    // 속성
    private String name;
    
    // 생성자
    
    // 기능
    public String getName() {
        return this.name;
    }
}
public class Camp {

    // 속성
    private Student student;

    // 생성자
    
    // 기능: ⚠️ null 을 반환할 수 있는 메서드
    public Student getStudent() {
        return student;
    }
    
    public void setStudent(Student student) {
		    this.student = student;    
    }
}
public class Main {

    public static void main(String[] args) {

        Camp camp = new Camp();
        Student student = camp.getStudent(); // ⚠️ student 에는 null 이 담김
        // ⚠️ 아래 코드에서 NPE 발생! 컴파일러가 잡아주지 않음
        String studentName = student.getName(); // 🔥 NPE 발생 -> 프로그램 종료
        System.out.println("studentName = " + studentName);
    }
}

3. null 직접 처리 한계

  • if문을 활용해서 null 처리가능하나 null이 발생할 가능성미리 예측하고 처리하는 것이 어려움
public class Main {
    public static void main(String[] args) {
        Camp camp = new Camp();
        Student student = camp.getStudent();

        String studentName;
        if (student != null) { // ⚠️ 가능은하지만 현실적으로 어려움
            studentName = student.getName();
        } else {
            studentName = "등록된 학생 없음"; // 기본값 제공
        }

        System.out.println("studentName = " + studentName);
    }
}

 

4. Optional 활용

 

1) 개념

  • Optional 객체는 값이 있을 수도 있고 없을 수도 있음
  • Optional 객체를 메서드 반환 자료형에 선언해서 해당 메서드가 null 이 반환될 가능성을 명확하게 전달
  • Optional.ofNullable() 을 사용하여  null이 반환될 수 있는 객체를 감쌈
  • isPresent() 와 같은 Optional API 를 통해 안전하게 null 처리

2) isPresent() 활용 방법

  • Optional 내부의 값이 존재할 경우에 true 반환
  • 내부 값이 null 일 경우 false 를 반환
더보기
import java.util.Optional;

public class Camp {

    // 속성
    private Student student;

    // 생성자

    // 기능
    // ✅ null 이 반환될 수 있음을 명확하게 표시
    public Optional<Student> getStudent() {
        return Optional.ofNullable(student);
    }
    
    public void setStudent(Student student) {
		    this.student = student;    
    }
}
public class Main {

    public static void main(String[] args) {

        Camp camp = new Camp();
        
        // isPresent() 활용시 true 를 반환하고 싶을때 활용
        // Student newStudent = new Student();
        // camp.setStudent(newStudent);

        //  Optional 객체 반환받음
        Optional<Student> studentOptional = camp.getStudent();

        // Optional 객체의 기능 활용
        boolean flag = studentOptional.isPresent(); // false 반환
        if (flag) {
            // 존재할 경우
            Student student = studentOptional.get(); // ✅ 안전하게 Student 객체 가져오기
            String studentName = student.getName();
            System.out.println("studentName = " + studentName);

        } else {
            // null 일 경우
            System.out.println("학생이 없습니다.");
        }
    }
}

3) orElseGet() 활용 방법

  • 값이 없을 때만 기본값을 제공하는 로직을 실행하는 메서드
  • 메서드를 매개변수로 전달
더보기
import java.util.Optional;

public class Camp {

    // 속성
    private Student student;

    // 생성자

    // 기능
    // ✅ null 이 반환될 수 있음을 명확하게 표시
    public Optional<Student> getStudent() {
        return Optional.ofNullable(student);
    }
}
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Camp camp = new Camp();

        // ✅ Optional 객체의 기능 활용 (orElseGet 사용)
        Student student = camp.getStudent()
                              .orElseGet(() -> new Student("미등록 학생"));

        System.out.println("studentName = " + student.getName());
    }
}

➕  컬렉션

1.  컬렉션

  • 자바 컬렉션 프레임워크 : 인터페이스와 구현체(ArrayList, HashSet, HashMap 등)를 제공하는 집합
  • 데이터 저장, 조회, 삭제, 정렬 등 다양한 기능 구현
  • 길이를 동적으로 변경

2. 배열의 한계

  • 배열 = 크기 고정 / 길이 변경x > 길이 초과시 에러
// 배열은 길이가 고정됨
int[] numbers = new int[3];
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40; // ❌ 요소 추가시 에러발생
  • 컬렉션 클래스: ArrayList, HashSet, HashMap 등 (데이터 저장, 관리 가능) / 길이 동적 변경o
컬렉션객체<자료형> 변수이름 = new 컬렉션객체<자료형>();

// 객체<다룰 데이터: 정수> 변수이름 = new 컬렉션객체생성자<정수>();
ArrayList<Integer> arrayList = new ArrayList<Integer>();
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(10);
arrayList.add(20);
arrayList.add(30);
arrayList.add(40); // ✅ 정상 동작 (길이 제한 없음)

 

3. 컬렉션 종류와 특징

1) Arraylist

  • 순서를 유지하고 중복된 값을 저장
// List 를 구현한 ArrayList
ArrayList<String> names = new ArrayList<>();
names.add("Spartan");      // 1 번째 요소 추가
names.add("Steve");        // 2 번째 요소 추가
names.add("Isac");         // 3 번째 요소 추가
names.add("1");
names.add("2");

 // ✅ 순서 보장
System.out.println("names = " + names);

// ✅ 중복 데이터 허용
names.add("Spartan");
System.out.println("names = " + names);

// ✅ 단건 조회
System.out.println("1 번째 요소 조회: " + names.get(0)); // 조회 Spartan

// ✅ 데이터 삭제
names.remove("Steve"); 
System.out.println("names = " + names);

 

2) HashSet

  • Set 인터페이스 구현: 순서를 유지하지 않고 중복을 허용 x > get 지원x
// Set 을 구현한 HashSet
HashSet<String> uniqueNames = new HashSet<>();

// ✅ 추가
uniqueNames.add("Spartan");
uniqueNames.add("Steve");
uniqueNames.add("Isac");
uniqueNames.add("1");
uniqueNames.add("2");

// ⚠️ 순서를 보장 안함
System.out.println("uniqueNames = " + uniqueNames); 
uniqueNames.get(0); // ❌ get 사용 불가

// ⚠️ 중복 불가
uniqueNames.add("Spartan");
System.out.println("uniqueNames = " + uniqueNames); 

// ✅ 제거
uniqueNames.remove("Spartan");
System.out.println("uniqueNames = " + uniqueNames);
  • Map 인터페이스 구현: 키(Key) - 값(Value) 구조로 데이터를 저장 / 키 중복x / 순서보장 x
// Map 을 구현한 HashMap
HashMap<String, Integer> memberMap = new HashMap<>();

// ✅ 추가
memberMap.put("Spartan", 15);
memberMap.put("Steve", 15); // ✅ 값은 중복 가능
memberMap.put("Isac", 1);
memberMap.put("John", 2);
memberMap.put("Alice", 3);

// ⚠️ 순서 보장 안함 
System.out.println("memberMap = " + memberMap);

// ⚠️ 키 중복 불가: 값 덮어쓰기 발생
memberMap.put("Alice", 5);
System.out.println("memberMap = " + memberMap);

// ✅ 조회: 15
System.out.println(memberMap.get("Steve"));

// ✅ 삭제 가능
memberMap.remove("Spartan"); 
System.out.println("memberMap = " + memberMap);

// ✅ 키 확인
Set<String> keys = memberMap.keySet();
System.out.println("keys = " + keys);

// ✅ 값 확인
Collection<Integer> values = memberMap.values();
System.out.println("values = " + values);

 

➕ 제네릭

1. 제네릭

  • 클래스, 메서드 등에 사용되는 <T>타입 매개변수
  • 타입을 미리 지정x > 유연하게 결정
  • 코드 재사용성과 타입 안정성(잘못된 타입 사용 방지)을 보장

2. 제네릭이 없는 경우

 

1) 재사용 불가

public class Box {
    private Integer item; // ⚠️ Integer 타입으로 고정

    public Box(Integer item) { // ⚠️ Integer 타입으로 고정
        this.item = item;
    }

    public Integer getItem() {
        return this.item;
    }
}
public class Main {
    public static void main(String[] args) {
        // ✅ Integer 타입 박스
        Box box1 = new Box(100);

        // ❌ String 타입을 저장하려면 새로운 클래스를 만들어야 함
        Box box2 = new Box("ABC"); 

    }
}

 

2) 낮은 타입 안정성

  • 실행 중 오류가 발생할 가능성 / 런타임 오류 발생
public class ObjectBox {
    private Object item; // ⚠️ 다형성: 모든 타입 저장 가능 (하지만 안전하지 않음)

    public ObjectBox(Object item) {
        this.item = item;
    }

    public Object getItem() {
        return this.item;
    }
}
public class Main {
    public static void main(String[] args) {
        // ✅ ObjectBox 사용
        ObjectBox objBox = new ObjectBox("Hello");
        String str = (String) objBox.getItem(); // 형변환 필요
        System.out.println("objBox 내용: " + str); // Hello

        // ⚠️ 실행 중 오류 발생 (잘못된 다운 캐스팅: ClassCastException)
        objBox = new ObjectBox(100); // 정수 저장
        String error = (String) objBox.getItem(); // ❌ 오류: Integer -> String 
        System.out.println("잘못된 변환: " + error);
    }
}

 

→  잘못된 다운캐스팅 활용 시: ClassCastException

 

3. 제네릭 활용

  • <T>(타입매개변수): 타입을 의미하는 자리 / 데이터 타입으로 대체되어 활용
public class GenericBox<T> { // ✅ 제네릭 클래스
    private T item;

    public GenericBox(T item) { // 다양한 타입 저장 가능
        this.item = item;
    }

    public T getItem() {
        return this.item;
    }
}
  • 타입소거(Erasure) : 컴파일 시점에 제네릭 타입 정보를 제거하는 과정
    <T> 타입 매개변수 부분은 Object 로 대체
    자동으로 강제 다운 캐스팅(cast) 코드를 삽입하여 타입 안전성을 보장
public class Main {
    public static void main(String[] args) {
    
        // 1. ✅ 재사용 가능(컴파일시 타입소거: T -> Object)
        GenericBox<String> strGBox = new GenericBox<>("ABC");
        GenericBox<Integer> intGBox = new GenericBox<>(100);
        GenericBox<Double> doubleGBox = new GenericBox<>(0.1);

        // 2. ✅ 타입 안정성 보장(컴파일시 타입소거: 자동으로 다운캐스팅)
        String strGBoxItem = strGBox.getItem();
        Integer intGBoxItem = intGBox.getItem();
        Double doubleGBoxItem = doubleGBox.getItem();
        System.out.println("strGBoxItem = " + strGBoxItem);
        System.out.println("intGBoxItem = " + intGBoxItem);
        System.out.println("doubleGBoxItem = " + doubleGBoxItem);
    }
}

 

4. 제네릭 메서드

  • 메서드 선언부에 <T> 가 선언된 메서드
  • 클래스 제네릭 타입과 별개로 독립적인 타입 매개변수를 가짐
더보기
public class GenericBox<T> {

    // 속성
    private T item;

    // 생성자
    public GenericBox(T item) {
        this.item = item;
    }

    // 기능
    public T getItem() {
        return this.item;
    }

		// ⚠️ 일반 메서드 T item 는 클래스의 <T> 를 따라갑니다.
    public void printItem(T item) {
        System.out.println(item);
    }
    
    // ✅ 제네릭 메서드 <S> 는 <T> 와 별개로 독립적이다.
    public <S> void printBoxItem(S item) { 
        System.out.println(item);
    }
}
public class Main {

    public static void main(String[] args) {
        GenericBox<String> strGBox = new GenericBox<>("ABC");
        GenericBox<Integer> intGBox = new GenericBox<>(100);
        
        // ⚠️ 일반메서드: 클래스 타입 매개변수를 따라갑니다.
        // String 데이터 타입 기반으로 타입소거가 발생.
        // String 타입의 다운캐스팅 코드 삽입!
        strGBox.printItem("ABC"); // ✅ String 만 사용가능
        strGBox.printItem(100);   // ❌ 에러 발생 
        
        // ✅ 제네릭 메서드: 독립적인 타입 매개변수를 가집니다.
        // String 타입 정보가 제네릭 메서드에 아무런 영향을 주지 못함.
        // 다운캐스팅 코드 삽입되지 않음.
        strGBox.printBoxItem("ABC"); //✅ 모든 데이터 타입 활용 가능
        strGBox.printBoxItem(100);   //✅ 모든 데이터 타입 활용 가능
        strGBox.printBoxItem(0.1);   //✅ 모든 데이터 타입 활용 가능
    }
}

➕ 람다

1. 익명클래스

  • 별도의 클래스 파일을 만들지 않고 코드 내에서 일회성으로 정의해 사용하기 때문에 이름x
  • 인터페이스, 클래스(일반, 추상)의 구현과 상속을 활용해 익명 클래스를 구현
  • 인터페이스를 활용한 익명 클래스
더보기
public interface Calculator {

    int sum(int a, int b);
}
public class Main {

    public static void main(String[] args) {
    
		    // ✅ 익명 클래스 활용
        Calculator calculator1 = new Calculator() {
            @Override
            public int sum(int a, int b) {
                return a + b;
            }
        };
        
        int ret1 = calculator1.sum(1, 1);
        System.out.println("ret1 = " + ret1);
    }
}

2. 람다

  • 함수형 인터페이스를 통해서 구현 권장: 하나의 추상 메서드만 가져야 하기 때문
    하나의 추상 메서드를 가진 일반 인터페이스 통해서도 사용o
  • 람다식을 활용한 익명 클래스 변환
더보기
// 람다 표현식
Calculator calculator1 = (a, b) -> a + b;

// 익명클래스
Calculator calculator1 = new Calculator() {
		@Override
		public int sum(int a, int b) {
				return a + b;
		}
};
@FunctionalInterface // ✅ 함수형 인터페이스 선언
public interface Calculator {

    int sum(int a, int b); // ✅ 오직 하나의 추상 메서드만 선언해야합니다.
}
public class Main {

    public static void main(String[] args) {
    
		    ...

        // ✅ 람다식 활용
        Calculator calculator2 = (a, b) -> a + b;
        int ret2 = calculator2.sum(2, 2);
        System.out.println("ret2 = " + ret2);
    }
}

3. 주의사항

  • 함수형 인터페이스를 활용
  • 두 개 이상의 추상 메서드가 존재 > 어떤 메서드를 구현하는지 모호
  • ex: 오버로딩 기능 
    > 같은 이름의 sum() 메서드를 여러 형태로 정의 
    > 람다 표현식이 어떤 메서드를 구현하는 것인지 명확하지 않아 모호성이 발생
더보기
public interface Calculator {
    int sum(int a, int b);        // ✅ 선언 가능
    int sum(int a, int b, int c); // ⚠️ 오버로딩으로 선언 가능 모호성 발생!
}
@FunctionalInterface // ✅ 함수형 인터페이스 선언
public interface Calculator {
    int sum(int a, int b); // ✅ 오직 하나의 추상 메서드만 선언해야합니다.
    int sum(int a, int b, int c); // ❌ 선언 불가 에러발생!
}

4. 람다식을 매개변수로 전달

  • 익명 클래스를 변수에 담아 전달(직접 객체를 생성)
public class Main {
    public static int calculate(int a, int b, Calculator calculator) {
        return calculator.sum(a, b);
    }

    public static void main(String[] args) {

        Calculator cal1 = new Calculator() {
            @Override
            public int sum(int a, int b) {
                return a + b;
            }
        };

        // ✅ 익명 클래스를 변수에 담아 전달
        int ret3 = calculate(3, 3, cal1);
        System.out.println("ret3 = " + ret3); // 출력: ret3 = 6
    }
}
  • 람다식을 변수에 담아 전달
public class Main {

    public static int calculate(int a, int b, Calculator calculator) {
        return calculator.sum(a, b);
    }

    public static void main(String[] args) {
        Calculator cal2 = (a, b) -> a + b;
        
        // ✅ 람다식을 변수에 담아 전달
        int ret4 = calculate(4, 4, cal2);
        System.out.println("ret4 = " + ret4); // 출력: ret4 = 8
    }
}
  • 람다식을 직접 전달
public class Main {

    public static int calculate(int a, int b, Calculator calculator) {
        return calculator.sum(a, b);
    }

    public static void main(String[] args) {
        // ✅ 람다식을 직접 매개변수로 전달
        int ret5 = calculate(5, 5, (a, b) -> a + b);
        System.out.println("ret5 = " + ret5); // 출력: ret5 = 10
    }
}

 

➕ 스트림

1. 스트림

  • 데이터를 효율적으로 처리할 수 있는 흐름
  • 특징: 선언형 스타일로 가독성이 굉장히 뛰어남
  • 데이터 준비 → 중간 연산 → 최종 연산 순으로 처리
  • 컬렉션과 자주 함께 활용

2. for 과 스트림 비교

  • arrayList 의 각 요소를 10배로 변환
더보기
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
public class Main {

    public static void main(String[] args) {

        // ArrayList 선언
        List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // ✅ for 명령형 스타일: 각 요소 * 10 처리
        List<Integer> ret1 = new ArrayList<>();
        for (Integer num : arrayList) {
            int multipliedNum = num * 10; // 각 요소 * 10
            ret1.add(multipliedNum);
        }
        System.out.println("ret1 = " + ret1); 
    }
}
public class Main {

    public static void main(String[] args) {

        // ArrayList 선언
        List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // ✅ 스트림 선언적 스타일: 각 요소 * 10 처리
        List<Integer> ret2 = arrayList.stream().map(num -> num * 10).collect(Collectors.toList());
        System.out.println("ret2 = " + ret2);
    }
}

3. 스트림 (선언형 스타일)

 

1) 스트림 처리단계

  • 여러 API 제공

 

2) 스트림을 사용 >  각 요소를 10배로 변환 후 리스트로 변환

  • stream() → map() → collect() 순으로 데이터 흐름을 처리
    stream():  데이터 준비 - 데이터를 스트림으로 변환하여 연산 흐름을 만들 준비
    map():  중간 연산 등록 - 각 요소를 주어진 함수에 적용해서 변환
    collect(): 최종 연산 - 결과를 원하는 형태(List, Set)로 수집
더보기
arrayList
	.stream()  // 1. 데이터 준비
	.map()     // 2. 중간 연산 등록
	.collect() // 3. 최종 연산
// 1. 데이터 준비: 스트림 생성
Stream<Integer> stream = arrayList.stream();

// 2. 중간 연산 등록: 각 요소를 10배로 변환 로직 등록
Stream<Integer> mappedStream = stream.map(num -> num * 10);

// 3. 최종 연산: 최종 결과 리스트로 변환
List<Integer> ret2 = mappedStream.collect(Collectors.toList());
// ✅ 한 줄로 표현 가능
List<Integer> ret2 = arrayList.stream() // 1. 데이터 준비
    .map(num -> num * 10)               // 2. 중간 연산 등록
    .collect(Collectors.toList());  // 3. 최종 연산

4. 스트림과 람다식 활용

  • 각 요소 * 10 예시
더보기
  • 매개변수 = 함수형 인터페이스
// map() 메서드를 살펴봅시다.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

  •  익명클래스를 변수에 담아 활용
    Function<T, R> T 는 매개변수, R 은 반환 타입을 의미
public class Main {

    public static void main(String[] args) {

        // ArrayList 선언
        List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // ✅ 1. 익명클래스를 변수에 담아 활용
        Function<Integer, Integer> function = new Function<>() {

            @Override
            public Integer apply(Integer integer) {
                return integer * 10;
            }
        };
        
        List<Integer> ret3 = arrayList.stream()
                .map(function)
                .collect(Collectors.toList());
        System.out.println("ret3 = " + ret3);
    }
}
  • 람다식을 변수로 활용
public class Main {

    public static void main(String[] args) {

        // ArrayList 선언
        List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // ✅ 2. 람다식을 변수에 담아 활용
        Function<Integer, Integer> functionLambda = (num -> num * 10);
        List<Integer> ret4 = arrayList.stream()
                .map(functionLambda)
                .collect(Collectors.toList());
        System.out.println("ret4 = " + ret4);
    }
}
  • 람다식을 매개변수에 직접 활용
public class Main {

    public static void main(String[] args) {

        // ArrayList 선언
        List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // ✅ 3. 람다식을 직접 활용
        List<Integer> ret5 = arrayList.stream()
                .map(num -> num * 10)
                .collect(Collectors.toList());
        System.out.println("ret5 = " + ret5);
    }
}
  • 전체코드
public class Main {

    public static void main(String[] args) {

        // ArrayList 선언
        List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

        // 스트림 없이: 각 요소 * 10 처리
        ArrayList<Integer> ret1 = new ArrayList<>();
        for (Integer num : arrayList) {
            int multipliedNum = num * 10;
            ret1.add(multipliedNum);
        }
        System.out.println("ret1 = " + ret1);

        // 스트림 활용: 각 요소 * 10 처리
        List<Integer> ret2 = arrayList.stream().map(num -> num * 10).collect(Collectors.toList());
        System.out.println("ret2 = " + ret2);

        // 직접 map() 활용해보기
        // 1. 익명클래스를 변수에 담아 전달
        Function<Integer, Integer> function = new Function<>() {

            @Override
            public Integer apply(Integer integer) {
                return integer * 10;
            }
        };
        List<Integer> ret3 = arrayList.stream()
                .map(function)
                .collect(Collectors.toList());
        System.out.println("ret3 = " + ret3);

        // 2. 람다식을 변수에 담아 전달
        Function<Integer, Integer> functionLambda = (num -> num * 10);
        List<Integer> ret4 = arrayList.stream()
                .map(functionLambda)
                .collect(Collectors.toList());
        System.out.println("ret4 = " + ret4);
				
				// 람다식 직접 활용
        List<Integer> ret5 = arrayList.stream()
                .map(num -> num * 10)
                .collect(Collectors.toList());
        System.out.println("ret5 = " + ret5);
    }
}

5. 스트림 중간연산 함께 사용하기

  • 리스트에서 짝수만 10배로 변환시키는 예시(filter + map)
더보기
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));

// ✅ filter() + map() 사용 예제
List<Integer> ret6 = arrayList.stream() // 1. 데이터 준비: 스트림 생성
        .filter(num -> num % 2 == 0)    // 2. 중간 연산: 짝수만 필터링
        .map(num -> num * 10)           // 3. 중간 연산: 10배로 변환
        .collect(Collectors.toList()); // 4. 최종 연산: 리스트로 변환

System.out.println(ret6); // 출력: [20, 40]
// For 문 예시 / for 문과 비교
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));


List<Integer> ret6 = new ArrayList<>();
for (int num : arrayList) {
    int remain = num % 2;
    if (remain == 0) {
        int data = num * 10;
        ret6.add(data);
    }
}
// 5. 결과 출력
System.out.println(ret6); // 출력: [20, 40]

➕ 쓰레드

1. 쓰레드

  • 프로그램 내에서 독립적으로 실행되는 하나의 작업 단위(한 명의 일꾼)
  • 싱글 쓰레드:  한 번에 하나의 작업만 처리
    멀티 쓰레드: 여러 작업을 동시에 처리 / 여러 작업을 병렬로 수행 > 처리 성능을 향상

2. 싱글 쓰레드

  • 순차적으로 처리
  • main() 메서드:  프로그램 시작과 동시에 생성되는 하나의 쓰레드
public class Main {

    public static void main(String[] args) {
        System.out.println("::: main 쓰레드 시작 :::");
        String threadName = Thread.currentThread().getName();
        
        // ✅ 하나의 작업 단위: 숫자를 0 부터 9 까지 출력
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500); // 0.5 초 대기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // ✅ 추가작업
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500); // 0.5 초 대기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("::: 작업 끝 :::");
    }
}

 

3. 멀티 쓰레드와 쓰레드 구현

  • 여러 작업(0 ~9 출력)을 병렬(동시)로 처리
  • start(): 쓰레드 실행시킬 때 필수 사용 / 새로운 쓰레드에서 run() 실행
더보기
// ✅ Thread 클래스 상속으로 쓰레드 구현
public class MyThread extends Thread{

	// Thread.run() 메서드를 오버라이드 해서 쓰레드가 수행할 작업을 정의
    
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println("::: " + threadName + "쓰레드 시작 :::");
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500); // 딜레이 0.5 초 
           // 생성된 thread0, thread1 는 0.5초마다 0 ~ 9까지 숫자를 출력
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("::: " + threadName + "쓰레드 종료 :::");
    }
}
public class Main {

    public static void main(String[] args) {
        System.out.println("::: main 쓰레드 시작");

	// main 쓰레드 > thread0, thread1 을 생성하고 실행
    
        MyThread thread0 = new MyThread();
        MyThread thread1 = new MyThread();

	// 총 세 개의 쓰레드(main, thread0, thread1) 병렬로 실행
    
        // 1. thread0 실행
        System.out.println("::: main 이 thread0 을 실행");
        thread0.start();

        // 2. thread1 실행
        System.out.println("::: main 이 thread1 을 실행");
        thread1.start();
        
        // start() 메서드를 호출하면 새로운 쓰레드가 생성 > run() 의 작업 내용이 실행

        System.out.println("::: main 쓰레드 종료");
    }
}

4. join()

  •  main() 쓰레드가 다른 쓰레드가 종료될 때까지 기다리게 하는 메서드
  • 호출한 쓰레드가 끝날 때까지 main() 쓰레드가 대기
  • 쓰레드가 너무 빨리 끝나지 않고 모든 작업이 완료된 후 종료되도록 할 때 유용
    > main() 쓰레드가 총 작업 완료 시간을 측정할 수 있게 만들 수 있음
public class Main {

    public static void main(String[] args) {
        System.out.println("::: main 쓰레드 시작");
        MyThread thread0 = new MyThread();
        MyThread thread1 = new MyThread();

        // 시작시간 기록
        long startTime = System.currentTimeMillis();

        // 1. thread0 시작
        System.out.println("thread0 시작");
        thread0.start();

        // 2. thread1 시작
        System.out.println("thread1 시작");
        thread1.start();

        // ⌛️ main 쓰레드 대기 시키기
        try {
            thread0.join();
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        long totalTime = endTime - startTime;
        System.out.println("총 작업 시간: " + totalTime + "ms");
        System.out.println("::: main 쓰레드 종료");
    }
}

 

5. Runnable 인터페이스 활용

 

1) 유지 보수성과 재사용성 향상

  • Thread쓰레드를 제어하기 위해 존재하는 클래스
  • Thread 클래스를 상속 > MyThread를 구현 > 실행 로직과 쓰레드 제어 로직이 결합되어 한 가지 클래스에서 두 가지 역할을 담당
  • Runnable 을 활용하면 실행 로직을 별도의 구현체로 분리
    Thread는 쓰레드를 제어하는 역할
    Runnable 구현체는 실행 로직을 관리
더보기
// ⚠️ Thread는 Thread 제어 역할 그리고 실행로직 두가지를 담당합니다.
public class MyThread extends Thread {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println("현재 시작된 쓰레드: " + threadName);

        for(int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드 : " + threadName + " - " + i);
            try {
                Thread.sleep(500); // 딜레이 0.5 초
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("종료된 쓰레드: " + threadName);
    }
}
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Main {

    public static void main(String[] args) {

        MyRunnable task = new MyRunnable(); // ✅ 하나의 작업 객체 선언

				// ✅ 하나의 작업을 여러 쓰레드에서 공유
        Thread thread0 = new Thread(task); // 작업 객체 공유
        Thread thread1 = new Thread(task); // 작업 객체 공유

				// 실행
        thread0.start();
        thread1.start();
    }
}

2) 기존 클래스를 유지하면서 확장 가능

  • Thread 를 상속 > MyThread 를 구현 = 다른 클래스 상속x
  • Runnable: 기존 클래스의 기능을 유지하면서 상속을 통해 확장
더보기
public class MyNewClass { // ✅ 새로운 클래스 

    public void printMessage() {
        System.out.println("MyClass 기능 실행");
    }
}
public class MyThread extends Thread, MyNewClass{ // ❌ 다중 상속 불가
		...
}
public class MyRunnable extends MyNewClass implements Runnable { // ✅ 다중 상속

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Main {

    public static void main(String[] args) {

        MyRunnable task = new MyRunnable();

        // ✅ 기존 클래스를 유지하면서 확장해서 활용
        task.printMessage(); 

        Thread thread0 = new Thread(task);
        Thread thread1 = new Thread(task);

        thread0.start();
        thread1.start();
    }
}

🐢 걷기반 1일차

 

1.  JAVA 는 객체 지향의 언어가 맞지만, 기본적으로는 main 에 있는 것들이 순서대로 실행된다 ( 위에서 아래, 좌에서 우 )

 

2. 변수는 값을 저장할 수 있고, 일반적으로 변수 명을 통해서 의미를 부여해준다.  ex > String menuName = "커피";

 

3. 조건문과 반복문 

1) 변수의 저장 만으로는 무엇인가 할 수 있지 않다. 어떤 액션이 필요하다

2) 대표적인 액션에는 + - * / 과 같은 연산이 있는데,

3) 언제 그러한 액션들을 취해줄지를 조건문 ( if/else, switch 등... ) 을 통해서 정해준다.

  • switch 에서, break; 나 default 는 필수는 아니다.
  • 다만, break 가 없으면, 걸린 case 이후 값들을 다 출력 해준다거나, default 가 없으면 출력이 우리가 허용해둔 범위 밖으로 나가서 문제가 생길 수 있음을 인지해야한다.

4) 반복문 역시 마찬가지다, 액션들을 "언제까지" 계속 반복할지 정해준다.

5) 다만 조건에 따라 반복할 경우 while, 일단 한번 하고 조건을 볼 경우 do-while, 횟수에 민감할 경우 for 를 써준다.

6) 물론, while로 count 같은 변수를 만들어 for 문처럼 횟수 제한을 걸거나, for 에 조건만 넣어서 while 처럼 조건부 반복을 계속 하는 게 불가능 한 것은 아니다.

7) 조건부 반복(while) 의 경우, 조건이 들어갈 공간에, 가능한 직접 true false 를 넣진 않는다-> boolean run = true; 같은 "변수"를 사전에 만들고, 그 값을 바꿔줌으로서 반복문을 탈출한다.  ( 변수는 재사용도, 추후에 값을 바꾸는 것도 가능하기때문에 !)

8) 물론 while(ture) 로 한다고 그것을 탈출 못한는 것은 아니다 -> return, break etc...

 

4. 메서드의 필요성과 어떻게 묶을지

  • 반드시 메서드로 묶어야 하는 법은 없다
  • 중복을 줄이고 가독성을 높이기 위해 사용 하는 것
  • 변수명 처럼, 메서드 명을 통해서 어떤 동작을 할 지 알 수 있다
  • 한마디로 "특정 행동에 이름을 붙이는 것" 이라 할 수 있다. -> 단어처럼 만들어서 굳이 풀어 쓸 필요 없이 단어로만 뜻이 통하게 만드는 것

5. 클래스의 필요성과 어떻게 다룰지, 클래스란 어떤것

  • 앞서 본 변수와 메서드를 보면, 변수는 어떤 대상이 갖고 있는 값 이고, 메서드는 그 대상이 할 수 있는 행동
  • 그 두 개 이상의 것들 ( 여러 변수, 여러 메서드) 를 합쳐주기위해 만들어 진 것이 클래스
  • 클래스는 그래서 가본적으로 상태 / 행동, 혹은 둘 다를 가진다

 

  •  

 

'🔌 SPARTA > Courses' 카테고리의 다른 글

걷기반 3회차: 생성자, this, Order  (0) 2026.01.21
걷기반 2회차: 배열(Array와 List)  (0) 2026.01.16
객체지향 특징  (0) 2026.01.14
객체지향 이해하기  (0) 2026.01.09
Java 기초문법(2)  (0) 2026.01.08