728x90
반응형
📒 자바 객체지향 프로그래밍
📕 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 (네트워크 관련 클래스 포함)
- java.lang (기본적인 클래스 포함)
📕 6. String 클래스
- 1) String 선언 방법
- 문자열은 글자들을 큰따옴표로 묶은 값
- 종류
- String str1 = new String("김");
➡️ new 연산자와 문자열 리터럴 매개변수가 있는 생성자를 이용하여 객체를 힙에 만들고 그 참조값을 변수에 대입 (객체를 무조건 새로 만듦) - String str2 = "수";
➡️ 문자열 리터럴을 직접 대입 (이미 만들어진 객체 있다면 객체의 참조값을 변수에 대입)
- String str1 = new String("김");
- 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);
- double e
- StringBuilder 클래스 : 문자열 연결 (내부에 변경 가능한 char[] 변수 가짐)
- append("문자열") // 추가
- delete(0, 3) // 구간 삭제
- replace(3, 4, "문자열") // 값 변경
- reverse() // 순서 반전
- StringBuffer 클래스 : StringBuilder와 기능 동일하지만, 스레드에 불안전함
- StringTokenizer 클래스 : 문자열 분할 (토큰으로 분할)
- StringTokenizer("문자열", "자를 구분자")
- hasMoreTokens() // 토큰이 있으면 true 반환
- nextToken() // 토큰을 차례대로 가져옴
- eqauls() : 변수의 내용이 같은지 비교
📕 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;
- int[] arr = new int[3];
- 3) String 형
- String[] name = new String[3];
name[0] = new String("kim"); // 문자열 리터럴로 대입X
- String[] name = new String[3];
- 4) 클래스형
- Dog[] arr = new Dog[3];
arr[0] = new Dog("멍멍"); // 배열에 객체 저장(초기화)
- Dog[] arr = new Dog[3];
- 5) 매개변수, 반환형
- pubic static int[] makeArray(int len);
int [] arr = new int[3];
return arr;
- pubic static int[] makeArray(int len);
- 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() : 배열 내용 오름차순 정렬
- fill() : 배열의 초기화
📕 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"); } } }
- try {}
- 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 } }
- Queue 구현
- 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() {// 익명 클래스(String s) -> { System.out.println(s);}};
public void move - 익명 내부 클래스
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()); } }
- Unit unit =
- 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); // 해리포터 (길이가 같을 경우 초기값 남음) } }
- sorted()
📕 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(); } }
- Thread 클래스를 상속받아 만들기
- 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); } }
728x90
반응형