이 글은 2026년 2월 11일에 작성된 글입니다.
예외 처리
예외가 발생하더라도 프로그램이 비정상 종료되지 않도록 하는 방법.
try, catch, finally
예외 처리는 try, catch, finally 키워드를 사용한다.
try구문 안에서 예외가 발생하면catch를 이용해 예외를 처리한다.- 예외가 발생하던 그렇지 않던 마지막에 반드시 실행되어야 하는 코드가 있다면
finally를 이용한다.
try { //예외가 발생할 수 있는 부분
intnumber = Integer.parseInt"ABC"); // NumberFormatException 발생
}
catch( NumberFormatExceptione ) { //예외처리 부분
System.out.println( "에러가 발생했습니다." + e.getMessage( ) );
e.printStackTrace( );
}
finally { //항상 수행되는 부분
System.out.println( "숫자 변환을 마쳤습니다.");
}
throws
throws를 이용하여 예외를 해당 메서드에서 처리하지 않고 미룬 후 메서드를 호출하여 사용하는 부분에서 예외처리를 할 수 있다.
각 예외 상황마다 다르게 처리해야 하고 로그도 다르게 남겨야 한다면 이 옵션을 사용한다.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowsException{
public Class loadClass(String filename, String className) throws FileNotFoundException, ClassNotFoundException{
FileInputStreamfis= new FileInputStream(fileName); //FileNotFoundException발생
Class c = Class.forName(className); //ClassNotFoundException발생
return c;
}
public static void main(String[] args) { // loadClass 메서드 호출하는 부분에서 예외처리
ThrowsExceptiontest = new ThrowsException();
try{
test.loadClass("a.txt", "java.lang.String");
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(ClassNotFoundException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
}
제네릭
제네릭 프로그래밍: 어떤 값이 하나의 참조 자료형이 아닌 여러 참조 자료형을 사용할 수 있도록 프로그래밍 하는 것.
- 제네릭 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있게 된다.
- 제네릭을 사용하게 되면 불필요한 타입 변환을 할 필요가 없어 프로그램 성능이 향상된다.
제네릭 클래스
- 여러 자료형으로 바꿔 사용할 변수의 자료형을
T(자료형 매개변수)로 작성한다. - 나중에 클래스를 사용할 때 T 위치에 실제 사용할 자료형을 지정한다.
- 클래스의 각 메서드에 해당 자료형이 필요한 부분에 모두 T 문자를 사용하여 구현한다.
public class GenericEx<T> { // 제네릭 클래스
private T variable; // 자료형 매개변수 T로 선언
public void setVariable(T variable) {
this.variable = variable;
}
public T getVariable() { // 제네릭 메서드
return variable;
}
}
- 제네릭 클래스의 인스턴스가 생성되는 순간 T의 자료형이 정해진다.
- 따라서 인스턴스 변수가 생성되지 전 static 변수가 생성되기 때문에, static 변수의 자료형이나 static 메서드 내부 변수의 자료형으로는 T를 사용할 수 없다.
컬렉션 프레임워크에서의 제네릭 사용
컬렉션 프레임워크에서 다양한 자료형을 관리하기 위해 제네릭을 사용한다.
예를 들어, ArrayList의 정의는 다음과 같다.
public class ArrayList<E> extends AbstractList<E> implememtsList<E>, RandomAccess, Cloneable, java.io.Serializable{
publid boolean add (E e) {
...
}
public E get(int index){
...
}
}
ArrayList를 사용할 때는 다음과 같이 사용한다.
// String 자료형의 ArrayList 생성
ArrayList<String> list = new ArrayList<>();
// add 메서드 사용
list.add(String addStr);
// get 메서드 사용
String s = list.get(0);
컬렉션 프레임워크
자바에서 필요한 자료 구조를 미리 구현하여 java.util 패키지에서 제공하고 있는 라이브러리
- Collection 인터페이스: 하나의 자료를 모아서 관리하는데 필요한 기능 제공
- Map 인터페이스: 쌍으로 된 자료를 관리하는 데 유용한 기능 제공

List
List 인터페이스에서 가장 많이 쓰는 것은 ArrayList이다.
ArrayList는 자바에서 배열을 구현한 대표클래스 중 하나로, 객체 순서를 기반으로 순차적으로 자료를 관리하는 프로그램을 구현할 때 사용한다.
(ArrayList는 앞서 배웠기 때문에 넘어가겠다.)
향상된 for문
배열의 처음에서 끝까지 모든 요소를 참조할 때 사용하면 편리한 반복문이다.
배열 요소 값을 순서대로 하나씩 가져와서 변수에 대입한다.
for (변수 : 배열){
반복실행문;
}
Set
순서와 상관없이 중복을 허용하지 않는 경우에는 Set 인터페이스를 구현한 클래스를 사용한다.
보통 HashSet 클래스를 많이 사용한다.
import java.util.HashSet;
public class HashSetTest {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
hashSet.add(new String("소준형"));
hashSet.add(new String("곽민지"));
hashSet.add(new String("박찬규"));
hashSet.add(new String("이현석"));
hashSet.add(new String("소준형")); // 중복이므로 추가되지 않음
System.out.println(hashSet); // [곽민지, 이현석, 소준형, 박찬규]
}
}
Iterator
컬렉션 프레임워크에 저장된 요소들을 순차적으로 읽어오기 위해 사용되는 인터페이스.
hasNext(),next(),remove()메서드를 제공하여 데이터 구조와 무관하게 일관된 방식으로 요소를 읽거나 삭제할 수 있어 코드의 유연성을 높인다.- 인덱스 기반이 아니므로 Set과 같은 비순차적 구조에서도 효율적이다.
- Collection 인터페이스에는 Iterator를 구현한 클래스의 인스턴스를 반환하는 메서드
iterator()가 정의되어 있다.
즉iterator()메서드를 호출하면, Iterator 타입의 인스턴스가 반환되어 컬렉션을 순회할 수 있다.
다음은 StringSet이라는 클래스를 만들어 HashSet 멤버변수에 add, remove하는 코드이다.
removeString() 메서드에서 Iterator를 사용해 컬렉션을 순회하고 있다.
package practice;
import java.util.HashSet;
import java.util.Iterator;
class StringSet {
private HashSet<String> stringSet;
public StringSet() {
stringSet = new HashSet<String>();
}
public void addString(String s) {
stringSet.add(s);
}
public boolean removeString(String s) {
Iterator<String> ir = stringSet.iterator();
while(ir.hasNext()) {
String r = ir.next();
if (r.equals(s)) {
ir.remove();
return true;
}
}
return false;
}
@Override
public String toString() {
return stringSet.toString();
}
}
public class iteratorTest {
public static void main(String[] args) {
StringSet newSet = new StringSet();
newSet.addString("hi");
newSet.addString("bye");
newSet.addString("hello");
System.out.println(newSet); // [hi, hello, bye]
newSet.removeString("bye");
System.out.println(newSet); // [hi, hello]
}
}
hashCode(), equals() 메서드
Object 클래스에서 제공하는 기본 메서드로, 객체의 참조(주소)가 아닌 논리적 동등성(내용)을 기준으로 두 객체가 같은지 비교하고 싶을 때 반드시 함께 재정의해야 한다
equals()- 어떤 두 참조 변수의 값이 같은지 다른지 동등 여부를 비교해야 할때 사용한다.
- 비교할 대상이 객체일 경우 객체의 주소를 이용하여 비교한다.
hashCode()- 객체의 주소 값을 이용해서 해싱 기법을 통해 해시 코드를 만든 후 반환한다.
- 서로 다른 두 객체는 같은 해시 코드를 가질 수 없어 객체의 지문이라고도 한다.
- 해시 코드는 주소값이 아닌 주소로 만든 고유한 숫자값이다.
equals()가 true인 두 객체의 hashCode()는 같다는 자바 규약이 있다.
따라서 두 객체가 동일하다는 조건을 바꾸고 싶다면 해당 클래스에서 equals()와 hashCode()를 둘 다 재정의 해야 한다.
오늘의 문제 해결
🔴 문제
HashSet 예제 코드를 보던 중 removeMember() 메서드 구현 과정에서 ‘복잡하게 왜 Iterator를 쓰지? 그냥 확장된 for문으로 하나씩 꺼내오면 되지 않나?’ 하는 의문이 들었다. 그래서 예제에서는 Iterator로 설명되어 있지만 확장된 for문으로 바꿔보았다.
🔎 시도
기존 예제 코드는 이렇다.
public boolean removeMember(int memberId) {
Iterator<Member> ir = hashSet.iterator();
while (ir.hasNext()) {
Member member = ir.next();
int tempId = member.getMemberId();
if (tempId == memberId) {
hashSet.remove(member);
return true;
}
}
System.out.println(memberId + "가 존재하지 않습니다.");
return false;
}
위 코드를 확장 for문으로 바꾸는 시도를 해보았다.
public boolean removeMember(int memberId) {
for (Member member : hashSet) {
int tempId = member.getMemberId();
if (tempId == memberId) {
hashSet.remove(member);
return true;
}
}
System.out.println(memberId + "가 존재하지 않습니다.");
return false;
}
그럼 이렇게 해도 되는걸까?
✅ 해결
코드 자체는 좀 더 간단해졌고 Test 클래스에서 실행해본 결과 잘 실행이 되었다.
그러나... 찾아보니 이렇게 향상된 for문에서 hashSet.remove(member)를 하면 안된다고 한다.
그 이유는 ConcurrentModificationException이 터질 가능성이 매우 높기 때문이다.
향상된 for문은 내부적으로 이렇게 동작한다.
Iterator<Member> it = hashSet.iterator();
while (it.hasNext()) {
Member member = it.next();
...
}
즉 Iterator를 내부에서 자동으로 쓰고 있는 것.
그렇데 반복 도중에 remove() 메서드를 사용해 컬렉션을 직접 수정하면, Iterator가 "어? 내가 모르는 사이에 컬렉션이 바뀌었네?" 하고 ConcurrentModificationException 예외를 던진다.
`ConcurrentModificationException`: 자바에서 컬렉션을 반복문으로 조회(순회)하는 도중에 해당 컬렉션의 구조가 변경(요소 추가/삭제)될 때 발생하는 런타임 예외
따라서 향상된 for문은 단순 조회만 할 때, 컬렉션을 수정하지 않을 때만 사용해야 한다.
그러므로 기존 코드대로 Iterator 객체를 만들어 순회하며 remove 하는 것이 안정적이다.
💡 알게된 점
향상된 for문은 조회할 때만 사용하자!
역시 예제 코드에서 괜히 이터레이터를 쓴게 아니구나를 알게 되었다ㅋㅋ 그래도 새로운 시도를 해서 덕분에 저런 예외도 있구나를 알게 되어서 좋았다.
앞으로도 그저 코드를 따라치기만 할게 아니라 ‘이 부분은 왜 이렇게 짰지?, 이렇게 바꿔도 실행되려나? 좀 더 효율적으로 짜는 방법은 없을까?’를 고민하며 코드의 품질을 높이기 위해 고민을 많이 해야겠다고 생각했다.
'Java' 카테고리의 다른 글
| [TIL-260306] Servlet & JSP 입문: 자바와 웹, 웹 애플리케이션, HTTP (0) | 2026.03.08 |
|---|---|
| [TIL-260212] 자바 기초: 내부 클래스, 스레드, 람다, 스트림 (0) | 2026.02.17 |
| [TIL-260210] 자바 기초: 인터페이스 (0) | 2026.02.17 |
| [TIL-260209] 자바 기초: static, 상속, 추상 클래스 (0) | 2026.02.17 |
| [TIL-260206] 자바 기초: 메소드와 생성자 (0) | 2026.02.17 |