0416 Collection, Outer/Inner, 익명, Lambda, Iterator, Stream

Fifty·2025년 4월 16일

2. JAVA

목록 보기
30/33

Collection

List에 값을 채운 상태로 생성하는 방법

		List<String> list = Arrays.asList("홍길동","전우치","손오공","멀린");
		for(String s:list) {
			System.out.print(s+"\t");
		}
		System.out.println();
		list.add("aaa");		// 에러발생 → 불변자료

추가 가능하게 하려면 아래와 같이 해준다.

		list = new ArrayList<>(list);
		list.add("aaa");
		for(String s:list) {
			System.out.print(s+"\t");
		}

Collection Sort

Collection의 정렬 기능 활용
문자열을 정렬할 때 기본적으로 오름차순으로 설정

Java의 Collections.sort() 메서드는 내부적으로 TimSort 알고리즘을 사용
TimSort는 Merge Sort와 Insertion Sort를 혼합한 정렬 알고리즘
최악의 시간 복잡도: O(n log n)
최선의 시간 복잡도: O(n) (이미 정렬된 데이터일 때)

		Collections.sort(list);
		System.out.println();
		for(String s:list) {
			System.out.print(s+"\t");
		}

결과

list자료의 요소로 쓰이는 객체에 클래스를 정의할 때 Comparable 인터페이스를 정의해야한다.

Collections.class

public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

정의하려고 보니 Override가 Comparable이 아니라 compare다.

package ex0416;

import java.util.Comparator;

public class StringDesc implements Comparator<String>{

	@Override
	public int compare(String o1, String o2) {
		return o2.compareTo(o1);
	}
}

2번째 테스트

package ex0416;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionSortTest2 {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();
		list.add("홍길동");
		list.add("전우치");
		list.add("손오공");
		
		Collections.sort(list);
		System.out.println(list);
		
		StringDesc cmp = new StringDesc();
		Collections.sort(list, cmp);
		System.out.println(list);
	}

}

결과

[손오공, 전우치, 홍길동]
[홍길동, 전우치, 손오공]

→ 원본이 바뀐 것은 아님, 대입연산X


[참고] 내림차순 정렬

		Collections.reverse(list);
		System.out.println();
		for(String s:list) {
			System.out.print(s+"\t");
		}

@AllArgsConstructor
@Getter
@ToString

→ @Data를 쓰지 않은 이유: 데이터 생성 시 초기값을 두고 변경하지 않겠다.

Person 클래스로 테스트

package ex0416;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@AllArgsConstructor
@Getter
@ToString
public class Person {
	private String name;
	private int age;
}
package ex0416;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class PersonSortTest {

	public static void main(String[] args) {
		Person p1 = new Person("aaa",20);
		Person p2 = new Person("ccc",20);
		Person p3 = new Person("bbb",20);
		Person p4 = new Person("zzz",20);
		Person p5 = new Person("ttt",20);
		
		List<Person> list = new ArrayList<>();
		list.add(p1);
		list.add(p2);
		list.add(p3);
		list.add(p4);
		list.add(p5);
		
		System.out.println(list);
		Collections.sort(list); 	// Person class 에 comparable을 만들지 않아서 어떤 기준으로 정렬해야할지 모름
        System.out.println(list);
		
	}
}

Person.java 수정해주고

package ex0416;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@AllArgsConstructor
@Getter
@ToString
public class Person implements Comparable<Person>{
	private String name;
	private int age;
	@Override
	public int compareTo(Person o) {
		return this.name.compareTo(o.name);	// 오름차순
        // return o.name.compareTo(this.name); // 내림차순
	}
}

다시 테스트하면 에러가 사라지고 아래와 같이 결과가 나온다.

[Person(name=aaa, age=20), Person(name=ccc, age=20), Person(name=bbb, age=20), Person(name=zzz, age=20), Person(name=ttt, age=20)]
[Person(name=aaa, age=20), Person(name=bbb, age=20), Person(name=ccc, age=20), Person(name=ttt, age=20), Person(name=zzz, age=20)]

compareTo의 return type은 int인데, compareTo를 한 값을 출력해보니 아래와 같이 나왔다.

-1
1
24
23
17
-6

→ 양수, 음수, 0 중에 나온다.

나이로 정렬하기

	public int compareTo(Person o) {
		return this.age-o.age;
	}
package ex0416;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionSearchTest {
	public static void main(String[] args) {
	
		List<String> list = new ArrayList<>();
		list.add("홍길동");
		list.add("전우치");
		list.add("손오공");
		
		Collections.sort(list);
		System.out.println(list);
		
		int idx1 = Collections.binarySearch(list, "홍길동");
		System.out.println(idx1);
	
	}
}

결과
[손오공, 전우치, 홍길동]
2

없는 데이터를 검색하면 음수값을 반환
반환되는 값을 활용해서 아래와 같이 출력이 가능하다.

		if(idx1<0) 
			System.out.println("해당 데이터는 리스트에 없습니다.");
		else
			System.out.println("해당 데이터는 리스트의 "+(idx1+1)+"번째에 있습니다.");

Collection Copy

package ex0416;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class CollectionsCopyTest {

	public static void main(String[] args) {

		// Inmutable(변경 불가능한) 객체 생성
		List<String> src = Arrays.asList("홍길동","전우치","손오공","멀린");
		// 수정 가능한 리스트 생성
		List<String> dst = new ArrayList<>(src);
		System.out.println("1dst: "+dst);
		// 정렬 수행
		Collections.sort(dst);
		System.out.println("2dst: "+dst);
		// 다시 정렬 이전 상태로 돌아갈 필요가 생겼다.
		Collections.copy(dst, src);
		System.out.println("3dst: " + dst);
		System.out.println("3srt: " + src);
		// 수정가능한 상태인지 확인 (Mutable 객체)
		dst.remove(0);
		System.out.println("4dst: " + dst);
		// 반대로 copy 했을 때는 안된다.
		Collections.copy(src, dst);
		System.out.println("5dst: " + dst);
		System.out.println("5src: " + src);
	}
}

Outer/InnerClass

내부 클래스가 외부 클래스의 값을 제약없이 사용 가능.
클래스 파일을 1개만 만들 수 있다.(관리 이점)
내부 클래스는 외부 클래스에만 필요한 클래스다.(다른 클래스에는 필요X)
외부 클래스가 내부 클래스를 매우 필요로 할 때 사용.
내부클래스를 멤버변수화

예제 1

package ex0416;

public class Outer1 {

	private int speed = 10;
	
	// 이너클래스: 클래스 파일 안의 클래스
	class MemberInner1{
		public void move() {
			System.out.printf("인간형 유닛이 %d 속도로 이동합니다.\n", speed);
			// 내부 클래스가 감싸는 클래스의 값을 사용
		}
	}
	
	public void getUnit() {
		MemberInner1 inner = new MemberInner1();
		inner.move();
	}
}
package ex0416;

public class MemberInnerTest {

	public static void main(String[] args) {

		// 외부 클래스에서 직접 작동
		Outer1 out = new Outer1();
		out.getUnit();
		
		// 다른 곳에서 내부 클래스를 생성 가능(외부를 거쳐서 만들어야 함)
		Outer1.MemberInner1 inner = out.new MemberInner1();
		inner.move();

	}
}

결과

인간형 유닛이 10 속도로 이동합니다.
인간형 유닛이 10 속도로 이동합니다.

예제2

package ex0416;

public class HumanCamp {

	private int speed = 10;
	
	public void getMarine() {
		class Marine{
			public void move() {
				System.out.println("인간형 유닛이 "+speed+" 속도로 이동합니다.");
			}
		}
		Marine inner = new Marine();
		inner.move();
	}
}
package ex0416;
public class LocalInnerTest {
	public static void main(String[] args) {
		HumanCamp hc = new HumanCamp();
		hc.getMarine();
	}
}

결과: 인간형 유닛이 10 속도로 이동합니다.

예제1과 2의 차이점: 클래스가 멤버처럼 있는지, 멤버함수 안에 있는지
멤버 → 전역 | 멤버 안 → 지역
예제2: 나만이 사용 가능한 클래스를 생성, 외부클래스의 메서드에서만 내부클래스를 생성 가능


익명클래스

인터페이스 클래스

package ex0416;

public interface Unit {
	void move();
}

HumanCamp2 클래스

package ex0416;

public class HumanCamp2 {
	private int speed = 10;
	
	public Unit getMarine() {
		class Marine2 implements Unit{
			@Override
			public void move() {
				System.out.println("인간형 유닛이 "+speed+" 속도로 이동합니다.");
			}
			
		}
		return new Marine2();
	}
}

메인

package ex0416;

public class AnnoymousInner1Test {

	public static void main(String[] args) {

		HumanCamp2 hc = new HumanCamp2();
		Unit unit = hc.getMarine();
		unit.move();
	}
}

HumanCamp2 를 아래와 같이 수정 가능하다.
문법적으로 말은 안되는데 가능함.
또 클래스 이름이 사라짐 Marin2 → 익명

package ex0416;

public class HumanCamp2 {
	private int speed = 10;
	public Unit getMarine() {
		return new Unit() {
			@Override	//추상메서드이니까 완성은 시켜야함
			public void move() {
				System.out.println("인간형 유닛이 "+speed+" 속도로 이동합니다.");
			}
		};
	}
}

Lambda

화살표(->)가 붙어있는 표현식

Unit.java

package ex0416;

public interface Unit {
	void move();
}

Human.java

package ex0416;

public class Human implements Unit{

	@Override
	public void move() {
		System.out.println("인간이 움직입니다.");
	}
}

Main.java

package ex0416;

public class LambdaTest1 {

	public static void main(String[] args) {
		// 가능한 문법
		Unit unit = new Human();
		unit.move();
		
		
		// 익명클래스
		Unit unit = new Unit() {
			@Override
			public void move() {
				System.out.println("인간이 움직인다구요..");
			}
		};
		unit.move();
		
        
		Unit unit = 
			() -> {
				System.out.println("인간이 움직인다구요..");
			};
		unit.move();
		
		
	} // End of Main
}

함수 이름이 필요없다고 지운다?
ex) 교실에 여러 명인데 '야 일로와' 했을 때 누구인지 모른다, 만약 한명이면 누구인지 안다.

인터페이스에 메서드가 하나일 경우에 이런 상황에 적용이 가능하다.
이런 문법을 Lambda식이라고 한다.

Unit2

package ex0416;

public interface Unit2 {
	void move(String s);
}

Main 일부

		Unit2 unit2 = (String s) ->{
			System.out.println(s);
		};
		unit2.move("move move");
		Unit2 unit2 = s -> System.out.println(s);

매개변수가 하나일 때 함수 소괄호()를 생략할 수 있다.
매개변수의 타입도 생략할 수 있다.
명령어가 한 줄일 경우 블록{} 도 생략할 수 있다.

매개변수가 2개

소괄호를 사용해야한다.

    public interface Unit2 {
        void move(String s, int n);
    }
	Unit2 unit2 = (s,n) -> System.out.println(s+", "+n);
	unit2.move("move move",3);

String 자료형일 때,

public interface Unit2 {
	String move(String s);
}
		Unit2 unit2 = s -> {return s+"!";};
		System.out.println(unit2.move("hey"));

결과: hey!

int 자료형일 때,

public interface Unit2 {
	int move(String s);
}
		Unit2 unit2 = s -> s.length();
		
		int result = unit2.move("hey");
		System.out.println(result);

결과: 3

기능 사용을 위해 클래스, 인스턴스를 생성하는 과정을 없앤 것.
람다식을 사용할 때 메서드가 반드시 하나만 있어야 한다.

@FunctionalInterface

람다식으로 사용할 인터페이스는 메서드가 한 개여야 하는데,
또 다른 메서드를 작성하면 오류가 나지 않으므로 그 표시를 FunctionalInterface로 해준다.

package ex0416;

@FunctionalInterface // 메서드가 1개만 있어야한다.
public interface Unit2 {
	int move(String s);
	//int fly(int x);
}

Iterator

향상된 for문과 Iterator는 사실상 같음
기본 for문과의 차이점: 기본 for문은 반복을 제어할 수 있지만 향상된 for문과 Iterator는 처음부터 끝까지 순서대로

package ex0416;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {

	public static void main(String[] args) {
		
		List<String> list = Arrays.asList("aaa","bbb","ccc");
		// 기본 for문
		for(int i=0; i<list.size(); i++) {
			System.out.print(list.get(i)+"\t");
		}
		System.out.println();
		// 향상된 for문 → 실제로 컴파일될 때 Iterator로 돌아감
		for(String s: list) { 
			System.out.print(s+"\t");
		}
		System.out.println();
		// 컬렉션에서 제공하는 반복자(list, set)를 쓸 때 사용하는 반복문
		Iterator<String> itr = list.iterator();
		while(itr.hasNext()) {
			System.out.print(itr.next()+"\t");
		}
	}
}

결과

aaa		bbb		ccc	
aaa		bbb		ccc	
aaa		bbb		ccc	

Stream

데이터의 흐름 or 흐르는 통로
기존 자료를 변경하지 않는다.
중간연산과 최종연산으로 구성된다.
한 번 생성하고 사용한 Stream은 재사용할 수 없다. int n = stm2.sum()을 하면 오류

package ex0416;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Steam1 {

	public static void main(String[] args) {
		int[] arr = {1,2,3,4,5};
		
		// 1. 스트림 생성
		IntStream stm1 = Arrays.stream(arr);
		
		// 2. 중간 연산
		IntStream stm2 = stm1.filter(n -> n%2==1);
		
		// 3. 최종 연산
		int num = stm2.sum();
		
		System.out.println(num);
		
	}
}

아래는 파이프라인, 메서드체이닝이라고도 부르는 방법

		int num2 = Arrays.stream(arr)
				.filter(n -> n%2==0)
				.sum();
		System.out.println(num2);

한 줄로 표현도 가능

System.out.println(Arrays.stream(arr).filter(n->n%2==1).sum());

StreamSorted

package ex0416;

import java.util.Arrays;
import java.util.List;

public class StreamSortedTest {

	public static void main(String[] args) {

		List<String> list = Arrays.asList("홍길동","해리포터","멸린");
		
		list.stream().sorted().forEach(n -> System.out.println(n+"\t"));
	}

}

Stream 구문을 보면 아래와 같은 역할을 한다.

		list.stream()		//스트림생성
			.sorted()		//정렬(중간연산)
			.forEach(n -> System.out.println(n+"\t"));	// 하나씩 꺼내서 소비함(최종연산)

결과(가나다 순 정렬)

멸린
해리포터
홍길동

문자열의 길이를 기준으로 정렬

		// 오름차순
		list.stream()
			.sorted( (s1, s2) -> s1.length() - s2.length())
			.forEach(n-> System.out.println(n+"\t"));

		// 내림차순
		list.stream()
			.sorted( (s1, s2) -> s2.length() - s1.length())
			.forEach(n-> System.out.println(n+"\t"));

정리

  1. List list = Arrays.asList(~~); // 불변의 리스트
  1. Collection
  • sort: 정렬 → compare를 반드시 구현해줘야 함
  • search: 몇번째 인덱스에 있는지?
  • copy: 복사
    원본이 바뀐다.

3.Outer/Inner Class
나만 사용하는 클래스
클래스 1개에 여러 개 클래스 관리 가능
[선언방법]
1. 멤버변수 구역 선언: 외부클래스.내부클래스로 접근 가능
2. 멤버함수 안에 선언: 접근 어려움 숨김

  1. 익명 클래스로 됨
    클래스 선언할 때 생성자, 인스턴스 생성해야하지만,
    Lambda식을 사용해서 그 과정을 생략할 수 있게 함
    매개변수 타입 생략 가능
    매개변수가 하나일 때 소괄호 생략가능
    명령어가 한 줄일 경우 블록 생략가능
    인터페이스 클래스에서 메소드가 한 개일 때
    @FunctionalInterface로 지정해주면 추가 메소드를 쓰는 것을 방지
  1. 반복문 형태
  • 기본 for문: 제어가 가능함
    for(int i=0; i<x; i++)
  • 향상된 for문 → 컴파일과정에서 Iterator함
    for(String s : list)
  • Iterator
    Iterator itr = list.iterator();
    while(itr.hasNest()) { }
  1. Stream
    데이터의 흐름, 통로
    Stream에서 수행하는 연산은 기존 자료를 변경하지 않는다.
    중간, 최종연산으로 구성, 일회성

0개의 댓글