🦁멋쟁이 사자처럼 15기/3월달 수업 내용 정리

멋쟁이 사자처럼 12회 ( 03 / 13 )

코딩하는 하마 2025. 3. 13. 16:56

 

[학습 목표]

1. 문자열 처리와 관련된 String , StringBuilder , StringBuffer 클래스의 특징과 차이점을 이해하고, 각 클래스의 적절한 문자열 조작을 수행할 수 있다.

2. 어노테이션 (Annotation) 의 목적과 기본 사용법을 이해하고, 커스텀 어노테이션을 정의하여 코드에 메타데이터를 추가하고 활용할 수 있다.

3. 제네릭(Generic)의 개념과 사용법을 이해하고, 타입 안정성을 보장하면서 재사용 가능한 코드를 작성할 수 있으며, 와일드 카드와 타입 제한을 활용하여 유연한 제네릭 프로그래밍을 구현할 수 있다.

4.컬렉션 프레임워크의 구조와 주요 인터페이스( List , Set, Map )를 이해하고 설명할 수 있다.

예외 처리 

사용자 정의 예외 처리

 

ex)

// 사용자 정의 예외 클래스
class CustomException extends Exception {   

	public CustomException(String message) {
        super(message); //public Exception (String message)
    }
}

public class d_MyException {
    public static void main(String[] args) {
        try {
            if (args.length == 0) {
                throw new IllegalArgumentException("값을 입력하세요!"); //명시로 
            }

            int a = Integer.parseInt(args[0]); //정수 범위를 나가면 에러가 남 , NumberFormatException 위임 , 선처리 

            if (a < 0) {
                throw new CustomException("음수잖아~~~");
            }

            System.out.println("입력 값: " + a);

        } catch (CustomException e) {
        	e.printStackTrace();
            System.err.println("사용자 정의 예외 발생: " + e.getMessage()+":"+e.toString());
        } catch (NumberFormatException e) {
        	e.printStackTrace();
            System.err.println("숫자가 아닌 값을 입력하셨습니다: " + e.getMessage()+":"+e.toString());
        } catch (IllegalArgumentException e) {
        	e.printStackTrace();
            System.err.println("잘못된 입력: " + e.getMessage()+":"+e.toString());
        } catch (Exception e) {
        	e.printStackTrace();
            System.err.println("예외 발생: " + e+":"+e.toString());
        } finally {
        	System.out.println("예외 상관 없이 무조건 실행");
        }
    }
}

 

위 코드를 보면 다음과 같음을 볼 수 있다.

CustomException -> 음수를 입력했을 때 발생하는 사용자 지정 예외 처리 클래스

 

NumberFormatException -> 숫자가 아닌 값을 입력 했을 때 그리고 지정된 값보다 많은 값을 입력했을 때

발생하는 예외 처리

 

IllegalArgumentException -> 입력을 안했을 때 발생하는 예외 처리

 


 

String, StringBuffer , StringBuilder

- String 은 불변 (Imutable) 객체이므로 연결 연산(+)이 많을수록 성능이 떨어짐

- StringBufferStringBuilder는 가변 (Mutable) 객체로 , 문자열 변경이 많을 때 성능이 더 좋음 (메모리에 민감)

- StringBuilder 는 싱글쓰레드 환경에서 가장 빠른 성능을 제공

 

-StringBuffer

public class a_StringBufferTest {
    public static void main(String[] args) {
        // 1. StringBuffer 객체 생성
        StringBuffer sb = new StringBuffer("Hello");

        // 2️. append(): 문자열 추가
        sb.append(" Java");
        System.out.println("append 후: " + sb); // Hello Java

        // 3️. insert(): 특정 위치에 문자열 삽입
        sb.insert(6, "World ");
        System.out.println("insert 후: " + sb); // Hello World Java

        // 4️. replace(): 특정 범위를 다른 문자열로 변경
        sb.replace(6, 11, "Amazing");
        System.out.println("replace 후: " + sb); // Hello Amazing Java

        // 5️. delete(): 특정 범위 삭제
        sb.delete(6, 13);
        System.out.println("delete 후: " + sb); // Hello Java

        // 6️.reverse(): 문자열 뒤집기
        sb.reverse();
        System.out.println("reverse 후: " + sb); // avaJ olleH

        // 7️. length()와 capacity() 확인
        System.out.println("문자열 길이: " + sb.length()); // 10
        System.out.println("버퍼 크기: " + sb.capacity()); // 기본 용량(16) + 초기 문자열 길이
    }
}

 

실행 결과는 다음과 같음을 볼 수 있다.

 

 

- StringStringBuffer , StringBuilder에 대한 성능 시간 차이

public class d_StringPerformanceTest {

	    public static void main(String[] args) {
	        final int ITERATIONS = 100000; 

	        // 1.String 성능 테스트
	        long startTime = System.nanoTime();
	        String str = "";
	        for (int i = 0; i < ITERATIONS; i++) {
	            str += "a"; 
	        }
	        long endTime = System.nanoTime();
	        System.out.println("String 실행 시간: " + (endTime - startTime) / 1_000_000.0 + " ms");

	        
	        // 2. StringBuffer 성능 테스트
	        startTime = System.nanoTime();
	        StringBuffer stringBuffer = new StringBuffer();
	        for (int i = 0; i < ITERATIONS; i++) {
	            stringBuffer.append("a"); // 가변 객체 (성능 향상)
	        }
	        endTime = System.nanoTime();
	        System.out.println("StringBuffer 실행 시간: " + (endTime - startTime) / 1_000_000.0 + " ms");

	        
	        // 3️. StringBuilder 성능 테스트
	        startTime = System.nanoTime();
	        StringBuilder stringBuilder = new StringBuilder();
	        for (int i = 0; i < ITERATIONS; i++) {
	            stringBuilder.append("a"); // 가변 객체 (StringBuffer보다 빠름)
	        }
	        endTime = System.nanoTime();
	        System.out.println("StringBuilder 실행 시간: " + (endTime - startTime) / 1_000_000.0 + " ms");
	    }
	}

 

이를 실행해보면 String이 제일 오래 걸림을 볼 수 있다.

 


 

reflect

- import java.lang.reflect : 실행 중에 클래스 , 메소드 , 필드 생성자동의 정보를 동적으로 조회하고 조작할 수 있는 기능을 제공하는 API

- 클래스 , 메소드 , 필드 생성자 등의 정보를 동적으로 조회 java Test

- private 필드 메소드 접근 가능

- 프레임워크 (Spring , SpringBoot )에서 객체를 동적으로 생성함

- @랑 함께 사용하면 런타임 조직이 확장가능

- 런타임 중에 특정 메소드 실행 -> invoke()

 

- 클래스 객체 리플렉트 하고 조작 실행해보기

import java.lang.reflect.Field;
//클래스 객체를 리플렉트 하고 조작 실행 해보자.
class My{
	@SuppressWarnings("unused")
	private String name = "홍길동";
	@SuppressWarnings("unused")
	private int a =10;
	
}

public class a_ClassInfoTest {
	public static void main(String[] args) throws Exception {
		My m = new My();
		Class<?> clazz = m.getClass();
		
		//필드 정보 가져오기 
		Field field = clazz.getDeclaredField("name");
		field.setAccessible(true); //private 멤버 접근 
		
		//필드 값 변경 
		field.set(m, "정길동");
		System.out.println("변경된 값 : "+ field.get(m));

System.out.println("=================================");
		field = clazz.getDeclaredField("a");
		field.setAccessible(true);
		
		//필드 값 변경
		field.setInt(m, 20);
		System.out.println("변경된 값 : "+field.get(m));
	}
}

 

실행 결과는 다음과 같음을 볼 수 있다.

 

 

- 클래스 정보 출력해보기

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class a_ClassInfo {
	public static void main(String[] args) {
		Integer number = 42;
		printClassInfo(number);
	}

	public static void printClassInfo(Object obj) {
		Class<?> clazz = obj.getClass(); // 객체의 클래스 정보
		System.out.println("클래스 이름: " + clazz.getName());

		// 필드 정보 출력
		System.out.println("\n[필드 정보]");
		for (Field field : clazz.getDeclaredFields()) {
			System.out.println(" - " + field);
		}
		
		//메소드 정보 출력 
		System.out.println("\n[메소드 정보]");
		for(Method field : clazz.getMethods()) {
			System.out.println(" - "+field);
		}
		//생성자 정보 출력
		System.out.println("\n[생성자 정보]");
		for (Constructor field : clazz.getConstructors()) {
			System.out.println(" - "+field);
		}
				

	}
}

 

위 코드는 클래스 이름과 필드 정보 , 메소드 정보 , 생성자 정보를 출력하는 코드이다.

 


 

어노테이션 (annotation)

FunctionalInterface -> 람다식이랑 같이 사용

SafeVarags -> 가변인자 메소드의 값을 안전하게 보장

Deprecated -> 취소선 , 사용 중지 경고

Override -> 재정의

SuppressWarnings -> 컴파일러 경고 무시

 

ex)

package com.sec10.myreflect;
//@Override  // 메서드 재정의 명시 [검색 결과 없음]
//@Deprecated  // 사용 중지 경고
//@SuppressWarnings("unchecked")  // 컴파일러 경고 무시

public class c_AnnotatinTest {

	@Override
	public String toString() {
		return "This method overrides Object.toString()";
	}

	@Deprecated
	public void oldMethod() {
		System.out.println("This method is deprecated");
	}

	@SuppressWarnings("unchecked")  //타입 체크하지 말고 경고 내지 말아달라고 하는 annotation임
	public <T> T unsafeOperation(Object ...obj) { //obj를 한 개 받을 지 여러 개 받을지 모름
		//Object  ...obj -> 가변 매개이자 , @SafeVarags -> static 메소드 , final 에만 사용한다.
		return (T) obj; //값에 의해 타입을 결정하겠다.   ... -> 1 more (가변) 
		//<> Generic type  , <T> T -> value 에 의해 type 이 결정되는 것 , 제네릭 타입
	}
}

 

Override는 메소드를 재정의한 것이다. 

Deprecated 는 취소선을 만들고 사용 중지 경고를 함을 알 수 있다.

위 코드에서의 SuppressWarnings는 타입을 체크하지 말고 경고 내지 말아달라고 하는 annotation이다. 

 


제네릭스

제네릭스는 컴파일 시 타입 체크하고, 다양한 데이터 타입을 처리할 수 있도록 도와주는 기능

제네릭 클래스 : 특정 타입을 지정하지 않고 , 여러 타입의 데이터를 저장할 수 있는 클래스 T를 사용해 다양한 타입 을 받을 수 있다.

T를 사용해 다양한 타입을 받을 수 있다.

class Test<T> {} - 제네릭 메소드 : 특정 메소드에서만 제네릭 타입을 적용하는 방법

 

class Test<T> {}-제네릭 메서드(Generic Method) : 특정 메서드에서만 제네릭 타입을 적용하는 방법

public static <T> void print(T item) {
System.out.println(item);}

 

와일드카드(Generics Wildcard) : 유연한 타입을 허용

List<? extends T> TT의 하위 클래스만 허용 -> 상한 제한

List<? super T> TT의 상위 클래스만 허용 -> 하한 제한

 

ex)

public class ProFile<T> {	
	private String name;
	private T dept;
	public ProFile(String name, T dept) {
		super();
		this.name = name;
		this.dept = dept;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public T getDept() {
		return dept;
	}
	public void setDept(T dept) {
		this.dept = dept;
	}	
}

package com.sec11.myutil;

public class ProFileTest {
	public static void main(String[] args) {
		ProFile<String> p1 = new ProFile<String>("Dominica", "관리자");
		ProFile<Integer> p2 = new ProFile<Integer>("Dominico", 1111);
		System.out.println(p1.getName() + "   " + p1.getDept());
		System.out.println(p2.getName() + "   " + p2.getDept());
	}
}

 

 


 

컬렉션 프레임워크

List : 순서가 있는 데이터의 집합 , 데이터 중복 허용 (ArrayList, LinkedList)

Set : 순서를 유지하지 않는 데이터의 집합 , 데이터 중복 허용 x (HashSet , LinkedHashSet )

Map : 키와 값의 쌍으로 이루어진 데이터의 집합, 순서 유지x , 키 중복 x, 값 중복x ( HashMap, LinkedHashMap , Properties )

 

- List

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;

public class ArrayTest {

	public static void main(String[] args) {
		
		List<Integer> arr1 = new LinkedList<>();
		arr1.add(80);
		arr1.add(90);
		arr1.add(100);
		arr1.add(200);

		System.out.println(" for 문을 이용한 반복 ");
		for (int r : arr1) {
			System.out.printf("%5d", r);
		}
		System.out.println("\n listIterator 문을 이용한 반복 ");

		ListIterator<Integer> res = arr1.listIterator();
		while (res.hasNext()) {
			System.out.printf("%5d", res.next());
		}
	}
}

 

- Set

package com.sec11.myutil01;
import java.util.*;

public class SetTest {
	public static void main(String[] args) {
		
     Set<String>  m_set =new HashSet<String>();
     m_set.add("java");
     m_set.add("servlet/jsp");
     m_set.add("spring");
     m_set.add("python");    
     m_set.add("python");   
     m_set.add(null);
     System.out.println("HashSet : " + m_set);
     
    Set<String>   t_set = new TreeSet<String>();
     t_set.add("java");
     t_set.add("servlet/jsp");
     t_set.add("spring");
     t_set.add("python");  
     t_set.add("python"); 
   
     System.out.println("\nTreeSet  :"+  t_set);
     
     Set <String>   lh_set = new LinkedHashSet <String>();
     lh_set.add("java");
     lh_set.add("servlet/jsp");
     lh_set.add("spring");
     lh_set.add("python");   
     lh_set.add("python");         
     System.out.println( "\nLinkedHashSet :"+  lh_set);
	}

}

 

-Map

package com.sec11.myutil01;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

public class MapTest {

	public static void main(String[] args) {

		HashMap<String, String> hm = new HashMap<String, String>();

		hm.put("name", "밀로");
		hm.put("addr", "Toronto");
		hm.put("tel","02-000-000");

		System.out.println("전체출력 : " + hm);

		//entrySet() -> 키와 값을 분리해주는 역할을 함
		System.out.println("\n  entrySet()을 이용한 View");
		Set< Entry<String, String> > entires = hm.entrySet();

		for (Entry<String, String> ent : entires) {
			System.out.println(ent.getKey() + " ==> " + ent.getValue());
		}
		
		///////////////////////////////

		System.out.println("\n  keySet()을 이용한 View");
		Set<String> keys = hm.keySet();

		for (String key : keys) {
			System.out.println("Value of " + key + " is: " + hm.get(key));
		}

		//Collection<V> values()
		System.out.println("\n  values()을 이용한 View"); //값의 String으로 리턴하겠다.
		Collection<String> con = hm.values();
		for (String value : con) {
			System.out.println("Value is :" + value);
		}

		System.out.println("\n  키와 값을 찾아 값을 변경 ");
		if (hm.containsKey("name") && hm.containsValue("밀로")) {
			hm.replace("name", "루리");
		}

		System.out.println(hm);
	}

}