1. JRE, JDK의 차이점
    • JRE - Runtime관련
    • JDK - 개발 + JRE
  2. == 과 equals의 차이점
    • 오버라이딩한 객체를 비교할때 ==과 equals는 같은 동작을 진행한다
    • 하지만 str1과 str2가 각각 있다고 가정했을떄 equals는 true지만 == 는 false가 나온다
    • equals → 객체 내용비교
    • == → 같은 객체 또는 같은 참조(주소)를 비교 (기본 데이터는 값을, 객체는 참조위치(메모리주소)를 비교)
  3. 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.
    
  4. 자바의 final의 기능
    • 변수,메서드, 클래스 등에 변형이 안되도록 수정을 막아버리는것
    • 변수 - 값 변경, 중간할당 불가 (초기화 값이 그대로감)
    • 메서드 - 오버라이드 불가
    • 클래스 - 상속불가
  5. 자바의 Math.Round(-1,5)의 결과는?
    • 반올림을 진행하는데 -1번쨰 자리(소수점 첫번째에서 반올림)
    • 5 초과는 올림, 5 이하는 내림
  6. String은 기본타입?
    • String는 int, char 같은 기본 데이터 타입이 아닌 참조 데이터 타입이다
    • Stack이 아닌 Heap부분에서 문자열 데이터가 생성된다 →
    • String은 불변이라 String a = “A”: a+”B”일때 a에 글자를 더하는게 아닌 AB를 새로 만들어 주소를 다시 참조하는 형태이다
    • “AB”가 아닌 “A”는 gabage가 되며 GC가 이를 수집하고 삭제한다
    • JAVA에선 성능 최적화를 위해 String Pool 이란걸 사용하는데 동일한 내용의 String 객체는 메모리 낭비를 막기위해 재사용된다.
  7. 자바의 문자열을 조작하는 클래스
    • 앞서 말한 Stirng의 불변성으로 인해 문자열을 조작할경우 성능적인 낭비가 발생할수있으며 StringBuffer, StringBuilder를 사용하여 해당 문제를 해결할수 있다.
    • StirngBuffer - 멀티쓰레드 환경에서도 안전하다 → 동기화(synchronized)가 자동으로 적용, 문자열을 자주 변경할경우 성능저하를 피할수있다.
    • StringBuilder - StringBuffer에서 동기화만 빠진것 → 단일 스레드 환경에선 더욱 빠른 성능을 제공한다
  8. 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”)를 할경우 서로 다른 주소를 가리킨다.
  9. 문자열을 반복시키는 가장 좋은 방법
    • 리스트를 사용해 반복문으로 변경시키는 방법
      1. 직접 리스트를 만들어 반복문으로 변경하는 작업
      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)
      1. StringBuilder
      String a = "asdf";
      StringBuilder sb = new StringBuilder();
      sb.append(a).reverse();
      
  10. String 클래스의 일반적인 메서드
    • endWith : 문자열 마지막에 지정한 문자가 있는지 판단후 있으면 true, 없으면 false 반환
    • equlas : Object의 메서드이나 오버라이드된것으로 String의 값만 비교해 True, false 를 반환한다
    • indexOf : 지정한 문자가 문자열 몇번째에 있는지 반환
    • length : 문자열 길이 반환
    • replace : 문자열에 지정한 문자 “asd”가 있으면 “hello”로 바꿔서 출력
    • subString : 문자열에 지정한 범위에 속하는 문자열을 반환 (시작범위 값은 포함, 끝나는 범위 값은 미포함)
  11. 추상클래스의 추상 메서드는 필수인가?
    • 추상메서드는 필수가 아니며 추상클래스는 추상메서드를 갖지않아도 무방하다.
    • 추상클래스 - abstract를 사용해 선언된것, 인스턴스화 불가
    • 추상메서드 - 구현부가 없는 메서드로 추상클래스는 추상메서드를 0개 가질수 있다.
    • 구체클래스 - 추상클래스를 할당받은 클래스로 추상메서드를 무조건 구현해야한다
  12. 보통 클래스와 추상클래스의 차이
    • 실제 내용이 안들어가있어도 문제가 없다.
    • 클래스의 상속에선 부모 메서드를 오버라이딩 하는것이 필수가 아니며 자식에게 특정 메서드를 구현하라고 강요할수 없다.
    • 추상클래스는 추상 메서드를 자식 클래스가 구현하는게 강제이기에 해당 클래스를 상속받는 자식클래스는 항상 해당 메서드를 구현해야한다.
  13. final은 추상클래스를 수정할때 사용이 가능한가?
    • final이 붙는순간 상속이 불가능한 클래스로 바뀐다 → 추상클래스는 자체로 인스턴스가 불가능 하기에 구체 클래스에게 상속을 해줘야 한다 → 상속불가한데 상속을 해줘야한다? → 컴파일 에러
    • 애초에 final과 abstract는 같이 붙을수 없고 컴파일 단계에러 에러가 나온다
  14. 자바 컨테이터란?
    • 객체들을 저장하기 위한 저장소(컨테이너도 객체!)이다.
    • 배열은 원시타입의 배열을 사용할떄 크기를 미리 선언해놓는데 1번 정할경우 바꿀수 없는 제약이 생긴다
    • 이때 자바 컨테이너가 java.util 라이브러리에 컨테이너 클래스가 있으며 List,Set,Queue,Map이 존재한다.
  15. Collection, Collections의 차이

    • Collection - 데이터의 자료구조를 다루는 Collection 프레임워크(List, Set, Map*)에 필요한 메서드가 선언되있는 인터페이스
      1. add,contains,clear 등 자료구조 관련 메서드가 정의되있따.
      2. Map은 Collection을 구현하지 않았으나 Collection으로 본다. (Key-값의 쌍)
    • Collections - 컬렉션과 관련된 메서드를 제공하는 클래스
      1. fill, copy, sort등과 같은 Arrays에서 제공하는 메서드들은 물론 멀티 쓰레드 프로그래밍에 필요한 동기화 처리가 가능하도록 제작된 Collection을 반환하는 메서드도 제공
  16. List,Set,Map의 차이점
    1. List - 순서가 있는 데이터구조로 중복을 허용, 인덱스를 통해 데이터 접근이 가능 (ArrayList, LinkedList)
    2. Set - 순서가 없고 중복값 미허용(중복값은 자동으로 제거), 순서가 없기때문에 iterator(순차접근)를 사용하여 검색속도가 빠르다 (HashSet, TreeSet)
    3. Map - key,value의 형태로 key는 미중복, value는 중복허용, iterator를 사용하여 접근은 ㄱ가능하나 KeySet(), entrySet()을 사용해 순회해야한다 (HashMap,TreeMap)
  17. HashMap과 Hashtable의 차이
    • HashMap
      1. 동기화가 보장되지 않아 멀티스레드 환경에서 불안전하나 단일 스레드에선 더 나은 성능을 제공
      2. 검색에 뛰어난 성능을 갖고 Key,value로 null을 허용한다
    • HashTable
      1. 동기화가 보장되어 멀티스레드 환경에서 사용이 가능
      2. HashMap보다 처리속도가 느리다.
      3. Key,Value로 Null이 허용되지 않는다.
  18. 어떤 상황에서 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번이면 찾는게 아닌가??
      1. HashMap은 key를 입력받았을때 해시값으로 계산한뒤 이를 이용해 한번에 위치를 특정할수 있다.
      2. TreeMap은 이진탐색으로 0~50, 51~100(루트노드) → 0~25, 26~50 → … 이런 형식으로 나무 뿌리처럼 타고 들어가기에 더 느리다.
      3. 만약 해시 충돌이 나타날경우 hash값에 여러개의 key가 매핑될수도 있는데 해시값이 존재하는 버킷에서 체이닝, 오픈 어드레싱 방식으로 충돌을 처리한다. (키가 30개인데 테이블(버킷) 크기가 10개면 3개씩 중복이 나타나는데 이런경우 해시충돌이 발생한다)
      4. 버킷 - 해시 테이블에서 데이터를 저장하는 공간 (key의 해시값 보관소)
      5. 체이닝 - 하나의 버킷에 여러개의 요소를 저장하는 방법으로 해당 버킷에 있는 데이터를 리스트 형태로 저장하며 삽입된 데이터는 버킷의 리스트 끝에 추가되고 해당 해시값에 매핑된 리스트를 순회하며 원하는값을 탐색
      6. 버킷 0: (key1 -> value1) -> (key3 -> value3) 해당 상황에서 key1과 key3은 동일한 해시값을 갖게되어 버킷 0번에 들어가있는 형태이다. 여기서 사용자가 key3을 검색할경우 순차적으로(key1 -> key3의 순서) 탐색하며 키가 일치할경우 해당하는 값을 반환 index 목적으로 사용되는 key1,key3의 해시값이 123123으로 동일할경우 해시충돌이 일어나며 이때 index키가 같으니 대신 사용할수 있는 후보키로 key의 value를 사용하여 한번 더 탐색
      7. 오픈 어드레싱 - 충돌이 발생했을떄 빈 버킷을 찾아 데이터를 저장하는형식으로 1개의 버킷에 여러 요소를 저장하는게 아닌 비어있는 버킷을 찾아서 저장한다
  19. HashMap의 구현원칙
    • Key는 저장된 값을 찾기위하여 컬렉션 내부에서 유일해야한다 (PK일것)
    • HashMap은 내부적으로 Entry 또는 Node라는 내부 객체로 묶어서 저장하게되는데 키-값 쌍을 저장하는 형태라 키와 값 서로 연관성을 유지할수 있게된다. HashMap의 1개 배열엔 Entry, Node 객체가 저장되며 그 안에서 key와 value가 같이 저장된다.
    • 즉, 1개의 HashMap은 키와 값이 서로 연관된 값이기에 키, 값을 각자 배열로 사용하여 필요할때마다 가져오기 보단 키,값을 1개의 클래스로 정의해 둘을 1개의 배열로 다루는것이 데이터 무결성측면에서 더 바람직하기 떄문이다
    • HashMap의 hashCode는 Object의 hashCode메서드를 사용하며 각 주소값을 해싱하기에 가장 좋은 방식이기도 하지만 String 클래스와 비슷하게 equals를 재정의 해야한다면 hashCode도 재정의 해야 해싱을 구현한 컬렉션 클래스도 정상적인 동작이 가능하다
      1. HashMap은 HashCode를 사용해 해당 객체가 저장될 객체를 결정하는데 같은 해시코드를 가진 키는 같은 버킷에 저장되며 버킷에서 equals로 동일한 키인지 검사르 진행한다.
      2. HashCode(버킷위치 - 저장주소), equals(객체 동일여부)
      3. 만일 a=b 를 진행할경우 equals()를 재정의 한다면 둘은 동일한 객체가 되는데 HashCode는 해당 객체가 동일한 HashCode를 반환해야한다
      4. 하지만 a는 1번버킷, b는 2번 버킷에 저장되어있기에 다른 HashCode를 가지며 이로인해 키를 제대로 못찾게된다.
      a = (1,test)
      b = (2,hello) 
      a=b 
      a = (1,test), b= (2,test)
      
  20. HashSet의 구현원칙
    • HashSet을 구현할땐 Set의 특징처럼 중복된 요소가 저장되면 안되며 저장순서를 유지하지 않는다.
    • HashSet의 hashCode는 Object의 hashCode 메서드를 사용하여 각주소값을 해싱해야 정상적으로 동작한다.
    • 오버라이딩을 통해 작성된 hashCode가 만족해야할 3가지 조건
      1. 실행중인 애플리케이션에서 동일한 객체에 대해 여러번 hashCode를 호출할때 동일한 int를 반환해야한다(실행할떄마다 동일한 int를 반환할 필요는없다) → 일관성
      2. equals메서드를 이용한 비교에서 true를 얻은 두 객체는 hashCode()로 해시값을 호출했을떄 항상 같아야한다. →
      3. 서로 다른 객체들에 대해 반드시 다른 해시코드를 반환할 필요는 없지만 가급적 서로 다른 해시코드를 반환하는게 바람직하다. → equals로 false를 반환하는 객체는 같은 hashCode를 가질수 있으나 서로 다른 해시코드를 반환하는게 효율적이다.
        1. equals는 내용이 같은지 비교하는 함수인데 hashcode가 동일할수가 있다 (해시충돌) 3-2 문항 참조
        String str1 = "FB";
        String str2 = "Ea";
                
        System.out.println(str1.hashCode());  // 2236
        System.out.println(str2.hashCode());  // 2236
        System.out.println(str1.equals(str2));  // false
        

 

 

 

'JAVA' 카테고리의 다른 글

Call by value와 Call by reference  (0) 2024.02.24
DocumentBuilderFactory와 예제  (0) 2024.02.18
스레드풀  (0) 2023.03.07
프로세스와 스레드  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26

우리가 함수나 메서드를 호출할때 방식은 크게 2가지의 방법이 있다.

Call by Value, Call by Reference

그렇다면 2가지에 대해서 비교해보자

Call by Value

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 static void main(String[] args) {
		String valueTest1 = new String("abc");
		String valueTest2 = valueTest1;
		
		callme(valueTest1);
		
		System.out.println("#3 " + valueTest1.equals("abc")); // true
		System.out.println("#4 " + valueTest1.equals("ccc")); // false
		System.out.println("#5 " + (valueTest1==valueTest2)); // true
	}
	
	static void callme(String str) {
		System.out.println("#1 " +str.equals("abc")); // true 
		str = new String("ccc");
		System.out.println("#2 " + str.equals("ccc")); // true 
	}

 

안정성과 예측 가능성: "Call by Value"를 사용하면 함수에 전달된 값이나 참조의 복사본을 작업하게 되므로, 원본 데이터가변경될 위험이 줄어든다.

디버깅 용이성: "Call by Value" 방식에서는 함수 호출 시 매개변수의 값이나 참조의 복사본이 생성되기에 이로 인해 함수 내에서 발생하는 변경이 외부에 영향을 미치지 않아, 디버깅 시 변수의 값이 어디에서 변경되었는지 알기가 쉽다.

메모리 관리: Call by Value 방식은 함수 호출이 끝나면 매개변수로 사용된 복사본은 스코프를 벗어나 가비지 컬렉터에 의해 처리되어 메모리 누수의 위험이 줄어든다.

멀티스레드 환경에서의 안정성: Java는 멀티스레드 프로그래밍을 광범위하게 지원하는데 "Call by Value" 방식은 멀티스레딩 환경에서 공유 데이터에 대한 동시 접근으로 인한 RaceCondition을 방지할수 있다.

'JAVA' 카테고리의 다른 글

자바 주니어 질문 100건 (기초)  (0) 2024.11.02
DocumentBuilderFactory와 예제  (0) 2024.02.18
스레드풀  (0) 2023.03.07
프로세스와 스레드  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26

개인 공부용으로 뉴스 페이지를 가져오기 위해 찾아보던중 DocumentBuilderFactory에 대해 공부할 기회가 생겨 정리할겸 포스팅한다.

 

초기 조건

RSS 뉴스 가져오기 -> JDBC의 rss 뉴스를 가져왔으며 링크는 다음과 같다.

https://news.jtbc.co.kr/Etc/RssService.aspx

 

데이터 확인하기 -> 해당 부분에서 경제파트를 가져오기 위하여를 확인한 결과 다음과 같은 xml이 출력되었다.

https://fs.jtbc.co.kr/RSS/economy.xml

 


 

여기서 우리가 필요한건 title, link, description, pubDate이니 이것을 저장할 VO를 만들었다.

ItemVO.java

public class ItemVO {
	private String title;
    private String link;
    private String description;
    private String pubDate;
	
    
	public ItemVO(String title, String link, String description, String pubDate) {
        this.title = title;
        this.link = link;
        this.description = description;
        this.pubDate = pubDate;
    }
	
	@Override
	public String toString() {
		return "ItemVO [title=" + title + ", link=" + link + ", description=" + description + ", pubDate=" + pubDate
				+ "]";
	}
}

 

 

 

Controller

@RequestMapping("homepage/newsPage")
	public String newsPage(Model model) throws ServletException, IOException {
		System.out.println("aa2");
		
		List<ItemVO> items = new ArrayList<>();
        try {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(new URL("https://fs.jtbc.co.kr/RSS/economy.xml").openStream());

            NodeList nList = doc.getElementsByTagName("item");
            for (int temp = 0; temp < nList.getLength(); temp++) {
                Node nNode = nList.item(temp);
                if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element eElement = (Element) nNode;
                    String title = eElement.getElementsByTagName("title").item(0).getTextContent();
                    String link = eElement.getElementsByTagName("link").item(0).getTextContent();
                    String description = eElement.getElementsByTagName("description").item(0).getTextContent();
                    String pubDate = eElement.getElementsByTagName("pubDate").item(0).getTextContent();
                    items.add(new ItemVO(title, link, description, pubDate));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        model.addAttribute("items", items);
        
		return "homepage/newsPage";
	}

 

이게 기본적인 코드를 뜻하는데 각각 어떤것을 의미하는지 확인해보곘다.

  • DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); //A
  • DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();   //B
  • Document doc = dBuilder.parse(new URL("https://fs.jtbc.co.kr/RSS/economy.xml").openStream());  //C
  • NodeList nList = doc.getElementsByTagName("item"); //D

DocumentBuilderFactory (A)

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를 정의합니다.

즉, A는 XML을 우선 파싱하기 위해 인스턴스를 생성한 코드이다.링크:https://docs.oracle.com/javase/7/docs/api/javax/xml/parsers/DocumentBuilderFactory.html

DocumentBuilder (B)

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입니다

Document (C)

Document doc = dBuilder.parse(new URL("https://fs.jtbc.co.kr/RSS/economy.xml").openStream());

번역

  • Document 인터페이스는 전체 HTML 또는 XML 문서를 나타냅니다.
  • 요소, 텍스트 노드, 주석, 처리 지시문 등은 Document의 맥락 외부에서 존재할 수 없기 때문에, Document 인터페이스는 이러한 객체들을 생성하기 위한 팩토리 메소드들도 포함하고 있습니다.

 

new URL("https://fs.jtbc.co.kr/RSS/economy.xml") -  URL 객체를 생성

.openStream() - URL의 내용을 읽기 위한 연결

dBuilder.parse(...): DocumentBuilder 객체의 parse 메소드를 사용해, 읽어온 XML 데이터를 파싱 -> 이 과정에서 XML 문서의 구조를 분석하고, 해당 구조에 따라 DOM 객체 트리를 생성

Document doc = ...: 파싱의 결과로 생성된 DOM 객체 트리의 루트인 Document 객체를 doc 변수에 할당

 

즉,1. 사용하려는 요소(HTML, XML 요소들)를 담기위한 Document를 선언하였고2. URL을 하드코딩하여 값을 가져왔고 3. 파싱한뒤 doc에 할당하였다.링크 : https://docs.oracle.com/javase/8/docs/api/org/w3c/dom/Document.html

 

 

NodeList (D)

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를 순회하면서 필요한 정보(예: 뉴스 제목, 링크, 설명 등)를 추출할 수 있습니다.

 

 

링크 : https://docs.oracle.com/javase/8/docs/api/org/w3c/dom/NodeList.html


요약

A - DocumentBuilderFactory.newInstance();

  • XML 입력을 DOM 객체 트리로 생성할 수 있는 파서의 인스턴스를 얻기 위한 설정.

B - DocumentBuilder();

  • XML 데이터를 읽고 이를 해석하여 DOM Document 인스턴스로 변환하는 역할

C - Document

  • Document 인터페이스는 전체 HTML 또는 XML 문서를 의미

D - NodeList

  • Document에서 얻을 값을 NodeList 형태로 저장

 

'JAVA' 카테고리의 다른 글

자바 주니어 질문 100건 (기초)  (0) 2024.11.02
Call by value와 Call by reference  (0) 2024.02.24
스레드풀  (0) 2023.03.07
프로세스와 스레드  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26

스레드는 스레드 스케쥴링(우선순위 할당)이 이뤄지기전 스레드는 실행대기 상태로 변경되며 이상태에서 스레드 스케쥴링이 이뤄진후 선택된 스레드가 CPU를 점유 및 run메서드를 실행한다.

(일시정지 상태에서 실행으로 넘어가기위해서는 실행대기 상대를 무조건 거쳐야한다)

 

스레드의 상태는 다음과 같이 분류할수 있다.

//스레드의 흐름도는 하단의 사진 참조
//생성
NEW(생성완료) - 스레드 객체는 생성되었으나 start() 메서드가 미호출

//실행대기
RUNNABLE(실행 대기중) - 실행상태로 언제든 갈수있는상태

//일시대기
WAITING(후순위로 대기중) - 다른스레드가 통지할때까지 대기
TIMED_WAITING(실행중 대기상태) -일정시간동안 대기
BLOCKED(해제까지 대기) - 락이 푸릴떄까지 대기

//종료
TERMINATED(종료) - 실행끝

//사용예시
Thread.State.NEW -> 객체생성상태일때 실행 대기상태로 변경
Thread.State.TERMINATED -> 종료상태
Thread.getState(); -> 현재 스레드 상태얻기

 

스레드 상태제어

상단에선 스레드의 생성, 실행,종료 3단계를 확인하였으니 이번엔 스레드의 상태제어를 어떤식으로하는지 확인해보겠다. (스레드 상태제어 -> 실행중인 스레드의 상태를 변경하는것)

동영상 플레이어 실행 중 영상의 일시정지, 음소거, 자막삽입등 우리가 사용하는 프로그램에선 여러가지 기능(스레드)을 활용하는데 이것을 조정하는게 스레드 상태제어라고 이야기한다.

상태제어의  흐름도와 대표적으로 사용되는 메서드는 다음과 같다

스레드 흐름도 (밑줄친건 가급적 사용하지 말라)

상태제어 메서드

메소드 설명
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()메서드이다.

(게임에서 A스테이지가 끝나야 B스테이지로 넘어가는데 버그로 B스테이지부터 시작하는경우 몬스터나 오브젝트가 정상적으로 안되는경우가 있지않던가?) 

Thread.start();	//스레드시작
Thread.join();	//일시정지

 

wait(), notify(), notifyAll() - 스레드간협업

wait(); - 일시정지

notify() - 일시정지에서 실행대기로 변경

notifyAll() - wait에 의해 일시정지된 모든 스레드를 전부 실행대기로 만든다

두개 이상의 스레드를 교대로 번갈아가며 실행할경우 사용하며 호출한 스레드는 실행대기상태로 된다.

(캐릭터 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를 한번 더 실행한경우 ("이미 실행중인데 또 실행하라고?")

threadA.setDeamon(true);	//threadA를 데몬(보조)스레드로 변경
threadA.isDaemon();		//threaA가 데몬스레드인지 확인용 (데몬일경우 true 리턴)

스레드 그룹

관련된 스레드를 묶어서 한번에 관리할목적으로 이용된다.

또한 스레드 그룹에서 제공하는 여러 메서드가 있는데 한가지 예시로 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
);

 

corePoolSize -기본풀 사이즈로 최소 실행할 쓰레드의 숫자(코어스레드)

maximumPoolSize - 최대 지원 스레드 숫자(최대스레드)

keepAliveTime - 스레드 미사용시 제거대기시간

TimeUnit unit - keepAliveTime값의 시간단위 

BlockingQueue <Runnable> workQueue - 요청된 작업들이 저장될 큐 (corePoolSize보다 작업스레드가 많을때 남는쓰레드도 없는경우 workQueue에서 대기 -> 마린 3개를 한번에 찍으면 2개는 생산대기중이 되는것과 같은개념이다.)

RejectedExecutionHandler handler - 작업요청 거부시 처리할 핸들러

 

코어 스레드 수 - 스레드 수가 증가된후 미사용 스레드를 스레드풀에서 제거할때 최소한 유지해야할 스레드수 

초기 스레드 수 - ExecutorService객체가 생성될때 기본적으로 생성되는 스레드의 숫자 (디폴트는 0)

최대 스레드 수 - 스레드풀에서 관리하는 최대 스레드 수(최대로 가용할수 있는 스레드수)

 

newCachedThreadPool 로 생성된 경우 스레드 개수보다 작업 개수가 많으면 새 스레드를 생성하며 스레드가 추가되었을때 1분만 작업이 없으면 추가된 스레드를 종료 및 풀에서 제거한다. 

 

newFixedThreadPool(int nThreads) 로 생성된경우 nThreads의 값만큼 작업을 처리하지 않고 놀고있어도 스레드 개수는 줄지않는다. (nThreads가 5인경우 Idle상태더라도 최소5개의 스레드는 들고간다)

 

스레드풀의 종료

스레드풀의 스레드는 데몬스레드(보조스레드)가 아니기에 main스레드가 종료되도 작업 마무리를 위해 잔존한다 ->

어플리케이션의 완전한 종료를 위해선 스레드풀도 종료시켜 하위스레드도 전부 종료시켜야한다.

(리턴 타입) 메서드명 (매개변수) 설명
(void형) shutdown() 현재 처리중인 작업, 작업 큐에 대기중인 작업을 처리 후 종료
-> 일반적인 종료
(List<Runnable>형) shotdownNow() 현재 작업처리중인 스레드를 interrupt하여 작업중지 시도, 스레드풀 종료 (리턴값은 작업큐에 있는 미처리된 작업목록)
 -> 강제종료
(bool형) awaitTermination(long timeout,TimeUnit unit) shutdown() 메서드 호출 이후 모든 작업처리시간을 timout 시간 내에 완료하면 true, 그렇지 않으면 false를 리턴

(timeout - 120, TimeUnit - 초, 분, 시 EX: awaitTermination(50,MINUTES) -> 50분까지 끝내면 true, 그렇지 않으면 false)

ExecutorService.shoutdown(); //일반적인 종료
ExecutorService.shoutdownNow(); //강제 종료
executor.awaitTermination(30, TimeUnit.SECONDS); //30초안에 못끝내면 false;

스레드풀 작업생성

작업생성은 하나의 작업은 Runnable 또는 Callable 구현클래스로 표현한다

Runnable 예시코드 - 리턴값 없음 (void)

Runnable task = newRunnable() {
	@Override
    public void run() {
		//작업할 내용
	}
}

Callable 예시코드 - 리턴값 있음

- <T>는 제네릭의 표시로 예비 데이터 타입을 뜻한다

- 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법

Callable<T> task = new Callable<T>() {
	@Override
    public T call() throws Exception{
    	//스레드가 처리할 내용
        return T;
    }
}

스레드풀 작업처리요청

ExecutService의 작업 큐에 Runnable 또는 Callable 객체를 넣는행위를 뜻한다. (유닛생산건물 대기열에 유닛생산을 추가버튼을 누르는것 생각하면 된다)

(리턴 타입) 메서드명 (매개변수) 설명
(void형) execute(Runnable command) - Runnable을 작업큐에 저장
- 작업 처리결과를 받지 못한다 (void형)
(Future<?>형) submit(Runnable task)
(Future<V>형) submit(Runnable task, V result)
(Future<V>형) submit(Callable<V> task)

- Runnable 또는 Callable을 작업큐에 저장
- 리턴된 Future를 통하여 작업처리결과를 얻을수 있다.

execute() 메서드는 작업처리도중 예외가 발생하면 스레드가 종료되고 해당 스레드는 스레드 풀에서 제거하며 새로운 스레드를 생성한다.(처리결과를 받지 못함과 동시에 삭제)

submit() 메서드는 작업처리도중 예외가 발생하여도 스레드는 종료되지않고 계속 재사용된다.

여기서 submit의 리턴타입인 Future는 블로킹 방식으로 작동하며 최종결과를 얻는데 사용된다.

(작업요청시 요청한 순서대로 작업처리가 완료되는것은 아니다 -> 코드 100개있는 스레드랑 코드10줄있는 스레드랑 속도가 다르다 -> 작업처리 완료된것만 확인하는건 CompletionService이용)

 

블로킹 방식의 작업완료 통보

블로킹이란 작업결과가 아닌 작업이 완료될떄까지 기다렸다가 최종결과를 얻는데 사용된다 (즉, 중간 산출물정도라 이해하면 되며 이러한 블로킹 기능 때문에 Future객체를 지연완료 객체라고 칭한다.)

리턴타입 메서드명 (매개변수) 설명  
Future<?> submit(Runnable task)  - Runnable 또는 Callable를 작업 큐에 저장
- 리턴된 Future를 통해 작업 처리 결과를 얻는다.
Future<V> submit(Runnable task,Integer result)
Future<V> submit(Callable<String> task)

이것을 좀더 자세히 살펴보겠다

리턴타입인 future를 통하여 작업 처리결과를 얻는다고 하는데 submit을 리턴부분(future)과 메서드 부분을 나눠 설명하겠다.

 

첫번째로 submit의 리턴부분인 future가 갖고있는 get() 메서드를 설명한게 하단의 표이며 V는 (상단의) submit(Runnable task, V result)의 V형 또는  submit(Callable<V> task)의 V형이다

리턴타입 메서드명(매개변수) 설명
V타입 get() 블로킹 되어있다 처리결과V를 리턴
V타입 get(long timeout, TimeUint unit) timeout 안에 작업 완료시 V를 리턴
timeout 초과시 TimeoutException 발생

 

두번째로 submit메서드를 좀더 상세히 설명하자면 다음과 같다

작업처리도중 예외는 동일하며 리턴타입이 다른형식으로 나타나는걸 확인할수 있다.

메서드 리턴타입 작업 처리도중 예외발생
submit(Runnable task) -> 리턴값이 없는 작업완료통보 future.get() -> null future.get() -> 예외발생
submit(Runnable task, Integer result) future.get() -> int 타입 값
submit(Callable<String> task)
future.get() -> String 타입 값

future를 이용한 블로킹 방식은 작업을 처리하는 스레드가 작업을 완료하기 전까진 get메서드는 블로킹 된다.

작업결과를 도출하는걸 봤으니 결과를 확인하는 방법을 알아보자

 

 

여기서 Runnable은 리턴값 없으며 

future객체는 작업결과를 얻기 위한 get 메서드 이외에도 다음과 같은 메서드를 제공한다

 

리턴타입 메서드명(매개변수) 설명
bool cancel(boolean mayInterruptlfRunning) 작업처리가 진행중일경우 취소시킴
bool isCancelled() 작업취소가 되었는지 여부
bool isDone() 작업처리가 완료되었는지 여부

 

cancel()

작업이 시작되기 전 - mayInterruptIfRunning와 무관하게 작업취소후 true리턴 (시작도 안했으니 true(취소작업가능))

작업진행중 - mayInterruptIfRunning가 ture일경우만 작업스레드 interrupt (인터럽트가 떠야만 멈춤가능)

작업완료, 모종의 이유로 취소불가시 - false 리턴 (이미 끝낸걸, 내권한 밖인걸 취소하라고? false(취소작업 불가))

 

iscancelled()

작업이 완료되기전에 취소가 되었을경우만 true (현재 작업중인것만 취소가능)

 

isdone()

정상완료 뿐만아니라 예외, 취소등 작업이 완료되기만하면 true(완료는 했는데 정상완료인지 모른다..)

 

리턴값이 없는 작업완료 통보 (Runnable)

 

Runnable객체로 생성시 리턴값이 없으며 하단은 양식이다.

 

Runnable task = new Runnable() {
    @Override
    public void run() {	//리턴값 없기에 void
  	  //실행내용
    }
}

리턴값이 없는 작업처리요청은 submit(Runnable task)메서드를 활용한다 (상단의 표에 표시해놓았다).

여기서 리턴값이 없는데 future를 리턴하는 이유가 무엇이냐고 물을수 있는데 이것은 작업처리도중 예외가 발생했는지 확인하기 위함이다.

//void지만 작업처리 도중 예외가 발생했는지 여부를 확인하기 위해 future 객체를 받는다
Future future = ExecutorService.submit(task);

 

리턴값이 있는 작업완료 통보 (Callable)

스레드풀의 스레드가 작업을 완료한후 애플리케이션이 처리결과를 얻어야한다면 작업객체를 Callable로 생성한다.

Callable<T> task = new Callable<T>() {
    @Override
    public T Call() throws Exception {	//T타입 , 예외발생여부
  	  //실행내용
      return T;
    }
}

작업처리요청은 submit()메서드를 실행하고나면 T타입의 값을 리턴하면 Future<T>의 get 메서드는 블로킹 해제,T타입 값 리턴한다.

//submit메서드는 작업큐에 Callable객체를 저장, Future<T>를 리턴한다.
//여기서 T는 call메서드가 리턴하는 타입
Future<T> future = executorService.submit(task);

작업처리결과를 외부 객체에 저장

작업이 완료된것을 외부 객체에 저장해야 하는경우도 종종 있다.

스레드가 작업처리를 완료하고 외부 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 리턴
Future<V> take() 완료된 작업의 Future를 가져옴
완료된 작업이 없을경우 있을때까지 블로킹
Future<V> submit(Callable<V> task) 스레드풀에 Callable작업 처리요청 (리턴 있음)
Future<V> submit(Runnable task, V result) 스레드풀에 Runnable작업 처리요청 (리턴 있음)

CompletionService의 구현클래스는 ExecutorCompletionService<V>이다.

//CompletionService 양식
CompletionService<V> completionService = new ExecutorCompletionService<V>{
	executorService
}

콜백(callback)방식의 작업 완료통보

콜백 - 애플리케이션 스레드에게 작업처리를 요청한후 스레드가 작업을 완료하면 특정 메서드를 자동실행하는 기법

블로킹 방식과 콜백방식 비교

블로킹은 앞서 "작업이 완료될떄까지 기다렸다가 최종결과를 얻는데 사용된다"라고 설명하였다

콜백은 블로킹과 다르게 작업처리 요청후 다른기능을 수행할수 있다. -> 작업처리가 완료되면 콜백메서드가 자동적으로 실행되어 결과를 알수 있기 때문이다. 

콜백메서드는 CompletionHandler를 사용하며 이것은 completed()와 failed() 메서드가 있다.

completed는 정상적으로 완료될경우 사용하며 failed는 비정상적으로 작동시 사용된다 

하단의 코드를 참조하길 바란다.

//CompletionHandler<V,A>에서 V는 결과값의 타입, A는 첨부값의 타입이다.
//첨부값은 콜백 메서드에 결과값 이외에 추가적으로 전달하는 객체라고 이해하면 된다.
CompletionHandler<V,A> callback = new CompletionHandler<V,A>() {
    //정상 완료시 completed
    @Override
	public void completed(V result, A attachment){
    }   
    //작동중 문제발생 failed
    @Override
    public void failed(V exc, A attachment){
	}
};

//사용예시
private CompletionHandler<Integer,void> callback = new CompletionHandler<Integer, void>() {
//정상 완료시 completed (A가 필요없을경우)
    @Override
	public void completed(V result, void attachment){
    }
    
    try {
    	callback.completed(result,null);	//정상처리 했을겨우 호출
    } catch (NumberFormatExeception e) {
    	callback.failed(e.null);	//예외 발생시 호출
    }

'JAVA' 카테고리의 다른 글

Call by value와 Call by reference  (0) 2024.02.24
DocumentBuilderFactory와 예제  (0) 2024.02.18
프로세스와 스레드  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26
예외에 관하여  (0) 2023.02.24

프로세스(process) - OS에서 실행중인 하나의 애플리케이션을 프로세스라 칭한다

스레드(thread) - 1가지 작업을 실행하기 위하여 순차적으로 실행할 코드를 실처럼 이어놓았다는 의미로 1개의 스레드는 1개의 코드 실행흐름이기에 1개의 프로세스 내에서 스레드가 2개면 2개의 코드실행 흐름이 생긴다는 의미이다.

 

21개의 프로세스..

1개의 프로세스가 2개의 스레드를 실행하는것을 멀티스레드라 칭하는데 이러한 작업작업을 위해선 OS가 CPU 및 메모리 자원을 프로세스마다 할당해주는 작업을 한다.

 

위의 사진과 연관지어 설명하자면

1. 1개의 프로세스는 여러개의 프로세스를 가질수 있다 (사진속 크롬은 21개의 프로세스를 갖고있으며 새로운창 1개만 추가해도 프로세스가 여러개 추가되는걸 볼수있다.) 

2. 각 프로세스마다 싱글스레드 및 멀티스레드가 실행될수 있다는 의미이다.

 

추가로 이전에 예외와 에러에 대한 포스팅을 진행했는데 부연설명을 하면 예외는 문제생겨도 실행, 에러는 발생시 일단 종료 하는걸 하였다.이것은 웹페이지가 응답없음이 발생했을때 에러로 크롬의 전체 프로세스가 꺼지는것으로 확인이 가능하다.


스레드의 우선순위

멀티스레드는 동시성 또는 병렬성으로 실행되는데스레드의 갯수가 코어의 숫자보다 많을경우 스레드는 어떤순서에 의하여 동시성으로 실행할지를 결정하는데 이것을 스레드스케쥴링이라 칭한다.

 

스레드 스케쥴링 - 스레드 개수가 코어수보다 많을때 스레드를 어떤순서로 동시성으로 실행할지 결정하는것

동시성 - 멀티작업이 1개의 코어에서 멀티스레드가 번갈아가며 실행하는 성질

병렬성- 멀티작업을 위해 멀티코어에서 개별 스레드를 동시에 실행하는 성질

 

스레드 스케쥴링은 우선순위방식, 순환할당방식 2가지로 나뉜다.

우선순위방식(Priority) -> 중요도가 높은스레드가 더 많은 스레드를 실행하는것으로 스레드는 1~10의 우선순위를 가지며 디폴트로 5의 우선순위를 할당받는데 우선순위를 변경하려면 하단의 양식처럼 사용하면 된다. 

//thread.setPriority(우선순위);
thread.setPriority(Thread.MAX_PRIORItY);	//중요도 10
thread.setPriority(Thread.NORM_PRIORItY);	//중요도 5
thread.setPriority(Thread.MIN_PRIORItY);	//중요도 1
thread.setPriority(4);				//중요도 4
thread.setPriority(1);				//중요도 1

 

순환할당방식(Round-Robin) -> 일정시간(Time Slice)마다 번갈아가며 실행하는것이며 해당방식은 JVM에 의하여 정해지기에 코드로 제어는 불가능하다

(JVM - 자바가상머신)


메인 스레드

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는 객체를 동기화 시키는 기능으로 멀티스레드의 공유객체간 참조를 막아 비정상적인 출력을 막는것이 목적이다.

'JAVA' 카테고리의 다른 글

DocumentBuilderFactory와 예제  (0) 2024.02.18
스레드풀  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26
예외에 관하여  (0) 2023.02.24
중첩클래스  (0) 2023.02.21

이전에 포스팅을 진행하며 동일성과 동등성에 관하여 언급한 기억이 있는데 다시 상기시킬겸 좀더 상세히 작성하려고 포스팅을 진행한다.

 


동일성과 동등성의 사전적의미는 다음과 같다.

동일성 (identity) - 객체가 완전히 같은경우를 의미한다

동등성 (equality) - 값이 같은경우를 의미한다.

 

한가지 예시를 들어보겠다.

위의 사진처럼 똑같은 모델, 똑같은 스펙의 핸드폰이 있다. (좌측을 A, 우측을 B라 가정)

그렇다면 이것을 동일하다고 설명할 수 있을까?

A와 B는 스펙은 똑같을지언정 제품마다 별도의 제조번호가 부여되었고 이는 2개의 핸드폰이 엄연히 다르다는것을 보여주는것으로 즉, A와 B는 동등(equality)하지만 동일(identity)하지는 않다고 말하는것이다.

이러한 동등성과 동일성을 자바에서는 hashcode 메서드와 equal 메서드로 확인할수 있다. 

 


동등성 - equal() 

public boolean equals (Object anObject){
    if (this == anObject){
    	return true;
    }
    if (anObject intstanceof String)
        String aString = (String)anObject;
        if(coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value,aString.value)
            			:StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}

상단의 코드는 Object의 내장함수중 equals의 로직을 보여주는 코드로 객체의 값이 일치하는 여부(boolean)를 반환하는 메서드이다 ("=="을 의미한다)

메모리 주소가 아닌 객체 값만을 비교하는 메서드이다.

3항 연산자를 사용해 StingLatin1과 StringUTF16를 비교하여 반환하기로 하는 메서드이다.

 

StringUTF16 = JVM이 문자, 문자열을 메모리에 저장할때 UTF16을 사용하여 인코딩 하는데 이를 같은지를 확인하기 위해 StringUTF16.equal()메서드를 사용하였다.

 

isLatin1

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

 

coder() =

 

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는 서로 알맞는 타입을 찾을때까지 컴파일시 모든 타입을 돌며 검사하는데 이러한 단점때문에 가급적 다형성을 활용한 타입점검이 필요하다

'JAVA' 카테고리의 다른 글

스레드풀  (0) 2023.03.07
프로세스와 스레드  (0) 2023.03.07
예외에 관하여  (0) 2023.02.24
중첩클래스  (0) 2023.02.21
생성자(Constructor)와 this  (0) 2023.02.06

프로그램을 실행할때 사진과 같은 에러가 나오는 경우가 종종 있을것이다.

예전엔 모르고 넘어가도 상관없지만 개발을 하는 입장이 되었으니 이것이 왜 에러가 나타나는지 알아야한다!

오늘은 이러한 에러 및 예외에 관하여 설명하겠다.


에러와 예외

먼저 에러와 예외의 정의를 확인하고 가자.

예외 (Exception) - 코드로 수습할수 있는 미약한 오류 (실행은 됩니다!)

오류(Error) - 코드로 수습 불가능한 심각한 오류로 수습이 안되니 일단 종료시키는것이다. (실행도 안됩니다!, 가장 자주 나타는 오류는 OM으로 사용되는 메모리를 초과할때 나타나는 오류인데 게임 2개를 동시에 켜보면 알것이다..)

여기서 OM이란 out of memory의 약자로 만일 GC(Garbage Collection)가 제대로 실행되지 않는다면 메모리 누수가 점점 쌓여서 OM이 발생될것이다.

 

하단의 사진은 에러와 예외의 관한 계층도이다.

여기서 에러의 종류를 나타내면 다음과 같다.(더있을수도 있다..)

분류 설명
컴파일 에러 (Compile Error) 컴파일 작업시 발생하는 에러(문법적인 오류)
런타임 에러 (Run-time Error) 실행할때 발생하는 에러(무한루프, Nullpoint 등 설계미숙)
논리적 에러 (Logic Error) 사용자가 의도한 작업을 실행하지 못한에러
링킹 에러 (Linking Error) 컴파일 후 이를 연결할때 발생 (헤더파일등이 없는경우 발생)
파스 에러 (Parse Error) 인터프리터 언어에서 나타나는 문법오류

컴파일 : 내가 작성한 "hello world"(고급언어)를 add  esp,12(어셈블리어) -> 010100010101(기계어)로 바꾸는것

인터프리터 : 고급언어로 작성된 코드를 한줄씩 읽어 내려가며 실행하는 프로그램(파이썬, MatLAB)

 

상단에서 오류는 수습이 불가능하지만 예외는 수습이 가능하다고 설명을 해놨는데 이것을 예외처리 한다는 표현을 사용한다. (프로그램 실행시 발생할수 있는 예외의 발생에 대비한 코드를 작성하는것 -> 프로그램의 비정상적인 종료를 막기 위함)

 

에러의 종류

분류 설명
컴파일 에러 (Compile Error) 컴파일 작업시 발생하는 에러(문법적인 오류)
런타임 에러 (Run-time Error) 실행할때 발생하는 에러(무한루프, Nullpoint 등 설계미숙)
논리적 에러 (Logic Error) 사용자가 의도한 작업을 실행하지 못한에러
링킹 에러 (Linking Error) 컴파일 후 이를 연결할때 발생 (헤더파일등이 없는경우 발생)
파스 에러 (Parse Error) 인터프리터 언어에서 나타나는 문법오류

 


 

예외의 종류

 

Exception 클래스 - 사용자 실수에 따른 문제발생 (메모리가 안맞음, 사용자의 파일이 없음 등)

IOException - I/O (Input,Output)오류가 발생하는경우에 throw되는 예외

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{

}

'JAVA' 카테고리의 다른 글

프로세스와 스레드  (0) 2023.03.07
동일성(identity)과 동등성(equality)  (0) 2023.02.26
중첩클래스  (0) 2023.02.21
생성자(Constructor)와 this  (0) 2023.02.06
오버로딩  (0) 2023.02.02

앞에서의 포스팅은 전부 클래스를 단독으로 사용하는것을 예제로 들었는데 코드를 작성했을때 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();
    }
}

'JAVA' 카테고리의 다른 글

동일성(identity)과 동등성(equality)  (0) 2023.02.26
예외에 관하여  (0) 2023.02.24
생성자(Constructor)와 this  (0) 2023.02.06
오버로딩  (0) 2023.02.02
매개변수  (0) 2023.01.31

+ Recent posts