#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의 사이즈가 교집합의 개수
}
#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' 사용 -> 시간초과 방지
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다.
리플렉션
리플렉션이란 실행 도중에 타입(클래스, 인터페이스 등)을 검사하고 구성 멤버를 조사하는 것을 말한다.
이클립스 개발환경에서 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; }
}
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))이라서 시간초과가 발생하지 않는다.
#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 버튼을 누른다.
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파일이 있을 때 클릭한다.)
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 실행
*/
#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'사용 -> 시간초과 방지