프로그래머스 자바 중급 강의를 정리 한 내용
Object와 오버라이딩
- Object클래스는 모든 클래스의 최상위 클래스
- 아무것도 상속 받지 않으면 자동으로 Object를 상속
- Object가 가지고 있는 메소드는 모든 클래스에서 다 사용 할 수 있다는 것을 의미
- 가장 많이 사용되는 메소드 (사용 할때 반드시 오버라이딩 해서 사용해야 함)
- equals
- 객체가 가진 값을 비교할 때 사용
- 오버라이딩을 해서 객체의 비교 기준을 설정해야함
- toString
- 객체가 가진 값을 문자열로 반환
- hashCode
- 객체의 해시코드 값 반환
- 해시코드 값은 객체 별로 서로 다른 값을 가지게 하는 것이 좋음
- 자료구조에서 많이 사용됨
- equals
public class Student {
String name;
String number;
int birthYear;
public static void main(String[] args){
Student s1 = new Student();
s1.name = "홍길동";
s1.number = "1234";
s1.birthYear = 1995;
Student s2 = new Student();
s2.name = "홍길동";
s2.number = "1234";
s2.birthYear = 1995;
if(s1.equals(s2))
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
이렇게 사용 할 수 있는데, s1과 s2의 내용이 같더라도 객체의 주소 값이 다르기 때문에 같지 않다는 결과가 나옴
equals를 사용하기 위해선 객체의 주소값으로 비교하지 않고 내부 값(필드)로 변경 하고 싶다면 equals를 오버라이딩 해야함. 또한 HashSet이나 HashMap에서 동일한 데이터로 인식하지 못하는 오류가 발생할 가능성이 있어 hashCode도 함께 오버라이딩 해야 한다.
public class Student {
String name;
String number;
int birthYear;
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result + ((number == null) ? 0 : number.hashCode());
return result;
}
@Override
public boolean equals(Object obj){
if (this == obj) return true; //매개변수 오브젝트와 본인이 같은지(참조가 같은지)
if (obj == null) return false;//매개변수 오브젝트가 null인지
if (getClass() != obj.getClass()) return false; //본인과 매개변수 오브젝트의 class정보가 다른지
Student other = (Student) obj;
if (number == null) {
if(other.number != null) return false;
}else if(!number.equals(other.number)) return false; //매개변수 오브젝트의 number와 본인의 number가 같은지 확인
return true;
}
public static void main(String[] args){
Student s1 = new Student();
s1.name = "홍길동";
s1.number = "1234";
s1.birthYear = 1995;
Student s2 = new Student();
s2.name = "홍길동";
s2.number = "1234";
s2.birthYear = 1995;
if(s1.equals(s2))
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
hashCode()
- 해시 함수의 목표는 서로 다른 객체들에게 최대한 겹치지 않는 고유한 번호를 부여하는 것
- Prime이라는소수를 곱해서 만든다는 개념으로 홀수이면서 소수인 수를 사용하는데 이는 짝수를 곱하면 시프트 연산과 같아 정보가 손실될 확률이 높지만 비트 연산 최적화(31 * i == (i <<5) -i)가 가능하면서도 값이 골고루 퍼지게(해시 충돌 최소화) 도와준다.
- 자세한 내용은 알고리즘때 다시 다루기
- number.hashCode()
- String클래스는 equals()와 hashCode()가 오버라이딩 되어 있음
- 문자열의 내용(값)을 계산해서 반환해줌 따라서 스트링 내용이 같다면 동일한 값이 반환
- 따라서 자바의 표준 클레스들은 이미 내부 값을 바탕으로 hashCode가 구현 되어 있으므로, 이를 활용하면 새로운 객체의 해쉬값도 내부 값에 의존하게 만들 수 있다.
equals()
- 해시로만 비교할 경우 낮은 확률로 해시 충돌이 발생 할 수 도 있음
- equals() 전으로 확인할 것들을 확인 후 확인
- 따라서 String 클래스의 equals()를 통해서 확실히 비교 가능
import java.util.*;
import java.lang.*;
import java.io.*;
public class Student {
String name;
String number;
int birthYear;
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result + ((number == null) ? 0 : number.hashCode());
return result;
}
@Override
public boolean equals(Object obj){
if (this == obj) return true; //매개변수 오브젝트와 본인이 같은지(참조가 같은지)
if (obj == null) return false;//매개변수 오브젝트가 null인지
if (getClass() != obj.getClass()) return false; //본인과 매개변수 오브젝트의 class정보가 다른지
Student other = (Student) obj;
if (number == null) {
if(other.number != null) return false;
}else if(!number.equals(other.number)) return false; //매개변수 오브젝트의 number와 본인의 number가 같은지 확인
return true;
}
@Override
public String toString() {
return "Student [name=" + name + ", number=" + number + ", birthYear=" + birthYear + "]";
}
public static void main(String[] args){
Student s1 = new Student();
s1.name = "홍길동";
s1.number = "1234";
s1.birthYear = 1995;
Student s2 = new Student();
s2.name = "홍길동";
s2.number = "1234";
s2.birthYear = 1995;
if(s1.equals(s2))
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s1);
System.out.println(s1.toString());
}
}
toString()
- 기본 동작은 클래스명@해시코드 형태의 알 수없는 형태의 문자열을 반환
- 객체의 필드값을 사람이 읽기 쉬운 문자열로 출력하기 위해 사용 (디버깅 등)
- 사용은 객체자체를 print해도 되고 toString() 메서드를 사용해도 됨 (객체 자체를 반환하더라도 toString이 자동적으로 사용 됨)
java.lang 패키지/오토박싱
자바는 기본적으로 다양한 패키지를 지원 그중에서도 가장 기본이되며 중요한 패키지는 java.lang
java.lang의 특징 및 포함된 클래스
- import를 하지 않고도 사용 가능
- 기본형타입을 객체로 변환 시킬때 사용하는 Wrapper클래스 존재
- Boolean, Byte, Short, Integer, Long, Float, Double 클래스
- Wrapper 클래스는 기본적으로 불변이다. 즉, Integer 객체에 들어있는 값은 한번 정해지면 바꿀 수 없으며, 값을 변경하면 새로운 객체 생성됨(String과 유사)
- 모든 클래스의 최상위 클래스인 Object
- 문자열과 관련된 String, StringBuffer, StringBuilder
- String : 불변 문자열
- StringBuffer / StringBuilder : 가변 문자열
- 화면에 값을 출력할때 사용하는 System
- 표준 입출력, 환경변수, 가비지 컬렉터 호출 등
- 수학과 관련된 Math
- 모든 메서드가 static으로 정의 되어 있음
- Thread와 관련된 중요 클래스
- 이외에도 다양한 클래스와 인터페이스
Wrapper
public class WrapperExam {
public static void main(String[] args){
int i = 5; //기본형
Integer i2 = new Integer(5); //객체로 생성
Integer i3 = 5; // 원래 기본형이지만 자동으로 Integer형태로 변환된다
int i4 = i2.intValue();
int i5 = i2; // Integer객체타입의 값을 기본형으로 자동으로 변환되어 할당.
}
}
자바 5이후로 오토박싱(Auto Boxing), 오토 언박싱(Auto UnBoxing)을 지원한다. 이때 내부적으로 Wrapper클래스들이 사용됨.
- 오토박싱(Auto Boxing)
- int(기본형) -> Integer(객체)로 자동변환
- 오토언박싱(Auto UnBoxing)
- Integer(객체) -> int(기본형)로 자동변환
자바 9이후로는 new Integer()를 생성자를 직접사용하는 방식을 권장하지 않는다.
- new Integer() 생성자 사용 시
- new 키워드는 무조건 힙(Heap) 메모리에 새로운 객체를 생성
- 오토박싱(valueOf()) 활용 시
- valueOf() 메서드에는 Integer Cache라는 기능이 있음
- Integer Cache란?
- 자바는 -128부터 127사이의 숫자를 미리 객체로 만들어 캐시에 저장해둠
- 따라서 Integer i = 5를 몇번 호출하더라도 단 하나의 '5' 객체를 반환
- 주의점
- -128미만, 127초과 인 숫자도 오토박싱은 정상 작동 하지만 새로운 객체를 생성
- 따라서 Wrapper객체끼리 비교 할때 캐시 여부와 상관없이 .equals()를 사용해서 비교하는 것이 좋음
사용예시
Integer i = 5; //컴파일 과정에서 자동으로 5는 Integer.valueOf(5)로 변경됨.
Integer i2 = Integer.valueOf(5);
int max_int = i.MAX_VALUE; // int가 가질 수 있는 최대값을 반환함.
StringBuffer
스트링버퍼는 스트링과 다르게 불변 문자열이 아닌 가변 문자열을 나타냄
public class StringBufferExam {
public static void main(String[] args){
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append(" ");
sb.append("world");
String str = sb.toString();
System.out.println(str);
StringBuffer sb2 = new StringBuffer();
StringBuffer sb3 = sb2.append("hello");
if (sb2 == sb3)
System.out.println("sb2 == sb3"); //출력됨
String str4 = new StringBuffer().append("hello").append(" ").append("world").toString();
System.out.println(str4);
}
}
StringBuffer
- StringBuffer에 메소드들은 대부분 자기 자신, this를 반환
- 자기 자신의 메소드를 호출하여 자기 자신의 값을 바꿔나가는 것을 메소드 체이닝이라고 한다.
- 주요 메소드
- append() : 문자열 뒤에 내용 추가
- insert(index, data) : 특정 위치에 데이터 삽입
- delete(start, end) : 특정 범위의 문자열 삭제
- reverse() : 문자열 순서 뒤집기
- String은 내용 변경시 새로운 새 객체를 만드는 반면, StringBuffer는 자기 자신의 메모리 공간을 직접 수정하며, 수정 후 자기 자신의 주소값을 반환하기 때문에 효율적이고, 연속적인 작업(체이닝)이 가능.
String Class의 문제점
public class StringExam2 {
public static void main(String[] args){
String str1 = "hello world";
String str2 = str1.substring(5);
System.out.println(str1);
System.out.println(str2);
String str3 = str1 + str2;
System.out.println(str3);
String str4 = new StringBuffer().append(str1).append(str2).toString();
System.out.println(str4);
/* 아래와 같이 수행시 100번의 new연산이 일어남
String str5 = new String();
for(int i = 0; i < 100; i++)
str = str + "*";
*/
StringBuffer sb = new StringBuffer();
for(int i = 0; i < 100; i++)
sb.append("*");
String str5 = sb.toString();
}
}
문자열과 문자열의 합은 내부적으로 append연산을 사용함
문자열을 반복문 안에서 더하는 것은 성능상 문제가 생길 수 있음 (StringBuffer를 새로 생성. 즉, new 연산이 n만큼 발생해 성능문제 발생)
StringBuilder
단일 쓰레드 환경이나 지역변수로 사용할때 권장되는 가변 문자열
StringBuffer와 StringBuilder의 차이점
| 특징 | StringBuffer | StringBuilder |
| 공통점 | 가변 문자열, 메소드 제공(append, insert등) | |
| 동기화 | 동기화 지원(Synchronized) | 동기화 지원 안함 |
| 안전성 | 멀티쓰레드 환경에서 안정 (Thread-Safe) | 멀티쓰레드 환경에서 불안전 |
| 성능 | 상대적으로 느림(동기화 체크로 인한 이슈) | 상대적으로 빠름 |
| 권장 상황 | 여러 쓰레드가 동시에 하나의 문자열에 접근할 때 | 단일 쓰레드환경이나 지역변수로 사용할때 (대부분의상황) |
Math
수학계산을 위한 클래스로 생성자가 Private으로 되어 있기 때문에 new연산자를 이용해 객체를 생성할 수 없지만, 모든 메소드와 속성이 static으로 정의 되어 있기 때문에 객체 생성하지 않고 사용 가능.
public class MathExam {
public static void main(String[] args) {
int value1 = Math.max(5, 20);
int value2 = Math.min(5, -5);
int value3 = Math.abs(-10);
double value4 = Math.random();
double value5 = Math.sqrt(25);
}
}
자주 사용되는 메소드
- Math.max(a, b) : 두 값 중 큰 값을 반환
- Math.min(a, b) : 두 값 중 작은 값을 반환
- Math.abs(a) : a의 절대값을 반환
- Math.random() : 0.0이상 1.0 미만의 실수 반환 (double)
- Math.sqrt(a) : a의 제곱근(실수) 반환 (double)
- Math.ceil(a) : a의 올림 값 (double)
- Math.floor(a) : a의 내림 값 (double)
- Math.round(a) : a의 반올림 값 (결과는 long 또는 int)
java.util패키지
유용한 클래스를 많이 가지고 있으며, 자바공식 문서 링크에서 확인 가능
Since에서 자바 버전이 나오며 그버전 이후에서 사용이 가능하단 의미이며, 생성자에서 deprecated라고 나온 것은 더 이상 지원 하지 않는 것은 아니나 더 효율적이거나 안전한 대체 기능이 있다는 의미임.
util 패키지는 lang과 달리 상단에 import해줘야함
주요 클래스
- 날짜와 관련된 클래스
- Date 클래스 : 지역화를 지원하지 않는 날짜와 시간등을 제공
- Calendar 클래스 : 지역화를 제공하는 날짜와 시간등을 제공 (자바 1.1에 등장)
- 지역화를 가진 클래스들
- Locale로 시작되는 이름을 가짐
- 주의점 : Date와 Calender 클래스는 월(Month)이 0부터 시작함.
- 자료구조 관련 클래스
- List, Set, Collection, Map
Collection 프레임 워크
자료 구조 클래스들을 컬렉션 프레임워크라고 함, 자료 구조란 자료를 저장할 수 있는 구조
자바 2에서 등장
interface Collection
- 중복 허용, 자료가 저장되는 순서 기억 못함
- add(Object) : 반환값 boolean
- 컬렉션에 자료를 추가하는 메서드
- iterator() : 반환값 iterator
- 자료를 하나씩 꺼내기 위한 메서드
- size() : 반환값 int
- 저장된 자료의 수를 반환하는 메서드
- 저장된 자료를 하나씩 하나씩 꺼낼 수 있는 interface Iterator를 반환 (의존 관계)
- interface Iterator
- hasNext() : 반환값 boolean
- next() : 반환값 Object
- interface Iterator
interface Set
- interface Collection을 상속 받는 인터페이스
- 순서가 없고 중복 허용 안함
- 가지고 있는 메소드
- add(object) : 반환값 boolean
- 중복값이 들어온다면 false 반환
- add(object) : 반환값 boolean
- 구현체
- HashSet
- TreeSet
interface List
- interface Collection을 상속 받는 인터페이스
- 순서가 중요하며, 중복된 데이터를 넣어도 인덱스로 구분
- 가지고 있는 메소드
- get(int) : 반환값 Object
- int는 인덱스 값으로 해당 하는 인덱스값을 꺼낼 수 있음
- get(int) : 반환값 Object
- 구현체
- ArrayList
- LinkedList
interface Map
- key관리 체계가 interface Set의 원리와 같음
- Map은 key값이 중복될 수 없음, value는 중복가능
- 가지고 있는 메소드
- put(Object, Object) : void (반환값없음)
- 키와 밸류를 같이 저장
- get(Object) : 반환값 Object
- 키를 매개변수로 받아 들여 키에 해당하는 값을 꺼냄
- keySet() : Set
- 자신이 가지고 있는 모든 키의 값을 반환 할수 있는 메소드
- put(Object, Object) : void (반환값없음)
- 구현체
- HashMap
- TreeMap
Generic
제네릭이란 문법은 인스턴스를 만들때 사용하는 타입을 지정하는 문법
제네릭을 사용하는 대표적인 클래스는 컬렉션 프레임워크와 관련된 클래스.
제네릭이 없었을때 사용 방법
public class Box {
private Object obj;
public void setObj(Object obj){
this.obj = obj;
}
public Object getObj(){
return obj;
}
}
public class BoxExam {
public static void main(String[] args) {
Box box = new Box();
box.setObj(new Object());
Object obj = box.getObj();
box.setObj("hello"); // Object는 최상위 클래스기에 하위 클래스인 String도 가능 상속받은 모든 객체는 가능
box.getObj(); //Object 값으로 변경해서 넣었기 때문에 꺼낼때도 Object로 접근됨
System.out.println((string)box.getObj());//이렇게 형변환을 사용
box.setObj(1);
int value = (int)box.getObj;
}
}
넣을땐 자동 형변환으로 업캐스팅이 일어나지만 꺼내 올땐 Object 객체로 나오기 때문에 형변환을 통해 다운캐스팅을 해줘야함
또한 런타임 에러가 발생할 수 있음 넣은 값은 int 1의 값을 넣고 String으로 형변환시 프로그램이 꺼질 수 있지만 컴파일 시점에서는 런타임을 잡지 못함
Generic을 사용 하는 예시
public class Box<E> {
private E obj;
public void setObj(E obj){
this.obj = obj;
}
public E getObj(){
return obj;
}
}
public class BoxExam {
public static void main(String[] args) {
Box<Object> box = new Box<>();
box.setObj(new Object());
Object obj = box.getObj();
Box<String> box2 = new Box<>();
box2.setObj("hello"); // 매개변수로 다른 값을 넣으면 컴파일 에러가 일어남
String str = box2.getObj();
Box<Integer> box3 = new Box<>();
box3.setObj(4);
int value = box3.getObj();
}
}
Generic을 사용함으로써 선언할때 가상의 타입을 선언하고, 사용시에는 구체적인 타입을 설정함으로써 다양한 타입의 클래스를 이용하는 클래스를 만들 수 있다.
제네릭에서 자주 사용하는 알파벳 의미
| 알파벳 | 의미 | 주로 사용하는 곳 |
| T | Type (타입) | 일반적인 클래스 타입을 의미할 때 (가장 많이 사용) |
| E | Element (요소) | Collection(List, Set등)처럼 내부에 무언가를 담을 때 |
| K | key (키) | Map의 Key를 나타낼 때 |
| V | Value (값) | Map의 Value를 나타낼 때 |
| N | Number (숫자) | 숫자 관련 타입을 나타낼 때 |
Set
중복이 없고, 순서도 없는 자료 구조. 대표적으로 HashSet과 TreeSet이 있다.
public class SetExam {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
boolean flag1 = set1.add("semo");//True
boolean flag2 = set1.add("nemo");//True
boolean flag3 = set1.add("semo");//False
System.out.println(set1.size());//중복 허용 하지 않아 2가 출력됨.
Iterator<String> iter = set1.iterator();
while(iter.hasNext()){
String str = iter.next();
System.out.println(str);
}
}
}
값을 꺼내기 위해선 Iterator를 통해 받아와야하며 내부 값은 String이기에 제네릭을 사용
iter.hasNext()는 꺼낼것이 더 있다면 true를 반환하며
next를 사용 시 객체를 꺼내옴
꺼내온 값은 넣은 순서와 상관없이 결과가 출력 될 수 있음
최근 자바 버전에선 Iterator를 꺼내는 대신 for-each문을 더 많이 사용
예시
public class SetExam {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
boolean flag1 = set1.add("semo");//True
boolean flag2 = set1.add("nemo");//True
boolean flag3 = set1.add("semo");//False
System.out.println(set1.size());//중복 허용 하지 않아 2가 출력됨.
for(String str : set1){
System.out.println(str);
}
}
}
List
데이터의 중복이 있을 수 있으며, 순서도 존재
자바에서 List 사용시 두 가지가 존재하는데 현재 사용하는 것은 util의 List를 의미함
| 구분 | java.util.List (인터페이스) | java.awt.List (클래스) |
| 목적 | 데이터를 저장하는 자료구조 | 화면에 보여주는 UI 컴포넌트 |
| 용도 | 객체들을 순서대로 저장하고 관리할 때 | 윈도우 창에 목록(목록 박스)을 띄울 때 |
| 사용예시 | ArrayList, LinkedList 등 | 운영체제 스타일의 리스트 선택창 |
| 주요 메소드 | add(E e), get(int index), size() | add(String item), select(int, index) |
public class ListExam {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("semo");
list.add("nemo");
list.add("semo");
System.out.println(list.size()); // 3이 출력 됨
for(int i = 0; i < list.size(); i++){
String str = list.get(i);
System.out.println(str);
}
for(String str : list){
System.out.println(str);
}
}
}
리스트 사용법은 위와 같으며 set과 동일하게 for-each문 사용 가능
구현체
- ArrayList
- 내부적으로 배열 사용. 데이터 조회(get)가 매우 빠르지만 중간에 데이터를 넣거나 빼는건 느림
- LinkedList
- 노드들이 연결된 형태. 데이터 삽입/ 삭제가 빈번할 때 유리
- 노드의 값과 다음노드의 주소값(포인터)가 존재
Map
Key와 Value를 쌍으로 저장하는 자료구조로 키는 중복 될 수 없고, 값은 중복 될 수 있다.
public class MapExam {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("001","semo");
map.put("002","nemo");
map.put("003","damo");
map.put("001","lee");
System.out.println(map.size()); // 3을 출력
System.out.println(map.get("001")); // lee를 출력
Set<String> keys = map.keySet();
for(String str : keys){
System.out.println(map.get(str));
}
}
}
key는 중복이 될 수 없고 미리 저장된 키가 있다면 새로운 값으로 다시 저장되므로 이전 저장된 값은 사라진다.
keySet을 사용하면 저장된 Key값들이 set 자료구조로 반환이 된다.
추가로 사용되는 메소드
- map.containsKey(key) : boolean 반환값
- 특정 키가 존재하는지 확인하여 boolean으로 반환
- map.remove(key)
- 특정 키와 그에 해당하는 값을 삭제
Date
날짜와 시간을 구하기 위한 클래스
- JDK 1.0때 만들어졌음.
- 지역화에 대한 부분이 고려되지 않았음
- 지역화란, 지역에 따른 시간, 통화(돈의 단위), 언어 등에 대하여 고려하는 프로그래밍을 지역화에 맞춘 프로그래밍이라고 함.
- Date클래스는 대부분 생성자와 메소드가 Deprecated되어 있음
- 앞으로 지원을 안하거나 문제가 생길 가능성이 있음
public class DateExam {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toString()); // toString이 오버라이드 되어 있음
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd 'at' hh:mm:ss a zzz");
System.out.println(ft.format(date));
System.out.println(date.getTime());
long today =System.currentTimeMillis();
System.out.println(today);
System.out.println(today - date.getTime());
}
}
내가 원하는 포멧으로 시간을 나타내고 싶을땐 SimpleDateFormat을 사용해서 수정할 수 있음
Calendar
Date의 단점을 해결하고 등장한 Calendar클래스 JDK 1.1
많은 상수 값이 있음
- 추상클래스로 존재
- 인스턴스 생성하려면 Calendar 가지고 있는 클래스 메소드 getInstance()를 사용
- getInstance()메소드를 호출하면 내부적으로 java.util.GregorianCalendar 인스턴스를 만들어서 리턴한다.
- getIntance() 사용이유
- 세계적으로 달력 체계는 다양해 그나라에 맞는 달력 객체를 알아서 가져오기 위해 추상클래스.
- getIntance() 사용이유
- GregorianCalendar는 Calendar의 자식 클래스
public class CalendarExam {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; //month의 대한 값은 0부터 시작
int date = cal.get(Calendar.DATE);
int hour = cal.get(Calendar.HOUR_OF_DAY);//HOUR_OF_DAY는 24시간 표기 HOUR는 12시간 표기
int minute = cal.get(Calendar.MINUTE);
//원하는 시간대 만드는 방법
cal.add(Calendar.HOUR, 5);// -5하면 이전 시간으로
}
}
java.time 패키지
Java에서 제공 하는 Date, Time API는 부족한 기능 지원을 포함한 여러 문제점을 가지고 있어 JDK 코어에서 이런 문제점을 해결 하고 직관적인 API를 제공하기 위해 새롭게 재 디자인한 Date, Time API를 Java SE 8부터 제공
public class TimeExam {
public static void main(String[] args) {
LocalDateTime timePoint = LocalDateTime.now();
System.out.println(timePoint);
LocalDate ld = LocalDate.of(2018, Month.DECEMBER, 12);
System.out.println(ld);
LocalTime lt1 = LocalTime.of(17, 18);
System.out.println(lt1);
LocalTime lt2 = LocalTime.parse("10:15:30");
System.out.println(lt2);
LocalDate theDate = timePoint.toLocalDate();
System.out.println(theDate);
System.out.println(timePoint.getMonth());
}
}
Date나 Calender와 다르게 Month값에 +1을 하지 않아도 된다는 장점도 존재
또한 불변의 성질을 가지고 있기 때문에 값을 바꾸면 항상 새로운 객체를 반환해서 멀티쓰레드 환경에서도 안전하다.
JAVA IO
입력은 I(Input), 출력은 O(Output)을 의미하 이러한 입출력을 위한 인터페이스와 클래스들
- Byte 단위 입출력
- InputStream / OutputStream 추상 클래스를 상속 받아 만들어짐
- 파일로 부터 입력받고 쓰기 위한 클래스
- FileInputStream / FileOutputStream
- 배열로 부터 입력받고 쓰기 위한 클래스
- ByteArrayInputStream / ByteArrayOutputStream
- Char 단위 입출력
- Reader / Writer 추상클래스를 상속받아 만들어짐
- 파일로 부터 입력받고 쓰기 위한 클래스
- FileReader / FileWirter
- 배열로 부터 입력받고 쓰기 위한 클래스
- CharArrayReader / CharArrayWriter
- 위에서 사용되는 클래스들은 어디로부터, 어디에라는 대상을 지정할 수 있는 IO클래스이며 장식대상 클래스라고 함
- 아래에서 사용되는 클래스들은 다양한 방식으로 입력하고, 출력하는 기능을 제공하며 장식하는 클래스라고 함
- DataInputStream / DataOutputStream
- 다양한 데이터 형을 입력 받고 출력
- PrintWriter
- 다양하게 한줄 출력하는 println() 메소드를 가지고 있음
- BufferedReader
- 한줄 입력 받는 readLine() 메소드를 가짐
- DataInputStream / DataOutputStream
- 자바 IO는 데코레이터 패턴(Decorator Pattern)을 가지고 있음
- 하나의 클래스를 장식하는 것처럼 생성자에서 감싸서 새로운 기능을 추가 할 수 있도록 클래스를 만드는 방식
| 구분 | 바이트(Byte) 스트림 | 문자(Char) 스트림 |
| 단위 | 8-bit (1Byte) | 16-bit(2 Byte, 유니코드) |
| 용도 | 이미지, 동영상, 실행파일 등 이진 데이터 | 텍스트 파일, HTML등 문자 데이터 |
| 최상위 클래스 | InputStream / OutputStream | Reader / Writer |
대상(Node)스트림 / 장식(Filter) 스트림
- 대상스트림 (Node Stream) : 실제 데이터 소스에 연결되는 클래스
- FileInputStream, FileReader (파일)
- ByteArrayInputStream (메모리)
- 장식스트림 (Filter/Decorator Stream) : 대상스트림에 편리한 기능을 추가하는 클래스
- BufferedReader (한줄씩 읽기)
- DataInputStream (기본 타입 읽기)
Byte 단위 입력
public class ByteExam1 {
public static void main(String[] args){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("src/javaIO/exam/ByteExam1.java");
fos = new FileOutputStream("byte.txt");
int readData = -1; //읽어을 수 있다면 항상 양수가 나옴
while((readData = fis.read())!= -1){
fos.write(readData);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
결과 값은 위의 코드가 text파일로 저장되는 결과로 나옴
해당 코드는 1byte씩 읽어오는 과정을 거치게 되는데
아래코드는 512바이트씩 읽어오는 과정
public class ByteExam2 {
public static void main(String[] args){
long startTime = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("src/javaIO/exam/ByteExam1.java");
fos = new FileOutputStream("byte.txt");
int readCount = -1; //읽어을 수 있다면 항상 양수가 나옴
byte[] buffer = new byte[512];
while((readCount = fis.read(buffer))!= -1){
fos.write(buffer,0,readCount); //읽어온 양만큼 저장하는 코드로 데이터 오염을 막음
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
//메소드를 수행하는데 걸린 시간을 구할 수 있음.
System.out.println(endTime - startTime);
}
}
512바이트씩 가져오는 이유
- 1바이트를 요청해도 512바이트 공간에 1바이트만 담아오기에 CPU활용을 제대로 할 수 없으니 512바이트 전체를 담아오는것
- 주의점
- 컴퓨터 공학에서 입출력의 가장 기본 단위가 512바이트였지만 OS나 SSD/HDD의 포맷 방식에 따라 최적화 단위가 다르며, 실무에서는 1KB나 4KB를 많이 씀.
다양한 타입의 출력
java io객체는 인스턴스를 만들고, 모두 사용하면 close() 메소드를 호출해야 함.
try-with-resources 블럭 선언을 사용하게 되면 close()메소드를 사용자가 호출 하지 않아도 Exception이 발생하지 않았다면 자동으로 close()가 호출된다.
public class ByteExam3 {
public static void main(String[] args){
try(
DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));
){
out.writeInt(100);
out.writeBoolean(true);
out.writeDouble(50.5);
}catch(Exception e){
e.printStackTrace();
}
}
}
이렇게 저장하게 되면 데이터 크기가 정해져 있고 변환 과정이 없어 처리 속도는 빠르지만 저장된 값은 사람이 읽기 어려운 값(데이터)으로 저장이 되며 이를 읽기 위해선 InputStream을 사용해서 읽어야한다.
해당 파일의 크기는 int(4byte) + boolean(1byte) + double(8byte) = 13byte가 된다.
다양한 타입의 입력
방금 넣은 값인 data.txt를 읽어오는 InputStream으로 값을 읽어서 화면에 출력을 예제로 한다.
다양한 타입이 저장이 되어 있기 때문에 DataInputStream 객체를 사용한다.
public class ByteExam4 {
public static void main(String[] args){
try(
DataInputStream in = new DataInputStream(new FileInputStream("data.txt"));
){
int i = in.readInt();
boolean b = in.readBoolean();
double d = in.readDouble();
System.out.println(i);
System.out.println(b);
System.out.println(d);
}catch(Exception e){
e.printStackTrace();
}
}
}
주의할 점은 파일에 저장된 순서대로 읽어 들여야한다.
- int, boolean, double 순서대로 저장했기 때문에 읽어들일 때도 같은 순서로 읽어야 한다.
- 파일의 끝(EOF) 처리
- 만약 파일에 데이터가 얼마나 있는지 모르는 상태에서 계속 읽으려면 while문과 available()메소드를 쓰거나, 읽을 데이터가 없을 때 발생하는 EOFException을 처리해야한다.
Char 단위 입력 입출력 ( Console )
char단위 입출력 클래스
- 클래스 이름이 Reader나 Writer로 끝난다.
- 키보드로부터 입력을 받아서 콘솔에 출력
- System.in : 키보드를 의미 (InputStream)
- InputStream타입으로 BufferReader의 생성자에 바로 들어갈 수 없으므로 InputStreamReader클래스를 이용함.
- BufferedReader : 한줄씩 입력 받기위한 클래스
- 생성자는 InputStream을 입력 받는 생성자가 없다.
- System.in : 키보드를 의미 (InputStream)
public class CharIOExam1 {
public static void main(String[] args){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
try{
line = br.readLine();
}catch (IOException e) {
e.printStackTrace();
}
System.out.println(line);
}
}
InputStream의 역할
System.in은 바이트 단위로 데이터를 받는데. 한글은 한 글자에 2~3바이트이므로 바이트 스트림으로 받으면 깨지기 떄문에 문자 단위로 변환 해줌
Char 단위 입력 입출력 ( File )
파일에서 한줄씩 받아서 파일에 출력 하는 예제
- 파일에서 읽기 위해 FileReader 클래스 이용
- 한 줄 읽어 들이기 위해서 BufferedReader 클래스 이용
- BufferedReader 클래스가 가지고 있는 readLine() 메소드가 한줄씩 읽게 함
- 더 이상 읽어 들일 내용이 없을 때 null을 반환
- BufferedReader 클래스가 가지고 있는 readLine() 메소드가 한줄씩 읽게 함
public class CharIOExam2 {
public static void main(String[] args) {
try (
BufferedReader br = new BufferedReader(new FileReader("src/javaIO/exam/CharIOExam2.java"));
PrintWriter pw = new PrintWriter(new FileWriter("test.txt")) //경로가 없다면 프로젝트 폴더에 저장
) {
String line = null;
while ((line = br.readLine()) != null) {// readLine()이 null을 반환할 때까지 반복
pw.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// close() 명시적 호출 불필요!
}
}
- BufferReader와 FileReader
- 파일을 문자 단위로 읽되, 버퍼에 담아두었다가 한줄씩 꺼내옴
- PrintWrite와 FileWrite
- 파일을 문자 단위로 쓰되, println()메소드를 사용 할 수 있게 해줌 (자동 줄바꿈)
- PrintWrite의 특징
- 생성자의 다양성
- new PrintWriter("filename")처럼 파일명을 바로 넣을 수도 있고, 위와 같이 new FileWriter를 감싸서 사용할 수 있음.
- 자동 줄바꿈
- println()메소드를 사용하게 되면 OS에 맞는 줄바꿈 문자를 알아서 붙여줌
- 생성자의 다양성
어노테이션
Annotation이란?
- 자바 5부터 추가된 기능
- 클래스나 메소드 위에 붙여서 사용
- ex ) @Override
- 소스코드에 메타코드(추가 정보)를 주는 것
- 사용자 정의 가능
- 커스텀 어노테이션이라고 지칭
- 커스텀 어노테이션 이용
- 어노테이션을 정의
- 어노테이션을 클래스에서 사용
- 어노테이션을 이용하여 실행
- 필수 구성요소
- @Retention : 해당 어노테이션이 언제까지 살아 남을지 결정
- SOURCE : 소스 코드에만 있고 컴파일 시 사라짐 (@Override)
- CLASS : 클래스 파일까지는 남지만 실행 시점엔 사라짐
- RUNTIME : 실행 시점(JVM)에도 살아있어 리플렉션으로 정보 확인 가능
- @Retention : 해당 어노테이션이 언제까지 살아 남을지 결정
- 필수는 아니지만 권장 되는 사항
- @Target : 해당 어노테이션을 어디에 붙일지 설정
- ElementType.METHOD : 메소드
- ElementType.TYPE : 클래스, 인터페이스, 열거형
- 이외 추가적으로 더 존재
- @Target : 해당 어노테이션을 어디에 붙일지 설정
@Target(ElementType.METHOD) // 메소드에만 붙일 수 있게 설정
@Retention(RetentionPolicy.RUNTIME) // 실행 시 감지 가능하게 설정
public @interface Count100{
}
public class MyHello {
@Count100
public void hello() {
System.out.println("hello");
}
}
public class MyHelloExam {
public static void main(String[] args) {
MyHello hello = new MyHello();
try{
Method method = hello.getClass().getDeclaredMethod("hello");
//get클래스로 정보를 찾고 특정메소드의 어노테이션 확인
if(method.isAnnotationPresent(Count100.class)){
for(int i = 0; i < 100; i++)
hello.hello();
}else{
hello.hello();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
직접 어노테이션을 만든 예시
Count100 어노테이션을 JVM실행시에 감지할 수 있도록 하려면 @Retention(RetentionPolicy.RUNTIME)을 사용해야 한다
쓰레드 ( Thread )
쓰레드란?
- 운영체제(OS; 윈도우, 리눅스 등) 위에서 실행되는 프로세스들의 작업의 동시성을 주기 위한 흐름을 쓰레드라고 하며 단일 쓰레드와 멀티쓰레드로 구분이 된다.
- 단일 쓰레드 (Single Thread)
- 프로그램이 한 번에 하나의 코드라인만을 따라 가는 것으로 쉽게 계산기를 떠올리면 순서대로 계산을 하는 방식인데 이러한 흐름을 단일 쓰레드라고 하며, 동시 작업이 불가능한 흐름이다.
- 멀티 쓰레드 (Multi Thread)
- 하나의 프로세스(프로그램) 안에서 실행 줄기를 여러 개로 쪼개는 것으로 동시 작업을 가능하게 해주는 흐름이며, 쉽게 운전과 같이 전방 주시, 엑셀과 브레이크, 기어변속을 멈춘 상태에서 하는게 아닌 동시에 이뤄지는 것을 말함
- 단일 쓰레드 (Single Thread)
- 쓰레드를 공부하는 이유
- 자바 프로그램을 실행되는 순간 이미 하나의 쓰레드(main 쓰레드)를 가지고 시작하지만 Thread 클래스를 사용하여 기본 줄기 이외에 또 다른 줄기를 만들어서 동시에 일을 시키고 싶을 때 사용하며 쓰레드를 쓴다는 말은 멀티 쓰레드 환경을 구축한다는 의미
- 멀티 쓰레드 처리 방식
- 동시성 (Concurrency)
- CPU 코어가 하나인데 쓰레드가 여러 개일 때, CPU가 빠르게 쓰레드들을 접근하며 조금씩 일하는 방식으로 동시라고 느낄 수 있지만 번갈아 가면서 작업을 진행
- 병렬성 (Parallelism)
- CPU 코어가 여러개( 듀얼코어, 쿼드코어 등)라서 물리적으로 동시에 여러 쓰레드를 처리하는 방식
- 동시성 (Concurrency)
쓰레드 만들기 ( extend Thread )
자바에서 Thread를 만드는 방법은 크게 Thread 클래스를 상속받는 방법과 Runnable 인터페이스를 구현 하는 방법이 있음
Thread를 상속 받아서 쓰레드 생성하기
- java.lang.Thread 클래스 상속받고 Thread가 가지고 있는 run()메소드를 오버라이딩한다.
public class MyThread1 extends Thread {
String str;
public MyThread1(String str){
this.str = str;
}
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(str);
try{
Thread.sleep((int)(Math.random()*1000)); // 매개변수(밀리세컨드)만큼 멈춤
} catch (InterruptedException e) { //쓰레드의 상태변화가 감지되면(강제중지등)처리할 예외처리
e.printStackTrace();
}
}
}
}
public class ThreadExam1 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("*");
MyThread1 t2 = new MyThread1("-");
t1.start(); //쓰레드 동작시 run()이 아닌 start()를 호출
t2.start();
System.out.println("main end !!!");
}
}
- run()이 아닌 start()를 호출하는 이유
- run() 호출
- 일반 객체의 메소드를 실행하는 것으로 새로운 쓰레드를 만들지 않고 main 쓰레드 위에서 순차적으로 실행됨
- start() 호출
- JVM이 새로운 쓰레드를 호출하고 새로운 쓰레드가 생성되면 그 쓰레드에 run()이 독립적으로 실행됨
- run() 호출
main 쓰레드가 종료되더라도 프로그램이 종료되는 것이 아닌 모든 쓰레드가 종료된 후 프로그램이 종료됨.
Runnable인터페이스를 구현해서 쓰레드 생성
자바는 다중 상속이 불가능 하기때문에 이미 다른 클래스를 상속 받고 있다면 인터페이스를 통해 구현 가능
public class MyThread2 implements Runnable {
String str;
public MyThread2(String str){
this.str = str;
}
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(str);
try{
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadExam2 {
public static void main(String[] args) {
MyThread2 r1 = new MyThread2("*");
MyThread2 r2 = new MyThread2("-");
//Thread를 상속받지 않아 start()메소드가 존재하지 않음
Thread t1 = new Thread(r1);//쓰레드 생성자는 Runnable을 매개변수로 받을 수 있음
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("main end !!!");
}
}
쓰레드와 공유객체
하나의 객체를 여러개의 Thread가 사용한다는 것을 의미
공유객체란?
- 여러 개의 쓰레드가 동일한 메모리 주소를 가리키고 있는 객체
- 쓰레드들이 각자 자기 일을 하다가도, 공유 객체의 상태(변수)를 바꾸면 다른 쓰레드에게도 즉시 영향이 미침
public class MusicBox {
public void playMusicA() {
for(int i = 0; i < 10; i++){
System.out.println("신나는 음악");
try{
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void playMusicB() {
for(int i = 0; i < 10; i++){
System.out.println("슬픈 음악");
try{
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void playMusicC() {
for(int i = 0; i < 10; i++){
System.out.println("차분한 음악");
try{
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MusicPlayer extends Thread {
int type;
MusicBox musicBox;
public MusicPlayer(int type, MusicBox musicBox) {
this.type = type;
this.musicBox = musicBox;
}
@Override
public void run(){
switch(type){
case 1:
musicBox.playMusicA();
break;
case 2:
musicBox.playMusicB();
break;
case 3:
musicBox.playMusicC();
break;
}
}
}
public class MusicBoxExam {
public static void main(String[] args) {
MusicBox box = new MusicBox();
MusicPlayer im = new MusicPlayer(1, box);
MusicPlayer se = new MusicPlayer(2, box);
MusicPlayer mo = new MusicPlayer(3, box);
im.start();
se.start();
mo.start();
}
}
공유객체 MusicBox, MusicBox를 가지는 쓰레드 MusicPlayer
동기화 메소드와 동기화 블록
공유객체가 가진 메소드를 동시에 호출 되지 않도록 하는 방법
- 메소드 앞에 synchronized를 사용
- 여러개의 쓰레드들이 공유 객체의 메소드를 사용할 때 메소드의 synchronized가 붙어있을 경우 먼저 호출된 메소드가 객체의 사용권(Monitoring Lock)을 얻는다.
- 객체의 사용권의 상실 사유
- 메소드 실행 종료
- wait()와 같은 메소드의 호출
- 객체의 사용권의 상실 사유
public class MusicBox {
public synchronized void playMusicA() { //메소드 전체를 동기화 메소드로 만듦
for(int i = 0; i < 10; i++){
System.out.println("신나는 음악");
try{
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void playMusicB() {
for(int i = 0; i < 10; i++){
synchronized(this){//국소적인 문제가 있을만한 부분만 동기화작업하는 것을 동기화 블록이라 함
System.out.println("슬픈 음악");
}
try{
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void playMusicC() {
for(int i = 0; i < 10; i++){
System.out.println("차분한 음악");
try{
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 동기화 메소드 : 객체 전체를 잠그는 안전하지만 무거운 방식
- 동기화 블록 : 필요한 부분만 잠그는 효율적인 방식 / synchronized(this)의 this는 자신의 객체를 의미함
- 동기화 작업의 이유
- 여러 쓰레드가 하나의 공유 객체를 사용할 때 발생할 수 있는 데이터 오염을 방지하고 순차적 실행을 보장
쓰레드와 상태제어
쓰레드가 3개 있다면 JVM은 시간을 짤게 쪼갠 후 한번은 쓰레드 1을 한번은 쓰레드 2를 한번은 쓰레드 3을 실행
이것이 빠르게 작동하니 모두 동작하는 것처럼 보임
쓰레드의 상태
- New Thread : 생성
- Start()를 실행하면 Runnable과 Running 상태로 전환
- Runnable : 실행 가능 상태
- 호출 시 Blocked 상태로 전환되는 메소드
- Thread.sleep() 메소드 : 시간이 지남에 따라 자동으로 블록 해제
- wait() 메소드 : 다른 쓰레드가 notify() / notifyAll() 메소드 호출전까진 블록 유지
- wait() 메소드 호출 당시 모니터링 락을 놓게 됨
- 호출 시 Blocked 상태로 전환되는 메소드
- Running : 실행 상태
- 호출 시 Blocked 상태로 전환되는 메소드
- Thread.sleep() 메소드 : 시간이 지남에 따라 자동으로 블록 해제
- wait() 메소드 : 다른 쓰레드가 notify() / notifyAll() 메소드 호출전까진 블록 유지
- wait() 메소드 호출 당시 모니터링 락을 놓게 됨
- 호출 시 Blocked 상태로 전환되는 메소드
- Blocked : 블록 상태 (멈춘 상태)
- Dead : 종료 상태
- 추가적인 메소드
- yield() 메소드
- 다른 쓰레드에게 자원을 양보하고 본인은 Runnable상태로 돌아
- join() 메소드
- t1.join()을 호출하면 현재 실행중인 쓰레드는 t1이 종료될때 까지 Blocked상태
- yield() 메소드
wait()와 notify()는 반드시 synchronized 블록 내에서만 사용해야한다. 객체의 모니터링을 제어하는 행위이기 때문
쓰레드와 상태제어 ( join )
public class MyThread5 extends Thread {
@Override
public void run() {
for(int i = 0; i < 5; i++){
System.out.println("MyThread5 : "+ i);
try{
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class JoinExam {
public static void main(String[] args) {
MyThread5 thread = new MyThread5();
thread.start();
System.out.println("쓰레드가 종료될때까지 대기.");
try {
thread.join();
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("쓰레드 종료");
}
}
실행결과는 main의 쓰레드 종료 출력 전에 MyThread5의 동작이 우선적으로 진행된다.
쓰레드와 상태제어 ( wait, notify )
public class ThreadB extends Thread {
int total = 0;
@Override
public void run() {
synchronized (this){
for(int i = 0; i < 5; i++){
System.out.println(i + "를 더합니다.");
total += i;
try{
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notify();
}
}
}
public class ThreadA {
public static void main(String[] args) {
ThreadB b = new ThreadB();
b.start();
synchronized(b){
try{
System.out.println("b가 완료될때까지 기다립니다. ");
b.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Total is : " + b.total);
}
}
}
wait와 notify는 동기화된 블록안에서 사용해야 한다.
- 모니터링 락을 제어하는 명령이기 때문에 모니터링 락을 가질 수 있는 동기화 상태에서만 사용이 가능하다.
wait의 동작원리
- 쓰레드가 b.wait()을 호출하면, 자기가 쥐고 있던 b 객체의 모니터링 락을 즉시 내려 놓고 블록 상태로 전환된다.
- 모니터링 락은 대기하던 다른 쓰레드(ThreadB)가 모니터링 락을 얻어 작업을 시작.
notify()
- 연산을 마치고 notify()를 호출하게 되면 블록상태인 쓰레드에게 모니터링 락을 반환하게 되어 동작 가능
- notify() 메소드를 호출 했더라도 즉시 모니터링 락이 넘어가는 것이 아닌 동기화 블록을 나와야 반환됨
데몬 쓰레드
데몬(Daemon)이란?
- 보통 리눅스와 같은 유닉스계열의 운영체제에서 백그라운드로 동작하는 프로그램
- 백그라운드에서 특별한 작업을 처리하는 용도로 사용
- 주요 사용처
- 가비지 컬렉터 (GC) : 자바 메모리관리
- 자동 저장 : 워드나 에디터의 자동 저장 기능
- 화면 갱신 : 시계나 주기적인 상태 체크
- 데몬 쓰레드는 일반 쓰레드(main 등)가 모두 종료되면 강제적으로 종료되는 특징을 가짐
- 반드시 start() 메서드 호출전에 setDaemon(true) 설정 해야함
- 호출 후 설정시 IllegalThreadStateException이 발생
public class DaemonThread implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("데몬 쓰레드가 실행 중");
try{
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread th = new Thread(new DaemonThread());
th.setDaemon(true);
th.start();
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("메인 쓰레드 종료");
}
}
Thread를 상속 받지 않아 별도의 쓰레드를 선언 후 사용하며 main이 바로 종료될 경우 확인이 불가능하니 1000밀리세컨드의 시간을 두고 종료
람다식
람다식이란?
- 자바 8에서 추가된 문법이며 익명 메소드라고도 한다.
- 인터페이스 중에서 메소드를 하나만 가지고 있는 인터페이스를 함수형 인터페이스에만 사용할 수 있는 문법임
- ex) Runnable : run()하나만 가지고 있는 함수형 인터페이스
public class LambdaExam {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println("hello");
}
}
}).start();
}
}
- 자바는 메소드만 매개변수로 전달할 방법이 없고, 인스턴스만 전달 할 수 있다.
- 그렇기 때문에 run() 메소드를 가지고 있는 Runnable객체를 만들어서 전달함. 자바는 메소드만 전달할 수 있는 방법이 없기에 매번 객체를 생성해서 매개변수로 전달함
- 따라서 메소드만 전달해 좀더 편리하게 프로그래밍 할 수 있도록 해결한 것이 람다식이다.
public class LambdaExam {
public static void main(String[] args) {
new Thread(() -> {
for(int i = 0; i < 10; i++){
System.out.println("hello");
}
}).start();
}
}
- JVM은 Thread생성자를 보고 () ->{}이 무엇인지 대상을 추론함
- Thread생성자 api를 보면 Runnable인터페이스를 받아들이는걸 알 수 있음.
- 따라서 JVM이 자동으로 객체를 만들어서 매개변수로 넣어줌
Lambda식 기본문법
(매개변수 목록) -> {실행문}
public interface Compare{
public int compareTo(int value1, int value2);
}
public class CompareExam {
public static void exec(Compare compare) {
int k = 10;
int m = 20;
int value = compare.compareTo(k, m);
System.out.println(value);
}
public static void main(String[] args) {
exec((i, j) ->{
return i - j;
});
}
/* 이방식 또한 가능
public static void main(String[] args) {
exec((i, j) ->i - j);
}
*/
}
인터페이스를 하나만들고 구현체를 람다식으로 변환한 내용
자바는 메소드만 인자로 전달하려면 반드시 객체를 만들어서 전달해야 했으나, Java 8에 람다식이 생기면서, 마치 함수만 전달 하는 것처럼 간편하게 문법을 사용할 수 있다.
람다 문법 규칙
- 매개변수
- 타입 생략 가능 (추론)
- 매개변수가 하나면 () 생략 가능
- 단, 매개변수가 없거나 두개 이상일 경우 () 필수
- 화살표
- ->를 사용해서 매개변수와 실행블록 구분
- 실행문
- 한 줄일 경우 {} 생략 가능
- 한줄이면서 값을 반환 할때 {}를 생략 했다면 return 생략 필수
자바로 스프링을 하기 위해 중급 수업을 듣고 그에 따라 정리한 내용입니다.
문제가 있을 시에는 비공개로 돌리겠습니다.

'BackEnd > 자바 스프링' 카테고리의 다른 글
| [Java Spring] Transaction (0) | 2026.03.22 |
|---|---|
| [Java Spring] 웹 서버 구조와 IoC/DI 핵심 개념 (0) | 2026.03.18 |
| [Java Spring] 객체 지향 설계와 스프링 (0) | 2026.03.04 |
| [Java Spring] AOP : Aspect Oriented Programming (0) | 2026.03.03 |
| [JAVA] 입문 정리 (0) | 2026.02.21 |