JAVA - 이재환의 자바 프로그래밍 입문

수현·2023년 9월 10일
0

Book

목록 보기
8/12

📒 자바 객체지향 프로그래밍

📕 1. 클래스의 기초

  • 1) 객체 (object)
    • 표현할 수 있는 실제 세계의 모든 사물들
    • 물리적인 객체/기념적인 객체로 구분
  • 2) 클래스 (class)
    • 객체를 추상화해서 기술해놓은 설계도 (객체를 데이터는 필드로, 동작은 메서드로 추상화)
    • 클래스의 특징적인 데이터와 처리 동작을 추려내는 과정(추상화) 필요
      • 명사적인 특징을 뽑아내는 추상화 과정 ➡️ 멤버 변수(필드) 추출
      • 동사적인 특징을 뽑아내는 추상화 과정 ➡️ 멤버 함수 (메서드) 추출
// 클래스 : 객체를 추상화 해놓은 것
class Npc
{
	// 필드 : 데이터
    String name;
    int hp;
    
    // 메서드 : 동작(기능)
    void say()
    {
    	System.out.println("안녕하세요");
     }
}

public class NpcUse
{
	public static void main(String[] args) {
    // 클래스를 이용해 객체 생성
    // Npc라는 설걔도(클래스) 이용해 Npc 객체 생성
    // 클래스 타입의 변수는 new를 통해 객체 생성
    // 클래스 타입의 참조 변수는 스택, 생성된 객체는 힙에 적재
    Npc saram1 = new Npc();
    
    // 필드 접근
    saram1.name = '경비'; // 멤버 변수에 직접 접근
    saram1.hp = 100;
    System.out.println(saram1.name + ":" + saram1.hp);
    
    // 메서드 호출
    saram1.say();
    }
}
  • 3) 객체와 클래스

    • 클래스를 객체로 만들기
      Book myBook = new Book();
      클래스 타입 변수 = 객체 생성 생성자
    • 실행 : java 파일명 으로 java.exe 실행 ➡️ JVM 만들고 파일명 클래스를 찾아 main() 실행)
  • 4) 오버로딩 (overloading)

    • 클래스 내에 매개변수 개수나 자료형은 다르지만 메서드 명은 같은 메서드를 여러 개 정의하는 것
    • 유사한 일을 수행하는 메서드가 전달하는 매개변수에 따라 다른 연산을 함
    class Calc
    {
    	int add(int a, int b)
      {
      	return a + b;
      }
      
      int add(int a)
      {
      	return a + 1;
      }
      
      double add(double a, double b)
      {
      	return a + b;
      }
    }
    
    public class OverloadingUse
    {
    	public static void main(String[] args)
      {
      	Calc calc = new Calc();
          int nRtn1 = calc.add(3, 9);
          int nRtn2 = calc.add(3);
          double nRtn3 = calc.add(3.0, 9.0);
          
          System.out.println("Rtn1 = " + nRtn1);
          System.out.println("Rtn2 = " + nRtn2);
          System.out.println("Rtn3 = " + nRtn3);
    }
  • 5) 생성자

    • 객체 생성을 할 때만 호출하는 메서드
    • 디폴트 생성자는 클래스 정의할 때 생성자를 기술하지 않으면 매개변수 없는 생성자 자동 생성
    • 특징
      • 생성자명은 클래스명과 똑같음
      • 메서드지만 반환형이 없는 형태
      • 디폴트 생성자는 매개변수 없음
  • 6) 접근 제한자

    • 클래스 외부에서 클래서 내부로의 변수와 메서드에 대한 접근을 제한 (정보 은닉화)
    • 멤버 변수 접근 제한해도 해당 멤버 변수를 사용할 수 있는 메소드 이용하여 변경 가능
    • 종류
      • public : 외부 클래스 어디에나 접근 가능
      • protected : 같은 패키지 내부나 상속 관계의 클래스에서만 접근 가능
      • (default) : 같은 패키지 내부에서만 접근 가능
      • private : 같은 클래스 내부에서만 접근 가능
    class Student1
    {
    	String name;
      int age;
    }
    
    class Stuedent2
    {
    	public String name;
      private int age; // private 멤버 변수
      
      public Student2(String name, int age)
      {
      	this.name = name; // 멤버변수와 매개변수명이 겹침
          this.age = age;
      }
      
      public int getAge()  // 게터 (값 가져오는 메서드
      {
      	return age;
      }
      
      public void setAge() // 세터 (변수에 값을 대입하는 메서드)
      {
      	if (age < 0 || age > 150) // 유효성 검사
          {
          	System.out.println("나이가 부적절합니다");
              this.age = 0;
              return;
          }
          this.age = age;
      }
    }
    
    public class PrivateUse
    {
    	public static void main(String[] args)
      {
      	Student1 student1 = new Student1();
          student1.name = "김수현"; // 멤버 변수 직접 접근
          student1.age = -20;
          
          Student2 student2 = new Student2("김수현", 20);
          student2.name = "손오공";
          // student2.age = -10; // 에러 발생
          student2.setAge(10);
          int age = student2.getAge();
      }
      
    }

📕 2. 자바의 메모리 모델

  • 1) 자바의 메모리 모델
    • 메서드 영역
      • 프로그램 실행에 대한 코드, 스태틱 변수 및 메서드, 런타임 상수 풀이 메서드 영역에 생성
      • 영역에 저장된 내용은 프로그램 시작 전에 로드되고, 프로그램 종료시 소멸
      • 런타임 상수 풀 : 컴파일 타임에 알려진 숫자 리터럴부터 런타임에 확인되어야 하는 메서드 및 필드 참조 등 상수 포함
    • 스택 영역
      • 메서드가 호출되면 지역 변수, 매개변수가 프레임 형태로 생성되어 스택 영역에 저장 후 삭제
      • 동일하지 않은 프레임에 있는 메서드의 변수들은 서로 참조X
    • 힙 영역
      • 클래스의 객체(인스턴스), 배열이 new 연산자에 의해 동적으로 생성
      • 생성된 객체는 자동 저장소 관리 시스템인 가비지 컬렉터에 의해 사용이 없으면 자동으로 제거
    • main
      • JVM은 무조건 메서드 영역 내 스태틱 영역에서 main() 메서드를 첫 메서드로 실행시킴 (없으면 프로그램 실행X)
      • JVM에 전달한 클래스는 main() 메서드가 반드시 있어야 하고, public으로 접근 가능

📖 참고 📖 가비지 컬렉션

  • 수행하는 동안에는 모든 스레드가 멈춤
    • 실행 타이밍은 시스템의 성능에 영향을 미치지 않도록 별도의 알고리즘으로 실행됨
  • 가비지 컬렉션 발생하면, 소멸 대상이 되는 인스턴스가 결정되지만 바로 소멸X
    • 종료가 되면 객체는 운영체제에 의해 소멸됨
    • 반드시 객체 소멸하기 원한다면 finalize() 메서드 호출
  • 실행 : System.gc();
  • 객체 소멸 : System.runFinallization();

📕 3. 스태틱의 이해

  • 1) 스태틱

    • static 예약어 표시를 하여 메모리의 특정 영역에 따로, 미리 로딩시킨 것
    • 스태틱 예약어는 변수, 영역, 메서드에 붙일 수 있음
    • static 변수(정적 변수)는 값이 메모리에 로딩될 때 대입되고 블록이 있다면 메모리에 로딩될 때 실행함
    • 어떤 객체에서도 접근해서 사용 가능
  • 2) 전역 변수로 사용

    • 메서드 영역 내 스태틱 영역의 변수/메서드는 어떤 객체에서도 접근해서 사용 가능
    class Cat {
    	static int a = 5; // 스태틱 변수 (전역 변수)
      int num = 3; // 인스턴스 변수
      
      void printValue(int num) {
      	this.num = num;
          System.out.println("num : " + this.num);
          System.out.println("a : " + a);
      }
    }
    
    public class Ex01{
    	public static void main(String[] args) {
      	int num = 2;
          
          Cat cat1 = new Cat(); // cat1 변수 - 스택, Cat클래스형 객체 - 힙에 생성
          cat1.num = 5; // 힙 영역에 생성된 객체 안에 존재
          cat1.a = 10; // static 영역
      }
    }
  • 3) main보다 먼저 실행

    • 인스턴스 생성과 관계 없이 static 변수가 메모리 공간에 할당될 때 실행됨
    • 프로그램 시작 전 메서드 영역의 스태틱 영역에 로드되면서 값 대입 (기본 자료형은 0으로 대입)
    • 스태틱 변수 단점
      • 힙 영역을 사용하지 않고, 메서드 영역의 일부분만 사용하여 메모리 사용이 비효율적
      • 한 객체가 가지고 있는 데이터들은 외부에서 함부로 접근하여 수정할 수 없도록 해야한다는 객체지향 프로그래밍 원칙에 위배
  • 4) 유틸 메서드로 사용

    • 특정 기능이 필요한데 자주 사용된다면 많은 클래스에서 중복되어 만들어지지 않도록 하는 의도
    • 스태틱 영역에 만들어져서 클래스에서 얼마를 사용하든 메모리에는 1번만 올라와 있음
    • static을 메서드에 붙여주면, 객체 생성 없이 클래스명.메서드명 형식으로 유틸 메서드 사용
    • System.out.println()
      • System 클래스의 멤버 변수 out은 객체를 참조
      • 그 참조한 객체의 println() 메서드를 이용해 출력 기능 제공
    public calss MyCalculator
    {
    	public static int add(int n1, int n2)
      {
      	return n1 + n2;
      }
    }
    
    public class Ex {
    	public static void main(Stiring[] args) 
      {
      	MyCalculator calc1= new MyCalculator(); // 객체 생성 후 사용
          int num1 = calc1.add(1, 2);
          
          int num2 = MyCalculator.add(2,3); // 새로 객체 생성하지 않고 사용
      }
    }

📕 4. 클래스의 상속

  • 1) 상속
    • 클래스가 가지고 있는 멤버를 다른 클래스에게 계승시키는 것
    • 상속한 멤버는 자식 클래스에서 정의하지 않아도 사용 가능
    • 자식 클래스 내에서 멤버를 추가로 정의해서 사용 가능
    • 구현 : extends 예약어 사용
    class 자식 클래스 extends 부모 클래스
    {
    }
    • 특징
      • private으로 접근 제한되어 있는 멤버들은 상속X
    • 장점
      • 클래스 간의 전체 계층 구조를 파악하기 쉬움
      • 재사용성 증대 : 기존 클래스에 있는 것을 재사용 가능
      • 확장 용이 : 새로운 클래스, 데이터, 메서드 추가하기 쉬움
      • 유지보수 용이 : 데이터와 메서드를 변경할 때 상위에 있는 것만 수정하여 전체적으로 일관성 유지
    • 호칭
      • 슈퍼 클래스 ↔️ 서브 클래스
      • 부모 클래스 ↔️ 자식 클래스
      • 기반 클래스 ↔️ 파생 클래스
      • 조상 클래스 ↔️ 자손 클래스
      • 상위 클래스 ↔️하위 클래스

📖 참고 📖 자바의 다중 상속

  • 여러 클래스를 동시에 상속하는 다중 상속 지원X (단계별 상속 사용)
  • 2개 이상의 상위 클래스에 같은 이름의 메서드가 정의되어 있다면, 다중 상속을 받는 하위 클래스는 어떤 클래스의 메서드를 상속받아 사용해야하는지 혼동
  • 2) 오버라이딩 (overriding)

    • 상속된 메서드와 동일한 이름, 동일한 매개변수, 동일한 반환형을 가지는 메서드르르 정의하여 메서드를 덮어쓰는 것
    • 목적
      • 상속받은 부모 클래스 메서드의 기능 변경
      • 상속받은 부모 클래스 메서드에 기능 추가
    class Unit // 부모 클래스
    {
    	String	name;
      int 	hp;
      
      void printUnit()
      {
      	System.out.println("이름 : " + name);
          System.out.println("HP : " + hp);
      }
    }
    
    class Marine extends Unit // 자식 클래스
    {
    	int	attack;
      
      void printUnit() // 오버라이딩
      {
      	System.out.println("this 이름 : " + name);
          System.out.println("this HP : " + hp);
      }
      
      void printMarine()
      {
      	printUnit(); // 상속 받은 기능
          System.out.println("공격력 : " + attack);
      }
    }
    
    public class MyTerran
    {
    	public static void main(String[] args)
      {
      	Marine unit1 = new Marine(); // 객체 생성
          unit1.name = "마린";
          unit1.hp = 100;
          unit1.attack = 20;
          
          unit1.printMarine();
      }
    }
  • 3) 상속이 제한되는 final

    • 필드, 메서드, 클래스에 붙이는 final 예약어
    • final 변수 : 상수
    • final 메서드 : 하위 클래스에서 오버라이딩 할 수 없음
    • final 클래스 : 상속 할 수 없음
  • 4) 추상 클래스

    • 구체적인 처리 내용을 기술하지 않고, 호출하는 방법만을 정의한 메서드 (↔️ 구상 메서드 : 메서드 표현) (구조)
    • abstract 클래스 : 추상 메서드를 가지고 있음
    • abstract 메서드 : 기능이 구현되지 않고 호출 방법만 있는 메서드
    • 구현 : abstract 예약어 사용
    • 특징
      • 객체 생성할 수 없음
      • 상속받은 클래스의 기능을 미리 지정하기 위해서 사용
    abstract class Unit // 부모 클래스
    {
    	String 	name; // 멤버 변수
      int		hp;
      
    	abstract void doMove(); // 추상 메서드
      
      void printUnit() // 구상 메서드
      {
      	System.out.println("이름 : " + name);
          System.out.println("HP : " + hp);
      }
    }
    
    class Marine extends Unit // 자식 클래스
    {
    	void doMove() // 구체적인 기능 구현(오버라이딩)
      {
      	System.out.println("마린은 두 발로 이동");
      }
    }
    
    public class MyStarcraft
    {
    	public static void main(Sting[] args)
      {
      	Marine unit1 = new Marine(); // 객체 생성
          unit1.doMove(); // 객체 메서드 호출
      }
    }
  • 5) 인터페이스

    • 상속 관계가 아닌 클래스에 기능을 제공하는 구조
    • 인터페이스의 메서드는 추상 메서드이므로 구상 메서드로 오버라이드해서 구현 필요
    • 구현 : implements 예약어 사용
    • 특징
      • 인터페이스간 상속 가능
      • 인터페이스가 일반 클래스 상속 못함
      • 인터페이스는 다중 상속/다중 구현 가능
    interface A
    {
    	public static final int a = 2; // 스태틱 상수 정의 (public static final 생략 가능)
      public abstract void say(); // 추상 메서드 (public abstract 생략 가능)
      public default void desc() // 디폴트 메서드
      {
      	System.out.println("기능이 구현된 메서드");
      }
    }
    
    interface A extends B, C, D  // 인터페이스 다중 상속
    {
    	...
    }
    
    class X implements B, C, D // 다중 인터페이스 구현
    {
    	...
    }
    
    class X extends Y implements B // 상속과 인터페이스 
    {
    	...
    }
    • 디폴트 메서드 : 하휘 호환성을 유지하면서 기존 인터페이스기 새로운 기능 추가 가능
    interface X
    {
    	void method1();
      default void method2() {};; // 디폴트 메서드
    }
    
    class B implements X
    {
    	void method1() {};
      void method2() {}; // 오버라이딩
    }
  • 6) 다형성 (polymorphism)

    • 하나의 객체와 메서드가 많은 형태를 가지고 있는 것
    abstract class Calc
    {
    	int a = 5;
      int b = 6;
      
      abstract void plus();
    }
    
    class MyCalc extends Calc
    {
    	void plus() { System.out.println(a + b);
      void minus() { System.out.println(a - b);
    }
    
    pulbic class Ex01
    {
    	public static void main(String[] args)
      {
      	MyCalc mc1 = new MyCalc();
          mc1.plus();
          mc1.minus();
          
          Calc mc2 = new MyCalc(); //하위 클래스 객체를 상위 클래스 객체에 대입
          mc2.plus();
          // mc2.minus(); // 오류 발생 
      }
    }
  • 7) instanceof 연산자

    • 다형성을 사용하려고 이용하는 연산자
    • 객체가 지정한 클래스 형의 객체인지 조사하는 연산자
      boolean bCheck = obj(클래스형 변수) instanceof MyClaa(클래스명);
    • 지정한 인터페이스를 오브젝트가 구현하고 있는지를 조사 가능
      boolean bCheck = obj(클래스형 변수) instanceof MyInterface(인터페이스명);
    interface Cry
    {
    	void cry();
    }
    
    class Cat implements Cry
    {
    	public void cry()
      {
      	System.out.println("야옹");
      }
    }
    
    class Dog implements Cry
    {
    	public void cry()
      {
      	System.out.println("멍멍");
      }
    }
    
    public class Ex03{
    	public static void main(String[] args)
      {
      	Cry test1 = new Cat();
          
          if (test1 instanceof Cat) // test1에 있는 참조값이 어떤 객체 가르키는지 조사 (참)
          	test1.cry();
          else if (test1 instanceof Dog)
          	System.out.println("고양이가 아닙니다");
      }
    }

📕 5. 패키지와 클래스 패스

  • 1) 클래스 패스
    • 자바 가상 머신이 클래스 실행시키는 방법
      • 같은 폴더에서 클래스 파일을 찾아서 실행
      • 경로를 지정하면 그 경로에 있는 클래스 파일을 찾아서 실행 (경로 지정은 클래스 패스/패키지 이용 가능)
      • 같은 폴더나 지정된 경로에서 클래스 파일을 찾지 못했다면 클래스 패스에 지정된 폴더에서 찾아서 실행
    • 환경 변수로 클래스 패스 지정 가능
      • '시스템 환경 변수 편집'에서 CLASSPATH 변수를 만들어 클래스 패스로 사용할 폴더 등록
  • 2) 패키지
    • 클래스를 묶어 폴더로 구분하여 관리하는 방법
    • 클래스명이 충돌하는 것을 방지
  • 3) 임포트
    • 매번 패키지명.클래스명으로 사용하게 되면 불편
    • 패키지명.클래스명을 import하여 사용
  • 4) 자바 기본 제공 패키지와 클래스
    • java.lang (기본적인 클래스 포함)
      • 예외적으로 import 필요X
      • String 클래스 포함
      • 🗒️ 예시 : System.out.println 메서드
    • java.io (입출력 관련 클래스 포함)
    • java.net (네트워크 관련 클래스 포함)

📕 6. String 클래스

  • 1) String 선언 방법

    • 문자열은 글자들을 큰따옴표로 묶은 값
    • 종류
      • String str1 = new String("김");
        ➡️ new 연산자와 문자열 리터럴 매개변수가 있는 생성자를 이용하여 객체를 힙에 만들고 그 참조값을 변수에 대입 (객체를 무조건 새로 만듦)
      • String str2 = "수";
        ➡️ 문자열 리터럴을 직접 대입 (이미 만들어진 객체 있다면 객체의 참조값을 변수에 대입)
  • 2) 문자열형 변수의 참조 비교

    pulbic class Ex02{
    	public static void main(String[] args)
       {
       	String str1 = new String("java"); // str1 != str2
           String str2 = new String("java");
           String str3 = "java";             // str3 = str4
           String str4 = "java";
           
       }
    }
  • 3) String 클래스의 메서드

    • eqauls() : 변수의 내용이 같은지 비교
      • A.equals(B)
    • compareTo() : 변수의 내용이 크다/같다/작다 사전순 비교
      • A.compareTo(B)
      • A.compareToIgnoreCase(B) // 대소문자 구분 없이 비교
    • concat() : 문자열 합치기
      • A.concat(B)
      • A += B // 컴파일러가 자동 변환
    • indexOf() : 문자열에서 문자의 위치 반환
      • num = A.indexOf(B)
      • A.indoxOf(B, num + 1) // 그다음 B위치 반환
    • substring() : 문자열에서 특정 위치의 문자열 잘라냄
      • A.substring(B) // B 위치 이후 반환
      • A.substring(B, C) // B와 C 사이에 구간 반환
    • length() : 문자열 길이
      • A.length()
    • contains() : 문자열이 포함되어있는지 조사
      • A.contains(B)
    • startsWith() : 시작하는 문자열이 s인지 조사
      • A.startWith(B)
    • endsWith() : 끝나는 문자열이 s인지 조사
      • A.endsWith(B)
    • isEmpty() : 문자열의 길이가 0이면 true 변환
    • toLowerCase() : 소문자 변환
    • toUpperCase() : 대문자 변환
    • trim() : 앞뒤 공백 제거한 후 변환
    • String.valueOf() : 기본 자료형의 값을 문자열로 변환
      • double e
        String s = String.valueOf(e);
    • StringBuilder 클래스 : 문자열 연결 (내부에 변경 가능한 char[] 변수 가짐)
      • append("문자열") // 추가
      • delete(0, 3) // 구간 삭제
      • replace(3, 4, "문자열") // 값 변경
      • reverse() // 순서 반전
    • StringBuffer 클래스 : StringBuilder와 기능 동일하지만, 스레드에 불안전함
    • StringTokenizer 클래스 : 문자열 분할 (토큰으로 분할)
      • StringTokenizer("문자열", "자를 구분자")
      • hasMoreTokens() // 토큰이 있으면 true 반환
      • nextToken() // 토큰을 차례대로 가져옴

📕 7. 배열

  • 1) 배열 선언

    • 똑같은 자료형을 저장
    • 자료형[] 변수명 = new 자료형[개수];
    • 자료형 변수명[] = new 자료형[개수];
    • 배열 생성 및 초기화
      • int[] arr = new int[] {1, 2, 3};
      • int[] arr = {1, 2, 3};
  • 2) 기본 자료형

    • int[] arr = new int[3];
      arr[0] = 100;
  • 3) String 형

    • String[] name = new String[3];
      name[0] = new String("kim"); // 문자열 리터럴로 대입X
  • 4) 클래스형

    • Dog[] arr = new Dog[3];
      arr[0] = new Dog("멍멍"); // 배열에 객체 저장(초기화)
  • 5) 매개변수, 반환형

    • pubic static int[] makeArray(int len);
      int [] arr = new int[3];
      return arr;
  • 6) for ~ each문

    • 배열의 길이만큼 반복하는 코드
    int[] arr = {1, 2, 3, 4, 5};
    
    for (int e : arr)
    	sum += e;
  • 7) 배열 관련 유틸리티 메서드

    • fill() : 배열의 초기화
      • fill(int[] arr, int val) // val 값으로 배열 초기화
      • fill(int[] arr, int fromIndex, int toIndex, int val)
    • copyOf()
      • copyOf(int[] original, int newLength) // original을 newLength 길이만큼 복사
      • copyOfRange(int[] original, int from, int to)
      • arraycopy(Object src, int srcPos, Object dest, int destPos, int length) // src의 srcPos에서 dest의 destPos로 length만큼 복사
    • equals() : 두 배열의 데이터 비교 (배열 길이 다르면 false)
    • sort() : 배열 내용 오름차순 정렬

📕 8. 예외 처리

  • 1) 예외와 에러

    • 에러
      • 컴파일 에러
      • 런타임 에러 : (예측 불가능) 시스템 에러, (예측 가능) 예외
    • 예외 발생시
      • 프로그램 정상 종료
      • 예외 발생시 무시하고 프로그램 계속 실행
  • 2) 예외 종류

    • 실행 예외
      • 예외 처리를 하지 않아도 컴파일할 수 있는 비검사형 예외
      • 실행 단계에서 체크
      • ArithmeticException : 0으로 나누기와 같은 부적절한 산술 연산 수행할 때 발생
      • IllegalArgumentException : 메서드에 부적절한 매개변수를 전달할 때 발생
      • IndexOutOfBoundException : 배열, 벡터 등에서 범위를 벗어난 인덱스를 사용할 때 발생
      • NoSuchElementException : 요구한 원소가 없을 때 발생
      • NullPointerException : null값을 가진 참조 변수에 접근할 때 발생
      • NuberFormatException : 숫자로 바꿀 수 없는 문자열을 숫자로 변환하려 할 때 발생
    • 일반 예외
      • 예외 처리를 하지 않으면 컴파일 오류가 발생해서 꼭 처리해야 하는 검사형 예외
      • 컴파일 단계에서 체크
      • ClassNotFoundException : 존재하지 않는 클래슬르 사용하려고 할 때 발생
      • NoSuchFieldException : 클래스가 명시한 필드를 포함하지 않을 때 발생
      • NoSuchMethodException : 클래스가 명시한 메서드를 포함하지 않을 때 발생
      • IOException : 데이터 읽기 쓰기 같은 입출력 문제가 있을 때 발생
  • 3) 예외 처리하기

    • try {}
      catch(예외 타입) {}
      finally { 이 부분 마지막에 무조건 실행 }
    • 예외 처리 합치기 : catch (예외1 || 예외2)
    • 모든 예외 한번에 처리 : catch (Exception e)
    import java.util.Scanner;
    import java.util.InputMismatchException;
    
    public class Ex03 {
    	public static void main(String[] args)
      {
      	Scanner sc = new Scanner(System.in);
          
          try
          {
          	int num1 = sc.nextInt(); // 에러 발생 시점
              int num2 = 10 / num1; // 에러 발생 시점
              System.out.println(num2);
          }
          catch (InputMismatchException e)
          {
          	System.out.println(e.getMessage());
              // e.printStactTrace();
          }
          catch (ArithmeticException e)
          {
          	String str = e.getMessage();
              System.out.println(str);
              if (str.equals("/ by zero"))
              	System.out.println("0으로 나눌 수 없습니다");
          }
          finally // 반드시 실행
          {
          	System.out.println("Good Bye");
          }
      }
    }
  • 4) 예외 처리 미루기(던지기)

    • 예외가 발생한 메서드에서 처리하지 않고 메서드를 호출한 곳으로 예외를 던져 호출 부분에서 예외 처리
    • 던져진 예외를 처리하기 위해 Exception 상위 객체인 Throwable 사용
    import java.util.Scanner;
    
    public class Ex07 {
    	public static void myMethod1()
      {
      	myMethod2(); 
      }
      
      public static void myMethod2()
      {
      	Scanner sc = new Scanner(System.in);
          
          int num1 = sc.nextInt(); // 에러 발생 시점
          int num2 = 10 / num1; // 에러 발생 시점
          System.out.println(num2);
      }
      
      public static void main(String[] args) {
      	try
          {
          	myMethod1(); // 여기로 myMethod1으로부터 예외가 넘어옴
          }
          catch (Trowable e)
          {
          	e.printStackTrace();
              System.out.println(e.getMessage());
          }
      }
    }
  • 5) 메서드에 예외 선언

    • 메서드의 선언부만 보아도 메서드 사용시 어떤 예외를 처리하면 되는지 쉽게 알 수 있음
    public static void myMethod2() throws ArimeticException, InputMismatchException
      {
      	Scanner sc = new Scanner(System.in);
          
          int num1 = sc.nextInt(); // 에러 발생 시점
          int num2 = 10 / num1; // 에러 발생 시점
          System.out.println(num2);
      }

📕 9. 자바의 기본 클래스

  • 1) java.lang 클래스

    • 자동 import됨
    • lib > src.zip 파일 안에 위치
    • Object : 최상위 클래스로 기본적인 메서드 제공
    • String, StringBuffer, StringBuilder : 문자열을 처리하는 메서드 제공
    • Number, Integer, Long, Float, Double : 기본형 데이터를 객체화
    • System : 시스템 정보나 입출력을 처리하는 메서드 제공
    • Math : 각종 수학 함수 제공
    • Thread : 스레드 처리하는 메서드 제공
    • Class : 실행 중에 클래스 정보 제공
  • 2) Object 클래스

    • 모든 자바 클래스의 최상위 클래스
    • 모든 자바 클래스는 Object 클래스로부터 상속 받음 (extends Object 자동 사용)
    • Object 클래스 메서드
      • toString() : 객체의 문자 정보 반환
      • equals(Object obj) : 두 객체가 동일한지 여부 반환 (객체의 주소값 비교, 보통은 오버라이딩하여 객체 안 변수값 비교로 사용)
      • hashcode() : 객체의 해시 코드 반환
      • clone() : 객체의 사본 생성
  • 3) 래퍼 클래스

    • 기본 자료형(정수형, 문자형, 논리형)에 대해서 객체로 인식되도록 포장한 클래스

    • 기본 자료형 대신 래퍼 클래스 사용 이유

      • 클래스가 제공하는 편리한 메서드 사용 (값/형/진법 변환)
      • 클래스가 제공하는 상수 사용 (MIN_VALUE, MAX_VALUE)
      • 메서드 매개변수 형이 Object여서 기본 자료형을 사용 못하고 클래스 형태인 래퍼로 넘겨야 할 때 상요 (컬렉션 프레임워크)
    • 래퍼 클래스 메서드

      • boolean
      • byte
      • char
      • short
      • int
      • long
      • float
      • double
      • valueOf()
      • max()
      • min()
      • sum()
      • toBinaryString()
      • toOctalString()
      • toHexString()
    • Nuber 클래스

      • 수치형 래퍼 클래스가 상속하는 추상 클래스
      • byteValue()
      • shortValue()
      • intValue()
      • longValue()
      • floatValue()
      • doubleValue()
      //Integer num = new Integer(20); 
      Integer num = integer.valueOf(20);
      
      System.out.println(num.intValue());
      System.out.println(num.doubleValue()); // 형변환하여 반환 
    • 문자열 ➡️ 수치형 형변환

      • parsePyte()
      • parseShort()
      • parseInt()
      • parseLong()
      • parseFloat()
      • parseDouble()
    • 오브젝트의 비교

      • 래퍼 클래스의 오브젝트끼리 비교하려면 == 대신 equals() 사용
    • 박싱 / 언박싱

      • 박싱 : 기본 타입 ➡️ 래퍼 클래스 (인스턴스 생성시)
      • 언박싱 : 래퍼 클래스 ➡️ 기본 타입 (래퍼 클래스에 정의된 메서드 호출시)
    // 박싱
    Integer iObj = Integer.valueOf(10);
    Double dObj = Double.valueOf(3.14);
    
    // 오토박싱
    Integer iObj2 = 10;
    Double dObj2 = 3.14;
    
    // 메서드 호출을 통한 언박싱
    int num1 = iObj.intValue();
    double num2 = dObj.doubleValue();
    
    // 오토 언박싱
    int num1 = iObj2;
    double num2 = dObj2;
    
    // 래퍼 인스턴스 값 증가 방법
    iObj = Integer.valueOf(iObj.intValue() + 10);
    dObj = Double.valueOf(dObj.doubleValue() + 1.2);
  • 4) Math 클래스

    • 정의된 메서드는 모두 static 선언
    • 기능 제공이 목적이고, 인스턴스 생성 목적X
    • Math 클래스의 메서드
      • sqrt() : 제곱근
      • log() : 로그
      • pow() : 지수
      • PI() : 원주율
      • toRadians() : 라디안으로 변환
      • sin() : 사인
      • cos() : 코사인
      • tan() : 탄젠트
  • 5) Random 클래스

    • 임의의 랜덤 값을 만들어 낼 때 사용한느 클래스
    • Random 클래스의 메서드
      • nextBoolean()
      • nextInt()
      • nextLong()
      • nextInt()
      • nextFloat()
      • nextDouble()
  • 6) Arrays 클래스

    • 배열의 초기화, 값 채우기, 복사, 정렬 기능
    • Arrays 클래스의 메서드
      • Arrays.eqauls() : 객체 저장 배열의 비교 (기본형 주소값 비교, 오버라이딩하여 사용)
      • Arrays.sort() : 객체 저장 배열의 정렬

📕 10. 열거형, 가변 인수, 어노테이션

  • 1) 열거형

    • 서로 관련있는 상수들을 모아 놓고 대표할 수 있는 이름을 정의
    • 자바에서 열거형을 클래스처럼 사용
    • enum 이름 {
      // 요소 나열
      }
    • 열거형으로 모호함 피함 (상수 사용시 의미의 모호함 해결)
    /* interface의 경우 (public static final 변수)
    interface Human1 {
    	int MAN = 1;   // 같은 값 가지고 있음
      int WOMAN = 2; // 잘못 사용하면 의미 전달에 있어 모호함
    }
    
    interface Machine1 {
    	int TANK = 1;
      int AIRPLANE = 2;
    }
    */
    
    enum Human2 {MAN, WOMAN}
    enum Machine2 {TANK, AIRPLANE}
    
    public class Ex02 {
    	public calss static void main(String[] args) {
      	createUnit(Machine2.TANK); // 알맞은 상수 사용
          createUnit(Human2.MAN); // 에러 (값이 동일한 잘못된 상수 사용)
          
          if (Hunam2.MAN == 0) // 에러 (숫자로 비교하면 에러가 남)
      }
    }
  • 2) 가변 인수

    • 메서드 인수 개수가 가변적인 것
    • 가변 인수는 항상 마지막에 위치
    • 컴파일러가 배열 기반 코드로 수정하여 처리
    public static void Hello(String ... vargs) // 가변 인수 표시
    {
    	for (String s : vargs) // 가변 인수 사용
      	System.out.println(s);
    }
  • 3) 어노테이션

    • 자바 소스 코드에 추가하여 사용할 수 있는 메타 데이터

    • @Override

      • 오버라이딩을 올바르게 했는지 컴파일러가 체크
      • 오버라이딩 할 때 메서드명을 잘못 적는 실수하는 것을 방지
      interface Unit4
      {
      	public void move(String str);
      }
      
      class Human4 implements Unit4
      {
      	@Override
          public void move(String str) // 오버라이딩
          {
          	System.out.println(str);
          }
      }
    • @Deprecated

      • 문제의 발생 소지가 있거나 개선된 기능의 다른 것으로 대체되어서 더 이상 필요 없음을 의미
      • 호환성 유지를 위해서 존재하지만 이후에 사라질 수 도 있는 클래스/메서드
      interface Unit5
      {
      	@Deprecated
          public void move(String str); // move가 run으로 대체됨
          public void run(String str);
      }
    • @SuppressWarings

      • Deprecated는 경고 메시지만 표시되고, 실제로 없어지지 않음
      • 경고 등 특정 메시지를 지정하면 해당 경고 메시지 출력X
      interface Unit5
      {
      	@Deprecated
          public void move(String str); // move가 run으로 대체됨
          public void run(String str);
      }
      
      class Human5 implements Unit5
      {
      				@Override
          @SuppressWarings("deprecation")
          public void move(String str)
          {
          	System.out.println(str);
          }
          
          @Override
          public void run (String str)
          {
          	System.out.println(str);
          }
      }

📒 자바 클래스 응용 프로그래밍

📕 1. 제네릭

  • 1) 제네릭의 필요성

    • 제네릭을 사용하지 않으면, 객체를 돌려받을 때 형변환 필요
    • 프로그래머가 실수를 해도 확인이 어려움
    class NPC 
    {
    	public String toString() {
      	return "This is a NPC"; // 기대 출력 결과
      }
    }
    
    class Camp
    {
    	private Object unit;
      
      public void set(Object unit) {
      	this.unit = unit;
      }
      
      public Object get() {
      	return unit;
      }
    }
    
    public class Ex03 {
    	public static void main(String[] args) {
      	// 객체 생성
          Camp human =  new Camp();
          
          // (1) 자식 객체를 부모 타입의변수에 대입
          human.set(new NPC());
          
          // (2) 만약, Object가 아닌 잘못된 String 대입한다면
          human.set("난 공룡");
          
          // 꺼낼 때 형변환 필요
          NPC unit = (NPC)human.get();
          
          
          // (1)의 결과 : This is a NPC
          // (2)의 결과 : 난 공룡 (+ 에러 발생)
          System.out.println(unit); 
      }
    }
  • 2) 제네릭 기반의 클래스 정의

    • 제너릭은 클래스, 메서드에서 사용할 자료형을 나중에 확정하는 기법
    • 클래스나 메서드를 선언할 때가 아닌 사용할 때(객체를 생성할 때, 메서드 호출할 때) 정함
    • 구현
      클래스명<타입 매개변수>(매개변수화 타입) i = new 클래스명<타입 인수>();
      class 클래스명<T(타입 매개변수)> {}
    • 타입 매개변수 규칙
      • 보통 1문자, 대문자
      • E (element)
      • K (key)
      • N (number)
      • T (type)
      • V (value)
    • 장점
      • 중복된 코드의 결합 & 간소화
      • 데이터 가져올 때 형변환 없이 가져옴
      • 데이터 대입시 다른 자료형 대입되는 것 방지 (강한 자료형 체크)
    // 제네릭 사용 결과
    class NPC 
    {
    	public String toString() {
      	return "This is a NPC"; 
      }
    }
    
    class Camp<T> {
    	private T unit;
      
      public void set(T Unit) {
      	this.unit = unit;
      }
      
      public T get() {
      	return unit;
      }
    }
    
    public class Ex05 {
    	public static void main(String[] args) {
      	Camp<NPC> human = new Camp<>();
          
          human.set(new NPC());
          
          NPC unit = human.get();
          
          System.out.println(unit);
      }
    }
  • 3) 매개변수 여러 개일 때 제네릭 클래스 정의

    class Camp<T1, T2>
    {
    	private T1 param1;
      private T2 param2;
      
      public void set(T1 o1, T2, o2)
      {
      	param1 = o1;
          param2 = 2;
      }
      
      public String toString()
      {
      	return param1 + '&' + param2;
       }
    }
    
    public class Ex07{
    	public static void main(String[] args)
      {
      	Camp<String, Integer> camp = new Camp<>();
          
          camp.set("Apple", 25);
          System.out.println(camp);
      }
    }
  • 4) 제네릭 클래스의 매개변수 타입 제한

    • 상속 관계를 표시하여 매개변수의 타입 제한 가능
    • 인스턴스 생성시 타입 인수로 Number/상속하는 클래스만 올 수 있도록 설정
    // 매개변수 타입을 제한하지 않은 경우
    class Camp<T> {
    	private T ob;
      ...
      
      public int toIntValue() {
      	return ob.intValue(); // 에러 (아무 자료형이나 들어올 수 있음)
      }
    }
    
    // 매개변수 타입을 제한하는 경우
    class Camp<T extends Number> { // Number로 제한
    	private T ob;
      ...
      
      public int toIntValue() {
      	return ob.intValue(); // 정상
      }
    }
  • 5) 제네릭 메서드의 정의

    • 클래스가 아닌 메서드 하나에 대해서도 제네릭 정의 가능
    • 제네릭 메서드는 메서드 호출 시점에 결정됨
    • 타입 인수 생략 가능 (생략된 이수는 들어온 데이터 자료형으로 추론)
    class MyData 
    {
    	public static <T> T showData(T data) // 앞의 <T>가 뒤의 매개변수 자료형을 결정함
      {
      	if (data instanceof String)
          	System.out.println("String");
          else if (data instanceof Integer)
          	System.out.println("Integer");
          else if (data instanceof Double)
          	System.out.println("Double");
      }
    }
    
    public class Ex09 {
    	public static void main(String[] args) {
      	MyData.<String>showData("Hello"); // 메서드 호출 시점에 데이터 타입 결정
          MyData.showData(1); // 타입 인수 생략
          MyData.showData(1.0);
      }
    }

📕 2. 컬렉션 프레임워크

  • 1) 자료구조
    • 대량의 데이터를 효율적으로 관리하는 메커니즘
    • 종류
      • 배열 (크기 고정, 데이터 추가/삭제 불가)
      • 리스트 (원소가 원소를 가리켜서 관리, 데이터 추가/삭제 쉬움)
      • 스택 (한 쪽 끝에서만 자료 넣고 빼는 선형 구조)
      • 큐 (먼저 집어넣은 데이터가 먼저 나오는 구조)
      • 트리 (부모 노드 밑에 여러 자식 노드가 연결되는 구조)
  • 2) 컬렉션 프레임워크의 구조
    • 개발자가 자료구조를 편리하게 사용할 수 있도록 컬렉션 프레임워크 제공
    • 컬렉션 프레임워크에서 제공한느 인터페이스는 상속관계
      • Map<K, V>
      • List<E> ➡️ Collection<E> ➡️ Iterable<E>
        Set<E>
        Queue<E>
    • 컬렉션 프레임워크에 속하는 인터페이스
      • List<E> : 순서가 있는 데이터 집합 (데이터 중복 허용)
      • Set<E> : 순서 유지되지 않느 집합 (데이터 중복X)
      • Map<K, V> : 키(key)와 값(value)로 이루어진 데이터 집합 (키는 중복X, 값은 중복 허용)
  • 3) List<E> 인터페이스를 구현하는 컬렉션 클래스
    • 데이터 저장 순서 유지

    • 동일 데이터의 중복 저장 허용

    • ArrayList<E> : 배열 기반 자료구조 (배열 이용)

      • 장점 : 객체의 참조 속도 빠름
      • 단점 : 객체 추가시 저장 공간을 늘리는 과정에서 시간 소요, 객체 삭제시 많은 연산 필요
      import java.util.ArrayList;
      import jaava.util.List;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	List<String> list = new ArrayList<>();
              
              // 객체 저장 (순서 있음, 중복 허용)
              list.add("orange");
              list.add("apple");
              list.add("apple"); 
              list.add("banana"); // orange, apple, apple, banana
              
              // 객체 참조
              for (int i = 0; i < list.size(); i++) 
              	System.out.print(list.get(i) + '\t');
                  
              // 첫 번째 객체 삭제
              list.remove(0); // apple, apple, banana
          }
      }
    • LinkedList<E> : 연결 기반 자료구조 (리스트 이용)

      • 장점 : 객체 추가, 삭제 속도 빠름 (저장 공간 늘리는 과정 간단)
      • 단점 : 객체 참조 과정이 복잡
      import java.util.LinkedList;
      import jaava.util.List;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	List<String> list = new LinkedList<>();
              
              // 객체 저장 (순서 있음, 중복 허용)
              list.add("orange");
              list.add("apple");
              list.add("apple"); 
              list.add("banana"); // orange, apple, apple, banana
              
              // 객체 참조
              for (int i = 0; i < list.size(); i++) 
              	System.out.print(list.get(i) + '\t');
                  
              // 첫 번째 객체 삭제
              list.remove(0); // apple, apple, banana
          }
      }
    • Iterator 반복자

      • 저장된 인스턴스의 순차적 접근에 for문, Iterator 반복자 이용
      • iterator() 메서드로 반복자 구하기
      • 한 번 사용한 반복자는 다시 사용X
      import java.util.Iterator;
      import java.util.LinkedList;
      import jaava.util.List;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	Iterator<String> itr = list.iterator(); // 반복자 획득
              
              String str;
              while (itr.hasNext()) // 반복자를 이용한 순차적 참조
              {
              	str = itr.next();
                  System.out.print(str + '\t');
                  
                  if (str.equals("orange:)
                  	itr.remove();
              }
              
              itr = list.iterator(); // 반복자 다시 획득
              
              while(itr.hasNext()) // 삭제 후 결과 확인 
              	System.out.print(itr.next() + '\t');
          }
      }
    • 리스트 형식 바꾸기

      • 매개변수로 전달된 객체들은 저장한 컬렉션 객체의 생성 및 반환
      • 생성된 리스트 객체는 요소를 추가/삭제할 수 없는 객체
      import java.util.Iterator;
      			import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.LinkedList;
      import jaava.util.List;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	List<String> list = Arrays.asList("홍길동", "전우치", "손오공", "전우치");
              // list.add("멀린"); // 에러 (추가 불가)
              list = new ArrayList<>(list); // 수정 가능한 객체로 변환
              list.add("해리포터");
              
              /* ArrayList<E> 객체의 순환 */
              for(Iterator<String> itr = list.iterator(); itr.hasNext(); )
              	System.out.println();
                  
               
              HashSet<String> set = new HashSet<>(list); // 중복 제거 (ArrayList<E>를 HashSet으로 변환)
              list = new LinkedList<>(set); // HashSet를 LinkedList로 변환
              
              /* LinkedList<E> 객체의 순환 */
              for (String s : list)
              	System.out.print(s + '\t');
          }
      }
    • 컬렉션 프레임워크에 기본 자료형을 데이터로 사용

      • 제네릭 부분에 클래스 타입 지정해야 함 (기본 자료형X)
      • 래퍼 클래스들은 오토 박싱(int ➡️ Integer)과 오토 언박싱(Integer ➡️ int)이 되어 기본 자료형 사용하는데 제약사항X
      • List<Integer> list = new LinkedList<>(); // O
        List<int> list = new LinkedList<>(); // X
  • 4) Set<E> 인터페이스를 구현하는 컬렉션 클래스
    • 저장 순서가 유지X

    • 데이터 중복 저장 허용X

    • HashSet<E>

      import java.util.HashSet;
      import java.util.Iterator;
      import java.util.Set;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	Set<String> set = new HashSet(); // HashSet으로 객체 생성, Set으로 사용
              
              // 객체 저장 (순서 없음, 중복 허용X)
              list.add("orange");
              list.add("apple");
              list.add("apple"); 
              list.add("banana"); // orange, apple, banana
              
              System.out.println(set.size()); // 객체 수
              
              // 반복자를 이용한 전체 출력
              for(Iterator<String> itr = set.iterator(); itr.hasNext(); )
              	System.out.print(itr.next() + '\t');
                  
              // 향상된 기능의 for문을 이용한 전체 출력
              for (String s : set)
               	System.out.print(s + '\t'); // orange, apple, banana
          }
      }
    • TreeSet<E>

      • 자료의 중복 허용X
      • 출력값 정렬 (이진 탐색 트리 이용)
      import java.util.Iterator;
      import java.util.TreeSet;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	TreeSet<String> tree = new TreeSet<>();
              
              // 객체 저장 (순서 없음, 중복 허용X)
              tree.add("orange");
              tree.add("apple");
              tree.add("apple"); 
              tree.add("banana"); // orange, apple, banana
              
              System.out.println(tree.size()); // 객체 수
              
              // 반복자를 이용한 전체 출력
              for(Iterator<String> itr = tree.iterator(); itr.hasNext(); )
              	System.out.print(itr.next() + '\t');   
          }
      }
  • 5) Queue<E> 인터페이스를 구현하는 컬렉션 클래스
    • Queue 구현

      • Queue<String> q = new LinkedList<>();
      import java.util.LinkedList;
      import java.util.Queue;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	Queue<String> q = new LinkedList<>();
              
              // 데이터 저장
              q.offer("A");
              q.offer("B");
              q.offer("C"); // A B C
              
              System.out.println(que.size()); // 큐 크기
              System.out.println(que.peek()); // 다음에 나올 값 확인 (A)
              System.out.println(que.poll()); // 객체 꺼내기 (A)
          }
      }
    • Stack 구현

      • Deque<String> deq = new ArrayDeque<>();
      • Deque<String> deq = new LinkedList<>();
      import java.util.ArrayDeque;
      import java.util.Deque;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	Deque<String\> deq = new ArrayDeque<>();
              // Deque<String\> deq = new LinkedList<>(); // 동일
              
              // 데이터 (앞) 저장
              deq.offerFirst("A");
              deq.offerFirst("B");
              deq.offerFirst("C"); // C B A
              // 데이터 (뒤) 저장)
              deq.offerLast("D");
              deq.offerLast("E");
              deq.offerLast("F"); // C B A D E F
              
              // 데이터 (앞) 꺼내기
              System.out.println(deq.pollFirst()); // C
              
              // 데이터 (뒤) 꺼내기
              System.out.println(deq.pollLast()); // F
          }
      }
  • 6) Map<K,V> 인터페이스를 구현하는 컬렉션 클래스
    • 객체 Key값은 유일, value값 중복 가능

    • HashMap<K,V>

      • 내부적으로 해시 알고리즘에 의해 구현
      • Interable<T> 인터페이스 구현되어 있지 않아 for문/Iterator 얻어서 순차적 접근 가능
      import java.util.HashMap;
      import java.util.Iterator;
      import java.util.Set;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	HashMap<String, String> map = new HashMap<>();
              
              // 데이터 저장 (Key-Value 기반)
              map.put("홍길동", "010-1234-1443");
              map.put("전우치", "010-4321-1446");
              map.put("손오공", "010-9876-1443");
              
              // 데이터 확인
              System.out.println(map.get("홍길동");
              
              // 데이터 삭제
              map.remove("손오공");
		/* Key만 담고 있는 컬렉션 객체 생성 */
		Set<String> ks = map.keySet();
        
        // 전체 key 출력 (향상된 기능의 for문 기반)
        for(String s : ks)
        	System.out.print(s + '\t');
            
        // 전체 Value 출력 (향상된 기능의 for문 기반)
        for(String s : ks)
        	System.out.print(map.get(s).toString() + '\t');
        // 전체 Value 출력 (반복자 기반)
        for(Iterator<String> itr = ks.iterator(); itr.hasNext(); )
        	System.out.print(map.get(itr.next()).toString() + '\t');
    }
}
```
  • TreeMap<K,V>

    • TreeSet처럼 이진 탐색 트리로 구현
    • key값으로 정렬해서, key값 해당 클래스에 Comparable/Comparator 인터페이스 구현되어 있어야 함
    • Iterable<T> 인터페이스는 구현하지 않았지만, keySet() 메서드 호출을 통해서 Key 컬렉션 객체를 통해 순차적 접근 가능
    import java.util.TreeMap;
    import java.util.Iterator;
    import java.util.Set;
    
    public class Ex01 {
    	public static void main(String[] args) {
        	TreeMap<String, Integer> map = new TreeMap<>();
            
            // 데이터 저장 (Key-Value 기반)
            map.put("홍길동", "010-1234-1443");
            map.put("전우치", "010-4321-1446");
            map.put("손오공", "010-9876-1443");
            
    		/* Key만 담고 있는 컬렉션 객체 생성 */
    		Set<String> ks = map.keySet();
            
            // 전체 key 출력 (향상된 기능의 for문 기반)
            for(String s : ks)
            	System.out.print(s + '\t');
                
            // 전체 Value 출력 (향상된 기능의 for문 기반)
            for(String s : ks)
            	System.out.print(map.get(s).toString() + '\t');
            // 전체 Value 출력 (반복자 기반)
            for(Iterator<String> itr = ks.iterator(); itr.hasNext(); )
            	System.out.print(map.get(itr.next()).toString() + '\t');
        }
    }
  • 7) 컬렉션 기반 알고리즘

    • 정렬

      • 정렬시 Collections.sort() 메서드 사용
      • 객체 크기 비교해야 정렬할 수 있어서 객체의 클래스는 Comparable<T> 인터페이스 구현한 상태여야 함
      import.java.util.ArrayList;
      import.java.util.Arrays;
      import.java.util.Collections;
      import.java.util.List;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	List<String> list = Arrays.asList("홍길동", "전우치", "손오공", "멀린"); // 크기 변경 불가능한 리스트
              list = new ArrayList<>(); // 크기 변경 가능하도록 리스트 새로 생성
              
              Collections.sort(list); // 리스트 정렬 (원본 데이터 변경)
          }
      }
    • 검색

      • 이진 탐색 기능 이용하여 리스트 안에 데이터 확인
      • 이진 탐색을 위해 데이터가 먼저 정렬되어 있어야 함
      import.java.util.ArrayList;
      import.java.util.Collections;
      import.java.util.List;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	List<String> list = new ArrayList<>();
              
              list.add("홍길동");
              list.add("전우치");
              list.add("손오공");
              
              Collections.sort(list); // 정렬
              
              int idx = Collections.binarySearch(list, "홍길동"); // idx = 2
          }
      }
    • 복사

      import.java.util.ArrayList;
      import.java.util.Arrays;
      import.java.util.Collections;
      import.java.util.List;
      
      public class Ex01 {
      	public static void main(String[] args) {
          	List<String> src = Array.asList<"홍길동", "전우치", "손오공", "멀린");
              
              // 수정 가능한 리스트 생성
              List<String> dst = new ArrayList<>(src);
              
              // 정렬하여 결과 출력
              Collections.sort(dst); // 멀린, 손오공, 전우치, 홍길동
              
              // 정렬 이전의 상태로 되돌리기
              // 원본 src의 데이터를 dst에 복사
              Collections.copy(dst, src); // 홍길동, 전우치, 손오공, 멀린
              
              // 데이터 수정
              dst.remove(0); // 전우칭, 손오공, 멀린
          }
      }

📕 3. 내부 클래스, 람다식

  • 1) 내부 클래스

    • 클래스 안에 클래스 선언할 때, 안쪽 클래스(중첩 클래스)와 중첩 클래스를 가진 클래스(외부 클래스)
    • 중첩 클래스 구분
      • 스태틱 중첩 클래스 (스태틱이지만 내부 클래스 아님)
      • 논스태틱 중첩 클래스 (내부 클래스) : 멤버 내부 클래스 / 지역 내부 클래스 / 익명 내부 클래스
    class MyClass // 외부 클래스
    {
    	static clas NestedClass{} // 스태틱 중첩 클래스
      class c1{} // 멤버 내부 클래스
      
      public void myFunc()
      {
      	class c2{} // 지역 내부 클래스
      }
    }
  • 2) 멤버 내부 클래스

    • 외부 클래스는 내부 클래스를 멤버 변수처럼 사용 가능 (외부클래스.new 내부클래스 생성자();)
    class Outer // 외부 클래스
    {
    	private int speed = 10;
      
      class MemberInner // 멤버 내부 클래스 
      {
      	public void move()
          {
          	// 외부 클래스의 자원(speed 변수) 사용 가능
              System.out.prinf(speed);
          }
      }
      
      public void getMarine() // 외부 클래스의 메서드에서 내부 클래스 객체 만들고 참조 가능
      {
          MemberIneer ineer = new MemberIneer();
          inner.move();
       }
    }
    
    public class Ex01 {
    	public static void main(String[] args) 
      {
      	Outer out = new Outer();
          out.getMarine(); // out 기반으로 생성된 객체의 메서드 호출
          
          Outer.MemberInner in = out.new MemberInner(); // out 기반으로 내부 클래스 객체 생성
          inner.move(); // inner 기반으로 생성된 객체의 메서드 호출
      }
    }
  • 3) 지역 내부 클래스

    • 메서드, if문, while문 등 중괄호 블록 안에 정의되어있는 클래스
    • 해당 메서드 안에서만 객체 생성 가능 (클래스 정의를 깊이 숨길 수 있음)
    class HumanCamp
    {
    	private int speed = 10;
      
      public void getMarine()
      {
      	class Marine // 지역 내부 클래스
          {
          	public void move() {
              	System.out.println(speed); // 내부 클래스라서 외부 클래스 자원 사용 가능 
              }
          }
          
          Marine inner = New Marine(); // 해당 메서드 안에서만 생성 가능 (객체의 생성 제한)
          inner.move();
      }
    }
    
    public calss Ex02 {
    	public static void main(String[] args) {
      	HumanCamp hc = new HumanCamp();
          
          hc.getMarine();
      }
    }
  • 4) 익명 내부 클래스

    • 지역 내부 클래스는 해당 메서드에서만 클래스 생성이 가능하므로 제한적으로 클래스명 사용
    • 클래스명을 생략한 지역 내부 클래스
    interface Unit
    {
    	void move();
    }
    
    class HumanCamp
    {
    	private int speed = 10;
      
      // class Marine implements Unit
      // {
      //	  public void move()
      //   {
      //    	System.out.println(speed);
      //    }
      // }
      // return new Marine();
      
      // 이름 생략, 이름이 없으므로 부모 클래스나 인터페이스 이름 사용
      return new Unit()
      {
      	public void move() 
          {
          	System.out.println(speed);
          }
      }
    }
    
    public class Ex04 {
    	public static void main(String[] args) {
      	HumanCamp hc = new HumanCamp();
          
          Unit unit = hc.getMarine();
          unit.move();
      }
    }
  • 5) 람다식

    • 자바에서 클래스 만들고, 클래스 안에 기능을 구현한 만든 후 객체로 메서드 호출하는 불편함 해소

    • 인터페이스의 메서드는 무조건 구현이 필요해서, 람다식이 인터페이스의 메서드에 할당됨

    • 익명 내부 클래스 ➡️ 람다식

      • Unit unit = new Unit() {// 익명 클래스
        public void move
        (String s) -> { System.out.println(s);}};
      • 익명 내부 클래스
      interface Unit
      {
      	void move(String s);
      }
      
      public class Ex06 {
      	public static void main(String[] args) {
          	Unit unit = new Unit() // 인터페이스 메서드를 익명 클래스로 구현
              {
              	public void move(String s)
                  {
                  	System.out.println(s);
                  }
              };
              
              unit.move();
          }
      }
      • 람다식
      @FuntionalInterface 
      interface Unit // 인터페이스에 메서드 추가시 에러 발생 
      {
      	void move(String s);
      }
      
      @FuntionalInterface
      interface Unit2
      {
      	void calc(int a, int b);
      }
      
      @FuntionalInterface
      interface Unit3
      {
      	String move();
      }
      
      public class Ex06 {
      	public static void main(String[] args) {
          	Unit unit;
              
              /* 매개변수 1개 */
              unit = (String s) -> { System.out.println(s); };
              // 중괄호 구현부가 1문장이면 중괄호 생략 가능
              unit = (String s) -> System.out.println(s);
              // 매개변수 1개이면 자료형 생략 가능
              unit = (s) -> { System.out.println(s); };
               // 매개변수 1개이면 소괄호 생략 가능
              unit = s -> System.out.println(s);
              // (오류) 구현부에 return 있을 경우 중괄호 생략X
              unit = (String s) -> return s.length();
      
             
              /* 매개변수 2개 */
              Unit2 unit2;
              unit2 = (a, b) -> { return a + b; };
              
              // (오류) 매개변수 2개 이상이면 소괄호 생략X
              unit2 = a, b -> { return a + b; };
              // (오류) 매개변수 2개 이상이면 중괄호 생략X
              unit2 = (a, b) -> return a + b; 
              // 구현부에 return만 있을 경우 return과 중괄호 생략 가능
              unit = s -> s.length();
              unit2 = (a, b) -> a + b;
              
              
               /* 매개변수 0개 */
               Unit3 unit3;
               // 매개변수 없을 경우 소괄호 생략X
               unit3 = () -> { return "Hi"; };
               
               System.out.println(unit3.move());
          }
      }
  • 6) 함수형 인터페이스

    • 람다식을 선언하는 전용 인터페이스
    • 익명 함수와 매개변수만으로 구현 (단 하나의 메서드만 가짐)
      • 인터페이스에 @FunctionalInterface 어노테이션으로 함수형 인터페이스 표시
      • 메서드 추가할 경우 에러 발생 (어떤 메서드에 익명 함수 대입할지 모호해지기 때문)

📕 4. 스트림

  • 1) 스트림

    • 컬렉션, 배열로 만드는 연속적인 데이터의 흐름을 반복적으로 처리하는 기능
    • 특징
      • 스트림 연산은 기존 자료를 변경X
      • 스트림 연산은 중간 연산과 최종 연산으로 구분
      • 한 번 생성하고 사용한 스트림은 재샤용 불가
    • java.util.stream 패키지 멤버
    • BaseStream 인터페이스를 부모로 하여 4가지 스트림 제공
      • Stream
      • IntStream
      • LongStream
      • DoubleStream
  • 2) 중간 연산, 최종 연산

    • 과정
      • 1️⃣ : 스트림 생성
      • 2️⃣ : 중간 연산
      • 3️⃣ : 최종 연산
    • 중간 연산
      • filter() : 조건에 맞는 요소 추출
      • map() : 조건에 맞는 요소 변환
      • sorted() : 정렬
    • 최종 연산
      • 스트림 자료를 소모하면서 연산 수행
      • 최종 연산 후에 스트림은 더 이상 다른 연산을 적용할 수 없음
      • forEach() : 요소를 하나씩 꺼냄
      • count() : 요소 개수
      • sum() : 요소 합
    import java.util.Arrays;
    import java.util.stream.IntStream;
    
    public class Ex01 {
    	public static void main(String[] args) {
      	int[] arr = {1, 2, 3, 4. 5};
          
          // 스트림 생성
          IntStream stm1 = Arrays.stream(arr);
          
          // 중간 연산
          IntStream stm2 = stm1.filter(n -> n%2 == 1);
          
          // 최종 연산
          int sum = stm2.sum();
          
          System.out.println(sum);
      }
    }
  • 3) 파이프라인 구성

    • Stream 인터페이스가 제공하는 메서드는 대부분 반환 타입이 Stream이므로 메서드를 연속해 호출 가능
    • 스트림 연산을 연결해 파이프라인으로 구성
    import java.util.Arrays;
    
    public class Ex02 {
    	public static void main(String[] args) {
      	int[] arr = {1, 2, 3, 4, 5};
          
          // Pipeline 구성
          int sum = Arrays.stream(arr) // 스트림 생성 
         			.filter(n -> n%2 == 1) // 중간 연산
                  	.sum();  // 최종 연산 
                      
          System.out.println(sum);
      }
    }
  • 4) 컬렉션 객체 vs 스트림

    • 스트림 사용시 컬렉션만 사용한 것보다 코드가 간결하고 쉽게 의미 확인 가능
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    
    public class Ex03 {
    	public static void main(String[] args) {
      	int[] arr = {1, 2, 3, 4, 5};
          
          // 컬렉션 프레임워크를 이용한 방식
          for (int i : arr) {          // 필터링 
          	if (i%2 == 1) 
              {
              	list.add(i);
              }
          }
          Collections.sort(list);       // 정렬
          for (int i : list) {           // 요소 추출
          	System.out.print(i + "\t");
          }
          
          // Stream 이용한 방식
          Arrays.stream(arr)
          		.filter(n -> n%2 == 1) // 필터링
                  .sorted()             // 정렬
                  .forEach(n -> Stream.out.print(n + "\t"); // 요소 추출
      }
    }
  • 5) 여러 가지 중간 연산

    • sorted()

      • 스트림을 구성하는 데이터를 조건에 따라 정렬하는 연산
      import java.util.Array;
      import java.util.list;
      
      public class Ex04 {
      	public static void main(String[] args) {
          	List<String> List = Arrays.asList("홍길동", "멀린", "해리포터");
              
              // 사전순 정렬
              list.stream()
              	.sorted()
                  .forEach(n -> System.out.print(n + "\t");
                  
              // 글자 길이순 정렬
              list.stream()
              	.sorted((s1, s2) -> s1.length() - s2.length())
                  .forEach(n -> System.out.print(n + "\t");
                  
               System.out.println();
          }
      }
    • map()

      • 스트림을 구성하는 데이터를 조건에 따라 변환
      import java.util.Array;
      import java.util.list;
      
      public class Ex05 {
      	public static void main(String[] args) {
          	List<String> list = Arrays.asList("apple", "banana", "orange");
              
              list.steam()
              	.map(s -> s.toUpperCase()) // 데이터를 하나씩 받아서 대문자로 변환
                  .forEach(n -> System.out.print(n + "\t"));
                  
              System.out.println();
          }
      }
    • sum(), count(), average(), min(), max()

      • 합, 개수, 평균, 최소, 최대 함수
      import java.util.stream.IntStream;
      
      public class Ex06 {
      	public static void main(String[] args) {
          	// 합
              int sum = IntStream.of(1, 3, 5. 7. 9)
              					.sum();
              System.out.println(sum);
              
             // 개수
              long cnt = IntStream.of(1, 3, 5. 7. 9)
              					.count();
              System.out.println(cnt);
              
             // 평균
             IntStream.of(1, 3, 5. 7. 9)
              		.average()
                      .ifPresent(avg -> System.out.println(avg));
              
             // 최소
             IntStream.of(1, 3, 5. 7. 9)
              		.min()
                      .ifPresent(min -> System.out.println(min));
              
             // 최대
             IntStream.of(1, 3, 5. 7. 9)
              		.max()
                      .ifPresent(max -> System.out.println(max));
          }
      }
    • reduce()

      • 정의된 연산이 아닌 프로그래머가 직접 지정하는 연산 적용
      • reduce(초기값, (전달되는 요소) -> 수행해야 할 기능);
      // 문자열 길이를 세서 긴 문자열 남기기
      import java.util.Arrays;
      import java.util.List;
      
      public class Ex06 {
      	public static void main(String[] args) {
          	List<String> list1 = Arrays.asList("홍길동", "전우치", "손오공");
              
              String name1 = list1.stream()
              	.reduce("이순신", (s1, s2) ->
                 	 	s1.length() >= s2.length() ? s1 : s2);
              System.out.println(name1); // 이순신
              
              List<String> list2 = Arrays.asList("해리포터", "호그와트", "그리핀도르");
              
              String name2 = list2.stream()
              	.reduce("김수현", (s1, s2) -> 
                  	 s1.length() >= s2.length() ? s1 : s2);
              System.out.println(name2); // 해리포터 (길이가 같을 경우 초기값 남음)
          }
      }

📕 5. 입출력 스트림

  • 1) 자바의 입출력 스트림

    • 자바 입출력 모델의 모든 입출력을 입출력 스트림을 통해 처리하는 기능
      • 파일에서의 입출력
      • 키보드와 모니터의 입출력
      • 그래픽카드, 사운드카드의 입출력
      • 프린터, 팩스 등 출력 장치의 입출력
      • 인터넷으로 연결되어 있는 서버 또는 클라이언트 입출력
    • 구분
      • 대상의 기준) 입력 스트림 / 출력 스트림
      • 자료의 종류) 바이트 단위 스트림 / 문자 단위 스트림
      • 기능) 기반 스트림 / 보조 스트림(필터 스트림)
  • 2) 입출력 스트림의 구분

    • 입력 스트림 : 대상으로부터 자료를 읽어들이는 스트림
      • 자바 응용 프로그램 ⬅️ 입출력 자료
      • FileInputStream, FileReader, BufferedInputStream, BufferedReader
    • 출력 스트림 : 대상으로 자료를 출력하는 스트림
      • 자바 응용 프로그램 ➡️ 입출력 자료
      • FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter
    • 바이트 단위 스트림 : 동영상, 음악 파일 등을 읽고 쓸 때 사용
      • FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream
    • 문자 단위 스트림 : 바이트 단위로 자료를 처리하면 문자는 깨짐 (2바이트 단위로 처리하도록 구현된 스트림)
      • FileReader, FileWriter, BufferedReader, BufferedWriter
    • 기반 스트림 : 대상에 직접 자료를 읽고 쓰는 기능의 스트림
      • FileInputStream, FileOutputSteam, FileReader, FileWriter
    • 보조 스트림 : 직접 읽고 쓰는 기능은 없이 추가적인 기능을 더해주는 스트림 (항상 기반 스트림이나 또 다른 보조 스트림을 생성자 매개변수로 포함함)
      • InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream
  • 3) 파일 대상 입출력 스트림 생성

    • 파일 대상 출력 스트림 생성
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    
    public class Ex01 {
    	public static void main(String[] args) throws IOException {
      	OutputStream out = new FileOutputStream("data.txt"); // 파일 생성하고, 파일에 스트림 생성
          
          out.write(65); // 스트림을 통해 데이터 전송 (ASCII 코드 65 = 'A')
          out.close(); // 파일 닫기 
      }
    }
    • 입출력 스트림 예외 직접 처리
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    
    public class Ex02 {
    	public static void main(String[] args) {
      	OutputStream out = null;
          
          try 
          {
          	out = newFileOutputStream("data.txt");
              out.write(65);
          }
          catch (IOException e)
          {
          }
          finally
          {
          	if (out != null)
              {
              	try
                  {
                  	out.close(); // 에러 발생해도 확실히 종료하기 위해 finally로 이동
                  }
                  catch (IOException e2)
                  {
                  }
              }
          }
      }
    }
    • 입출력 스트림 예외 처리 개선
      • try~with~resource 사용
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    
    public class Ex02 {
    	public static void main(String[] args) {
      	
          try (OutputStream out = new FileOutputStream("data.txt")) // 스트림 닫지 않아도 자바에서 자동 처리
          {
          	out.write(65);
          }
          catch (IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
    • 파일 대상 입력 스트림 생성
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    public class Ex02 {
    	public static void main(String[] args) {
          try (InputStream in = new FileInputStream("data.txt")) 
          {
          	int dat = in.read(); // 데이터 1바이트 읽음
          }
          catch (IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
    • 바이트 단위 입력 및 출력 스트림 이용 파일 복사
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.IOException;
    import java.time.Duration;
    import java.time.Instant;
    
    public class Ex02 {
    	public static void main(String[] args) {
      	
          String src = "./src/FileRead.java";
          String dst = "FileRead1.txt";
          
          try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst))
          {
          	Instant start = Instant.now(); // 복사에 걸린 시간 측정을 위해 시간 클래스 이용해 현재 시각 구함
              
              int data;
              while(true) // 한 바이트씩 소스 파일의 스트림으로부터 데이터 읽어 대상 파일 스트림에 써줌
              {
              	data = in.read();
                  if(data == -1) // 더 이상 데이터 읽지 못하면 -1 반환
                  	break;
                  out.write(data);
              }
              
              Instant end = Instant.now();
              System.out.println(Duration.between(start, end).toMillis()); // 복사에 걸린 시간
          }
          catch (IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
    • 버퍼를 이용한 파일 복사
      • 메모리를 이용하여 버퍼에 저장해서 한 번에 읽고 쓴느 방식으로 하드웨어적 I/O 횟수 줄임
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.IOException;
    import java.time.Duration;
    import java.time.Instant;
    
    public class Ex02 {
    	public static void main(String[] args) {
      	String src = "./src/FileRead.java";
          String dst = "FileRead1.txt";
          
          try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst))
          {
          	byte[] buf = new byte[1024]; // 데이터 담을 버퍼로 바이트 배열 1KB 크기 생성
              int len;
              
              Instant start = Instant.now();
              
              int data;
              while(true) // 버퍼 크기만큼 한 번에 읽음
              {
              	len = in.read(buf);
                  if(len == -1) 
                  	break;
                  out.write(buf, 0, len);
              }
              
              Instant end = Instant.now();
              System.out.println(Duration.between(start, end).toMillis()); // 복사에 걸린 시간
          }
          catch (IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
  • 4) 보조 스트림

    • 기반 스트림(직접 자료 읽고 쓰는 기능)에 추가적인 기능 더해주는 스트림
    • 버퍼링 기능 제공하는 필터 스트림
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.time.Duration;
    import java.time.Instant;
    
    public class Ex02 {
    	public static void main(String[] args) {
      	String src = "./src/FileRead.java";
          String dst = "FileRead1.txt";
          
          try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(src));
          	BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dst))) // 기반 스트림에 없는 기능 추가 (기반 스트림 : FileInputStream, 보조 스트림 : BufferedInputStream)
          {
          	Instant start = Instant.now();
              
              int data;
              while(true) // 1 바이트씩 읽고 있지만 보조 스트림에 의해 버퍼링처럼 동작
              {
              	data = in.read();
                  if(data == -1) 
                  	break;
                  out.write(data);
              }
              
              Instant end = Instant.now();
              System.out.println(Duration.between(start, end).toMillis()); // 복사에 걸린 시간
          }
          catch (IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
  • 5) 문자 스트림

    • FileInputStream으로 파일 읽으면 한글 등 문서는 글자 깨짐 (2바이트 필요한데 1바이트씩 읽어서 발생)
    • FileReader/FileWriter 클래스를 사용해서 입출력 스트림에서 2바이트씩 데이터 처리
    • FileWriter
    import java.io.FileWriter;
    import java.io.Writer;
    import java.io.IOException;
    
    public class Ex01 {
    	public static void main(String[] args) {
      	try(Writer out = new FileWriter("text.txt"))
          {
          	for(int ch = (int)'A'; ch < (int)('Z' + 1); ch++)
              	out.write(ch); // char를 int로 형변환해서 A부터 Z까지 반복
                   
              // out.write("\r\n")과 동일
              out.write(13); // 캐리지 리턴 값 저장 (현재 위치 커서를 맨 앞으로 이동)
              out.write(10); // 라인 피드 값 저장 (커서 위치를 아랫줄로 이동)
          }
          catch(IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
    • FileReader
    import java.io.FileReader;
    import java.io.Reader;
    import java.io.IOException;
    
    public class Ex01 {
    	public static void main(String[] args) {
      	try (Reader in = new FileReader("text.txt"))
          {
          	int ch;
              
              while(true)
              {
              	ch = in.read(); // 문자를 스트림으로부터 하나 읽어 int형 변수에 대입
                  if (ch == -1) // 더 이상 읽을 수 없을 때 -1 반환받기 위해 int 사용
                  	break;
                  System.out.println((char)ch);
              }
          }
          catch(IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
    • BufferedWriter
    import java.io.BufferedWriter;
    import java.io.FileWriter
    import java.io.IOException;
    
    public class Ex01 {
    	public static void main(String[] args) {
      	String str1 = "동해물과";
          String str2 = "백두산이";
          
      	try (BufferedWriter bw = new BufferedWriter(new FileWriter("text.txt"))) // 기반 스트림 : FileWriter, 보조 스트림 : BufferedWriter
          {
          	bw.write(str1, 0, str1.length()); // 문자열의 크기만큼 버퍼링하여 한 번에 출력 스트림으로 저장
              bw.newLine(); // 줄바꿈 문자를 스트림으로 저장
              bw.write(str2, 0, str2.length());
          }
          catch(IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
    • BufferedReader
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class Ex01 {
    	public static void main(String[] args) {
      	try (BufferedReader br = new BufferedReader(new FileReader("text.txt")))
          {
          	String str;
              
              while(true)
              {
              	str = br.readLine(); // 입력 스트림에서 라인별로 구분하여 데이터를 읽어들임
                  if (str == null)
                  	break;
                  System.out.println(str);
              }
          }
          catch(IOException e)
          {
          	e.printStackTrace();
          }
      }
    }
  • 6) IO 스트림 기반의 인스턴스 저장

    • 직렬화

      • 객체의 상태를 그대로 저장하거나 다시 복원하는 것
      • 자바 가상 머신의 메모리에 있는 객체 데이터를 바이트 형태로 변환하는 기술
      • 직렬화 의도를 표시하기 위해 java.io.Serializable 인터페이스 사용 (마커 인터페이스)
      public class Ex12 implements java.io.Serializable
      {
      	private static final long serialVersionUID = 1L; // 직렬화에 사용되는 고유 아이디 (없으면 JVM에서 디폴트로 자동 생성)
          private String name;
          
          public Ex12 (String name)
          {
          	this.name = name;
          }
          
          public String getName()
          {
          	return name;
          }
      }
    • ObjectOutputStream

      • InputStream을 생성자의 매개변수로 받아 ObjectInputStream을 생성
      import java.io.FileOutputStream;
      import java.io.ObjectOutputStream;
      import java.io.IOException;
      
      public class Ex13 {
      	public static void main(String[] args) {
       	Ex13 unit1 = new Ex13("Marine");
           Ex13 unit2 = new Ex13("Medic");
           
           try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Object.bin")))
           {
           	oos.writeObject(unit1);
               oos.writeObject(unit2);
           }
           catch(IOException e)
           {
           	e.printStackTrace();
           }
       }
      }
    • ObjectInputStream

      • OutputStream을 생성자의 매개변수로 받아 ObjectOutputStream을 생성
      • ObjectOutputStream을 이용하여 객체를 저장한 경우 ObjectInputStream으로 읽어서 객체 복원해야 정보 읽을 수 있음
      import java.io.FileInputStream;
      import java.io.ObjectInputStream;
      import java.io.IOException;
      
      public class Ex13 {
      	public static void main(String[] args) {
           
           try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Object.bin")))
           {
           	Ex13 unit1 = (Ex13) ois.readObject();
               System.out.println(unit1.getName());
               
           	Ex13 unit2 = (Ex13) ois.readObject();
               System.out.println(unit2.getName());
           }
           catch(ClassNotFoundException e)
           {
           	e.printStackTrace();
           }
           catch(IOException e)
           {
           	e.printStackTrace();
           }
       }
      }

📕 6. 스레드

  • 1) 스레드의 이해

    • 하나의 실행 흐름으로 프로세스 내부에 존재
    • 스레드는 각자의 자원을 가지고 독립적으로 실행
    • 스레드 구현 (java.lang 패키지에 Thread 클래스와 Runnable 인터페이스 구현되어 있음)
      • Thread 클래스를 상속받아 run() 메서드 오버라이딩
      • Runnable 인터페이스 구현
  • 2) 스레드 생성과 실행

    • Thread 클래스를 상속받아 만들기

      • 스레드는 start() 메서드를 통해 동작
      • 스레드 실행은 시작하라는 명령만 내리고 바로 다음 라인 실행으로 옮겨짐
      class MyThread extends Thread
      {
      	public void run()
          {
          	int sum = 0;
              for (int i = 0; i < 10; i++)
              	sum += i;
              String name = Thread.currentThread().getName(); // 현재 스레드명 name = Thread-0
          }
      }
      
      public class Ex02 {
      	public static void main(String[] args) {
          	MyThread t = new MyThread();
              t.start();
              System.out.println(Thread.currentThread().getName()); // main 출력
          }
      }
    • Runnable 인터페이스 구현

      class MyThread implements Runnable
      {
      	public void run()
          {
          	int sum = 0;
              for (int i = 0; i < 10; i++)
              	sum += i;
              String name = Thread.currentThread().getName(); // 현재 스레드명 name = Thread-0
          }
      }
      
      public class Ex02 {
      	public static void main(String[] args) {
          	Thread t = new Thread(new MyThread());
              t.start();
              System.out.println(Thread.currentThread().getName()); // main 출력
          }
      }
    • 람다식으로 Runnable 구현

    public class Ex02 {
    	public static void main(String[] args) {
      
      	Runnable task = () -> {
          	try
              {
              	Thread.sleep(3000);
              }
              catch(Exception e) {}
              
              int sum = 0;
              for (int i = 0; i < 10; i++)
              	sum += i;
              String name = Thread.currentThread().getName(); // 현재 스레드명 name = Thread-0
          };
          
      	Thread t = new Thread(task);
          t.start();
          
          System.out.println(Thread.currentThread().getName()); // main 출력
          }
      }
    • 여러 개의 스레드 동시 실행
    public class Ex02 {
    	public static void main(String[] args) {
      
      	Runnable task1 = () -> {
          	try
              {
              	for (int i = 0; i < 20; i = i + 2)
                  {
                  	System.out.print(i + " ");
                      Thread.sleep(1000); // 1000밀리세컨드(1초) 쉼
                  }
              }
              catch(InterruptedException e) {}
          };
          
      	Runnable task2 = () -> {
          	try
              {
              	for (int i = 9; i > 0; i--)
                  {
                  	System.out.print("(" + i + ")");
                      Thread.sleep(500); // 500밀리세컨드 쉼
                  }
              }
              catch(InterruptedException e) {}
          };
          
      	Thread t1 = new Thread(task1);
          Thread t2 = new Thread(task2);
          
          t1.start();
          t2.start();
          }
      }
  • 3) 스레드 동기화

    • 스레드 문제점
      • 스태틱 영역의 변수는 모든 스레드에서 값을 공유하여 사용
      • 여러 스레드가 같은 변수의 값을 증감시키는 연산 수행시 문제 발생
        public class Ex02 {
        	public static int money = 0;
         
        	public static void main(String[] args) throws InterruptedException {
         
         	Runnable task1 = () -> {
             	for (int i = 0; i < 10000; i++)
                 	money++;
             };
             
         	Runnable task2 = () -> {
             	for (int i = 0; i < 10000; i++)
                 	money--;
             };
             
         	Thread t1 = new Thread(task1);
             Thread t2 = new Thread(task2);
             
             t1.start();
             t2.start();
             
             t1.join(); // t1이 참조하는 스레드의 종료를 기다림
             t2.join(); // t2이 참조하는 스레드의 종료를 기다림
             
             System.out.println(money); // 스레드가 종료되면 출력을 진행함
             }
         }
    - 스레드 동기화로 해결
      - 메서드에 synchronized 키워드 지정
      ```java
      public synchronized static void 메서드()
      {
      	// 동기화 대상 코드
      }
    • 코드의 일부에 동기화 블록 지정
      public void 메서드()
      {
      	synchronized(공유 객체)
          {
          	// 동기화 대상 코드
          }
      }
  • 4) 스레드 풀

    • 스레드 개수에 제한이 없으면 생성 및 종료에 많은 오버헤드가 발생하여 OutOfMemoryError 발생
    • 제한된 개수의 스레드를 JVM에 관리하도록 맡기는 방식
    • 실행할 작업을 스레드 풀로 전달하면 JVM이 스레드 풀의 유휴 스레드 중 하나를 선택해서 스레드로 실행
    • 스레드 풀 유형
      • newSingleThreadExecutor
      • newFixedThreadPool
      • newCachedThreadPool
    • newSingleThreadExecutor
      • 풀 안에 하나의 스레드만 생성/유지
      • 하나의 태스그가 완료된 후에 다음 태스크 실행
      • 여러 스레드가 동시 실행되지 않아 동기화 필요X
    • newFixedThreadPool
      • 풀 안에 인수로 전달된 수의 스레드를 생성/유지
      • 초기 스레드(0개) & 코어 스레드(매개변수 nThread개) & 최대 스레드(매개변수 nThread개)
      • 생성된 스레드가 놀고있어도 제거하지 않고 유지
    • newCachedThreadPool
      • 풀 안의 스레드 수를 작업 수에 맞게 관리
      • 초기 스레드(0개) & 코어 스레드(0개) & 최대 스레드(Integer 데이터형 최대값)
      • 60초 동안 스레드가 일하지 않으면 스레드 종료+제거
    • 최대 스레드 수가 1개인 스레드 풀
    import java.util.concurrent.ExcutorService;
    import java.util.concurrent.Executors;
    
    public class Ex08 {
    
    	public static int money = 0;
      
    	public static void main(String[] args) {
      	Runnable task1 = () -> {
          	for (int i = 0; i < 10000; i++)
              	money++;
               String name = Thread.currentThread().getName(); // pool-1-thread-1
          };
          
      	Runnable task2 = () -> {
          	for (int i = 0; i < 10000; i++)
              	money--;
               String name = Thread.currentThread().getName(); // pool-1-thread-1
          };
          
          ExecutorService pool = Executors.newSingleThreadExecutor();
          pool.submit(task1); // 스레드 풀에 작업을 전달
          pool.submit(task2); 
          
          System.out.println(Thread.currentThread().getName()); // main
          
          pool.shutdown(); // 스레드 풀과 그 안에 있는 스레드 소멸
      }
    }
    • 최대 스레드 수가 2개인 스레드 풀
    import java.util.concurrent.ExcutorService;
    import java.util.concurrent.Executors;
    
    public class Ex08 {
    	public static void main(String[] args) {
      	Runnable task1 = () -> {
          	String name = Thread.currentThread().getName(); // pool-1-thread-1
              try
              {
              	Thread.sleep(5000);
              }
              catch (Exception e) { }
          };
          
      	Runnable task2 = () -> {
          	String name = Thread.currentThread().getName(); // pool-1-thread-2
          };
          
          Runnable task3 = () -> {
          	String name = Thread.currentThread().getName(); // pool-1-thread-2
              try
              {
              	Thread.sleep(2000);
              }
              catch (Exception e) { }
          };
          
          ExecutorService pool = Executors.newFixedThreadPool();
          pool.submit(task1); // 스레드 풀에 작업을 전달
          pool.submit(task2); 
          pool.submit(task3); 
          
          pool.shutdown(); // 스레드 풀과 그 안에 있는 스레드 소멸
      }
    }
  • 5) Callable & Future

    • 스레드는 실행만 시키고, 스레드로부터 결과 반환받을 수 없음
    • Executor 프레임워크를 사용하면, 작업 대상의 Callable 객체를 만들고 ExecutorService에 등록
      ➡️ 태스크 처리가 끝난 후 작업 결과를 Future 객체를 통해서 반환
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class Ex10 {
    	public static void main(String[] args) throws InterruptedException, ExecutionException {
      
      	Callable<Integer> task1 = () -> { // Callable과 사용할 자료형을 제네릭으로 지정하여 스레드 생성
          	Thread.sleep(2000);
              return 2 + 3; // 제네릭에서 지정한 형태의 값을 반환
          };
          
          Callable<Integer> task2 = () -> {
          	Thread.sleep(10);
              return 2 * 3;
          };
      	
          ExecutorService pool = Executors.newFixedThreadPool(2); // 스레드 풀 생성 (동시에 두 스레드 처리 가능)
          Future<Integer> future1 = pool.submit(tast1); // 스레드를 스레드 풀에 전달함 (스레드 풀은 전달된 스레드를 실행시킴)
          Future<Integer> future2 = pool.submit(tast2);
          
          Integer r1 = future1.get(); // Future형의 변수에서 get() 메서드를 통해 스레드에서 반환 값 구해옴
          Integer r2 = future2.get();
          
          pool.shutdown(); // 마지막 스레드가 종료되면 스레드 풀 종료
      }
    }
  • 6) ReentrantLock 클래스 : 명시적 동기화

    • 메서드 전체나 구간이 아닌 시작점과 끝점 동기화 명백히 명시 가능
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    class BankAccount
    {
    	ReentrantLock myLock = new ReentrantLock();
      int money = 0;
      
      public void deposit()
      {
      	myLock.lock(); // 동기화 시작점 설정
          money++;
          myLock.unlock(); // 동기화 끝점 설정
      }
      
      public void withdraw()
      {
      	myLock.lock();
          money--;
          myLock.unlock();
      }
      
      public int balance()
      {
      	return money;
      }
    }
    
    public class Ex11 {
    
    	public static BankAccount account = new BankAccount();
      
    	public static void main(String[] args) throws InterruptedException {
      	Runnable task1 = () -> {
          	for (int i = 0; i < 10000; i++)
              	accout.deposit();
          }
          
          Runnable task2 = () -> {
          	for (int i = 0; i < 10000; i++)
              	accout.withdraw();
          }
          
          ExecutorService pool = Executors.newFixedThreadPool(2); // 스레드 풀 생성 (스레드 동시에 2개까지 처리 가능)
          pool.submit(task1);
          pool.submit(task2);
          
          pool.shutdown(); // 스레드 종료되면 스레드 풀 종료
          pool.awaitTermination(100, TimeUnit.SECONDS); // 스레드 풀이 완전하게 종료되도록 대기
          
          System.out.println(accout.balance()); // 0
      }
    }
  • 7) 컬렉션 객체 동기화

    • synchronized를 이용한 동기화
    import java.util.ArrayList;
    import java.util.List;
    import java.util.ListIterator;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    public class Ex13 {
    
    	public static List<Integer> list = new ArrayList<>(); 
      
    	public static void main(String[] args) throws InterruptedException
      {
      	for (int i = 0; i < 10; i++)
          	list.add(i);
              
          Runnable task = () -> { // list 객체 사용할 때 객체에 동기화 Lock 설정
          	synchronized(list)
              {
              	ListIterator<Integer> itr = list.listIterator();
                  
                  while (itr.hasNext())
                  	itr.set(itr.next() + 1);
              }
          };
          
          ExecutorService pool = Executors.newFixedThreadPool(5);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          
          pool.shutdown();
          pool.awaitTermination(100, TimeUnit.SECONDS);
      }
    }
    • Collections 클래스의 메서드를 이용한 동기화
      • 비동기화된 메서드를 동기화된 메서드로 래핑
      • List<T> list = Collections.synchronizedList(new ArrayList<T>());
      • Set<E> set = Collections.synchronizedSet(new HashSet<E>());
      • Map<K,V> map = Collections.synchronizedMap(new HashMap<K,V>());
    import java.util.ArrayList;
    import java.util.List;
    import java.util.ListIterator;
    import java.util.Collections;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    public class Ex13 {
    
    	// public static List<Integer> list = new ArrayList<>();
      public static List<Integer> list = Collections.synchronizedList(new ArrayList<>());
      
    	public static void main(String[] args) throws InterruptedException
      {
      	for (int i = 0; i < 10; i++)
          	list.add(i);
              
          Runnable task = () -> { 
          	synchronized(list)
              {
              	ListIterator<Integer> itr = list.listIterator();
                  
                  while (itr.hasNext())
                  	itr.set(itr.next() + 1);
              }
          };
          
          ExecutorService pool = Executors.newFixedThreadPool(5);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          
          pool.shutdown();
          pool.awaitTermination(100, TimeUnit.SECONDS);
      }
    }
    • ConcurrentHashMap 이용
      • 스레드가 컬렉션 객체의 요소를 처리할 때 전체 잠금이 발생하여 처리 속도 느려짐
      • 자바는 멀티스레드가 컬렉션의 요소를 병렬적으로 처리할 수 있도록 java.util.concurrent 패키지에서 사용
      • Map<K,V> map = new ConcurrentHashMap<K,V>();
      • Queue<E> queue = new ConcurrentQueue<E>();
    import java.time.Duration;
    import java.time.Instant;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Collections;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    public class Ex13 {
    	public static Map<String, Integer> syncMap = null; // 컬렉션 객체를 담을 변수를 선언
      public static Map<Stirng, Integer> concMap = null;
      
      public static void performanceTest(final Map<String, Integer> target) throws InterruptedException
      {
      	System.out.println(Thread.currentThread().getName()); //main
          Instant start = Instant.now();
          
          Runnable task = () -> { // 스레드 생성
          	for (int i = 0; i < 10000; i++)
              	target.put(String.valueOf(i), i);
          };
          
          ExecutorService pool = Executors.newFixedThreadPool(5);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          pool.submit(task);
          
          pool.shutdown();
          pool.awaitTermination(100, TimeUnit.SECONDS);
          
          Instant end = Instant.now();
          System.out.println(Duration.between(start, end).toMillis());
      }
      
      public static void main(String[] args) throws InterruptedException
      {
      	syncMap = Collections.synchronizedMap(new HashMap<>());
          performanceTest(syncMap);
          
          concMap = new ConcurrentHashMap<>(); // 속도 더 빠름 
          performanceTest(concMap);
      }
    }
profile
Notion으로 이동 (https://24tngus.notion.site/3a6883f0f47041fe8045ef330a147da3?v=973a0b5ec78a4462bac8010e3b4cd5c0&pvs=4)

0개의 댓글