하지만 str1과 str2가 각각 있다고 가정했을떄 equals는 true지만 == 는 false가 나온다
equals → 객체 내용비교
== → 같은 객체 또는 같은 참조(주소)를 비교 (기본 데이터는 값을, 객체는 참조위치(메모리주소)를 비교)
2개의 객체가 동일한 HashCode를 갖고있으면 무조건 equals가 true로 나와야하는가?
아니다. HashCode는 해싱 기법에 사용되는 해시함수를 구현한것이다.
해시충돌 - 서로 다른 객체나 데이터가 동일한 해시 코드를 갖는문제
public int hashCode() {
int h = 0;
int len = value.length;
for (int i = 0; i < len; i++) {
h = 31 * h + value[i];
}
return h;
}
//h = 31 * h + value[i]:
//이는 각 문자의 유니코드 값을 더하면서, 31이라는 소수를 곱해 누적된 해시 코드를 계산하는 방식
//문자열의 길이가 같거나, 특정 값에 의해 충돌이 발생할 수 있습니다.
//"FB"와 "Ea"는 서로 다른 문자열이지만 해시 코드가 같습니다(2236).
// F의 유니코드 값은 70, B의 유니코드 값은 66.
// h = 31 * 0 + 'F' = 70
// h = 31 * 70 + 'B' = 2236.
// E의 유니코드 값은 69, a의 유니코드 값은 97.
// 해시 코드는 h = 31 * 0 + 'E' = 69, 그리고 h = 31 * 69 + 'a' = 2236.
자바의 final의 기능
변수,메서드, 클래스 등에 변형이 안되도록 수정을 막아버리는것
변수 - 값 변경, 중간할당 불가 (초기화 값이 그대로감)
메서드 - 오버라이드 불가
클래스 - 상속불가
자바의 Math.Round(-1,5)의 결과는?
반올림을 진행하는데 -1번쨰 자리(소수점 첫번째에서 반올림)
5 초과는 올림, 5 이하는 내림
String은 기본타입?
String는 int, char 같은 기본 데이터 타입이 아닌 참조 데이터 타입이다
Stack이 아닌 Heap부분에서 문자열 데이터가 생성된다 →
String은 불변이라 String a = “A”: a+”B”일때 a에 글자를 더하는게 아닌 AB를 새로 만들어 주소를 다시 참조하는 형태이다
“AB”가 아닌 “A”는 gabage가 되며 GC가 이를 수집하고 삭제한다
JAVA에선 성능 최적화를 위해 String Pool 이란걸 사용하는데 동일한 내용의 String 객체는 메모리 낭비를 막기위해 재사용된다.
자바의 문자열을 조작하는 클래스
앞서 말한 Stirng의 불변성으로 인해 문자열을 조작할경우 성능적인 낭비가 발생할수있으며 StringBuffer, StringBuilder를 사용하여 해당 문제를 해결할수 있다.
StirngBuffer - 멀티쓰레드 환경에서도 안전하다 → 동기화(synchronized)가 자동으로 적용, 문자열을 자주 변경할경우 성능저하를 피할수있다.
StringBuilder - StringBuffer에서 동기화만 빠진것 → 단일 스레드 환경에선 더욱 빠른 성능을 제공한다
String str = “i”와 String str = new String(”i”)가 동일할까?
다르다!
str i 는 문자열 상수를 선언하는 형태로 i 라는 문자열이 만들어지며 JVM의 constant pool(상수 풀)에 저장된다.
만일 str2 - “i”를 사용할경우 상수풀에 존재하는 i를 돌려쓰는는것이다. (싱글톤)
new String은 새로운 String 객체를 생성하는 방식으로 i가 상수 풀에 존재하여도 JVM이 관리하는 heap 공간에 새로운 객체를 저장한다.
만일 str2 =”i”를 선언하고 str과 비교할경우 두 변수가 같은 주소를 가리키며 str2 = new String(”i”)를 할경우 서로 다른 주소를 가리킨다.
문자열을 반복시키는 가장 좋은 방법
리스트를 사용해 반복문으로 변경시키는 방법
직접 리스트를 만들어 반복문으로 변경하는 작업
String b = "asdf";
sb = new StringBuilder();
char[] charArray = b.toCharArray();
for(int i = chrArray.length -1; i>=0; i--){
sb.append(charArray[i]);
}
라이브러리 메서드를 이용하는 방법 (StringBuilder,StringBuffer)
StringBuilder
String a = "asdf";
StringBuilder sb = new StringBuilder();
sb.append(a).reverse();
String 클래스의 일반적인 메서드
endWith : 문자열 마지막에 지정한 문자가 있는지 판단후 있으면 true, 없으면 false 반환
equlas : Object의 메서드이나 오버라이드된것으로 String의 값만 비교해 True, false 를 반환한다
indexOf : 지정한 문자가 문자열 몇번째에 있는지 반환
length : 문자열 길이 반환
replace : 문자열에 지정한 문자 “asd”가 있으면 “hello”로 바꿔서 출력
subString : 문자열에 지정한 범위에 속하는 문자열을 반환 (시작범위 값은 포함, 끝나는 범위 값은 미포함)
추상클래스의 추상 메서드는 필수인가?
추상메서드는 필수가 아니며 추상클래스는 추상메서드를 갖지않아도 무방하다.
추상클래스 - abstract를 사용해 선언된것, 인스턴스화 불가
추상메서드 - 구현부가 없는 메서드로 추상클래스는 추상메서드를 0개 가질수 있다.
구체클래스 - 추상클래스를 할당받은 클래스로 추상메서드를 무조건 구현해야한다
보통 클래스와 추상클래스의 차이
실제 내용이 안들어가있어도 문제가 없다.
클래스의 상속에선 부모 메서드를 오버라이딩 하는것이 필수가 아니며 자식에게 특정 메서드를 구현하라고 강요할수 없다.
추상클래스는 추상 메서드를 자식 클래스가 구현하는게 강제이기에 해당 클래스를 상속받는 자식클래스는 항상 해당 메서드를 구현해야한다.
배열은 원시타입의 배열을 사용할떄 크기를 미리 선언해놓는데 1번 정할경우 바꿀수 없는 제약이 생긴다
이때 자바 컨테이너가 java.util 라이브러리에 컨테이너 클래스가 있으며 List,Set,Queue,Map이 존재한다.
Collection, Collections의 차이
Collection - 데이터의 자료구조를 다루는 Collection 프레임워크(List, Set, Map*)에 필요한 메서드가 선언되있는 인터페이스
add,contains,clear 등 자료구조 관련 메서드가 정의되있따.
Map은 Collection을 구현하지 않았으나 Collection으로 본다. (Key-값의 쌍)
Collections - 컬렉션과 관련된 메서드를 제공하는 클래스
fill, copy, sort등과 같은 Arrays에서 제공하는 메서드들은 물론 멀티 쓰레드 프로그래밍에 필요한 동기화 처리가 가능하도록 제작된 Collection을 반환하는 메서드도 제공
List,Set,Map의 차이점
List - 순서가 있는 데이터구조로 중복을 허용, 인덱스를 통해 데이터 접근이 가능 (ArrayList, LinkedList)
Set - 순서가 없고 중복값 미허용(중복값은 자동으로 제거), 순서가 없기때문에 iterator(순차접근)를 사용하여 검색속도가 빠르다 (HashSet, TreeSet)
Map - key,value의 형태로 key는 미중복, value는 중복허용, iterator를 사용하여 접근은 ㄱ가능하나 KeySet(), entrySet()을 사용해 순회해야한다 (HashMap,TreeMap)
HashMap과 Hashtable의 차이
HashMap
동기화가 보장되지 않아 멀티스레드 환경에서 불안전하나 단일 스레드에선 더 나은 성능을 제공
검색에 뛰어난 성능을 갖고 Key,value로 null을 허용한다
HashTable
동기화가 보장되어 멀티스레드 환경에서 사용이 가능
HashMap보다 처리속도가 느리다.
Key,Value로 Null이 허용되지 않는다.
어떤 상황에서 HashMap과 TreeMap을 사용하는가?
HashMap은 해시함수를 사용하여 빠른 탐색시간을 갖는게 장점(O(1))이며 정렬되지 않는다(key로 검색해버리면 되니까!)
TreeMap은 HashMap보다 느린탐색(O(logN))을 갖는다
TreeMap은 Red-Black Tree로 관리하여 Key값을 기준으로 정렬된 상태를 유지하는데 key가 오름차순으로 자동정렬되기에 정렬된 순서로 데이터에 접근한다
정렬보다 빠른 탐색을 우선해야 할경우 HashMap을 사용
빠른 탐색보다 정렬이 우선되면 TreeMap을 사용
추가 궁금증 - key가 0~100까지 있다고 가정했을때 30번째 key를 찾는다고 가정했을때 TreeMap은 3~4번이면 찾는게 아닌가??
HashMap은 key를 입력받았을때 해시값으로 계산한뒤 이를 이용해 한번에 위치를 특정할수 있다.
TreeMap은 이진탐색으로 0~50, 51~100(루트노드) → 0~25, 26~50 → … 이런 형식으로 나무 뿌리처럼 타고 들어가기에 더 느리다.
만약 해시 충돌이 나타날경우 hash값에 여러개의 key가 매핑될수도 있는데 해시값이 존재하는 버킷에서 체이닝, 오픈 어드레싱 방식으로 충돌을 처리한다. (키가 30개인데 테이블(버킷) 크기가 10개면 3개씩 중복이 나타나는데 이런경우 해시충돌이 발생한다)
버킷 - 해시 테이블에서 데이터를 저장하는 공간 (key의 해시값 보관소)
체이닝 - 하나의 버킷에 여러개의 요소를 저장하는 방법으로 해당 버킷에 있는 데이터를 리스트 형태로 저장하며 삽입된 데이터는 버킷의 리스트 끝에 추가되고 해당 해시값에 매핑된 리스트를 순회하며 원하는값을 탐색
버킷 0: (key1 -> value1) -> (key3 -> value3) 해당 상황에서 key1과 key3은 동일한 해시값을 갖게되어 버킷 0번에 들어가있는 형태이다. 여기서 사용자가 key3을 검색할경우 순차적으로(key1 -> key3의 순서) 탐색하며 키가 일치할경우 해당하는 값을 반환 index 목적으로 사용되는 key1,key3의 해시값이 123123으로 동일할경우 해시충돌이 일어나며 이때 index키가 같으니 대신 사용할수 있는 후보키로 key의 value를 사용하여 한번 더 탐색
오픈 어드레싱 - 충돌이 발생했을떄 빈 버킷을 찾아 데이터를 저장하는형식으로 1개의 버킷에 여러 요소를 저장하는게 아닌 비어있는 버킷을 찾아서 저장한다
HashMap의 구현원칙
Key는 저장된 값을 찾기위하여 컬렉션 내부에서 유일해야한다 (PK일것)
HashMap은 내부적으로 Entry 또는 Node라는 내부 객체로 묶어서 저장하게되는데 키-값 쌍을 저장하는 형태라 키와 값 서로 연관성을 유지할수 있게된다. HashMap의 1개 배열엔 Entry, Node 객체가 저장되며 그 안에서 key와 value가 같이 저장된다.
즉, 1개의 HashMap은 키와 값이 서로 연관된 값이기에 키, 값을 각자 배열로 사용하여 필요할때마다 가져오기 보단 키,값을 1개의 클래스로 정의해 둘을 1개의 배열로 다루는것이 데이터 무결성측면에서 더 바람직하기 떄문이다
HashMap의 hashCode는 Object의 hashCode메서드를 사용하며 각 주소값을 해싱하기에 가장 좋은 방식이기도 하지만 String 클래스와 비슷하게 equals를 재정의 해야한다면 hashCode도 재정의 해야 해싱을 구현한 컬렉션 클래스도 정상적인 동작이 가능하다
HashMap은 HashCode를 사용해 해당 객체가 저장될 객체를 결정하는데 같은 해시코드를 가진 키는 같은 버킷에 저장되며 버킷에서 equals로 동일한 키인지 검사르 진행한다.
HashCode(버킷위치 - 저장주소), equals(객체 동일여부)
만일 a=b 를 진행할경우 equals()를 재정의 한다면 둘은 동일한 객체가 되는데 HashCode는 해당 객체가 동일한 HashCode를 반환해야한다
하지만 a는 1번버킷, b는 2번 버킷에 저장되어있기에 다른 HashCode를 가지며 이로인해 키를 제대로 못찾게된다.
a = (1,test)
b = (2,hello)
a=b
a = (1,test), b= (2,test)
HashSet의 구현원칙
HashSet을 구현할땐 Set의 특징처럼 중복된 요소가 저장되면 안되며 저장순서를 유지하지 않는다.
HashSet의 hashCode는 Object의 hashCode 메서드를 사용하여 각주소값을 해싱해야 정상적으로 동작한다.
오버라이딩을 통해 작성된 hashCode가 만족해야할 3가지 조건
실행중인 애플리케이션에서 동일한 객체에 대해 여러번 hashCode를 호출할때 동일한 int를 반환해야한다(실행할떄마다 동일한 int를 반환할 필요는없다) → 일관성
equals메서드를 이용한 비교에서 true를 얻은 두 객체는 hashCode()로 해시값을 호출했을떄 항상 같아야한다. →
서로 다른 객체들에 대해 반드시 다른 해시코드를 반환할 필요는 없지만 가급적 서로 다른 해시코드를 반환하는게 바람직하다. → equals로 false를 반환하는 객체는 같은 hashCode를 가질수 있으나 서로 다른 해시코드를 반환하는게 효율적이다.
equals는 내용이 같은지 비교하는 함수인데 hashcode가 동일할수가 있다 (해시충돌) 3-2 문항 참조
public class CallByValueExample {
public static void addTen(int number) {
number = number + 10; // 매개변수의 값 변경
System.out.println("Inside addTen: " + number);
}
public static void main(String[] args) {
int a = 5;
addTen(a); // a의 값은 여전히 5
System.out.println("In main after addTen: " + a);
}
}
정의 - Call by value 방식에서는 함수에 인자를 전달할 때, 인자의 실제 값(value)이 복사되어 함수의 매개변수로 전달됩니다.
동작 방식
함수가 호출될 때, 인자의 값이 새로운 메모리 위치에 복사되며 함수 내에서 매개변수의 값을 변경해도, 원래 인자의 값은 불변.
특징
함수 내에서 매개변수의 값을 변경해도 원본 데이터에는 영향을 미치지 않아 부수 효과(side effect)가 없다
큰 데이터를 복사할 때 성능 저하가 발생우려
함수 안에서 인자 값이 변경되더라도, 외부 변수 값은 변경되지 않는다.
addTen(a);를 하여도 기존의 a는 변경되지 않는다
Call by Reference
public class CallByValueOfReferenceExample {
static class Number {
int value;
Number(int value) {
this.value = value;
}
}
public static void addTen(Number number) {
number.value += 10; // 객체의 상태 변경
System.out.println("Inside addTen: " + number.value);
number = new Number(30); // 매개변수에 새로운 객체 할당
System.out.println("Inside addTen after new assignment: " + number.value);
}
public static void main(String[] args) {
Number myNumber = new Number(5);
addTen(myNumber); // myNumber 객체의 상태가 변경됨
System.out.println("In main after addTen: " + myNumber.value); // new Number(30) 할당은 영향을 미치지 않음
}
}
정의: 함수에 인자를 전달할 때, 인자의 메모리 주소(reference)가 전달됩니다. 즉, 함수는 원본 인자를 직접 참조하게 됩니다.
동작 방식: 함수가 호출될 때, 인자의 메모리 주소가 함수의 매개변수로 전달됩니다. 함수 내에서 매개변수를 통해 원본 데이터를 직접 변경할 수 있습니다.
특징: 함수 내에서의 변경이 원본 데이터에 직접 반영되므로, 원본 데이터를 쉽게 수정할 수 있습니다. 그러나 이로 인해 부수 효과가 발생할 수 있으며, 원본 데이터를 실수로 변경할 위험이 있습니다.
addTen(myNumber);를 진행할경우 myNumber의 값이 변경된다.
자바의 경우 call by value 방식을 사용한다.
원본 데이터를 변경할 위험이 줄어들며
하단의 코드는 Call By value를 실제 코드를 비교한 방법이며
callme 함수를 호출한 하여도 원본 객체인 valueTest1의 값이 변경되지 않음을 확인할수있다.
public abstract class DocumentBuilderFactory
extends Object
//Defines a factory API that enables applications to obtain a parser that produces
//DOM object trees from XML documents.
번역
응용 프로그램이 XML 문서로부터 DOM 객체 트리를 생성하는 파서를 얻을 수 있게 하는 팩토리 API를 정의합니다.
public abstract class DocumentBuilder
extends Object
//Defines the API to obtain DOM Document instances from an XML document. Using this class, an application programmer can obtain a Document from XML.
//An instance of this class can be obtained from the DocumentBuilderFactory.newDocumentBuilder() method. Once an instance of this class is obtained, XML can be parsed
//from a variety of input sources. These input sources are InputStreams, Files, URLs, and SAX InputSources.
//Note that this class reuses several classes from the SAX API.
//This does not require that the implementor of the underlying
//DOM implementation use a SAX parser to parse XML document into a Document.
//It merely requires that the implementation communicate with the application using these existing APIs.
번역
이 클래스는 XML 문서에서 DOM Document 인스턴스를 얻기 위한 API를 정의합니다. 이 클래스를 사용하면, 응용 프로그램 프로그래머는 XML로부터 Document를 얻을 수 있습니다. 이 클래스의 인스턴스는 DocumentBuilderFactory.newDocumentBuilder() 메소드로부터 얻을 수 있습니다. 일단 이 클래스의 인스턴스를 얻으면, 다양한 입력 소스로부터 XML을 파싱할 수 있습니다. 이 입력 소스들은 InputStreams, Files, URLs 및 SAX InputSources입니다
NodeList nList = doc.getElementsByTagName("item");
public interface NodeList
The NodeList interface provides the abstraction of an ordered collection of nodes
without defining or constraining how this collection is implemented.
NodeList objects in the DOM are live.
The items in the NodeList are accessible via an integral index, starting from 0.
번역
NodeList인터페이스는 노드들의 순서가 있는 컬렉션을 추상화합니다.
DOM 내의NodeList객체들은 실시간으로 반응합니다.
NodeList내의 항목들은 0부터 시작하는 정수 인덱스를 통해 접근할 수 있습니다.
이 컬렉션이 어떻게 구현되는지 정의하거나 제약하지는 않습니다.
doc.getElementsByTagName("item") - Document 객체인 doc에 대해 getElementsByTagName 메서드를 호출
주어진 태그 이름("item")과 일치하는 모든 요소를 문서 전체에서 찾습니다.
"item" 태그는 RSS 피드에서 개별 뉴스 항목을 나타내는 데 사용되므로, 이 호출은 RSS 피드 내의 모든 뉴스 항목을 나타내는 요소들의 목록을 반환합니다.(getElementsByTagName 메서드는 NodeList 객체를 반환)
NodeList nList - 메서드로부터 반환된 NodeList 객체를 nList 변수에 할당합니다. 이 변수를 통해 개발자는 찾아낸 모든 "item" 요소들에 접근할 수 있게 됩니다. 각 "item" 요소는 개별 뉴스 항목의 데이터를 포함하고 있으므로, nList를 순회하면서 필요한 정보(예: 뉴스 제목, 링크, 설명 등)를 추출할 수 있습니다.
동영상 플레이어 실행 중 영상의 일시정지, 음소거, 자막삽입등 우리가 사용하는 프로그램에선 여러가지 기능(스레드)을 활용하는데 이것을 조정하는게 스레드 상태제어라고 이야기한다.
상태제어의 흐름도와 대표적으로 사용되는 메서드는 다음과 같다
스레드 흐름도 (밑줄친건 가급적 사용하지 말라)
상태제어 메서드
메소드
설명
interrupt()
일시 정지 상태의 스레드에서 InterruptedException 예외를 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다.
notify() norifyAll()
동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다.
resume()
suspend() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. - Deprecated(대신 notify(), notifyAll() 사용)
sleep(long millis) sleep(long millis, int nanos)
주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다.
join() join(long millis) join(long millis, int nanos)
join() 메소드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다.
wait() wait(long millis) wait(long millism, int nanos)
동기화(Synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 상태가 된다. 시간이 주어지지 않으면 notify(), notifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다.
suspend()
스레드를 일시 정지 상태로 만든다. resume() 메소드를 호출하면 다시 실행 대기 상태가 된다. - Deprecated(대신 wait() 사용)
yield()
실행중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다.
stop()
스레드를 즉시 종료시킨다. - Depecated (가능한 쓰지말것)
sleep() - 일정시간동안 정지(시간 경과시 실행대기상태)
주어진 시간동안 일시정지, 시간 경과시 실행대기로 변환
Thread.sleep(1000); //sleep의 단위는 밀리세컨드(1/1000)이다.
yield() - 우선순위가 같거나 높은 스레드에게 실행양보(호출한 스레드가 실행대기상태)
실행한 스레드는 실행대기가 되며 자신보다 우선, 동일한 순위의 스레드에게 실행을 양보한다
반복적인 작업(for,while)을 하는경우 이것이 진행중임을 확인하기위해 일정한 코드를 넣어 확인하는 경우가 있는데 (우리가 핸드폰을 터치중일경우 핸드폰은 하루종일 터치받는 신호를 1밀리초마다 받고있는것이다) 이것이 언제 종료될것인지 정해주지 않는다면 계속 무의미한 반복(메모리 낭비)을 하게되는데 이런낭비를 다른곳으로 돌려쓰기위한 메서드가 yield()이다.
Thread.yield();
join() - 다른 스레드의 종료를 기다림
스레드의 우선순위를 정했지만 선행되야할 스레드를 무시한채 후순위 스레드부터 시작하는경우 예상과 다른 결과가 출력된다. 이러한 문제를 해결하기 위해 사용하는것이 join()메서드이다.
두개 이상의 스레드를 교대로 번갈아가며 실행할경우 사용하며 호출한 스레드는 실행대기상태로 된다.
(캐릭터 2~3개 또는 A,B모드를 한번에 조종한다고 생각하면 이해할것이다)
wait를 사용하기 위해선 공유객체는 두 스레드가 작업할 내용을 각각 동기화 메서드(Synchronized Method)로 구분한뒤 한 스레드가 작업을 완료한경우 notify()를 호출하여 일시정지 상태에 있는 다른 스레드를 실행대기 상태로 만들고 자신은 wait()를 호출하여 일시정지로 만든다.
wait()에 값을 넣는경우 notify()를 호출하지 않아도 값(시간)이 지나면 자동적으로 실행대기 상태가 된다.
//일시정지 = wait()호출, 일시정지 -> 실행대기로 변경 - notify()호출
//초기
A - 실행대기
B - 실행대기
//A실행
A - 실행중
B - 실행대기
//A 종료 및 B 실행
A - 일시정지 (wait())
B - 실행대기 -> 실행
//B 종료 A 실행
A - 일시정지 -> 실행대기 -> 실행 (notify())
B - 일시정지 (wait())
스레드 종료 - Stop플래그, interrupt()
run 메서드가 모두 실행되면 종료되는데 이것이 비정상적으로 종료되는경우 기존에 사용되던 자원은 환원되지 못하고 계속 메모리를 차지하고 있게되는 불상사가 있다.
(다운로드중 인터넷이 끊기면 파일이 fdsragds.434.fds이런 형식으로 남는다)
Stop 플래그
stop 플래그 방식은 조건식에 stop를 넣어서 false인경우 자원반환 코드를 넣는방식이다.
public void run() {
while(!stop){ 반복할 코드 }
스레드가 사용한 자원 정리 코드
}
}
interrupt()
스레드가 일시정지 상태에 있을때 interruptedException예외를 발생시키는 역할로 해당 에러가 발생했을때 run메서드의 정상종료를 꾀할수 있다. (일시정지가 아닌 실행,실행대기 상태에서 interrupt() 사용시 예외가 발생하지 않고 이후에 일시정지 상태가 되어야 예외가 발생한다.)
interrupt가 정상적으로 작동된경우 true를 리턴하는데 이것을 이용하여
Thread.interrupt(); //interruptedException예외발생
데몬스레드(daemon)
주 스레드의 작업을 돕는 보조적인 스레드로 주 스레드 종료시 보조 스레드(데몬스레드)도 종료된다. (종속된다고 생각하면 된다)
주스레드 : 워드, 영상 플레이어, JVM
보조 스레드 : 워드의 자동저장, 동영상 및 음악재생, GC
단, start() 메서드 호출한뒤 setDaemon(true)를 호출하면 ILLEGAlThreadStateException발생
ILLEGAlThreadStateException - 쓰레드 에서 Start로 작업중인데 Start를 한번 더 실행한경우 ("이미 실행중인데 또 실행하라고?")
또한 스레드 그룹에서 제공하는 여러 메서드가 있는데 한가지 예시로 interrupt()메서드를 호출하면 스레드 전체의 제어 및 정보를 얻어올수 있기 때문이다.
(스레드 그룹의 interrupt() 메서드 호출시 각 스레드마다 interrupt() 메서드를 내부적으로 호출해준다.)
interrupt - 스레드에게 작업을 멈춰달라고 요청하는것
여담으로 JVM은 실행될때 system이름의 스레드 그룹을 생성하고 JVM이 운영에 필요한 스레드를 생성하여 system그룹에 포함시킨다. (Finalizer(GC관련 스레드)가 있다.)
스레드그룹이 갖고있는 주요 메서드
코드
설명
(int형) activeCount()
현재 또는 하위그룹에서 활동중인 모든 스레드의 수를 리턴
(int형) activeGroupCount()
현재 그룹에서 활동중인 모든 하위그룹의 수를 리턴
(int형) getMaxPriority()
현재 그룹에 속한 스레드가 가질수 있는 최대 우선순위를 리턴
(bool형) isDestoryed()
현재 그룹이 삭제되었는지 여부를 리턴
(bool형) isDaemon()
현재 그룹이 데몬그룹(보조그룹)인지 여부를 리턴
(bool형) parentOf(ThreadGroup g)
현재 그룹이 매개값으로 지정한 스레드 그룹의 부모인지 여부를 리턴
(void형) checkAccess()
스레드가 스레드 그룹을 변경할 권한여부 체크(관리자인지?) 권한이 없으면 SecurityException을 발생
(void형) destory()
현재 및 하위그룹을 모두 삭제 (그룹내에 포함된 스레드가 종료상태가 되는게 전제조건)
(void형) setMaxPriority(int pri)
현재 그룹에 속한 스레드가 가질수 있는 최대 우선순위를 설정
(void형) setDaemon(boolean daemon)
현재 그룹을 데몬그룹(보조그룹)으로 설정한다
(void형) list()
현재 그룹에 포함된 스레드와 하위그룹에 대한 정보를 출력
(void형) interrupt()
현재 그룹에 포함된 모든 스레드를 interrupt()한다.
(String형)getName()
현재 그룹의 이름을 리턴
(ThreadGroup형)getParent()
현재그룹의 부모그룹을 리턴한다.
스레드 그룹 생성
자신이 원하는 스레드 그룹을 만들어 관리하고 싶다면 하단의 코드를 활용하여 스레드 그룹을 생성하면 된다.
//parent - 부모로 만들 스레드그룹 이름
//name - 생성할 스레드그룹 이름 작성
ThreadGroup tg = new ThreadGroup(String name); //메인스레드에서 생성시 main의 하위스레ㅡ
ThreadGroup tg = new ThreadGroup(ThreadGroup parent, String name);
//스레드 그룹을 매개값으로 Thread 생성자
//target - Runnable 타입 구현객체
//name - 생성할 스레드의 이름
//stackSize - JVM이 할당할 stack의 크기
Thread t = new Thread(ThreadGroup group, Runnable targer);
Thread t = new Thread(ThreadGroup group, Runnable targer, String name);
Thread t = new Thread(ThreadGroup group, Runnable targer, String name, long stackSzie);
Thread t = new Thread(ThreadGroup group, String name);
모든 스레드는 반드시 하나의 스레드 그룹에 포함되며 명시적으로 스레드 그룹에 포함시키지 않으면 자신을 생성한 스레드 그룹에 속하게된다.(디폴트)
EX : 우리가 생성하는 작업스레드는 대부분 main스레드가 생성한다(main 스레드 그룹에 속한다)
또한 현재 사용중인 스레드 그룹의 이름을 확인하려면 다음과 같은 코드를 작성한다.
//현재 스레드가 속한 스레드그룹의 이름
ThreadGroup group = new Thread.currentThread().getThreadGroup();
String a = group.getName();
//프로세스내에서 실행하는 모든 스레드의 대한 정보 (그룹명, 소속그룹)
스레드 그룹 예시 코드
//자동 저장용 코드
public class AutoSaveThread extends Thread {
public void save(){ //메서드 생성
System.out.println("저장완료");
}
@Override
public void run(){
while(true){ //무한반복
try{
Thread.sleep(1000); //1000밀리초 = 1초 딜레이
} catch (InterruptedException e) {
break; //while 탈출
}
save(); //상단의 save 호출
}
}
}
스레드 그룹 예시코드 2
//스레드 정보 얻기 코드
public class ThreadInfoExample {
public static void main(String[] args) {
AutoSaveThread autoSaveThread = new AutoSaveThread(); //스레드 생성자
autoSaveThread.setName("AutoSaveThread"); //스레드명 변경
autoSaveThread.setDaemon(true); //데몬(보조)스레드 선언
autoSaveThread.start();
//autoSaveThread.isDaemon(); //autoSaveThread 데몬스레드인지 확인용 (true 리턴)
Map<Thread. StackTraceElement[]> map = Thread.getAllStackTraces();
Set<Thread> threads = map.keySet();
for(Thread thread : threads) { //Thread 전체 루핑
System.out.println("Name: " + thread.getName() +
((thread.isDaemon())?"(데몬)": "(주)"));
System.out.println("\t" + "소속그룹: " + thread.getThreadGroup().getName());
System.out.println();
}
}
}
(루핑 - 요소 전체를 반복하라)
스레드 풀
멀티스레드, 병렬작업이 많아질수록 스레드 증가, 이에 따른 스케쥴링으로 메모리 사용량이 늘어나는데 이를 내버려뒀다간 스레드 폭증으로 PC가 뻗어버리게된다.
이를 막기 위해선 스레드풀이란 기능을 활용해야한다.
스레드풀 - 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해놓고 작업 큐(대기열)에 들어오는 작업을 하나씩 스레드가 처리한후 작업처리가 끝난 스레드는 다시 작업 큐(대기열)에 들어오는작업을 가져와 처리한다.
(손님이 들어오는대로 다 받으면 식당 내부에서 난리가 날테니 줄세워놓고 한명씩 들어오라 하는것)
자바는 스레드풀 생성을 위하여 java.util.concurrent 패키지에 ExecutorService인터페이스 및 Executor 클래스를 제공한다
스레드풀 생성
ExecutorService 구현객체는 Executor 클래스의 여러메서드중 하단의 양식을 사용하여 생성한다.
//스레드풀 생성 1번
//양식 : newCachedThreadPool()
//초기 스레드 및 코어스레드 : 0개, 최대스레드 : interger.MAX_VALUE
ExecutorService executorService = Executors.newCachedThreadPool();
//스레드풀 생성 2번
//양식 : newFixedThreadPool(int nThreads)
//초기 스레드 : 0개, 코어스레드 : nThreads개, 최대스레드 : nThreads
ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() //JVM에서 현재 이용가능한 코어갯수를 리턴
//CMD사용하기 - Runtime.getRuntime()
//JVM에서 현재 이용가능한 코어갯수를 리턴 - availableProcessors()
)
//스레드풀 생성 3번 (Executor 클래스 미사용시)
//초기스레드는 0개이다.
//ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
ExecutorService threadPool = new ThreadPoolExecutor(
3, //코어 스레드 개수 -> corePoolSize(생성할 개수)
100, //최대 스레드 개수 -> maximumPoolSize(생성할 최대 개수)
120L, //놀고있는 시간으로 초과시 삭제 -> keepAliveTime(유지 시간)
TimeUnit.SECONDS, //놀고있는 시간단위 (초) -> TimeUnit unit
new SyschronousQueue<Runnable>() //작업 큐 -> BlockingQueue<Runnable> workQueue
);
스레드가 작업처리를 완료하고 외부 Result객체에 작업결과를 저장하면 애플리케이션이 이것을 활용해 작업이 가능하다.
class Task implements Runnable {
Result result; //외부 객체 사용을 위해 선언
Task(Result result) { this.result = result; } //외부 Result 객체를 필드에 저장
@Override
public void run() {
//작업코드
//처리 결과를 result에 저장
}
}
작업완료순으로 통보
작업을 요청한 순서대로 작업처리가 완료되는건 아니며 드문드문 완료된 스레드를 찾는 작업은 CompletionService의 poll(), take()메서드를 사용한다
리턴타입
메서드명(매개변수)
설명
Future<V>
poll()
완료된 작업의 Future를 가져옴 완료된 작업이 없을경우 null리턴
Future<V>
poll(long timeout, TimeUnit unit)
완료된 작업의 Future를 가져옴 완료된 작업이 없을경우 timeout까지 블로킹후 그래도 없으면 null 리턴
main스레드의 첫 코드부터 아래로 순차적으로 실행하며 코드 중간에 외부에 있는 스레드가 있을경우 그것을 중간에 실행한뒤 메인스레드로 넘어오는 방식이다.
요점은 싱글 스레드에선 메인스레드 종료시 프로세스가 종료되지만 멀티스레드 종료시 실행중인 스레드가 1개라도 있다면 프로세스는 종료되지 않는다. -> 기타 프로그램이 실행중일때 종료하려면 이 프로그램과 관련된 프로세스를 종료해야 가능하다 (백신 삭제할때 자주 나온다)
public static void main(String[] args){
//메인스레드는 요기서 main()을 뜻한다
}
작업 스레드
스레드를 구현하는 방식에는 2가지 방식있다.
1. Thread클래스로부터 직접생성
2. Theard를 상속받아 하위클래스 생성
스레드 클래스로부터 직접생성
먼저 스레드를 직접 생성했을때 스레드는 Thread-n이라는 형태의 이름으로 저장되며 이를 변경하려면 Thread.currentThread()메서드를 우선 선언해야하고 setName, getName 활용해 이름을 변경할수 있다.
//이름확인, 수정을 위한 선행코드
Thread thread = Thread.currentThread();
//이름변경
thread.setName("스레드 이름");
//이름확인(호출)
thread.getName();
하단의 코드는 터치했을때 소리와 동시에 텍스트를 출력하는 코드를 만들었지만 실행시 (A)가 전부 실행된뒤 (B)가 실행되는 순서로 예상과 다른 결과가 나타났다 (위에 말한 쓰레드는 한번에 1줄의 코드씩 실행하기에 (A)가 전부 실행되야 (B)가 실행되기 때문)
//양식 Thread a = new Thread (Runnable target);
public class BeepPrintEx1{
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit;
for (int i=0; i<5; i++) {
toolkit.beep(); //1초마다 소리 출력 (A)
try{ Thread.sleep(1000); } catch (Exception e) {}
}
for (int i=0; i<5; i++) { //1초마다 텍스트 출력 (B)
System.out.println("띵")
try { Thread.sleep(1000); } catch(Exception e) {}
}
}
}
이를 해결하기 위해선 싱글쓰레드가 아닌 멀티쓰레드로 코드변경이 필요하고 이를 하단의 코드와 같이 작성한다면 문제없이 소리와 텍스트가 동시에 출력됨을 확인할수 있다.
(A) 코드 - 1초마다 사운드 출력
//소리출력 스레드 (A)
//Thread 직접생성
public class BeepThread extend Thread {
@Override
public void run() {
Toolkit toolkit = Toolkit.getDefaultToolkit;
for (int i=0; i<5; i++) {
toolkit.beep(); //1초마다 소리 출력 (A)
try{ Thread.sleep(1000); } catch (Exception e) {}
}
}
}
(B) 코드 - 1초마다 사운드 및 텍스트 출력 ((A)코드를 객체화하여 동시실행)
public class BeepPrintEx2 {
public static void main (String[] args) {
Thread thread = new BeepThread(); //(A)코드 객체화
thread.start(); //(A)코드 실행
for (int i=0; i<5; i++) { //1초마다 텍스트 출력 (B)
System.out.println("띵")
try { Thread.sleep(1000); }
catch(Exception e) {}
}
}
}
Theard를 상속받아 하위클래스 생성
1
스레드의 우선순위는
동기화메서드(synchronized)
OS에서 실행중인 하나의 애플리케이션을 프로세스라 칭하고멀티스레드는 1개의 프로세스가 여러개의 스레드를 갖는것이라 배웠다
멀티스레드로 작업할경우 여러 작업을 동시에 할수 있지만 여기에도 단점이 한가지 존재한다.
그것은 바로 객체를 공유해서 사용하는 경우이다.
객체의 공유
멀티스레드를 사용하는 프로그램은 스레드들이 객체를 공유해서 작업해야하는 경우가 있는데 같은객체를 공유하게되면 스레드A를 사용하던 객체가 스레드B에 의하여 다른결과가 나올수도 있다.
한가지 예시를 들자면 계산기 1개로 2명이 돌려쓰는경우가 있는데 내가 계산한 이전값(A)이 다른사람이(B) 써서 변경되는 사례로 이를 하단에 코드로 구현하였다..
// 메인코드
public class test1 {
public static void main(String[] args) {
Calculator calculator = new Calculator();
User1 user1 = new User1(); //User1 스레드 생성
user1.setCalculator(calculator); //공유객체 설정
user1.start(); //User1 스레드 시작
User2 user2 = new User2(); //User2 스레드 생성
user2.setCalculator(calculator); //공유객체 설정
user2.start(); //User2 스레드 시작
}
}
(A),(B) 코드 -> 이름, 저장값만 다름
public class User1 extends Thread { //(A)
private Calculator calculator;
public void setCalculator(Calculator calculator) {
this.setName("User1");
this.calculator = calculator;
}
public void run() {
calculator.setMemory(100) //메모리에 100 저장
}
################################
public class User2 extends Thread { //(B)
private Calculator calculator;
public void setCalculator(Calculator calculator) {
this.setName("User2");
this.calculator = calculator;
}
public void run() {
calculator.setMemory(50) //메모리에 50 저장
}
공유객체코드
//계산용 코드
public class Calculator{
private int memory;
public int getMemory() { //메모리 값 반환용
return memory;
}
public void setMemory(int Memory) { //초기 메서드
//public synchronized void setMemory(int Memory) { //동기화 메서드 synchronized추가
this.memory = memory;
try{
Thread.sleep(2000);
} catch(InterrupedException e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
//Thread.currentThread().getName()는 스레드 이름이다.
}
}
현재 Calculator의 setMemory를 본다면 User1이 진행되고 User2가 진행되지만 User2가 User1과 같은객체를 참조하기에 User1과 User2는 같은결과가 나오는것을 확인할수 있다.
이러한 문제를 방지하기 위한게 동기화 메서드(synchronized)이다.
상단의 코드를 그림으로 표현하면 다음과 같다.
현재 코드가 어떤방식으로 진행되는지 나오는데 Calculator가 동일한 객체를 참조해서 사용하기 떄문에 User1도, User2도 50이 나타나는것이다.
이것을 현재 주석처리된 synchronized를 추가한다면 다음과 같은 그림이 된다.
Calculator에 synchronized를 추가하여 User1이 실행되는동안 User2가 실행되지 않도록 만든것이다,
요점정리
1. 프로세스(process) - OS에서 실행중인 하나의 애플리케이션 // 스레드(thread) - 1가지 작업을 실행하기 위하여 순차적으로 실행할 코드
2. 싱글 스레드에선 메인스레드 종료시 프로세스가 종료 / 멀티스레드 종료시 실행중인 스레드가 1개라도 있다면 프로세스는 종료되지 않는다. (백신을 끄려면 현재 켜져있는 익스플로러를 전부 닫아라)
3. 스레드의 우선순위를 정하는것을 스레드 스케쥴링이라 하며 우선순위방식 (1~10, 디폴트5) / 순환할당방식 (인터벌로 실행되며 우선순위는 JVM이 임의로 배정) 2가지이다.
4. 작업스레드를 구현하는 방식은 클래스를 직접생성, 쓰레드를 상속받아 하위클래스로 생성한다
5. synchronized는 객체를 동기화 시키는 기능으로 멀티스레드의 공유객체간 참조를 막아 비정상적인 출력을 막는것이 목적이다.
byte 배열(value)을 인코딩하는데 사용하는 변수이며 그리고 지원하는 인코딩 타입은LATIN1, UTF16이다.
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;
equals 연산자는 두객체의 동등성을 판별하기 위하여 사용하지만 equals연산자를재정의하지 않으면 내부적으로 == 연산자와 같은 로직을 수행하므로 차이가 없다 그렇기때문에 각 객체의 특성에 맞게 재정의를 하여야 동등성의 기능을 수행한다.
//int형 equals 연산자
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
//Object의 equals 연산자
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
//ArrayList의 equals 연산자
private boolean equalsArrayList(ArrayList<?> other) {
final int otherModCount = other.modCount;
final int s = size;
boolean equal;
if (equal = (s == other.size)) {
final Object[] otherEs = other.elementData;
final Object[] es = elementData;
if (s > es.length || s > otherEs.length) {
throw new ConcurrentModificationException();
}
for (int i = 0; i < s; i++) {
if (!Objects.equals(es[i], otherEs[i])) {
equal = false;
break;
}
}
}
other.checkForComodification(otherModCount);
return equal;
}
동일성 - hashcode()
hashcode란 객체를 식별할 하나의 정수값을 의미한다. 일반적으로 객체의 주소값(메모리번지)을 변환하여 생성한 객체의 고유한 정수값이다.
(논리적 동등 비교시 hashcode를 오버라이딩 할 필요성이 있다고 하는데 이것은 hashset,hashmap,hashtable방식이며 3항목은 강의 순서에 맞게 나중에 돌아와서 추가로 작성예정..)
public class Key{
public int number;
public Key(int number){
this.number = number;
}
@Override
public boolean equals(Object obj) [
if(obj instanceof Key) { //클래스확인용
Key compareKey = (key) obj; //캐스팅
if(this.number == compareKey.number) {
return ture;
}
}
return false;
}
}
//instanceof는 서로 알맞는 타입을 찾을때까지 컴파일시 모든 타입을 돌며 검사하는데 이러한 단점때문에 가급적 다형성을 활용한 타입점검이 필요하다
Runtime Exception 클래스 - 프로그래머 실수에 따른 문제발생 (코드 안맞음)
Runtime Exception 종류
예외코드
설명
NullPointerException
객체의 참조가 없을경우 (Null을 갖는경우)
ArrayIndexOutOfBoundException
배열을 참조하는 인덱스가 잘못된경우(초과하는경우)
NumberFormatException
문자열 <-> 숫자간 변경할수 없는경우 출력
ClassCastException
클래스 형변환(Casting)이 어려울경우 -> 다형성 참고
ArithmeticException
예욎거인 산술조건이 발생하는경우 (5/0.0/6 등)
NoClassDefFoundException
원하는 클래스가 없음
NegativeArraySizeException
배열의 크기가 음수인경우
OutOfMemoryException
사용 가능한 메모리가 없는경우(OM)
interruptedException
ILLEGAlThreadStateException(대소문자 확인 잘할것..)
스레드가 실행중인데 또 Start를 호출한경우(이중실행시)
상단 외에도 기타 문제가 발생하면 콘솔창에 예외코드가 나타나게 되는데 이러한문제를 방지하는걸 예외처리 한다고 표현한다.
예외를 처리하는 방법은 크게 2가지 방법이 있다.
1.try-catch-finally 문법(JDK 7 미만), try-with-resources(JDK 7 이상)문법을 활용해 에러를 처리하는 방법
2.throws 2가지이다.
try-catch-finally
//try-catch예시
//try { 에러 발생우려 코드 }
//catch ( 예외클래스 예외객체생성 ) { 해당 에러시 실행할 내용 }
try {
//예외 가능성이 있는 문장을 넣는다
} catch (Exception1 e1) { //case1
//Exception1이 발생할 우려가 있는경우
System.out.println("잔액이 부족합니다");
} catch (Exception e2) { //case2
System.out.println("최소주문금액이 부족합니다");
} catch (Exception e3) { //case3
System.out.println("배달불가지역 입니다");
} finally {
System.out.println("이전화면으로 돌아갑니다"); //예외처리가 발생해도 실행
}
이것을하단의 예시를 참고하면서 설명을진행하겠다.
사용자 정의 예외클래스(A)
//사용자 정의 예외 클래스 (A)
public class BalanceInsufficientException extends Exception {
public BalanceInsufficientException {}
public BalanceInsufficientException (String messge) { //오버로딩
super(messge); //부모클래스 호출
}
}
에러 발생메서드 (B)
//사용자 정의 예외 발생시키기(B)
public class Account{
private long balance;
public Account() {
}
public lon getBalance() { //값얻기.
return balance;
}
public voud deposit (int money) {
balance += money;
}
public void withdraw(int money) throws BalanceInsufficientException {
if (balance < money) {
throw new BalanceInsufficientException("잔고부족:"+(money-balance)+"원 모자람");
}
balance -= money;
}
}
실제코드(C)
//사용자 정의 예외 테스트 코드(C)
public class AccountExample {
public static void main(String[] args) {
Account account = new Account(); //생성자
//예금하기
account.deposit(10000); //입금하는금액
System.out.println("예금액 : "+account.getBalnce());
//출금하기
try {
account.withdraw(30000); //가격비교용 메서드
} catch(BalanceInsufficientException e) { //에러throws
String messge = e.getMessage();
System.out.println(messge);
System.out.println();
e.printStackTrace();
}
finally {
if(e != null) is.close();
}
}
}
//deposit = 입금액
//getBalance = 현재 잔고에 있는금액 (정상입금 확인용)
//withdraw = 입금액과 상품가격 비교 메서드
(A)는 사용자가 예외클래스를 정의한 내용
(B)는 예외를 발생시켰을때 실행할 내용을 작성
(C)는 실제 사용되는 코드 이다.
(C)를 기준으로 설명하자면
deposit 메서드를 사용하여 10000을 넣었고((B)의 Balance에 삽입), 정상적으로 입금된것인지 확인하기 위하여 출력문을 작성하였다.
이후 withdraw메서드를 활용하여 Balance에서 값을 출력하는 방식인데 Balance는 10000밖에 없으니 당연히 e에러가 출력될것이다.
이것을 (C)의 catch에서 잡아서 (B)로 예외(e)를 (B)로던지고 (B)는 에러에 해당하는 메세지를 출력하게 되는것이다.
하지만 try-catch 방식은 사용이 종료한후 close메서드를 호출해야 자원을 반납할수 있었는데 이러한 방식을 해결하기 위해 나타난것이 try-with-resources 방식이다.
try-with-resources
try-with-resources는 close를 자동적으로 진행되는 기능으로 하단의 코드를 보며 설명하겠다.
//try-catch-finally예시
public class TCF{
public stati void main(String[] args) {
FileWriter f = null;
try{
f = new FileWriter ("data.txt");
f.write("Hello");
} catch (IOException e){
e.printStackTrace();
} finally{
if(f != null){
try {
f.close();
} catch(IOException e){
e.printStackTrace();
}
}
}
}
}
상단의 코드를 줄인것이 하단의 코드이다.
//try-with-resource예시
public class TWR{
public stati void main(String[] args) {
//try with resource statemanets
try (FileWriter f = new FileWriter ("data.txt")) {
//close를 시켜야하는 코드를 파라미터로 넣는다.
f.write("Hello");
} catch(IOException e){
e.printStackTrace();
System.out.println("에러내용 :"e.getMessage());
//e.getMessage 메서드는 내장메서드로 어떠한 오류인지 간략하게 출력해주는 메서드
}
}
}
코드의 줄이 눈에띄게 줄어든것을 확인할수 있다.
try안에 close()가 필요한 클래스를 인스턴스화 시켜 자동으로 f.close를 내부적으로 실행하는것이다.
(Filewriter에 내장된 API중 AutoCloseable란 인터페이스가 try문 안에 선언된 객체를 자동으로 close해주기 때문에 가능)
추가로 printStackTrace()는 예외 발생 당시의 호출스택(Call Stack)에 잇던 메서드의 정보와 예외결과를 화면에 출력하는것으로 디버깅할때 유용하게 사용할수 있다.
하지만 성능은 떨어져(리플렉션을 활용해 에러를 추적하기에 오버헤드 발생, 스택정보 취합으로 서버에 부하 우려) 실제 런칭할때는 없애고 테스트 할때만 사용하는게 좋다.
Throws,Throw
예외를 임의적으로 발생시켜 해당 문제가 발생했을때 해당 에러가 나타나도 정상적으로 작동되도록 하는 기능이다.
throws - 메서드,새생성자가 수행할때 발생하는 예외를 선언할때 사용하며 상위의 메서드로 예외를 보낸다(던진다)
throw값을 받는곳에서 예외를 처리하도록 예외처리의 주체를 바꾸는것
public class ThrowExample {
//요기!!
public void divideNum(int i, int a) throws OneShouldNotInHereException {
if (a == 1) {
throw new OneShouldNotInHereException();
} else {
System.out.println(i / a);
}
}
public static void main(String[] args) {
ThrowExample throwExample = new ThrowExample();
System.out.println("*첫 번째 계산");
// 예외 발생 가능성이 있는 코드
int i = 100;
int a = 1;
try {
throwExample.divideNum(i, a);
}
catch (OneShouldNotInHereException e) {
System.out.println("에러: 1로 나눌 수 없습니다.");
}
}
}
throw - 강제로 예외를 발생시키는경우 사용, 발생시킨 예외를try-catch로 잡는다.
나눗셈을 만들었는데 1로 나누면 안되도록 조건을걸어 코딩
public class ThrowExample {
public void divideNum(int i, int a) {
if (a == 1) {
try {
throw new OneShouldNotInHereException();
} catch (OneShouldNotInHereException e) {
System.out.println("에러: 1로 나눌 수 없습니다.");
}
} else {
System.out.println(i / a);
}
}
public static void main(String[] args) {
ThrowExample throwExample = new ThrowExample();
System.out.println("*첫 번째 계산");
// 예외 발생 가능성이 있는 코드
int i = 100;
int a = 1;
throwExample.divideNum(i, a);
}
}
예외코드
public class OneShouldNotInHererException extends Exception{
}
앞에서의 포스팅은 전부 클래스를 단독으로 사용하는것을 예제로 들었는데 코드를 작성했을때 class안에서 다른 class를 선언한다던가 잘못 복사했을때 내부에 class파일가 여러개 생기는 경우가 종종 있었을것이다.
이것을 자바에선 중첩클래스라고 의미하며 이것에 대해 설명하려고 한다.
중첩클래스
중첩 클래스는 클래스 안에서 또다른 클래스가 정의되는것을 말한다.(클래스 안의 클래스)
그리고 이것을 2가지로 나눠서 외부클래스(A), 내부클래스(B)로 나눌수 있다.
//기존클래스
class A{
int i = 100;
B b = new B();
}
class B{
void method(){
A a = new A();
System.out.println(a.i);
}
}
class C{
B b = new B(); //실행가능
b.method(); //100출력
}
상단의 코드는 클래스를 호출하여 값을 사용하는 코드이다.
이것을 중첩클래스로 바꾼다면 하단과 같은 코드로 변경할수 있다.
//중첩클래스
class A{ //B의 외부클래스
int i = 100;
B b = new B();
class B{ //A의 내부클래스
void method(){
int a = 200;
System.out.println(i); //객체 생성 없이 즉시 외부 클래스 맴버 접근가능
}
}
}
class C{
//B b = new B(); //실행불가
//외부클래스의 멤버에 접근하는 방식
//바깥클래스.this.필드OR메서드
System.out.println(test.this.a);
}
class B가 A의 내부로 들어간것을 볼수 있는데 이러한 형태가 중첩클래스의 기본형태이다
그렇다면 굳이 중첩클래스를 사용하는 이유는 무엇일까?
(외부클래스에 종속되어 사용되기 때문에 외부에서 접근이 불가능한 private 접근제어자를 사용한다.)
중첩클래스를 사용하는 이유
1. 외부 클래스의 메서드가 너무 많아 기능별로 분류하기 위하여(개발자가 보고 나중에 유지보수나 활용하기 좋으려고)
2. 선언하려는 클래스가 외부 클래스에 종속적일경우 사용 (얘만 가지고 있는 특수 기믹 또는 특수한 기능 등 -> 마린의 스팀팩, 탱크의 시즈모드 등)
3.특정 클래스를 자신의 클래스 내부적인 용도로만 사용하고자 할때 효율적 -> 만일 다른 클래스에서 내부클래스에 접근하려면 외부클래스를 통하여 접근할수 있다.
중첩클래스의 장점
1. 내부 클래스(B)에서 외부 클래스(A)의 맴버들을 쉽게 접근할수 있다 (객체 생성 없이 사용가능)
2. 코드복잡성(캡슐화) 가능 ->사용하는 이유 1번과 연관
3. 클래스가 타 클래스에 종속적이라면 캡슐화된 클래스들 간의 맴버를 접근할땐 getter와 setter를 사용하여야 하는데 중첩클래스를 사용하는경우 getter와setter를 사용없이 서로의 맴버에 접근이 가능하다.
중첩클래스의 종류
중첩클래스는 static으로 선언되지 않은 내부클래스, static으로 선언된 정적클래스로 나뉜다.
내부클래스 - 인스턴스, 지역클래스,익명클래스
인스턴스 클래스(instance class -> IC) - 외부클래스의맴버변수, 메서드처럼 클래스 정의
스태틱 클래스(static class -> SC) -static키워드를 이용해 클래스가 정의
지역 클래스 (local class -> LC) - 외부클래스의 특정메서드 안에서클래스가 정의
익명 클래스 (anonymous calss -> AC) - 익명클래스를 이용해 클래스 정의
//내부클래스 종류
class test{
//인스턴스클래스
class A{
int inv = 100;
// static int cv = 100; //인스턴스는 static변수를 선언할수 없다.
final static int CONST = 100; //final static은 상수이므로 허용된다.
}
//정적클래스
static class B{
int iv = 200;
static int cv = 200; //static클래스만 static맴버를 정의할수 있다.
}
//로컬클래스
void method(){
class C{
int iv = 300;
// static int cv = 300; //static 클래스만 static맴버 정의가능
final static int CONST = 300; //fianl static은 상수이므로 허용!
}
}
//메인함수
public static void main(String args[]){
System.out.println(A.CONST);
System.out.println(B.cv);
System.out.println(C.CONST); //지역클래스는 메서드 내에서만 허용
}
}
static은 객체생성 없이 사용이 가능해야 하는데 정적클래스를 제외한 나머지는 모두 객체생성을 필요로 하기 때문이다 ,
단, fianl은 상수이므로 사용이 가능하다.
종류
사용불가
선언위치
비고
인스턴스클래스
static
외부클래스와 맴버변수 사이에 선언
외부클래스의 인스턴스 맴버와 관련된 작업
스태틱 클래스 (정적 클래스)
-
외부클래스와 맴버변수 사이에 선언
staic은 메서드,변수,클래스 모두 상위에 존재하는 클래스의 인스턴스를 필요로 하지 않는다.
로컬 클래스 (지역 클래스)
static, 접근 제한자
외부 클래스의 메서드 내부, 초기화 블럭 내부
메모리 실행이 끝나도 힙메모리에 계속 존재, 파라미터는 로컬클래스에 복사해서 사용
익명 클래스
-
외부 클래스의 메서드 내부에 선언
상속 또는 생성과 동시에 재정의하여 사용 -> 주로 1회용 목적으로 사용
익명클래스
// 인터페이스
interface IAnimal {
public String bark(); // 추상 메소드
public String run();
}
public class Main {
public static void main(String[] args) {
// 인터페이스 익명 구현 객체 생성
IAnimal dog = new IAnimal() {
@Override
public String bark() {
return "개가 짖습니다";
}
@Override
public String run() {
return "개가 달립니다";
}
}; //뒤에 세미콜론을 붙여줘야 한다
// 인터페이스 구현 객체 사용
dog.bark();
dog.run();
}
}