no image
[C++] 백준 14단계 - 1269번 문제 (대칭 차집합)
문제설명 소스코드 map을 이용 #include #include using namespace std; int main(void) { map m; int aSize, bSize; cin >> aSize >> bSize; for (int i = 0; i > input; m.insert(pair(input, true)); //입력받은 값과 true를 맵에 저장 } int count = 0; //교집합 개수를 저장 for (int i = 0; i > input; if (m[input] == true) count++; //맵에 해당하는 값이 있으면 count를 1증가 } cout > aSize >> ..
2023.07.24
no image
[C++] 백준 14단계 1764번 문제 (듣보잡)
문제설명 소스코드 #include #include #include using namespace std; int main() { ios_base::sync_with_stdio(false); //표준 스트림 동기화 해제 cin.tie(0); //입출력 연결 끊기 vectorv1, v2; int N, M; cin >> N >> M; for (int i = 0; i > input; v1.push_back(input); } sort(v1.begin(), v1.end()); //v1 정렬 for (int i = 0; i > input; if (binary_search(v1.begin(), v1.end(),..
2023.07.23
no image
[JAVA] 리플렉션(Reflection)과 어노테이션(Annotation)
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 리플렉션 리플렉션이란 실행 도중에 타입(클래스, 인터페이스 등)을 검사하고 구성 멤버를 조사하는 것을 말한다. 이클립스 개발환경에서 outline과 비슷하다고 생각하면 된다. 자바는 클래스와 인터페이스의 메타 정보를 Class 객체로 관리한다. 메타 정보 패키지 정보, 타입 정보, 멤버(생성자, 필드, 메소드) 정보 등을 말한다. 이러한 메타 정보를 프로그램에서 읽고 수정하는 행위를 리플렉션이라고 한다. 프로그램에서 Class 객체를 얻으려면 아래의 3가지 방법 중 하나를 이용한다. Class clazz = 클래스이름.class; //클래스로부터 얻는 방법 Class clazz = C..
2023.07.23
no image
[C++] 백준 14단계 - 10816번 문제 (숫자 카드 2)
문제설명 소스코드 #include #include using namespace std; int main(void) { ios_base::sync_with_stdio(false); //표준 스트림 동기화 해제 cin.tie(nullptr); //입출력 연결 끊기 map m; int N, M; cin >> N; for (int i = 0; i > input; ++m[input]; } cin >> M; for (int i = 0; i > input; cout
2023.07.22
no image
[JAVA] 전이 의존과 집합 모듈
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 전이 의존 이러한 의존 관계를 아래의 의존 관계로 바꾸었다고 해보자. 이렇게 되면 myapp1은 module_1에 있는 내용은 접근할 수 있지만, module_2의 내용은 접근할 수 없다. myapp1에서 module_1과 module_2에 있는 내용 전부 접근하고 싶다면 전이 의존을 이용해야 한다. 이 상황에서 전이 의존은 module_1의 모듈 기술자에 transitive 키워드와 module_2를 의존 설정하면 된다. module myapp1 { requires module_1; } module module_1 { requires transitive module_2 } 당연히 m..
2023.07.22
no image
[C++] 백준 14단계 - 14425번 문제 (문자열 집합)
문제설명 소스코드 #include #include using namespace std; int main(void) { map m; int N, M; int count = 0; cin >> N >> M; for (int i = 0; i > str; m.insert(pair(str, true)); } for (int i = 0; i > str; if (m[str] == true) count++; } cout
2023.07.21
no image
[JAVA] 모듈(응용 프로그램 모듈화)
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 모듈 Java 9부터 지원함 모듈은 패키지 관리 기능까지 포함된 라이브러리 모듈을 만들려면 필수로 모듈 기술자(module-info.java)가 있어야 한다. 모듈 기술자가 없으면 라이브러리이고, 있으면 모듈이다. 라이브러리와 달리 모듈은 패키지를 은닉하여 모듈 밖인 외부에서는 접근할 수 없게 할 수 있다.(접근 제한자 private와 비슷한 기능) 패키지 은닉 패키지를 은닉하는 이유는 아래와 같다. -패키지 1은 공개하고 패키지 2와 3은 은닉하여, 패키지 1로 사용방법을 통일할 수 있기 때문이다. -모듈 성능 향상을 위해 패키지 2와 3을 수정하더라도 외부에서는 모듈 사용 방법(패..
2023.07.21
no image
[C++] 백준 14단계 - 10815번 문제 (숫자 카드)
문제설명 소스코드 #include #include #include using namespace std; int main() { ios_base::sync_with_stdio(false); //표준 스트림 동기화 해제 cin.tie(nullptr); //입출력 연결 끊기 int N, M; vector vec; cin >> N; for (int i = 0; i > input; vec.push_back(input); } sort(vec.begin(), vec.end()); cin >> M; for (int i = 0; i > input; cout
2023.07.20

문제설명

 

소스코드

map을 이용

#include <iostream>
#include <map>
using namespace std;
int main(void)
{
	map<int, bool> m;
	int aSize, bSize;
	cin >> aSize >> bSize;
	for (int i = 0; i < aSize; i++)
	{
		int input;
		cin >> input;
		m.insert(pair<int, bool>(input, true)); //입력받은 값과 true를 맵에 저장
	}
	int count = 0; //교집합 개수를 저장
	for (int i = 0; i < bSize; i++)
	{
		int input;
		cin >> input;
		if (m[input] == true) count++; //맵에 해당하는 값이 있으면 count를 1증가
	}
	cout << (aSize-count)+(bSize-count);
}

 

vector를 이용

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
    ios_base::sync_with_stdio(false); //표준 스트림 동기화 해제
    cin.tie(nullptr); //입출력 연결 끊기
    vector<int>v1, v2;
    int aSize, bSize;
    cin >> aSize >> bSize;
    for (int i = 0; i < aSize; i++)
    {
        int input;
        cin >> input;
        v1.push_back(input);
    }
    sort(v1.begin(), v1.end()); //이진 탐색을 위한 v1 정렬
    for (int i = 0; i < bSize; i++)
    {
        int input;
        cin >> input;
        if (binary_search(v1.begin(), v1.end(), input)) v2.push_back(input); //이진 탐색으로 v1에 input이 없으면 v2에 추가
    }
    cout << (aSize - v2.size()) + (bSize - v2.size()); //v2의 사이즈가 교집합의 개수
}

 

설명

  • 두 코드 모두 집합 A는 모두 입력을 받는다.
  • B 입력에서 A와 중복된 값이 있으면 교집합 개수를 증가시킨다.
  • 답 : (A집합 크기 - 교집합 개수) + (B집합 크기 - 교집합 개수)

문제설명

 

소스코드

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() 
{
    ios_base::sync_with_stdio(false); //표준 스트림 동기화 해제
    cin.tie(0); //입출력 연결 끊기
    vector<string>v1, v2;
    int N, M;
    cin >> N >> M;
    for (int i = 0; i < N; i++)
    {
        string input;
        cin >> input;
        v1.push_back(input);
    }
    sort(v1.begin(), v1.end()); //v1 정렬
    for (int i = 0; i < M; i++)
    {
        string input;
        cin >> input;
        if (binary_search(v1.begin(), v1.end(), input)) v2.push_back(input); //이진 탐색으로 v1에 input이 없으면 v2에 추가
    }
    sort(v2.begin(), v2.end()); //v2 정렬
    cout << v2.size() << "\n"; //v2의 개수 출력
    for (auto i : v2) cout << i << "\n"; //v2 내용 출력
}

 

설명

  • 표준 스트림 동기화 해제, 입출력 연결 끊기, endl 대신 '\n' 사용 -> 시간초과 방지
  • v1에 듣도 못한 사람을 입력받는다.
  • v2에 v1에 없는(듣도 보도 못한 사람) 문자열만 저장한다.
  • v2를 정렬시키고, v1의 크기를 출력 한다.
  • v2의 내용을 출력한다.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


리플렉션

리플렉션이란 실행 도중에 타입(클래스, 인터페이스 등)을 검사하고 구성 멤버를 조사하는 것을 말한다.

이클립스 개발환경에서 outline과 비슷하다고 생각하면 된다.

 

 

자바는 클래스와 인터페이스의 메타 정보를 Class 객체로 관리한다.

메타 정보
패키지 정보, 타입 정보, 멤버(생성자, 필드, 메소드) 정보 등을 말한다.

이러한 메타 정보를 프로그램에서 읽고 수정하는 행위를 리플렉션이라고 한다.

 

프로그램에서 Class 객체를 얻으려면 아래의 3가지 방법 중 하나를 이용한다.

Class clazz = 클래스이름.class; //클래스로부터 얻는 방법
Class clazz = Class.forName("패키지...클래스이름"); //클래스로부터 얻는 방법
Class clazz = 객체참조변수.getClass(); // 객체로부터 얻는 방법

 셋 중 어떤 방법을 사용하더라도 동일한 Class 객체를 얻을 수 있다.

 

패키지와 타입 정보 얻기

패키지와 타입(클래스, 인터페이스) 이름 정보는 아래와 같은 메소드를 통해 얻을 수 있다.

메소드 용도
Package getPackage() 패키지 정보 읽기
String getSimpleName() 패키지를 제외한 타입 이름
String getName() 패키지를 포함한 전체 타입 이름

 

Car.java

package ch12.sec11.exam01;
public class Car {
}

 

GetClassExample.java

package ch12.sec11.exam01;
public class GetClassExample {
	public static void main(String[] args) throws Exception {
		//how1
		Class clazz = Car.class;
		
		//how2
		//Class clazz = Class.forName("ch12.sec11.exam01.Car");
		
		//how3
		//Car car = new Car();
		//Class clazz = car.getClass();
		
		System.out.println("패키지: " + clazz.getPackage().getName());
		System.out.println("클래스 간단 이름: " + clazz.getSimpleName());
		System.out.println("클래스 전체 이름: " + clazz.getName());
	}
}
/*
패키지: ch12.sec11.exam01
클래스 간단 이름: Car
클래스 전체 이름: ch12.sec11.exam01.Car
*/

 

멤버 정보 얻기

타입(클래스, 인터페이스)가 가지고 있는 멤버 정보는 다음 메소드를 통해 얻을 수 있다.

메소드 용도
Constructor[] getDeclaredConstructors() 생성자 정보 읽기
Field[] getDeclaredFields() 필드 정보 읽기
Method[] getDeclaredMethod() 메소드 정보 읽기

메소드 이름에서 알 수 있듯이 각각 생성자 배열, 필드 배열, 메소드 배열을 리턴한다.

각각 생성자, 필드, 메소드에 대한 선언부 정보를 제공한다.

 

아래는 Car 클래스에서 선언된 생성자, 필드, 메소드의 선언부 정보를 얻고 출력하는 예제이다.

Car.java

package ch12.sec11.exam02;
public class Car {
	//필드
	private String model;
	private String owner;
	
	//생성자
	public Car() {
	}
	public Car(String model) {
		this.model = model;
	}
	
	//메소드
	public String getModel() { return model; }
	public void setModel(String model) { this.model = model; }
	public String getOwner() { return owner; }
	public void setOwner(String owner) { this.owner = owner; }
}

 

ReflectionExample.java

package ch12.sec11.exam02;
import java.lang.reflect.*;
	
public class ReflectionExample {
	public static void main(String[] args) throws Exception {
		Class clazz = Car.class;
		
		System.out.println("[생성자 정보]");
		Constructor[] constructors = clazz.getDeclaredConstructors();
		for(Constructor constructor : constructors) {
			System.out.print(constructor.getName() + "(");
			Class[] parameters = constructor.getParameterTypes();
			printParameters(parameters);
			System.out.println(")");
		 	}
		System.out.println();
		
		System.out.println("[필드 정보]");
		Field[] fields = clazz.getDeclaredFields();
		for(Field field : fields) {
			System.out.println(field.getType().getName() + " " + field.getName());
		}
		System.out.println();
		
		System.out.println("[메소드 정보]");
		Method[] methods = clazz.getDeclaredMethods();
		for(Method method : methods) {
			System.out.print(method.getName() + "(");
			Class[] parameters = method.getParameterTypes();
			printParameters(parameters);
			System.out.println(")");
		}
	}
			
	private static void printParameters(Class[] parameters) {
		for(int i=0; i<parameters.length; i++) {
			System.out.print(parameters[i].getName());
			if(i<(parameters.length-1)) {
				System.out.print(",");
			}
		}
	}
}
/*
[생성자 정보]
ch12.sec11.exam02.Car()
ch12.sec11.exam02.Car(java.lang.String)

[필드 정보]
java.lang.String model
java.lang.String owner

[메소드 정보]
getOwner()
setOwner(java.lang.String)
getModel()
setModel(java.lang.String)
*/

 

리소스 경로 얻기

Class 객체는 클래스 파일(~.class)의 경로 정보를 가지고 있기 때문에 이 경로를 기준으로 상대 경로에 있는 다른 리소스 파일의 정보를 얻을 수 있다.

이때 사용하는 메소드는 아래와 같다.

메소드 용도
URL getResource(String name) 리소스 파일의 URL 리턴
InputStream getResourceAsStream(String name) 리소스 파일의 InputStream 리턴
  • getResource() : 경로 정보가 담긴 URL 객체를 리턴
  • getResourceAsStream() : 파일의 내용을 읽을 수 있도록 InputStream 객체를 리턴

 

프로그램에서 이미지 파일의 절대 경로가 필요할 경우, Car.class가 있는 곳에서 상대 경로로 아래와 같이 얻을 수 있다.

Car.java

package ch12.sec11.exam03;
public class Car {
}

 

GetResourceExample.java

package ch12.sec11.exam03;

public class GetResourceExample {
	public static void main(String[] args) {
		Class clazz = Car.class;

		String photo1Path = clazz.getResource("photo1.jpg").getPath();
		String photo2Path = clazz.getResource("images/photo2.jpg").getPath();

		System.out.println(photo1Path);
		System.out.println(photo2Path);
	}
}
/*
/D:/Eclipse/thisisjava/bin/ch12/sec11/exam03/photo1.jpg
/D:/Eclipse/thisisjava/bin/ch12/sec11/exam03/images/photo2.jpg
*/

 

 

리플렉션 허용

모듈에서 은닉된 패키지는 기본적으로 다른 모듈에 의해 리플렉션을 허용하지 않는다.

하지만 경우에 따라서는 은닉된 패키지도 리플렉션을 허용해야 할 때가 있다.

모듈은 모듈 기술자를 통해 모듈 전체 또는 지정된 패키지에 대해 리플렉션을 허용할 수 있고, 특정 외부 모듈에서만 리플렉션을 허용할 수도 있다.

 

모듈 전체를 리플렉션 허용

open module 모듈이름 {
	//내용 작성
}

 

지정된 패키지에 대해 리플렉션 허용

module 모듈이름 {
    opens 허용할 패키지 이름;
    opens 허용할 패키지 이름2;
}

 

지정된 패키지에 대해 특정 외부 모듈에서만 리플렉션 허용

module 모듈이름 {
    opens 패키지1 to 외부모듈이름, 외부모듈이름2;
    opens 패키지2 to 외부모듈이름3;
}

 


 

어노테이션(Annotation)

@로 작성되는 요소를 어노테이션이라고 한다. 

어노테이션은 클래스 또는 인터페이스를 컴파일하거나 실행할 때 어떻게 처리해야 할 것인지를 알려주는 설정 정보이다.

어노테이션은 아래의 세 가지 용도로 사용된다.

  1. 컴파일 시 사용하는 정보 전달
  2. 빌드를 하는 프로그램이 코드를 자동으로 생성할 때 사용하는 정보 전달
  3. 실행 시 특정 기능을 처리할 때 사용하는 정보 전달

 

어노테이션은 자바 프로그램을 개발할 때 필수요소가 되었다.

Spring Framework 또는 Spring Boot는 다양한 종류의 어노테이션을 사용해서 웹 어플리케이션을 설정하는 데 사용된다.

 

어노테이션 타입 정의와 적용

  • 어노테이션도 하나의 타입이므로 먼저 정의를 해야 한다.
  • 어노테이션 정의 방법은 인터페이스를 정의하는 것과 유사
public @interface AnnotationName{
}

 

이렇게 정의한 어노테이션은 아래와 같이 사용한다.

@AnnotationName

 

  • 어노테이션은 필드와 유사한 속성을 가질 수 있다. 주의해야 할 것은 유사한 것이지 필드랑 속성은 다르다는 것이다.
  • 속성의 기본값은 default 키워드로 지정할 수 있다.
public @interface AnnotationName {
	String prop1(); //기본 값 없음
	int prop2() default 1; //기본 값 1
}

 

이렇게 정의한 어노테이션은 코드에서 아래와 같이 사용할 수 있다. prop1은 기본값이 없기 때문에 반드시 값을 넣어야 하고, prop2는 기본값이 있기 때문에 생략이 가능하다.

@AnnotationName(prop1 = "a")
@AnnotationName(prop1 = "a", prop2 = 3)

 

  • 어노테이션은 기본 속성인 value를 아래와 같이 가질 수 있다
  • value 속성을 가진 어노테이션을 코드에서 사용할 때는 값만 기술할 수 있다. 기술한 값은 value 속성에 자동으로 대입된다.
public @interface AnnotationName {
	String value();
	int prop2() default 1;
}

 

@AnnotationName("값")

 

하지만 value 속성과 다른 속성의 값을 동시에 주고 싶다면 value 속성 이름을 반드시 언급해야 한다.

@AnnotationName(value = "값", prop2 = 3)

 

어노테이션 적용 대상

  • 어노테이션은 설정 정보이다.
  • 어떤 대상에 설정 정보를 적용할 것인지(클래스에 적용할지 메소드에 적용할지..) 명시해야 한다.
  • 적용할 수 있는 대상의 종류는 아래의 표처럼 ElementType 열거 상수로 정의되어 있다.
ElementType 열거 상수 적용 요소
TPYE 클래스, 인터페이스, 열거 타입
ANNOTATION_TYPE 어노테이션
FIELD 필드
CONSTRUCTOR 생성자
METHOD 메소드
LOCAL_VARIABLE 로컬 변수
PACKAGE 패키지

 

  • 적용대상을 지정할 때는 @Target 어노테이션을 사용한다.
  • @Target의 기본 속성인 value는 ElementType 배열을 값으로 가진다.(적용 대상을 복수 개로 지정하기 위함)
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target ({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface AnnotationName {
	String value();
	int prop2() default 1;
}

이 어노테이션은 클래스, 필드, 메소드에 적용할 수 있다.

 

어노테이션 유지 정책

  • 어노테이션을 정의할 때 한 가지 더 추가해야 할 내용은 어노테이션을 언제까지 유지할 것인지 이다.
  • 어노테이션 유지 정책은 RetentionPolicy 열거 상수로 아래와 같이 정의되어 있다.
RetentionPolicy 열거 상수 어노테이션 적용 시점 어노테이션 제거 시점
SOURCE 컴파일할 때 적용 컴파일된 후에 제거됨
CLASS 메모리로 로딩할 때 적용 메모리로 로딩된 후에 제거됨
RUNTIME 실행할 때 적용 계속 유지됨

 

  • 유지 정책을 지정할 때에는 @Retention 어노테이션을 사용한다.
  • @Retention의 기본 속성인 value는 RetentionPolicy 열거 상수 값을 가진다.
  • 아래의 코드는 어노테이션 설정 정보를 이용할 수 있도록 유지 정책을 RUNTIME으로 지정한 것이다.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target ({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationName {
	String value();
	int prop2() default 1;
}

 

 

어노테이션 설정 정보 이용

  • 어노테이션은 아무런 동작을 가지지 않는 설정 정보이다.
  • 이 설정 정보를 이용해서 어떻게 처리할 것인지는 어플리케이션의 몫이다.
  • 어플리케이션은 리플렉션을 이용해서 적용 대상으로부터 어노테이션의 정보를 아래의 메소드로 얻어낼 수 있다.
리턴 타입 메소드명(매개변수) 설명
boolean isAnnotationPresent(AnnotationName.class) 지정한 어노테이션이 적용되었는지 여부
Annotation getAnnotation(AnnotationName.class) 지정한 어노테이션이 적용되어 있으면 어노테이션을 리턴하고, 그렇지 않다면 null을 리턴
Annotation[] getDeclaredAnnotations() 적용된 모든 어노테이션을 리턴

 

PrintAnnotation.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintAnnotation {
	String value() default "-";
	int number() default 15;
}

 

Service.java

public class Service {
	@PrintAnnotation
	public void method1() {
		System.out.println("실행 내용1");
	}
	
	@PrintAnnotation("*")
	public void method2() {
		System.out.println("실행 내용2");
	}
	
	@PrintAnnotation(value="#", number=20)
	public void method3() {
		System.out.println("실행 내용3");
	}
}

 

PrintAnnotationExample.java

import java.lang.reflect.Method;
public class PrintAnnotationExample {
	public static void main(String[] args) throws Exception {
		Method[] declaredMethods = Service.class.getDeclaredMethods();
		for(Method method : declaredMethods) {
			//PrintAnnotation 얻기
			PrintAnnotation printAnnotation = method.getAnnotation
					(PrintAnnotation.class);

			//설정 정보를 이용해서 선 출력
			printLine(printAnnotation);

			//메소드 호출
			method.invoke(new Service());
			
			//설정 정보를 이용해서 선 출력
			printLine(printAnnotation);
		}
	}
	
	public static void printLine(PrintAnnotation printAnnotation) {
		if(printAnnotation != null) {
			//number 속성값 얻기
			int number = printAnnotation.number();
			for(int i=0; i<number; i++) {
				//value 속성값 얻기
				String value = printAnnotation.value();
				System.out.print(value);
			}
			System.out.println();
		}
	}
}
/*
####################
실행 내용3
####################
***************
실행 내용2
***************
---------------
실행 내용1
---------------
*/

문제설명

 

소스코드

#include <iostream>
#include <map>
using namespace std;
int main(void)
{
	ios_base::sync_with_stdio(false); //표준 스트림 동기화 해제
	cin.tie(nullptr); //입출력 연결 끊기
	map<int, int> m;
	int N, M;
	cin >> N;
	for (int i = 0; i < N; ++i)
	{
		int input;
		cin >> input;
		++m[input];
	}
	cin >> M;
	for (int i = 0; i < M; ++i)
	{
		int input;
		cin >> input;
		cout << m[input] << " ";
	}
}

 

설명

  • 표준 스트림 동기화해제, 입출력 연결 끊기 -> 시간초과 방지
  • 선형 방식인 이중for문은 시간복잡도 O(N^2)이므로 시간초과가 발생한다.
  • map 라이브러리의 map을 사용하면 시간복잡도O(log(N))이라서 시간초과가 발생하지 않는다.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


전이 의존

이러한 의존 관계를 아래의 의존 관계로 바꾸었다고 해보자.

 

 

이렇게 되면 myapp1은 module_1에 있는 내용은 접근할 수 있지만, module_2의 내용은 접근할 수 없다.

myapp1에서 module_1과 module_2에 있는 내용 전부 접근하고 싶다면 전이 의존을 이용해야 한다.

이 상황에서 전이 의존은 module_1의 모듈 기술자에 transitive 키워드와 module_2를 의존 설정하면 된다.

module myapp1 {
	requires module_1;
}
module module_1 {
	requires transitive module_2
}

 

당연히 module_1과 module_2의 위치를 알려줘야 한다.

 

이제 의존 관계는 아래와 같다.

 


 

집합 모듈

  • 집합 모듈은 전이 의존을 이용하여 여러 모듈을 모아놓은 모듈을 뜻한다.
  • 자주 사용되는 모듈들을 일일이 requires 하는 번거로움을 피하고 싶을 때 집합 모듈을 생성하면 편리하다.
  • 집합 모듈은 자체적인 패키지를 가지지 않고, 모듈 기술자에 전이 의존 설정만 하면 된다.

 

예를 들어 my_module은 module_1과 module_2를 제공하는 집합 모듈이라고 가정하면  my_module의 모듈 기술자는 아래와 같이 작성할 수 있다.

module my_module {
    requires transitive module_1;
    requires transitive module_2;
}

따라서 외부에서는 my_module만 모듈 기술자에서 requires 해주면  module_1과 module_2를 모두 사용할 수 있다.

하지만 my_module, module_1, module_2 모두 경로를 알려줘야 한다는 귀찮은 점이 있다.

 

문제설명

 

소스코드

#include <iostream>
#include <map>
using namespace std;
int main(void)
{
	map<string, bool> m;
	int N, M;
	int count = 0;
	cin >> N >> M;
	for (int i = 0; i < N; i++)
	{
		string str;
		cin >> str;
		m.insert(pair<string, bool>(str, true));
	}
	for (int i = 0; i < M; i++)
	{
		string str;
		cin >> str;
		if (m[str] == true) count++;
	}
	cout << count;
}

 

설명

  • 문자열과 true를 맵에 저장한다.
  • 맵에서 문자열을 찾고, 해당 문자열의 bool 타입 값이 true면 count를 1 증가시킨다.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


모듈

  • Java 9부터 지원함
  • 모듈은 패키지 관리 기능까지 포함된 라이브러리
  • 모듈을 만들려면 필수로 모듈 기술자(module-info.java)가 있어야 한다.
    모듈 기술자가 없으면 라이브러리이고, 있으면 모듈이다.

패키지 은닉

  • 라이브러리와 달리 모듈은 패키지를 은닉하여 모듈 밖인 외부에서는 접근할 수 없게 할 수 있다.(접근 제한자 private와 비슷한 기능)
패키지 은닉
패키지를 은닉하는 이유는 아래와 같다.
-패키지 1은 공개하고 패키지 2와 3은 은닉하여, 패키지 1로 사용방법을 통일할 수 있기 때문이다.
-모듈 성능 향상을 위해 패키지 2와 3을 수정하더라도 외부에서는 모듈 사용 방법(패키지1)이 달라지지 않기 때문에 외부에 영향을 주지 않는다.
  • 모듈은 자신이 실행할 때 필요로 하는 의존 모듈을 모듈 기술자에 기술할 수 있기 때문에 모듈 간의 의존 관계를 쉽게 파악 가능하다.

의존관계

 

  • 모둘도 라이브러리이므로 JAR 파일 형태로 배포할 수 있다. 응용프로그램을 개발할 때 원하는 기능의 모듈을 다운로드해서 사용하면 된다.
  • 대규모 응용프로그램은 기능별로 모듈화 해서 개발할 수도 있다. 모듈별로 개발하고 조립하는 방식을 사용하면 재사용성 및 유지 보수에 유리하다.

 


 

응용프로그램 모듈화 (프로젝트 단위)

응용프로그램은 하나의 프로젝트로도 개발이 가능하지만, 대규모 프로젝트와 같은 경우 기능별로 서브 프로젝트(모듈)로 쪼갠다음 조합해서 개발할 수 있다.

아래의 그림처럼 my_app1 응용프로그램은 2개의 서브 프로젝트(모듈)인 module_1과 module_2로 쪼개서 개발하고, 이들을 조합해서 완성할 수 있다.

 

module_1 만들기

module_1에는 패키지 pack1과 pack2를 가지고 있다. 따라서 모듈 기술자에 아래와 같이 선언한다.

exports
exports 키워드는 모듈이 가지고 있는 패키지를 외부에서 사용할 수 있도록 노출시키는 역할을 한다.
exports 하지 않으면 해당 패키지는 은닉이 된다.
module my_app1 {
	//여기에 패키지를 exports 하지 않으면 은닉 패키지가 된다.
	exports pack1;
	exports pack2;
}

 

A.class

package pack1;
public class A {
	public void method(){
		System.out.println("A-method 실행");
	}
}

 

B.class

package pack2;
public class B {
	public void method(){
		System.out.println("B-method 실행");
	}
}

 

module_2 만들기

module_2에는 패키지 pack3과 pack4를 가지고 있다. 따라서 모듈 기술자에 아래와 같이 선언한다.

module module_2 {
	exports pack3;
	exports pack4;
}

 

C.java

package pack3;
public class C {
	public void method(){
		System.out.println("C-method 실행");
	}
}

 

D.java

package pack4;
public class D {
	public void method(){
		System.out.println("D-method 실행");
	}
}

 

myapp_1 프로젝트 만들기

myapp_1 프로젝트를 만들 때 꼭 해야 하는 것이 있다.

반드시 myapp_1도 모듈 기술자를 만들어야 한다. 왜냐하면 myapp_1은 module1과 modul_2를 사용해야 하는데 이것은 해당 모듈에 의존한다는 것이다. 때문에 이를 모듈 기술자에 써야 한다.

requires
requires 키워드는 필요한 모듈을 컴파일하거나 실행할 때 필요한 의존 모듈을 지정한다.
requires 키워드를 기술하고 반드시 해당 모듈이 있는 경로를 설정해야 한다.

모듈 경로를 알려주는 방법은 아래와 같다.
프로젝트 마우스 우클릭 -> Build Path -> Confugure Build Path

Project 탭 -> modulepath -> add 버튼을 누르고 필요한 모듈을 선택한다.
제대로 선택이 되었다면 apply and close 버튼을 누른다.
module my_app1 {
	requires module_1;
	requires module_2;
}

 

Main.java

package app;

import pack1.A;
import pack2.B;
import pack3.C;
import pack4.D;

public class Main {
	public static void main(String[] args) {
		A a = new A();
		a.method();
		
		B b = new B();
		b.method();
		
		C c = new C();
		c.method();
		
		D d = new D();
		d.method();
	}

}
/*
A-method 실행
B-method 실행
C-method 실행
D-method 실행
*/

필요한 모듈을 import 해서 쓰면 된다.

 


 

모듈 배포용 JAR 파일 만들기

모듈 개발을 완료했다면 다른 모듈에서 쉽게 사용할 수 있도록 바이트코드 파일로 구성된 배포용 JAR 파일을 생성할 수 있다.

위 예제에서 만든 module1과 module2 모듈의 배포용 JAR 파일을 생성하는 방법은 아래와 같다.

1. 프로젝트 우클릭 -> Export

 

2. Java -> JAR file 클릭 -> Next

 

3. src 빼고 모두 체크 해제

 

4. JAR 파일을 저장할 경로를 지정 -> 저장 -> Finish

 

5. modul_2 도 1~4 과정을 동일하게 반복

 


 

JAR 모듈 사용하기

새로운 프로젝트 myapp_2 프로젝트를 생성한다.

반드시 myapp_1도 모듈 기술자를 만들어야 한다. 왜냐하면 myapp_1은 module1과 modul_2를 사용해야 하는데 이것은 해당 모듈에 의존한다는 것이다. 때문에 이를 모듈 기술자에 써야 한다.

requires
requires 키워드는 필요한 모듈을 컴파일하거나 실행할 때 필요한 의존 모듈을 지정한다.
requires 키워드를 기술하고 반드시 해당 모듈이 있는 경로를 설정해야 한다.

JAR 모듈의 경로를 알려주는 방법은 프로젝트 모듈 경로를 알려주는 방법과 살짝 다르다.
모듈 경로를 알려주는 방법은 아래와 같다.
프로젝트 마우스 우클릭 -> Build Path -> Confugure Build Path

Libraries 탭 -> Modulepath 클릭 -> add External JARs 버튼 클릭
(그냥 Add JARs 버튼은 같은 프로젝트 내에 JAR파일이 있을 때 클릭한다.)

원하는 JAR 파일을 선택한다.

다 선택했으면 apply and close 버튼을 누른다.

 

myapp_2.java

module myapp_2 {
	requires module_1;
	requires module_2;
}

 

Main.java

package app;

import pack1.A;
import pack2.B;
import pack3.C;
import pack4.D;

public class Main {
	public static void main(String[] args) {
		A a = new A();
		a.method();
		
		B b = new B();
		b.method();
		
		C c = new C();
		c.method();
		
		D d = new D();
		d.method();
	}

}
/*
A-method 실행
B-method 실행
C-method 실행
D-method 실행
*/

필요한 모듈을 import 해서 쓰면 된다.

문제설명

 

소스코드

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
    ios_base::sync_with_stdio(false); //표준 스트림 동기화 해제
    cin.tie(nullptr); //입출력 연결 끊기
    int N, M;
    vector<int> vec;
    cin >> N;
    for (int i = 0; i < N; ++i)
    {
        int input;
        cin >> input;
        vec.push_back(input);
    }
    sort(vec.begin(), vec.end());
    cin >> M;
    for (int i = 0; i < M; ++i)
    {
        int input;
        cin >> input;
        cout << binary_search(vec.begin(), vec.end(), input) << ' ';
    }
}

 

설명

  • 이중 for문을 사용하면 시간초과가 된다.
  • 이진 탐색으로 탐색하기 위해 algorithm 라이브러리의 sort() 함수로 정렬을 해준다.
  • algorithm 라이브러리의 binary_search() 함수로 정렬된 벡터에서 입력받은 값이 있는지 확인한다.
  • 표준 스트림 동기화 해제, 입출력 연결 끊기, endl 대신 '\n'사용 -> 시간초과 방지