Compare commits
20 Commits
a51bd5e574
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dfee41a740 | |||
| 09731f464d | |||
| 3db3499d30 | |||
| c1bbaac367 | |||
| f63e3fd52a | |||
| 3fd3f895fc | |||
| 3a8509088f | |||
| 13f2867f99 | |||
| 805816491a | |||
| 51a85cbcfc | |||
| 0efdfb1085 | |||
| 1f2b1607c8 | |||
| e41240ccb2 | |||
| 28cddcb77b | |||
| bd1440e1a5 | |||
| d28485c0c8 | |||
| dfde55f05d | |||
| 46c07638a7 | |||
| 2832fa0b5f | |||
| 1a6ee1f972 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@
|
||||
/build/
|
||||
/**/build/**
|
||||
/test.db
|
||||
/out/
|
||||
|
||||
BIN
MIDI_sample.mid
Normal file
BIN
MIDI_sample.mid
Normal file
Binary file not shown.
13
app.sh
Executable file
13
app.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
case $1 in
|
||||
gitup)
|
||||
echo "Pushing to git"
|
||||
git add -A
|
||||
git commit -m $(date "+%Y-%m-%dT%H:%M:%S")
|
||||
git push origin
|
||||
;;
|
||||
*)
|
||||
echo "Launching editor"
|
||||
code .
|
||||
esac
|
||||
16
batik/build.gradle.kts
Normal file
16
batik/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("elex-java")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-rasterizer
|
||||
implementation("org.apache.xmlgraphics:batik-rasterizer:1.14")
|
||||
|
||||
}
|
||||
51
batik/src/main/java/kr/pe/elex/examples/Sample.java
Normal file
51
batik/src/main/java/kr/pe/elex/examples/Sample.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import org.apache.batik.transcoder.TranscoderException;
|
||||
import org.apache.batik.transcoder.TranscoderInput;
|
||||
import org.apache.batik.transcoder.TranscoderOutput;
|
||||
import org.apache.batik.transcoder.image.PNGTranscoder;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
|
||||
public class Sample {
|
||||
public static void main(String... args) throws IOException, TranscoderException {
|
||||
toBufferedImage();
|
||||
}
|
||||
|
||||
public static void toPNG() throws FileNotFoundException, TranscoderException {
|
||||
PNGTranscoder transcoder = new PNGTranscoder();
|
||||
transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, 64f);
|
||||
transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, 64f);
|
||||
|
||||
TranscoderInput input = new TranscoderInput(Sample.class
|
||||
.getResourceAsStream("/java.svg"));
|
||||
TranscoderOutput output = new TranscoderOutput(
|
||||
new FileOutputStream(new File("out/test_out.png")));
|
||||
transcoder.transcode(input, output);
|
||||
|
||||
}
|
||||
|
||||
public static void toBufferedImage() throws IOException, TranscoderException {
|
||||
PNGTranscoder transcoder = new PNGTranscoder();
|
||||
transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, 64f);
|
||||
transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, 64f);
|
||||
|
||||
TranscoderInput input = new TranscoderInput(Sample.class
|
||||
.getResourceAsStream("/java.svg"));
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
TranscoderOutput output = new TranscoderOutput(os);
|
||||
transcoder.transcode(input, output);
|
||||
|
||||
BufferedImage out = ImageIO.read(new ByteArrayInputStream(os.toByteArray()));
|
||||
ImageIO.write(out, "png", new File("out/test-buffered-image.png"));
|
||||
}
|
||||
}
|
||||
1
batik/src/main/resources/java.svg
Normal file
1
batik/src/main/resources/java.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 192.756 192.756"><g fill-rule="evenodd" clip-rule="evenodd"><path fill="#fff" d="M0 0h192.756v192.756H0V0z"/><path d="M80.372 101.729s-4.604 2.679 3.28 3.584c9.554 1.091 14.434.934 24.959-1.057 0 0 2.771 1.735 6.639 3.236-23.601 10.113-53.413-.585-34.878-5.763zM77.487 88.532s-5.165 3.823 2.726 4.639c10.206 1.054 18.262 1.14 32.211-1.544 0 0 1.926 1.955 4.957 3.023-28.531 8.345-60.307.657-39.894-6.118z" fill="#3174b9"/><path d="M101.797 66.143c5.818 6.697-1.525 12.72-1.525 12.72s14.766-7.621 7.984-17.168c-6.332-8.899-11.189-13.32 15.102-28.566-.001-.001-41.27 10.303-21.561 33.014z" fill="#ca3132"/><path d="M133.01 111.491s3.408 2.81-3.754 4.983c-13.619 4.125-56.694 5.369-68.659.164-4.298-1.872 3.766-4.467 6.303-5.015 2.646-.572 4.156-.468 4.156-.468-4.783-3.368-30.916 6.615-13.272 9.479 48.112 7.801 87.704-3.512 75.226-9.143zM82.587 74.857s-21.908 5.205-7.757 7.097c5.977.799 17.883.615 28.982-.316 9.068-.761 18.17-2.389 18.17-2.389s-3.195 1.371-5.51 2.949c-22.251 5.853-65.229 3.127-52.855-2.856 10.462-5.061 18.97-4.485 18.97-4.485zM121.891 96.824c22.617-11.75 12.16-23.044 4.859-21.522-1.785.373-2.586.695-2.586.695s.666-1.042 1.932-1.49c14.441-5.075 25.545 14.972-4.656 22.911-.001 0 .347-.314.451-.594z" fill="#3174b9"/><path d="M108.256 8.504s12.523 12.531-11.881 31.794c-19.571 15.458-4.462 24.269-.006 34.34-11.426-10.307-19.807-19.382-14.185-27.826 8.254-12.395 31.125-18.406 26.072-38.308z" fill="#ca3132"/><path d="M84.812 128.674c21.706 1.388 55.045-.771 55.836-11.044 0 0-1.518 3.894-17.941 6.983-18.529 3.488-41.386 3.082-54.938.845 0 0 2.777 2.298 17.043 3.216z" fill="#3174b9"/><path d="M139.645 147.096h-.66v-.37h1.781v.37h-.66v1.848h-.461v-1.848zm3.554.092h-.008l-.656 1.755h-.301l-.652-1.755h-.008v1.755h-.438v-2.218h.643l.604 1.569.604-1.569h.637v2.218h-.424v-1.755h-.001zM81.255 167.921c-2.047 1.774-4.211 2.772-6.154 2.772-2.768 0-4.27-1.663-4.27-4.324 0-2.881 1.608-4.989 8.044-4.989h2.379v6.541h.001zm5.65 6.374v-19.732c0-5.043-2.876-8.371-9.809-8.371-4.045 0-7.591.999-10.474 2.272l.83 3.495c2.271-.834 5.207-1.607 8.089-1.607 3.994 0 5.713 1.607 5.713 4.934v2.495h-1.996c-9.702 0-14.08 3.764-14.08 9.423 0 4.876 2.885 7.648 8.316 7.648 3.491 0 6.099-1.441 8.534-3.55l.443 2.993h4.434zM105.762 174.295h-7.045l-8.483-27.601h6.154l5.265 16.961 1.172 5.096c2.656-7.371 4.541-14.854 5.484-22.057h5.984c-1.602 9.088-4.488 19.066-8.531 27.601zM132.799 167.921c-2.053 1.774-4.217 2.772-6.156 2.772-2.768 0-4.268-1.663-4.268-4.324 0-2.881 1.609-4.989 8.041-4.989h2.383v6.541zm5.652 6.374v-19.732c0-5.043-2.885-8.371-9.811-8.371-4.049 0-7.594.999-10.477 2.272l.83 3.495c2.271-.834 5.213-1.607 8.096-1.607 3.988 0 5.709 1.607 5.709 4.934v2.495h-1.996c-9.703 0-14.078 3.764-14.078 9.423 0 4.876 2.879 7.648 8.311 7.648 3.494 0 6.098-1.441 8.539-3.55l.445 2.993h4.432zM58.983 178.985c-1.61 2.353-4.214 4.216-7.061 5.267l-2.79-3.286c2.169-1.113 4.027-2.91 4.892-4.582.745-1.49 1.056-3.406 1.056-7.992v-31.515h6.005v31.08c0 6.134-.49 8.613-2.102 11.028z" fill="#ca3132"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -6,5 +6,5 @@
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("com.github.ben-manes.versions") version "0.39.0"
|
||||
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
|
||||
plugins {
|
||||
id("elex-java")
|
||||
application
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes(mapOf(
|
||||
"Implementation-Title" to project.name,
|
||||
"Implementation-Version" to project.version,
|
||||
"Implementation-Vendor" to "ELEX co.,pte.",
|
||||
"Main-Class" to application.mainClass,
|
||||
"Automatic-Module-Name" to "com.elex_project.${project.name}"
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
java
|
||||
}
|
||||
|
||||
group = "com.elex-project"
|
||||
version = "1.0-SNAPSHOT"
|
||||
description = ""//todo
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = uri("https://repository.elex-project.com/repository/maven")
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
sourceCompatibility = org.gradle.api.JavaVersion.VERSION_11
|
||||
targetCompatibility = org.gradle.api.JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom(annotationProcessor.get())
|
||||
}
|
||||
testCompileOnly {
|
||||
extendsFrom(testAnnotationProcessor.get())
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes(mapOf(
|
||||
"Implementation-Title" to project.name,
|
||||
"Implementation-Version" to project.version,
|
||||
"Implementation-Vendor" to "ELEX co.,pte."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
tasks.compileTestJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
if (JavaVersion.current().isJava9Compatible) {
|
||||
(options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
|
||||
}
|
||||
(options as StandardJavadocDocletOptions).encoding = "UTF-8"
|
||||
(options as StandardJavadocDocletOptions).charSet = "UTF-8"
|
||||
(options as StandardJavadocDocletOptions).docEncoding = "UTF-8"
|
||||
|
||||
}
|
||||
dependencies {
|
||||
//implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
implementation("org.slf4j:slf4j-api:1.7.32")
|
||||
implementation("org.jetbrains:annotations:22.0.0")
|
||||
|
||||
implementation("com.elex-project:abraxas:4.7.1")
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.20")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.20")
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.20")
|
||||
|
||||
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.2")
|
||||
}
|
||||
@@ -1,14 +1,75 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id ("elex-base")
|
||||
java
|
||||
//id("com.github.ben-manes.versions") version "0.39.0"
|
||||
}
|
||||
|
||||
group = "com.elex-project"
|
||||
version = "1.0-SNAPSHOT"
|
||||
description = ""//todo
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = uri(project.findProperty("repo.url") as String)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = org.gradle.api.JavaVersion.VERSION_17
|
||||
targetCompatibility = org.gradle.api.JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom(annotationProcessor.get())
|
||||
}
|
||||
testCompileOnly {
|
||||
extendsFrom(testAnnotationProcessor.get())
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes(mapOf(
|
||||
"Implementation-Title" to project.name,
|
||||
"Implementation-Version" to project.version,
|
||||
"Implementation-Vendor" to "ELEX co.,pte."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
tasks.compileTestJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
if (JavaVersion.current().isJava9Compatible) {
|
||||
(options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
|
||||
}
|
||||
(options as StandardJavadocDocletOptions).encoding = "UTF-8"
|
||||
(options as StandardJavadocDocletOptions).charSet = "UTF-8"
|
||||
(options as StandardJavadocDocletOptions).docEncoding = "UTF-8"
|
||||
|
||||
}
|
||||
dependencies {
|
||||
//implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
implementation("org.slf4j:slf4j-api:2.0.6")
|
||||
implementation("org.jetbrains:annotations:24.0.0")
|
||||
|
||||
implementation("com.elex-project:abraxas:4.13.0")
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.26")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.26")
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.26")
|
||||
|
||||
implementation("ch.qos.logback:logback-classic:1.4.5")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id ("elex-java")
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
from(components["java"])
|
||||
pom {
|
||||
// todo
|
||||
name.set(project.name)
|
||||
description.set(project.description)
|
||||
url.set("https://")
|
||||
inceptionYear.set("2021")
|
||||
properties.set(mapOf(
|
||||
"myProp" to "value",
|
||||
"prop.with.dots" to "anotherValue"
|
||||
))
|
||||
organization {
|
||||
name.set("Elex co.,Pte.")
|
||||
url.set("https://www.elex-project.com/")
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
// todo
|
||||
name.set("BSD 3-Clause License")
|
||||
url.set("licenseUrl")
|
||||
comments.set("")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id.set("elex")
|
||||
name.set("Elex")
|
||||
url.set("https://www.elex.pe.kr/")
|
||||
email.set("developer@elex-project.com")
|
||||
organization.set("Elex Co.,Pte.")
|
||||
organizationUrl.set("https://www.elex-project.com/")
|
||||
roles.set(arrayListOf("Developer", "CEO"))
|
||||
timezone.set("Asia/Seoul")
|
||||
properties.set(mapOf("" to ""))
|
||||
}
|
||||
}
|
||||
contributors {
|
||||
contributor {
|
||||
name.set("")
|
||||
email.set("")
|
||||
url.set("")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
// todo
|
||||
connection.set("scm:git:https://github.com/my-library.git")
|
||||
developerConnection.set("scm:git:https://github.com/my-library.git")
|
||||
url.set("https://github.com/my-library/")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "mavenElex"
|
||||
val urlRelease = uri("https://repository.elex-project.com/repository/maven-releases")
|
||||
val urlSnapshot = uri("https://repository.elex-project.com/repository/maven-snapshots")
|
||||
url = if (version.toString().endsWith("SNAPSHOT")) urlSnapshot else urlRelease
|
||||
// Repository credential, Must be defined in ~/.gradle/gradle.properties
|
||||
credentials {
|
||||
username = project.findProperty("repo.username") as String
|
||||
password = project.findProperty("repo.password") as String
|
||||
}
|
||||
}
|
||||
maven { //todo
|
||||
name = "mavenGithub"
|
||||
url = uri("https://maven.pkg.github.com/elex-project/tmpl-java-library")
|
||||
credentials {
|
||||
username = project.findProperty("github.username") as String
|
||||
password = project.findProperty("github.token") as String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
145
docs/2D 그래픽스.md
Normal file
145
docs/2D 그래픽스.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# **자바 2D 그래픽스 쉽게 배우기**
|
||||
|
||||
## **1. 자바 2D 그래픽스란?**
|
||||
자바에서 2D 그래픽을 그리려면 `java.awt`와 `javax.swing` 패키지를 사용한다.
|
||||
특히 **`Graphics`, `Graphics2D` 클래스**를 활용하면 **선을 긋고, 도형을 그리고, 색상을 채우고, 이미지를 출력할 수 있다.**
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `Graphics` 클래스 (기본 그래픽 처리)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `drawLine(x1, y1, x2, y2)` | (x1, y1) → (x2, y2) 직선 그리기 |
|
||||
| `drawRect(x, y, width, height)` | 사각형 그리기 (테두리만) |
|
||||
| `fillRect(x, y, width, height)` | 사각형 그리기 (채우기) |
|
||||
| `drawOval(x, y, width, height)` | 타원 그리기 (테두리만) |
|
||||
| `fillOval(x, y, width, height)` | 타원 그리기 (채우기) |
|
||||
| `drawPolygon(xPoints, yPoints, nPoints)` | 다각형 그리기 |
|
||||
| `fillPolygon(xPoints, yPoints, nPoints)` | 다각형 채우기 |
|
||||
| `setColor(Color c)` | 그리기 색상 변경 |
|
||||
| `setFont(Font f)` | 글꼴 변경 |
|
||||
| `drawString(String str, x, y)` | 문자열 그리기 |
|
||||
|
||||
**예제 코드 (사각형과 문자열 그리기)**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class MyPanel extends JPanel {
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
g.setColor(Color.BLUE);
|
||||
g.fillRect(50, 50, 100, 100); // 파란색 사각형
|
||||
|
||||
g.setColor(Color.RED);
|
||||
g.drawString("Hello, Graphics!", 60, 180); // 문자열 출력
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame();
|
||||
frame.setSize(300, 300);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.add(new MyPanel());
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **`paintComponent(Graphics g)`를 오버라이드하여 원하는 그래픽을 그릴 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
### **(2) `Graphics2D` 클래스 (고급 그래픽 처리)**
|
||||
`Graphics2D`는 `Graphics`의 확장판으로, **더 정밀한 그래픽을 그릴 수 있다.**
|
||||
`Graphics2D` 객체는 `Graphics`에서 다운캐스팅하여 얻을 수 있다.
|
||||
```java
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
```
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setStroke(Stroke s)` | 선 두께 설정 |
|
||||
| `setRenderingHint(RenderingHints.Key, Object)` | 안티앨리어싱 적용 (부드러운 그래픽) |
|
||||
| `draw(Shape s)` | `Shape` 객체 그리기 |
|
||||
| `fill(Shape s)` | `Shape` 객체 채우기 |
|
||||
| `rotate(theta, x, y)` | (x, y)를 중심으로 회전 |
|
||||
| `translate(x, y)` | (x, y)만큼 이동 |
|
||||
| `scale(sx, sy)` | x, y 방향으로 확대/축소 |
|
||||
|
||||
**예제 코드 (둥근 사각형과 선 두께 조절)**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class MyPanel extends JPanel {
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
|
||||
g2.setStroke(new BasicStroke(5)); // 선 두께 설정
|
||||
g2.setColor(Color.MAGENTA);
|
||||
g2.drawRoundRect(50, 50, 100, 100, 20, 20); // 둥근 사각형
|
||||
|
||||
g2.setColor(Color.GREEN);
|
||||
g2.drawLine(50, 200, 200, 200); // 굵은 선
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame();
|
||||
frame.setSize(300, 300);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.add(new MyPanel());
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **`Graphics2D`를 사용하면 더 정밀한 그래픽 조작이 가능하다!**
|
||||
|
||||
---
|
||||
|
||||
### **(3) `Color`와 `Font` 클래스 (색상과 글꼴 설정)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `new Color(r, g, b)` | RGB 값으로 색상 생성 |
|
||||
| `setColor(Color c)` | 현재 색상 변경 |
|
||||
| `setFont(Font f)` | 글꼴 설정 |
|
||||
| `new Font("FontName", style, size)` | 글꼴 생성 |
|
||||
|
||||
**예제 코드 (다양한 색상과 글꼴 적용)**
|
||||
```java
|
||||
g.setColor(new Color(255, 100, 100)); // 연한 빨간색
|
||||
g.fillOval(50, 50, 100, 100);
|
||||
|
||||
g.setColor(Color.BLUE);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 20));
|
||||
g.drawString("Hello!", 70, 180);
|
||||
```
|
||||
✅ **`Color`와 `Font`를 조합하면 더 다양한 표현이 가능하다!**
|
||||
|
||||
---
|
||||
|
||||
### **(4) `Image` 클래스 (이미지 출력)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `Image img = Toolkit.getDefaultToolkit().getImage("image.png")` | 이미지 로드 |
|
||||
| `drawImage(img, x, y, this)` | 이미지 그리기 |
|
||||
|
||||
**예제 코드 (이미지 출력)**
|
||||
```java
|
||||
Image img = Toolkit.getDefaultToolkit().getImage("image.png");
|
||||
g.drawImage(img, 50, 50, this);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **3. 정리**
|
||||
✅ **기본 도형을 그리려면 `Graphics` 클래스 사용! (`drawLine()`, `drawRect()` 등)**
|
||||
✅ **더 정밀한 그래픽을 원하면 `Graphics2D` 사용! (`setStroke()`, `rotate()` 등)**
|
||||
✅ **색상은 `Color`, 글꼴은 `Font` 클래스로 설정 가능!**
|
||||
✅ **이미지 출력도 가능! (`drawImage()`)**
|
||||
|
||||
자바의 2D 그래픽을 활용하면 **간단한 그림부터 UI 요소, 차트, 애니메이션까지 다양한 그래픽을 만들 수 있다!**
|
||||
536
docs/BitSet.md
Normal file
536
docs/BitSet.md
Normal file
@@ -0,0 +1,536 @@
|
||||
# **BitSet 클래스란?**
|
||||
|
||||
`BitSet`은 **비트(bit) 단위의 데이터를 효율적으로 저장하고 조작할 수 있도록 설계된 클래스**이다.
|
||||
기본적인 `boolean[]` 배열과 유사하지만, **각 요소를 1비트로 저장**하기 때문에 **메모리를 훨씬 적게 사용**한다.
|
||||
|
||||
이 클래스는 **대량의 불리언 데이터를 다룰 때** 유용하며, **비트 연산을 활용하는 알고리즘**(예: 비트마스크, 소수 판별, 집합 연산 등)에서도 사용된다.
|
||||
|
||||
---
|
||||
|
||||
## **1. BitSet의 주요 특징**
|
||||
✔ **1비트 단위로 저장** → `boolean[]`보다 메모리 절약
|
||||
✔ **자동 확장 가능** → 크기를 명시할 필요 없음
|
||||
✔ **비트 연산 지원** → AND, OR, XOR 등 논리 연산 가능
|
||||
✔ **빠른 검색** → 특정 비트의 ON/OFF를 빠르게 확인 가능
|
||||
|
||||
---
|
||||
|
||||
## **2. BitSet의 주요 메서드 정리**
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `BitSet()` | 빈 `BitSet` 생성 (기본 크기 64비트) |
|
||||
| `BitSet(int nbits)` | 최소 `nbits` 크기의 `BitSet` 생성 |
|
||||
| `set(int index)` | 특정 위치의 비트를 `1(true)`로 설정 |
|
||||
| `set(int fromIndex, int toIndex)` | 특정 범위의 비트를 `1(true)`로 설정 |
|
||||
| `clear(int index)` | 특정 위치의 비트를 `0(false)`로 설정 |
|
||||
| `clear(int fromIndex, int toIndex)` | 특정 범위의 비트를 `0(false)`로 설정 |
|
||||
| `flip(int index)` | 특정 위치의 비트를 반전 |
|
||||
| `flip(int fromIndex, int toIndex)` | 특정 범위의 비트를 반전 |
|
||||
| `get(int index)` | 특정 위치의 비트 값을 반환 (`true/false`) |
|
||||
| `get(int fromIndex, int toIndex)` | 특정 범위의 비트 값을 `BitSet`으로 반환 |
|
||||
| `and(BitSet set)` | AND 연산 수행 |
|
||||
| `or(BitSet set)` | OR 연산 수행 |
|
||||
| `xor(BitSet set)` | XOR 연산 수행 |
|
||||
| `cardinality()` | `1(true)`로 설정된 비트의 개수 반환 |
|
||||
| `length()` | 가장 높은 `1(true)` 비트의 인덱스 + 1 반환 |
|
||||
| `size()` | 내부 비트 배열의 전체 크기 반환 |
|
||||
| `isEmpty()` | 모든 비트가 `0(false)`인지 확인 |
|
||||
| `nextSetBit(int index)` | `index` 이후 첫 번째 `1(true)` 비트의 위치 반환 |
|
||||
| `nextClearBit(int index)` | `index` 이후 첫 번째 `0(false)` 비트의 위치 반환 |
|
||||
|
||||
---
|
||||
|
||||
## **3. BitSet 사용 예제**
|
||||
|
||||
### **(1) 기본적인 비트 설정 및 조회**
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetExample {
|
||||
public static void main(String[] args) {
|
||||
BitSet bitSet = new BitSet(); // 기본 크기 64비트
|
||||
|
||||
bitSet.set(0); // 0번째 비트 ON
|
||||
bitSet.set(2); // 2번째 비트 ON
|
||||
bitSet.set(4); // 4번째 비트 ON
|
||||
|
||||
System.out.println("BitSet: " + bitSet); // {0, 2, 4}
|
||||
|
||||
// 특정 비트 확인
|
||||
System.out.println("0번째 비트: " + bitSet.get(0)); // true
|
||||
System.out.println("1번째 비트: " + bitSet.get(1)); // false
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ `BitSet`을 출력하면 **1(true)인 비트의 위치만 표시**된다.
|
||||
|
||||
---
|
||||
|
||||
### **(2) `flip()`을 활용한 비트 반전**
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetFlipExample {
|
||||
public static void main(String[] args) {
|
||||
BitSet bitSet = new BitSet();
|
||||
bitSet.set(1);
|
||||
bitSet.set(3);
|
||||
bitSet.set(5);
|
||||
System.out.println("초기 BitSet: " + bitSet); // {1, 3, 5}
|
||||
|
||||
bitSet.flip(0, 6); // 0~5까지 비트 반전
|
||||
System.out.println("flip(0,6) 후: " + bitSet); // {0, 2, 4}
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ `flip()`을 사용하면 **지정된 범위의 비트가 반전**된다.
|
||||
|
||||
---
|
||||
|
||||
### **(3) 비트 연산 (AND, OR, XOR)**
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetOperations {
|
||||
public static void main(String[] args) {
|
||||
BitSet set1 = new BitSet();
|
||||
BitSet set2 = new BitSet();
|
||||
|
||||
set1.set(0);
|
||||
set1.set(1);
|
||||
set1.set(2);
|
||||
System.out.println("Set1: " + set1); // {0, 1, 2}
|
||||
|
||||
set2.set(1);
|
||||
set2.set(2);
|
||||
set2.set(3);
|
||||
System.out.println("Set2: " + set2); // {1, 2, 3}
|
||||
|
||||
BitSet andSet = (BitSet) set1.clone();
|
||||
andSet.and(set2);
|
||||
System.out.println("AND 연산: " + andSet); // {1, 2}
|
||||
|
||||
BitSet orSet = (BitSet) set1.clone();
|
||||
orSet.or(set2);
|
||||
System.out.println("OR 연산: " + orSet); // {0, 1, 2, 3}
|
||||
|
||||
BitSet xorSet = (BitSet) set1.clone();
|
||||
xorSet.xor(set2);
|
||||
System.out.println("XOR 연산: " + xorSet); // {0, 3}
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ **AND 연산:** `1(true)`인 비트만 유지
|
||||
✔ **OR 연산:** 하나라도 `1(true)`이면 유지
|
||||
✔ **XOR 연산:** `1(true)`가 서로 다르면 유지
|
||||
|
||||
---
|
||||
|
||||
### **(4) `cardinality()`, `nextSetBit()`, `nextClearBit()` 활용**
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetMethods {
|
||||
public static void main(String[] args) {
|
||||
BitSet bitSet = new BitSet();
|
||||
bitSet.set(1);
|
||||
bitSet.set(3);
|
||||
bitSet.set(5);
|
||||
|
||||
System.out.println("BitSet: " + bitSet); // {1, 3, 5}
|
||||
System.out.println("1의 개수: " + bitSet.cardinality()); // 3
|
||||
|
||||
System.out.println("다음 1(true) 비트 위치: " + bitSet.nextSetBit(0)); // 1
|
||||
System.out.println("다음 0(false) 비트 위치: " + bitSet.nextClearBit(0)); // 0
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ `cardinality()`는 `1(true)`의 개수를 반환
|
||||
✔ `nextSetBit()`는 **다음 `1(true)`인 비트 위치** 반환
|
||||
✔ `nextClearBit()`는 **다음 `0(false)`인 비트 위치** 반환
|
||||
|
||||
---
|
||||
|
||||
## **4. BitSet vs. boolean 배열**
|
||||
| 비교 항목 | `BitSet` | `boolean[]` |
|
||||
|----------|---------|------------|
|
||||
| 메모리 사용 | **1비트(압축됨)** | 1바이트(8배 큼) |
|
||||
| 크기 조정 | **자동 확장** | **고정 크기** |
|
||||
| 비트 연산 | AND, OR, XOR 지원 | 직접 구현 필요 |
|
||||
| 성능 | 비트 연산이 빠름 | 단순한 경우 유리 |
|
||||
|
||||
✔ `BitSet`은 **메모리를 절약하면서도 대량의 비트 연산을 효율적으로 수행**할 수 있다.
|
||||
✔ 다만, **단순한 배열이 필요하다면 `boolean[]`이 더 직관적**일 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## **5. 결론**
|
||||
`BitSet`은 **대량의 불리언 데이터를 비트 단위로 저장하고 처리하는 데 유용**하며,
|
||||
비트 연산이 필요한 **비트마스크, 소수 판별, 집합 연산, 압축 데이터 처리** 등에 많이 활용된다.
|
||||
단순한 `boolean[]`보다 **메모리를 절약**하면서도 **연산 속도가 빠르다**는 점이 강점이다.
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### `BitSet` 클래스의 메서드 정리 및 설명
|
||||
|
||||
자바의 `BitSet` 클래스는 `java.util` 패키지에 포함된 클래스로, 비트 단위로 데이터를 효율적으로 관리하기 위한 자료 구조입니다. 이는 이진수 데이터를 다룰 때 메모리를 절약하며, 비트 연산(AND, OR, XOR 등)을 쉽게 수행할 수 있도록 설계되었습니다. `BitSet`은 가변 크기의 비트 배열로 동작하며, 인덱스는 0부터 시작합니다.
|
||||
|
||||
아래에서 `BitSet`의 주요 메서드를 표로 정리하고, 각 메서드의 사용법과 예시를 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### `BitSet` 클래스 메서드 표
|
||||
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|------------------------------------|-------------|------------------------------------------------------------------------------------------|
|
||||
| `BitSet()` | - | 빈 `BitSet` 객체를 생성 (기본 크기 64비트) |
|
||||
| `BitSet(int nbits)` | - | 지정된 초기 크기(`nbits`)로 `BitSet` 생성 |
|
||||
| `set(int bitIndex)` | `void` | 지정된 인덱스의 비트를 `true`(1)로 설정 |
|
||||
| `set(int bitIndex, boolean value)`| `void` | 지정된 인덱스의 비트를 주어진 값(`true` 또는 `false`)으로 설정 |
|
||||
| `set(int fromIndex, int toIndex)` | `void` | 지정된 범위(`fromIndex`부터 `toIndex-1`까지)의 비트를 `true`로 설정 |
|
||||
| `clear(int bitIndex)` | `void` | 지정된 인덱스의 비트를 `false`(0)로 설정 |
|
||||
| `clear(int fromIndex, int toIndex)` | `void` | 지정된 범위의 비트를 `false`로 설정 |
|
||||
| `clear()` | `void` | 모든 비트를 `false`로 설정 |
|
||||
| `get(int bitIndex)` | `boolean` | 지정된 인덱스의 비트 값을 반환 |
|
||||
| `get(int fromIndex, int toIndex)` | `BitSet` | 지정된 범위의 비트를 포함하는 새로운 `BitSet` 반환 |
|
||||
| `flip(int bitIndex)` | `void` | 지정된 인덱스의 비트를 반전 (`true` ↔ `false`) |
|
||||
| `flip(int fromIndex, int toIndex)`| `void` | 지정된 범위의 비트를 반전 |
|
||||
| `and(BitSet set)` | `void` | 현재 `BitSet`과 주어진 `BitSet` 간 비트 단위 AND 연산 수행 |
|
||||
| `or(BitSet set)` | `void` | 현재 `BitSet`과 주어진 `BitSet` 간 비트 단위 OR 연산 수행 |
|
||||
| `xor(BitSet set)` | `void` | 현재 `BitSet`과 주어진 `BitSet` 간 비트 단위 XOR 연산 수행 |
|
||||
| `andNot(BitSet set)` | `void` | 현재 `BitSet`에서 주어진 `BitSet`의 비트를 제거 (AND NOT 연산) |
|
||||
| `cardinality()` | `int` | `true`인 비트의 개수 반환 |
|
||||
| `length()` | `int` | 마지막 `true` 비트의 인덱스 + 1 반환 (논리적 크기) |
|
||||
| `size()` | `int` | 내부적으로 할당된 비트 수 반환 (물리적 크기, 64의 배수) |
|
||||
| `isEmpty()` | `boolean` | 모든 비트가 `false`인지 확인 |
|
||||
| `intersects(BitSet set)` | `boolean` | 주어진 `BitSet`과 공통된 `true` 비트가 있는지 확인 |
|
||||
| `nextSetBit(int fromIndex)` | `int` | 지정된 인덱스 이후 첫 번째 `true` 비트의 인덱스 반환, 없으면 -1 |
|
||||
| `nextClearBit(int fromIndex)` | `int` | 지정된 인덱스 이후 첫 번째 `false` 비트의 인덱스 반환 |
|
||||
| `previousSetBit(int fromIndex)` | `int` | 지정된 인덱스 이전의 마지막 `true` 비트의 인덱스 반환, 없으면 -1 |
|
||||
| `previousClearBit(int fromIndex)` | `int` | 지정된 인덱스 이전의 마지막 `false` 비트의 인덱스 반환 |
|
||||
| `toByteArray()` | `byte[]` | `BitSet`을 바이트 배열로 변환 |
|
||||
| `toLongArray()` | `long[]` | `BitSet`을 `long` 배열로 변환 |
|
||||
| `valueOf(byte[] bytes)` | `BitSet` | 바이트 배열에서 `BitSet` 생성 (정적 메서드) |
|
||||
| `valueOf(long[] longs)` | `BitSet` | `long` 배열에서 `BitSet` 생성 (정적 메서드) |
|
||||
| `clone()` | `Object` | `BitSet`의 복사본 반환 |
|
||||
| `equals(Object obj)` | `boolean` | 주어진 객체와 동일한지 비교 |
|
||||
| `toString()` | `String` | `true` 비트의 인덱스를 포함한 문자열 반환 (예: "{0, 2, 4}") |
|
||||
|
||||
---
|
||||
|
||||
### `BitSet` 설명 및 예시
|
||||
|
||||
`BitSet`은 비트 단위로 데이터를 표현하므로 메모리 효율성이 높고, 비트 연산이 필요한 경우 유용합니다. 주요 사용 사례는 플래그 관리, 집합 표현, 데이터 압축 등입니다. 아래에서 주요 메서드의 사용법을 예시로 설명합니다.
|
||||
|
||||
#### 1. 비트 설정 및 확인
|
||||
비트를 설정하고 값을 확인합니다.
|
||||
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetBasicExample {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits = new BitSet();
|
||||
bits.set(0); // 0번 비트 = true
|
||||
bits.set(2); // 2번 비트 = true
|
||||
bits.set(4, 6); // 4~5번 비트 = true
|
||||
|
||||
System.out.println("Bit at 0: " + bits.get(0)); // true
|
||||
System.out.println("Bit at 1: " + bits.get(1)); // false
|
||||
System.out.println("Bits: " + bits); // {0, 2, 4, 5}
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**:
|
||||
```
|
||||
Bit at 0: true
|
||||
Bit at 1: false
|
||||
Bits: {0, 2, 4, 5}
|
||||
```
|
||||
|
||||
#### 2. 비트 연산
|
||||
`and`, `or`, `xor` 연산을 수행합니다.
|
||||
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetOperationExample {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits1 = new BitSet();
|
||||
bits1.set(0);
|
||||
bits1.set(1);
|
||||
BitSet bits2 = new BitSet();
|
||||
bits2.set(1);
|
||||
bits2.set(2);
|
||||
|
||||
BitSet andResult = (BitSet) bits1.clone();
|
||||
andResult.and(bits2);
|
||||
System.out.println("AND: " + andResult); // {1}
|
||||
|
||||
BitSet orResult = (BitSet) bits1.clone();
|
||||
orResult.or(bits2);
|
||||
System.out.println("OR: " + orResult); // {0, 1, 2}
|
||||
|
||||
BitSet xorResult = (BitSet) bits1.clone();
|
||||
xorResult.xor(bits2);
|
||||
System.out.println("XOR: " + xorResult); // {0, 2}
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**:
|
||||
```
|
||||
AND: {1}
|
||||
OR: {0, 1, 2}
|
||||
XOR: {0, 2}
|
||||
```
|
||||
|
||||
#### 3. 비트 탐색
|
||||
다음 `true` 또는 `false` 비트를 찾습니다.
|
||||
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetSearchExample {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits = new BitSet();
|
||||
bits.set(2);
|
||||
bits.set(5);
|
||||
|
||||
int nextSet = bits.nextSetBit(0); // 2
|
||||
int nextClear = bits.nextClearBit(2); // 3
|
||||
System.out.println("Next set bit from 0: " + nextSet);
|
||||
System.out.println("Next clear bit from 2: " + nextClear);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**:
|
||||
```
|
||||
Next set bit from 0: 2
|
||||
Next clear bit from 2: 3
|
||||
```
|
||||
|
||||
#### 4. 바이트 배열 변환
|
||||
`BitSet`을 바이트 배열로 변환하고 역변환합니다.
|
||||
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetToByteExample {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits = new BitSet();
|
||||
bits.set(0);
|
||||
bits.set(3);
|
||||
bits.set(7);
|
||||
|
||||
byte[] byteArray = bits.toByteArray();
|
||||
System.out.print("Byte array: ");
|
||||
for (byte b : byteArray) {
|
||||
System.out.printf("%02X ", b); // 09 80 (00001001 10000000)
|
||||
}
|
||||
System.out.println();
|
||||
|
||||
BitSet fromBytes = BitSet.valueOf(byteArray);
|
||||
System.out.println("From bytes: " + fromBytes); // {0, 3, 7}
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**:
|
||||
```
|
||||
Byte array: 09 80
|
||||
From bytes: {0, 3, 7}
|
||||
```
|
||||
|
||||
#### 5. 상태 확인
|
||||
`BitSet`의 크기와 개수를 확인합니다.
|
||||
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetStatusExample {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits = new BitSet();
|
||||
bits.set(2);
|
||||
bits.set(5);
|
||||
|
||||
System.out.println("Cardinality: " + bits.cardinality()); // 2
|
||||
System.out.println("Length: " + bits.length()); // 6
|
||||
System.out.println("Size: " + bits.size()); // 64
|
||||
System.out.println("Is empty: " + bits.isEmpty()); // false
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**:
|
||||
```
|
||||
Cardinality: 2
|
||||
Length: 6
|
||||
Size: 64
|
||||
Is empty: false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `BitSet`의 특징과 주의점
|
||||
- **장점**:
|
||||
- 메모리 효율성: `true` 비트만 저장하며, 필요한 만큼 동적으로 확장.
|
||||
- 비트 연산 지원: 집합 연산(합집합, 교집합 등)을 쉽게 구현.
|
||||
- **주의점**:
|
||||
- 인덱스 음수 불가: `IllegalArgumentException` 발생.
|
||||
- 동기화 없음: 멀티스레드 환경에서 `Collections.synchronizedSet`으로 감싸야 함.
|
||||
- 크기: `size()`는 물리적 크기(64비트 단위), `length()`는 논리적 크기.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
`BitSet`은 비트 단위 작업을 효율적으로 처리하며, 설정(`set`), 확인(`get`), 연산(`and`, `or`, `xor`), 변환(`toByteArray`) 등 다양한 기능을 제공합니다. 위 예시를 통해 비트 관리, 집합 연산, 데이터 변환 등에 활용해 보세요! 필요 시 크기 조정이나 동기화 처리를 추가해 사용하면 더욱 강력한 도구가 됩니다.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
### `BitSet`의 인덱스 순서에 대한 설명
|
||||
|
||||
자바의 `BitSet` 클래스는 비트 단위로 데이터를 관리하는 자료 구조로, 각 비트는 0부터 시작하는 정수 인덱스에 의해 식별됩니다. 이 인덱스 순서는 비트의 위치를 나타내며, `BitSet`의 동작 방식과 데이터 표현에 중요한 영향을 미칩니다. 아래에서 `BitSet`의 인덱스 순서에 대해 자세히 설명하고, 예시를 통해 이해를 돕겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### 1. **인덱스 순서의 기본 개념**
|
||||
- **`BitSet`의 인덱스**: 0부터 시작하는 정수로, 비트의 위치를 나타냅니다.
|
||||
- 인덱스 0은 가장 낮은 비트(least significant bit, LSB)를 의미하며, 인덱스가 증가할수록 더 높은 비트(most significant bit, MSB)로 이동합니다.
|
||||
- **순서**: 논리적으로 왼쪽에서 오른쪽으로 증가하는 순서가 아니라, 내부적으로는 오른쪽에서 왼쪽으로 비트가 배치됩니다. 하지만 사용자가 접근할 때는 인덱스 0이 시작점으로 간주됩니다.
|
||||
- **크기**: `BitSet`은 필요에 따라 동적으로 확장되며, 초기 크기는 64비트 단위로 할당됩니다.
|
||||
|
||||
#### 비트와 인덱스 매핑 예시
|
||||
- 8비트 기준으로 생각하면:
|
||||
```
|
||||
인덱스: 7 6 5 4 3 2 1 0
|
||||
비트: [0, 0, 0, 0, 1, 0, 1, 0] // 10진수 10
|
||||
```
|
||||
- 인덱스 1이 `true`이고, 인덱스 3이 `true`이면 값은 `00001010` (10진수로 10).
|
||||
|
||||
---
|
||||
|
||||
### 2. **인덱스 순서와 메서드 동작**
|
||||
`BitSet`의 메서드는 이 인덱스 순서를 기반으로 동작합니다. 주요 메서드와 인덱스 순서의 관계를 살펴보겠습니다.
|
||||
|
||||
#### `set(int bitIndex)`와 `get(int bitIndex)`
|
||||
- `set(3)`은 인덱스 3의 비트를 `true`로 설정하며, 이는 오른쪽에서 네 번째 비트에 해당합니다.
|
||||
- `get(3)`은 인덱스 3의 비트 값을 반환합니다.
|
||||
|
||||
#### `nextSetBit(int fromIndex)`와 `previousSetBit(int fromIndex)`
|
||||
- `nextSetBit(0)`은 인덱스 0부터 시작해 첫 번째 `true` 비트의 인덱스를 반환합니다. 순서는 낮은 인덱스에서 높은 인덱스로 진행.
|
||||
- `previousSetBit(5)`는 인덱스 5 이전의 마지막 `true` 비트의 인덱스를 반환하며, 높은 인덱스에서 낮은 인덱스로 역방향 탐색.
|
||||
|
||||
#### `toByteArray()`와 바이트 순서
|
||||
- `BitSet`을 바이트 배열로 변환할 때, 낮은 인덱스가 낮은 바이트에 먼저 매핑됩니다.
|
||||
- 예: 인덱스 0~7이 첫 번째 바이트, 8~15가 두 번째 바이트로 변환.
|
||||
- 바이트 내부에서는 비트가 LSB에서 MSB로 배치됩니다 (즉, 인덱스 0이 바이트의 최하위 비트).
|
||||
|
||||
---
|
||||
|
||||
### 3. **인덱스 순서 예시**
|
||||
#### 예시 1: 비트 설정과 확인
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetIndexOrder {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits = new BitSet();
|
||||
bits.set(0); // 인덱스 0 = true
|
||||
bits.set(3); // 인덱스 3 = true
|
||||
bits.set(7); // 인덱스 7 = true
|
||||
|
||||
System.out.println("Bits: " + bits); // {0, 3, 7}
|
||||
System.out.println("Bit at 0: " + bits.get(0)); // true
|
||||
System.out.println("Bit at 1: " + bits.get(1)); // false
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**:
|
||||
```
|
||||
Bits: {0, 3, 7}
|
||||
Bit at 0: true
|
||||
Bit at 1: false
|
||||
```
|
||||
- **설명**: 인덱스 0, 3, 7이 `true`로 설정되며, 이는 8비트 기준으로 `10001001` (10진수 137)과 같습니다.
|
||||
|
||||
#### 예시 2: 바이트 배열 변환
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetToByteOrder {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits = new BitSet();
|
||||
bits.set(0); // 00000001
|
||||
bits.set(3); // 00001000
|
||||
bits.set(7); // 10000000
|
||||
|
||||
byte[] bytes = bits.toByteArray();
|
||||
for (byte b : bytes) {
|
||||
System.out.printf("%02X ", b); // 09 (00001001)
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**: `09`
|
||||
- **설명**:
|
||||
- 인덱스 0~7이 첫 번째 바이트로 변환.
|
||||
- `00001001` (인덱스 7이 MSB, 인덱스 0이 LSB) = 16진수 `09`.
|
||||
|
||||
#### 예시 3: 비트 탐색
|
||||
```java
|
||||
import java.util.BitSet;
|
||||
|
||||
public class BitSetSearchOrder {
|
||||
public static void main(String[] args) {
|
||||
BitSet bits = new BitSet();
|
||||
bits.set(2);
|
||||
bits.set(5);
|
||||
bits.set(10);
|
||||
|
||||
int next = bits.nextSetBit(0); // 2
|
||||
int nextFrom3 = bits.nextSetBit(3); // 5
|
||||
int prevFrom10 = bits.previousSetBit(10); // 10
|
||||
|
||||
System.out.println("Next set from 0: " + next);
|
||||
System.out.println("Next set from 3: " + nextFrom3);
|
||||
System.out.println("Previous set from 10: " + prevFrom10);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력**:
|
||||
```
|
||||
Next set from 0: 2
|
||||
Next set from 3: 5
|
||||
Previous set from 10: 10
|
||||
```
|
||||
- **설명**: 인덱스 순서에 따라 낮은 인덱스에서 높은 인덱스로(또는 그 반대로) 탐색.
|
||||
|
||||
---
|
||||
|
||||
### 4. **인덱스 순서와 바이트 순서의 관계**
|
||||
- **내부 표현**: `BitSet`은 `long[]` 배열로 비트를 저장하며, 각 `long`은 64비트를 표현합니다.
|
||||
- 첫 번째 `long`은 인덱스 0~63, 두 번째는 64~127을 다룹니다.
|
||||
- 바이트로 변환 시, 낮은 인덱스가 낮은 바이트에 매핑되며, 바이트 내에서는 LSB에서 MSB로 진행.
|
||||
- **예**: 인덱스 0~15 설정 시:
|
||||
```
|
||||
BitSet: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
|
||||
Byte array: FF FF (11111111 11111111)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. **주의점**
|
||||
- **음수 인덱스**: `BitSet`은 음수 인덱스를 허용하지 않으며, `IllegalArgumentException` 발생.
|
||||
- **바이트 순서**: `toByteArray()` 결과는 little-endian 스타일로, 인덱스 0이 첫 바이트의 LSB에 매핑.
|
||||
- **논리적 vs 물리적 크기**: `length()`는 마지막 `true` 비트의 인덱스 + 1, `size()`는 할당된 비트 수(64의 배수).
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
`BitSet`의 인덱스 순서는 0부터 시작하며, 낮은 인덱스가 LSB, 높은 인덱스가 MSB에 해당합니다. 메서드는 이 순서를 기반으로 비트를 설정, 확인, 연산하며, 바이트 변환 시에도 인덱스 순서가 반영됩니다. 위 예시를 통해 인덱스 순서가 데이터 표현과 탐색에 어떻게 적용되는지 확인할 수 있습니다. 비트 단위 작업 시 이 순서를 이해하면 `BitSet`을 더 효과적으로 활용할 수 있습니다!
|
||||
156
docs/ByteBuffer.md
Normal file
156
docs/ByteBuffer.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# **ByteBuffer란?**
|
||||
|
||||
`ByteBuffer`는 Java NIO(Non-blocking I/O)에서 **바이트 데이터를 효율적으로 다룰 수 있도록 제공하는 클래스**이다.
|
||||
기존의 `InputStream` 및 `OutputStream` 기반의 **blocking I/O**와 달리, **비동기적(Non-blocking)**으로 데이터를 처리할 수 있는 구조를 제공한다.
|
||||
|
||||
이 클래스는 **버퍼(Buffer)**라는 개념을 사용하여 데이터를 읽고 쓸 때 **직접 메모리에서 조작**할 수 있도록 하며, 성능 최적화에 유리하다.
|
||||
특히 **파일 I/O, 네트워크 전송, 바이너리 데이터 처리** 등에서 활용된다.
|
||||
|
||||
---
|
||||
|
||||
## **1. ByteBuffer의 주요 특징**
|
||||
✔ **고정된 크기의 버퍼**를 사용하여 데이터를 읽고 쓴다.
|
||||
✔ **Direct Buffer(직접 버퍼)와 Heap Buffer(힙 버퍼)** 두 가지 방식이 있다.
|
||||
✔ **데이터를 읽고 쓰는 포인터(위치, 한계, 용량)가 존재**하여 데이터 흐름을 효율적으로 관리한다.
|
||||
✔ **채널(Channel)과 함께 사용하여 고성능 I/O 처리**가 가능하다.
|
||||
|
||||
---
|
||||
|
||||
## **2. ByteBuffer의 구조**
|
||||
`ByteBuffer`는 데이터를 저장하는 **고정된 크기의 메모리 공간**을 가지며, 다음과 같은 **3가지 중요한 속성**을 갖는다.
|
||||
|
||||
| 속성 | 설명 |
|
||||
|------|------|
|
||||
| **position** | 현재 읽거나 쓸 위치 (0부터 시작) |
|
||||
| **limit** | 읽거나 쓸 수 있는 최대 위치 |
|
||||
| **capacity** | 버퍼의 전체 크기 (변경 불가) |
|
||||
|
||||
### **🔹 주요 속성 변화 예시**
|
||||
```java
|
||||
ByteBuffer buffer = ByteBuffer.allocate(10);
|
||||
System.out.println("초기 상태: " + buffer);
|
||||
// 출력: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
|
||||
```
|
||||
위 상태에서 데이터를 `put()`하면 `position`이 증가하고, `flip()`을 호출하면 `position`이 0으로 초기화되어 데이터를 `get()`으로 읽을 준비를 한다.
|
||||
|
||||
---
|
||||
|
||||
## **3. ByteBuffer의 주요 메서드 정리**
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `allocate(int capacity)` | 힙 메모리에 지정한 크기의 버퍼 생성 |
|
||||
| `allocateDirect(int capacity)` | OS의 직접 메모리에 버퍼 생성 (성능 최적화) |
|
||||
| `put(byte b)` | 한 바이트 추가 |
|
||||
| `put(byte[] src)` | 바이트 배열을 버퍼에 추가 |
|
||||
| `get()` | 현재 위치의 바이트를 가져오고 `position`을 증가 |
|
||||
| `get(byte[] dst)` | 데이터를 바이트 배열로 가져옴 |
|
||||
| `flip()` | **쓰기 모드 → 읽기 모드** 변경 (`position=0`, `limit=현재 position`) |
|
||||
| `clear()` | 버퍼를 초기화 (`position=0`, `limit=capacity`) |
|
||||
| `rewind()` | **읽기 모드에서 처음부터 다시 읽기** (`position=0`, `limit` 유지) |
|
||||
| `compact()` | 아직 읽지 않은 데이터는 유지한 채, 새로운 데이터를 쓸 수 있도록 `position`을 조정 |
|
||||
| `mark()` | 현재 `position`을 저장 |
|
||||
| `reset()` | 저장한 `mark` 위치로 `position`을 되돌림 |
|
||||
|
||||
---
|
||||
|
||||
## **4. ByteBuffer 사용 예제**
|
||||
|
||||
### **(1) ByteBuffer 생성과 데이터 추가 (`put()`, `flip()`, `get()`)**
|
||||
```java
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteBufferExample {
|
||||
public static void main(String[] args) {
|
||||
// 10바이트 크기의 버퍼 생성
|
||||
ByteBuffer buffer = ByteBuffer.allocate(10);
|
||||
|
||||
// 데이터 추가 (쓰기 모드)
|
||||
buffer.put((byte) 1);
|
||||
buffer.put((byte) 2);
|
||||
buffer.put((byte) 3);
|
||||
System.out.println("데이터 추가 후: " + buffer);
|
||||
|
||||
// 읽기 모드로 변경 (flip)
|
||||
buffer.flip();
|
||||
System.out.println("flip() 후: " + buffer);
|
||||
|
||||
// 데이터 읽기
|
||||
System.out.println("읽은 데이터: " + buffer.get()); // 1
|
||||
System.out.println("읽은 데이터: " + buffer.get()); // 2
|
||||
System.out.println("get() 후: " + buffer);
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ `flip()`을 호출하면 `position`이 0이 되어 데이터를 읽을 준비를 한다.
|
||||
✔ `get()`을 호출할 때마다 `position`이 증가하여 다음 데이터를 읽을 수 있다.
|
||||
|
||||
---
|
||||
|
||||
### **(2) `compact()`와 `clear()` 차이점**
|
||||
```java
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteBufferCompactClearExample {
|
||||
public static void main(String[] args) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(10);
|
||||
buffer.put((byte) 10);
|
||||
buffer.put((byte) 20);
|
||||
buffer.put((byte) 30);
|
||||
|
||||
buffer.flip(); // 읽기 모드 전환
|
||||
System.out.println("flip() 후: " + buffer);
|
||||
|
||||
System.out.println("읽은 데이터: " + buffer.get()); // 10
|
||||
System.out.println("읽은 데이터: " + buffer.get()); // 20
|
||||
|
||||
buffer.compact(); // 읽지 않은 데이터 유지한 채 쓰기 모드 변경
|
||||
System.out.println("compact() 후: " + buffer);
|
||||
|
||||
buffer.clear(); // 완전히 초기화
|
||||
System.out.println("clear() 후: " + buffer);
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ **`compact()`는 읽지 않은 데이터를 유지하고 `position`을 이동**
|
||||
✔ **`clear()`는 전체를 초기화하고 `position=0`으로 변경**
|
||||
|
||||
---
|
||||
|
||||
### **(3) DirectBuffer 사용 (`allocateDirect()`)**
|
||||
```java
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class DirectByteBufferExample {
|
||||
public static void main(String[] args) {
|
||||
// DirectBuffer 생성
|
||||
ByteBuffer directBuffer = ByteBuffer.allocateDirect(10);
|
||||
|
||||
directBuffer.put((byte) 42);
|
||||
directBuffer.flip();
|
||||
|
||||
System.out.println("DirectBuffer 데이터: " + directBuffer.get()); // 42
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ `allocateDirect()`를 사용하면 **JVM의 힙 메모리가 아닌 OS의 직접 메모리에 할당**하여 성능을 높일 수 있다.
|
||||
✔ 다만, **GC(가비지 컬렉터)가 자동으로 해제하지 않기 때문에 직접 관리해야 한다.**
|
||||
|
||||
---
|
||||
|
||||
## **5. ByteBuffer vs. 일반 배열**
|
||||
| 비교 항목 | `ByteBuffer` | 일반 `byte[]` 배열 |
|
||||
|----------|-------------|----------------|
|
||||
| 메모리 할당 | 힙(Heap) 또는 직접 메모리(Direct) | 힙 메모리 |
|
||||
| 데이터 조작 | position/limit을 활용한 조작 | 단순한 인덱스 접근 |
|
||||
| 성능 | `allocateDirect()` 사용 시 속도 향상 | 단순 배열보다 속도가 느릴 수 있음 |
|
||||
| 가비지 컬렉션 | DirectBuffer는 수동 해제 필요 | 자동 관리 |
|
||||
|
||||
✔ **파일 I/O, 네트워크 프로그래밍에서는 `ByteBuffer`를 사용**하는 것이 효율적이다.
|
||||
✔ **단순한 배열 조작이라면 `byte[]`가 더 간단하다.**
|
||||
|
||||
---
|
||||
|
||||
## **6. 결론**
|
||||
`ByteBuffer`는 **Java NIO에서 고성능 I/O 처리를 위한 핵심 클래스**로, 데이터를 읽고 쓰는 방식이 기존의 `InputStream`보다 **더 유연하고 효율적**이다.
|
||||
특히 **파일 I/O, 네트워크 전송, 바이너리 데이터 처리**에 적합하며, **DirectBuffer**를 사용하면 성능을 극대화할 수 있다.
|
||||
380
docs/Collection Framework.md
Normal file
380
docs/Collection Framework.md
Normal file
@@ -0,0 +1,380 @@
|
||||
# **Java의 List, Set, Queue, Deque, Map 인터페이스 및 구현 클래스 비교**
|
||||
|
||||
Java의 **Collection Framework**는 데이터를 효율적으로 저장하고 관리할 수 있는 다양한 자료구조를 제공한다.
|
||||
그중에서 `List`, `Set`, `Queue`, `Deque`, `Map` 인터페이스는 각기 다른 특성을 가지며,
|
||||
이를 구현하는 여러 클래스가 존재한다.
|
||||
|
||||
---
|
||||
|
||||
## **📌 1. 인터페이스별 메서드 정리**
|
||||
|
||||
### **1️⃣ List 인터페이스 (순서 유지, 중복 허용)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `void add(int index, E element)` | 특정 위치에 요소 삽입 |
|
||||
| `boolean add(E element)` | 리스트 끝에 요소 추가 |
|
||||
| `E get(int index)` | 특정 위치의 요소 반환 |
|
||||
| `E remove(int index)` | 특정 위치의 요소 삭제 |
|
||||
| `boolean remove(Object o)` | 특정 요소 삭제 |
|
||||
| `int indexOf(Object o)` | 특정 요소의 첫 번째 인덱스 반환 |
|
||||
| `int lastIndexOf(Object o)` | 특정 요소의 마지막 인덱스 반환 |
|
||||
| `List<E> subList(int fromIndex, int toIndex)` | 특정 범위의 부분 리스트 반환 |
|
||||
|
||||
#### **구현 클래스 비교**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------|
|
||||
| `ArrayList<E>` | 배열 기반, 인덱스 접근 빠름, 삽입/삭제 느림 |
|
||||
| `LinkedList<E>` | 이중 연결 리스트, 삽입/삭제 빠름, 인덱스 접근 느림 |
|
||||
| `Vector<E>` | `ArrayList`와 유사하지만 동기화(Synchronized) 지원 |
|
||||
| `Stack<E>` | `Vector`를 상속받아 LIFO(후입선출) 기능 제공 |
|
||||
|
||||
---
|
||||
|
||||
### **2️⃣ Set 인터페이스 (중복 허용 X, 순서 보장 X)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `boolean add(E e)` | 요소 추가 (중복이면 추가되지 않음) |
|
||||
| `boolean remove(Object o)` | 특정 요소 제거 |
|
||||
| `boolean contains(Object o)` | 특정 요소 포함 여부 확인 |
|
||||
| `int size()` | 요소 개수 반환 |
|
||||
| `void clear()` | 모든 요소 제거 |
|
||||
|
||||
#### **구현 클래스 비교**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------|
|
||||
| `HashSet<E>` | 해시 테이블 기반, 요소 순서 유지 X, 검색 빠름 |
|
||||
| `LinkedHashSet<E>` | `HashSet`과 같지만 요소 삽입 순서 유지 |
|
||||
| `TreeSet<E>` | 이진 탐색 트리 기반, 요소가 **자동 정렬됨** |
|
||||
|
||||
---
|
||||
|
||||
### **3️⃣ Queue 인터페이스 (FIFO, 선입선출 구조)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `boolean add(E e)` | 요소 추가 (실패 시 예외 발생) |
|
||||
| `boolean offer(E e)` | 요소 추가 (실패 시 false 반환) |
|
||||
| `E remove()` | 첫 번째 요소 제거 (큐가 비었으면 예외 발생) |
|
||||
| `E poll()` | 첫 번째 요소 제거 (큐가 비었으면 null 반환) |
|
||||
| `E element()` | 첫 번째 요소 반환 (큐가 비었으면 예외 발생) |
|
||||
| `E peek()` | 첫 번째 요소 반환 (큐가 비었으면 null 반환) |
|
||||
|
||||
#### **구현 클래스 비교**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------|
|
||||
| `LinkedList<E>` | `Queue` 인터페이스 구현, 양방향 리스트 구조 |
|
||||
| `PriorityQueue<E>` | 내부적으로 힙(Heap) 사용, **우선순위에 따라 정렬** |
|
||||
| `ArrayDeque<E>` | 양방향 큐 (Deque) 지원, `Stack` 대체 가능 |
|
||||
|
||||
---
|
||||
|
||||
### **4️⃣ Deque 인터페이스 (양방향 큐)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `void addFirst(E e)` | 앞쪽에 요소 추가 |
|
||||
| `void addLast(E e)` | 뒤쪽에 요소 추가 |
|
||||
| `E removeFirst()` | 앞쪽 요소 제거 |
|
||||
| `E removeLast()` | 뒤쪽 요소 제거 |
|
||||
| `E getFirst()` | 첫 번째 요소 반환 |
|
||||
| `E getLast()` | 마지막 요소 반환 |
|
||||
| `boolean offerFirst(E e)` | 앞쪽에 요소 추가 (실패 시 false 반환) |
|
||||
| `boolean offerLast(E e)` | 뒤쪽에 요소 추가 (실패 시 false 반환) |
|
||||
|
||||
#### **구현 클래스 비교**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------|
|
||||
| `ArrayDeque<E>` | 배열 기반, 크기 자동 조절 |
|
||||
| `LinkedList<E>` | 이중 연결 리스트 기반 |
|
||||
|
||||
---
|
||||
|
||||
### **5️⃣ Map 인터페이스 (키-값 쌍 저장, 중복 키 X)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `V put(K key, V value)` | 키-값 쌍 추가 (같은 키가 존재하면 기존 값 덮어쓰기) |
|
||||
| `V get(Object key)` | 특정 키에 대한 값 반환 |
|
||||
| `V remove(Object key)` | 특정 키의 값 삭제 |
|
||||
| `boolean containsKey(Object key)` | 특정 키 포함 여부 확인 |
|
||||
| `boolean containsValue(Object value)` | 특정 값 포함 여부 확인 |
|
||||
| `Set<K> keySet()` | 모든 키 반환 |
|
||||
| `Collection<V> values()` | 모든 값 반환 |
|
||||
| `Set<Map.Entry<K,V>> entrySet()` | 모든 키-값 쌍 반환 |
|
||||
|
||||
#### **구현 클래스 비교**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------|
|
||||
| `HashMap<K,V>` | 해시 테이블 기반, 키 순서 보장 X, 빠른 검색 |
|
||||
| `LinkedHashMap<K,V>` | 삽입 순서 유지 |
|
||||
| `TreeMap<K,V>` | 키 기준으로 **자동 정렬** |
|
||||
|
||||
---
|
||||
|
||||
## **📌 2. 구현 클래스 비교 요약**
|
||||
| 인터페이스 | 주요 구현 클래스 | 특징 |
|
||||
|------------|--------------|------|
|
||||
| `List` | `ArrayList` | 배열 기반, 빠른 조회, 삽입/삭제 느림 |
|
||||
| | `LinkedList` | 연결 리스트 기반, 삽입/삭제 빠름, 조회 느림 |
|
||||
| | `Vector` | `ArrayList`와 유사하지만 동기화 지원 |
|
||||
| | `Stack` | `Vector`를 확장하여 LIFO(후입선출) 제공 |
|
||||
| `Set` | `HashSet` | 중복 X, 순서 보장 X |
|
||||
| | `LinkedHashSet` | 삽입 순서 유지 |
|
||||
| | `TreeSet` | 자동 정렬 (오름차순) |
|
||||
| `Queue` | `LinkedList` | FIFO (선입선출) |
|
||||
| | `PriorityQueue` | 우선순위 기반 정렬 |
|
||||
| | `ArrayDeque` | 양방향 큐 (Deque) |
|
||||
| `Deque` | `ArrayDeque` | 배열 기반, 크기 자동 조절 |
|
||||
| | `LinkedList` | 연결 리스트 기반 |
|
||||
| `Map` | `HashMap` | 중복 키 X, 순서 보장 X |
|
||||
| | `LinkedHashMap` | 삽입 순서 유지 |
|
||||
| | `TreeMap` | 키 기준 자동 정렬 |
|
||||
|
||||
---
|
||||
|
||||
## **📌 3. 예제 코드**
|
||||
### **✔ `List` 사용 예제**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class ListExample {
|
||||
public static void main(String[] args) {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("Apple");
|
||||
list.add("Banana");
|
||||
list.add("Cherry");
|
||||
|
||||
System.out.println(list.get(1)); // 출력: Banana
|
||||
list.remove("Banana");
|
||||
System.out.println(list); // 출력: [Apple, Cherry]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **📌 4. 마무리**
|
||||
✔ **List**: 순서 유지, 중복 허용
|
||||
✔ **Set**: 순서 없음, 중복 불가
|
||||
✔ **Queue**: FIFO 구조, `PriorityQueue`는 우선순위 정렬
|
||||
✔ **Deque**: 양방향 큐
|
||||
✔ **Map**: 키-값 저장, 키 중복 불가
|
||||
✔ 각 자료구조의 **특성을 이해하고 상황에 맞게 선택**하자! 🚀
|
||||
|
||||
|
||||
---
|
||||
|
||||
# **Java 컬렉션 유틸리티 클래스 (Collections, Arrays, etc.)**
|
||||
|
||||
Java에서는 컬렉션을 쉽게 다룰 수 있도록 **유틸리티 클래스**를 제공한다.
|
||||
대표적으로 `Collections`와 `Arrays` 클래스가 있으며, 이들은 **정렬, 검색, 동기화, 불변 컬렉션 생성** 등의 기능을 제공한다.
|
||||
|
||||
---
|
||||
|
||||
## **📌 1. Collections 클래스의 주요 메서드 정리**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `sort(List<T> list)` | 오름차순 정렬 |
|
||||
| `sort(List<T> list, Comparator<? super T> c)` | 지정한 정렬 기준으로 정렬 |
|
||||
| `binarySearch(List<T> list, T key)` | 이진 탐색 (정렬된 리스트에서만 사용 가능) |
|
||||
| `reverse(List<?> list)` | 리스트 요소 순서를 반대로 변경 |
|
||||
| `shuffle(List<?> list)` | 리스트 요소를 무작위로 섞음 |
|
||||
| `min(Collection<? extends T> coll)` | 컬렉션에서 최소값 반환 |
|
||||
| `max(Collection<? extends T> coll)` | 컬렉션에서 최대값 반환 |
|
||||
| `fill(List<? super T> list, T obj)` | 리스트의 모든 요소를 특정 값으로 채움 |
|
||||
| `copy(List<? super T> dest, List<? extends T> src)` | 리스트 복사 |
|
||||
| `replaceAll(List<T> list, T oldVal, T newVal)` | 특정 값을 새로운 값으로 대체 |
|
||||
| `frequency(Collection<?> c, Object o)` | 특정 요소가 컬렉션에 몇 번 등장하는지 반환 |
|
||||
| `disjoint(Collection<?> c1, Collection<?> c2)` | 두 컬렉션이 공통 요소를 가지지 않으면 `true` 반환 |
|
||||
| `synchronizedList(List<T> list)` | 동기화된 리스트 반환 |
|
||||
| `synchronizedSet(Set<T> set)` | 동기화된 셋 반환 |
|
||||
| `synchronizedMap(Map<K,V> map)` | 동기화된 맵 반환 |
|
||||
| `synchronizedSortedMap(SortedMap<K,V> map)` | 동기화된 정렬된 맵 반환 |
|
||||
| `unmodifiableList(List<? extends T> list)` | 불변 리스트 반환 |
|
||||
| `unmodifiableSet(Set<? extends T> set)` | 불변 셋 반환 |
|
||||
| `unmodifiableMap(Map<? extends K,? extends V> map)` | 불변 맵 반환 |
|
||||
|
||||
---
|
||||
|
||||
## **📌 2. Arrays 클래스의 주요 메서드 정리**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `sort(T[] array)` | 배열 정렬 (오름차순) |
|
||||
| `sort(T[] array, Comparator<? super T> c)` | 사용자 정의 비교 기준으로 정렬 |
|
||||
| `binarySearch(T[] array, T key)` | 배열에서 이진 탐색 수행 |
|
||||
| `fill(T[] array, T val)` | 배열의 모든 요소를 특정 값으로 채움 |
|
||||
| `copyOf(T[] original, int newLength)` | 배열을 새로운 크기로 복사 |
|
||||
| `copyOfRange(T[] original, int from, int to)` | 배열의 특정 범위를 복사 |
|
||||
| `equals(T[] a, T[] b)` | 두 배열이 같은지 비교 |
|
||||
| `deepEquals(Object[] a, Object[] b)` | 다차원 배열 비교 |
|
||||
| `asList(T... a)` | 배열을 리스트로 변환 |
|
||||
| `toString(T[] a)` | 배열을 문자열로 변환 |
|
||||
|
||||
---
|
||||
|
||||
## **📌 3. 주요 메서드 설명 및 예제**
|
||||
|
||||
### **✔ `sort()` - 정렬**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class SortExample {
|
||||
public static void main(String[] args) {
|
||||
List<Integer> list = Arrays.asList(5, 3, 8, 1, 2);
|
||||
Collections.sort(list);
|
||||
System.out.println(list); // 출력: [1, 2, 3, 5, 8]
|
||||
}
|
||||
}
|
||||
```
|
||||
- 기본적으로 **오름차순 정렬**한다.
|
||||
- `Comparator`를 사용하면 정렬 기준을 변경할 수 있다.
|
||||
|
||||
```java
|
||||
Collections.sort(list, Comparator.reverseOrder()); // 내림차순 정렬
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **✔ `binarySearch()` - 이진 탐색**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class BinarySearchExample {
|
||||
public static void main(String[] args) {
|
||||
List<Integer> list = Arrays.asList(1, 2, 3, 5, 8);
|
||||
int index = Collections.binarySearch(list, 3);
|
||||
System.out.println("Index of 3: " + index); // 출력: Index of 3: 2
|
||||
}
|
||||
}
|
||||
```
|
||||
- **정렬된 리스트에서만 사용 가능**하다.
|
||||
- 찾는 값이 없으면 **음수 반환**.
|
||||
|
||||
---
|
||||
|
||||
### **✔ `shuffle()` - 무작위 섞기**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class ShuffleExample {
|
||||
public static void main(String[] args) {
|
||||
List<String> list = Arrays.asList("A", "B", "C", "D");
|
||||
Collections.shuffle(list);
|
||||
System.out.println(list); // 매 실행마다 다른 순서
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **✔ `min()` / `max()` - 최소값과 최대값 찾기**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class MinMaxExample {
|
||||
public static void main(String[] args) {
|
||||
List<Integer> list = Arrays.asList(3, 8, 1, 5, 2);
|
||||
System.out.println(Collections.min(list)); // 출력: 1
|
||||
System.out.println(Collections.max(list)); // 출력: 8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **✔ `fill()` - 모든 요소를 특정 값으로 채우기**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class FillExample {
|
||||
public static void main(String[] args) {
|
||||
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
|
||||
Collections.fill(list, "X");
|
||||
System.out.println(list); // 출력: [X, X, X]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **✔ `replaceAll()` - 특정 값 치환**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class ReplaceAllExample {
|
||||
public static void main(String[] args) {
|
||||
List<String> list = new ArrayList<>(Arrays.asList("apple", "banana", "apple"));
|
||||
Collections.replaceAll(list, "apple", "orange");
|
||||
System.out.println(list); // 출력: [orange, banana, orange]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **✔ `frequency()` - 특정 값 개수 세기**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class FrequencyExample {
|
||||
public static void main(String[] args) {
|
||||
List<String> list = Arrays.asList("apple", "banana", "apple", "cherry", "apple");
|
||||
System.out.println(Collections.frequency(list, "apple")); // 출력: 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **✔ `synchronizedList()` - 동기화 리스트**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class SynchronizedListExample {
|
||||
public static void main(String[] args) {
|
||||
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
synchronized (list) {
|
||||
list.add(1);
|
||||
list.add(2);
|
||||
list.add(3);
|
||||
}
|
||||
|
||||
System.out.println(list); // 출력: [1, 2, 3]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **✔ `unmodifiableList()` - 불변 리스트**
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class UnmodifiableListExample {
|
||||
public static void main(String[] args) {
|
||||
List<String> list = Arrays.asList("A", "B", "C");
|
||||
List<String> unmodifiableList = Collections.unmodifiableList(list);
|
||||
|
||||
unmodifiableList.add("D"); // 예외 발생
|
||||
}
|
||||
}
|
||||
```
|
||||
- `unmodifiableList`는 **수정 불가**.
|
||||
|
||||
---
|
||||
|
||||
## **📌 4. 정리**
|
||||
| 기능 | Collections 메서드 | Arrays 메서드 |
|
||||
|------|----------------|------------|
|
||||
| 정렬 | `sort()` | `sort()` |
|
||||
| 검색 | `binarySearch()` | `binarySearch()` |
|
||||
| 최소/최대값 | `min()`, `max()` | X |
|
||||
| 무작위 섞기 | `shuffle()` | X |
|
||||
| 요소 채우기 | `fill()` | `fill()` |
|
||||
| 요소 치환 | `replaceAll()` | X |
|
||||
| 특정 값 개수 | `frequency()` | X |
|
||||
| 동기화 컬렉션 | `synchronizedList()` | X |
|
||||
| 불변 컬렉션 | `unmodifiableList()` | X |
|
||||
|
||||
✔ `Collections`는 **컬렉션(List, Set, Map) 관련 기능** 제공
|
||||
✔ `Arrays`는 **배열 관련 기능** 제공
|
||||
|
||||
이제 Java 컬렉션을 더욱 편리하게 다룰 수 있다! 🚀
|
||||
151
docs/Collections.md
Normal file
151
docs/Collections.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# **자바 Collections 쉽게 배우기**
|
||||
|
||||
자바에서 `Collections Framework`는 **배열보다 더 강력한 자료구조**를 제공한다.
|
||||
배열은 크기가 고정되지만, 컬렉션은 **동적으로 크기를 조정할 수 있으며, 다양한 데이터 구조**를 제공한다.
|
||||
|
||||
컬렉션은 크게 다음과 같은 주요 인터페이스로 구성된다.
|
||||
- `List` : 순서가 있는 자료구조 (배열과 유사, 중복 허용)
|
||||
- `Set` : 중복을 허용하지 않는 자료구조
|
||||
- `Queue` : FIFO(선입선출) 방식의 자료구조
|
||||
- `Map` : 키-값 쌍을 저장하는 자료구조
|
||||
|
||||
---
|
||||
|
||||
## **1. 주요 컬렉션 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `List` 인터페이스를 구현하는 클래스 (`ArrayList`, `LinkedList`)**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------------------------------|
|
||||
| `ArrayList<E>` | 크기가 동적으로 변하는 배열 |
|
||||
| `LinkedList<E>` | 이중 연결 리스트 구조, 삽입·삭제 빠름 |
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `add(E e)` | 요소 추가 |
|
||||
| `get(int index)` | 특정 위치의 요소 반환 |
|
||||
| `remove(int index)` | 특정 위치의 요소 삭제 |
|
||||
| `size()` | 리스트 크기 반환 |
|
||||
| `contains(Object o)` | 특정 요소 포함 여부 확인 |
|
||||
| `indexOf(Object o)` | 특정 요소의 인덱스 반환 |
|
||||
|
||||
**예제 코드:**
|
||||
```java
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("Apple");
|
||||
list.add("Banana");
|
||||
|
||||
System.out.println(list.get(0)); // Apple
|
||||
System.out.println(list.contains("Banana")); // true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(2) `Set` 인터페이스를 구현하는 클래스 (`HashSet`, `TreeSet`)**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------------------------------|
|
||||
| `HashSet<E>` | 중복 없는 요소 저장, 순서 보장 안 됨 |
|
||||
| `TreeSet<E>` | 정렬된 상태 유지 |
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `add(E e)` | 요소 추가 |
|
||||
| `remove(Object o)` | 요소 삭제 |
|
||||
| `contains(Object o)` | 요소 포함 여부 확인 |
|
||||
| `size()` | 크기 반환 |
|
||||
| `clear()` | 모든 요소 제거 |
|
||||
|
||||
**예제 코드:**
|
||||
```java
|
||||
Set<Integer> set = new HashSet<>();
|
||||
set.add(10);
|
||||
set.add(20);
|
||||
set.add(10); // 중복된 값 추가 X
|
||||
|
||||
System.out.println(set.size()); // 2
|
||||
System.out.println(set.contains(20)); // true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(3) `Queue` 인터페이스를 구현하는 클래스 (`LinkedList`, `PriorityQueue`)**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------------------------------|
|
||||
| `LinkedList<E>` | FIFO(선입선출) 방식으로 동작 |
|
||||
| `PriorityQueue<E>` | 우선순위에 따라 정렬 |
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `offer(E e)` | 요소 추가 (큐의 끝에) |
|
||||
| `poll()` | 첫 번째 요소 반환 후 제거 |
|
||||
| `peek()` | 첫 번째 요소 조회 (삭제 X) |
|
||||
| `isEmpty()` | 큐가 비어 있는지 확인 |
|
||||
|
||||
**예제 코드:**
|
||||
```java
|
||||
Queue<String> queue = new LinkedList<>();
|
||||
queue.offer("A");
|
||||
queue.offer("B");
|
||||
|
||||
System.out.println(queue.poll()); // A (먼저 들어온 요소가 제거됨)
|
||||
System.out.println(queue.peek()); // B (두 번째 요소 조회)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(4) `Map` 인터페이스를 구현하는 클래스 (`HashMap`, `TreeMap`)**
|
||||
| 클래스 | 특징 |
|
||||
|--------|------------------------------|
|
||||
| `HashMap<K, V>` | 키-값 쌍 저장, 순서 보장 안 됨 |
|
||||
| `TreeMap<K, V>` | 키 기준으로 정렬 |
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `put(K key, V value)` | 키-값 추가 |
|
||||
| `get(Object key)` | 키에 해당하는 값 반환 |
|
||||
| `remove(Object key)` | 키에 해당하는 요소 삭제 |
|
||||
| `containsKey(Object key)` | 특정 키 존재 여부 확인 |
|
||||
| `containsValue(Object value)` | 특정 값 존재 여부 확인 |
|
||||
|
||||
**예제 코드:**
|
||||
```java
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put("Apple", 100);
|
||||
map.put("Banana", 200);
|
||||
|
||||
System.out.println(map.get("Apple")); // 100
|
||||
System.out.println(map.containsKey("Banana")); // true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **2. `Collections` 유틸리티 클래스**
|
||||
자바에서는 `java.util.Collections` 클래스를 제공하여 **컬렉션을 조작하는 다양한 기능**을 지원한다.
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `sort(List<T> list)` | 리스트 정렬 |
|
||||
| `reverse(List<T> list)` | 리스트 역순 정렬 |
|
||||
| `shuffle(List<T> list)` | 요소 섞기 |
|
||||
| `max(Collection<T> coll)` | 최대값 반환 |
|
||||
| `min(Collection<T> coll)` | 최소값 반환 |
|
||||
|
||||
**예제 코드:**
|
||||
```java
|
||||
List<Integer> numbers = Arrays.asList(5, 3, 8, 1);
|
||||
Collections.sort(numbers);
|
||||
System.out.println(numbers); // [1, 3, 5, 8]
|
||||
|
||||
Collections.reverse(numbers);
|
||||
System.out.println(numbers); // [8, 5, 3, 1]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **3. 정리**
|
||||
✅ **`List`** : 순서가 있는 자료구조 (중복 허용)
|
||||
✅ **`Set`** : 중복을 허용하지 않는 자료구조
|
||||
✅ **`Queue`** : 선입선출(FIFO) 자료구조
|
||||
✅ **`Map`** : 키-값 쌍을 저장하는 자료구조
|
||||
✅ **`Collections` 유틸리티 클래스**를 활용하면 컬렉션을 더욱 편리하게 조작 가능!
|
||||
|
||||
즉, 자바의 컬렉션을 잘 활용하면 **효율적인 데이터 관리와 조작이 가능하다!**
|
||||
225
docs/Concurrency.md
Normal file
225
docs/Concurrency.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# 자바 동시성(Concurrency) API 정리 및 쉬운 설명
|
||||
|
||||
자바의 동시성(Concurrency) API는 **멀티스레딩을 효율적으로 관리하고 안전하게 실행할 수 있도록 지원하는 기능**을 제공한다.
|
||||
즉, **여러 작업을 동시에 실행하여 프로그램 성능을 향상**시킬 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 주요 클래스 및 메서드 정리
|
||||
|
||||
### (1) `Thread` 클래스 (기본 스레드 실행)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `start()` | 새로운 스레드를 실행 |
|
||||
| `run()` | 실행할 코드 정의 (직접 호출하면 동작하지 않음) |
|
||||
| `sleep(ms)` | 지정된 시간(ms) 동안 스레드 일시 정지 |
|
||||
| `join()` | 현재 스레드가 종료될 때까지 다른 스레드 대기 |
|
||||
| `interrupt()` | 실행 중인 스레드를 인터럽트 (깨우기) |
|
||||
| `isAlive()` | 스레드가 실행 중인지 확인 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
class MyThread extends Thread {
|
||||
public void run() {
|
||||
System.out.println("스레드 실행 중!");
|
||||
}
|
||||
}
|
||||
|
||||
MyThread t = new MyThread();
|
||||
t.start(); // 스레드 실행
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### (2) `Runnable` 인터페이스 (스레드 실행)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `run()` | 실행할 코드 정의 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
class MyRunnable implements Runnable {
|
||||
public void run() {
|
||||
System.out.println("Runnable 스레드 실행!");
|
||||
}
|
||||
}
|
||||
|
||||
Thread t = new Thread(new MyRunnable());
|
||||
t.start();
|
||||
```
|
||||
→ `Thread` 클래스를 직접 상속하지 않고 `Runnable`을 구현하여 사용.
|
||||
|
||||
---
|
||||
|
||||
### (3) `ExecutorService` (스레드 풀)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `submit(Runnable)` | 스레드를 실행 (결과 없음) |
|
||||
| `submit(Callable<T>)` | 스레드를 실행하고 결과 반환 |
|
||||
| `shutdown()` | 스레드 풀 종료 (기존 작업 수행 후 종료) |
|
||||
| `shutdownNow()` | 즉시 모든 작업 중단 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
|
||||
executor.submit(() -> System.out.println("스레드 풀에서 실행"));
|
||||
executor.shutdown();
|
||||
```
|
||||
→ `Executors.newFixedThreadPool(2)`를 사용해 2개의 스레드를 관리.
|
||||
|
||||
---
|
||||
|
||||
### (4) `Callable` & `Future` (결과 반환이 필요한 작업)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `call()` | 실행할 코드 정의, 결과 반환 |
|
||||
| `get()` | `Future`에서 결과 가져오기 (블로킹) |
|
||||
| `isDone()` | 작업 완료 여부 확인 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
Callable<Integer> task = () -> {
|
||||
Thread.sleep(1000);
|
||||
return 10;
|
||||
};
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
Future<Integer> future = executor.submit(task);
|
||||
|
||||
System.out.println(future.get()); // 결과 가져오기
|
||||
executor.shutdown();
|
||||
```
|
||||
→ `Callable`을 사용하면 **스레드 실행 후 결과 값을 반환**할 수 있음.
|
||||
|
||||
---
|
||||
|
||||
### (5) `ReentrantLock` (락을 이용한 동기화)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `lock()` | 락 획득 |
|
||||
| `unlock()` | 락 해제 |
|
||||
| `tryLock()` | 락을 시도하고 성공하면 `true` 반환 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
System.out.println("임계영역 실행");
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
```
|
||||
→ `synchronized` 키워드 대신 `ReentrantLock`을 사용하여 더 정교한 동기화 가능.
|
||||
|
||||
---
|
||||
|
||||
### (6) `Semaphore` (동시 실행 제한)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `acquire()` | 리소스 사용 요청 (없으면 대기) |
|
||||
| `release()` | 사용한 리소스 반환 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
Semaphore semaphore = new Semaphore(2);
|
||||
|
||||
semaphore.acquire(); // 사용 가능하면 진행, 아니면 대기
|
||||
System.out.println("리소스 사용 중");
|
||||
semaphore.release();
|
||||
```
|
||||
→ 한 번에 2개의 스레드만 특정 코드 실행 가능.
|
||||
|
||||
---
|
||||
|
||||
### (7) `CountDownLatch` (스레드가 모두 끝날 때까지 대기)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `await()` | 모든 스레드가 완료될 때까지 대기 |
|
||||
| `countDown()` | 하나의 작업 완료 처리 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
new Thread(() -> {
|
||||
System.out.println("작업 완료");
|
||||
latch.countDown();
|
||||
}).start();
|
||||
}
|
||||
|
||||
latch.await();
|
||||
System.out.println("모든 작업 완료");
|
||||
```
|
||||
→ 모든 작업(`countDown()` 3회)이 끝나야 `await()`이 풀림.
|
||||
|
||||
---
|
||||
|
||||
### (8) `CyclicBarrier` (스레드가 모두 도달할 때까지 대기)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `await()` | 지정된 개수의 스레드가 모일 때까지 대기 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
|
||||
System.out.println("모든 스레드 도착!");
|
||||
});
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
new Thread(() -> {
|
||||
System.out.println("스레드 실행 중");
|
||||
barrier.await();
|
||||
}).start();
|
||||
}
|
||||
```
|
||||
→ `CyclicBarrier`는 정해진 개수의 스레드가 모두 도착할 때까지 기다림.
|
||||
|
||||
---
|
||||
|
||||
## 2. 자바 동시성 쉽게 설명하기
|
||||
|
||||
자바에서 동시성을 다룰 때 중요한 개념은 **멀티스레딩과 스레드 동기화**다.
|
||||
즉, 여러 개의 스레드가 **같은 자원을 동시에 접근하면 충돌이 발생**할 수 있으므로, 이를 적절히 제어해야 한다.
|
||||
|
||||
### 1️⃣ **기본적인 멀티스레드**
|
||||
- `Thread` 또는 `Runnable`을 사용해 실행
|
||||
- `start()`로 실행, `join()`으로 대기
|
||||
|
||||
### 2️⃣ **스레드 풀 사용 (`ExecutorService`)**
|
||||
- 직접 스레드를 생성하면 관리가 어렵기 때문에, **스레드 풀을 사용하여 성능 최적화**
|
||||
- `submit()`으로 작업을 실행하고, `shutdown()`으로 종료
|
||||
|
||||
### 3️⃣ **결과를 받아야 한다면? (`Callable` & `Future`)**
|
||||
- `Callable`을 사용하면 작업이 끝난 후 결과 값을 반환 가능
|
||||
- `future.get()`을 호출하면 결과를 가져올 수 있음
|
||||
|
||||
### 4️⃣ **공유 자원 충돌 방지 (`ReentrantLock`, `synchronized`)**
|
||||
- `synchronized` 키워드는 단순하지만 유연성이 낮음
|
||||
- `ReentrantLock`은 더 정교한 동기화 제공
|
||||
|
||||
### 5️⃣ **특정 조건을 만족할 때 실행 (`CountDownLatch`, `CyclicBarrier`)**
|
||||
- `CountDownLatch` → 여러 개의 스레드가 끝날 때까지 대기
|
||||
- `CyclicBarrier` → 여러 스레드가 동시에 도달해야 실행
|
||||
|
||||
---
|
||||
|
||||
## 3. 정리
|
||||
|
||||
✅ 멀티스레딩을 쉽게 하려면? → `ExecutorService`
|
||||
✅ 스레드 실행 결과를 받아야 한다면? → `Callable`, `Future`
|
||||
✅ 여러 스레드가 동시에 공유 자원을 사용하면? → `ReentrantLock`, `synchronized`
|
||||
✅ 여러 작업이 끝난 후 실행되게 하려면? → `CountDownLatch`, `CyclicBarrier`
|
||||
|
||||
즉, **자바 동시성 API를 적절히 활용하면 멀티스레딩을 효율적으로 관리할 수 있다!**
|
||||
199
docs/Concurrency/01_Thread.md
Normal file
199
docs/Concurrency/01_Thread.md
Normal file
@@ -0,0 +1,199 @@
|
||||
자바의 `Thread` 클래스와 관련된 주요 메서드들을 표로 정리하고, 각 메서드에 대한 설명과 예시를 포함한 글을 작성하겠습니다. 아래는 요청하신 내용입니다.
|
||||
|
||||
---
|
||||
|
||||
### 자바 Thread 관련 메서드 표
|
||||
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-------------------------|-----------|--------------------------------------------------------------------------------------|
|
||||
| `start()` | `void` | 스레드를 시작하며, `run()` 메서드를 호출하여 실행 준비 상태로 만듦 |
|
||||
| `run()` | `void` | 스레드가 실행할 작업을 정의 (직접 호출 시 스레드 생성 없이 현재 스레드에서 실행) |
|
||||
| `sleep(long millis)` | `void` | 지정된 시간(밀리초) 동안 스레드를 일시 정지 (정적 메서드) |
|
||||
| `join()` | `void` | 해당 스레드가 종료될 때까지 현재 스레드가 기다림 |
|
||||
| `interrupt()` | `void` | 스레드에 인터럽트를 발생시켜 작업을 중단 요청 |
|
||||
| `isAlive()` | `boolean` | 스레드가 실행 중인지 여부를 확인 |
|
||||
| `setPriority(int n)` | `void` | 스레드의 우선순위를 설정 (1~10 사이 값) |
|
||||
| `getPriority()` | `int` | 스레드의 현재 우선순위를 반환 |
|
||||
| `yield()` | `void` | 현재 스레드가 다른 스레드에게 실행 기회를 양보 (정적 메서드) |
|
||||
| `currentThread()` | `Thread` | 현재 실행 중인 스레드 객체를 반환 (정적 메서드) |
|
||||
|
||||
---
|
||||
|
||||
### Thread 메서드 설명 및 예시
|
||||
|
||||
자바에서 스레드는 멀티태스킹을 구현하는 데 필수적인 요소입니다. `Thread` 클래스와 `Runnable` 인터페이스를 통해 스레드를 생성하고 관리할 수 있으며, 위 표에 정리된 메서드들은 스레드의 생명주기와 동작을 제어하는 데 사용됩니다. 아래에서는 주요 메서드들을 예시와 함께 설명하겠습니다.
|
||||
|
||||
#### 1. `start()`와 `run()`
|
||||
- **`start()`**: 새로운 스레드를 생성하고 실행 가능한 상태로 만들어 `run()` 메서드를 호출합니다.
|
||||
- **`run()`**: 실제 스레드가 수행할 작업을 정의합니다. 단, `run()`을 직접 호출하면 새로운 스레드가 생성되지 않고 현재 스레드에서 실행됩니다.
|
||||
|
||||
```java
|
||||
class MyThread extends Thread {
|
||||
public void run() {
|
||||
System.out.println("스레드 실행 중: " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public class ThreadExample {
|
||||
public static void main(String[] args) {
|
||||
MyThread thread = new MyThread();
|
||||
thread.start(); // 새로운 스레드 생성 및 실행
|
||||
// thread.run(); // 이렇게 호출하면 메인 스레드에서 실행됨
|
||||
}
|
||||
}
|
||||
```
|
||||
- 출력: `스레드 실행 중: Thread-0`
|
||||
|
||||
#### 2. `sleep(long millis)`
|
||||
- 지정된 시간 동안 현재 스레드를 일시 정지합니다. 다른 스레드에게 CPU를 양보하며, `InterruptedException`을 처리해야 합니다.
|
||||
|
||||
```java
|
||||
public class SleepExample {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
System.out.println("2초 후에 깨어납니다.");
|
||||
Thread.sleep(2000); // 2초 대기
|
||||
System.out.println("깨어났습니다!");
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- 출력:
|
||||
```
|
||||
2초 후에 깨어납니다.
|
||||
(2초 대기 후)
|
||||
깨어났습니다!
|
||||
```
|
||||
|
||||
#### 3. `join()`
|
||||
- 다른 스레드가 종료될 때까지 현재 스레드가 기다리도록 합니다. 작업 순서를 보장할 때 유용합니다.
|
||||
|
||||
```java
|
||||
class WorkerThread extends Thread {
|
||||
public void run() {
|
||||
System.out.println("작업 시작");
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("작업 완료");
|
||||
}
|
||||
}
|
||||
|
||||
public class JoinExample {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
WorkerThread worker = new WorkerThread();
|
||||
worker.start();
|
||||
worker.join(); // worker 스레드가 끝날 때까지 대기
|
||||
System.out.println("메인 스레드 계속 진행");
|
||||
}
|
||||
}
|
||||
```
|
||||
- 출력:
|
||||
```
|
||||
작업 시작
|
||||
(1초 후)
|
||||
작업 완료
|
||||
메인 스레드 계속 진행
|
||||
```
|
||||
|
||||
#### 4. `interrupt()`
|
||||
- 스레드에 인터럽트를 발생시켜 작업을 중단하도록 요청합니다. `sleep()`이나 `wait()` 중인 스레드는 `InterruptedException`을 발생시킵니다.
|
||||
|
||||
```java
|
||||
class InterruptThread extends Thread {
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
System.out.println("작업 중...");
|
||||
Thread.sleep(500);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("인터럽트 발생! 종료합니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InterruptExample {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
InterruptThread thread = new InterruptThread();
|
||||
thread.start();
|
||||
Thread.sleep(2000); // 2초 후 인터럽트
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
```
|
||||
- 출력:
|
||||
```
|
||||
작업 중...
|
||||
작업 중...
|
||||
작업 중...
|
||||
인터럽트 발생! 종료합니다.
|
||||
```
|
||||
|
||||
#### 5. `setPriority(int n)`와 `getPriority()`
|
||||
- 스레드의 우선순위를 설정하고 확인합니다. 우선순위는 1(최저)에서 10(최고)까지이며, 기본값은 5입니다.
|
||||
|
||||
```java
|
||||
public class PriorityExample {
|
||||
public static void main(String[] args) {
|
||||
Thread thread1 = new Thread(() -> System.out.println("Thread 1 실행"));
|
||||
Thread thread2 = new Thread(() -> System.out.println("Thread 2 실행"));
|
||||
|
||||
thread1.setPriority(Thread.MIN_PRIORITY); // 1
|
||||
thread2.setPriority(Thread.MAX_PRIORITY); // 10
|
||||
|
||||
System.out.println("Thread 1 우선순위: " + thread1.getPriority());
|
||||
System.out.println("Thread 2 우선순위: " + thread2.getPriority());
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
- 출력 (실행 순서는 보장되지 않음):
|
||||
```
|
||||
Thread 1 우선순위: 1
|
||||
Thread 2 우선순위: 10
|
||||
Thread 2 실행
|
||||
Thread 1 실행
|
||||
```
|
||||
|
||||
#### 6. `yield()`
|
||||
- 현재 스레드가 다른 스레드에게 실행 기회를 양보합니다. CPU 스케줄링에 따라 결과가 달라질 수 있습니다.
|
||||
|
||||
```java
|
||||
class YieldThread extends Thread {
|
||||
public void run() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
System.out.println(getName() + " 실행 중");
|
||||
Thread.yield(); // 다른 스레드에게 양보
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class YieldExample {
|
||||
public static void main(String[] args) {
|
||||
Thread thread1 = new YieldThread();
|
||||
Thread thread2 = new YieldThread();
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
- 출력 (순서 무작위):
|
||||
```
|
||||
Thread-0 실행 중
|
||||
Thread-1 실행 중
|
||||
Thread-0 실행 중
|
||||
Thread-1 실행 중
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
자바의 `Thread` 클래스는 스레드 생성, 실행, 제어를 위한 다양한 메서드를 제공합니다. `start()`와 `run()`으로 스레드를 시작하고, `sleep()`, `join()`, `interrupt()`로 동작을 제어하며, `setPriority()`와 `yield()`로 스케줄링을 조정할 수 있습니다. 위 예시를 통해 각 메서드의 동작을 이해하고, 실제 멀티스레드 프로그래밍에 적용해 보세요!
|
||||
193
docs/Concurrency/02_synchronized.md
Normal file
193
docs/Concurrency/02_synchronized.md
Normal file
@@ -0,0 +1,193 @@
|
||||
### 자바 스레드 동기화에 대한 설명
|
||||
|
||||
스레드 동기화는 멀티스레드 환경에서 여러 스레드가 공유 자원(예: 변수, 객체 등)에 동시에 접근할 때 발생할 수 있는 데이터 불일치 문제를 해결하기 위해 사용됩니다. 자바에서는 동기화를 통해 특정 코드 블록이나 메서드가 한 번에 하나의 스레드만 실행하도록 보장합니다. 이를 통해 **경쟁 조건(race condition)**을 방지하고, 데이터의 무결성을 유지할 수 있습니다.
|
||||
|
||||
자바에서 스레드 동기화를 구현하는 주요 방법은 `synchronized` 키워드와 `java.util.concurrent` 패키지의 도구(예: `Lock`, `Semaphore` 등)를 사용하는 것입니다. 아래에서는 `synchronized` 키워드를 중심으로 설명하고, 예시를 제공하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### 스레드 동기화의 필요성
|
||||
예를 들어, 은행 계좌 잔액을 관리하는 프로그램에서 두 스레드가 동시에 잔액을 수정하려고 하면 문제가 발생할 수 있습니다. 동기화가 없으면 잔액이 잘못 계산될 가능성이 높습니다. 이를 **경쟁 조건**이라고 부르며, 동기화로 해결할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
### `synchronized` 키워드 사용 방법
|
||||
자바에서 동기화는 두 가지 방식으로 적용됩니다:
|
||||
1. **`synchronized` 메서드**: 메서드 전체를 동기화하여 한 번에 하나의 스레드만 실행하도록 만듭니다.
|
||||
2. **`synchronized` 블록**: 특정 코드 블록만 동기화하여 더 세밀한 제어가 가능합니다.
|
||||
|
||||
---
|
||||
|
||||
### 예시 1: 동기화 없는 경우 (문제 발생)
|
||||
아래는 동기화 없이 두 스레드가 계좌 잔액을 동시에 수정하는 예시입니다.
|
||||
|
||||
```java
|
||||
class BankAccount {
|
||||
private int balance = 1000; // 초기 잔액
|
||||
|
||||
public void withdraw(int amount) {
|
||||
if (balance >= amount) {
|
||||
System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
|
||||
balance -= amount; // 잔액 감소
|
||||
System.out.println(Thread.currentThread().getName() + " 출금 후 잔액: " + balance);
|
||||
}
|
||||
}
|
||||
|
||||
public int getBalance() {
|
||||
return balance;
|
||||
}
|
||||
}
|
||||
|
||||
public class NoSyncExample {
|
||||
public static void main(String[] args) {
|
||||
BankAccount account = new BankAccount();
|
||||
|
||||
Thread t1 = new Thread(() -> {
|
||||
account.withdraw(800);
|
||||
}, "스레드1");
|
||||
|
||||
Thread t2 = new Thread(() -> {
|
||||
account.withdraw(800);
|
||||
}, "스레드2");
|
||||
|
||||
t1.start();
|
||||
t2.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시 (무작위 결과)**:
|
||||
```
|
||||
스레드1 출금 시도: 800
|
||||
스레드2 출금 시도: 800
|
||||
스레드1 출금 후 잔액: 200
|
||||
스레드2 출금 후 잔액: -600
|
||||
```
|
||||
- **문제**: 두 스레드가 동시에 `withdraw`를 호출하면서 잔액이 음수가 되는 비정상적인 결과 발생. 이는 `if (balance >= amount)` 조건을 확인한 후 `balance -= amount`가 실행되기 전에 다른 스레드가 끼어들었기 때문입니다.
|
||||
|
||||
---
|
||||
|
||||
### 예시 2: `synchronized` 메서드로 동기화
|
||||
`withdraw` 메서드에 `synchronized`를 적용하면 한 번에 하나의 스레드만 메서드를 실행할 수 있습니다.
|
||||
|
||||
```java
|
||||
class BankAccount {
|
||||
private int balance = 1000;
|
||||
|
||||
public synchronized void withdraw(int amount) {
|
||||
if (balance >= amount) {
|
||||
System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
|
||||
try {
|
||||
Thread.sleep(100); // 경쟁 조건 시뮬레이션
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
balance -= amount;
|
||||
System.out.println(Thread.currentThread().getName() + " 출금 후 잔액: " + balance);
|
||||
} else {
|
||||
System.out.println(Thread.currentThread().getName() + " 잔액 부족");
|
||||
}
|
||||
}
|
||||
|
||||
public int getBalance() {
|
||||
return balance;
|
||||
}
|
||||
}
|
||||
|
||||
public class SyncMethodExample {
|
||||
public static void main(String[] args) {
|
||||
BankAccount account = new BankAccount();
|
||||
|
||||
Thread t1 = new Thread(() -> {
|
||||
account.withdraw(800);
|
||||
}, "스레드1");
|
||||
|
||||
Thread t2 = new Thread(() -> {
|
||||
account.withdraw(800);
|
||||
}, "스레드2");
|
||||
|
||||
t1.start();
|
||||
t2.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
스레드1 출금 시도: 800
|
||||
스레드1 출금 후 잔액: 200
|
||||
스레드2 출금 시도: 800
|
||||
스레드2 잔액 부족
|
||||
```
|
||||
- **설명**: `synchronized` 덕분에 첫 번째 스레드가 `withdraw`를 완료할 때까지 두 번째 스레드가 대기하며, 잔액이 음수로 떨어지지 않음.
|
||||
|
||||
---
|
||||
|
||||
### 예시 3: `synchronized` 블록으로 동기화
|
||||
메서드 전체가 아닌 특정 코드 블록만 동기화하려면 `synchronized` 블록을 사용합니다. 이때 동기화 대상 객체(락 객체)를 지정해야 합니다.
|
||||
|
||||
```java
|
||||
class BankAccount {
|
||||
private int balance = 1000;
|
||||
private final Object lock = new Object(); // 락 객체
|
||||
|
||||
public void withdraw(int amount) {
|
||||
System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
|
||||
synchronized (lock) { // 동기화 블록
|
||||
if (balance >= amount) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
balance -= amount;
|
||||
System.out.println(Thread.currentThread().getName() + " 출금 후 잔액: " + balance);
|
||||
} else {
|
||||
System.out.println(Thread.currentThread().getName() + " 잔액 부족");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getBalance() {
|
||||
return balance;
|
||||
}
|
||||
}
|
||||
|
||||
public class SyncBlockExample {
|
||||
public static void main(String[] args) {
|
||||
BankAccount account = new BankAccount();
|
||||
|
||||
Thread t1 = new Thread(() -> {
|
||||
account.withdraw(800);
|
||||
}, "스레드1");
|
||||
|
||||
Thread t2 = new Thread(() -> {
|
||||
account.withdraw(800);
|
||||
}, "스레드2");
|
||||
|
||||
t1.start();
|
||||
t2.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
스레드1 출금 시도: 800
|
||||
스레드2 출금 시도: 800
|
||||
스레드1 출금 후 잔액: 200
|
||||
스레드2 잔액 부족
|
||||
```
|
||||
- **설명**: `synchronized (lock)` 블록 내의 코드만 동기화되며, `lock` 객체를 통해 스레드 간 접근을 제어함. 메서드 전체를 동기화하는 것보다 유연성이 높음.
|
||||
|
||||
---
|
||||
|
||||
### 동기화의 장점과 주의점
|
||||
- **장점**:
|
||||
- 공유 자원에 대한 안전한 접근 보장.
|
||||
- 데이터 무결성 유지.
|
||||
- **주의점**:
|
||||
- 과도한 동기화는 성능 저하(데드락, 스레드 대기 시간 증가)를 유발할 수 있음.
|
||||
- 필요한 범위만 동기화하도록 설계해야 함.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
스레드 동기화는 멀티스레드 프로그래밍에서 필수적인 개념으로, 자바에서는 `synchronized` 키워드를 통해 간단히 구현할 수 있습니다. `synchronized` 메서드와 블록을 적절히 사용하면 경쟁 조건을 방지하고 안정적인 프로그램을 작성할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 동기화를 적용해 보세요!
|
||||
198
docs/Concurrency/03_Timer.md
Normal file
198
docs/Concurrency/03_Timer.md
Normal file
@@ -0,0 +1,198 @@
|
||||
### 자바의 `Timer`와 `TimerTask`에 대한 설명
|
||||
|
||||
자바에서 `Timer`와 `TimerTask`는 특정 작업을 일정 시간 간격으로 또는 지정된 시간에 실행하기 위한 유틸리티 클래스입니다. `java.util` 패키지에 포함되어 있으며, 스레드를 기반으로 동작하여 비동기적으로 작업을 스케줄링할 수 있습니다. 아래에서는 이와 관련된 클래스 및 메서드를 표로 정리하고, 예시와 함께 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### `Timer`와 `TimerTask` 관련 클래스 및 메서드 표
|
||||
|
||||
#### `Timer` 클래스
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------------------------|-----------|---------------------------------------------------------------------------------------|
|
||||
| `Timer()` | - | 새로운 `Timer` 객체를 생성 |
|
||||
| `schedule(TimerTask, long delay)`| `void` | 지정된 지연 시간(밀리초) 후에 `TimerTask`를 한 번 실행 |
|
||||
| `schedule(TimerTask, Date time)` | `void` | 지정된 시간(`Date`)에 `TimerTask`를 한 번 실행 |
|
||||
| `schedule(TimerTask, long delay, long period)` | `void` | 지정된 지연 시간 후 주기적으로(밀리초 단위) `TimerTask`를 반복 실행 |
|
||||
| `scheduleAtFixedRate(TimerTask, long delay, long period)` | `void` | 지정된 지연 시간 후 고정된 주기로 `TimerTask`를 반복 실행 (지연 보정 없음) |
|
||||
| `cancel()` | `void` | `Timer`를 종료하고 모든 스케줄링된 작업을 취소 |
|
||||
| `purge()` | `int` | 취소된 작업을 큐에서 제거하고 제거된 작업 수를 반환 |
|
||||
|
||||
#### `TimerTask` 클래스
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------------------------|-----------|---------------------------------------------------------------------------------------|
|
||||
| `run()` | `void` | `Timer`에 의해 실행될 작업을 정의 (추상 메서드, 오버라이드 필요) |
|
||||
| `cancel()` | `boolean` | 해당 `TimerTask`의 스케줄링을 취소하고 성공 여부를 반환 |
|
||||
| `scheduledExecutionTime()` | `long` | 해당 작업이 마지막으로 스케줄링된 시간을 반환 (밀리초 단위) |
|
||||
|
||||
---
|
||||
|
||||
### `Timer`와 `TimerTask` 설명 및 예시
|
||||
|
||||
`Timer`는 백그라운드 스레드를 생성하여 작업을 스케줄링하고, `TimerTask`는 실행할 작업을 정의하는 추상 클래스입니다. `TimerTask`를 상속받아 `run()` 메서드를 구현한 후, `Timer`의 `schedule` 메서드로 작업을 등록합니다. 주요 사용 사례는 주기적인 알림, 로그 기록, 상태 점검 등입니다.
|
||||
|
||||
#### 1. 기본 사용: 일정 시간 후 한 번 실행
|
||||
`schedule(TimerTask, long delay)`를 사용해 지정된 지연 시간 후 작업을 한 번 실행합니다.
|
||||
|
||||
```java
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class SimpleTimerExample {
|
||||
public static void main(String[] args) {
|
||||
Timer timer = new Timer();
|
||||
TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("작업 실행: " + System.currentTimeMillis());
|
||||
}
|
||||
};
|
||||
|
||||
timer.schedule(task, 2000); // 2초 후 실행
|
||||
System.out.println("스케줄링 완료: " + System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
스케줄링 완료: 1678321234567
|
||||
(2초 후)
|
||||
작업 실행: 1678321236567
|
||||
```
|
||||
|
||||
#### 2. 주기적 실행: 고정 주기 반복
|
||||
`schedule(TimerTask, long delay, long period)`를 사용해 주기적으로 작업을 반복합니다.
|
||||
|
||||
```java
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class PeriodicTimerExample {
|
||||
public static void main(String[] args) {
|
||||
Timer timer = new Timer();
|
||||
TimerTask task = new TimerTask() {
|
||||
int count = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
count++;
|
||||
System.out.println("작업 실행 " + count + "회: " + System.currentTimeMillis());
|
||||
if (count == 3) {
|
||||
timer.cancel(); // 3회 실행 후 종료
|
||||
System.out.println("타이머 종료");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
timer.schedule(task, 1000, 2000); // 1초 후 시작, 2초마다 반복
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
(1초 후)
|
||||
작업 실행 1회: 1678321235567
|
||||
(2초 후)
|
||||
작업 실행 2회: 1678321237567
|
||||
(2초 후)
|
||||
작업 실행 3회: 1678321239567
|
||||
타이머 종료
|
||||
```
|
||||
|
||||
#### 3. 고정 속도 실행: `scheduleAtFixedRate`
|
||||
`scheduleAtFixedRate`는 작업 간 간격을 고정하며, 지연이 발생하더라도 이를 보정하지 않고 계속 실행합니다.
|
||||
|
||||
```java
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class FixedRateTimerExample {
|
||||
public static void main(String[] args) {
|
||||
Timer timer = new Timer();
|
||||
TimerTask task = new TimerTask() {
|
||||
int count = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
count++;
|
||||
System.out.println("작업 실행 " + count + "회: " + System.currentTimeMillis());
|
||||
try {
|
||||
Thread.sleep(1500); // 작업 시간이 주기보다 길어짐
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (count == 3) {
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
timer.scheduleAtFixedRate(task, 1000, 1000); // 1초 후 시작, 1초마다 실행 시도
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
(1초 후)
|
||||
작업 실행 1회: 1678321235567
|
||||
(1.5초 후, 지연 발생)
|
||||
작업 실행 2회: 1678321237067
|
||||
(1.5초 후)
|
||||
작업 실행 3회: 1678321238567
|
||||
```
|
||||
- **설명**: `scheduleAtFixedRate`는 주기를 엄격히 유지하려 하지만, 작업 시간이 주기(1초)보다 길어지면 다음 실행이 바로 이어집니다.
|
||||
|
||||
#### 4. 작업 및 타이머 취소
|
||||
`TimerTask.cancel()`로 개별 작업을, `Timer.cancel()`로 모든 작업을 취소할 수 있습니다.
|
||||
|
||||
```java
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class CancelTimerExample {
|
||||
public static void main(String[] args) {
|
||||
Timer timer = new Timer();
|
||||
TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("작업 실행");
|
||||
}
|
||||
};
|
||||
|
||||
timer.schedule(task, 1000, 1000); // 1초 후 시작, 1초마다 반복
|
||||
try {
|
||||
Thread.sleep(2500); // 2.5초 대기
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
task.cancel(); // 개별 작업 취소
|
||||
System.out.println("작업 취소됨");
|
||||
timer.cancel(); // 타이머 종료
|
||||
System.out.println("타이머 종료");
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
(1초 후)
|
||||
작업 실행
|
||||
(1초 후)
|
||||
작업 실행
|
||||
작업 취소됨
|
||||
타이머 종료
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Timer`와 `TimerTask`의 장점과 한계
|
||||
- **장점**:
|
||||
- 간단한 스케줄링 작업에 적합.
|
||||
- 구현이 쉬움.
|
||||
- **한계**:
|
||||
- 단일 스레드로 동작하므로 하나의 작업이 지연되면 다른 작업에도 영향을 미침.
|
||||
- 예외 발생 시 `Timer`가 종료될 수 있음.
|
||||
- 복잡한 스케줄링에는 `ScheduledExecutorService`가 더 적합.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
`Timer`와 `TimerTask`는 주기적이거나 지연된 작업을 간단히 처리할 때 유용합니다. `schedule`과 `scheduleAtFixedRate`를 통해 다양한 스케줄링 요구를 충족할 수 있으며, `cancel`로 작업을 제어할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 활용해 보세요! 더 복잡한 경우에는 `java.util.concurrent.ScheduledExecutorService`를 고려하는 것도 좋은 대안입니다.
|
||||
226
docs/Concurrency/04_CompletableFuture.md
Normal file
226
docs/Concurrency/04_CompletableFuture.md
Normal file
@@ -0,0 +1,226 @@
|
||||
### `CompletableFuture`에 대한 설명
|
||||
|
||||
`CompletableFuture`는 자바의 `java.util.concurrent` 패키지에 포함된 클래스입니다. 비동기 작업을 처리하고 결과를 조합하며, 예외 처리를 유연하게 다룰 수 있는 강력한 도구입니다. `Future` 인터페이스를 확장하여 논블록킹(non-blocking) 방식으로 작업을 연결하고, 작업 완료 시 후속 작업을 정의할 수 있습니다. 아래에서 주요 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### `CompletableFuture` 주요 메서드 표
|
||||
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|---------------------------------------|----------------------------|----------------------------------------------------------------------------------------|
|
||||
| `runAsync(Runnable)` | `CompletableFuture<Void>` | 지정된 `Runnable`을 비동기적으로 실행 |
|
||||
| `supplyAsync(Supplier<T>)` | `CompletableFuture<T>` | 값을 제공하는 `Supplier`를 비동기적으로 실행하고 결과 반환 |
|
||||
| `thenApply(Function<T, U>)` | `CompletableFuture<U>` | 이전 작업의 결과에 함수를 적용하여 변환된 결과 반환 |
|
||||
| `thenAccept(Consumer<T>)` | `CompletableFuture<Void>` | 이전 작업의 결과를 소비(사용)하는 동작 수행 |
|
||||
| `thenRun(Runnable)` | `CompletableFuture<Void>` | 이전 작업 완료 후 결과와 관계없이 실행할 작업 정의 |
|
||||
| `thenCompose(Function<T, CompletableFuture<U>>)` | `CompletableFuture<U>` | 이전 결과에 따라 새로운 `CompletableFuture`를 반환 |
|
||||
| `exceptionally(Function<Throwable, T>)` | `CompletableFuture<T>` | 예외 발생 시 복구 로직을 정의 |
|
||||
| `handle(BiFunction<T, Throwable, U>)` | `CompletableFuture<U>` | 결과와 예외를 모두 처리하는 로직 정의 |
|
||||
| `complete(T)` | `boolean` | 작업을 수동으로 완료하고 결과 설정 |
|
||||
| `completeExceptionally(Throwable)` | `boolean` | 작업을 예외와 함께 수동으로 완료 |
|
||||
| `join()` | `T` | 작업 완료를 기다리고 결과 반환 (예외 발생 시 던짐) |
|
||||
| `get()` | `T` | `Future`처럼 결과를 기다리고 반환 (예외 처리 필요) |
|
||||
| `allOf(CompletableFuture<?>...)` | `CompletableFuture<Void>` | 여러 `CompletableFuture`가 모두 완료될 때까지 대기 |
|
||||
| `anyOf(CompletableFuture<?>...)` | `CompletableFuture<Object>`| 여러 `CompletableFuture` 중 하나가 완료되면 결과 반환 |
|
||||
| `whenComplete(BiConsumer<T, Throwable>)` | `CompletableFuture<T>` | 작업 완료 시 결과와 예외를 처리 |
|
||||
|
||||
---
|
||||
|
||||
### `CompletableFuture` 설명 및 예시
|
||||
|
||||
`CompletableFuture`는 비동기 프로그래밍을 단순화하며, 작업 간 의존성을 표현하고 예외를 처리하는 데 유용합니다. 기본적으로 `ForkJoinPool.commonPool()`을 사용하지만, 사용자 지정 `Executor`를 전달할 수도 있습니다.
|
||||
|
||||
#### 1. 기본 비동기 작업: `supplyAsync`
|
||||
비동기적으로 값을 생성합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class SupplyAsyncExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "비동기 작업 결과";
|
||||
});
|
||||
|
||||
System.out.println("작업 시작");
|
||||
String result = future.get();
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
작업 시작
|
||||
(1초 후)
|
||||
비동기 작업 결과
|
||||
```
|
||||
|
||||
#### 2. 결과 변환: `thenApply`
|
||||
이전 작업의 결과를 변환합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ThenApplyExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
|
||||
.thenApply(s -> s + " World")
|
||||
.thenApply(String::toUpperCase);
|
||||
|
||||
String result = future.get();
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
HELLO WORLD
|
||||
```
|
||||
- **설명**: `thenApply`로 결과에 연속적인 변환을 적용.
|
||||
|
||||
#### 3. 결과 소비: `thenAccept`
|
||||
결과를 소비하고 후속 작업을 정의합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ThenAcceptExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture.supplyAsync(() -> "데이터 처리")
|
||||
.thenAccept(result -> System.out.println("결과: " + result))
|
||||
.thenRun(() -> System.out.println("작업 완료"));
|
||||
|
||||
Thread.sleep(1000); // 비동기 작업 완료 대기
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
결과: 데이터 처리
|
||||
작업 완료
|
||||
```
|
||||
|
||||
#### 4. 예외 처리: `exceptionally`
|
||||
예외 발생 시 복구 로직을 정의합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ExceptionallyExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
|
||||
if (true) throw new RuntimeException("에러 발생");
|
||||
return "성공";
|
||||
}).exceptionally(throwable -> "복구: " + throwable.getMessage());
|
||||
|
||||
String result = future.get();
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
복구: java.lang.RuntimeException: 에러 발생
|
||||
```
|
||||
|
||||
#### 5. 작업 조합: `thenCompose`
|
||||
이전 결과에 따라 새로운 `CompletableFuture`를 연결합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ThenComposeExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "첫 번째")
|
||||
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " 두 번째"));
|
||||
|
||||
String result = future.get();
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
첫 번째 두 번째
|
||||
```
|
||||
|
||||
#### 6. 여러 작업 조합: `allOf`
|
||||
여러 작업의 완료를 기다립니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class AllOfExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
|
||||
try { Thread.sleep(1000); } catch (Exception e) {}
|
||||
return "작업 1";
|
||||
});
|
||||
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "작업 2");
|
||||
|
||||
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
|
||||
all.thenRun(() -> {
|
||||
try {
|
||||
System.out.println(future1.get() + " & " + future2.get());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).join();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
작업 1 & 작업 2
|
||||
```
|
||||
|
||||
#### 7. 수동 완료: `complete`
|
||||
작업을 외부에서 수동으로 완료합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class CompleteExample {
|
||||
public static void main(String[] args) {
|
||||
CompletableFuture<String> future = new CompletableFuture<>();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
future.complete("수동 완료");
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
|
||||
String result = future.join();
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
(1초 후)
|
||||
수동 완료
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `CompletableFuture`의 장점과 주의점
|
||||
- **장점**:
|
||||
- 비동기 작업의 연속적 처리와 조합 가능.
|
||||
- 예외 처리가 명시적이고 유연.
|
||||
- 논블록킹 방식으로 작업 연결 가능.
|
||||
- **주의점**:
|
||||
- `get()`이나 `join()`을 과도히 사용하면 블록킹으로 인해 비동기 이점이 줄어듦.
|
||||
- 복잡한 조합 시 코드 가독성 관리 필요.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
`CompletableFuture`는 비동기 작업을 정의하고, 결과를 변환하며, 예외를 처리하는 데 탁월합니다. `supplyAsync`, `thenApply`, `exceptionally`, `allOf` 등 다양한 메서드를 활용하면 작업 흐름을 효율적으로 설계할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 비동기 프로그래밍을 구현해 보세요!
|
||||
401
docs/Concurrency/05_Executor.md
Normal file
401
docs/Concurrency/05_Executor.md
Normal file
@@ -0,0 +1,401 @@
|
||||
### 자바의 `Executor`와 관련된 설명
|
||||
|
||||
자바의 `Executor` 프레임워크는 스레드 풀을 관리하고 작업을 비동기적으로 실행하기 위한 강력한 도구로, `java.util.concurrent` 패키지에 포함되어 있습니다. `Executor` 인터페이스를 기반으로, 스레드 생성과 관리를 직접 다루는 대신 작업 단위로 실행을 위임하여 코드의 가독성과 효율성을 높입니다. 주요 클래스와 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### `Executor`와 관련된 클래스 및 메서드 표
|
||||
|
||||
#### `Executor` 인터페이스
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-------------------------|-----------|---------------------------------------------------|
|
||||
| `execute(Runnable)` | `void` | `Runnable` 작업을 실행하도록 스레드 풀에 위임 |
|
||||
|
||||
#### `ExecutorService` 인터페이스 (확장 인터페이스)
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------------------------|-------------------|----------------------------------------------------------------------|
|
||||
| `submit(Runnable)` | `Future<?>` | `Runnable` 작업을 실행하고 결과를 추적할 수 있는 `Future` 반환 |
|
||||
| `submit(Callable<T>)` | `Future<T>` | `Callable` 작업을 실행하고 결과를 반환하는 `Future` 반환 |
|
||||
| `shutdown()` | `void` | 새로운 작업을 받지 않고, 기존 작업 완료 후 종료 |
|
||||
| `shutdownNow()` | `List<Runnable>` | 즉시 종료 시도하고 실행 대기 중인 작업 목록 반환 |
|
||||
| `awaitTermination(long, TimeUnit)` | `boolean` | 지정된 시간 동안 종료를 기다리며, 완료 여부 반환 |
|
||||
| `invokeAll(Collection<Callable<T>>)` | `List<Future<T>>` | 모든 `Callable` 작업을 실행하고 결과 목록 반환 |
|
||||
| `invokeAny(Collection<Callable<T>>)` | `T` | 주어진 `Callable` 중 하나가 완료되면 그 결과를 반환 |
|
||||
|
||||
#### `Executors` 유틸리티 클래스 (정적 팩토리 메서드)
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------------------------|-------------------|----------------------------------------------------------------------|
|
||||
| `newFixedThreadPool(int n)` | `ExecutorService` | 고정 크기의 스레드 풀 생성 |
|
||||
| `newSingleThreadExecutor()` | `ExecutorService` | 단일 스레드로 작업을 순차적으로 실행하는 풀 생성 |
|
||||
| `newCachedThreadPool()` | `ExecutorService` | 필요에 따라 스레드를 생성/재사용하는 동적 풀 생성 |
|
||||
| `newScheduledThreadPool(int n)` | `ScheduledExecutorService` | 주기적 작업을 스케줄링할 수 있는 스레드 풀 생성 |
|
||||
|
||||
#### `ScheduledExecutorService` 인터페이스 (확장 인터페이스)
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------------------------|-------------------|----------------------------------------------------------------------|
|
||||
| `schedule(Runnable, long, TimeUnit)` | `ScheduledFuture<?>` | 지정된 지연 시간 후 작업 실행 |
|
||||
| `scheduleAtFixedRate(Runnable, long, long, TimeUnit)` | `ScheduledFuture<?>` | 고정 주기로 작업 반복 실행 |
|
||||
| `scheduleWithFixedDelay(Runnable, long, long, TimeUnit)` | `ScheduledFuture<?>` | 작업 완료 후 고정 지연을 두고 반복 실행 |
|
||||
|
||||
#### 관련 클래스
|
||||
| 클래스명 | 설명 |
|
||||
|-------------------------|----------------------------------------------------------------------|
|
||||
| `ThreadPoolExecutor` | 스레드 풀의 세부 설정(코어 풀 크기, 최대 풀 크기 등)을 커스터마이징 가능 |
|
||||
| `Future<T>` | 비동기 작업의 결과를 추적하거나 취소할 수 있는 인터페이스 |
|
||||
| `Callable<T>` | `Runnable`과 유사하나 결과를 반환하며 예외를 던질 수 있는 인터페이스 |
|
||||
|
||||
---
|
||||
|
||||
### `Executor` 설명 및 예시
|
||||
|
||||
`Executor` 프레임워크는 스레드 관리의 복잡성을 줄이고, 작업 실행을 추상화하여 재사용 가능한 스레드 풀을 제공합니다. 아래에서 주요 사용 사례를 예시로 설명합니다.
|
||||
|
||||
#### 1. 기본 사용: `ExecutorService`와 `newFixedThreadPool`
|
||||
고정 크기의 스레드 풀을 생성하고 작업을 실행합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class FixedThreadPoolExample {
|
||||
public static void main(String[] args) {
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2); // 2개 스레드 풀
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int taskId = i;
|
||||
executor.execute(() -> {
|
||||
System.out.println("작업 " + taskId + " 실행 중: " + Thread.currentThread().getName());
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executor.shutdown(); // 작업 제출 후 종료 요청
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
작업 0 실행 중: pool-1-thread-1
|
||||
작업 1 실행 중: pool-1-thread-2
|
||||
(1초 후)
|
||||
작업 2 실행 중: pool-1-thread-1
|
||||
작업 3 실행 중: pool-1-thread-2
|
||||
(1초 후)
|
||||
작업 4 실행 중: pool-1-thread-1
|
||||
```
|
||||
- **설명**: 2개의 스레드가 최대 2개 작업을 동시에 처리하며, 나머지는 대기 후 실행됨.
|
||||
|
||||
#### 2. `Callable`과 `Future`로 결과 받기
|
||||
`submit`을 사용해 결과를 반환하는 작업을 실행합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class CallableExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
Callable<Integer> task = () -> {
|
||||
Thread.sleep(1000);
|
||||
return 42;
|
||||
};
|
||||
|
||||
Future<Integer> future = executor.submit(task);
|
||||
System.out.println("작업 제출 완료");
|
||||
|
||||
Integer result = future.get(); // 작업 완료까지 대기 후 결과 가져오기
|
||||
System.out.println("결과: " + result);
|
||||
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
작업 제출 완료
|
||||
(1초 후)
|
||||
결과: 42
|
||||
```
|
||||
- **설명**: `Future.get()`은 작업이 완료될 때까지 블록하며, 결과를 반환받음.
|
||||
|
||||
#### 3. 주기적 실행: `ScheduledExecutorService`
|
||||
`scheduleAtFixedRate`로 주기적으로 작업을 실행합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ScheduledExecutorExample {
|
||||
public static void main(String[] args) {
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
|
||||
Runnable task = () -> System.out.println("작업 실행: " + System.currentTimeMillis());
|
||||
|
||||
executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS); // 1초 후 시작, 2초 주기
|
||||
|
||||
try {
|
||||
Thread.sleep(5000); // 5초 동안 실행
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
(1초 후)
|
||||
작업 실행: 1678321234567
|
||||
(2초 후)
|
||||
작업 실행: 1678321236567
|
||||
(2초 후)
|
||||
작업 실행: 1678321238567
|
||||
```
|
||||
- **설명**: 작업이 고정된 2초 주기로 실행됨.
|
||||
|
||||
#### 4. `invokeAll`로 여러 작업 실행
|
||||
여러 `Callable` 작업을 한 번에 실행하고 결과를 수집합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class InvokeAllExample {
|
||||
public static void main(String[] args) throws InterruptedException, ExecutionException {
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
|
||||
List<Callable<String>> tasks = Arrays.asList(
|
||||
() -> { Thread.sleep(1000); return "작업 1 완료"; },
|
||||
() -> { Thread.sleep(500); return "작업 2 완료"; },
|
||||
() -> { Thread.sleep(1500); return "작업 3 완료"; }
|
||||
);
|
||||
|
||||
List<Future<String>> futures = executor.invokeAll(tasks);
|
||||
|
||||
for (Future<String> future : futures) {
|
||||
System.out.println(future.get());
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
작업 1 완료
|
||||
작업 2 완료
|
||||
작업 3 완료
|
||||
```
|
||||
- **설명**: 모든 작업이 완료될 때까지 대기한 후 결과를 순차적으로 출력.
|
||||
|
||||
---
|
||||
|
||||
### `Executor`의 장점과 주의점
|
||||
- **장점**:
|
||||
- 스레드 풀을 재사용하여 리소스 낭비 감소.
|
||||
- 작업 실행과 스레드 관리를 분리하여 코드 간소화.
|
||||
- 주기적 작업 및 결과 처리가 용이.
|
||||
- **주의점**:
|
||||
- `shutdown()`을 호출하지 않으면 프로그램이 종료되지 않을 수 있음.
|
||||
- 스레드 풀 크기와 작업 부하를 적절히 조정해야 성능 최적화 가능.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
`Executor` 프레임워크는 스레드 관리의 복잡성을 줄이고, 다양한 실행 패턴(단일 실행, 주기적 실행, 병렬 실행 등)을 지원합니다. `ExecutorService`, `ScheduledExecutorService`, `Executors`를 활용하면 효율적인 멀티스레드 애플리케이션을 쉽게 구현할 수 있습니다. 위 예시를 참고하여 실제 프로젝트에 적용해 보세요!
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### `ScheduledExecutorService`와 다른 스케줄링 도구 비교
|
||||
|
||||
자바에서 주기적이거나 지연된 작업을 스케줄링할 때 `ScheduledExecutorService`는 강력한 도구로 사용됩니다. 하지만 `java.util.Timer`와 `CompletableFuture`와 같은 대안도 있으며, 각각의 특징과 사용 사례가 다릅니다. 아래에서 `ScheduledExecutorService`를 `Timer`와 `CompletableFuture`와 비교하여 표로 정리하고, 예시와 함께 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### 비교 표
|
||||
|
||||
| **특징** | **`ScheduledExecutorService`** | **`Timer`** | **`CompletableFuture`** |
|
||||
|---------------------------|-----------------------------------------------------|------------------------------------------|------------------------------------------|
|
||||
| **패키지** | `java.util.concurrent` | `java.util` | `java.util.concurrent` |
|
||||
| **스레드 모델** | 스레드 풀 기반 (다중 스레드 지원) | 단일 스레드 기반 | 기본 풀(`ForkJoinPool`) 또는 사용자 지정 |
|
||||
| **작업 타입** | `Runnable`, `Callable` | `TimerTask` | `Runnable`, `Supplier` |
|
||||
| **스케줄링 메서드** | `schedule`, `scheduleAtFixedRate`, `scheduleWithFixedDelay` | `schedule`, `scheduleAtFixedRate` | `supplyAsync` + `then` 조합으로 간접 지원 |
|
||||
| **고정 주기 지원** | `scheduleAtFixedRate` (고정 주기), `scheduleWithFixedDelay` (고정 지연) | `scheduleAtFixedRate` (고정 주기) | 직접 지원 없음, 별도 로직 필요 |
|
||||
| **예외 처리** | 개별 작업 예외가 전체에 영향 없음 | 예외 발생 시 `Timer` 종료 가능 | `exceptionally` 등으로 명시적 처리 가능 |
|
||||
| **취소 기능** | `ScheduledFuture.cancel()` | `TimerTask.cancel()`, `Timer.cancel()` | `complete()`, `cancel()` |
|
||||
| **복잡한 작업 조합** | 제한적 | 없음 | `thenApply`, `allOf` 등으로 가능 |
|
||||
| **성능 및 확장성** | 높은 부하와 복잡한 작업에 적합 | 간단한 작업에 적합 | 비동기 작업 조합에 강력 |
|
||||
| **사용 용도** | 주기적 작업, 다중 스레드 스케줄링 | 간단한 단일 스레드 스케줄링 | 비동기 작업 흐름 관리 |
|
||||
|
||||
---
|
||||
|
||||
### 상세 설명 및 예시
|
||||
|
||||
#### 1. `ScheduledExecutorService`
|
||||
- **특징**: 스레드 풀을 활용하여 다중 스레드로 작업을 처리하며, 예외 발생 시 다른 작업에 영향을 주지 않습니다.
|
||||
- **예시**: 주기적으로 상태를 점검하는 작업.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ScheduledExecutorExample {
|
||||
public static void main(String[] args) {
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
|
||||
|
||||
Runnable task = () -> System.out.println("실행: " + System.currentTimeMillis());
|
||||
executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
|
||||
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
실행: 1678321234567
|
||||
실행: 1678321236567
|
||||
실행: 1678321238567
|
||||
```
|
||||
|
||||
#### 2. `Timer`
|
||||
- **특징**: 단일 스레드로 동작하며, 작업 중 예외가 발생하면 `Timer`가 종료될 수 있습니다.
|
||||
- **예시**: 간단한 알림 기능.
|
||||
|
||||
```java
|
||||
import java.util.*;
|
||||
|
||||
public class TimerExample {
|
||||
public static void main(String[] args) {
|
||||
Timer timer = new Timer();
|
||||
TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("실행: " + System.currentTimeMillis());
|
||||
}
|
||||
};
|
||||
|
||||
timer.scheduleAtFixedRate(task, 1000, 2000);
|
||||
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
실행: 1678321234567
|
||||
실행: 1678321236567
|
||||
실행: 1678321238567
|
||||
```
|
||||
|
||||
#### 3. `CompletableFuture`
|
||||
- **특징**: 주기적 스케줄링은 직접 지원하지 않지만, 비동기 작업 조합과 예외 처리가 뛰어납니다.
|
||||
- **예시**: 작업 완료 후 후속 작업 실행.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class CompletableFutureExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("실행: " + System.currentTimeMillis());
|
||||
}).thenRun(() -> System.out.println("후속 작업"));
|
||||
|
||||
future.get();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
실행: 1678321235567
|
||||
후속 작업
|
||||
```
|
||||
|
||||
#### 비교 예시: 예외 발생 시 동작
|
||||
- **`ScheduledExecutorService`**와 **`Timer`**의 차이점을 보여줍니다.
|
||||
|
||||
```java
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ExceptionComparison {
|
||||
public static void main(String[] args) {
|
||||
// Timer 예시
|
||||
Timer timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
int count = 0;
|
||||
@Override
|
||||
public void run() {
|
||||
count++;
|
||||
System.out.println("Timer 실행 " + count);
|
||||
if (count == 2) throw new RuntimeException("Timer 예외");
|
||||
}
|
||||
}, 0, 1000);
|
||||
|
||||
// ScheduledExecutorService 예시
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
executor.scheduleAtFixedRate(() -> {
|
||||
int count = 0;
|
||||
count++;
|
||||
System.out.println("Executor 실행 " + count);
|
||||
if (count == 2) throw new RuntimeException("Executor 예외");
|
||||
}, 0, 1, TimeUnit.SECONDS);
|
||||
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
timer.cancel();
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예상**:
|
||||
```
|
||||
Timer 실행 1
|
||||
Timer 실행 2
|
||||
(예외 발생 후 Timer 종료, 더 이상 실행 안 됨)
|
||||
|
||||
Executor 실행 1
|
||||
Executor 실행 2
|
||||
(예외 발생 후에도 다른 작업은 계속 실행 가능)
|
||||
```
|
||||
- **설명**: `Timer`는 예외로 종료되지만, `ScheduledExecutorService`는 영향을 받지 않음.
|
||||
|
||||
---
|
||||
|
||||
### 선택 가이드
|
||||
- **`ScheduledExecutorService`**:
|
||||
- 다중 스레드와 안정성이 필요한 주기적 작업.
|
||||
- 복잡한 작업 관리와 확장성 요구 시.
|
||||
- **`Timer`**:
|
||||
- 간단한 단일 스레드 작업.
|
||||
- 경량 애플리케이션에서 최소한의 스케줄링 필요 시.
|
||||
- **`CompletableFuture`**:
|
||||
- 비동기 작업 흐름을 조합하거나 단일 작업의 후속 처리가 필요할 때.
|
||||
- 주기적 스케줄링보다는 결과 기반 작업에 적합.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
`ScheduledExecutorService`는 스레드 풀 기반으로 안정성과 유연성을 제공하며, `Timer`는 간단한 작업에 적합하지만 취약점이 있습니다. `CompletableFuture`는 주기적 스케줄링보다는 비동기 작업 조합에 강점을 가집니다. 요구사항에 따라 적절한 도구를 선택해 사용하세요!
|
||||
210
docs/Concurrency/06_Runnable,Callable,Future.md
Normal file
210
docs/Concurrency/06_Runnable,Callable,Future.md
Normal file
@@ -0,0 +1,210 @@
|
||||
### 자바의 `Runnable`, `Callable`, `Future`, `CompletableFuture`에 대한 설명
|
||||
|
||||
자바에서 멀티스레드 프로그래밍을 지원하는 주요 인터페이스와 클래스는 `Runnable`, `Callable`, `Future`, 그리고 `CompletableFuture`입니다. 이들은 작업을 정의하고, 실행하며, 결과를 처리하는 데 사용됩니다. 아래에서 각 클래스와 관련 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### 관련 클래스 및 메서드 표
|
||||
|
||||
#### `Runnable` 인터페이스
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------|-----------|---------------------------------------------------|
|
||||
| `run()` | `void` | 스레드가 실행할 작업을 정의 (추상 메서드) |
|
||||
|
||||
#### `Callable<V>` 인터페이스
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------|-----------|---------------------------------------------------|
|
||||
| `call()` | `V` | 결과를 반환하며 예외를 던질 수 있는 작업 정의 |
|
||||
|
||||
#### `Future<V>` 인터페이스
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-------------------------|-----------|---------------------------------------------------|
|
||||
| `get()` | `V` | 작업 결과를 반환 (완료될 때까지 대기) |
|
||||
| `get(long, TimeUnit)` | `V` | 지정된 시간 동안 대기 후 결과 반환 |
|
||||
| `cancel(boolean)` | `boolean` | 작업을 취소 시도하고 성공 여부 반환 |
|
||||
| `isDone()` | `boolean` | 작업이 완료되었는지 확인 |
|
||||
| `isCancelled()` | `boolean` | 작업이 취소되었는지 확인 |
|
||||
|
||||
#### `CompletableFuture<V>` 클래스
|
||||
| 메서드명 | 반환 타입 | 설명 |
|
||||
|-----------------------------------|-------------------------|----------------------------------------------------------------------|
|
||||
| `runAsync(Runnable)` | `CompletableFuture<Void>` | 비동기적으로 `Runnable` 실행 |
|
||||
| `supplyAsync(Supplier<V>)` | `CompletableFuture<V>` | 비동기적으로 값을 제공하는 작업 실행 |
|
||||
| `thenApply(Function<T, U>)` | `CompletableFuture<U>` | 이전 작업의 결과에 함수를 적용하여 변환된 결과 반환 |
|
||||
| `thenAccept(Consumer<T>)` | `CompletableFuture<Void>` | 결과에 대한 소비 동작 수행 |
|
||||
| `thenRun(Runnable)` | `CompletableFuture<Void>` | 결과와 상관없이 후속 작업 실행 |
|
||||
| `exceptionally(Function<Throwable, T>)` | `CompletableFuture<T>` | 예외 발생 시 처리 로직 정의 |
|
||||
| `complete(V)` | `boolean` | 작업을 수동으로 완료하고 결과 설정 |
|
||||
| `join()` | `V` | 작업 완료를 기다리고 결과 반환 (스레드 풀에서 사용 시 주의) |
|
||||
| `allOf(CompletableFuture<?>...)` | `CompletableFuture<Void>` | 여러 `CompletableFuture`가 모두 완료될 때까지 대기 |
|
||||
| `anyOf(CompletableFuture<?>...)` | `CompletableFuture<Object>` | 여러 `CompletableFuture` 중 하나가 완료되면 결과 반환 |
|
||||
|
||||
---
|
||||
|
||||
### 설명 및 예시
|
||||
|
||||
이들 클래스는 멀티스레드 작업의 정의와 실행, 결과 처리를 다룹니다. `Runnable`은 기본적인 작업 단위이고, `Callable`은 결과를 반환하며, `Future`는 결과를 추적하고, `CompletableFuture`는 비동기 작업의 조합과 예외 처리를 지원합니다.
|
||||
|
||||
#### 1. `Runnable`: 기본 작업 정의
|
||||
`Runnable`은 결과를 반환하지 않는 간단한 작업을 정의합니다.
|
||||
|
||||
```java
|
||||
public class RunnableExample {
|
||||
public static void main(String[] args) {
|
||||
Runnable task = () -> {
|
||||
System.out.println("Runnable 작업 실행: " + Thread.currentThread().getName());
|
||||
};
|
||||
|
||||
Thread thread = new Thread(task);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
Runnable 작업 실행: Thread-0
|
||||
```
|
||||
|
||||
#### 2. `Callable`과 `Future`: 결과 반환
|
||||
`Callable`은 값을 반환하며, `Future`로 결과를 받습니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class CallableFutureExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
Callable<String> task = () -> {
|
||||
Thread.sleep(1000);
|
||||
return "Callable 작업 완료";
|
||||
};
|
||||
|
||||
Future<String> future = executor.submit(task);
|
||||
System.out.println("작업 제출 완료");
|
||||
|
||||
String result = future.get(); // 완료 대기
|
||||
System.out.println(result);
|
||||
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
작업 제출 완료
|
||||
(1초 후)
|
||||
Callable 작업 완료
|
||||
```
|
||||
- **설명**: `Future.get()`은 작업이 끝날 때까지 대기하며 결과를 반환.
|
||||
|
||||
#### 3. `CompletableFuture`: 비동기 작업 조합
|
||||
`CompletableFuture`는 비동기 작업을 연결하고 결과를 처리하는 데 유용합니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class CompletableFutureExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "비동기 결과";
|
||||
}).thenApply(result -> result + " 처리됨")
|
||||
.thenApply(result -> result.toUpperCase());
|
||||
|
||||
String result = future.get();
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
(1초 후)
|
||||
비동기 결과 처리됨
|
||||
```
|
||||
- **설명**: `supplyAsync`로 값을 생성하고, `thenApply`로 결과를 변환.
|
||||
|
||||
#### 4. `CompletableFuture` 예외 처리 및 조합
|
||||
예외 처리와 여러 작업의 동시 실행을 다룹니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class CompletableFutureAdvancedExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "작업 1";
|
||||
});
|
||||
|
||||
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
|
||||
throw new RuntimeException("작업 2 실패");
|
||||
}).exceptionally(throwable -> "복구: " + throwable.getMessage());
|
||||
|
||||
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
|
||||
all.thenRun(() -> {
|
||||
try {
|
||||
System.out.println(future1.get() + " & " + future2.get());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).join(); // 모든 작업 완료 대기
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
작업 1 & 복구: java.lang.RuntimeException: 작업 2 실패
|
||||
```
|
||||
- **설명**: `exceptionally`로 예외를 처리하고, `allOf`로 여러 작업을 조합.
|
||||
|
||||
#### 5. `CompletableFuture`로 수동 완료
|
||||
`complete`를 사용해 작업을 수동으로 완료할 수 있습니다.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ManualCompleteExample {
|
||||
public static void main(String[] args) {
|
||||
CompletableFuture<String> future = new CompletableFuture<>();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
future.complete("수동 완료");
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
|
||||
String result = future.join();
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **출력 예시**:
|
||||
```
|
||||
(1초 후)
|
||||
수동 완료
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 비교와 특징
|
||||
- **`Runnable`**: 결과 없음, 단순 작업 실행.
|
||||
- **`Callable`**: 결과 반환, `Future`와 함께 사용.
|
||||
- **`Future`**: 비동기 작업 상태 추적, 블록킹 방식.
|
||||
- **`CompletableFuture`**: 비동기 작업 조합, 예외 처리, 논블록킹 지원.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
`Runnable`과 `Callable`은 작업을 정의하고, `Future`는 결과를 추적하며, `CompletableFuture`는 복잡한 비동기 작업을 유연하게 처리합니다. 상황에 따라 적합한 클래스를 선택해 사용하면 효율적인 멀티스레드 프로그래밍이 가능합니다. 위 예시를 참고하여 실제 애플리케이션에 적용해 보세요!
|
||||
142
docs/Date-Time API.md
Normal file
142
docs/Date-Time API.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# **자바 Date-Time API 쉽게 배우기**
|
||||
|
||||
## **1. Date-Time API란?**
|
||||
자바에서 날짜와 시간을 다루는 API는 크게 두 가지가 있다.
|
||||
1. `java.util.Date`, `java.util.Calendar` (구버전, 사용 비추천)
|
||||
2. `java.time` 패키지 (Java 8 이후, 최신 API)
|
||||
|
||||
Java 8부터 `java.time` 패키지가 추가되면서 날짜와 시간을 **더 직관적이고 강력하게 다룰 수 있게 되었다.**
|
||||
이번 글에서는 `java.time` 패키지를 중심으로 설명하겠다.
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `LocalDate` (날짜만 다루는 클래스)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `now()` | 현재 날짜 가져오기 |
|
||||
| `of(year, month, dayOfMonth)` | 특정 날짜 생성 |
|
||||
| `plusDays(n)`, `minusDays(n)` | n일 더하기 / 빼기 |
|
||||
| `getYear()`, `getMonth()`, `getDayOfMonth()` | 연도, 월, 일 가져오기 |
|
||||
| `isBefore(date)`, `isAfter(date)` | 날짜 비교 |
|
||||
|
||||
**예제 코드**
|
||||
```java
|
||||
LocalDate today = LocalDate.now();
|
||||
System.out.println(today); // 2025-03-06
|
||||
|
||||
LocalDate specificDate = LocalDate.of(2025, 12, 25);
|
||||
System.out.println(specificDate); // 2025-12-25
|
||||
|
||||
LocalDate nextWeek = today.plusDays(7);
|
||||
System.out.println(nextWeek); // 2025-03-13
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(2) `LocalTime` (시간만 다루는 클래스)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `now()` | 현재 시간 가져오기 |
|
||||
| `of(hour, minute, second)` | 특정 시간 생성 |
|
||||
| `plusHours(n)`, `minusMinutes(n)` | 시간 또는 분 더하기 / 빼기 |
|
||||
| `getHour()`, `getMinute()`, `getSecond()` | 시, 분, 초 가져오기 |
|
||||
|
||||
**예제 코드**
|
||||
```java
|
||||
LocalTime now = LocalTime.now();
|
||||
System.out.println(now); // 14:30:15 (예시)
|
||||
|
||||
LocalTime meetingTime = LocalTime.of(10, 30);
|
||||
System.out.println(meetingTime); // 10:30
|
||||
|
||||
LocalTime afterOneHour = now.plusHours(1);
|
||||
System.out.println(afterOneHour); // 15:30:15 (예시)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(3) `LocalDateTime` (날짜 + 시간)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `now()` | 현재 날짜와 시간 가져오기 |
|
||||
| `of(LocalDate, LocalTime)` | 특정 날짜와 시간 생성 |
|
||||
| `plusDays(n)`, `plusHours(n)` | 날짜/시간 더하기 |
|
||||
| `getYear()`, `getMonth()`, `getDayOfMonth()` | 날짜 정보 가져오기 |
|
||||
|
||||
**예제 코드**
|
||||
```java
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
System.out.println(now); // 2025-03-06T14:30:15 (예시)
|
||||
|
||||
LocalDateTime eventTime = LocalDateTime.of(2025, 12, 25, 18, 30);
|
||||
System.out.println(eventTime); // 2025-12-25T18:30
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(4) `ZonedDateTime` (시간대 정보 포함)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `now(ZoneId.of("Asia/Seoul"))` | 특정 시간대의 현재 시간 가져오기 |
|
||||
| `of(year, month, day, hour, min, sec, ZoneId.of("UTC"))` | 특정 시간대의 날짜/시간 생성 |
|
||||
|
||||
**예제 코드**
|
||||
```java
|
||||
ZonedDateTime seoulTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
|
||||
System.out.println(seoulTime); // 2025-03-06T14:30:15+09:00[Asia/Seoul]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(5) 날짜 포맷 변환 (`DateTimeFormatter`)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `ofPattern("yyyy-MM-dd HH:mm")` | 날짜/시간 형식 지정 |
|
||||
| `format(DateTimeFormatter formatter)` | 날짜/시간을 문자열로 변환 |
|
||||
| `parse(String, DateTimeFormatter formatter)` | 문자열을 날짜/시간으로 변환 |
|
||||
|
||||
**예제 코드**
|
||||
```java
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||
|
||||
String formattedDate = now.format(formatter);
|
||||
System.out.println(formattedDate); // 2025-03-06 14:30
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(6) 날짜 차이 계산 (`Duration`, `Period`)**
|
||||
| 클래스 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `Duration.between(time1, time2)` | 시간 차이 계산 (초, 분, 시간 단위) |
|
||||
| `Period.between(date1, date2)` | 날짜 차이 계산 (년, 월, 일 단위) |
|
||||
|
||||
**예제 코드**
|
||||
```java
|
||||
LocalDate date1 = LocalDate.of(2025, 1, 1);
|
||||
LocalDate date2 = LocalDate.of(2025, 3, 6);
|
||||
|
||||
Period period = Period.between(date1, date2);
|
||||
System.out.println(period.getMonths() + "개월 " + period.getDays() + "일"); // 2개월 5일
|
||||
|
||||
LocalTime time1 = LocalTime.of(10, 0);
|
||||
LocalTime time2 = LocalTime.of(12, 30);
|
||||
|
||||
Duration duration = Duration.between(time1, time2);
|
||||
System.out.println(duration.toHours() + "시간 " + duration.toMinutes() + "분"); // 2시간 30분
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **3. 정리**
|
||||
✅ **`LocalDate`** : 날짜만 다룸 (`2025-03-06`)
|
||||
✅ **`LocalTime`** : 시간만 다룸 (`14:30:15`)
|
||||
✅ **`LocalDateTime`** : 날짜 + 시간 (`2025-03-06T14:30:15`)
|
||||
✅ **`ZonedDateTime`** : 시간대 정보 포함 (`+09:00[Asia/Seoul]`)
|
||||
✅ **`DateTimeFormatter`** : 날짜 형식 변환 (`yyyy-MM-dd HH:mm`)
|
||||
✅ **`Period` / `Duration`** : 날짜/시간 차이 계산
|
||||
|
||||
자바의 `java.time` 패키지를 활용하면 **날짜와 시간을 더욱 쉽게 관리할 수 있다!**
|
||||
163
docs/EqualsAndHashCode.md
Normal file
163
docs/EqualsAndHashCode.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# **자바의 `equals()` 와 `hashCode()` 메서드 완벽 정리**
|
||||
|
||||
## **1. `equals()` 와 `hashCode()`란?**
|
||||
자바에서 객체를 비교할 때, `==` 연산자는 **메모리 주소를 비교**한다.
|
||||
하지만 **객체의 내용이 같은지를 비교**하려면 `equals()`를 재정의해야 한다.
|
||||
|
||||
또한, **HashMap, HashSet 같은 컬렉션에서 객체를 올바르게 저장하고 검색하려면 `hashCode()`도 재정의**해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## **2. `equals()` 와 `hashCode()`의 기본 동작**
|
||||
### ✅ **1. `equals()` 기본 구현 (`Object` 클래스)**
|
||||
모든 클래스는 `Object` 클래스를 상속받으며, `Object`의 기본 `equals()`는 **메모리 주소를 비교**한다.
|
||||
```java
|
||||
public boolean equals(Object obj) {
|
||||
return (this == obj);
|
||||
}
|
||||
```
|
||||
즉, 기본적으로 `equals()`는 `==`과 동일한 동작을 한다.
|
||||
|
||||
### ✅ **2. `hashCode()` 기본 구현 (`Object` 클래스)**
|
||||
`Object`의 `hashCode()`는 **객체의 메모리 주소를 기반으로 해시 값을 반환**한다.
|
||||
```java
|
||||
public native int hashCode();
|
||||
```
|
||||
(※ `native`는 자바가 아닌 C/C++로 구현되었다는 의미)
|
||||
|
||||
---
|
||||
|
||||
## **3. `equals()`와 `hashCode()`를 함께 재정의해야 하는 이유**
|
||||
**예제:** `HashSet`에서 `equals()`만 재정의했을 때 발생하는 문제
|
||||
```java
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
class Person {
|
||||
String name;
|
||||
|
||||
public Person(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Person person = (Person) obj;
|
||||
return Objects.equals(name, person.name);
|
||||
}
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
HashSet<Person> set = new HashSet<>();
|
||||
set.add(new Person("Alice"));
|
||||
System.out.println(set.contains(new Person("Alice"))); // false (올바르게 동작하지 않음!)
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ 이유: `HashSet`은 **`hashCode()`를 먼저 비교한 후 `equals()`를 실행**하는데, `hashCode()`가 재정의되지 않아서 `contains()`가 실패한다.
|
||||
|
||||
✅ 해결 방법: `hashCode()`를 `equals()`와 함께 재정의한다.
|
||||
|
||||
---
|
||||
|
||||
## **4. `equals()`와 `hashCode()` 올바르게 재정의하는 방법**
|
||||
### ✅ **올바른 `equals()` 재정의**
|
||||
1. **반사성** (`x.equals(x) == true`)
|
||||
2. **대칭성** (`x.equals(y) == true` 면 `y.equals(x) == true`)
|
||||
3. **추이성** (`x.equals(y) == true` & `y.equals(z) == true` 면 `x.equals(z) == true`)
|
||||
4. **일관성** (`x.equals(y)`의 결과가 변하지 않음)
|
||||
5. **null 비교 시 `false` 반환** (`x.equals(null) == false`)
|
||||
|
||||
**예제 코드:**
|
||||
```java
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Person person = (Person) obj;
|
||||
return Objects.equals(name, person.name);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ **올바른 `hashCode()` 재정의**
|
||||
✔ **같은 객체는 같은 `hashCode()`를 가져야 한다.**
|
||||
✔ **다른 객체라도 같은 `hashCode()`를 가질 수는 있지만 가능하면 충돌을 줄이는 것이 좋다.**
|
||||
|
||||
**예제 코드:**
|
||||
```java
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
```
|
||||
`Objects.hash()`는 `null` 체크까지 포함된 안전한 방식이다.
|
||||
|
||||
---
|
||||
|
||||
## **5. `equals()`와 `hashCode()`를 올바르게 구현한 예제**
|
||||
```java
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
class Person {
|
||||
String name;
|
||||
|
||||
public Person(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Person person = (Person) obj;
|
||||
return Objects.equals(name, person.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
HashSet<Person> set = new HashSet<>();
|
||||
set.add(new Person("Alice"));
|
||||
System.out.println(set.contains(new Person("Alice"))); // true (올바르게 동작!)
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ `equals()`와 `hashCode()`를 함께 재정의했기 때문에 `contains()`가 정상적으로 동작한다.
|
||||
|
||||
---
|
||||
|
||||
## **6. `record`를 사용하면 자동 구현됨! (Java 14+)**
|
||||
자바 14부터 도입된 `record`는 `equals()`와 `hashCode()`를 자동 생성해준다.
|
||||
|
||||
```java
|
||||
record Person(String name) {}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
HashSet<Person> set = new HashSet<>();
|
||||
set.add(new Person("Alice"));
|
||||
System.out.println(set.contains(new Person("Alice"))); // true
|
||||
}
|
||||
}
|
||||
```
|
||||
✔ `record`는 **자동으로 `equals()`와 `hashCode()`를 올바르게 구현**해준다.
|
||||
|
||||
---
|
||||
|
||||
## **7. 결론**
|
||||
- **`equals()`만 재정의하면 HashSet, HashMap 등의 컬렉션에서 문제가 발생할 수 있다.**
|
||||
- **`hashCode()`도 함께 재정의해야 컬렉션에서 정상 동작한다.**
|
||||
- **`record`를 사용하면 자동으로 `equals()`와 `hashCode()`가 구현된다.**
|
||||
|
||||
이제 `equals()`와 `hashCode()`를 제대로 이해했으니, 컬렉션을 사용할 때 예상치 못한 버그를 피할 수 있을 것이다!
|
||||
149
docs/Generic.md
Normal file
149
docs/Generic.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# **자바 제너릭(Generic) 쉽게 배우기**
|
||||
|
||||
## **1. 제너릭이 뭐야?**
|
||||
제너릭(Generic)은 **클래스나 메서드에서 사용할 데이터 타입을 미리 지정하지 않고, 나중에 정할 수 있게 해주는 기능**이다.
|
||||
쉽게 말해 **"박스를 만들되, 안에 뭐가 들어갈지는 나중에 정하는 것"**과 같다.
|
||||
|
||||
예를 들어, `List`는 여러 종류의 데이터를 저장할 수 있다.
|
||||
```java
|
||||
List<String> strList = new ArrayList<>();
|
||||
List<Integer> intList = new ArrayList<>();
|
||||
```
|
||||
여기서 `List<String>`은 문자열만, `List<Integer>`는 정수만 저장할 수 있다.
|
||||
이처럼 **제너릭을 사용하면 타입을 강제할 수 있어 코드의 안정성이 높아진다.**
|
||||
|
||||
---
|
||||
|
||||
## **2. 제너릭을 사용하지 않으면? (비교 예제)**
|
||||
제너릭을 사용하지 않는 코드:
|
||||
```java
|
||||
List list = new ArrayList();
|
||||
list.add("Hello");
|
||||
list.add(123); // 문자열 리스트인데 숫자가 들어감
|
||||
|
||||
String str = (String) list.get(1); // 실행 시 오류 발생 가능!
|
||||
```
|
||||
위 코드에서는 `List`가 **어떤 타입의 데이터를 가질지 정해져 있지 않다.**
|
||||
따라서 다른 타입의 데이터가 들어와도 **컴파일러가 오류를 잡아주지 못한다.**
|
||||
|
||||
---
|
||||
|
||||
## **3. 제너릭을 사용한 코드 (안전한 코드!)**
|
||||
```java
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("Hello");
|
||||
// list.add(123); // 컴파일 오류 발생! (안전성 증가)
|
||||
|
||||
String str = list.get(0); // 타입 캐스팅 없이 사용 가능
|
||||
```
|
||||
✅ **제너릭을 사용하면 타입을 제한할 수 있어 실수를 줄이고, 형 변환 없이 안전하게 데이터를 사용할 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
## **4. 제너릭 클래스 만들기**
|
||||
제너릭을 직접 만들어 보자!
|
||||
```java
|
||||
class Box<T> { // T는 타입 매개변수
|
||||
private T item;
|
||||
|
||||
public void setItem(T item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public T getItem() {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
```
|
||||
이제 `Box<T>`를 다양한 타입으로 사용할 수 있다.
|
||||
```java
|
||||
Box<String> strBox = new Box<>();
|
||||
strBox.setItem("Hello");
|
||||
System.out.println(strBox.getItem()); // Hello
|
||||
|
||||
Box<Integer> intBox = new Box<>();
|
||||
intBox.setItem(100);
|
||||
System.out.println(intBox.getItem()); // 100
|
||||
```
|
||||
✅ **제너릭을 사용하면 같은 코드로 여러 타입을 지원할 수 있어 코드의 재사용성이 높아진다!**
|
||||
|
||||
---
|
||||
|
||||
## **5. 제너릭 메서드 만들기**
|
||||
제너릭은 클래스뿐만 아니라 **메서드에서도 사용할 수 있다.**
|
||||
```java
|
||||
class Util {
|
||||
public static <T> void printItem(T item) {
|
||||
System.out.println(item);
|
||||
}
|
||||
}
|
||||
|
||||
Util.printItem("Hello"); // Hello
|
||||
Util.printItem(123); // 123
|
||||
Util.printItem(3.14); // 3.14
|
||||
```
|
||||
✅ **제너릭 메서드를 사용하면 여러 타입을 지원하는 함수를 쉽게 만들 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
## **6. 제너릭 타입 제한하기 (extends 사용)**
|
||||
어떤 타입이든 받을 수 있는 것도 좋지만, **특정 타입만 허용하고 싶을 때**가 있다.
|
||||
이럴 때 `extends` 키워드를 사용해 제한할 수 있다.
|
||||
```java
|
||||
class NumberBox<T extends Number> { // Number 또는 그 자식 타입만 가능
|
||||
private T num;
|
||||
|
||||
public NumberBox(T num) {
|
||||
this.num = num;
|
||||
}
|
||||
|
||||
public double getDoubleValue() {
|
||||
return num.doubleValue();
|
||||
}
|
||||
}
|
||||
```
|
||||
사용 예시:
|
||||
```java
|
||||
NumberBox<Integer> intBox = new NumberBox<>(10);
|
||||
System.out.println(intBox.getDoubleValue()); // 10.0
|
||||
|
||||
NumberBox<Double> doubleBox = new NumberBox<>(5.5);
|
||||
System.out.println(doubleBox.getDoubleValue()); // 5.5
|
||||
|
||||
// NumberBox<String> strBox = new NumberBox<>("Hello"); // 오류 발생!
|
||||
```
|
||||
✅ **제너릭에 `extends`를 사용하면 특정 타입만 허용할 수 있어 더 안전한 코드 작성이 가능하다!**
|
||||
|
||||
---
|
||||
|
||||
## **7. 와일드카드 (`?`) 활용하기**
|
||||
제너릭을 사용하다 보면 **다양한 타입을 받아야 하지만, 타입을 정확히 모를 때**가 있다.
|
||||
이럴 때 **와일드카드 (`?`)** 를 사용할 수 있다.
|
||||
|
||||
```java
|
||||
public static void printList(List<?> list) {
|
||||
for (Object obj : list) {
|
||||
System.out.println(obj);
|
||||
}
|
||||
}
|
||||
```
|
||||
사용 예시:
|
||||
```java
|
||||
List<String> strList = Arrays.asList("A", "B", "C");
|
||||
List<Integer> intList = Arrays.asList(1, 2, 3);
|
||||
|
||||
printList(strList); // A, B, C
|
||||
printList(intList); // 1, 2, 3
|
||||
```
|
||||
✅ **`?`를 사용하면 어떤 타입이든 받을 수 있어 유연한 코드 작성이 가능하다!**
|
||||
|
||||
---
|
||||
|
||||
## **8. 정리**
|
||||
✅ **제너릭을 사용하면 코드의 타입 안정성이 증가하고, 불필요한 형 변환을 줄일 수 있다!**
|
||||
✅ **제너릭 클래스(`Box<T>`)를 사용하면 여러 타입을 지원하는 재사용 가능한 클래스를 만들 수 있다!**
|
||||
✅ **제너릭 메서드(`printItem<T>(T item)`)를 사용하면 다양한 타입을 처리하는 메서드를 쉽게 작성할 수 있다!**
|
||||
✅ **`extends`를 사용하면 특정 타입만 허용할 수 있다!**
|
||||
✅ **`?`(와일드카드)를 사용하면 유연한 코드 작성이 가능하다!**
|
||||
|
||||
자바 제너릭을 활용하면 **더 안전하고 재사용 가능한 코드**를 작성할 수 있다!
|
||||
141
docs/GlassFish.md
Normal file
141
docs/GlassFish.md
Normal file
@@ -0,0 +1,141 @@
|
||||
### Jakarta EE란 무엇인가?
|
||||
|
||||
Jakarta EE(Enterprise Edition)는 Java EE(Java Platform, Enterprise Edition)의 후속 프로젝트로, 엔터프라이즈급 애플리케이션 개발을 위한 표준화된 플랫폼입니다. 2017년 Oracle이 Java EE를 Eclipse 재단으로 이관하면서 이름이 Jakarta EE로 변경되었으며, 현재는 오픈 소스 커뮤니티 주도로 발전하고 있습니다. Jakarta EE는 분산 시스템, 웹 애플리케이션, 비즈니스 로직 처리 등 엔터프라이즈 환경에서 필요한 다양한 기술을 제공합니다.
|
||||
|
||||
현재 날짜 기준(2025년 3월 9일)으로, 최신 버전은 **Jakarta EE 10**이며, 이는 2022년에 출시되었습니다. Jakarta EE 11은 2025년 중반 출시를 목표로 개발 중입니다.
|
||||
|
||||
---
|
||||
|
||||
### Jakarta EE의 주요 세부 사항
|
||||
|
||||
Jakarta EE는 여러 API와 기술 스펙으로 구성되어 있으며, 이를 통해 개발자는 표준화된 방식으로 애플리케이션을 구축할 수 있습니다. 아래에서 주요 구성 요소와 세부 사항을 상세히 설명합니다.
|
||||
|
||||
#### 1. **핵심 구성 요소 (API와 기술)**
|
||||
|
||||
| **기술/API** | **설명** | **주요 용도** | **Jakarta EE 10에서의 특징** |
|
||||
|-------------------------|------------------------------------------------------------------------------------------|---------------------------------------|---------------------------------------------|
|
||||
| **Servlet** | HTTP 요청/응답 처리를 위한 웹 컴포넌트 | 웹 애플리케이션 개발 | Servlet 6.0: 비동기 처리 개선, HTTP/2 지원 |
|
||||
| **JSP (JavaServer Pages)** | 동적 웹 콘텐츠 생성을 위한 템플릿 엔진 | 웹 페이지 렌더링 | JSP 3.1: EL(Expression Language) 개선 |
|
||||
| **JSF (JavaServer Faces)** | 컴포넌트 기반의 웹 UI 프레임워크 | 복잡한 웹 UI 개발 | JSF 4.0: Facelets 개선, CDI 통합 강화 |
|
||||
| **EJB (Enterprise JavaBeans)** | 비즈니스 로직을 캡슐화한 서버 측 컴포넌트 | 트랜잭션 관리, 분산 컴포넌트 | EJB 4.0: 경량화 및 CDI와 통합 |
|
||||
| **JPA (Java Persistence API)** | 객체-관계 매핑(ORM)을 위한 표준 | 데이터베이스 액세스 | JPA 3.1: Criteria API 개선, Jakarta 네임스페이스 |
|
||||
| **JMS (Java Message Service)** | 메시지 기반 비동기 통신을 위한 API | 메시지 큐, Pub/Sub 모델 | JMS 3.0: 클라우드 환경 지원 강화 |
|
||||
| **CDI (Contexts and Dependency Injection)** | 의존성 주입과 컨텍스트 관리를 위한 표준 | 객체 생명주기 관리, DI | CDI 4.0: 모듈성 강화, 이벤트 처리 개선 |
|
||||
| **JAX-RS (RESTful Web Services)** | RESTful 웹 서비스를 위한 API | API 개발 | JAX-RS 3.1: 클라이언트 API 개선, JSON-B 통합 |
|
||||
| **JSON-P (JSON Processing)** | JSON 데이터 처리 API | JSON 파싱 및 생성 | JSON-P 2.1: 스트리밍 API 개선 |
|
||||
| **JSON-B (JSON Binding)** | 객체와 JSON 간 매핑 API | 데이터 직렬화/역직렬화 | JSON-B 3.0: 어노테이션 기반 매핑 강화 |
|
||||
| **Jakarta Mail** | 이메일 전송 및 수신을 위한 API | 이메일 기능 | Jakarta Mail 2.1: 보안 강화 |
|
||||
| **Jakarta Transactions (JTA)** | 분산 트랜잭션 관리 API | 트랜잭션 처리 | JTA 2.0: CDI 통합 개선 |
|
||||
| **Jakarta Security** | 인증 및 권한 부여를 위한 표준 API | 보안 관리 | Security 3.0: SSO(Single Sign-On) 지원 강화 |
|
||||
|
||||
---
|
||||
|
||||
#### 2. **버전별 주요 변경 사항**
|
||||
|
||||
- **Jakarta EE 8 (2019)**:
|
||||
Java EE 8의 리브랜딩 버전으로, 패키지 네임스페이스가 `javax.*`에서 `jakarta.*`로 변경. 기술적으로는 Java EE 8과 동일.
|
||||
|
||||
- **Jakarta EE 9 (2020)**:
|
||||
- 네임스페이스 전환 완료 (`javax.*` → `jakarta.*`).
|
||||
- API 변화는 최소화, 주로 호환성 유지에 초점.
|
||||
|
||||
- **Jakarta EE 9.1 (2021)**:
|
||||
- Java SE 11 지원 추가.
|
||||
- 기존 API의 안정화.
|
||||
|
||||
- **Jakarta EE 10 (2022)**:
|
||||
- Java SE 17 지원.
|
||||
- CDI와 JAX-RS의 개선, 마이크로서비스 친화적인 기능 추가.
|
||||
- Web Profile과 Full Platform 간 격차 축소.
|
||||
|
||||
- **Jakarta EE 11 (예정, 2025)**:
|
||||
- 클라우드 네이티브 지원 강화 (예: Kubernetes 통합).
|
||||
- 새로운 API 추가 및 기존 API의 경량화.
|
||||
|
||||
---
|
||||
|
||||
#### 3. **플랫폼 구성**
|
||||
|
||||
Jakarta EE는 두 가지 프로필로 제공됩니다:
|
||||
- **Web Profile**:
|
||||
웹 애플리케이션 개발에 초점을 맞춘 경량 프로필. Servlet, JSP, JSF, JPA, CDI, JAX-RS 등 포함.
|
||||
- **Full Platform**:
|
||||
모든 Jakarta EE 기술을 포함하며, EJB, JMS, JTA 등 엔터프라이즈 기능을 지원.
|
||||
|
||||
---
|
||||
|
||||
#### 4. **주요 구현체**
|
||||
|
||||
Jakarta EE 사양을 구현한 애플리케이션 서버는 여러 가지가 있으며, 대표적인 구현체는 다음과 같습니다:
|
||||
- **Eclipse GlassFish**: Jakarta EE의 공식 레퍼런스 구현.
|
||||
- **Payara Server**: GlassFish 기반의 포크로, 상용 지원과 추가 기능 제공.
|
||||
- **WildFly**: Red Hat에서 개발, 경량화와 성능에 초점.
|
||||
- **Apache TomEE**: Tomcat에 EE 기능을 추가한 구현체.
|
||||
- **Open Liberty**: IBM의 경량화된 오픈 소스 서버.
|
||||
|
||||
---
|
||||
|
||||
#### 5. **Jakarta EE의 장점**
|
||||
|
||||
- **표준화**: 모든 구현체가 동일한 API를 준수하므로, 서버 간 호환성이 높음.
|
||||
- **확장성**: 분산 시스템과 대규모 애플리케이션에 적합.
|
||||
- **생태계**: 방대한 오픈 소스 커뮤니티와 도구 지원 (Maven, Gradle, IDE 등).
|
||||
- **모듈성**: 필요한 기능만 선택해 사용할 수 있음.
|
||||
|
||||
---
|
||||
|
||||
#### 6. **Jakarta EE의 단점**
|
||||
|
||||
- **복잡성**: Full Platform은 초보자에게 다소 무겁고 복잡할 수 있음.
|
||||
- **경쟁**: Spring Boot와 같은 마이크로서비스 중심 프레임워크에 비해 전통적인 모놀리식 구조에 치우침.
|
||||
- **학습 곡선**: 다양한 API를 익히는 데 시간이 필요.
|
||||
|
||||
---
|
||||
|
||||
#### 7. **실제 사용 예시**
|
||||
|
||||
간단한 JAX-RS 기반 REST API 예시:
|
||||
```java
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("/hello")
|
||||
public class HelloResource {
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String sayHello() {
|
||||
return "Hello, Jakarta EE!";
|
||||
}
|
||||
}
|
||||
```
|
||||
- 위 코드를 GlassFish에 배포하면 `http://localhost:8080/your-app/hello`로 접근 가능.
|
||||
|
||||
---
|
||||
|
||||
#### 8. **Jakarta EE와 Spring 비교**
|
||||
|
||||
| **항목** | **Jakarta EE** | **Spring** |
|
||||
|--------------------|--------------------------------|--------------------------------|
|
||||
| **표준 여부** | 표준 사양 (Jakarta EE) | 비표준 프레임워크 |
|
||||
| **구성** | 여러 독립 API | 통합된 생태계 (Spring Boot 등) |
|
||||
| **배포** | 애플리케이션 서버 필요 | 내장 서버 지원 |
|
||||
| **용도** | 전통적 엔터프라이즈 애플리케이션 | 마이크로서비스, 웹 애플리케이션 |
|
||||
| **경량화** | Web Profile로 가능 | 기본적으로 경량화 |
|
||||
|
||||
---
|
||||
|
||||
#### 9. **미래 전망 (2025년 기준)**
|
||||
|
||||
- **클라우드 네이티브**: Jakarta EE 11은 Kubernetes와 같은 클라우드 환경에 최적화될 예정.
|
||||
- **경쟁력 강화**: Spring Boot와의 경쟁에서 살아남기 위해 경량화와 모듈성에 더욱 집중.
|
||||
- **커뮤니티 활성화**: Eclipse 재단의 지속적인 지원으로 커뮤니티가 성장 중.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
|
||||
Jakarta EE는 엔터프라이즈급 애플리케이션 개발을 위한 견고한 표준 플랫폼으로, 다양한 API와 구현체를 통해 유연성과 확장성을 제공합니다. 최신 버전인 Jakarta EE 10은 현대적인 요구사항을 반영하며, 다가오는 Jakarta EE 11은 클라우드 네이티브 환경에 더 적합해질 전망입니다. 프로젝트의 규모와 요구사항에 따라 Web Profile 또는 Full Platform을 선택해 사용하면 됩니다.
|
||||
|
||||
추가로 궁금한 점이 있다면 말씀해주세요!
|
||||
262
docs/Guice.md
Normal file
262
docs/Guice.md
Normal file
@@ -0,0 +1,262 @@
|
||||
### Guice의 주요 어노테이션과 사용 예시
|
||||
|
||||
Google Guice는 의존성 주입(Dependency Injection, DI)을 간편하게 구현할 수 있는 경량 프레임워크로, 다양한 어노테이션을 통해 의존성을 정의하고 관리합니다. 아래에서는 Guice에서 자주 사용되는 주요 어노테이션들을 표로 정리하고, 각 어노테이션의 역할과 사용 예시를 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
#### Guice 주요 어노테이션 표
|
||||
|
||||
| 어노테이션 | 설명 | 사용 위치 | 주요 특징 및 용도 |
|
||||
|-------------------|----------------------------------------------------------------------|-----------------------|-------------------------------------------------------|
|
||||
| `@Inject` | 의존성을 주입할 위치를 지정합니다. 생성자, 필드, 메서드에 사용 가능합니다. | 생성자, 필드, Setter 메서드 | Guice가 자동으로 의존성을 주입하도록 지시. 선택적 주입 가능 (`@Inject(optional=true)`). |
|
||||
| `@Named` | 동일한 타입의 여러 구현체 중 특정 구현체를 선택하기 위해 사용됩니다. | 필드, 매개변수 | 문자열 키로 구체적인 바인딩을 식별. |
|
||||
| `@Singleton` | 클래스의 인스턴스가 단일 객체로 유지되도록 지정합니다. | 클래스 | 싱글턴 패턴을 구현하여 메모리 효율성 향상. |
|
||||
| `@Provides` | 모듈 내에서 의존성을 제공하는 메서드를 정의합니다. | 모듈의 메서드 | 복잡한 객체 생성 로직을 직접 작성 가능. |
|
||||
| `@ImplementedBy` | 인터페이스의 기본 구현체를 지정합니다. | 인터페이스 | 모듈 없이 기본 바인딩을 설정. |
|
||||
| `@ProvidedBy` | 동적으로 의존성을 제공하는 클래스를 지정합니다. | 인터페이스 | Provider 클래스를 통해 의존성 공급. |
|
||||
| `@Qualifier` | 사용자 정의 qualifier를 생성하기 위한 메타 어노테이션입니다. | 사용자 정의 어노테이션 | `@Named`보다 더 세밀한 의존성 구분 가능. |
|
||||
|
||||
---
|
||||
|
||||
### 어노테이션별 설명 및 예시
|
||||
|
||||
#### 1. `@Inject`
|
||||
- **설명**: Guice가 의존성을 주입할 위치를 나타냅니다. 생성자 주입, 필드 주입, Setter 주입에 사용됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.google.inject.Inject;
|
||||
|
||||
class UserService {
|
||||
private final Database database;
|
||||
|
||||
@Inject
|
||||
public UserService(Database database) { // 생성자 주입
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
@Inject
|
||||
private Logger logger; // 필드 주입
|
||||
|
||||
@Inject
|
||||
public void setConfig(Config config) { // Setter 주입
|
||||
// 설정 로직
|
||||
}
|
||||
}
|
||||
```
|
||||
- **특징**: 생성자 주입이 가장 권장되며, 불변성을 보장합니다. 필드 주입은 간단하지만 테스트 시 불편할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
#### 2. `@Named`
|
||||
- **설명**: 동일한 인터페이스를 구현한 여러 클래스가 있을 때, 특정 구현체를 선택할 때 사용됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
interface Database {
|
||||
void connect();
|
||||
}
|
||||
|
||||
class MySQLDatabase implements Database {
|
||||
public void connect() { System.out.println("MySQL 연결"); }
|
||||
}
|
||||
|
||||
class PostgresDatabase implements Database {
|
||||
public void connect() { System.out.println("Postgres 연결"); }
|
||||
}
|
||||
|
||||
class MyModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Database.class).annotatedWith(Names.named("mysql")).to(MySQLDatabase.class);
|
||||
bind(Database.class).annotatedWith(Names.named("postgres")).to(PostgresDatabase.class);
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
@Inject @Named("mysql")
|
||||
private Database database;
|
||||
|
||||
public void run() {
|
||||
database.connect(); // 출력: MySQL 연결
|
||||
}
|
||||
}
|
||||
```
|
||||
- **특징**: 문자열 기반으로 동작하며, 오타에 주의해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
#### 3. `@Singleton`
|
||||
- **설명**: 클래스의 인스턴스를 싱글턴으로 관리하도록 지정합니다. Guice 인젝터가 단일 인스턴스를 재사용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@Singleton
|
||||
class Cache {
|
||||
public void store(String key, String value) {
|
||||
System.out.println("캐시에 저장: " + key + " -> " + value);
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
@Inject
|
||||
private Cache cache;
|
||||
|
||||
public void run() {
|
||||
cache.store("user", "data"); // 동일한 인스턴스 사용
|
||||
}
|
||||
}
|
||||
```
|
||||
- **특징**: 메모리 효율성을 높이지만, 상태를 공유하므로 주의가 필요합니다.
|
||||
|
||||
---
|
||||
|
||||
#### 4. `@Provides`
|
||||
- **설명**: 모듈 내에서 복잡한 객체 생성 로직을 정의할 때 사용됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
interface Client {
|
||||
void send(String msg);
|
||||
}
|
||||
|
||||
class HttpClient implements Client {
|
||||
private final String url;
|
||||
public HttpClient(String url) { this.url = url; }
|
||||
public void send(String msg) { System.out.println("Sending to " + url + ": " + msg); }
|
||||
}
|
||||
|
||||
class MyModule extends AbstractModule {
|
||||
@Provides
|
||||
public Client provideClient() {
|
||||
return new HttpClient("https://api.example.com");
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
public void run() {
|
||||
client.send("Hello"); // 출력: Sending to https://api.example.com: Hello
|
||||
}
|
||||
}
|
||||
```
|
||||
- **특징**: 동적 생성 로직을 커스터마이징할 수 있어 유연성이 높습니다.
|
||||
|
||||
---
|
||||
|
||||
#### 5. `@ImplementedBy`
|
||||
- **설명**: 인터페이스의 기본 구현체를 지정하여 모듈 설정 없이도 의존성을 주입할 수 있습니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.google.inject.ImplementedBy;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@ImplementedBy(DefaultService.class)
|
||||
interface Service {
|
||||
void execute();
|
||||
}
|
||||
|
||||
class DefaultService implements Service {
|
||||
public void execute() { System.out.println("기본 서비스 실행"); }
|
||||
}
|
||||
|
||||
class App {
|
||||
@Inject
|
||||
private Service service;
|
||||
|
||||
public void run() {
|
||||
service.execute(); // 출력: 기본 서비스 실행
|
||||
}
|
||||
}
|
||||
```
|
||||
- **특징**: 모듈 설정이 필요 없어 간단하지만, 유연성이 제한적입니다.
|
||||
|
||||
---
|
||||
|
||||
#### 6. `@ProvidedBy`
|
||||
- **설명**: Provider 클래스를 통해 동적으로 의존성을 제공합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.google.inject.ProvidedBy;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@ProvidedBy(DynamicServiceProvider.class)
|
||||
interface Service {
|
||||
void execute();
|
||||
}
|
||||
|
||||
class DynamicService implements Service {
|
||||
public void execute() { System.out.println("동적 서비스 실행"); }
|
||||
}
|
||||
|
||||
class DynamicServiceProvider implements Provider<Service> {
|
||||
@Override
|
||||
public Service get() {
|
||||
return new DynamicService();
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
@Inject
|
||||
private Service service;
|
||||
|
||||
public void run() {
|
||||
service.execute(); // 출력: 동적 서비스 실행
|
||||
}
|
||||
}
|
||||
```
|
||||
- **특징**: Provider를 통해 런타임에 객체를 생성할 수 있어 동적 처리가 가능합니다.
|
||||
|
||||
---
|
||||
|
||||
#### 7. `@Qualifier`
|
||||
- **설명**: 사용자 정의 어노테이션을 만들어 더 세밀한 의존성 구분을 가능하게 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.google.inject.Qualifier;
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface PrimaryDB {}
|
||||
|
||||
class MyModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Database.class).annotatedWith(PrimaryDB.class).to(MySQLDatabase.class);
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
@Inject @PrimaryDB
|
||||
private Database database;
|
||||
|
||||
public void run() {
|
||||
database.connect(); // 출력: MySQL 연결
|
||||
}
|
||||
}
|
||||
```
|
||||
- **특징**: `@Named`보다 타입 안전성이 높고, 오타 문제를 방지할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
### Guice 어노테이션 사용 팁
|
||||
- **생성자 주입 우선**: `@Inject`를 생성자에 사용해 불변성과 명확성을 유지하세요.
|
||||
- **Qualifier 활용**: `@Named` 대신 사용자 정의 `@Qualifier`를 사용하면 더 안전합니다.
|
||||
- **싱글턴 주의**: `@Singleton`은 상태를 공유하므로 스레드 안전성을 고려해야 합니다.
|
||||
- **모듈 분리**: 복잡한 프로젝트에서는 `@Provides`를 활용해 모듈을 깔끔하게 관리하세요.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
Guice의 어노테이션들은 의존성 주입을 간단하고 유연하게 만들어줍니다. 위 예시를 통해 각 어노테이션의 사용법과 장점을 이해할 수 있으며, 프로젝트 요구사항에 맞게 적절히 선택해 사용하면 됩니다. 추가 질문이 있다면 언제든 물어보세요!
|
||||
240
docs/JAXB.md
Normal file
240
docs/JAXB.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# JAXB (Java Architecture for XML Binding)
|
||||
|
||||
## **1. JAXB란?**
|
||||
JAXB(Java Architecture for XML Binding)는 **Java 객체를 XML로 변환하거나, XML을 Java 객체로 변환하는 기술**이다.
|
||||
XML 데이터를 다룰 때, DOM이나 SAX보다 훨씬 간편하다.
|
||||
|
||||
**JAXB 주요 기능**
|
||||
✔ **Java 객체 → XML 변환 (Marshalling)**
|
||||
✔ **XML → Java 객체 변환 (Unmarshalling)**
|
||||
✔ **XML Schema 기반 Java 클래스 자동 생성 가능**
|
||||
|
||||
---
|
||||
|
||||
## **2. JAXB 어노테이션 정리**
|
||||
JAXB는 여러 어노테이션을 제공하여 XML과 Java 객체 간 매핑을 설정할 수 있다.
|
||||
|
||||
| 어노테이션 | 설명 |
|
||||
|-----------|-----------------------------------------------------|
|
||||
| `@XmlRootElement` | XML의 루트 요소를 정의 |
|
||||
| `@XmlElement` | 특정 필드를 XML 요소로 매핑 |
|
||||
| `@XmlAttribute` | 특정 필드를 XML 속성으로 매핑 |
|
||||
| `@XmlAccessorType` | 필드 접근 방식 설정 (`FIELD`, `PROPERTY`, `NONE`, `PUBLIC_MEMBER`) |
|
||||
| `@XmlTransient` | 특정 필드를 XML에 포함하지 않음 |
|
||||
| `@XmlElementWrapper` | 컬렉션 요소를 감싸는 XML 요소를 추가 |
|
||||
| `@XmlType` | XML 요소의 순서를 지정 |
|
||||
| `@XmlEnum` | Enum을 XML 요소로 변환 |
|
||||
| `@XmlValue` | XML 요소의 값을 직접 지정 |
|
||||
| `@XmlJavaTypeAdapter` | 사용자 정의 변환을 적용 |
|
||||
|
||||
---
|
||||
|
||||
## **3. JAXB 사용 예제**
|
||||
|
||||
### **✔ Java 객체 → XML 변환 (Marshalling)**
|
||||
|
||||
```java
|
||||
import javax.xml.bind.annotation.*;
|
||||
import javax.xml.bind.*;
|
||||
|
||||
@XmlRootElement(name = "person")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
|
||||
public Person() {} // JAXB는 기본 생성자가 필요함
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Person person = new Person("홍길동", 30);
|
||||
|
||||
// JAXB 객체 생성
|
||||
JAXBContext context = JAXBContext.newInstance(Person.class);
|
||||
Marshaller marshaller = context.createMarshaller();
|
||||
|
||||
// XML 출력 형식 설정 (예쁘게 출력)
|
||||
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
|
||||
|
||||
// Java 객체를 XML로 변환 후 출력
|
||||
marshaller.marshal(person, System.out);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과 (XML 출력)**
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<person>
|
||||
<name>홍길동</name>
|
||||
<age>30</age>
|
||||
</person>
|
||||
```
|
||||
|
||||
✅ `@XmlRootElement`를 사용하여 `person`을 XML 루트 요소로 지정했다.
|
||||
✅ `@XmlAccessorType(XmlAccessType.FIELD)`을 사용하여 **클래스의 필드 기준으로 XML을 자동 생성**했다.
|
||||
|
||||
---
|
||||
|
||||
### **✔ XML → Java 객체 변환 (Unmarshalling)**
|
||||
|
||||
위에서 생성한 XML을 다시 Java 객체로 변환하는 코드:
|
||||
|
||||
```java
|
||||
import java.io.StringReader;
|
||||
|
||||
public class XMLToJava {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String xmlData = """
|
||||
<person>
|
||||
<name>홍길동</name>
|
||||
<age>30</age>
|
||||
</person>
|
||||
""";
|
||||
|
||||
JAXBContext context = JAXBContext.newInstance(Person.class);
|
||||
Unmarshaller unmarshaller = context.createUnmarshaller();
|
||||
|
||||
// XML을 Java 객체로 변환
|
||||
Person person = (Person) unmarshaller.unmarshal(new StringReader(xmlData));
|
||||
|
||||
System.out.println("이름: " + person.getName());
|
||||
System.out.println("나이: " + person.getAge());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
이름: 홍길동
|
||||
나이: 30
|
||||
```
|
||||
✅ `Unmarshaller`를 사용하여 XML 데이터를 Java 객체로 변환했다.
|
||||
|
||||
---
|
||||
|
||||
### **✔ XML 속성 처리 (`@XmlAttribute`)**
|
||||
필드를 XML 속성으로 변환할 수도 있다.
|
||||
|
||||
```java
|
||||
@XmlRootElement(name = "person")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Person {
|
||||
@XmlAttribute
|
||||
private int id;
|
||||
private String name;
|
||||
private int age;
|
||||
|
||||
public Person() {}
|
||||
|
||||
public Person(int id, String name, int age) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 XML 출력 결과**
|
||||
```xml
|
||||
<person id="1">
|
||||
<name>홍길동</name>
|
||||
<age>30</age>
|
||||
</person>
|
||||
```
|
||||
✅ `@XmlAttribute`를 사용하면 특정 필드를 **XML의 속성(attribute) 형태**로 변환할 수 있다.
|
||||
|
||||
---
|
||||
|
||||
### **✔ 컬렉션 처리 (`@XmlElementWrapper`)**
|
||||
리스트(List) 같은 컬렉션을 XML로 변환할 때 `@XmlElementWrapper`를 사용하면 요소를 그룹으로 감쌀 수 있다.
|
||||
|
||||
```java
|
||||
import java.util.List;
|
||||
|
||||
@XmlRootElement(name = "classroom")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Classroom {
|
||||
@XmlElementWrapper(name = "students")
|
||||
@XmlElement(name = "student")
|
||||
private List<Person> students;
|
||||
|
||||
public Classroom() {}
|
||||
|
||||
public Classroom(List<Person> students) {
|
||||
this.students = students;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 XML 출력 결과**
|
||||
```xml
|
||||
<classroom>
|
||||
<students>
|
||||
<student>
|
||||
<name>홍길동</name>
|
||||
<age>30</age>
|
||||
</student>
|
||||
<student>
|
||||
<name>이몽룡</name>
|
||||
<age>25</age>
|
||||
</student>
|
||||
</students>
|
||||
</classroom>
|
||||
```
|
||||
✅ `@XmlElementWrapper(name = "students")`를 사용하여 `students` 요소를 감쌌다.
|
||||
|
||||
---
|
||||
|
||||
### **✔ XML 요소 순서 지정 (`@XmlType`)**
|
||||
XML 요소의 순서를 지정하려면 `@XmlType`을 사용한다.
|
||||
|
||||
```java
|
||||
@XmlRootElement(name = "book")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(propOrder = { "title", "author", "price" }) // 요소 순서 지정
|
||||
public class Book {
|
||||
private String title;
|
||||
private String author;
|
||||
private double price;
|
||||
|
||||
public Book() {}
|
||||
|
||||
public Book(String title, String author, double price) {
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
this.price = price;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 XML 출력 결과**
|
||||
```xml
|
||||
<book>
|
||||
<title>자바 프로그래밍</title>
|
||||
<author>홍길동</author>
|
||||
<price>30000.0</price>
|
||||
</book>
|
||||
```
|
||||
✅ `@XmlType(propOrder = { "title", "author", "price" })`을 사용하여 요소 순서를 지정했다.
|
||||
|
||||
---
|
||||
|
||||
## **4. 정리**
|
||||
✔ **JAXB는 Java 객체와 XML 간 변환을 쉽게 할 수 있도록 도와주는 기술**
|
||||
✔ `@XmlRootElement`, `@XmlElement`, `@XmlAttribute` 등을 활용하여 XML 구조를 커스텀 가능
|
||||
✔ `Marshaller`를 사용하여 Java → XML 변환 (Marshalling)
|
||||
✔ `Unmarshaller`를 사용하여 XML → Java 변환 (Unmarshalling)
|
||||
✔ 컬렉션, 속성, 요소 순서 등도 세밀하게 제어 가능
|
||||
|
||||
✅ **JAXB를 활용하면 XML을 쉽게 다룰 수 있으며, 웹 서비스 (REST, SOAP) 개발에도 유용하다!**
|
||||
231
docs/JDBC.md
Normal file
231
docs/JDBC.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# **JDBC (Java Database Connectivity) 쉽게 배우기**
|
||||
|
||||
## **1. JDBC란?**
|
||||
JDBC는 **자바에서 데이터베이스와 연결**하여 **SQL을 실행하고 결과를 처리**하는 API이다.
|
||||
MySQL, PostgreSQL, Oracle 등 **다양한 DBMS에서 공통적으로 사용**할 수 있음.
|
||||
|
||||
✔ **주요 기능**
|
||||
- 데이터베이스 연결 (`Connection`)
|
||||
- SQL 실행 (`Statement`, `PreparedStatement`)
|
||||
- 결과 가져오기 (`ResultSet`)
|
||||
- 트랜잭션 처리
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 클래스 및 메서드 정리**
|
||||
|
||||
### **📌 JDBC 연결 관련 클래스**
|
||||
| 클래스 | 설명 |
|
||||
|--------|------|
|
||||
| `DriverManager` | 데이터베이스 연결을 관리하는 클래스 |
|
||||
| `Connection` | 데이터베이스와의 연결을 나타내는 객체 |
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `DriverManager.getConnection(url, user, password)` | DB 연결 생성 |
|
||||
| `Connection.close()` | DB 연결 종료 |
|
||||
| `Connection.setAutoCommit(boolean autoCommit)` | 자동 커밋 설정 |
|
||||
| `Connection.commit()` | 트랜잭션 커밋 |
|
||||
| `Connection.rollback()` | 트랜잭션 롤백 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 SQL 실행 관련 클래스**
|
||||
| 클래스 | 설명 |
|
||||
|--------|------|
|
||||
| `Statement` | SQL 쿼리를 실행하는 객체 (단순 SQL 실행) |
|
||||
| `PreparedStatement` | SQL 쿼리를 실행하는 객체 (파라미터 지원) |
|
||||
| `CallableStatement` | 저장 프로시저(Stored Procedure) 호출 객체 |
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `Statement.executeQuery(String sql)` | SELECT 실행 후 `ResultSet` 반환 |
|
||||
| `Statement.executeUpdate(String sql)` | INSERT, UPDATE, DELETE 실행 |
|
||||
| `PreparedStatement.setXXX(int index, value)` | SQL의 `?`에 값 바인딩 |
|
||||
| `PreparedStatement.executeQuery()` | SELECT 실행 후 `ResultSet` 반환 |
|
||||
| `PreparedStatement.executeUpdate()` | INSERT, UPDATE, DELETE 실행 |
|
||||
| `Statement.close()` | Statement 종료 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 결과 조회 관련 클래스**
|
||||
| 클래스 | 설명 |
|
||||
|--------|------|
|
||||
| `ResultSet` | SQL 실행 결과를 저장하는 객체 |
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `ResultSet.next()` | 다음 행으로 이동 |
|
||||
| `ResultSet.getString("column")` | 문자열 값 가져오기 |
|
||||
| `ResultSet.getInt("column")` | 정수 값 가져오기 |
|
||||
| `ResultSet.close()` | 결과 집합 종료 |
|
||||
|
||||
---
|
||||
|
||||
## **3. JDBC 기본 사용 예제**
|
||||
|
||||
### **✔ 데이터베이스 연결 및 SELECT 예제**
|
||||
아래 코드는 **JDBC를 이용하여 MySQL에 연결하고, 데이터를 조회하는 예제**이다.
|
||||
|
||||
```java
|
||||
import java.sql.*;
|
||||
|
||||
public class JdbcExample {
|
||||
public static void main(String[] args) {
|
||||
// 1. DB 연결 정보 설정
|
||||
String url = "jdbc:mysql://localhost:3306/mydb";
|
||||
String user = "root";
|
||||
String password = "1234";
|
||||
|
||||
// 2. JDBC를 이용한 DB 연결 및 데이터 조회
|
||||
try {
|
||||
// 3. 데이터베이스 연결
|
||||
Connection conn = DriverManager.getConnection(url, user, password);
|
||||
System.out.println("DB 연결 성공!");
|
||||
|
||||
// 4. SQL 실행
|
||||
String sql = "SELECT id, name, age FROM users";
|
||||
Statement stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(sql);
|
||||
|
||||
// 5. 결과 출력
|
||||
while (rs.next()) {
|
||||
int id = rs.getInt("id");
|
||||
String name = rs.getString("name");
|
||||
int age = rs.getInt("age");
|
||||
System.out.println("ID: " + id + ", 이름: " + name + ", 나이: " + age);
|
||||
}
|
||||
|
||||
// 6. 리소스 해제
|
||||
rs.close();
|
||||
stmt.close();
|
||||
conn.close();
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
DB 연결 성공!
|
||||
ID: 1, 이름: 홍길동, 나이: 30
|
||||
ID: 2, 이름: 이몽룡, 나이: 25
|
||||
```
|
||||
|
||||
✅ `DriverManager.getConnection(url, user, password)`을 이용해 DB 연결
|
||||
✅ `Statement.executeQuery(sql)`을 이용해 SELECT 실행
|
||||
✅ `ResultSet.getInt()`, `getString()`을 이용해 결과 가져오기
|
||||
|
||||
---
|
||||
|
||||
## **4. PreparedStatement 사용 (SQL Injection 방지)**
|
||||
|
||||
`PreparedStatement`는 **SQL에 `?`를 사용하여 값을 안전하게 전달**할 수 있음.
|
||||
**SQL Injection을 방지**하고 **반복 실행 시 성능 최적화** 가능.
|
||||
|
||||
```java
|
||||
import java.sql.*;
|
||||
|
||||
public class PreparedStatementExample {
|
||||
public static void main(String[] args) {
|
||||
String url = "jdbc:mysql://localhost:3306/mydb";
|
||||
String user = "root";
|
||||
String password = "1234";
|
||||
|
||||
try {
|
||||
Connection conn = DriverManager.getConnection(url, user, password);
|
||||
|
||||
// SQL에 ?를 사용하여 값 바인딩
|
||||
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
|
||||
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||||
pstmt.setString(1, "김철수"); // 첫 번째 ?에 값 설정
|
||||
pstmt.setInt(2, 28); // 두 번째 ?에 값 설정
|
||||
|
||||
int rows = pstmt.executeUpdate();
|
||||
System.out.println(rows + "개의 행이 삽입됨!");
|
||||
|
||||
pstmt.close();
|
||||
conn.close();
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
1개의 행이 삽입됨!
|
||||
```
|
||||
|
||||
✅ `PreparedStatement.setString(1, "김철수")`로 안전한 데이터 전달
|
||||
✅ `executeUpdate()`를 사용하여 INSERT 실행
|
||||
|
||||
---
|
||||
|
||||
## **5. 트랜잭션 처리 예제**
|
||||
기본적으로 **JDBC는 자동 커밋 모드**이므로, 수동으로 커밋하려면 `setAutoCommit(false)`를 설정해야 함.
|
||||
|
||||
```java
|
||||
import java.sql.*;
|
||||
|
||||
public class TransactionExample {
|
||||
public static void main(String[] args) {
|
||||
String url = "jdbc:mysql://localhost:3306/mydb";
|
||||
String user = "root";
|
||||
String password = "1234";
|
||||
|
||||
try {
|
||||
Connection conn = DriverManager.getConnection(url, user, password);
|
||||
conn.setAutoCommit(false); // 자동 커밋 해제
|
||||
|
||||
// 1. 첫 번째 INSERT 실행
|
||||
PreparedStatement pstmt1 = conn.prepareStatement("INSERT INTO users (name, age) VALUES (?, ?)");
|
||||
pstmt1.setString(1, "박영희");
|
||||
pstmt1.setInt(2, 27);
|
||||
pstmt1.executeUpdate();
|
||||
|
||||
// 2. 일부러 오류 발생시키기 (age 컬럼에 문자 입력)
|
||||
PreparedStatement pstmt2 = conn.prepareStatement("INSERT INTO users (name, age) VALUES (?, ?)");
|
||||
pstmt2.setString(1, "오류발생");
|
||||
pstmt2.setString(2, "문자"); // 오류 발생
|
||||
pstmt2.executeUpdate();
|
||||
|
||||
conn.commit(); // 커밋 (이 코드까지 실행되면 모든 변경이 적용됨)
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
System.out.println("롤백 실행");
|
||||
conn.rollback(); // 오류 발생 시 롤백
|
||||
} catch (SQLException rollbackEx) {
|
||||
rollbackEx.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
롤백 실행
|
||||
```
|
||||
|
||||
✅ `setAutoCommit(false)`를 설정하여 수동 커밋 모드로 변경
|
||||
✅ `commit()`이 실행되기 전에 오류가 발생하면 `rollback()`으로 변경 사항 취소
|
||||
|
||||
---
|
||||
|
||||
## **6. 정리**
|
||||
✔ **JDBC는 자바에서 DB와 상호작용하는 API**
|
||||
✔ `DriverManager.getConnection()`을 사용하여 DB 연결
|
||||
✔ `Statement`와 `PreparedStatement`를 사용하여 SQL 실행
|
||||
✔ `ResultSet`을 사용하여 데이터 조회
|
||||
✔ **트랜잭션 관리 (`commit()`, `rollback()`)를 활용하여 데이터 정합성 유지**
|
||||
|
||||
✅ **SQL 실행이 많다면 `PreparedStatement`를 사용하자!**
|
||||
✅ **트랜잭션 처리를 위해 `setAutoCommit(false)`를 활용하자!**
|
||||
162
docs/Jackson Streaming API.md
Normal file
162
docs/Jackson Streaming API.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# **Jackson Streaming API 정리 및 예제**
|
||||
|
||||
## **1. Jackson Streaming API란?**
|
||||
**Jackson Streaming API**는 대량의 JSON 데이터를 메모리 효율적으로 처리할 수 있도록 설계된 API다.
|
||||
기본적으로 **이벤트 기반 방식**(push/pull 방식)을 사용하며, JSON을 한 번에 모두 로드하지 않고 **스트림 방식으로 읽고 쓸 수 있다**.
|
||||
|
||||
**✔ 주요 특징:**
|
||||
✅ 대량의 JSON 데이터를 처리할 때 **메모리 사용량이 적다**
|
||||
✅ 기존 `ObjectMapper` 방식보다 **빠른 속도로 JSON을 처리 가능**
|
||||
✅ `JsonParser`와 `JsonGenerator`를 사용하여 **JSON을 읽고 쓸 수 있다**
|
||||
|
||||
---
|
||||
|
||||
## **2. Jackson Streaming API 주요 메서드 정리**
|
||||
|
||||
### **📌 JSON 읽기 (`JsonParser`)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `nextToken()` | 다음 JSON 토큰으로 이동 |
|
||||
| `getCurrentToken()` | 현재 위치의 JSON 토큰 가져오기 |
|
||||
| `getText()` | 현재 위치의 JSON 문자열 값 가져오기 |
|
||||
| `getIntValue()` | 현재 위치의 JSON 정수 값 가져오기 |
|
||||
| `getBooleanValue()` | 현재 위치의 JSON 불리언 값 가져오기 |
|
||||
| `getDoubleValue()` | 현재 위치의 JSON 실수 값 가져오기 |
|
||||
| `getCurrentName()` | 현재 JSON 필드명 가져오기 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 JSON 쓰기 (`JsonGenerator`)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `writeStartObject()` | JSON 객체 시작 (`{` 추가) |
|
||||
| `writeEndObject()` | JSON 객체 종료 (`}` 추가) |
|
||||
| `writeStartArray()` | JSON 배열 시작 (`[` 추가) |
|
||||
| `writeEndArray()` | JSON 배열 종료 (`]` 추가) |
|
||||
| `writeFieldName(String name)` | JSON 필드명 작성 |
|
||||
| `writeString(String text)` | JSON 문자열 값 작성 |
|
||||
| `writeNumber(int value)` | JSON 숫자 값 작성 |
|
||||
| `writeBoolean(boolean value)` | JSON 불리언 값 작성 |
|
||||
| `writeNull()` | JSON `null` 값 작성 |
|
||||
|
||||
---
|
||||
|
||||
## **3. Jackson Streaming API 예제**
|
||||
|
||||
### **✔ JSON 읽기 (`JsonParser`) 예제**
|
||||
아래 JSON 파일을 스트리밍 방식으로 읽어보자.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "홍길동",
|
||||
"age": 30,
|
||||
"isAdmin": false
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 JSON을 스트리밍 방식으로 읽는 코드**
|
||||
```java
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import java.io.File;
|
||||
|
||||
public class JsonParserExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
JsonFactory factory = new JsonFactory();
|
||||
JsonParser parser = factory.createParser(new File("data.json"));
|
||||
|
||||
String fieldName = null;
|
||||
while (!parser.isClosed()) {
|
||||
JsonToken token = parser.nextToken();
|
||||
|
||||
if (token == null) break;
|
||||
|
||||
if (token.isStructStart() || token.isStructEnd()) {
|
||||
// JSON의 {, } 같은 기호는 스킵
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == JsonToken.FIELD_NAME) {
|
||||
fieldName = parser.getCurrentName();
|
||||
} else if (fieldName != null) {
|
||||
switch (fieldName) {
|
||||
case "name":
|
||||
System.out.println("이름: " + parser.getText());
|
||||
break;
|
||||
case "age":
|
||||
System.out.println("나이: " + parser.getIntValue());
|
||||
break;
|
||||
case "isAdmin":
|
||||
System.out.println("관리자 여부: " + parser.getBooleanValue());
|
||||
break;
|
||||
}
|
||||
fieldName = null;
|
||||
}
|
||||
}
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
이름: 홍길동
|
||||
나이: 30
|
||||
관리자 여부: false
|
||||
```
|
||||
✅ **JSON을 한 줄씩 읽으면서 처리하는 방식이라 메모리 사용량이 적다!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ JSON 쓰기 (`JsonGenerator`) 예제**
|
||||
**🔹 JSON을 스트리밍 방식으로 생성하는 코드**
|
||||
```java
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import java.io.File;
|
||||
|
||||
public class JsonGeneratorExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
JsonFactory factory = new JsonFactory();
|
||||
JsonGenerator generator = factory.createGenerator(new File("output.json"), com.fasterxml.jackson.core.JsonEncoding.UTF8);
|
||||
|
||||
generator.writeStartObject();
|
||||
generator.writeStringField("name", "홍길동");
|
||||
generator.writeNumberField("age", 30);
|
||||
generator.writeBooleanField("isAdmin", false);
|
||||
generator.writeEndObject();
|
||||
|
||||
generator.close();
|
||||
System.out.println("JSON 파일 생성 완료!");
|
||||
}
|
||||
}
|
||||
```
|
||||
**🔹 생성된 `output.json` 파일 내용**
|
||||
```json
|
||||
{
|
||||
"name": "홍길동",
|
||||
"age": 30,
|
||||
"isAdmin": false
|
||||
}
|
||||
```
|
||||
✅ **한 줄씩 JSON을 생성하는 방식이라 메모리 사용량이 적다!**
|
||||
|
||||
---
|
||||
|
||||
## **4. Jackson Streaming API vs 일반 Jackson API**
|
||||
| 비교 항목 | Jackson Streaming API | 일반 Jackson (`ObjectMapper`) |
|
||||
|------------|------------------|----------------|
|
||||
| 데이터 처리 방식 | **스트리밍 방식 (한 줄씩 처리)** | **전체 로딩 방식 (객체 매핑)** |
|
||||
| 메모리 사용량 | **적음 (대량 데이터에 적합)** | 많음 (작은 데이터에 적합) |
|
||||
| 속도 | 빠름 | 비교적 느림 |
|
||||
| 사용 예시 | **대량의 JSON 데이터를 읽고 쓸 때** | **소규모 JSON 데이터를 객체로 변환할 때** |
|
||||
|
||||
---
|
||||
|
||||
## **5. 정리**
|
||||
✔ **Jackson Streaming API**는 JSON을 한 줄씩 읽고 쓰는 방식이라 **대량의 JSON 데이터를 처리할 때 매우 유용**하다.
|
||||
✔ `JsonParser`를 사용하면 JSON을 **이벤트 기반으로 파싱 가능**하다.
|
||||
✔ `JsonGenerator`를 사용하면 JSON을 **메모리 효율적으로 생성 가능**하다.
|
||||
✔ 일반 `ObjectMapper`보다 **메모리 사용량이 적고, 속도가 빠르다!**
|
||||
|
||||
✅ **대량의 JSON 데이터를 처리해야 한다면, Jackson Streaming API를 사용하자!**
|
||||
168
docs/Jackson.md
Normal file
168
docs/Jackson.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# **Jackson 어노테이션 정리 및 예제**
|
||||
|
||||
## **1. Jackson이란?**
|
||||
**Jackson**은 **자바 객체를 JSON으로 변환하거나 JSON을 자바 객체로 변환하는 라이브러리**다.
|
||||
Spring Boot에서도 기본적으로 사용되며, 속도가 빠르고 사용법이 간단하다.
|
||||
|
||||
**✅ Jackson의 주요 기능**
|
||||
✔ **자바 객체 → JSON 변환 (`ObjectMapper.writeValue()`)**
|
||||
✔ **JSON → 자바 객체 변환 (`ObjectMapper.readValue()`)**
|
||||
✔ **JSON 포맷 커스터마이징 (필드 제외, 포맷 변경 등)**
|
||||
|
||||
---
|
||||
|
||||
## **2. Jackson 주요 어노테이션 정리**
|
||||
|
||||
### **📌 직렬화 (객체 → JSON) 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|------------|------|
|
||||
| `@JsonProperty` | 필드의 JSON 속성명을 변경 |
|
||||
| `@JsonIgnore` | 특정 필드를 JSON 변환에서 제외 |
|
||||
| `@JsonIgnoreProperties` | 여러 필드를 JSON 변환에서 제외 |
|
||||
| `@JsonInclude` | `null` 값 또는 기본값을 JSON에서 제외 |
|
||||
| `@JsonGetter` | `getter` 메서드의 JSON 속성명을 변경 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 역직렬화 (JSON → 객체) 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|------------|------|
|
||||
| `@JsonCreator` | JSON 데이터를 객체로 변환할 때 생성자 지정 |
|
||||
| `@JsonSetter` | `setter` 메서드의 JSON 속성명을 변경 |
|
||||
| `@JsonAlias` | 여러 개의 JSON 키를 하나의 필드로 매핑 |
|
||||
| `@JsonIgnoreProperties(ignoreUnknown = true)` | JSON에 없는 필드 무시 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 날짜/시간 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|------------|------|
|
||||
| `@JsonFormat` | 날짜/시간 포맷을 지정 |
|
||||
| `@JsonDeserialize` | 커스텀 역직렬화 지정 |
|
||||
| `@JsonSerialize` | 커스텀 직렬화 지정 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 고급 기능 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|------------|------|
|
||||
| `@JsonUnwrapped` | 중첩 객체의 필드를 부모 객체에 포함 |
|
||||
| `@JsonTypeInfo` | 다형성(Polymorphism) 처리를 위한 타입 정보 추가 |
|
||||
|
||||
---
|
||||
|
||||
## **3. Jackson 어노테이션 예제**
|
||||
|
||||
### **✔ 1) `@JsonProperty` - JSON 속성명 변경**
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
class User {
|
||||
@JsonProperty("full_name")
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
public User(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
// Getter & Setter 생략
|
||||
}
|
||||
|
||||
public class JacksonExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
User user = new User("홍길동", 30);
|
||||
|
||||
String json = objectMapper.writeValueAsString(user);
|
||||
System.out.println(json); // 출력: {"full_name":"홍길동","age":30}
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **필드명이 `name`이지만, JSON에서는 `full_name`으로 출력됨!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ 2) `@JsonIgnore` - 특정 필드 제외**
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
class User {
|
||||
private String name;
|
||||
|
||||
@JsonIgnore
|
||||
private String password; // JSON에서 제외됨
|
||||
|
||||
public User(String name, String password) {
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
```
|
||||
**🔹 변환 결과**
|
||||
```json
|
||||
{"name":"홍길동"}
|
||||
```
|
||||
✅ **`password` 필드는 JSON에서 제외됨!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ 3) `@JsonInclude` - `null` 값 제외**
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
class User {
|
||||
private String name;
|
||||
private String nickname; // null 값일 경우 JSON에서 제외됨
|
||||
|
||||
public User(String name, String nickname) {
|
||||
this.name = name;
|
||||
this.nickname = nickname;
|
||||
}
|
||||
}
|
||||
```
|
||||
**🔹 변환 결과 (`nickname`이 `null`일 때)**
|
||||
```json
|
||||
{"name":"홍길동"}
|
||||
```
|
||||
✅ **`nickname`이 `null`이면 JSON에서 제외됨!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ 4) `@JsonAlias` - 여러 개의 키를 하나의 필드로 매핑**
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
|
||||
class User {
|
||||
@JsonAlias({"full_name", "user_name"})
|
||||
private String name;
|
||||
}
|
||||
```
|
||||
✅ **JSON의 `"full_name"` 또는 `"user_name"`이 모두 `name` 필드로 매핑됨!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ 5) `@JsonFormat` - 날짜 포맷 변경**
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.util.Date;
|
||||
|
||||
class Event {
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date eventDate;
|
||||
}
|
||||
```
|
||||
✅ **날짜를 `"2025-03-06 14:30:00"` 형식으로 변환 가능!**
|
||||
|
||||
---
|
||||
|
||||
## **4. Jackson 정리**
|
||||
✔ **Jackson은 JSON 변환을 위한 강력한 라이브러리**
|
||||
✔ **어노테이션을 활용하면 JSON 변환을 세밀하게 제어 가능**
|
||||
✔ **필드명 변경, 필드 제외, 날짜 포맷 지정 등 다양한 기능 지원**
|
||||
|
||||
✅ **Jackson을 사용하면 JSON 변환이 훨씬 쉽고 간결해짐!**
|
||||
190
docs/Lombok.md
Normal file
190
docs/Lombok.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# **Lombok 어노테이션 정리 및 예제**
|
||||
|
||||
## **1. Lombok이란?**
|
||||
Lombok은 **자바에서 반복적인 코드 작성을 줄여주는 라이브러리**이다.
|
||||
특히, **Getter, Setter, 생성자, toString, equals, hashCode** 등의 메서드를 **자동으로 생성**해준다.
|
||||
|
||||
**✅ Lombok을 사용하면 다음과 같은 장점이 있다.**
|
||||
✔ **코드가 간결해짐** (Getter, Setter, 생성자 등의 반복 코드 제거)
|
||||
✔ **가독성이 향상됨** (핵심 로직만 보이므로 더 쉽게 이해 가능)
|
||||
✔ **컴파일 타임에 코드가 자동 생성됨** (성능 저하 없음)
|
||||
|
||||
---
|
||||
|
||||
## **2. Lombok 주요 어노테이션 정리**
|
||||
|
||||
### **📌 클래스 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|---|---|
|
||||
| `@Getter` | 모든 필드의 Getter 메서드 생성 |
|
||||
| `@Setter` | 모든 필드의 Setter 메서드 생성 |
|
||||
| `@ToString` | `toString()` 메서드 자동 생성 |
|
||||
| `@EqualsAndHashCode` | `equals()` 및 `hashCode()` 자동 생성 |
|
||||
| `@NoArgsConstructor` | 기본 생성자 자동 생성 |
|
||||
| `@AllArgsConstructor` | 모든 필드를 포함한 생성자 자동 생성 |
|
||||
| `@RequiredArgsConstructor` | `final` 또는 `@NonNull` 필드만 포함하는 생성자 생성 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 필드 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|---|---|
|
||||
| `@NonNull` | 필드가 `null`이 될 수 없도록 설정 (생성자에서 `null` 체크) |
|
||||
| `@Value` | 불변(immutable) 클래스를 만들 때 사용 (`@Getter`, `@AllArgsConstructor`, `final` 자동 적용) |
|
||||
|
||||
---
|
||||
|
||||
### **📌 빌더 패턴 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|---|---|
|
||||
| `@Builder` | 빌더 패턴을 자동 생성 |
|
||||
| `@Singular` | `@Builder`에서 컬렉션(List, Set 등)을 초기화할 때 사용 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 로깅 관련 어노테이션**
|
||||
| 어노테이션 | 설명 |
|
||||
|---|---|
|
||||
| `@Slf4j` | `org.slf4j.Logger`를 자동 생성 |
|
||||
| `@Log` | `java.util.logging.Logger`를 자동 생성 |
|
||||
|
||||
---
|
||||
|
||||
## **3. Lombok 어노테이션 예제**
|
||||
|
||||
### **✔ 1) Getter, Setter 자동 생성**
|
||||
```java
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class User {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
```
|
||||
**🔹 사용 예시**
|
||||
```java
|
||||
User user = new User();
|
||||
user.setName("홍길동");
|
||||
user.setAge(30);
|
||||
System.out.println(user.getName()); // 출력: 홍길동
|
||||
```
|
||||
✅ `getName()`, `setName()`, `getAge()`, `setAge()` 메서드가 자동 생성됨!
|
||||
|
||||
---
|
||||
|
||||
### **✔ 2) `@ToString` 자동 생성**
|
||||
```java
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
public class User {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
```
|
||||
**🔹 사용 예시**
|
||||
```java
|
||||
User user = new User();
|
||||
user.setName("홍길동");
|
||||
user.setAge(30);
|
||||
System.out.println(user); // 출력: User(name=홍길동, age=30)
|
||||
```
|
||||
✅ `toString()`을 직접 작성할 필요 없음!
|
||||
|
||||
---
|
||||
|
||||
### **✔ 3) `@NoArgsConstructor`, `@AllArgsConstructor` 자동 생성**
|
||||
```java
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class User {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
```
|
||||
**🔹 사용 예시**
|
||||
```java
|
||||
User user1 = new User(); // 기본 생성자 사용
|
||||
User user2 = new User("홍길동", 30); // 모든 필드를 받는 생성자 사용
|
||||
```
|
||||
✅ **기본 생성자와 모든 필드를 포함한 생성자가 자동 생성됨!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ 4) `@RequiredArgsConstructor` 사용 (final 필드만 포함)**
|
||||
```java
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class User {
|
||||
private final String name;
|
||||
private int age;
|
||||
}
|
||||
```
|
||||
**🔹 사용 예시**
|
||||
```java
|
||||
User user = new User("홍길동"); // name 필드만 포함된 생성자
|
||||
```
|
||||
✅ **`final` 필드만 포함하는 생성자가 자동 생성됨!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ 5) `@Builder`를 이용한 빌더 패턴**
|
||||
```java
|
||||
import lombok.Builder;
|
||||
|
||||
@Builder
|
||||
public class User {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
```
|
||||
**🔹 사용 예시**
|
||||
```java
|
||||
User user = User.builder()
|
||||
.name("홍길동")
|
||||
.age(30)
|
||||
.build();
|
||||
System.out.println(user);
|
||||
```
|
||||
✅ **빌더 패턴이 자동 생성되어 `User.builder().name("홍길동").age(30).build();`로 객체 생성 가능!**
|
||||
|
||||
---
|
||||
|
||||
### **✔ 6) `@Slf4j`를 이용한 로그 출력**
|
||||
```java
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class LogExample {
|
||||
public static void main(String[] args) {
|
||||
log.info("Hello, Lombok!");
|
||||
}
|
||||
}
|
||||
```
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
INFO - Hello, Lombok!
|
||||
```
|
||||
✅ **Logger 객체를 직접 생성할 필요 없이 `log.info("Hello, Lombok!")`처럼 바로 사용 가능!**
|
||||
|
||||
---
|
||||
|
||||
## **4. Lombok 정리**
|
||||
✔ **Lombok은 자바에서 반복적인 코드 작성을 줄여주는 라이브러리**
|
||||
✔ **Getter, Setter, 생성자, toString, equals, hashCode 등을 자동 생성**
|
||||
✔ **빌더 패턴 및 로깅도 쉽게 적용 가능**
|
||||
✔ **코드를 간결하게 만들고 가독성을 향상시킴**
|
||||
|
||||
✅ **IDE에서 Lombok을 사용하려면?**
|
||||
- **Lombok 라이브러리를 추가** (`pom.xml` 또는 `build.gradle`)
|
||||
- **IntelliJ → Plugins에서 Lombok 플러그인 설치**
|
||||
- **설정에서 "Enable annotation processing" 활성화** (`Settings > Build, Execution, Deployment > Compiler > Annotation Processors`)
|
||||
|
||||
✅ **Lombok을 사용하면 코드가 간결해지고, 유지보수가 편리해짐!**
|
||||
164
docs/Optional.md
Normal file
164
docs/Optional.md
Normal file
@@ -0,0 +1,164 @@
|
||||
## 자바 `Optional` 주요 메서드 정리
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|-----|-----|
|
||||
| `of(T value)` | `null`이 아닌 값을 감싸는 `Optional` 생성 |
|
||||
| `ofNullable(T value)` | `null`일 수도 있는 값을 감싸는 `Optional` 생성 |
|
||||
| `empty()` | 비어 있는 `Optional` 생성 |
|
||||
| `isPresent()` | 값이 존재하면 `true`, 없으면 `false` 반환 |
|
||||
| `isEmpty()` | 값이 없으면 `true`, 있으면 `false` 반환 |
|
||||
| `get()` | 값이 존재하면 반환, 없으면 `NoSuchElementException` 발생 |
|
||||
| `orElse(T other)` | 값이 존재하면 반환, 없으면 기본값 반환 |
|
||||
| `orElseGet(Supplier)` | 값이 존재하면 반환, 없으면 함수 실행 결과 반환 |
|
||||
| `orElseThrow()` | 값이 존재하면 반환, 없으면 예외 발생 |
|
||||
| `or(Supplier)` | 값이 존재하면 현재 `Optional` 반환, 없으면 다른 `Optional` 반환 |
|
||||
| `ifPresent(Consumer)` | 값이 존재하면 실행할 코드 지정 |
|
||||
| `ifPresentOrElse(Consumer, Runnable)` | 값이 존재하면 실행할 코드, 없으면 실행할 코드 지정 |
|
||||
| `map(Function)` | 값을 변환하여 새로운 `Optional` 반환 |
|
||||
| `flatMap(Function)` | 중첩된 `Optional`을 평탄화하여 반환 |
|
||||
| `filter(Predicate)` | 조건을 만족하면 `Optional` 유지, 아니면 비움 |
|
||||
|
||||
---
|
||||
|
||||
## 자바 `Optional` 쉽게 설명하기
|
||||
|
||||
### `Optional`이란?
|
||||
자바에서는 `null`이 자주 등장한다. 하지만 `null`을 잘못 다루면 `NullPointerException`(NPE)이 발생한다.
|
||||
이 문제를 해결하기 위해 나온 것이 **`Optional`(옵셔널)**이다.
|
||||
|
||||
> **"옵셔널은 값이 있을 수도 있고 없을 수도 있는 박스다."**
|
||||
|
||||
즉, **값을 안전하게 감싸고 다룰 수 있도록 도와주는 도구**다.
|
||||
값이 있으면 꺼내서 사용하고, 없으면 `null` 대신 기본값을 사용하거나 예외를 발생시킬 수 있다.
|
||||
|
||||
---
|
||||
|
||||
### `Optional` 사용 예제
|
||||
|
||||
#### 1. `Optional` 생성하기
|
||||
```java
|
||||
Optional<String> name = Optional.of("Alice"); // 값이 있는 Optional
|
||||
Optional<String> emptyName = Optional.empty(); // 비어 있는 Optional
|
||||
Optional<String> nullableName = Optional.ofNullable(null); // null 가능
|
||||
```
|
||||
|
||||
- `Optional.of(value)`: `null`이 아닌 값을 감쌈 (null이면 예외 발생)
|
||||
- `Optional.empty()`: 비어 있는 `Optional` 생성
|
||||
- `Optional.ofNullable(value)`: `null`일 수도 있는 값을 감쌈
|
||||
|
||||
---
|
||||
|
||||
#### 2. 값이 있는지 확인하기
|
||||
```java
|
||||
Optional<String> name = Optional.of("Alice");
|
||||
|
||||
System.out.println(name.isPresent()); // true
|
||||
System.out.println(name.isEmpty()); // false
|
||||
```
|
||||
- `isPresent()`: 값이 있으면 `true`
|
||||
- `isEmpty()`: 값이 없으면 `true`
|
||||
|
||||
---
|
||||
|
||||
#### 3. 값 가져오기
|
||||
```java
|
||||
Optional<String> name = Optional.of("Alice");
|
||||
|
||||
System.out.println(name.get()); // "Alice"
|
||||
```
|
||||
하지만 **값이 없을 때 `get()`을 호출하면 예외가 발생**하므로 주의해야 한다.
|
||||
|
||||
```java
|
||||
Optional<String> emptyName = Optional.empty();
|
||||
System.out.println(emptyName.get()); // NoSuchElementException 발생!
|
||||
```
|
||||
|
||||
그래서 **`orElse()` 또는 `orElseGet()`을 사용하는 것이 안전하다.**
|
||||
|
||||
```java
|
||||
Optional<String> emptyName = Optional.empty();
|
||||
|
||||
String result1 = emptyName.orElse("Unknown"); // 기본값 제공
|
||||
String result2 = emptyName.orElseGet(() -> "Generated Name"); // 함수로 기본값 제공
|
||||
|
||||
System.out.println(result1); // "Unknown"
|
||||
System.out.println(result2); // "Generated Name"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4. 값이 없을 때 예외 던지기
|
||||
```java
|
||||
Optional<String> emptyName = Optional.empty();
|
||||
|
||||
String name = emptyName.orElseThrow(() -> new IllegalArgumentException("이름이 없습니다!"));
|
||||
```
|
||||
값이 없으면 `IllegalArgumentException`을 던진다.
|
||||
|
||||
---
|
||||
|
||||
#### 5. 값이 있을 때만 실행하기 (`ifPresent`)
|
||||
```java
|
||||
Optional<String> name = Optional.of("Alice");
|
||||
|
||||
name.ifPresent(n -> System.out.println("이름: " + n)); // 이름이 있으면 출력
|
||||
```
|
||||
값이 없으면 아무 일도 일어나지 않는다.
|
||||
|
||||
---
|
||||
|
||||
#### 6. 값이 있으면 실행, 없으면 다른 작업 수행 (`ifPresentOrElse`)
|
||||
```java
|
||||
Optional<String> name = Optional.empty();
|
||||
|
||||
name.ifPresentOrElse(
|
||||
n -> System.out.println("이름: " + n), // 값이 있을 때 실행
|
||||
() -> System.out.println("이름이 없습니다!") // 값이 없을 때 실행
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 7. 값 변환하기 (`map`)
|
||||
```java
|
||||
Optional<String> name = Optional.of("Alice");
|
||||
|
||||
Optional<Integer> length = name.map(String::length);
|
||||
|
||||
System.out.println(length.get()); // 5
|
||||
```
|
||||
- `map(Function)`: 값이 있으면 변환하고, 없으면 `Optional.empty()` 유지
|
||||
|
||||
---
|
||||
|
||||
#### 8. 중첩된 `Optional`을 평탄화 (`flatMap`)
|
||||
```java
|
||||
Optional<Optional<String>> nested = Optional.of(Optional.of("Alice"));
|
||||
|
||||
Optional<String> flat = nested.flatMap(n -> n);
|
||||
|
||||
System.out.println(flat.get()); // "Alice"
|
||||
```
|
||||
- `flatMap(Function)`: 중첩된 `Optional`을 단일 `Optional`로 변환
|
||||
|
||||
---
|
||||
|
||||
#### 9. 조건에 맞는 값만 유지하기 (`filter`)
|
||||
```java
|
||||
Optional<String> name = Optional.of("Alice");
|
||||
|
||||
Optional<String> filtered = name.filter(n -> n.startsWith("A"));
|
||||
|
||||
System.out.println(filtered.get()); // "Alice"
|
||||
```
|
||||
- `filter(Predicate)`: 조건을 만족하면 `Optional` 유지, 아니면 `Optional.empty()` 반환
|
||||
|
||||
---
|
||||
|
||||
### `Optional`을 사용하면 좋은 이유
|
||||
1. **`null` 체크가 필요 없음** → `if (value != null)` 같은 코드 제거 가능
|
||||
2. **코드가 더 읽기 쉬움** → `Optional` 메서드를 통해 명확한 의도를 전달
|
||||
3. **안전한 값 처리 가능** → `orElse()`, `ifPresent()` 등을 활용하여 `null`을 안전하게 대체
|
||||
|
||||
즉, **"옵셔널은 값이 있을 수도 있고 없을 수도 있는 안전한 박스"**다.
|
||||
무작정 `null`을 다루는 것보다 `Optional`을 활용하면 **예외 없이 더 깔끔하고 안전한 코드**를 작성할 수 있다.
|
||||
196
docs/Reflection.md
Normal file
196
docs/Reflection.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# **자바 리플렉션(Reflection) 쉽게 배우기**
|
||||
|
||||
## **1. 리플렉션이란?**
|
||||
리플렉션(Reflection)이란 **실행 중에 클래스의 정보를 조회하고, 필드·메서드·생성자를 동적으로 조작하는 기능**이다.
|
||||
즉, **컴파일 시점이 아니라 런타임(실행 중)에 클래스 내부를 들여다보고 조작할 수 있다.**
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `Class` 클래스 (클래스 정보 조회)**
|
||||
| 메서드 | 설명 |
|
||||
|-----|---|
|
||||
| `Class.forName("클래스명")` | 클래스 객체 가져오기 (정적 로드) |
|
||||
| `Object.getClass()` | 인스턴스로부터 클래스 객체 얻기 |
|
||||
| `getName()` | 클래스 전체 이름 (`패키지.클래스명`) 반환 |
|
||||
| `getSimpleName()` | 클래스 단순 이름 반환 |
|
||||
| `getDeclaredFields()` | 선언된 모든 필드(`Field[]`) 조회 |
|
||||
| `getDeclaredMethods()` | 선언된 모든 메서드(`Method[]`) 조회 |
|
||||
| `getDeclaredConstructors()` | 선언된 모든 생성자(`Constructor[]`) 조회 |
|
||||
|
||||
**예제 코드 (클래스 정보 출력)**
|
||||
```java
|
||||
Class<?> clazz = Class.forName("java.util.ArrayList");
|
||||
|
||||
System.out.println("클래스 이름: " + clazz.getName());
|
||||
System.out.println("간단한 이름: " + clazz.getSimpleName());
|
||||
System.out.println("패키지: " + clazz.getPackage().getName());
|
||||
```
|
||||
✅ **클래스의 이름과 패키지 정보를 확인할 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
### **(2) `Field` 클래스 (필드 정보 조회 및 수정)**
|
||||
| 메서드 | 설명 |
|
||||
|-----|---|
|
||||
| `getName()` | 필드 이름 가져오기 |
|
||||
| `getType()` | 필드 타입 가져오기 |
|
||||
| `getModifiers()` | 접근 제어자 가져오기 |
|
||||
| `setAccessible(true)` | private 필드 접근 가능하게 설정 |
|
||||
| `get(Object obj)` | 특정 객체의 필드 값 가져오기 |
|
||||
| `set(Object obj, Object value)` | 특정 객체의 필드 값 변경 |
|
||||
|
||||
**예제 코드 (필드 조회 및 값 변경)**
|
||||
```java
|
||||
import java.lang.reflect.*;
|
||||
|
||||
class Person {
|
||||
private String name = "John";
|
||||
}
|
||||
|
||||
public class ReflectionExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Person person = new Person();
|
||||
Class<?> clazz = person.getClass();
|
||||
|
||||
Field field = clazz.getDeclaredField("name");
|
||||
field.setAccessible(true); // private 접근 허용
|
||||
|
||||
System.out.println("기존 값: " + field.get(person));
|
||||
field.set(person, "Alice"); // 값 변경
|
||||
System.out.println("변경된 값: " + field.get(person));
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **private 필드도 강제로 조작할 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
### **(3) `Method` 클래스 (메서드 정보 조회 및 호출)**
|
||||
| 메서드 | 설명 |
|
||||
|-----|---|
|
||||
| `getName()` | 메서드 이름 가져오기 |
|
||||
| `getParameterTypes()` | 메서드 매개변수 타입 가져오기 |
|
||||
| `getReturnType()` | 반환 타입 가져오기 |
|
||||
| `invoke(Object obj, Object... args)` | 메서드 실행 |
|
||||
|
||||
**예제 코드 (메서드 실행)**
|
||||
```java
|
||||
import java.lang.reflect.*;
|
||||
|
||||
class Calculator {
|
||||
private int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReflectionExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Calculator calc = new Calculator();
|
||||
Class<?> clazz = calc.getClass();
|
||||
|
||||
Method method = clazz.getDeclaredMethod("add", int.class, int.class);
|
||||
method.setAccessible(true); // private 접근 허용
|
||||
|
||||
int result = (int) method.invoke(calc, 5, 10); // 메서드 실행
|
||||
System.out.println("결과: " + result);
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **private 메서드도 실행할 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
### **(4) `Constructor` 클래스 (생성자 정보 조회 및 인스턴스 생성)**
|
||||
| 메서드 | 설명 |
|
||||
|-----|---|
|
||||
| `getParameterTypes()` | 생성자 매개변수 타입 가져오기 |
|
||||
| `newInstance(Object... initargs)` | 새로운 인스턴스 생성 |
|
||||
|
||||
**예제 코드 (객체 동적 생성)**
|
||||
```java
|
||||
import java.lang.reflect.*;
|
||||
|
||||
class Person {
|
||||
private String name;
|
||||
|
||||
public Person(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReflectionExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Class<?> clazz = Person.class;
|
||||
|
||||
Constructor<?> constructor = clazz.getConstructor(String.class);
|
||||
Object person = constructor.newInstance("Charlie"); // 동적 생성
|
||||
|
||||
Method method = clazz.getMethod("getName");
|
||||
System.out.println("이름: " + method.invoke(person)); // Charlie 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **리플렉션을 이용해 생성자를 실행하고 객체를 만들 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
### **(5) `Modifier` 클래스 (접근 제어자 확인)**
|
||||
| 메서드 | 설명 |
|
||||
|-----|---|
|
||||
| `isPublic(int mod)` | public 여부 확인 |
|
||||
| `isPrivate(int mod)` | private 여부 확인 |
|
||||
| `isStatic(int mod)` | static 여부 확인 |
|
||||
|
||||
**예제 코드 (필드 접근 제어자 확인)**
|
||||
```java
|
||||
import java.lang.reflect.*;
|
||||
|
||||
class Sample {
|
||||
private int num;
|
||||
public static String text;
|
||||
}
|
||||
|
||||
public class ReflectionExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Field field = Sample.class.getDeclaredField("text");
|
||||
int modifiers = field.getModifiers();
|
||||
|
||||
System.out.println("static인가? " + Modifier.isStatic(modifiers)); // true
|
||||
System.out.println("public인가? " + Modifier.isPublic(modifiers)); // true
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **필드나 메서드가 `public`, `private`, `static`인지 확인할 수 있다!**
|
||||
|
||||
---
|
||||
|
||||
## **3. 리플렉션의 활용 예시**
|
||||
✔ **DI(의존성 주입) 프레임워크(Spring)** → 리플렉션으로 객체를 생성하고 자동 주입
|
||||
✔ **JUnit 테스트 프레임워크** → `@Test` 붙은 메서드 자동 실행
|
||||
✔ **JSON 라이브러리(Jackson, Gson)** → 객체를 JSON으로 변환할 때 필드 조회
|
||||
✔ **프록시 패턴(AOP, 동적 프록시)** → 런타임에 동적 메서드 실행
|
||||
|
||||
---
|
||||
|
||||
## **4. 리플렉션의 단점과 주의점**
|
||||
❌ **성능 저하** → 리플렉션은 일반 메서드 호출보다 느리다. 자주 사용하면 성능 문제가 생길 수 있다.
|
||||
❌ **보안 문제** → `setAccessible(true)`로 private 필드/메서드에 접근할 수 있어 보안 위험이 있다.
|
||||
❌ **컴파일 타임 체크 불가능** → 오타나 잘못된 메서드 호출은 실행 시점에서야 오류가 발생한다.
|
||||
|
||||
✅ **따라서, 꼭 필요한 경우에만 사용하고 남용하지 않는 것이 좋다!**
|
||||
|
||||
---
|
||||
|
||||
## **5. 정리**
|
||||
✅ **리플렉션은 실행 중 클래스 정보를 조회하고 조작할 수 있는 기능이다!**
|
||||
✅ **`Class`, `Field`, `Method`, `Constructor` 클래스를 활용하면 필드 값 변경, 메서드 실행, 객체 생성 등이 가능하다!**
|
||||
✅ **Spring, JSON 파싱, 테스트 프레임워크 등에서 널리 사용된다!**
|
||||
✅ **하지만 성능 저하와 보안 문제를 고려하여 신중히 사용해야 한다!**
|
||||
|
||||
✔ **리플렉션을 잘 활용하면, 자바 프로그램을 더 유연하게 만들 수 있다!**
|
||||
170
docs/Regex.md
Normal file
170
docs/Regex.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# **자바 정규 표현식(Regex) 쉽게 배우기**
|
||||
|
||||
자바에서는 정규 표현식(Regex)을 **문자열 검색, 패턴 매칭, 텍스트 변환** 등에 사용할 수 있다.
|
||||
정규 표현식을 활용하면 **복잡한 문자열을 간단한 패턴으로 처리**할 수 있다.
|
||||
|
||||
자바에서 정규 표현식을 다룰 때 주로 사용하는 클래스는 `Pattern`과 `Matcher`이다.
|
||||
또한, `String` 클래스에서도 정규 표현식을 지원하는 몇 가지 메서드가 있다.
|
||||
|
||||
---
|
||||
|
||||
## **1. 주요 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `Pattern` 클래스 (정규식 컴파일)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `Pattern.compile(String regex)` | 정규식을 컴파일하여 `Pattern` 객체 생성 |
|
||||
| `Pattern.matches(String regex, CharSequence input)` | 전체 문자열이 정규식과 일치하는지 확인 |
|
||||
| `pattern()` | 정규식을 문자열 형태로 반환 |
|
||||
| `split(CharSequence input)` | 정규식을 기준으로 문자열을 분리 |
|
||||
| `matcher(CharSequence input)` | `Matcher` 객체를 생성하여 세부적인 패턴 검색 수행 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
Pattern pattern = Pattern.compile("\\d+"); // 숫자 찾기
|
||||
Matcher matcher = pattern.matcher("abc123");
|
||||
|
||||
System.out.println(matcher.find()); // true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **(2) `Matcher` 클래스 (패턴 검색)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `find()` | 정규식과 일치하는 부분이 있는지 확인 |
|
||||
| `matches()` | 전체 문자열이 정규식과 일치하는지 확인 |
|
||||
| `lookingAt()` | 문자열의 **시작 부분**이 정규식과 일치하는지 확인 |
|
||||
| `group()` | 최근에 매칭된 문자열을 반환 |
|
||||
| `group(int group)` | 특정 그룹의 매칭 결과를 반환 |
|
||||
| `start()` | 매칭된 문자열의 시작 인덱스 반환 |
|
||||
| `end()` | 매칭된 문자열의 끝 인덱스 반환 |
|
||||
| `replaceAll(String replacement)` | 모든 매칭된 부분을 대체 |
|
||||
| `replaceFirst(String replacement)` | 첫 번째 매칭된 부분만 대체 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
Pattern pattern = Pattern.compile("\\d+");
|
||||
Matcher matcher = pattern.matcher("abc123xyz456");
|
||||
|
||||
while (matcher.find()) {
|
||||
System.out.println("찾은 숫자: " + matcher.group());
|
||||
}
|
||||
```
|
||||
출력 결과:
|
||||
```
|
||||
찾은 숫자: 123
|
||||
찾은 숫자: 456
|
||||
```
|
||||
→ `find()`를 사용하면 여러 번 매칭 가능.
|
||||
|
||||
---
|
||||
|
||||
### **(3) `String` 클래스의 정규 표현식 관련 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `matches(String regex)` | 전체 문자열이 정규식과 일치하는지 확인 |
|
||||
| `split(String regex)` | 정규식을 기준으로 문자열을 분리하여 배열 반환 |
|
||||
| `replaceAll(String regex, String replacement)` | 모든 매칭된 부분을 대체 |
|
||||
| `replaceFirst(String regex, String replacement)` | 첫 번째 매칭된 부분만 대체 |
|
||||
|
||||
**사용 예시:**
|
||||
```java
|
||||
String text = "apple,banana,grape";
|
||||
String[] fruits = text.split(",");
|
||||
|
||||
for (String fruit : fruits) {
|
||||
System.out.println(fruit);
|
||||
}
|
||||
```
|
||||
출력 결과:
|
||||
```
|
||||
apple
|
||||
banana
|
||||
grape
|
||||
```
|
||||
→ `split()`을 사용하면 쉼표(`,`)를 기준으로 문자열을 쉽게 분리할 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## **2. 정규 표현식 쉽게 설명하기**
|
||||
|
||||
정규 표현식은 **특정한 패턴을 가진 문자열을 검색하고, 추출하고, 변환할 수 있는 도구**이다.
|
||||
아래는 자주 사용하는 정규 표현식 패턴이다.
|
||||
|
||||
| 정규식 | 설명 | 예제 |
|
||||
|--------|------|------|
|
||||
| `.` | 임의의 한 글자 | `a.b` → "acb", "a1b" (O) / "ab", "acdb" (X) |
|
||||
| `\d` | 숫자 (0-9) | `\d+` → "123", "4567" |
|
||||
| `\D` | 숫자가 아닌 문자 | `\D+` → "abc", "!@#" |
|
||||
| `\w` | 알파벳, 숫자, `_` | `\w+` → "hello_123" |
|
||||
| `\W` | 알파벳, 숫자, `_` 제외한 문자 | `\W+` → "!@#" |
|
||||
| `\s` | 공백 (스페이스, 탭, 줄바꿈) | `\s+` → " " |
|
||||
| `\S` | 공백이 아닌 문자 | `\S+` → "Hello" |
|
||||
| `^` | 문자열의 시작 | `^abc` → "abcdef" (O) / "zabc" (X) |
|
||||
| `$` | 문자열의 끝 | `xyz$` → "abcxyz" (O) / "xyzabc" (X) |
|
||||
| `*` | 0개 이상 반복 | `a*` → "", "a", "aa", "aaaa" |
|
||||
| `+` | 1개 이상 반복 | `a+` → "a", "aa", "aaaa" (O) / "" (X) |
|
||||
| `?` | 0개 또는 1개 | `a?` → "", "a" |
|
||||
| `{n}` | 정확히 n번 반복 | `a{3}` → "aaa" |
|
||||
| `{n,}` | 최소 n번 반복 | `a{2,}` → "aa", "aaa", "aaaa" |
|
||||
| `{n,m}` | n번 이상 m번 이하 반복 | `a{2,4}` → "aa", "aaa", "aaaa" |
|
||||
|
||||
---
|
||||
|
||||
## **3. 자주 쓰는 정규식 예제**
|
||||
|
||||
### **(1) 이메일 검증**
|
||||
```java
|
||||
String regex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
|
||||
String email = "test@example.com";
|
||||
|
||||
boolean isValid = Pattern.matches(regex, email);
|
||||
System.out.println("이메일 검증: " + isValid); // true
|
||||
```
|
||||
→ 이메일 형식인지 확인할 때 사용.
|
||||
|
||||
---
|
||||
|
||||
### **(2) 전화번호 검증 (010-xxxx-xxxx)**
|
||||
```java
|
||||
String regex = "^010-\\d{4}-\\d{4}$";
|
||||
String phone = "010-1234-5678";
|
||||
|
||||
boolean isValid = Pattern.matches(regex, phone);
|
||||
System.out.println("전화번호 검증: " + isValid); // true
|
||||
```
|
||||
→ `010-xxxx-xxxx` 형식의 전화번호인지 확인.
|
||||
|
||||
---
|
||||
|
||||
### **(3) 숫자만 포함된 문자열 체크**
|
||||
```java
|
||||
String regex = "^\\d+$";
|
||||
String input = "123456";
|
||||
|
||||
boolean isNumber = Pattern.matches(regex, input);
|
||||
System.out.println("숫자만 포함?: " + isNumber); // true
|
||||
```
|
||||
→ 숫자로만 이루어진 문자열인지 검증할 때 사용.
|
||||
|
||||
---
|
||||
|
||||
### **(4) 특정 문자 제거 (`replaceAll()`)**
|
||||
```java
|
||||
String text = "Hello123World";
|
||||
String result = text.replaceAll("\\d", ""); // 숫자 제거
|
||||
|
||||
System.out.println(result); // "HelloWorld"
|
||||
```
|
||||
→ 숫자(`\d`)를 찾아서 제거.
|
||||
|
||||
---
|
||||
|
||||
## **4. 정리**
|
||||
|
||||
✅ **정규 표현식을 사용하면 복잡한 문자열 처리를 간단하게 해결 가능!**
|
||||
✅ **`Pattern` & `Matcher` 클래스를 사용하면 세부적인 패턴 매칭이 가능!**
|
||||
✅ **`String` 클래스에서도 `matches()`, `split()`, `replaceAll()` 등을 활용할 수 있음!**
|
||||
|
||||
즉, **자바의 정규 표현식 API를 잘 활용하면 문자열 검색, 치환, 검증이 훨씬 쉬워진다!**
|
||||
238
docs/RxJava.md
Normal file
238
docs/RxJava.md
Normal file
@@ -0,0 +1,238 @@
|
||||
### RxJava란?
|
||||
RxJava(ReactX for Java)는 **반응형 프로그래밍(Reactive Programming)** 을 Java에서 구현할 수 있도록 만든 라이브러리야. 비동기 데이터 흐름을 다루기 쉽게 만들어주고, 함수형 스타일의 연산자를 통해 데이터를 조작할 수 있게 해.
|
||||
|
||||
---
|
||||
|
||||
### 왜 RxJava를 사용할까?
|
||||
Java에서 비동기 작업을 하려면 **`Thread`**, **`Executor`**, **`Future`**, **`Callback`** 같은 것들을 써야 하는데, 이 방식들은 코드가 복잡해지고 가독성이 떨어지는 단점이 있어.
|
||||
RxJava는 이를 더 **선언적으로** 작성할 수 있도록 해주고, 다음과 같은 장점을 제공해:
|
||||
|
||||
1. **비동기 처리** – `Observable`을 사용하여 비동기적으로 데이터를 생성하고 소비할 수 있어.
|
||||
2. **함수형 조합** – `map`, `filter`, `flatMap` 등의 연산자를 사용해 데이터를 변환하고 조작할 수 있어.
|
||||
3. **스트림 기반 처리** – 데이터가 하나씩 처리되는 것이 아니라 스트림으로 들어오는 데이터를 반응형으로 처리할 수 있어.
|
||||
4. **에러 처리** – `onError` 핸들링을 통해 예외를 쉽게 관리할 수 있어.
|
||||
5. **백프레셔(Backpressure) 지원** – 소비자가 데이터를 처리할 수 없을 때 과부하를 방지할 수 있어.
|
||||
|
||||
---
|
||||
|
||||
### RxJava의 기본 개념
|
||||
#### 1. `Observable`과 `Observer`
|
||||
RxJava에서 가장 중요한 개념은 **`Observable(발행자)`** 와 **`Observer(구독자)`** 야.
|
||||
- **Observable**: 데이터를 발행하는 역할
|
||||
- **Observer**: 데이터를 구독해서 처리하는 역할
|
||||
|
||||
```java
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
|
||||
public class RxJavaExample {
|
||||
public static void main(String[] args) {
|
||||
// Observable 생성 (데이터 발행자)
|
||||
Observable<String> observable = Observable.just("Hello", "RxJava");
|
||||
|
||||
// Observer(구독자) 정의
|
||||
observable.subscribe(System.out::println);
|
||||
}
|
||||
}
|
||||
```
|
||||
출력:
|
||||
```
|
||||
Hello
|
||||
RxJava
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. `map`을 이용한 데이터 변환
|
||||
`map` 연산자를 사용하면 데이터를 변환할 수 있어.
|
||||
|
||||
```java
|
||||
Observable.just(1, 2, 3)
|
||||
.map(num -> num * 10) // 데이터를 10배 증가
|
||||
.subscribe(System.out::println);
|
||||
```
|
||||
출력:
|
||||
```
|
||||
10
|
||||
20
|
||||
30
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 3. `flatMap`을 이용한 비동기 작업
|
||||
`flatMap`은 비동기 작업을 수행하고 여러 개의 데이터를 반환할 때 유용해.
|
||||
|
||||
```java
|
||||
Observable.just("Hello")
|
||||
.flatMap(str -> Observable.fromArray(str.split("")))
|
||||
.subscribe(System.out::println);
|
||||
```
|
||||
출력:
|
||||
```
|
||||
H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4. `onError`를 활용한 에러 처리
|
||||
RxJava는 `onError`를 활용하여 에러를 잡아낼 수 있어.
|
||||
|
||||
```java
|
||||
Observable.just(5, 2, 0)
|
||||
.map(num -> 10 / num) // 0으로 나누면 예외 발생
|
||||
.onErrorReturnItem(-1) // 예외 발생 시 기본값 반환
|
||||
.subscribe(System.out::println);
|
||||
```
|
||||
출력:
|
||||
```
|
||||
2
|
||||
5
|
||||
-1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
RxJava는 비동기 프로그래밍을 보다 직관적이고 선언적으로 작성할 수 있도록 도와주는 라이브러리야.
|
||||
- `Observable`을 사용하여 데이터를 발행하고, `Observer`로 이를 구독할 수 있어.
|
||||
- `map`, `flatMap`, `filter` 등의 연산자를 사용해 데이터를 변환할 수 있어.
|
||||
- `onErrorReturnItem` 같은 방식으로 예외 처리를 쉽게 할 수 있어.
|
||||
|
||||
비동기 처리, API 호출, 이벤트 스트림 처리 등 다양한 곳에서 활용될 수 있기 때문에, 필요에 따라 적용해보면 좋아!
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## RxJava의 주요 클래스 및 메서드 정리
|
||||
|
||||
RxJava에는 다양한 클래스를 통해 **비동기 데이터 흐름을 처리**할 수 있도록 도와줘. 핵심 클래스들을 정리하고, 각 클래스별 주요 메서드를 표로 정리할게.
|
||||
|
||||
---
|
||||
|
||||
## 1. 주요 클래스 소개
|
||||
### (1) `Observable<T>`
|
||||
- **핵심 역할:** 데이터를 발행하는 기본적인 클래스. **N개의 데이터를 발행**할 수 있어.
|
||||
- **특징:** 하나 이상의 `Observer`가 구독할 수 있으며, **onNext, onError, onComplete** 이벤트를 제공해.
|
||||
|
||||
### (2) `Observer<T>`
|
||||
- **핵심 역할:** `Observable`이 발행하는 데이터를 **구독(Subscribe)** 하고 처리하는 클래스.
|
||||
- **주요 메서드:**
|
||||
- `onNext(T item)`: 새로운 데이터가 발행될 때 호출됨.
|
||||
- `onError(Throwable e)`: 오류 발생 시 호출됨.
|
||||
- `onComplete()`: 모든 데이터가 정상적으로 발행 완료되면 호출됨.
|
||||
|
||||
### (3) `Single<T>`
|
||||
- **핵심 역할:** **오직 하나의 데이터** 혹은 **에러만**을 발행하는 클래스.
|
||||
- **사용 예:** 네트워크 요청 결과(예: 로그인 API 응답).
|
||||
- **특징:** `onSuccess(T item)`, `onError(Throwable e)` 이벤트를 제공.
|
||||
|
||||
### (4) `Maybe<T>`
|
||||
- **핵심 역할:** 데이터가 **있을 수도 있고 없을 수도 있는** 경우 사용.
|
||||
- **사용 예:** 로컬 캐시에서 데이터를 불러올 때.
|
||||
- **특징:** `onSuccess`, `onError`, `onComplete` 이벤트 제공.
|
||||
|
||||
### (5) `Completable`
|
||||
- **핵심 역할:** 데이터를 발행하지 않고, **완료 여부만** 전달하는 클래스.
|
||||
- **사용 예:** 데이터 저장, 파일 다운로드 완료 이벤트.
|
||||
- **특징:** `onComplete()`, `onError(Throwable e)` 이벤트 제공.
|
||||
|
||||
### (6) `Flowable<T>`
|
||||
- **핵심 역할:** `Observable`과 유사하지만 **백프레셔(Backpressure) 지원**이 필요한 경우 사용.
|
||||
- **사용 예:** 많은 양의 데이터를 빠르게 발행할 때(예: 실시간 로그 스트림).
|
||||
|
||||
---
|
||||
|
||||
## 2. 클래스별 주요 메서드 정리
|
||||
각 클래스별 주요 메서드를 표로 정리하면 다음과 같아.
|
||||
|
||||
### (1) `Observable<T>`의 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `just(T...)` | 지정한 데이터를 순차적으로 발행 |
|
||||
| `fromArray(T[])` | 배열 데이터를 순차적으로 발행 |
|
||||
| `fromIterable(Iterable<T>)` | 리스트 데이터를 순차적으로 발행 |
|
||||
| `create(ObservableOnSubscribe<T>)` | 직접 `onNext`, `onError`, `onComplete` 이벤트를 발생시키도록 정의 |
|
||||
| `map(Function<T, R>)` | 데이터를 변환 |
|
||||
| `flatMap(Function<T, Observable<R>>)` | 비동기적으로 데이터를 변환 |
|
||||
| `filter(Predicate<T>)` | 조건을 만족하는 데이터만 발행 |
|
||||
| `debounce(long, TimeUnit)` | 짧은 시간 내에 연속으로 발행되는 데이터 중 마지막 데이터만 발행 |
|
||||
| `merge(Observable<T>...)` | 여러 개의 `Observable`을 합침 |
|
||||
| `zip(Observable<T1>, Observable<T2>, BiFunction<T1, T2, R>)` | 두 개의 `Observable`을 결합하여 새로운 데이터 발행 |
|
||||
| `onErrorReturn(Function<Throwable, T>)` | 에러 발생 시 기본값을 반환 |
|
||||
| `subscribe(Observer<T>)` | 데이터를 구독 |
|
||||
|
||||
---
|
||||
|
||||
### (2) `Single<T>`의 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `just(T)` | 단일 데이터를 발행 |
|
||||
| `fromCallable(Callable<T>)` | 데이터 생성 작업을 비동기적으로 수행 |
|
||||
| `map(Function<T, R>)` | 데이터를 변환 |
|
||||
| `flatMap(Function<T, Single<R>>)` | 비동기적으로 데이터를 변환 |
|
||||
| `subscribe(SingleObserver<T>)` | 데이터를 구독 |
|
||||
|
||||
---
|
||||
|
||||
### (3) `Maybe<T>`의 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `just(T)` | 단일 데이터를 발행 |
|
||||
| `empty()` | 아무 데이터도 발행하지 않음 |
|
||||
| `fromCallable(Callable<T>)` | 데이터 생성 작업을 비동기적으로 수행 |
|
||||
| `map(Function<T, R>)` | 데이터를 변환 |
|
||||
| `flatMap(Function<T, Maybe<R>>)` | 비동기적으로 데이터를 변환 |
|
||||
| `subscribe(MaybeObserver<T>)` | 데이터를 구독 |
|
||||
|
||||
---
|
||||
|
||||
### (4) `Completable`의 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `complete()` | 작업을 성공적으로 완료 |
|
||||
| `fromRunnable(Runnable)` | 실행 후 완료 이벤트를 발행 |
|
||||
| `fromCallable(Callable<Void>)` | 실행 후 완료 이벤트를 발행 |
|
||||
| `subscribe(CompletableObserver)` | 완료 이벤트를 구독 |
|
||||
|
||||
---
|
||||
|
||||
### (5) `Flowable<T>`의 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `just(T...)` | 지정한 데이터를 순차적으로 발행 |
|
||||
| `fromArray(T[])` | 배열 데이터를 순차적으로 발행 |
|
||||
| `fromIterable(Iterable<T>)` | 리스트 데이터를 순차적으로 발행 |
|
||||
| `create(FlowableOnSubscribe<T>, BackpressureStrategy)` | 직접 데이터를 발행하며 백프레셔 전략 설정 |
|
||||
| `map(Function<T, R>)` | 데이터를 변환 |
|
||||
| `flatMap(Function<T, Flowable<R>>)` | 비동기적으로 데이터를 변환 |
|
||||
| `filter(Predicate<T>)` | 조건을 만족하는 데이터만 발행 |
|
||||
| `subscribe(Subscriber<T>)` | 데이터를 구독 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 마무리
|
||||
RxJava에는 다양한 클래스가 있고, 각각의 역할이 있어:
|
||||
- `Observable` → 일반적인 비동기 데이터 스트림 처리
|
||||
- `Single` → 하나의 데이터만 반환 (예: 네트워크 응답)
|
||||
- `Maybe` → 데이터가 있을 수도, 없을 수도 있음
|
||||
- `Completable` → 데이터 없이 완료 이벤트만 전달
|
||||
- `Flowable` → 백프레셔가 필요한 경우 사용
|
||||
|
||||
각 클래스에는 `map`, `flatMap`, `filter` 같은 연산자가 존재해서 **데이터를 조작하고 가공**하는 게 가능해.
|
||||
특히 `Observable`은 `just`, `fromArray`, `merge` 등을 이용해 다양한 방식으로 데이터를 발행할 수 있어.
|
||||
|
||||
필요한 기능에 맞게 적절한 클래스를 선택해서 사용하면 RxJava를 효과적으로 활용할 수 있을 거야!
|
||||
231
docs/Swing.md
Normal file
231
docs/Swing.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# **Java Swing 쉽게 배우기**
|
||||
|
||||
## **1. Swing이란?**
|
||||
**Java Swing**은 **GUI(그래픽 사용자 인터페이스)를 만드는 라이브러리**다.
|
||||
버튼, 창, 메뉴, 입력 필드 등을 쉽게 만들 수 있다.
|
||||
|
||||
**Swing의 특징**
|
||||
✔ **AWT보다 강력한 GUI 제공** (AWT는 운영체제에 의존, Swing은 독립적)
|
||||
✔ **더 많은 UI 컴포넌트 제공** (버튼, 리스트, 테이블, 트리 등)
|
||||
✔ **이식성이 높음** (운영체제에 상관없이 동일한 UI 제공)
|
||||
✔ **이벤트 기반 프로그래밍 가능**
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 Swing 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `JFrame` 클래스 (메인 창)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setSize(int width, int height)` | 창의 크기를 설정 |
|
||||
| `setVisible(boolean b)` | 창을 화면에 표시 (`true`) 또는 숨김 (`false`) |
|
||||
| `setDefaultCloseOperation(int operation)` | 창 닫기 동작 설정 (예: `EXIT_ON_CLOSE`) |
|
||||
| `add(Component comp)` | 창에 컴포넌트 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(2) `JPanel` 클래스 (패널 - 여러 컴포넌트를 담는 컨테이너)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `add(Component comp)` | 패널에 컴포넌트 추가 |
|
||||
| `setLayout(LayoutManager mgr)` | 패널의 배치 관리자 설정 |
|
||||
|
||||
---
|
||||
|
||||
### **(3) `JButton` 클래스 (버튼)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setText(String text)` | 버튼의 텍스트 설정 |
|
||||
| `addActionListener(ActionListener l)` | 버튼 클릭 이벤트 설정 |
|
||||
|
||||
---
|
||||
|
||||
### **(4) `JLabel` 클래스 (텍스트 라벨)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setText(String text)` | 라벨의 텍스트 변경 |
|
||||
| `setIcon(Icon icon)` | 라벨에 이미지 아이콘 설정 |
|
||||
|
||||
---
|
||||
|
||||
### **(5) `JTextField` 클래스 (한 줄 입력 필드)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setText(String text)` | 입력 필드에 텍스트 설정 |
|
||||
| `getText()` | 입력된 텍스트 가져오기 |
|
||||
| `addActionListener(ActionListener l)` | 엔터 키 입력 이벤트 설정 |
|
||||
|
||||
---
|
||||
|
||||
### **(6) `JTextArea` 클래스 (여러 줄 입력 필드)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setText(String text)` | 입력 필드에 텍스트 설정 |
|
||||
| `getText()` | 입력된 텍스트 가져오기 |
|
||||
| `setLineWrap(boolean wrap)` | 자동 줄바꿈 설정 |
|
||||
|
||||
---
|
||||
|
||||
### **(7) `JCheckBox` 클래스 (체크박스)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setSelected(boolean b)` | 체크 상태 설정 |
|
||||
| `isSelected()` | 체크 여부 확인 |
|
||||
|
||||
---
|
||||
|
||||
### **(8) `JRadioButton` 클래스 (라디오 버튼)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setSelected(boolean b)` | 선택 상태 설정 |
|
||||
| `isSelected()` | 선택 여부 확인 |
|
||||
|
||||
---
|
||||
|
||||
### **(9) `JComboBox` 클래스 (드롭다운 목록)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `addItem(Object item)` | 목록에 항목 추가 |
|
||||
| `getSelectedItem()` | 선택된 항목 가져오기 |
|
||||
|
||||
---
|
||||
|
||||
### **(10) `JList` 클래스 (리스트 박스)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setListData(Object[] data)` | 리스트 아이템 설정 |
|
||||
| `getSelectedValue()` | 선택된 값 가져오기 |
|
||||
|
||||
---
|
||||
|
||||
### **(11) `JTable` 클래스 (테이블)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `setModel(TableModel model)` | 테이블 데이터 설정 |
|
||||
| `getValueAt(int row, int column)` | 특정 위치의 값 가져오기 |
|
||||
|
||||
---
|
||||
|
||||
### **(12) `JMenuBar`, `JMenu`, `JMenuItem` 클래스 (메뉴)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `JMenuBar.add(JMenu menu)` | 메뉴바에 메뉴 추가 |
|
||||
| `JMenu.add(JMenuItem item)` | 메뉴에 항목 추가 |
|
||||
|
||||
---
|
||||
|
||||
## **3. 간단한 Swing 예제**
|
||||
### **✔ 기본 창 만들기 (`JFrame`)**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
|
||||
public class SwingExample {
|
||||
public static void main(String[] args) {
|
||||
// 프레임 생성
|
||||
JFrame frame = new JFrame("Swing 기본 창");
|
||||
frame.setSize(400, 300);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
// 창을 보이게 설정
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ `JFrame`을 사용하여 **기본적인 창을 생성**하는 코드다.
|
||||
✅ `setSize()`로 크기 설정, `setVisible(true)`로 창을 표시한다.
|
||||
|
||||
---
|
||||
|
||||
### **✔ 버튼 추가 및 이벤트 처리 (`JButton`)**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
public class ButtonExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("버튼 예제");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
JButton button = new JButton("클릭하세요!");
|
||||
button.addActionListener(e -> JOptionPane.showMessageDialog(null, "버튼이 눌렸습니다!"));
|
||||
|
||||
frame.add(button);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ `JButton`을 추가하고, **버튼 클릭 이벤트를 처리**하는 코드다.
|
||||
✅ `JOptionPane.showMessageDialog()`를 사용해 **팝업 메시지 출력**이 가능하다.
|
||||
|
||||
---
|
||||
|
||||
### **✔ 텍스트 입력 필드 (`JTextField`)**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
|
||||
public class TextFieldExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("텍스트 입력 예제");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
JTextField textField = new JTextField(20);
|
||||
JButton button = new JButton("확인");
|
||||
|
||||
button.addActionListener(e -> JOptionPane.showMessageDialog(null, "입력한 값: " + textField.getText()));
|
||||
|
||||
JPanel panel = new JPanel();
|
||||
panel.add(textField);
|
||||
panel.add(button);
|
||||
|
||||
frame.add(panel);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ `JTextField`를 사용하여 **사용자로부터 입력을 받는 GUI**다.
|
||||
✅ 버튼 클릭 시 **입력된 값이 팝업으로 출력**된다.
|
||||
|
||||
---
|
||||
|
||||
### **✔ 체크박스 (`JCheckBox`)**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
|
||||
public class CheckBoxExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("체크박스 예제");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
JCheckBox checkBox = new JCheckBox("동의합니다.");
|
||||
JButton button = new JButton("확인");
|
||||
|
||||
button.addActionListener(e -> {
|
||||
if (checkBox.isSelected()) {
|
||||
JOptionPane.showMessageDialog(null, "체크됨!");
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(null, "체크 안됨!");
|
||||
}
|
||||
});
|
||||
|
||||
JPanel panel = new JPanel();
|
||||
panel.add(checkBox);
|
||||
panel.add(button);
|
||||
|
||||
frame.add(panel);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **체크박스를 추가하고 상태를 확인**하는 코드다.
|
||||
|
||||
---
|
||||
|
||||
## **4. 정리**
|
||||
✔ **Swing은 Java에서 GUI를 만들기 위한 라이브러리!**
|
||||
✔ **버튼, 입력 필드, 체크박스, 리스트, 테이블 등 다양한 UI 제공**
|
||||
✔ **이벤트 리스너를 통해 사용자 입력 처리 가능**
|
||||
|
||||
✅ **Swing을 활용하면 간단한 GUI 프로그램부터 복잡한 데스크톱 애플리케이션까지 개발 가능!**
|
||||
213
docs/XML DOM Parser.md
Normal file
213
docs/XML DOM Parser.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# **XML DOM Parser 쉽게 배우기**
|
||||
|
||||
## **1. XML DOM Parser란?**
|
||||
DOM(Document Object Model) Parser는 XML 문서를 **트리 구조의 객체 모델**로 메모리에 로드하여 다루는 방식이다.
|
||||
XML 데이터를 **읽고, 수정하고, 추가하고, 삭제**할 수 있다.
|
||||
|
||||
✔ **장점**:
|
||||
- 문서를 **전체 로드**하므로 **빠른 검색 및 수정 가능**
|
||||
- XML을 **객체처럼 다룰 수 있음**
|
||||
|
||||
✔ **단점**:
|
||||
- **메모리 사용량이 큼** (큰 XML을 처리할 때 부담)
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 메서드 정리**
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|-----|----|
|
||||
| `DocumentBuilderFactory.newInstance()` | `DocumentBuilderFactory` 객체 생성 |
|
||||
| `DocumentBuilderFactory.setNamespaceAware(true)` | 네임스페이스를 인식하도록 설정 |
|
||||
| `DocumentBuilderFactory.setIgnoringElementContentWhitespace(true)` | 공백 무시 |
|
||||
| `DocumentBuilder.newDocumentBuilder()` | `DocumentBuilder` 객체 생성 |
|
||||
| `DocumentBuilder.parse(File file)` | XML 파일을 `Document` 객체로 변환 |
|
||||
| `Document.getDocumentElement()` | XML의 루트 요소 가져오기 |
|
||||
| `Document.getElementsByTagName(String tag)` | 특정 태그 이름으로 요소 리스트 가져오기 |
|
||||
| `Node.getNodeName()` | 노드의 이름 반환 |
|
||||
| `Node.getTextContent()` | 노드의 텍스트 내용 반환 |
|
||||
| `Node.getAttributes()` | 노드의 속성 반환 |
|
||||
| `Element.getAttribute(String name)` | 특정 속성 값 가져오기 |
|
||||
| `Element.setAttribute(String name, String value)` | 속성 추가 또는 변경 |
|
||||
| `Element.appendChild(Node node)` | 하위 노드 추가 |
|
||||
| `NodeList.getLength()` | `NodeList`의 크기 반환 |
|
||||
| `NodeList.item(int index)` | `NodeList`에서 특정 인덱스의 노드 반환 |
|
||||
| `TransformerFactory.newInstance()` | `TransformerFactory` 객체 생성 |
|
||||
| `TransformerFactory.newTransformer()` | `Transformer` 객체 생성 |
|
||||
| `Transformer.transform(Source, Result)` | XML을 파일 또는 콘솔에 출력 |
|
||||
|
||||
---
|
||||
|
||||
## **3. XML 문서 예제**
|
||||
아래 XML을 사용하여 실습해보자.
|
||||
|
||||
```xml
|
||||
<books>
|
||||
<book id="1">
|
||||
<title>자바 프로그래밍</title>
|
||||
<author>홍길동</author>
|
||||
<price>30000</price>
|
||||
</book>
|
||||
<book id="2">
|
||||
<title>데이터베이스 개론</title>
|
||||
<author>이몽룡</author>
|
||||
<price>25000</price>
|
||||
</book>
|
||||
</books>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. XML 읽기 (Parsing)**
|
||||
|
||||
### **✔ XML을 읽어서 출력하기**
|
||||
아래 코드는 XML을 `Document` 객체로 로드하고, `NodeList`를 이용하여 책 정보를 출력한다.
|
||||
|
||||
```java
|
||||
import org.w3c.dom.*;
|
||||
import javax.xml.parsers.*;
|
||||
import java.io.*;
|
||||
|
||||
public class XMLParserExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 1. XML 파일을 로드하여 Document 객체로 변환
|
||||
File xmlFile = new File("books.xml");
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(xmlFile);
|
||||
|
||||
// 2. 루트 요소 가져오기
|
||||
Element root = document.getDocumentElement();
|
||||
System.out.println("루트 요소: " + root.getNodeName());
|
||||
|
||||
// 3. 모든 book 요소 가져오기
|
||||
NodeList bookList = document.getElementsByTagName("book");
|
||||
|
||||
for (int i = 0; i < bookList.getLength(); i++) {
|
||||
Node bookNode = bookList.item(i);
|
||||
|
||||
if (bookNode.getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element book = (Element) bookNode;
|
||||
|
||||
// 속성 값 가져오기
|
||||
String id = book.getAttribute("id");
|
||||
|
||||
// 하위 요소 가져오기
|
||||
String title = book.getElementsByTagName("title").item(0).getTextContent();
|
||||
String author = book.getElementsByTagName("author").item(0).getTextContent();
|
||||
String price = book.getElementsByTagName("price").item(0).getTextContent();
|
||||
|
||||
System.out.println("책 ID: " + id);
|
||||
System.out.println("제목: " + title);
|
||||
System.out.println("저자: " + author);
|
||||
System.out.println("가격: " + price);
|
||||
System.out.println("----");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
루트 요소: books
|
||||
책 ID: 1
|
||||
제목: 자바 프로그래밍
|
||||
저자: 홍길동
|
||||
가격: 30000
|
||||
----
|
||||
책 ID: 2
|
||||
제목: 데이터베이스 개론
|
||||
저자: 이몽룡
|
||||
가격: 25000
|
||||
----
|
||||
```
|
||||
|
||||
✅ `DocumentBuilder.parse(xmlFile)`로 XML을 **Document 객체로 변환**
|
||||
✅ `getElementsByTagName("book")`으로 **모든 `<book>` 요소 가져오기**
|
||||
✅ `getAttribute("id")`로 **속성 값 읽기**
|
||||
✅ `getElementsByTagName("title").item(0).getTextContent()`로 **텍스트 값 가져오기**
|
||||
|
||||
---
|
||||
|
||||
## **5. XML 수정 (노드 추가, 수정, 삭제)**
|
||||
|
||||
### **✔ 새로운 `<book>` 요소 추가하기**
|
||||
|
||||
```java
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
public class XMLAddElementExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
File xmlFile = new File("books.xml");
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(xmlFile);
|
||||
|
||||
Element root = document.getDocumentElement();
|
||||
|
||||
// 새로운 book 요소 생성
|
||||
Element newBook = document.createElement("book");
|
||||
newBook.setAttribute("id", "3");
|
||||
|
||||
Element title = document.createElement("title");
|
||||
title.setTextContent("네트워크 프로그래밍");
|
||||
Element author = document.createElement("author");
|
||||
author.setTextContent("강감찬");
|
||||
Element price = document.createElement("price");
|
||||
price.setTextContent("35000");
|
||||
|
||||
// 새 book 요소에 자식 노드 추가
|
||||
newBook.appendChild(title);
|
||||
newBook.appendChild(author);
|
||||
newBook.appendChild(price);
|
||||
|
||||
// 루트 노드에 새로운 book 추가
|
||||
root.appendChild(newBook);
|
||||
|
||||
// 변경된 내용을 파일에 저장
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.transform(new DOMSource(document), new StreamResult(new File("books.xml")));
|
||||
|
||||
System.out.println("새로운 책 추가 완료!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 후 XML 파일 (`books.xml`)**
|
||||
```xml
|
||||
<books>
|
||||
<book id="1">
|
||||
<title>자바 프로그래밍</title>
|
||||
<author>홍길동</author>
|
||||
<price>30000</price>
|
||||
</book>
|
||||
<book id="2">
|
||||
<title>데이터베이스 개론</title>
|
||||
<author>이몽룡</author>
|
||||
<price>25000</price>
|
||||
</book>
|
||||
<book id="3">
|
||||
<title>네트워크 프로그래밍</title>
|
||||
<author>강감찬</author>
|
||||
<price>35000</price>
|
||||
</book>
|
||||
</books>
|
||||
```
|
||||
|
||||
✅ **새로운 `<book>` 요소를 추가하고 XML 파일을 업데이트**
|
||||
|
||||
---
|
||||
|
||||
## **6. 정리**
|
||||
✔ **DOM Parser는 XML을 메모리에 로드하여 트리 구조로 다룬다.**
|
||||
✔ `DocumentBuilder`를 사용해 XML을 `Document` 객체로 변환
|
||||
✔ `getElementsByTagName()`으로 특정 태그의 노드 가져오기
|
||||
✔ `appendChild()`로 새로운 노드 추가 가능
|
||||
✔ `Transformer`를 사용하여 변경 내용을 XML 파일로 저장
|
||||
|
||||
✅ **XML을 다룰 때, 읽기/수정/추가가 필요한 경우 DOM Parser가 매우 유용하다!**
|
||||
187
docs/XML SAX Parser.md
Normal file
187
docs/XML SAX Parser.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# **XML SAX Parser 쉽게 배우기**
|
||||
|
||||
## **1. SAX Parser란?**
|
||||
SAX(Simple API for XML) Parser는 XML 문서를 **한 줄씩 읽으며 이벤트 기반으로 처리하는 방식**이다.
|
||||
|
||||
✔ **장점**:
|
||||
- XML 문서를 **메모리에 전부 로드하지 않음 → 메모리 효율적**
|
||||
- **빠르게 읽기 가능**
|
||||
|
||||
✔ **단점**:
|
||||
- **문서 내 특정 요소를 수정하거나 추가하기 어려움**
|
||||
- **XML을 트리 형태로 다루지 않음 → 앞에서 읽은 데이터 유지 불가**
|
||||
|
||||
**➡ 적절한 사용 사례:**
|
||||
- **대용량 XML 파일을 빠르게 읽을 때**
|
||||
- **읽기 전용(XML을 수정할 필요가 없는 경우)**
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 메서드 정리**
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|-----|----|
|
||||
| `SAXParserFactory.newInstance()` | `SAXParserFactory` 객체 생성 |
|
||||
| `SAXParserFactory.setNamespaceAware(true)` | 네임스페이스 인식 설정 |
|
||||
| `SAXParserFactory.setValidating(true)` | XML 유효성 검사 설정 |
|
||||
| `SAXParserFactory.newSAXParser()` | `SAXParser` 객체 생성 |
|
||||
| `SAXParser.parse(File file, DefaultHandler handler)` | XML 파일을 읽고 핸들러로 전달 |
|
||||
| `DefaultHandler.startDocument()` | 문서의 시작을 처리 |
|
||||
| `DefaultHandler.endDocument()` | 문서의 끝을 처리 |
|
||||
| `DefaultHandler.startElement(String uri, String localName, String qName, Attributes attributes)` | 요소의 시작을 처리 |
|
||||
| `DefaultHandler.endElement(String uri, String localName, String qName)` | 요소의 끝을 처리 |
|
||||
| `DefaultHandler.characters(char[] ch, int start, int length)` | 요소의 텍스트 데이터를 처리 |
|
||||
|
||||
---
|
||||
|
||||
## **3. XML 문서 예제**
|
||||
|
||||
아래 XML 문서를 이용하여 SAX Parser를 적용해 보자.
|
||||
|
||||
```xml
|
||||
<books>
|
||||
<book id="1">
|
||||
<title>자바 프로그래밍</title>
|
||||
<author>홍길동</author>
|
||||
<price>30000</price>
|
||||
</book>
|
||||
<book id="2">
|
||||
<title>데이터베이스 개론</title>
|
||||
<author>이몽룡</author>
|
||||
<price>25000</price>
|
||||
</book>
|
||||
</books>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. SAX Parser로 XML 읽기**
|
||||
|
||||
### **✔ SAX Parser를 사용하여 XML 데이터 출력하기**
|
||||
아래 코드는 `SAXParser`를 사용하여 XML 파일을 읽고, 책 정보를 출력하는 예제이다.
|
||||
|
||||
```java
|
||||
import org.xml.sax.*;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.xml.parsers.*;
|
||||
import java.io.File;
|
||||
|
||||
public class SAXParserExample {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 1. SAXParserFactory 생성
|
||||
SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||
SAXParser saxParser = factory.newSAXParser();
|
||||
|
||||
// 2. 핸들러 설정
|
||||
DefaultHandler handler = new DefaultHandler() {
|
||||
boolean isTitle = false;
|
||||
boolean isAuthor = false;
|
||||
boolean isPrice = false;
|
||||
|
||||
// 문서의 시작
|
||||
public void startDocument() {
|
||||
System.out.println("XML 파싱 시작");
|
||||
}
|
||||
|
||||
// 문서의 끝
|
||||
public void endDocument() {
|
||||
System.out.println("XML 파싱 종료");
|
||||
}
|
||||
|
||||
// 요소의 시작 태그
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
if (qName.equalsIgnoreCase("book")) {
|
||||
System.out.println("\n책 ID: " + attributes.getValue("id"));
|
||||
} else if (qName.equalsIgnoreCase("title")) {
|
||||
isTitle = true;
|
||||
} else if (qName.equalsIgnoreCase("author")) {
|
||||
isAuthor = true;
|
||||
} else if (qName.equalsIgnoreCase("price")) {
|
||||
isPrice = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 요소의 끝 태그
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
if (qName.equalsIgnoreCase("title")) {
|
||||
isTitle = false;
|
||||
} else if (qName.equalsIgnoreCase("author")) {
|
||||
isAuthor = false;
|
||||
} else if (qName.equalsIgnoreCase("price")) {
|
||||
isPrice = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 요소 안의 텍스트 처리
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
String text = new String(ch, start, length).trim();
|
||||
if (!text.isEmpty()) {
|
||||
if (isTitle) {
|
||||
System.out.println("제목: " + text);
|
||||
} else if (isAuthor) {
|
||||
System.out.println("저자: " + text);
|
||||
} else if (isPrice) {
|
||||
System.out.println("가격: " + text);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 3. XML 파일 파싱
|
||||
File xmlFile = new File("books.xml");
|
||||
saxParser.parse(xmlFile, handler);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
XML 파싱 시작
|
||||
|
||||
책 ID: 1
|
||||
제목: 자바 프로그래밍
|
||||
저자: 홍길동
|
||||
가격: 30000
|
||||
|
||||
책 ID: 2
|
||||
제목: 데이터베이스 개론
|
||||
저자: 이몽룡
|
||||
가격: 25000
|
||||
|
||||
XML 파싱 종료
|
||||
```
|
||||
|
||||
✅ `startElement()`에서 태그 시작을 감지
|
||||
✅ `characters()`에서 태그 안의 텍스트를 처리
|
||||
✅ `endElement()`에서 태그 종료를 감지
|
||||
|
||||
---
|
||||
|
||||
## **5. SAX vs DOM 비교**
|
||||
|
||||
| 특징 | SAX Parser | DOM Parser |
|
||||
|---|-----|-----|
|
||||
| **메모리 사용량** | 적음 | 많음 |
|
||||
| **속도** | 빠름 (한 줄씩 읽음) | 느림 (전체 로드) |
|
||||
| **트리 구조 지원** | ❌ | ✅ |
|
||||
| **수정 가능 여부** | ❌ (읽기 전용) | ✅ |
|
||||
| **사용 사례** | 대용량 XML 읽기 | XML을 수정할 때 |
|
||||
|
||||
✅ **DOM Parser는 XML을 객체처럼 다루는 데 유리**
|
||||
✅ **SAX Parser는 대용량 XML을 읽기만 할 때 유리**
|
||||
|
||||
---
|
||||
|
||||
## **6. 정리**
|
||||
✔ **SAX Parser는 이벤트 기반으로 XML을 한 줄씩 읽으며 처리**
|
||||
✔ `startElement()`와 `characters()`를 이용해 데이터를 가져올 수 있음
|
||||
✔ **대용량 XML을 처리할 때 유용하지만, 수정 기능이 없음**
|
||||
✔ **DOM Parser와의 차이를 이해하고 적절한 방식 선택**
|
||||
|
||||
✅ **읽기 전용 & 성능이 중요한 경우 SAX Parser를 사용하자!**
|
||||
239
docs/XML StAX Parser.md
Normal file
239
docs/XML StAX Parser.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# **XML StAX Parser 쉽게 배우기**
|
||||
|
||||
## **1. StAX Parser란?**
|
||||
StAX (Streaming API for XML) Parser는 XML 문서를 읽고 쓰는 **스트리밍 방식의 API**이다.
|
||||
DOM이 **전체 XML을 메모리에 로드**하고, SAX가 **이벤트 기반으로 읽기만 가능**한 것과 다르게,
|
||||
StAX는 **"Pull 방식"** 으로 데이터를 직접 가져올 수 있어 **더 유연하게 읽고 쓸 수 있음**.
|
||||
|
||||
✔ **장점:**
|
||||
- **필요한 데이터만 직접 가져올 수 있음 (Pull 방식)**
|
||||
- **SAX보다 코드가 더 직관적**
|
||||
- **읽기(Parsing)뿐만 아니라 XML 파일 쓰기(XMLStreamWriter)도 지원**
|
||||
|
||||
✔ **단점:**
|
||||
- **트리 구조 접근 불가 (DOM처럼 노드를 조작할 수 없음)**
|
||||
- **SAX보다 약간 무겁지만 여전히 DOM보다는 가벼움**
|
||||
|
||||
**➡ 적절한 사용 사례:**
|
||||
- **대용량 XML 파일에서 특정 데이터만 가져올 때**
|
||||
- **XML을 읽을 뿐만 아니라, 새로운 XML을 생성해야 할 때**
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 메서드 정리**
|
||||
|
||||
### **📌 XMLStreamReader (읽기)**
|
||||
| 메서드 | 설명 |
|
||||
|-----|---|
|
||||
| `XMLInputFactory.newInstance()` | `XMLStreamReader` 객체를 생성하는 팩토리 메서드 |
|
||||
| `XMLStreamReader.next()` | 다음 XML 요소로 이동 |
|
||||
| `XMLStreamReader.hasNext()` | 다음 요소가 있는지 확인 |
|
||||
| `XMLStreamReader.getEventType()` | 현재 XML 이벤트 유형 반환 |
|
||||
| `XMLStreamReader.getLocalName()` | 현재 태그의 이름 반환 |
|
||||
| `XMLStreamReader.getAttributeValue(int index)` | 특정 인덱스의 속성 값 반환 |
|
||||
| `XMLStreamReader.getText()` | 현재 태그의 텍스트 값 반환 |
|
||||
| `XMLStreamReader.close()` | XML 리더 닫기 |
|
||||
|
||||
---
|
||||
|
||||
### **📌 XMLStreamWriter (쓰기)**
|
||||
| 메서드 | 설명 |
|
||||
|-----|---|
|
||||
| `XMLOutputFactory.newInstance()` | `XMLStreamWriter` 객체를 생성하는 팩토리 메서드 |
|
||||
| `XMLStreamWriter.writeStartDocument()` | XML 문서 시작 선언 |
|
||||
| `XMLStreamWriter.writeStartElement(String name)` | XML 태그 시작 |
|
||||
| `XMLStreamWriter.writeAttribute(String name, String value)` | 태그에 속성 추가 |
|
||||
| `XMLStreamWriter.writeCharacters(String text)` | 태그 안에 텍스트 추가 |
|
||||
| `XMLStreamWriter.writeEndElement()` | 현재 태그 종료 |
|
||||
| `XMLStreamWriter.writeEndDocument()` | XML 문서 종료 |
|
||||
| `XMLStreamWriter.close()` | XML 작성기 닫기 |
|
||||
|
||||
---
|
||||
|
||||
## **3. XML 문서 예제**
|
||||
|
||||
아래 XML 파일을 **StAX Parser를 사용하여 읽고 출력**해 보자.
|
||||
|
||||
```xml
|
||||
<books>
|
||||
<book id="1">
|
||||
<title>자바 프로그래밍</title>
|
||||
<author>홍길동</author>
|
||||
<price>30000</price>
|
||||
</book>
|
||||
<book id="2">
|
||||
<title>데이터베이스 개론</title>
|
||||
<author>이몽룡</author>
|
||||
<price>25000</price>
|
||||
</book>
|
||||
</books>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. StAX Parser로 XML 읽기 (Pull 방식)**
|
||||
|
||||
### **✔ XMLStreamReader를 사용하여 XML 데이터 출력하기**
|
||||
아래 코드는 `StAXParser`를 사용하여 XML 파일을 읽고, 책 정보를 출력하는 예제이다.
|
||||
|
||||
```java
|
||||
import javax.xml.stream.*;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
public class StAXParserExample {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 1. XMLStreamReader 생성
|
||||
XMLInputFactory factory = XMLInputFactory.newInstance();
|
||||
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("books.xml"));
|
||||
|
||||
// 2. XML 데이터 읽기
|
||||
String currentElement = "";
|
||||
while (reader.hasNext()) {
|
||||
int event = reader.next();
|
||||
|
||||
// 요소의 시작
|
||||
if (event == XMLStreamReader.START_ELEMENT) {
|
||||
currentElement = reader.getLocalName();
|
||||
|
||||
// <book> 태그의 id 속성 읽기
|
||||
if (currentElement.equals("book")) {
|
||||
System.out.println("\n책 ID: " + reader.getAttributeValue(0));
|
||||
}
|
||||
}
|
||||
// 요소 안의 텍스트 읽기
|
||||
else if (event == XMLStreamReader.CHARACTERS) {
|
||||
String text = reader.getText().trim();
|
||||
if (!text.isEmpty()) {
|
||||
switch (currentElement) {
|
||||
case "title":
|
||||
System.out.println("제목: " + text);
|
||||
break;
|
||||
case "author":
|
||||
System.out.println("저자: " + text);
|
||||
break;
|
||||
case "price":
|
||||
System.out.println("가격: " + text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 실행 결과**
|
||||
```
|
||||
책 ID: 1
|
||||
제목: 자바 프로그래밍
|
||||
저자: 홍길동
|
||||
가격: 30000
|
||||
|
||||
책 ID: 2
|
||||
제목: 데이터베이스 개론
|
||||
저자: 이몽룡
|
||||
가격: 25000
|
||||
```
|
||||
|
||||
✅ `reader.next()`를 이용해 **한 단계씩 이동하며 직접 데이터를 가져옴**
|
||||
✅ `reader.getLocalName()`을 이용해 현재 태그 이름 확인
|
||||
✅ `reader.getText()`로 태그 안의 텍스트 값 가져오기
|
||||
|
||||
---
|
||||
|
||||
## **5. StAX Parser로 XML 쓰기 (쓰기 예제)**
|
||||
|
||||
XML 데이터를 **파일로 저장하는 경우** `XMLStreamWriter`를 사용한다.
|
||||
|
||||
### **✔ XMLStreamWriter를 사용하여 XML 파일 생성**
|
||||
```java
|
||||
import javax.xml.stream.*;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class StAXWriterExample {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 1. XMLStreamWriter 생성
|
||||
XMLOutputFactory factory = XMLOutputFactory.newInstance();
|
||||
XMLStreamWriter writer = factory.createXMLStreamWriter(new FileOutputStream("new_books.xml"), "UTF-8");
|
||||
|
||||
// 2. XML 문서 작성
|
||||
writer.writeStartDocument("UTF-8", "1.0");
|
||||
writer.writeStartElement("books");
|
||||
|
||||
// 첫 번째 책
|
||||
writer.writeStartElement("book");
|
||||
writer.writeAttribute("id", "1");
|
||||
|
||||
writer.writeStartElement("title");
|
||||
writer.writeCharacters("자바 프로그래밍");
|
||||
writer.writeEndElement();
|
||||
|
||||
writer.writeStartElement("author");
|
||||
writer.writeCharacters("홍길동");
|
||||
writer.writeEndElement();
|
||||
|
||||
writer.writeStartElement("price");
|
||||
writer.writeCharacters("30000");
|
||||
writer.writeEndElement();
|
||||
|
||||
writer.writeEndElement(); // book 종료
|
||||
|
||||
// XML 종료
|
||||
writer.writeEndElement(); // books 종료
|
||||
writer.writeEndDocument();
|
||||
writer.close();
|
||||
|
||||
System.out.println("XML 파일 생성 완료!");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🔹 생성된 XML 파일 (new_books.xml)**
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<books>
|
||||
<book id="1">
|
||||
<title>자바 프로그래밍</title>
|
||||
<author>홍길동</author>
|
||||
<price>30000</price>
|
||||
</book>
|
||||
</books>
|
||||
```
|
||||
|
||||
✅ `writeStartElement()`를 이용해 태그 생성
|
||||
✅ `writeCharacters()`로 태그 안의 텍스트 추가
|
||||
✅ `writeAttribute()`를 이용해 속성 추가
|
||||
|
||||
---
|
||||
|
||||
## **6. StAX vs SAX vs DOM 비교**
|
||||
|
||||
| 특징 | StAX Parser | SAX Parser | DOM Parser |
|
||||
|---|-----|-----|-----|
|
||||
| **메모리 사용량** | 적음 | 적음 | 많음 |
|
||||
| **속도** | 빠름 | 빠름 | 느림 |
|
||||
| **데이터 접근 방식** | Pull 방식 | Push 방식 | 트리 기반 |
|
||||
| **읽기** | ✅ | ✅ | ✅ |
|
||||
| **쓰기** | ✅ | ❌ | ✅ |
|
||||
| **수정** | ❌ | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## **7. 정리**
|
||||
✔ **StAX Parser는 Pull 방식으로 XML을 읽고 쓸 수 있는 API**
|
||||
✔ **필요한 데이터만 직접 가져올 수 있어 SAX보다 유연함**
|
||||
✔ **쓰기 기능(XMLStreamWriter)을 지원하므로 XML 생성에도 적합**
|
||||
✔ **대용량 XML을 다룰 때 효율적인 선택**
|
||||
|
||||
✅ **읽기 & 쓰기가 필요한 경우 StAX Parser를 사용하자!**
|
||||
12
docs/app.sh
Executable file
12
docs/app.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
case $1 in
|
||||
gitup)
|
||||
echo "Pushing to git"
|
||||
git add -A
|
||||
git commit -m $(date "+%Y-%m-%dT%H:%M:%S")
|
||||
git push origin
|
||||
;;
|
||||
*)
|
||||
code .
|
||||
esac
|
||||
164
docs/database/database.md
Normal file
164
docs/database/database.md
Normal file
@@ -0,0 +1,164 @@
|
||||
Java에서 사용되는 데이터베이스 중 **Java DB**(Apache Derby), **H2 Database**, 그리고 **SQLite**는 각각 임베디드 데이터베이스로 널리 사용되며, 각기 다른 특징과 장단점을 가지고 있습니다. 아래에서 이 세 가지를 주요 항목별로 비교하여 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### **1. 개요**
|
||||
| **항목** | **Java DB (Apache Derby)** | **H2 Database** | **SQLite** |
|
||||
|------------------|-----------------------------------------------|---------------------------------------------|--------------------------------------------|
|
||||
| **개발 언어** | Java | Java | C |
|
||||
| **라이선스** | Apache License 2.0 | MPL 2.0 / EPL 1.0 | Public Domain |
|
||||
| **주요 용도** | 임베디드 및 경량 서버 애플리케이션 | 임베디드, 테스트, 경량 서버 애플리케이션 | 임베디드 애플리케이션 (모바일 포함) |
|
||||
| **최초 출시** | 2004년 (IBM Cloudscape에서 파생) | 2005년 | 2000년 |
|
||||
|
||||
- **Java DB**: IBM의 Cloudscape에서 파생된 Apache Derby로, 순수 Java로 작성된 관계형 데이터베이스.
|
||||
- **H2**: Java로 작성된 고속, 경량 데이터베이스로, 특히 테스트 환경에서 인기.
|
||||
- **SQLite**: C로 작성된 초경량 데이터베이스로, 모바일 및 임베디드 환경에서 널리 사용.
|
||||
|
||||
---
|
||||
|
||||
### **2. 기능 비교**
|
||||
| **항목** | **Java DB** | **H2** | **SQLite** |
|
||||
|------------------|--------------------------------------|-------------------------------------|------------------------------------|
|
||||
| **임베디드 모드**| 지원 | 지원 | 지원 |
|
||||
| **서버 모드** | 지원 (네트워크 연결 가능) | 지원 (TCP 기반) | 미지원 (파일 기반만 가능) |
|
||||
| **인메모리 지원**| 지원 | 지원 (빠른 성능) | 지원 |
|
||||
| **SQL 표준 준수**| 높음 (SQL-92, SQL-99 지원) | 매우 높음 (다양한 SQL 기능 지원) | 중간 (일부 고급 기능 미지원) |
|
||||
| **트랜잭션** | ACID 준수 | ACID 준수 | ACID 준수 |
|
||||
| **사용자 관리** | 지원 (username/password) | 지원 | 미지원 (파일 접근 기반) |
|
||||
| **최대 DB 크기** | 이론상 JVM 한계 (수 TB) | 4TB | 281TB |
|
||||
|
||||
- **Java DB**: 서버 모드와 임베디드 모드 모두 지원하며, SQL 표준 준수도가 높음.
|
||||
- **H2**: 서버 모드와 인메모리 모드를 지원하며, Java 함수 실행 등 고급 기능 제공.
|
||||
- **SQLite**: 서버 모드는 없고, 파일 기반으로 동작하며 사용자 인증 기능이 없음.
|
||||
|
||||
---
|
||||
|
||||
### **3. 성능**
|
||||
- **Java DB**: 순수 Java로 작성되어 JVM에 의존적이며, 중간 정도의 성능. 대규모 데이터 처리 시 속도가 느려질 수 있음.
|
||||
- **H2**: 인메모리 모드에서 매우 빠르며, Hibernate와 같은 ORM과의 통합에서 SQLite보다 우수한 성능을 보임(특히 쿼리 실행 속도).
|
||||
- **SQLite**: C로 작성되어 네이티브 성능이 뛰어나지만, 복잡한 쿼리나 동시성 처리에서 약간 뒤질 수 있음.
|
||||
|
||||
**참고**: Hibernate와 결합 시 H2가 SQLite보다 평균적으로 3~8배 빠르다는 벤치마크 결과가 있음(JPAB.org).
|
||||
|
||||
---
|
||||
|
||||
### **4. 장단점**
|
||||
#### **Java DB (Apache Derby)**
|
||||
- **장점**:
|
||||
- 순수 Java로 작성되어 플랫폼 독립적.
|
||||
- 서버 모드와 임베디드 모드 모두 지원.
|
||||
- 안정적이고 상용 소프트웨어에서도 사용됨.
|
||||
- **단점**:
|
||||
- SQLite나 H2에 비해 느린 성능.
|
||||
- 커뮤니티 활동이 상대적으로 적음.
|
||||
|
||||
#### **H2 Database**
|
||||
- **장점**:
|
||||
- 순수 Java로 작성, 설치 및 통합이 쉬움.
|
||||
- 인메모리 모드에서 빠른 속도, 테스트에 이상적.
|
||||
- 사용자 정의 Java 함수 지원.
|
||||
- 서버 모드와 네트워크 접근 가능.
|
||||
- **단점**:
|
||||
- 비정상 종료 시 MVStore 손상 가능성.
|
||||
- SQLite에 비해 커뮤니티와 사용 사례가 적음.
|
||||
|
||||
#### **SQLite**
|
||||
- **장점**:
|
||||
- 초경량(라이브러리 크기 약 700KB).
|
||||
- C로 작성되어 네이티브 성능 우수.
|
||||
- 모바일(Android, iOS) 및 임베디드 환경에서 검증됨.
|
||||
- 매우 큰 데이터베이스 크기 지원(281TB).
|
||||
- **단점**:
|
||||
- 서버 모드 미지원, 네트워크 접근 불가.
|
||||
- 사용자 인증/권한 관리 없음.
|
||||
- 복잡한 SQL 기능(예: RIGHT OUTER JOIN) 미지원.
|
||||
|
||||
---
|
||||
|
||||
### **5. 사용 예제 (Java에서 연결)**
|
||||
#### **Java DB**
|
||||
```java
|
||||
import java.sql.*;
|
||||
|
||||
public class JavaDBExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String url = "jdbc:derby:memory:myDB;create=true";
|
||||
Connection conn = DriverManager.getConnection(url);
|
||||
Statement stmt = conn.createStatement();
|
||||
stmt.execute("CREATE TABLE test (id INT, name VARCHAR(50))");
|
||||
stmt.execute("INSERT INTO test VALUES (1, 'JavaDB')");
|
||||
ResultSet rs = stmt.executeQuery("SELECT * FROM test");
|
||||
while (rs.next()) {
|
||||
System.out.println(rs.getInt("id") + ": " + rs.getString("name"));
|
||||
}
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **H2**
|
||||
```java
|
||||
import java.sql.*;
|
||||
|
||||
public class H2Example {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
|
||||
Connection conn = DriverManager.getConnection(url, "sa", "");
|
||||
Statement stmt = conn.createStatement();
|
||||
stmt.execute("CREATE TABLE test (id INT, name VARCHAR(50))");
|
||||
stmt.execute("INSERT INTO test VALUES (1, 'H2')");
|
||||
ResultSet rs = stmt.executeQuery("SELECT * FROM test");
|
||||
while (rs.next()) {
|
||||
System.out.println(rs.getInt("id") + ": " + rs.getString("name"));
|
||||
}
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **SQLite**
|
||||
```java
|
||||
import java.sql.*;
|
||||
|
||||
public class SQLiteExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String url = "jdbc:sqlite::memory:"; // 또는 파일 경로
|
||||
Connection conn = DriverManager.getConnection(url);
|
||||
Statement stmt = conn.createStatement();
|
||||
stmt.execute("CREATE TABLE test (id INTEGER, name TEXT)");
|
||||
stmt.execute("INSERT INTO test VALUES (1, 'SQLite')");
|
||||
ResultSet rs = stmt.executeQuery("SELECT * FROM test");
|
||||
while (rs.next()) {
|
||||
System.out.println(rs.getInt("id") + ": " + rs.getString("name"));
|
||||
}
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**의존성 (Maven)**:
|
||||
- Java DB: 기본 JDK에 포함 (별도 의존성 불필요).
|
||||
- H2: `<groupId>com.h2database</groupId><artifactId>h2</artifactId>`
|
||||
- SQLite: `<groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId>`
|
||||
|
||||
---
|
||||
|
||||
### **6. 언제 무엇을 사용할까?**
|
||||
- **Java DB**:
|
||||
- 순수 Java 환경을 선호하며, 서버 모드와 임베디드 모드를 모두 필요로 할 때.
|
||||
- 안정성과 표준 준수가 중요한 경우.
|
||||
- **H2**:
|
||||
- 테스트 환경(예: 단위 테스트) 또는 빠른 프로토타이핑이 필요할 때.
|
||||
- Java 기반 고급 기능(사용자 정의 함수 등)이 필요할 때.
|
||||
- **SQLite**:
|
||||
- 모바일 또는 초경량 임베디드 애플리케이션에 적합.
|
||||
- 네트워크 연결이 필요 없고, 단순한 파일 기반 저장소가 필요한 경우.
|
||||
|
||||
---
|
||||
|
||||
### **결론**
|
||||
- **성능과 테스트**: H2가 가장 빠르고 유연하며, Java 생태계와의 통합이 뛰어남.
|
||||
- **경량성과 범용성**: SQLite는 모바일 및 임베디드 환경에서 입증된 선택.
|
||||
- **안정성과 표준**: Java DB는 안정적이고 표준 준수도가 높아 신뢰성 있는 선택.
|
||||
|
||||
프로젝트 요구사항(성능, 크기, 네트워크 지원 여부 등)에 따라 선택하면 됩니다. 추가 질문이 있다면 말씀해주세요!
|
||||
201
docs/io.md
Normal file
201
docs/io.md
Normal file
@@ -0,0 +1,201 @@
|
||||
## 자바 IO (Input/Output) 관련 클래스 및 주요 메서드 정리
|
||||
|
||||
자바 IO는 **데이터를 스트림(Stream) 기반으로 읽고 쓰는 전통적인 입출력 API**다.
|
||||
주로 **파일, 콘솔, 네트워크, 메모리 등과 데이터를 주고받는 데 사용**된다.
|
||||
|
||||
---
|
||||
|
||||
### 1. `InputStream` (바이트 입력 스트림)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `read()` | 1바이트를 읽고 정수(0~255)로 반환, 끝이면 -1 반환 |
|
||||
| `read(byte[] b)` | 바이트 배열에 데이터를 읽음 |
|
||||
| `available()` | 읽을 수 있는 바이트 수 반환 |
|
||||
| `close()` | 스트림 닫기 |
|
||||
|
||||
**주요 하위 클래스:**
|
||||
- `FileInputStream` (파일에서 바이트 읽기)
|
||||
- `ByteArrayInputStream` (바이트 배열에서 읽기)
|
||||
- `BufferedInputStream` (버퍼링으로 성능 향상)
|
||||
|
||||
---
|
||||
|
||||
### 2. `OutputStream` (바이트 출력 스트림)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `write(int b)` | 1바이트 쓰기 |
|
||||
| `write(byte[] b)` | 바이트 배열을 스트림에 쓰기 |
|
||||
| `flush()` | 버퍼에 있는 데이터를 즉시 출력 |
|
||||
| `close()` | 스트림 닫기 |
|
||||
|
||||
**주요 하위 클래스:**
|
||||
- `FileOutputStream` (파일에 바이트 쓰기)
|
||||
- `ByteArrayOutputStream` (바이트 배열에 쓰기)
|
||||
- `BufferedOutputStream` (버퍼링으로 성능 향상)
|
||||
|
||||
---
|
||||
|
||||
### 3. `Reader` (문자 입력 스트림)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `read()` | 문자 하나 읽기 |
|
||||
| `read(char[] cbuf)` | 문자 배열에 읽기 |
|
||||
| `close()` | 스트림 닫기 |
|
||||
|
||||
**주요 하위 클래스:**
|
||||
- `FileReader` (파일에서 문자 읽기)
|
||||
- `BufferedReader` (버퍼링으로 성능 향상)
|
||||
- `StringReader` (문자열에서 읽기)
|
||||
|
||||
---
|
||||
|
||||
### 4. `Writer` (문자 출력 스트림)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `write(int c)` | 문자 하나 쓰기 |
|
||||
| `write(char[] cbuf)` | 문자 배열을 스트림에 쓰기 |
|
||||
| `flush()` | 버퍼 비우기 |
|
||||
| `close()` | 스트림 닫기 |
|
||||
|
||||
**주요 하위 클래스:**
|
||||
- `FileWriter` (파일에 문자 쓰기)
|
||||
- `BufferedWriter` (버퍼링으로 성능 향상)
|
||||
- `StringWriter` (문자열에 쓰기)
|
||||
|
||||
---
|
||||
|
||||
### 5. `File` 클래스 (파일 및 디렉토리 관리)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `exists()` | 파일이 존재하는지 확인 |
|
||||
| `createNewFile()` | 새 파일 생성 |
|
||||
| `delete()` | 파일 삭제 |
|
||||
| `mkdir()` | 새 디렉토리 생성 |
|
||||
| `listFiles()` | 디렉토리 내 파일 목록 반환 |
|
||||
|
||||
---
|
||||
|
||||
### 6. `BufferedReader`와 `BufferedWriter` (성능 향상)
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `readLine()` | 한 줄씩 읽기 (`BufferedReader` 전용) |
|
||||
| `write(String s)` | 문자열 출력 (`BufferedWriter` 전용) |
|
||||
| `flush()` | 버퍼 비우기 |
|
||||
| `close()` | 스트림 닫기 |
|
||||
|
||||
---
|
||||
|
||||
### 자바 IO 쉽게 설명하기
|
||||
|
||||
자바의 IO는 데이터를 **스트림(Stream) 기반으로 입출력**하는 방식이다.
|
||||
즉, **한 번에 하나의 데이터(바이트 or 문자)를 순차적으로 처리**하는 구조다.
|
||||
|
||||
---
|
||||
|
||||
### 1. **파일에서 데이터 읽기 (`FileReader`)**
|
||||
|
||||
```java
|
||||
FileReader reader = new FileReader("example.txt");
|
||||
int data;
|
||||
while ((data = reader.read()) != -1) {
|
||||
System.out.print((char) data);
|
||||
}
|
||||
reader.close();
|
||||
```
|
||||
- `read()` → 한 글자씩 읽음
|
||||
- `close()` → 파일 닫기
|
||||
|
||||
---
|
||||
|
||||
### 2. **파일에 데이터 쓰기 (`FileWriter`)**
|
||||
|
||||
```java
|
||||
FileWriter writer = new FileWriter("example.txt");
|
||||
writer.write("Hello, Java IO!");
|
||||
writer.close();
|
||||
```
|
||||
- `write()` → 파일에 문자열 저장
|
||||
- `close()` → 파일 닫기
|
||||
|
||||
---
|
||||
|
||||
### 3. **파일을 빠르게 읽기 (`BufferedReader`)**
|
||||
|
||||
```java
|
||||
BufferedReader br = new BufferedReader(new FileReader("example.txt"));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
System.out.println(line);
|
||||
}
|
||||
br.close();
|
||||
```
|
||||
- `readLine()` → 한 줄씩 읽기 (효율적)
|
||||
- `BufferedReader` → 버퍼링으로 속도 향상
|
||||
|
||||
---
|
||||
|
||||
### 4. **파일을 빠르게 쓰기 (`BufferedWriter`)**
|
||||
|
||||
```java
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter("example.txt"));
|
||||
bw.write("Buffered Writer 사용!");
|
||||
bw.newLine(); // 줄바꿈
|
||||
bw.close();
|
||||
```
|
||||
- `newLine()` → 줄바꿈 추가
|
||||
- `BufferedWriter` → 속도 향상
|
||||
|
||||
---
|
||||
|
||||
### 5. **바이트 단위 파일 복사 (`FileInputStream`, `FileOutputStream`)**
|
||||
|
||||
```java
|
||||
FileInputStream fis = new FileInputStream("input.txt");
|
||||
FileOutputStream fos = new FileOutputStream("output.txt");
|
||||
|
||||
int data;
|
||||
while ((data = fis.read()) != -1) {
|
||||
fos.write(data);
|
||||
}
|
||||
|
||||
fis.close();
|
||||
fos.close();
|
||||
```
|
||||
- `read()` → 1바이트씩 읽기
|
||||
- `write()` → 1바이트씩 출력
|
||||
|
||||
---
|
||||
|
||||
### 6. **문자 단위 파일 복사 (`FileReader`, `FileWriter`)**
|
||||
|
||||
```java
|
||||
FileReader fr = new FileReader("input.txt");
|
||||
FileWriter fw = new FileWriter("output.txt");
|
||||
|
||||
int data;
|
||||
while ((data = fr.read()) != -1) {
|
||||
fw.write(data);
|
||||
}
|
||||
|
||||
fr.close();
|
||||
fw.close();
|
||||
```
|
||||
- `FileReader` → 문자 기반 파일 읽기
|
||||
- `FileWriter` → 문자 기반 파일 쓰기
|
||||
|
||||
---
|
||||
|
||||
### 정리
|
||||
|
||||
자바 IO는 **스트림 기반으로 데이터를 읽고 쓰는 방식**을 제공한다.
|
||||
- **바이트 단위 (`InputStream`, `OutputStream`)** → 이미지, 동영상, 바이너리 파일 처리
|
||||
- **문자 단위 (`Reader`, `Writer`)** → 텍스트 파일 처리
|
||||
- **버퍼링 (`BufferedReader`, `BufferedWriter`)** → 성능 최적화
|
||||
|
||||
**즉, IO 스트림을 적절히 활용하면 다양한 입출력 작업을 쉽게 수행할 수 있다!**
|
||||
433
docs/jackson.md
Normal file
433
docs/jackson.md
Normal file
@@ -0,0 +1,433 @@
|
||||
자바의 Jackson에 대해 설명하는 글을 작성하겠습니다. Jackson은 자바에서 JSON 데이터를 처리하기 위해 널리 사용되는 라이브러리로, 객체와 JSON 간의 직렬화(Serialization) 및 역직렬화(Deserialization)를 쉽게 수행할 수 있게 해줍니다. 특히, Jackson의 어노테이션은 이러한 변환 과정을 세밀하게 제어할 수 있도록 도와줍니다. 아래에서는 Jackson의 주요 어노테이션을 표로 정리한 뒤, 각 어노테이션에 대한 상세 설명과 예시를 제공하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### Jackson 주요 어노테이션 표
|
||||
|
||||
| 어노테이션 | 설명 |
|
||||
|----------------------------|------------------------------------------------------------------------------------------|
|
||||
| `@JsonProperty` | JSON 키와 자바 필드/메서드 이름을 매핑하거나, 직렬화/역직렬화 시 속성 이름을 지정 |
|
||||
| `@JsonIgnore` | 직렬화 및 역직렬화 시 특정 필드를 무시 |
|
||||
| `@JsonIgnoreProperties` | 클래스 레벨에서 직렬화/역직렬화 시 무시할 속성(들)을 지정 |
|
||||
| `@JsonInclude` | 직렬화 시 특정 조건(예: null 값 제외)에 따라 필드를 포함하거나 제외 |
|
||||
| `@JsonCreator` | 역직렬화 시 사용할 생성자나 팩토리 메서드를 지정 |
|
||||
| `@JsonSetter` | 역직렬화 시 특정 Setter 메서드를 JSON 속성과 매핑 |
|
||||
| `@JsonGetter` | 직렬화 시 특정 Getter 메서드를 JSON 속성으로 사용 |
|
||||
| `@JsonAnySetter` | 정의되지 않은 JSON 속성을 Map 형태로 처리 |
|
||||
| `@JsonAnyGetter` | Map의 내용을 추가적인 JSON 속성으로 직렬화 |
|
||||
| `@JsonManagedReference` | 객체 간 순환 참조에서 부모 객체를 직렬화 |
|
||||
| `@JsonBackReference` | 객체 간 순환 참조에서 자식 객체를 직렬화에서 제외 |
|
||||
| `@JsonIdentityInfo` | 순환 참조 문제를 해결하기 위해 객체의 고유 식별자를 사용 |
|
||||
| `@JsonTypeInfo` | 다형성 타입 정보를 JSON에 포함시켜 직렬화/역직렬화 시 올바른 타입으로 처리 |
|
||||
| `@JsonSubTypes` | `@JsonTypeInfo`와 함께 사용되며, 하위 타입을 명시 |
|
||||
| `@JsonView` | 특정 뷰(View)에 따라 직렬화/역직렬화 대상을 제한 |
|
||||
| `@JacksonInject` | JSON 데이터 대신 외부에서 주입된 값을 필드에 설정 |
|
||||
|
||||
---
|
||||
|
||||
### 각 어노테이션 상세 설명 및 예시
|
||||
|
||||
#### 1. `@JsonProperty`
|
||||
- **설명**: JSON 속성 이름과 자바 필드/메서드 이름을 매핑하거나, 이름을 강제로 지정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class Person {
|
||||
@JsonProperty("fullName")
|
||||
private String name;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Person person = new Person();
|
||||
person.setName("홍길동");
|
||||
String json = mapper.writeValueAsString(person);
|
||||
System.out.println(json); // {"fullName":"홍길동"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `@JsonIgnore`
|
||||
- **설명**: 직렬화 및 역직렬화 시 특정 필드를 제외합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public class Person {
|
||||
private String name;
|
||||
@JsonIgnore
|
||||
private int age;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Person person = new Person();
|
||||
person.setName("김영희"); person.setAge(25);
|
||||
String json = mapper.writeValueAsString(person);
|
||||
System.out.println(json); // {"name":"김영희"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. `@JsonIgnoreProperties`
|
||||
- **설명**: 클래스 레벨에서 무시할 속성을 지정하거나, 알 수 없는 속성을 무시하도록 설정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties({"age", "ignoreUnknown = true"})
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String json = "{\"name\":\"이철수\",\"age\":30,\"extra\":\"data\"}";
|
||||
Person person = mapper.readValue(json, Person.class);
|
||||
System.out.println(person.getName()); // "이철수"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. `@JsonInclude`
|
||||
- **설명**: 직렬화 시 null 값이나 빈 값 등을 제외하는 조건을 설정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class Person {
|
||||
private String name;
|
||||
private String address;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getAddress() { return address; }
|
||||
public void setAddress(String address) { this.address = address; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Person person = new Person();
|
||||
person.setName("박민수");
|
||||
String json = mapper.writeValueAsString(person);
|
||||
System.out.println(json); // {"name":"박민수"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. `@JsonCreator`
|
||||
- **설명**: 역직렬화 시 사용할 생성자나 팩토리 메서드를 지정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class Person {
|
||||
private final String name;
|
||||
|
||||
@JsonCreator
|
||||
public Person(@JsonProperty("name") String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String json = "{\"name\":\"최수진\"}";
|
||||
Person person = mapper.readValue(json, Person.class);
|
||||
System.out.println(person.getName()); // "최수진"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6. `@JsonSetter`
|
||||
- **설명**: 역직렬화 시 Setter 메서드를 특정 JSON 속성과 매핑합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||
|
||||
public class Person {
|
||||
private String name;
|
||||
|
||||
@JsonSetter("fullName")
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public String getName() { return name; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String json = "{\"fullName\":\"김지영\"}";
|
||||
Person person = mapper.readValue(json, Person.class);
|
||||
System.out.println(person.getName()); // "김지영"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. `@JsonGetter`
|
||||
- **설명**: 직렬화 시 Getter 메서드를 JSON 속성으로 사용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||
|
||||
public class Person {
|
||||
private String name;
|
||||
|
||||
@JsonGetter("fullName")
|
||||
public String getName() { return name; }
|
||||
|
||||
public void setName(String name) { this.name = name; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Person person = new Person();
|
||||
person.setName("윤서진");
|
||||
String json = mapper.writeValueAsString(person);
|
||||
System.out.println(json); // {"fullName":"윤서진"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 8. `@JsonAnySetter`
|
||||
- **설명**: 정의되지 않은 JSON 속성을 Map으로 처리합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Person {
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
@JsonAnySetter
|
||||
public void setProperty(String key, Object value) {
|
||||
properties.put(key, value);
|
||||
}
|
||||
|
||||
public Map<String, Object> getProperties() { return properties; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String json = "{\"name\":\"이정훈\",\"age\":29}";
|
||||
Person person = mapper.readValue(json, Person.class);
|
||||
System.out.println(person.getProperties()); // {name=이정훈, age=29}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 9. `@JsonAnyGetter`
|
||||
- **설명**: Map의 내용을 추가적인 JSON 속성으로 직렬화합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Person {
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getProperties() { return properties; }
|
||||
|
||||
public void setProperty(String key, Object value) { properties.put(key, value); }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Person person = new Person();
|
||||
person.setProperty("name", "최영미");
|
||||
person.setProperty("age", 35);
|
||||
String json = mapper.writeValueAsString(person);
|
||||
System.out.println(json); // {"name":"최영미","age":35}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 10. `@JsonManagedReference`와 `@JsonBackReference`
|
||||
- **설명**: 순환 참조 문제를 해결하기 위해 부모-자식 관계를 정의합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonManagedReference;
|
||||
import com.fasterxml.jackson.annotation.JsonBackReference;
|
||||
|
||||
public class Parent {
|
||||
@JsonManagedReference
|
||||
public Child child;
|
||||
|
||||
public void setChild(Child child) { this.child = child; }
|
||||
}
|
||||
|
||||
public class Child {
|
||||
@JsonBackReference
|
||||
public Parent parent;
|
||||
|
||||
public void setParent(Parent parent) { this.parent = parent; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Parent parent = new Parent();
|
||||
Child child = new Child();
|
||||
parent.setChild(child);
|
||||
child.setParent(parent);
|
||||
String json = mapper.writeValueAsString(parent);
|
||||
System.out.println(json); // {"child":{}}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 11. `@JsonIdentityInfo`
|
||||
- **설명**: 순환 참조를 객체 ID로 해결합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
|
||||
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
|
||||
public class Person {
|
||||
private int id;
|
||||
private String name;
|
||||
private Person friend;
|
||||
|
||||
public Person(int id, String name) { this.id = id; this.name = name; }
|
||||
public void setFriend(Person friend) { this.friend = friend; }
|
||||
public String getName() { return name; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Person p1 = new Person(1, "김태양");
|
||||
Person p2 = new Person(2, "이수진");
|
||||
p1.setFriend(p2);
|
||||
p2.setFriend(p1);
|
||||
String json = mapper.writeValueAsString(p1);
|
||||
System.out.println(json); // {"id":1,"name":"김태양","friend":{"id":2,"name":"이수진","friend":1}}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 12. `@JsonTypeInfo`와 `@JsonSubTypes`
|
||||
- **설명**: 다형성을 처리하기 위해 타입 정보를 JSON에 포함합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
@JsonSubTypes({
|
||||
@Type(value = Dog.class, name = "dog"),
|
||||
@Type(value = Cat.class, name = "cat")
|
||||
})
|
||||
public abstract class Animal {
|
||||
public String name;
|
||||
}
|
||||
|
||||
public class Dog extends Animal {}
|
||||
public class Cat extends Animal {}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Dog dog = new Dog(); dog.name = "멍멍이";
|
||||
String json = mapper.writeValueAsString(dog);
|
||||
System.out.println(json); // {"type":"dog","name":"멍멍이"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 13. `@JsonView`
|
||||
- **설명**: 특정 뷰에 따라 직렬화 대상을 제한합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
|
||||
public class Person {
|
||||
public interface PublicView {}
|
||||
public interface PrivateView extends PublicView {}
|
||||
|
||||
@JsonView(PublicView.class)
|
||||
private String name;
|
||||
|
||||
@JsonView(PrivateView.class)
|
||||
private int age;
|
||||
|
||||
public Person(String name, int age) { this.name = name; this.age = age; }
|
||||
public String getName() { return name; }
|
||||
public int getAge() { return age; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Person person = new Person("박준영", 27);
|
||||
String json = mapper.writerWithView(Person.PublicView.class).writeValueAsString(person);
|
||||
System.out.println(json); // {"name":"박준영"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 14. `@JacksonInject`
|
||||
- **설명**: JSON 데이터가 아닌 외부에서 주입된 값을 사용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||
|
||||
public class Person {
|
||||
@JacksonInject
|
||||
private String source;
|
||||
private String name;
|
||||
|
||||
public Person(String name) { this.name = name; }
|
||||
public String getSource() { return source; }
|
||||
public String getName() { return name; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
InjectableValues inject = new InjectableValues.Std().addValue(String.class, "외부 소스");
|
||||
Person person = mapper.reader(inject).forType(Person.class)
|
||||
.readValue("{\"name\":\"최민수\"}");
|
||||
System.out.println(person.getSource()); // "외부 소스"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
Jackson은 강력한 JSON 처리 기능을 제공하며, 위의 어노테이션들은 이를 세밀하게 제어할 수 있게 해줍니다. 다형성 처리, 순환 참조 해결, 속성 이름 매핑 등 다양한 기능을 통해 개발자는 유연하고 효율적인 JSON 처리를 구현할 수 있습니다. 프로젝트에서 Jackson을 사용할 때는 팀원 모두가 관련 어노테이션을 이해하고, 적절한 설정(예: 의존성 추가, IDE 플러그인 설치)을 통해 효율적으로 활용하는 것이 중요합니다.
|
||||
|
||||
궁금한 점이 있다면 언제든 물어보세요!
|
||||
315
docs/jackson_2.md
Normal file
315
docs/jackson_2.md
Normal file
@@ -0,0 +1,315 @@
|
||||
자바의 Jackson에서 XML과 JSR-310(Java Time API) 관련 기능을 다루는 글을 작성하겠습니다. Jackson은 기본적으로 JSON 처리를 위한 라이브러리지만, `jackson-dataformat-xml` 모듈을 통해 XML 데이터를 처리할 수 있으며, `jackson-datatype-jsr310` 모듈을 통해 Java 8의 JSR-310 날짜/시간 API(LocalDate, LocalDateTime 등)를 지원합니다. 이들 모듈은 JSON 처리와 유사한 방식으로 동작하지만, XML 구조나 JSR-310 데이터 타입에 특화된 어노테이션과 설정이 필요합니다.
|
||||
|
||||
아래에서는 XML과 JSR-310 관련 주요 어노테이션을 표로 정리한 뒤, 각 어노테이션에 대한 설명과 예시를 상세히 다루겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### Jackson XML 및 JSR-310 관련 주요 어노테이션 표
|
||||
|
||||
#### XML 관련 어노테이션 (jackson-dataformat-xml)
|
||||
| 어노테이션 | 설명 |
|
||||
|----------------------------|----------------------------------------------------------------------------------------|
|
||||
| `@JacksonXmlRootElement` | XML 루트 요소의 이름과 네임스페이스를 지정 |
|
||||
| `@JacksonXmlProperty` | XML 요소나 속성의 이름과 네임스페이스를 지정하며, 속성 여부를 설정 |
|
||||
| `@JacksonXmlElementWrapper` | 컬렉션 필드를 감싸는 XML 요소를 지정 |
|
||||
| `@JacksonXmlText` | 필드를 XML 요소의 텍스트 콘텐츠로 직렬화 |
|
||||
| `@JacksonXmlCData` | 필드를 XML의 CDATA 섹션으로 직렬화 |
|
||||
|
||||
#### JSR-310 관련 어노테이션 (jackson-datatype-jsr310)
|
||||
| 어노테이션 | 설명 |
|
||||
|----------------------------|----------------------------------------------------------------------------------------|
|
||||
| `@JsonFormat` | JSR-310 날짜/시간 객체의 직렬화 형식을 지정 (패턴, 시간대 등) |
|
||||
| `@JsonSerialize` | JSR-310 객체를 커스터마이즈된 직렬화 방식으로 처리 |
|
||||
| `@JsonDeserialize` | JSR-310 객체를 커스터마이즈된 역직렬화 방식으로 처리 |
|
||||
|
||||
---
|
||||
|
||||
### 프로젝트 설정
|
||||
XML과 JSR-310 기능을 사용하려면 `pom.xml`에 다음 의존성을 추가해야 합니다:
|
||||
```xml
|
||||
<!-- Jackson XML 지원 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<version>2.17.0</version>
|
||||
</dependency>
|
||||
<!-- Jackson JSR-310 지원 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.17.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 각 어노테이션 상세 설명 및 예시
|
||||
|
||||
#### XML 관련 어노테이션
|
||||
|
||||
##### 1. `@JacksonXmlRootElement`
|
||||
- **설명**: XML 문서의 루트 요소 이름을 지정하며, 네임스페이스를 설정할 수 있습니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
@JacksonXmlRootElement(localName = "person", namespace = "http://example.com")
|
||||
public class Person {
|
||||
private String name;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
Person person = new Person();
|
||||
person.setName("홍길동");
|
||||
String xml = xmlMapper.writeValueAsString(person);
|
||||
System.out.println(xml);
|
||||
}
|
||||
}
|
||||
// 출력: <person xmlns="http://example.com"><name>홍길동</name></person>
|
||||
```
|
||||
|
||||
##### 2. `@JacksonXmlProperty`
|
||||
- **설명**: XML 요소나 속성의 이름을 지정하고, 속성으로 직렬화할지 여부를 설정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
public class Person {
|
||||
@JacksonXmlProperty(localName = "fullName")
|
||||
private String name;
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true)
|
||||
private int age;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
Person person = new Person();
|
||||
person.setName("김영희");
|
||||
person.setAge(25);
|
||||
String xml = xmlMapper.writeValueAsString(person);
|
||||
System.out.println(xml);
|
||||
}
|
||||
}
|
||||
// 출력: <Person age="25"><fullName>김영희</fullName></Person>
|
||||
```
|
||||
|
||||
##### 3. `@JacksonXmlElementWrapper`
|
||||
- **설명**: 컬렉션 필드를 감싸는 XML 요소를 지정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Person {
|
||||
@JacksonXmlElementWrapper(localName = "hobbies")
|
||||
@JacksonXmlProperty(localName = "hobby")
|
||||
private List<String> hobbies;
|
||||
|
||||
public List<String> getHobbies() { return hobbies; }
|
||||
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
Person person = new Person();
|
||||
person.setHobbies(Arrays.asList("축구", "게임"));
|
||||
String xml = xmlMapper.writeValueAsString(person);
|
||||
System.out.println(xml);
|
||||
}
|
||||
}
|
||||
// 출력: <Person><hobbies><hobby>축구</hobby><hobby>게임</hobby></hobbies></Person>
|
||||
```
|
||||
|
||||
##### 4. `@JacksonXmlText`
|
||||
- **설명**: 필드를 XML 요소의 텍스트 콘텐츠로 직렬화합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
public class Note {
|
||||
@JacksonXmlText
|
||||
private String content;
|
||||
|
||||
public String getContent() { return content; }
|
||||
public void setContent(String content) { this.content = content; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
Note note = new Note();
|
||||
note.setContent("안녕하세요");
|
||||
String xml = xmlMapper.writeValueAsString(note);
|
||||
System.out.println(xml);
|
||||
}
|
||||
}
|
||||
// 출력: <Note>안녕하세요</Note>
|
||||
```
|
||||
|
||||
##### 5. `@JacksonXmlCData`
|
||||
- **설명**: 필드를 CDATA 섹션으로 감싸서 직렬화합니다. HTML 태그 등 특수 문자를 포함할 때 유용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
public class Message {
|
||||
@JacksonXmlProperty(localName = "text")
|
||||
@JacksonXmlCData
|
||||
private String content;
|
||||
|
||||
public String getContent() { return content; }
|
||||
public void setContent(String content) { this.content = content; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
Message message = new Message();
|
||||
message.setContent("<b>중요</b>");
|
||||
String xml = xmlMapper.writeValueAsString(message);
|
||||
System.out.println(xml);
|
||||
}
|
||||
}
|
||||
// 출력: <Message><text><![CDATA[<b>중요</b>]]></text></Message>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### JSR-310 관련 어노테이션
|
||||
|
||||
##### 1. `@JsonFormat`
|
||||
- **설명**: JSR-310 날짜/시간 객체의 직렬화 형식을 지정합니다. `pattern` 속성을 사용하여 원하는 포맷을 정의합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class Event {
|
||||
@JsonFormat(pattern = "yyyy/MM/dd")
|
||||
private LocalDate date;
|
||||
|
||||
public LocalDate getDate() { return date; }
|
||||
public void setDate(LocalDate date) { this.date = date; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
Event event = new Event();
|
||||
event.setDate(LocalDate.of(2025, 2, 27));
|
||||
String json = mapper.writeValueAsString(event);
|
||||
System.out.println(json);
|
||||
}
|
||||
}
|
||||
// 출력: {"date":"2025/02/27"}
|
||||
```
|
||||
|
||||
##### 2. `@JsonSerialize`
|
||||
- **설명**: JSR-310 객체를 사용자 정의 직렬화 방식으로 처리합니다. `JavaTimeModule`과 함께 커스터마이징할 때 유용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class Event {
|
||||
@JsonSerialize(using = LocalDateSerializer.class)
|
||||
private LocalDate date;
|
||||
|
||||
public LocalDate getDate() { return date; }
|
||||
public void setDate(LocalDate date) { this.date = date; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
Event event = new Event();
|
||||
event.setDate(LocalDate.of(2025, 2, 27));
|
||||
String json = mapper.writeValueAsString(event);
|
||||
System.out.println(json);
|
||||
}
|
||||
}
|
||||
// 출력: {"date":"2025-02-27"} (기본 ISO 포맷)
|
||||
```
|
||||
|
||||
##### 3. `@JsonDeserialize`
|
||||
- **설명**: JSR-310 객체를 사용자 정의 역직렬화 방식으로 처리합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class Event {
|
||||
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||
private LocalDate date;
|
||||
|
||||
public LocalDate getDate() { return date; }
|
||||
public void setDate(LocalDate date) { this.date = date; }
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
String json = "{\"date\":\"2025-02-27\"}";
|
||||
Event event = mapper.readValue(json, Event.class);
|
||||
System.out.println(event.getDate());
|
||||
}
|
||||
}
|
||||
// 출력: 2025-02-27
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 추가 설정 및 주의사항
|
||||
1. **XML 처리**: `XmlMapper`를 사용하여 XML을 직렬화/역직렬화하며, JSON과 달리 계층적 구조를 잘 표현해야 합니다.
|
||||
```java
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
```
|
||||
2. **JSR-310 모듈 등록**: `JavaTimeModule`을 `ObjectMapper`에 등록해야 JSR-310 타입이 기본적으로 지원됩니다.
|
||||
```java
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
Jackson의 XML 및 JSR-310 지원은 JSON 처리 능력을 확장하여 다양한 데이터 형식을 다룰 수 있게 해줍니다. XML 관련 어노테이션은 XML의 요소, 속성, 텍스트를 세밀히 제어하며, JSR-310 관련 어노테이션은 날짜/시간 데이터의 직렬화 형식을 사용자 정의할 수 있게 합니다. 프로젝트에서 이를 활용하려면 적절한 모듈 의존성을 추가하고, `XmlMapper` 또는 `ObjectMapper`를 올바르게 설정하는 것이 중요합니다.
|
||||
|
||||
추가 질문이 있다면 언제든 물어보세요!
|
||||
280
docs/java-core.md
Normal file
280
docs/java-core.md
Normal file
@@ -0,0 +1,280 @@
|
||||
자바에서 기본적으로 자주 사용되는 어노테이션들을 정리하겠습니다. 여기서 "기본적으로 자주 사용되는"이란 자바 표준 API(JDK)와 스프링 프레임워크, 그리고 일반적인 ORM(JPA) 환경에서 널리 활용되는 어노테이션을 기준으로 하겠습니다. 이들은 자바 개발에서 필수적인 기능(예: 클래스/메서드 정의, DI, 데이터베이스 매핑 등)을 제공하며, 특별한 외부 라이브러리 없이도 자주 등장합니다.
|
||||
|
||||
아래에서 주요 어노테이션을 표 형식으로 정리한 뒤, 각 어노테이션에 대한 설명과 예시를 제공하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### 자주 사용되는 어노테이션 표
|
||||
|
||||
| 어노테이션 | 설명 | 출처 |
|
||||
|-----------------------|----------------------------------------------------------------------------------------|---------------------|
|
||||
| `@Override` | 메서드가 상위 클래스나 인터페이스의 메서드를 재정의했음을 명시 | JDK (java.lang) |
|
||||
| `@Deprecated` | 해당 요소가 더 이상 권장되지 않음을 표시 | JDK (java.lang) |
|
||||
| `@SuppressWarnings` | 컴파일러 경고를 억제 | JDK (java.lang) |
|
||||
| `@FunctionalInterface`| 인터페이스가 단일 추상 메서드를 가지는 함수형 인터페이스임을 명시 | JDK (java.lang) |
|
||||
| `@SafeVarargs` | 가변 인자 메서드에서 타입 안전성을 보장함을 명시 | JDK (java.lang) |
|
||||
| `@Retention` | 어노테이션의 유지 범위를 지정 (SOURCE, CLASS, RUNTIME) | JDK (java.lang.annotation) |
|
||||
| `@Target` | 어노테이션이 적용될 수 있는 대상을 지정 | JDK (java.lang.annotation) |
|
||||
| `@Documented` | 어노테이션이 Javadoc에 포함되도록 지정 | JDK (java.lang.annotation) |
|
||||
| `@Inherited` | 어노테이션이 하위 클래스에 상속되도록 지정 | JDK (java.lang.annotation) |
|
||||
| `@SpringBootApplication` | 스프링 부트 애플리케이션의 메인 클래스임을 나타내며, 여러 설정을 포함 | Spring Boot |
|
||||
| `@Autowired` | 의존성 주입(DI)을 자동으로 수행 | Spring Framework |
|
||||
| `@RestController` | RESTful 웹 서비스 컨트롤러임을 지정 | Spring Framework |
|
||||
| `@RequestMapping` | HTTP 요청을 메서드나 클래스에 매핑 | Spring Framework |
|
||||
| `@GetMapping` | HTTP GET 요청을 메서드에 매핑 | Spring Framework |
|
||||
| `@PostMapping` | HTTP POST 요청을 메서드에 매핑 | Spring Framework |
|
||||
| `@Entity` | 클래스가 JPA 엔티티임을 나타냄 | JPA (jakarta.persistence) |
|
||||
| `@Id` | 엔티티의 기본 키(PK)를 지정 | JPA (jakarta.persistence) |
|
||||
| `@Column` | 필드가 데이터베이스 컬럼에 매핑됨을 지정 | JPA (jakarta.persistence) |
|
||||
| `@Table` | 엔티티가 매핑될 데이터베이스 테이블을 지정 | JPA (jakarta.persistence) |
|
||||
|
||||
---
|
||||
|
||||
### 각 어노테이션 상세 설명 및 예시
|
||||
|
||||
#### 1. `@Override`
|
||||
- **설명**: 메서드가 상위 클래스나 인터페이스의 메서드를 재정의했음을 컴파일러에 알립니다.
|
||||
- **예시**:
|
||||
```java
|
||||
class Parent {
|
||||
void display() { System.out.println("부모"); }
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@Override
|
||||
void display() { System.out.println("자식"); }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `@Deprecated`
|
||||
- **설명**: 메서드나 클래스가 더 이상 사용되지 않음을 표시하며, 사용 시 경고를 발생시킵니다.
|
||||
- **예시**:
|
||||
```java
|
||||
class OldClass {
|
||||
@Deprecated
|
||||
void oldMethod() { System.out.println("구형 메서드"); }
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. `@SuppressWarnings`
|
||||
- **설명**: 특정 경고(예: "unchecked", "deprecated")를 억제합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
@SuppressWarnings("unchecked")
|
||||
List rawList = new ArrayList(); // 경고 억제
|
||||
```
|
||||
|
||||
#### 4. `@FunctionalInterface`
|
||||
- **설명**: 단일 추상 메서드를 가진 인터페이스를 함수형 인터페이스로 표시합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
@FunctionalInterface
|
||||
interface MyFunction {
|
||||
void run();
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. `@SafeVarargs`
|
||||
- **설명**: 가변 인자 메서드가 타입 안전함을 보장합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
@SafeVarargs
|
||||
static <T> void printAll(T... items) {
|
||||
for (T item : items) System.out.println(item);
|
||||
}
|
||||
```
|
||||
|
||||
#### 6. `@Retention`
|
||||
- **설명**: 어노테이션의 유지 범위를 지정합니다 (SOURCE: 소스 코드, CLASS: 바이트 코드, RUNTIME: 런타임).
|
||||
- **예시**:
|
||||
```java
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MyAnnotation {}
|
||||
```
|
||||
|
||||
#### 7. `@Target`
|
||||
- **설명**: 어노테이션이 적용될 수 있는 위치(예: 메서드, 클래스 등)를 지정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@interface MyMethodAnnotation {}
|
||||
```
|
||||
|
||||
#### 8. `@Documented`
|
||||
- **설명**: 어노테이션이 Javadoc에 포함되도록 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import java.lang.annotation.Documented;
|
||||
|
||||
@Documented
|
||||
@interface MyDocAnnotation {}
|
||||
```
|
||||
|
||||
#### 9. `@Inherited`
|
||||
- **설명**: 어노테이션이 하위 클래스에 상속됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import java.lang.annotation.Inherited;
|
||||
|
||||
@Inherited
|
||||
@interface MyInheritedAnnotation {}
|
||||
```
|
||||
|
||||
#### 10. `@SpringBootApplication`
|
||||
- **설명**: 스프링 부트 애플리케이션의 진입점을 정의하며, `@Configuration`, `@EnableAutoConfiguration`, `@ComponentScan`을 포함합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 11. `@Autowired`
|
||||
- **설명**: 스프링의 의존성 주입을 자동으로 수행합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class UserService {
|
||||
@Autowired
|
||||
private UserRepository repository;
|
||||
}
|
||||
```
|
||||
|
||||
#### 12. `@RestController`
|
||||
- **설명**: REST API 컨트롤러를 정의하며, `@Controller`와 `@ResponseBody`를 결합한 형태입니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@RestController
|
||||
public class HelloController {
|
||||
@GetMapping("/hello")
|
||||
public String hello() { return "Hello, World!"; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 13. `@RequestMapping`
|
||||
- **설명**: HTTP 요청을 클래스나 메서드에 매핑합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class ApiController {
|
||||
@RequestMapping("/test")
|
||||
public String test() { return "API 테스트"; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 14. `@GetMapping`
|
||||
- **설명**: HTTP GET 요청을 메서드에 매핑합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@RestController
|
||||
public class UserController {
|
||||
@GetMapping("/users")
|
||||
public String getUsers() { return "유저 목록"; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 15. `@PostMapping`
|
||||
- **설명**: HTTP POST 요청을 메서드에 매핑합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
@RestController
|
||||
public class UserController {
|
||||
@PostMapping("/users")
|
||||
public String createUser(@RequestBody String user) { return "유저 생성: " + user; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 16. `@Entity`
|
||||
- **설명**: 클래스가 JPA 엔티티임을 나타냅니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class User {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
}
|
||||
```
|
||||
|
||||
#### 17. `@Id`
|
||||
- **설명**: 엔티티의 기본 키를 지정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class Product {
|
||||
@Id
|
||||
private Long productId;
|
||||
}
|
||||
```
|
||||
|
||||
#### 18. `@Column`
|
||||
- **설명**: 필드가 매핑될 데이터베이스 컬럼을 정의합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Column;
|
||||
|
||||
@Entity
|
||||
public class Employee {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column(name = "full_name")
|
||||
private String name;
|
||||
}
|
||||
```
|
||||
|
||||
#### 19. `@Table`
|
||||
- **설명**: 엔티티가 매핑될 데이터베이스 테이블을 지정합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "customers")
|
||||
public class Customer {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
위 어노테이션들은 자바 개발에서 필수적인 역할을 합니다. JDK 제공 어노테이션은 기본적인 코드 품질과 가독성을 높이고, 스프링 프레임워크 어노테이션은 의존성 주입과 웹 애플리케이션 개발을 간소화하며, JPA 어노테이션은 데이터베이스와 객체 간 매핑을 쉽게 처리합니다. 프로젝트 요구사항에 따라 적절히 선택하고 사용하면 개발 효율성과 유지보수성을 크게 향상시킬 수 있습니다.
|
||||
|
||||
추가로 궁금한 점이 있다면 말씀해주세요!
|
||||
274
docs/javadoc.md
Normal file
274
docs/javadoc.md
Normal file
@@ -0,0 +1,274 @@
|
||||
자바독(Javadoc)은 자바 소스 코드에서 문서를 생성하기 위한 도구로, 주로 클래스, 인터페이스, 메서드, 필드 등에 대한 설명을 HTML 형식으로 생성합니다. Javadoc은 특정 태그(속성)를 사용하여 코드에 주석을 추가하며, 이를 기반으로 문서를 작성합니다. 아래에서는 Javadoc에서 지원하는 모든 속성(태그)을 표 형식으로 정리하고, 문서화를 잘하는 팁을 제공하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### Javadoc에서 지원하는 속성(태그) 표
|
||||
|
||||
| 태그 | 설명 | 적용 대상 |
|
||||
|-----------------------|----------------------------------------------------------------------------------------|-----------------------|
|
||||
| `@author` | 코드 작성자를 지정 | 클래스, 인터페이스 |
|
||||
| `@version` | 코드의 버전을 명시 | 클래스, 인터페이스 |
|
||||
| `@since` | 해당 요소가 추가된 버전이나 날짜를 명시 | 모든 요소 |
|
||||
| `@deprecated` | 해당 요소가 더 이상 권장되지 않음을 표시 (Java의 `@Deprecated`와 함께 사용 가능) | 모든 요소 |
|
||||
| `@see` | 관련 클래스, 메서드, 필드 등의 참조를 제공 | 모든 요소 |
|
||||
| `@link` | 인라인 텍스트 내에서 다른 요소로의 하이퍼링크를 생성 | 모든 요소 (인라인) |
|
||||
| `@linkplain` | `@link`와 유사하지만, 링크 텍스트를 코드 스타일이 아닌 일반 텍스트로 표시 | 모든 요소 (인라인) |
|
||||
| `@param` | 메서드의 매개변수를 설명 | 메서드, 생성자 |
|
||||
| `@return` | 메서드의 반환값을 설명 | 메서드 |
|
||||
| `@throws` / `@exception` | 메서드가 던질 수 있는 예외를 설명 | 메서드, 생성자 |
|
||||
| `@value` | 상수 필드의 값을 인라인으로 참조 | 필드 (static final) |
|
||||
| `@code` | 텍스트를 코드 스타일(모노스페이스)로 표시하며, HTML 태그를 이스케이프 처리 | 모든 요소 (인라인) |
|
||||
| `@literal` | 텍스트를 그대로 표시하며, HTML 태그를 이스케이프 처리 | 모든 요소 (인라인) |
|
||||
| `{@inheritDoc}` | 상위 클래스나 인터페이스의 Javadoc 설명을 상속 | 메서드, 클래스 |
|
||||
|
||||
---
|
||||
|
||||
### 각 태그 상세 설명 및 예시
|
||||
|
||||
#### 1. `@author`
|
||||
- **설명**: 코드 작성자를 나타냅니다. 여러 명일 경우 반복 사용 가능.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 사용자 관리 클래스
|
||||
* @author 홍길동
|
||||
* @author 김영희
|
||||
*/
|
||||
public class UserManager {}
|
||||
```
|
||||
|
||||
#### 2. `@version`
|
||||
- **설명**: 코드의 버전을 명시합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 계산기 유틸리티
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class Calculator {}
|
||||
```
|
||||
|
||||
#### 3. `@since`
|
||||
- **설명**: 해당 요소가 추가된 시점을 기록합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 사용자 ID 반환
|
||||
* @since 2023-01-01
|
||||
*/
|
||||
public Long getUserId() { return 1L; }
|
||||
```
|
||||
|
||||
#### 4. `@deprecated`
|
||||
- **설명**: 더 이상 사용되지 않는 요소임을 알립니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 구형 메서드
|
||||
* @deprecated 대신 {@link #newMethod()}를 사용하세요.
|
||||
*/
|
||||
@Deprecated
|
||||
public void oldMethod() {}
|
||||
```
|
||||
|
||||
#### 5. `@see`
|
||||
- **설명**: 관련된 참조를 추가합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 사용자 정보 클래스
|
||||
* @see java.util.List
|
||||
* @see UserManager#getUser()
|
||||
*/
|
||||
public class User {}
|
||||
```
|
||||
|
||||
#### 6. `@link`
|
||||
- **설명**: 인라인 링크를 생성합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 사용자를 추가합니다. 자세한 내용은 {@link UserManager}를 참조하세요.
|
||||
*/
|
||||
public void addUser() {}
|
||||
```
|
||||
|
||||
#### 7. `@linkplain`
|
||||
- **설명**: `@link`와 비슷하지만 코드 스타일이 아닌 일반 텍스트로 표시.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 사용자를 삭제합니다. {@linkplain UserManager}에서 관리합니다.
|
||||
*/
|
||||
public void deleteUser() {}
|
||||
```
|
||||
|
||||
#### 8. `@param`
|
||||
- **설명**: 메서드 매개변수를 설명합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 두 수를 더합니다.
|
||||
* @param a 첫 번째 수
|
||||
* @param b 두 번째 수
|
||||
*/
|
||||
public int add(int a, int b) { return a + b; }
|
||||
```
|
||||
|
||||
#### 9. `@return`
|
||||
- **설명**: 메서드 반환값을 설명합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 사용자 이름을 반환합니다.
|
||||
* @return 사용자 이름 문자열
|
||||
*/
|
||||
public String getName() { return "홍길동"; }
|
||||
```
|
||||
|
||||
#### 10. `@throws` / `@exception`
|
||||
- **설명**: 메서드가 발생시킬 수 있는 예외를 설명합니다 (동일한 기능).
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 파일을 읽습니다.
|
||||
* @throws IOException 파일 읽기 실패 시 발생
|
||||
*/
|
||||
public void readFile() throws IOException {}
|
||||
```
|
||||
|
||||
#### 11. `@value`
|
||||
- **설명**: 상수 필드의 값을 인라인으로 참조합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* 최대 사용자 수: {@value}
|
||||
*/
|
||||
public static final int MAX_USERS = 100;
|
||||
```
|
||||
|
||||
#### 12. `@code`
|
||||
- **설명**: 코드 스타일로 텍스트를 표시하며, HTML 태그를 이스케이프합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* {@code null}을 반환할 수 있습니다.
|
||||
*/
|
||||
public String getValue() { return null; }
|
||||
```
|
||||
|
||||
#### 13. `@literal`
|
||||
- **설명**: 텍스트를 그대로 표시하며, HTML 태그를 이스케이프합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* <b>중요</b>는 {@literal <b>중요</b>}로 표시됩니다.
|
||||
*/
|
||||
public void importantMethod() {}
|
||||
```
|
||||
|
||||
#### 14. `{@inheritDoc}`
|
||||
- **설명**: 상위 클래스나 인터페이스의 Javadoc을 상속합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String toString() { return super.toString(); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 문서화를 잘하는 팁
|
||||
|
||||
1. **간결하고 명확하게 작성**
|
||||
- 불필요한 장황함을 피하고 핵심 정보만 전달하세요.
|
||||
- 예: "이 메서드는 두 수를 더합니다." vs "두 정수를 더한 결과를 반환하는 함수입니다."
|
||||
|
||||
2. **일관된 스타일 유지**
|
||||
- 팀 내에서 Javadoc 스타일을 통일하세요 (예: 문장 끝에 마침표 사용 여부, 태그 순서 등).
|
||||
- 예: `@param`은 항상 먼저, `@throws`는 마지막에 배치.
|
||||
|
||||
3. **의미 있는 설명 제공**
|
||||
- 단순히 "이 메서드는 값을 반환합니다" 대신, **왜**, **어떻게**, **언제** 사용되는지 설명하세요.
|
||||
- 예:
|
||||
```java
|
||||
/**
|
||||
* 사용자 인증 토큰을 생성합니다.
|
||||
* 로그인 성공 시 호출되며, 토큰은 24시간 유효합니다.
|
||||
* @param userId 사용자 ID
|
||||
* @return JWT 형식의 인증 토큰
|
||||
*/
|
||||
```
|
||||
|
||||
4. **태그를 적절히 활용**
|
||||
- 모든 메서드에 `@param`, `@return`, `@throws`를 빠뜨리지 말고 작성하세요.
|
||||
- `@see`와 `@link`로 관련 요소를 연결해 탐색성을 높이세요.
|
||||
|
||||
5. **코드 예제 추가**
|
||||
- 복잡한 메서드에는 사용 예제를 포함하세요.
|
||||
- 예:
|
||||
```java
|
||||
/**
|
||||
* 문자열을 분리합니다.
|
||||
* 예: {@code splitString("a,b,c", ",")} -> ["a", "b", "c"]
|
||||
* @param input 입력 문자열
|
||||
* @param delimiter 구분자
|
||||
* @return 분리된 문자열 배열
|
||||
*/
|
||||
```
|
||||
|
||||
6. **Deprecated 요소 명확히 표시**
|
||||
- `@deprecated` 태그와 함께 대체 방법을 제안하세요.
|
||||
- 예:
|
||||
```java
|
||||
/**
|
||||
* @deprecated 대신 {@link #newMethod()}를 사용하세요.
|
||||
*/
|
||||
```
|
||||
|
||||
7. **HTML 태그 활용**
|
||||
- `<p>`, `<ul>`, `<li>` 등을 사용해 가독성을 높이세요.
|
||||
- 예:
|
||||
```java
|
||||
/**
|
||||
* <p>이 클래스는 사용자 정보를 관리합니다.</p>
|
||||
* <ul>
|
||||
* <li>등록</li>
|
||||
* <li>삭제</li>
|
||||
* </ul>
|
||||
*/
|
||||
```
|
||||
|
||||
8. **프로젝트별 규칙 정의**
|
||||
- `@since`, `@author` 등은 프로젝트 정책에 따라 선택적으로 사용하세요.
|
||||
- 예: 개인 프로젝트에서는 `@author` 생략 가능.
|
||||
|
||||
9. **자동화 도구 활용**
|
||||
- Maven이나 Gradle의 Javadoc 플러그인을 사용해 문서 생성을 자동화하세요.
|
||||
- 예 (Maven):
|
||||
```xml
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.6.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>javadoc</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
10. **정기적 리뷰**
|
||||
- 코드 변경 시 Javadoc도 함께 업데이트하고, 팀 리뷰를 통해 누락된 부분을 점검하세요.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
Javadoc 태그는 자바 코드의 문서화를 체계적으로 지원하며, 잘 작성된 Javadoc은 코드의 유지보수성과 협업 효율성을 크게 높입니다. 위의 태그를 적절히 사용하고, 제공된 팁을 적용하면 읽기 쉽고 유용한 문서를 만들 수 있습니다. 프로젝트의 규모와 팀의 필요에 따라 문서화 수준을 조정하되, 최소한 핵심 요소(메서드 설명, 매개변수, 반환값, 예외)는 항상 포함하는 습관을 들이세요.
|
||||
|
||||
추가 질문이 있다면 언제든 물어보세요!
|
||||
202
docs/javax.sound.midi.md
Normal file
202
docs/javax.sound.midi.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# **Java `javax.sound.midi` 쉽게 배우기**
|
||||
|
||||
## **1. `javax.sound.midi`란?**
|
||||
`javax.sound.midi` 패키지는 **자바에서 MIDI(악기 디지털 인터페이스) 데이터를 다루는 기능**을 제공한다.
|
||||
이를 이용하면 **MIDI 파일을 재생, 편집, 녹음, 생성**할 수 있다.
|
||||
|
||||
**주요 기능**
|
||||
✔ **MIDI 파일 재생** (악기 소리 출력)
|
||||
✔ **MIDI 이벤트 생성 및 수정** (노트 추가/삭제)
|
||||
✔ **실시간 MIDI 입력/출력** (MIDI 키보드 연결 가능)
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `MidiSystem` 클래스 (MIDI 시스템 제어)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `getSequencer()` | MIDI 파일을 재생하는 `Sequencer` 객체 반환 |
|
||||
| `getSynthesizer()` | 소프트웨어 신시사이저(`Synthesizer`) 반환 |
|
||||
| `getReceiver()` | MIDI 데이터를 출력할 `Receiver` 반환 |
|
||||
| `getTransmitter()` | MIDI 데이터를 입력받을 `Transmitter` 반환 |
|
||||
|
||||
---
|
||||
|
||||
### **(2) `Sequencer` 클래스 (MIDI 재생기)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `open()` | `Sequencer`를 초기화하고 사용 준비 |
|
||||
| `setSequence(Sequence sequence)` | 재생할 MIDI 시퀀스 설정 |
|
||||
| `start()` | MIDI 재생 시작 |
|
||||
| `stop()` | MIDI 재생 중지 |
|
||||
| `setLoopCount(int count)` | MIDI 반복 재생 설정 |
|
||||
|
||||
---
|
||||
|
||||
### **(3) `Synthesizer` 클래스 (신시사이저 - 소리 생성)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `open()` | 신시사이저를 활성화 |
|
||||
| `getChannels()` | MIDI 채널(`MidiChannel[]`) 가져오기 |
|
||||
| `getReceiver()` | 신시사이저에 MIDI 메시지를 보내는 `Receiver` 반환 |
|
||||
|
||||
---
|
||||
|
||||
### **(4) `MidiChannel` 클래스 (MIDI 음원 채널)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `noteOn(int note, int velocity)` | 특정 음을 연주 (노트 켜기) |
|
||||
| `noteOff(int note)` | 특정 음을 멈춤 (노트 끄기) |
|
||||
| `setInstrument(Instrument instrument)` | 채널의 악기 변경 |
|
||||
|
||||
---
|
||||
|
||||
### **(5) `Sequence` 클래스 (MIDI 데이터 관리)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `createTrack()` | 새로운 `Track`(MIDI 트랙) 생성 |
|
||||
| `getTracks()` | 시퀀스에 포함된 `Track` 목록 가져오기 |
|
||||
|
||||
---
|
||||
|
||||
### **(6) `Track` 클래스 (MIDI 트랙)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `add(MidiEvent event)` | MIDI 이벤트 추가 |
|
||||
| `remove(MidiEvent event)` | MIDI 이벤트 제거 |
|
||||
|
||||
---
|
||||
|
||||
### **(7) `MidiEvent` 클래스 (MIDI 이벤트)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `new MidiEvent(MidiMessage message, long tick)` | 특정 시간(tick)에 MIDI 메시지 추가 |
|
||||
|
||||
---
|
||||
|
||||
## **3. MIDI 파일 재생 예제**
|
||||
### **✔ MIDI 파일을 재생하는 코드 (`Sequencer` 사용)**
|
||||
```java
|
||||
import javax.sound.midi.*;
|
||||
import java.io.File;
|
||||
|
||||
public class MidiPlayer {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// MIDI 파일 로드
|
||||
File midiFile = new File("song.mid");
|
||||
Sequence sequence = MidiSystem.getSequence(midiFile);
|
||||
|
||||
// Sequencer 생성 및 열기
|
||||
Sequencer sequencer = MidiSystem.getSequencer();
|
||||
sequencer.open();
|
||||
sequencer.setSequence(sequence);
|
||||
|
||||
// 재생 시작
|
||||
sequencer.start();
|
||||
System.out.println("MIDI 재생 시작!");
|
||||
|
||||
// 재생이 끝날 때까지 대기
|
||||
while (sequencer.isRunning()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
// 종료 처리
|
||||
sequencer.close();
|
||||
System.out.println("MIDI 재생 완료!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **MIDI 파일을 불러와 재생하는 코드이다.**
|
||||
✅ **`Sequencer`를 사용하여 MIDI를 관리하고 재생한다.**
|
||||
|
||||
---
|
||||
|
||||
## **4. 실시간으로 MIDI 연주하기**
|
||||
### **✔ 신시사이저를 사용해 실시간으로 MIDI 노트 출력**
|
||||
```java
|
||||
import javax.sound.midi.*;
|
||||
|
||||
public class MidiSynthesizer {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 신시사이저 열기
|
||||
Synthesizer synthesizer = MidiSystem.getSynthesizer();
|
||||
synthesizer.open();
|
||||
|
||||
// MIDI 채널 가져오기
|
||||
MidiChannel[] channels = synthesizer.getChannels();
|
||||
MidiChannel channel = channels[0]; // 첫 번째 채널 사용
|
||||
|
||||
// 특정 노트 연주 (도(C4) - MIDI 번호 60)
|
||||
int note = 60;
|
||||
channel.noteOn(note, 80); // 음을 켬 (velocity: 80)
|
||||
System.out.println("도(C4) 연주 중...");
|
||||
|
||||
Thread.sleep(1000); // 1초 동안 연주
|
||||
|
||||
channel.noteOff(note); // 음을 끔
|
||||
System.out.println("연주 종료!");
|
||||
|
||||
// 신시사이저 닫기
|
||||
synthesizer.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **`Synthesizer`를 사용해 실시간으로 MIDI 소리를 출력한다.**
|
||||
✅ **`noteOn()`으로 음을 켜고 `noteOff()`로 끈다.**
|
||||
|
||||
---
|
||||
|
||||
## **5. 새로운 MIDI 파일 생성하기**
|
||||
### **✔ MIDI 노트를 추가하여 새로운 MIDI 파일 생성**
|
||||
```java
|
||||
import javax.sound.midi.*;
|
||||
import java.io.File;
|
||||
|
||||
public class MidiCreator {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 새로운 시퀀스 생성
|
||||
Sequence sequence = new Sequence(Sequence.PPQ, 24);
|
||||
Track track = sequence.createTrack();
|
||||
|
||||
// 노트 ON (C4 = 60)
|
||||
ShortMessage noteOn = new ShortMessage();
|
||||
noteOn.setMessage(ShortMessage.NOTE_ON, 0, 60, 80);
|
||||
track.add(new MidiEvent(noteOn, 1));
|
||||
|
||||
// 노트 OFF (C4 = 60)
|
||||
ShortMessage noteOff = new ShortMessage();
|
||||
noteOff.setMessage(ShortMessage.NOTE_OFF, 0, 60, 80);
|
||||
track.add(new MidiEvent(noteOff, 24));
|
||||
|
||||
// MIDI 파일로 저장
|
||||
File midiFile = new File("new_song.mid");
|
||||
MidiSystem.write(sequence, 1, midiFile);
|
||||
|
||||
System.out.println("새로운 MIDI 파일이 생성되었습니다: " + midiFile.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **새로운 MIDI 파일을 만들고, C4(도) 음을 추가하여 저장한다.**
|
||||
✅ **`MidiEvent`를 사용하여 MIDI 데이터를 구성한다.**
|
||||
|
||||
---
|
||||
|
||||
## **6. 정리**
|
||||
✔ **`javax.sound.midi`는 자바에서 MIDI 데이터를 다루는 API이다!**
|
||||
✔ **MIDI 파일을 재생(`Sequencer`), 실시간 연주(`Synthesizer`), 노트 추가(`Track`, `MidiEvent`) 가능**
|
||||
✔ **기본적으로 자바에서 제공하는 소프트웨어 신시사이저를 이용하여 다양한 MIDI 작업 가능**
|
||||
|
||||
✅ **MIDI 파일 플레이어, 가상 피아노, 미디 편집기 등을 만들 수 있다!**
|
||||
175
docs/javax.sound.sampled.md
Normal file
175
docs/javax.sound.sampled.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# **Java `javax.sound.sampled` 쉽게 배우기**
|
||||
|
||||
## **1. `javax.sound.sampled`란?**
|
||||
`javax.sound.sampled` 패키지는 **자바에서 오디오(소리)를 다루는 기능**을 제공한다.
|
||||
이를 이용하면 **오디오 파일을 재생, 녹음, 변환, 편집**할 수 있다.
|
||||
|
||||
**주요 기능**
|
||||
✔ 오디오 파일(WAV, AIFF 등) 재생
|
||||
✔ 마이크 입력 받아 녹음
|
||||
✔ 오디오 데이터 변환
|
||||
|
||||
---
|
||||
|
||||
## **2. 주요 클래스 및 메서드 정리**
|
||||
|
||||
### **(1) `AudioSystem` 클래스 (오디오 시스템 제어)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `getClip()` | 짧은 오디오를 재생하는 `Clip` 객체 반환 |
|
||||
| `getAudioInputStream(File file)` | 오디오 파일을 `AudioInputStream`으로 가져오기 |
|
||||
| `getMixerInfo()` | 사용 가능한 믹서(입출력 장치) 목록 가져오기 |
|
||||
| `getLine(DataLine.Info info)` | 특정 오디오 라인(스피커, 마이크 등) 가져오기 |
|
||||
|
||||
---
|
||||
|
||||
### **(2) `Clip` 클래스 (짧은 오디오 재생)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `open(AudioInputStream stream)` | 오디오 데이터를 로드하여 준비 |
|
||||
| `start()` | 오디오 재생 시작 |
|
||||
| `stop()` | 오디오 재생 중지 |
|
||||
| `loop(int count)` | 오디오를 반복 재생 |
|
||||
|
||||
---
|
||||
|
||||
### **(3) `SourceDataLine` 클래스 (실시간 오디오 출력)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `open(AudioFormat format)` | 특정 형식의 오디오를 출력하도록 설정 |
|
||||
| `write(byte[] data, int offset, int length)` | 오디오 데이터를 출력(재생) |
|
||||
| `start()` | 오디오 출력 시작 |
|
||||
| `stop()` | 오디오 출력 중지 |
|
||||
|
||||
---
|
||||
|
||||
### **(4) `TargetDataLine` 클래스 (마이크 입력 녹음)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `open(AudioFormat format)` | 특정 형식의 오디오를 입력받도록 설정 |
|
||||
| `start()` | 녹음 시작 |
|
||||
| `read(byte[] buffer, int offset, int length)` | 마이크 입력 데이터를 읽기 |
|
||||
| `stop()` | 녹음 중지 |
|
||||
|
||||
---
|
||||
|
||||
### **(5) `AudioInputStream` 클래스 (오디오 데이터 스트림)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------|
|
||||
| `read(byte[] buffer, int offset, int length)` | 오디오 데이터를 읽어오기 |
|
||||
| `skip(long n)` | 지정된 바이트 수만큼 건너뛰기 |
|
||||
| `close()` | 스트림 닫기 |
|
||||
|
||||
---
|
||||
|
||||
## **3. 자바로 오디오 재생하기**
|
||||
### **✔ WAV 파일 재생 예제 (`Clip` 사용)**
|
||||
```java
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.File;
|
||||
|
||||
public class AudioPlayer {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
File audioFile = new File("sound.wav"); // 재생할 파일
|
||||
AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile);
|
||||
|
||||
Clip clip = AudioSystem.getClip();
|
||||
clip.open(audioStream);
|
||||
clip.start(); // 재생 시작
|
||||
|
||||
Thread.sleep(clip.getMicrosecondLength() / 1000); // 재생 시간만큼 대기
|
||||
clip.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **`Clip`을 사용하면 짧은 오디오 파일을 쉽게 재생할 수 있다.**
|
||||
|
||||
---
|
||||
|
||||
## **4. 자바로 마이크 녹음하기**
|
||||
### **✔ 마이크 입력 받아 WAV 파일 저장**
|
||||
```java
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AudioRecorder {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
AudioFormat format = new AudioFormat(44100, 16, 2, true, true);
|
||||
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
|
||||
TargetDataLine microphone = (TargetDataLine) AudioSystem.getLine(info);
|
||||
|
||||
microphone.open(format);
|
||||
microphone.start();
|
||||
|
||||
File outputFile = new File("recorded.wav");
|
||||
AudioInputStream audioStream = new AudioInputStream(microphone);
|
||||
|
||||
System.out.println("녹음 시작! 5초간 녹음합니다...");
|
||||
AudioSystem.write(audioStream, AudioFileFormat.Type.WAVE, outputFile);
|
||||
|
||||
Thread.sleep(5000);
|
||||
microphone.stop();
|
||||
microphone.close();
|
||||
|
||||
System.out.println("녹음 완료! 파일 저장: " + outputFile.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **마이크로 5초 동안 녹음하고 WAV 파일로 저장한다.**
|
||||
|
||||
---
|
||||
|
||||
## **5. 자바에서 실시간 오디오 재생**
|
||||
### **✔ `SourceDataLine`을 사용한 실시간 오디오 출력**
|
||||
```java
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RealTimeAudioPlayer {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
File audioFile = new File("sound.wav");
|
||||
AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile);
|
||||
AudioFormat format = audioStream.getFormat();
|
||||
|
||||
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
|
||||
SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(info);
|
||||
sourceLine.open(format);
|
||||
sourceLine.start();
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = audioStream.read(buffer)) != -1) {
|
||||
sourceLine.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
sourceLine.drain();
|
||||
sourceLine.close();
|
||||
audioStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
✅ **오디오 데이터를 한 번에 조금씩 읽어 재생하는 방식으로, 큰 파일도 재생할 수 있다.**
|
||||
|
||||
---
|
||||
|
||||
## **6. 정리**
|
||||
✔ **`javax.sound.sampled`는 자바에서 오디오를 다루는 표준 API이다!**
|
||||
✔ **파일 재생 (`Clip`) / 실시간 출력 (`SourceDataLine`) / 마이크 입력 (`TargetDataLine`) 등 다양한 기능 제공**
|
||||
✔ **오디오 포맷 변환, 녹음 및 편집도 가능**
|
||||
|
||||
✅ **자바로 간단한 음악 플레이어나 음성 녹음 프로그램을 만들 수 있다!**
|
||||
357
docs/jgit.md
Normal file
357
docs/jgit.md
Normal file
@@ -0,0 +1,357 @@
|
||||
자바의 JGit 라이브러리에 대해 설명하자면, JGit은 Git 버전 관리 시스템을 자바 애플리케이션에서 사용할 수 있도록 해주는 순수 자바로 작성된 오픈소스 라이브러리입니다. 이 라이브러리는 Eclipse 재단에서 개발되었으며, Git의 기능을 프로그래밍 방식으로 활용할 수 있게 설계되었습니다. JGit을 사용하면 별도의 Git 클라이언트 설치 없이 자바 코드로 Git 저장소를 생성, 조작, 관리할 수 있습니다.
|
||||
|
||||
### 주요 특징
|
||||
1. **순수 자바 구현**: JGit은 네이티브 Git 명령어에 의존하지 않고 자바로 작성되었기 때문에 플랫폼 독립적입니다. Windows, Linux, macOS 등 어디서나 동일하게 동작합니다.
|
||||
2. **Git 기능 지원**: JGit은 Git의 주요 기능을 대부분 지원합니다. 예를 들어, 저장소 초기화, 커밋, 브랜치 관리, 머지, 푸시/풀, 로그 조회 등이 가능합니다.
|
||||
3. **임베디드 사용**: 외부 Git 설치 없이 애플리케이션에 임베드하여 사용할 수 있어, Git 기반 도구나 워크플로우를 자바로 쉽게 통합할 수 있습니다.
|
||||
4. **Eclipse와의 통합**: JGit은 Eclipse IDE의 Git 플러그인(EGit)의 핵심 구성 요소로 사용되며, IDE 내에서 Git 작업을 지원합니다.
|
||||
|
||||
### 주요 기능 예시
|
||||
JGit을 사용하면 다음과 같은 작업을 자바 코드로 수행할 수 있습니다:
|
||||
- **저장소 초기화**:
|
||||
```java
|
||||
Git.init().setDirectory(new File("/path/to/repo")).call();
|
||||
```
|
||||
- **파일 추가 및 커밋**:
|
||||
```java
|
||||
Git git = Git.open(new File("/path/to/repo/.git"));
|
||||
git.add().addFilepattern("file.txt").call();
|
||||
git.commit().setMessage("Initial commit").call();
|
||||
```
|
||||
- **브랜치 생성**:
|
||||
```java
|
||||
git.branchCreate().setName("feature-branch").call();
|
||||
```
|
||||
- **원격 저장소에서 풀**:
|
||||
```java
|
||||
git.pull().call();
|
||||
```
|
||||
|
||||
### 사용 사례
|
||||
- **자동화 도구**: CI/CD 파이프라인에서 Git 작업을 스크립트로 자동화.
|
||||
- **커스텀 Git 클라이언트**: GUI나 CLI 기반의 자체 Git 도구 개발.
|
||||
- **프로젝트 관리**: 소스 코드 관리나 버전 관리 기능을 애플리케이션에 통합.
|
||||
|
||||
### 장단점
|
||||
#### 장점
|
||||
- 설치 필요 없이 자바 환경에서 바로 사용 가능.
|
||||
- Git 명령어를 호출하는 대신 API로 세밀한 제어 가능.
|
||||
- 크로스 플랫폼 호환성.
|
||||
|
||||
#### 단점
|
||||
- 네이티브 Git에 비해 일부 고급 기능이 제한적이거나 느릴 수 있음.
|
||||
- 대규모 저장소에서 성능 최적화가 필요할 수 있음.
|
||||
|
||||
### 의존성 추가 (Maven 예시)
|
||||
JGit을 프로젝트에 추가하려면 Maven을 사용하는 경우 `pom.xml`에 다음 의존성을 추가하면 됩니다:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
<version>6.8.0.202311291450-r</version> <!-- 최신 버전 확인 필요 -->
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 참고 자료
|
||||
- 공식 사이트: [JGit GitHub](https://github.com/eclipse/jgit)
|
||||
- 문서: [JGit User Guide](https://www.eclipse.org/jgit/documentation/)
|
||||
|
||||
추가로 궁금한 점이 있거나 특정 기능에 대해 더 알고 싶다면 말씀해주세요!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
서버의 컨트롤러에서 Git 저장소를 제공한다는 것은 웹 애플리케이션의 백엔드에서 Git 저장소를 생성하고, 클라이언트가 이를 HTTP(S)를 통해 접근하거나 다운로드할 수 있도록 하는 것을 의미합니다. 여기서는 Spring Boot 기반 서버에서 JGit을 활용해 Git 저장소를 제공하는 방법을 단계별로 설명하겠습니다. 클라이언트가 저장소를 클론(clone)하거나 파일을 다운로드할 수 있는 시나리오를 중심으로 구성합니다.
|
||||
|
||||
---
|
||||
|
||||
### 목표
|
||||
- 서버에서 Git 저장소를 동적으로 생성하거나 관리.
|
||||
- HTTP 엔드포인트를 통해 저장소에 접근하거나 `.git` 디렉토리를 제공.
|
||||
- JGit을 사용해 저장소 초기화 및 기본 파일 추가.
|
||||
|
||||
---
|
||||
|
||||
### 1. **프로젝트 설정**
|
||||
Spring Boot와 JGit을 사용해 기본 환경을 설정합니다.
|
||||
|
||||
#### (1) 의존성 추가 (Maven)
|
||||
`pom.xml`에 필요한 의존성을 추가:
|
||||
```xml
|
||||
<dependencies>
|
||||
<!-- Spring Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- Thymeleaf (선택적, UI용) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<!-- JGit -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
<version>6.8.0.202311291450-r</version>
|
||||
</dependency>
|
||||
<!-- Lombok (선택적) -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
#### (2) 프로젝트 구조
|
||||
```
|
||||
src/
|
||||
├── main/
|
||||
│ ├── java/
|
||||
│ │ └── com/
|
||||
│ │ └── example/
|
||||
│ │ └── gitserver/
|
||||
│ │ ├── controller/ # 컨트롤러
|
||||
│ │ ├── service/ # Git 로직
|
||||
│ │ └── GitServerApplication.java
|
||||
│ └── resources/
|
||||
│ └── application.properties # 설정
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. **Git 서비스 구현**
|
||||
`GitService` 클래스에서 저장소를 생성하고 관리하는 로직을 작성합니다.
|
||||
|
||||
```java
|
||||
package com.example.gitserver.service;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@Service
|
||||
public class GitService {
|
||||
|
||||
// 저장소 생성 및 초기화
|
||||
public File createRepository(String repoName) throws GitAPIException, IOException {
|
||||
File repoDir = new File("./repos/" + repoName); // 서버 내 저장소 디렉토리
|
||||
if (!repoDir.exists()) {
|
||||
repoDir.mkdirs();
|
||||
}
|
||||
|
||||
try (Git git = Git.init().setDirectory(repoDir).call()) {
|
||||
// 초기 파일 추가 (선택적)
|
||||
File readme = new File(repoDir, "README.md");
|
||||
Files.write(readme.toPath(), "# Sample Repository".getBytes());
|
||||
git.add().addFilepattern("README.md").call();
|
||||
git.commit().setMessage("Initial commit").call();
|
||||
}
|
||||
return repoDir;
|
||||
}
|
||||
|
||||
// 저장소 디렉토리 반환
|
||||
public File getRepository(String repoName) {
|
||||
return new File("./repos/" + repoName);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. **컨트롤러에서 Git 저장소 제공**
|
||||
`GitController`에서 HTTP 엔드포인트를 통해 저장소를 제공합니다. 두 가지 방식으로 접근할 수 있습니다:
|
||||
1. **저장소 전체 다운로드**: `.git` 디렉토리를 ZIP 파일로 제공.
|
||||
2. **Git 프로토콜 지원**: HTTP를 통해 `git clone` 가능하도록 설정 (추가 설정 필요).
|
||||
|
||||
#### (1) 저장소 생성 및 ZIP 다운로드 방식
|
||||
```java
|
||||
package com.example.gitserver.controller;
|
||||
|
||||
import com.example.gitserver.service.GitService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/git")
|
||||
public class GitController {
|
||||
|
||||
@Autowired
|
||||
private GitService gitService;
|
||||
|
||||
// 저장소 생성
|
||||
@PostMapping("/create/{repoName}")
|
||||
public ResponseEntity<String> createRepository(@PathVariable String repoName) {
|
||||
try {
|
||||
gitService.createRepository(repoName);
|
||||
return ResponseEntity.ok("Repository '" + repoName + "' created successfully");
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(500).body("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 저장소 다운로드 (ZIP 형식)
|
||||
@GetMapping("/download/{repoName}")
|
||||
public ResponseEntity<Resource> downloadRepository(@PathVariable String repoName) throws Exception {
|
||||
File repoDir = gitService.getRepository(repoName);
|
||||
if (!repoDir.exists()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// .git 디렉토리만 압축
|
||||
File gitDir = new File(repoDir, ".git");
|
||||
Path zipPath = Files.createTempFile(repoName, ".zip");
|
||||
try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath))) {
|
||||
zipDirectory(gitDir, gitDir.getName(), zos);
|
||||
}
|
||||
|
||||
Resource resource = new FileSystemResource(zipPath.toFile());
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + repoName + ".zip")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(resource);
|
||||
}
|
||||
|
||||
// 디렉토리를 ZIP으로 압축하는 헬퍼 메서드
|
||||
private void zipDirectory(File folder, String parentFolder, ZipOutputStream zos) throws IOException {
|
||||
for (File file : folder.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
zipDirectory(file, parentFolder + "/" + file.getName(), zos);
|
||||
continue;
|
||||
}
|
||||
ZipEntry ze = new ZipEntry(parentFolder + "/" + file.getName());
|
||||
zos.putNextEntry(ze);
|
||||
Files.copy(file.toPath(), zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### (2) 실행 및 테스트
|
||||
- 애플리케이션 실행 후:
|
||||
- `POST /git/create/myrepo`: 저장소 생성.
|
||||
- `GET /git/download/myrepo`: `.git` 디렉토리를 ZIP으로 다운로드.
|
||||
- 클라이언트는 다운로드한 ZIP을 풀고 `git clone --bare`로 로컬 저장소로 변환 가능.
|
||||
|
||||
---
|
||||
|
||||
### 4. **HTTP를 통한 Git 프로토콜 제공 (고급)**
|
||||
ZIP 다운로드 대신 클라이언트가 `git clone https://your-server/git/myrepo`로 직접 클론하도록 하려면 Git HTTP 백엔드를 구현해야 합니다. JGit은 기본적으로 Git 서버 프로토콜을 제공하지 않으므로 추가 설정이 필요합니다.
|
||||
|
||||
#### (1) JGit Servlet 사용
|
||||
JGit은 `GitServlet`을 제공해 HTTP 기반 Git 프로토콜을 지원합니다.
|
||||
|
||||
- **의존성 추가**: JGit HTTP 서버 의존성 추가.
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit.http.server</artifactId>
|
||||
<version>6.8.0.202311291450-r</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
- **Servlet 설정**:
|
||||
```java
|
||||
package com.example.gitserver.config;
|
||||
|
||||
import org.eclipse.jgit.http.server.GitServlet;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import java.io.File;
|
||||
|
||||
@Configuration
|
||||
public class GitServletConfig {
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean<Servlet> gitServlet() {
|
||||
GitServlet gitServlet = new GitServlet();
|
||||
gitServlet.setRepositoryResolver((req, name) -> {
|
||||
File repoDir = new File("./repos/" + name + "/.git");
|
||||
if (!repoDir.exists()) {
|
||||
throw new IllegalStateException("Repository not found: " + name);
|
||||
}
|
||||
return new org.eclipse.jgit.internal.storage.file.FileRepository(repoDir);
|
||||
});
|
||||
|
||||
ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(gitServlet, "/git/*");
|
||||
bean.setLoadOnStartup(1);
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **저장소 준비**: 저장소는 **bare repository** 형식이어야 합니다.
|
||||
```java
|
||||
public File createBareRepository(String repoName) throws GitAPIException {
|
||||
File repoDir = new File("./repos/" + repoName);
|
||||
if (!repoDir.exists()) {
|
||||
repoDir.mkdirs();
|
||||
}
|
||||
try (Git git = Git.init().setDirectory(repoDir).setBare(true).call()) {
|
||||
System.out.println("Bare repository created: " + repoName);
|
||||
}
|
||||
return repoDir;
|
||||
}
|
||||
```
|
||||
|
||||
- **컨트롤러 수정**:
|
||||
```java
|
||||
@PostMapping("/create-bare/{repoName}")
|
||||
public ResponseEntity<String> createBareRepository(@PathVariable String repoName) {
|
||||
try {
|
||||
gitService.createBareRepository(repoName);
|
||||
return ResponseEntity.ok("Bare repository '" + repoName + "' created successfully");
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(500).body("Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### (2) 클라이언트에서 클론
|
||||
- 서버가 `http://localhost:8080`에서 실행 중이라면:
|
||||
```bash
|
||||
git clone http://localhost:8080/git/myrepo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. **추가 고려사항**
|
||||
- **보안**: 인증(예: Spring Security)을 추가해 저장소 접근을 제어.
|
||||
- **저장소 경로**: `./repos/`는 서버의 파일 시스템에 저장되므로, 실제 배포 시 경로를 환경 변수로 관리.
|
||||
- **성능**: 대규모 저장소의 경우 JGit 대신 네이티브 Git 서버(GitLab, Gitea 등)를 고려.
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
- **ZIP 방식**: 간단히 `.git` 디렉토리를 제공하며, 클라이언트가 수동으로 처리.
|
||||
- **Git 프로토콜 방식**: `GitServlet`을 사용해 HTTP로 직접 클론 가능하지만 설정이 복잡.
|
||||
필요에 따라 두 방식 중 하나를 선택하거나 혼합해 사용할 수 있습니다. 추가 질문이나 특정 기능 확장이 필요하면 말씀해주세요!
|
||||
359
docs/junit.md
Normal file
359
docs/junit.md
Normal file
@@ -0,0 +1,359 @@
|
||||
자바의 JUnit에 대해 설명하는 글을 작성하겠습니다. JUnit은 자바에서 단위 테스트(Unit Test)를 작성하고 실행하기 위한 가장 널리 사용되는 프레임워크입니다. 특히 JUnit 5는 모듈화된 구조와 다양한 기능을 제공하여 테스트 작성의 유연성과 가독성을 높여줍니다. JUnit은 어노테이션을 통해 테스트의 생명주기와 동작을 제어하며, 이를 통해 개발자는 테스트 코드를 간결하고 명확하게 작성할 수 있습니다.
|
||||
|
||||
아래에서는 JUnit 5의 주요 어노테이션을 표로 정리한 뒤, 각 어노테이션에 대한 설명과 예시를 상세히 다루겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### JUnit 5 주요 어노테이션 표
|
||||
|
||||
| 어노테이션 | 설명 |
|
||||
|----|----|
|
||||
| `@Test` | 해당 메서드가 테스트 메서드임을 나타냄 |
|
||||
| `@BeforeAll` | 모든 테스트 메서드 실행 전에 한 번만 실행되는 초기화 메서드 (static 필요) |
|
||||
| `@AfterAll` | 모든 테스트 메서드 실행 후에 한 번만 실행되는 정리 메서드 (static 필요) |
|
||||
| `@BeforeEach` | 각 테스트 메서드 실행 전에 실행되는 초기화 메서드 |
|
||||
| `@AfterEach` | 각 테스트 메서드 실행 후에 실행되는 정리 메서드 |
|
||||
| `@Disabled` | 테스트 클래스나 메서드를 비활성화하여 실행하지 않음 |
|
||||
| `@DisplayName` | 테스트 클래스나 메서드에 사용자 정의 이름을 지정 |
|
||||
| `@ParameterizedTest` | 동일한 테스트를 여러 입력값으로 반복 실행 |
|
||||
| `@ValueSource` | `@ParameterizedTest`와 함께 사용되며, 단일 값의 배열을 제공 |
|
||||
| `@CsvSource` | `@ParameterizedTest`와 함께 사용되며, CSV 형식의 데이터를 제공 |
|
||||
| `@MethodSource` | `@ParameterizedTest`와 함께 사용되며, 메서드에서 제공되는 데이터를 사용 |
|
||||
| `@RepeatedTest` | 테스트를 지정된 횟수만큼 반복 실행 |
|
||||
| `@Tag` | 테스트에 태그를 지정하여 필터링 가능 |
|
||||
| `@Nested` | 테스트 클래스 내에 중첩된 테스트 클래스를 정의 |
|
||||
| `@Timeout` | 테스트 실행 시간이 지정된 시간을 초과하면 실패 처리 |
|
||||
| `@ExtendWith` | 사용자 정의 확장을 등록하여 테스트 동작을 커스터마이징 |
|
||||
|
||||
---
|
||||
|
||||
### 각 어노테이션 상세 설명 및 예시
|
||||
|
||||
#### 프로젝트 설정
|
||||
JUnit 5를 사용하려면 `pom.xml`에 다음 의존성을 추가해야 합니다:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 1. `@Test`
|
||||
- **설명**: 단위 테스트 메서드를 정의합니다. 별도의 설정 없이 실행됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CalculatorTest {
|
||||
@Test
|
||||
void testAddition() {
|
||||
Calculator calc = new Calculator();
|
||||
assertEquals(4, calc.add(2, 2), "2 + 2는 4여야 합니다.");
|
||||
}
|
||||
}
|
||||
|
||||
class Calculator {
|
||||
int add(int a, int b) { return a + b; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `@BeforeAll`
|
||||
- **설명**: 모든 테스트 전에 한 번만 실행되며, static 메서드여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class DatabaseTest {
|
||||
@BeforeAll
|
||||
static void initDatabase() {
|
||||
System.out.println("데이터베이스 초기화");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQuery() {
|
||||
System.out.println("쿼리 테스트");
|
||||
}
|
||||
}
|
||||
// 출력: "데이터베이스 초기화" -> "쿼리 테스트"
|
||||
```
|
||||
|
||||
#### 3. `@AfterAll`
|
||||
- **설명**: 모든 테스트 후에 한 번만 실행되며, static 메서드여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class DatabaseTest {
|
||||
@AfterAll
|
||||
static void closeDatabase() {
|
||||
System.out.println("데이터베이스 종료");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQuery() {
|
||||
System.out.println("쿼리 테스트");
|
||||
}
|
||||
}
|
||||
// 출력: "쿼리 테스트" -> "데이터베이스 종료"
|
||||
```
|
||||
|
||||
#### 4. `@BeforeEach`
|
||||
- **설명**: 각 테스트 메서드 실행 전에 실행됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class UserTest {
|
||||
private User user;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
user = new User("홍길동");
|
||||
System.out.println("유저 설정");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testName() {
|
||||
System.out.println("이름 테스트: " + user.getName());
|
||||
}
|
||||
}
|
||||
// 출력: "유저 설정" -> "이름 테스트: 홍길동"
|
||||
```
|
||||
|
||||
#### 5. `@AfterEach`
|
||||
- **설명**: 각 테스트 메서드 실행 후에 실행됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class UserTest {
|
||||
private User user;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
user = null;
|
||||
System.out.println("유저 정리");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testName() {
|
||||
user = new User("김영희");
|
||||
System.out.println("이름 테스트");
|
||||
}
|
||||
}
|
||||
// 출력: "이름 테스트" -> "유저 정리"
|
||||
```
|
||||
|
||||
#### 6. `@Disabled`
|
||||
- **설명**: 테스트를 비활성화하여 실행되지 않게 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class FeatureTest {
|
||||
@Disabled("아직 구현되지 않음")
|
||||
@Test
|
||||
void testFeature() {
|
||||
System.out.println("이 테스트는 실행되지 않습니다.");
|
||||
}
|
||||
}
|
||||
// 출력: 없음
|
||||
```
|
||||
|
||||
#### 7. `@DisplayName`
|
||||
- **설명**: 테스트에 사용자 친화적인 이름을 붙입니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class MathTest {
|
||||
@Test
|
||||
@DisplayName("덧셈 테스트")
|
||||
void testAdd() {
|
||||
assertEquals(5, 2 + 3);
|
||||
}
|
||||
}
|
||||
// 테스트 결과에 "덧셈 테스트"로 표시됨
|
||||
```
|
||||
|
||||
#### 8. `@ParameterizedTest`
|
||||
- **설명**: 동일한 테스트를 여러 입력값으로 실행합니다.
|
||||
- **예시** (with `@ValueSource`):
|
||||
```java
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class NumberTest {
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {2, 4, 6})
|
||||
void testEvenNumbers(int number) {
|
||||
assertTrue(number % 2 == 0, number + "는 짝수여야 합니다.");
|
||||
}
|
||||
}
|
||||
// 2, 4, 6 각각에 대해 테스트 실행
|
||||
```
|
||||
|
||||
#### 9. `@CsvSource`
|
||||
- **설명**: CSV 형식의 데이터를 제공하여 여러 파라미터로 테스트합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class MathTest {
|
||||
@ParameterizedTest
|
||||
@CsvSource({"1, 2, 3", "4, 5, 9"})
|
||||
void testAddition(int a, int b, int expected) {
|
||||
assertEquals(expected, a + b);
|
||||
}
|
||||
}
|
||||
// (1, 2, 3), (4, 5, 9)로 테스트 실행
|
||||
```
|
||||
|
||||
#### 10. `@MethodSource`
|
||||
- **설명**: 사용자 정의 메서드에서 테스트 데이터를 제공합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class StringTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideStrings")
|
||||
void testLength(String input, int expected) {
|
||||
assertEquals(expected, input.length());
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("hello", 5),
|
||||
Arguments.of("world", 5)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 11. `@RepeatedTest`
|
||||
- **설명**: 테스트를 지정된 횟수만큼 반복 실행합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
|
||||
public class RandomTest {
|
||||
@RepeatedTest(3)
|
||||
void testRandom() {
|
||||
System.out.println("랜덤 테스트: " + Math.random());
|
||||
}
|
||||
}
|
||||
// 출력: 3번 실행, 각각 다른 랜덤 값
|
||||
```
|
||||
|
||||
#### 12. `@Tag`
|
||||
- **설명**: 테스트에 태그를 지정하여 특정 테스트만 실행할 수 있습니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TaggedTest {
|
||||
@Test
|
||||
@Tag("fast")
|
||||
void testFast() {
|
||||
System.out.println("빠른 테스트");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("slow")
|
||||
void testSlow() {
|
||||
System.out.println("느린 테스트");
|
||||
}
|
||||
}
|
||||
// IDE나 빌드 도구에서 "fast" 태그만 실행 가능
|
||||
```
|
||||
|
||||
#### 13. `@Nested`
|
||||
- **설명**: 테스트 클래스 내에 중첩된 테스트를 정의하여 계층적 테스트를 구성합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class OuterTest {
|
||||
@Test
|
||||
void outerTest() {
|
||||
System.out.println("외부 테스트");
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InnerTest {
|
||||
@Test
|
||||
void innerTest() {
|
||||
System.out.println("내부 테스트");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 14. `@Timeout`
|
||||
- **설명**: 테스트가 지정된 시간을 초과하면 실패 처리합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TimeoutTest {
|
||||
@Test
|
||||
@Timeout(value = 1, unit = TimeUnit.SECONDS)
|
||||
void testTimeout() throws InterruptedException {
|
||||
Thread.sleep(2000); // 2초 대기 -> 실패
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 15. `@ExtendWith`
|
||||
- **설명**: 사용자 정의 확장을 등록하여 테스트 동작을 확장합니다.
|
||||
- **예시** (간단한 확장 예시):
|
||||
```java
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
|
||||
|
||||
@ExtendWith(CustomExtension.class)
|
||||
public class ExtensionTest {
|
||||
@Test
|
||||
void testWithExtension() {
|
||||
System.out.println("확장 테스트");
|
||||
}
|
||||
}
|
||||
|
||||
class CustomExtension implements TestExecutionExceptionHandler {
|
||||
@Override
|
||||
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) {
|
||||
System.out.println("예외 처리: " + throwable.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
JUnit 5는 다양한 어노테이션을 통해 테스트의 생명주기 관리, 반복 실행, 데이터 기반 테스트, 태깅 등 강력한 기능을 제공합니다. 이를 활용하면 코드 품질을 높이고, 유지보수성을 개선할 수 있습니다. 특히 `@ParameterizedTest`와 `@Nested` 같은 기능은 복잡한 테스트 시나리오를 체계적으로 관리하는 데 유용합니다. 프로젝트에서 JUnit을 사용할 때는 팀원 모두가 어노테이션의 역할과 사용법을 이해하고, 지속적인 통합(CI) 환경에서 테스트를 자동화하는 것이 좋습니다.
|
||||
|
||||
추가 질문이 있다면 언제든 물어보세요!
|
||||
267
docs/lombok.md
Normal file
267
docs/lombok.md
Normal file
@@ -0,0 +1,267 @@
|
||||
자바의 Lombok에 대해 설명하는 글을 작성하겠습니다. Lombok은 자바에서 반복적인 코드를 줄여주는 라이브러리로, 어노테이션을 활용해 Boilerplate 코드(예: Getter, Setter, 생성자 등)를 자동으로 생성해줍니다. 이를 통해 코드 가독성을 높이고 개발 생산성을 향상시킬 수 있습니다.
|
||||
|
||||
먼저, Lombok에서 제공하는 주요 어노테이션을 표 형식으로 정리한 뒤, 각 어노테이션에 대해 설명과 예시를 상세히 다루겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### Lombok 주요 어노테이션 표
|
||||
|
||||
| 어노테이션 | 설명 |
|
||||
|-----------------------|----------------------------------------------------------------------|
|
||||
| `@Getter` | 필드에 대해 Getter 메서드를 자동 생성 |
|
||||
| `@Setter` | 필드에 대해 Setter 메서드를 자동 생성 |
|
||||
| `@ToString` | 클래스의 toString() 메서드를 자동 생성 |
|
||||
| `@EqualsAndHashCode` | equals()와 hashCode() 메서드를 자동 생성 |
|
||||
| `@NoArgsConstructor` | 매개변수가 없는 기본 생성자를 자동 생성 |
|
||||
| `@AllArgsConstructor`| 모든 필드를 매개변수로 받는 생성자를 자동 생성 |
|
||||
| `@RequiredArgsConstructor` | `@NonNull`이 붙은 필드나 final 필드만 매개변수로 받는 생성자를 생성 |
|
||||
| `@Data` | `@Getter`, `@Setter`, `@ToString`, `@EqualsAndHashCode`, `@RequiredArgsConstructor`를 포함 |
|
||||
| `@Builder` | 빌더 패턴을 구현한 코드를 자동 생성 |
|
||||
| `@Slf4j` | SLF4J 로깅 프레임워크를 위한 Logger 객체를 자동 생성 |
|
||||
| `@NonNull` | 필드나 매개변수가 null이 될 수 없음을 명시 |
|
||||
|
||||
---
|
||||
|
||||
### 각 어노테이션 상세 설명 및 예시
|
||||
|
||||
#### 1. `@Getter`
|
||||
- **설명**: 각 필드에 대해 Getter 메서드를 자동으로 생성합니다. 클래스의 모든 필드에 적용하려면 클래스 레벨에 붙일 수도 있습니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class Person {
|
||||
private String name = "홍길동";
|
||||
private int age = 30;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person();
|
||||
System.out.println(person.getName()); // "홍길동" 출력
|
||||
System.out.println(person.getAge()); // 30 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `@Setter`
|
||||
- **설명**: 각 필드에 대해 Setter 메서드를 생성합니다. 클래스 레벨에서도 사용 가능하며, 특정 필드에만 적용하려면 필드 위에 붙입니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.Setter;
|
||||
|
||||
@Setter
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person();
|
||||
person.setName("김영희");
|
||||
person.setAge(25);
|
||||
System.out.println(person); // toString 없으므로 객체 참조 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. `@ToString`
|
||||
- **설명**: 클래스의 필드를 기반으로 `toString()` 메서드를 생성합니다. 기본적으로 모든 필드를 포함하며, 제외하고 싶은 필드는 `@ToString.Exclude`를 사용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
public class Person {
|
||||
private String name = "이철수";
|
||||
private int age = 40;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person();
|
||||
System.out.println(person); // Person(name=이철수, age=40) 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. `@EqualsAndHashCode`
|
||||
- **설명**: `equals()`와 `hashCode()` 메서드를 필드 기반으로 생성합니다. 특정 필드를 제외하려면 `@EqualsAndHashCode.Exclude`를 사용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class Person {
|
||||
private String name = "박민수";
|
||||
private int age = 28;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person p1 = new Person();
|
||||
Person p2 = new Person();
|
||||
System.out.println(p1.equals(p2)); // true 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. `@NoArgsConstructor`
|
||||
- **설명**: 매개변수가 없는 기본 생성자를 생성합니다. 주로 JPA나 프레임워크에서 요구될 때 사용됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
public class Person {
|
||||
private String name = "최수진";
|
||||
private int age;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person(); // 기본 생성자 호출
|
||||
System.out.println(person.name); // "최수진" 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6. `@AllArgsConstructor`
|
||||
- **설명**: 모든 필드를 매개변수로 받는 생성자를 생성합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person("김지영", 33);
|
||||
System.out.println(person.name); // "김지영" 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. `@RequiredArgsConstructor`
|
||||
- **설명**: `@NonNull`이 붙은 필드나 `final` 필드만 포함한 생성자를 생성합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class Person {
|
||||
@NonNull private String name;
|
||||
private int age; // 포함되지 않음
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person("윤서진"); // name만 요구됨
|
||||
System.out.println(person.name); // "윤서진" 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 8. `@Data`
|
||||
- **설명**: `@Getter`, `@Setter`, `@ToString`, `@EqualsAndHashCode`, `@RequiredArgsConstructor`를 모두 포함하는 편리한 어노테이션입니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person();
|
||||
person.setName("이정훈");
|
||||
person.setAge(29);
|
||||
System.out.println(person); // Person(name=이정훈, age=29) 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 9. `@Builder`
|
||||
- **설명**: 빌더 패턴을 구현한 코드를 생성합니다. 객체를 유연하게 생성할 때 유용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.Builder;
|
||||
|
||||
@Builder
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = Person.builder()
|
||||
.name("최영미")
|
||||
.age(35)
|
||||
.build();
|
||||
System.out.println(person.name); // "최영미" 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 10. `@Slf4j`
|
||||
- **설명**: SLF4J 기반의 Logger 객체를 자동 생성합니다. 로깅 코드를 간소화할 수 있습니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class Person {
|
||||
private String name = "김태양";
|
||||
|
||||
public void printName() {
|
||||
log.info("Name: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person();
|
||||
person.printName(); // INFO: Name: 김태양 출력
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 11. `@NonNull`
|
||||
- **설명**: 필드나 매개변수가 null이 될 수 없음을 명시하며, null 체크 코드를 생성합니다. 주로 `@RequiredArgsConstructor`와 함께 사용됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import lombok.NonNull;
|
||||
|
||||
public class Person {
|
||||
private final String name;
|
||||
|
||||
public Person(@NonNull String name) {
|
||||
this.name = name; // null이면 예외 발생
|
||||
}
|
||||
}
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Person person = new Person("박준영"); // 정상 동작
|
||||
// Person nullPerson = new Person(null); // NullPointerException 발생
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
Lombok은 자바 개발에서 반복적인 코드를 줄이고, 가독성과 유지보수성을 높이는 데 큰 도움을 줍니다. 위 어노테이션들을 적절히 활용하면 생산성을 크게 향상시킬 수 있습니다. 단, 프로젝트에서 Lombok을 사용할 때는 팀원 모두가 이를 이해하고 IDE 설정(플러그인 설치 등)이 제대로 되어 있어야 원활한 협업이 가능합니다.
|
||||
|
||||
궁금한 점이 더 있다면 언제든 물어보세요!
|
||||
200
docs/nio.md
Normal file
200
docs/nio.md
Normal file
@@ -0,0 +1,200 @@
|
||||
## 자바 NIO (New Input/Output) 관련 클래스 및 주요 메서드 정리
|
||||
|
||||
자바 NIO는 기존의 `java.io`보다 **더 빠르고 효율적인 비동기식 입출력 처리**를 제공하는 API다.
|
||||
NIO의 핵심 개념은 **버퍼(Buffer), 채널(Channel), 셀렉터(Selector)**이다.
|
||||
|
||||
---
|
||||
|
||||
### 1. `Buffer` 클래스 및 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|-----------------------------|
|
||||
| `allocate(int capacity)` | 지정된 크기의 버퍼 생성 (`ByteBuffer.allocate(1024)`) |
|
||||
| `wrap(byte[] array)` | 기존 배열을 감싸는 버퍼 생성 |
|
||||
| `put(T value)` | 버퍼에 데이터 저장 |
|
||||
| `get()` | 버퍼에서 데이터 읽기 |
|
||||
| `flip()` | 읽기 모드로 전환 (쓰기 → 읽기) |
|
||||
| `clear()` | 버퍼를 초기화 (데이터 삭제 X, 포인터 리셋) |
|
||||
| `compact()` | 읽지 않은 데이터를 앞으로 이동하고, 쓰기 모드로 전환 |
|
||||
| `position()` | 현재 읽기/쓰기 위치 반환 |
|
||||
| `limit()` | 읽기/쓰기 가능한 최대 위치 반환 |
|
||||
| `remaining()` | 남은 읽기/쓰기 가능 데이터 개수 반환 |
|
||||
|
||||
**버퍼의 주요 종류**:
|
||||
- `ByteBuffer` (바이트 저장)
|
||||
- `CharBuffer` (문자 저장)
|
||||
- `IntBuffer`, `FloatBuffer` 등
|
||||
|
||||
---
|
||||
|
||||
### 2. `Channel` 인터페이스 및 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|-----------------------------------|
|
||||
| `open(Path path, OpenOption...)` | 파일 채널 열기 (`FileChannel.open(path)`) |
|
||||
| `read(ByteBuffer dst)` | 데이터를 버퍼로 읽음 |
|
||||
| `write(ByteBuffer src)` | 버퍼의 데이터를 채널에 씀 |
|
||||
| `close()` | 채널 닫기 |
|
||||
| `position()` | 현재 위치 반환 |
|
||||
| `size()` | 파일 크기 반환 |
|
||||
| `truncate(long size)` | 파일 크기를 지정한 크기로 자름 |
|
||||
| `force(boolean metaData)` | 버퍼 내용을 강제로 디스크에 저장 |
|
||||
|
||||
**채널의 주요 종류**:
|
||||
- `FileChannel` (파일 입출력)
|
||||
- `SocketChannel` (TCP 소켓 통신)
|
||||
- `ServerSocketChannel` (TCP 서버 소켓)
|
||||
- `DatagramChannel` (UDP 통신)
|
||||
|
||||
---
|
||||
|
||||
### 3. `Selector` 클래스 및 주요 메서드
|
||||
|
||||
| 메서드 | 설명 |
|
||||
|--------|-----------------------------|
|
||||
| `open()` | 새로운 셀렉터 생성 |
|
||||
| `select()` | I/O 이벤트가 발생할 때까지 대기 |
|
||||
| `selectNow()` | 즉시 이벤트 확인 (블로킹 X) |
|
||||
| `select(timeout)` | 지정된 시간 동안 대기 |
|
||||
| `keys()` | 등록된 모든 채널 반환 |
|
||||
| `selectedKeys()` | 이벤트가 발생한 채널 반환 |
|
||||
| `wakeup()` | 블로킹 상태에서 셀렉터를 깨움 |
|
||||
| `close()` | 셀렉터 닫기 |
|
||||
|
||||
**관련 클래스**:
|
||||
- `SelectionKey.OP_READ` (읽기 가능)
|
||||
- `SelectionKey.OP_WRITE` (쓰기 가능)
|
||||
- `SelectionKey.OP_CONNECT` (연결 가능)
|
||||
- `SelectionKey.OP_ACCEPT` (새로운 연결 가능)
|
||||
|
||||
---
|
||||
|
||||
## 자바 NIO 쉽게 설명하기
|
||||
|
||||
### NIO란 무엇인가?
|
||||
NIO는 기존의 `java.io`보다 **더 빠르고 비동기적으로 입출력을 처리**할 수 있는 기술이다.
|
||||
|
||||
- **기존 방식 (IO)**
|
||||
- 데이터를 **스트림(Stream)** 단위로 처리
|
||||
- 블로킹 방식 (데이터를 읽거나 쓸 때 작업이 끝날 때까지 대기)
|
||||
- 한 번에 하나의 입출력만 가능
|
||||
|
||||
- **NIO 방식**
|
||||
- 데이터를 **버퍼(Buffer)와 채널(Channel)**을 이용해 처리
|
||||
- 논블로킹 방식 (데이터를 읽거나 쓸 때 대기하지 않고 바로 진행)
|
||||
- 하나의 스레드가 여러 채널을 관리 가능 (셀렉터 사용)
|
||||
|
||||
---
|
||||
|
||||
### 1. **버퍼(Buffer) 이해하기**
|
||||
버퍼는 **데이터를 담아두는 공간**이다.
|
||||
IO에서는 데이터를 바로 읽고 쓰지만, **NIO에서는 데이터를 버퍼에 담고 처리**한다.
|
||||
|
||||
```java
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024); // 1KB 크기의 버퍼 생성
|
||||
buffer.put("Hello NIO".getBytes()); // 데이터 쓰기
|
||||
|
||||
buffer.flip(); // 읽기 모드로 변경
|
||||
byte[] data = new byte[buffer.remaining()];
|
||||
buffer.get(data); // 데이터 읽기
|
||||
System.out.println(new String(data)); // "Hello NIO"
|
||||
```
|
||||
- `put()` → 데이터를 버퍼에 저장
|
||||
- `flip()` → 읽기 모드로 변경
|
||||
- `get()` → 버퍼에서 데이터 읽기
|
||||
|
||||
---
|
||||
|
||||
### 2. **채널(Channel) 이해하기**
|
||||
채널은 **데이터가 오가는 통로**다.
|
||||
기존의 `InputStream` / `OutputStream` 대신 **파일, 소켓 등과 데이터를 주고받는 역할**을 한다.
|
||||
|
||||
#### **파일을 읽는 예제 (FileChannel)**
|
||||
```java
|
||||
Path path = Paths.get("example.txt");
|
||||
FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
channel.read(buffer); // 파일에서 데이터를 읽어 버퍼에 저장
|
||||
|
||||
buffer.flip();
|
||||
byte[] data = new byte[buffer.remaining()];
|
||||
buffer.get(data);
|
||||
|
||||
System.out.println(new String(data)); // 파일 내용 출력
|
||||
channel.close();
|
||||
```
|
||||
- `FileChannel.open()` → 파일을 열고 채널 생성
|
||||
- `read(ByteBuffer)` → 파일 데이터를 버퍼에 읽기
|
||||
- `flip()` → 읽기 모드로 변경
|
||||
- `get()` → 버퍼에서 데이터 가져오기
|
||||
|
||||
---
|
||||
|
||||
### 3. **논블로킹 소켓 (SocketChannel) 사용하기**
|
||||
NIO의 강점은 **네트워크 프로그래밍에서 논블로킹 소켓**을 사용할 수 있다는 점이다.
|
||||
즉, 하나의 스레드가 여러 소켓을 동시에 처리할 수 있다.
|
||||
|
||||
#### **비동기 TCP 클라이언트 예제**
|
||||
```java
|
||||
SocketChannel socketChannel = SocketChannel.open();
|
||||
socketChannel.configureBlocking(false); // 논블로킹 모드 설정
|
||||
socketChannel.connect(new InetSocketAddress("localhost", 8080));
|
||||
|
||||
while (!socketChannel.finishConnect()) {
|
||||
System.out.println("연결 시도 중...");
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap("Hello Server".getBytes());
|
||||
socketChannel.write(buffer); // 서버로 데이터 전송
|
||||
|
||||
socketChannel.close();
|
||||
```
|
||||
- `configureBlocking(false)` → 논블로킹 모드 설정
|
||||
- `connect()` → 서버에 연결
|
||||
- `write(ByteBuffer)` → 서버로 데이터 전송
|
||||
|
||||
---
|
||||
|
||||
### 4. **Selector로 다중 채널 관리하기**
|
||||
셀렉터(Selector)를 사용하면 **하나의 스레드로 여러 채널을 관리**할 수 있다.
|
||||
즉, 효율적인 **멀티플렉싱** 처리가 가능하다.
|
||||
|
||||
#### **비동기 서버 예제**
|
||||
```java
|
||||
Selector selector = Selector.open();
|
||||
ServerSocketChannel serverChannel = ServerSocketChannel.open();
|
||||
serverChannel.bind(new InetSocketAddress(8080));
|
||||
serverChannel.configureBlocking(false);
|
||||
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
|
||||
while (true) {
|
||||
selector.select(); // I/O 이벤트 발생할 때까지 대기
|
||||
|
||||
for (SelectionKey key : selector.selectedKeys()) {
|
||||
if (key.isAcceptable()) { // 클라이언트 연결 요청 처리
|
||||
SocketChannel client = serverChannel.accept();
|
||||
client.configureBlocking(false);
|
||||
client.register(selector, SelectionKey.OP_READ);
|
||||
} else if (key.isReadable()) { // 데이터 읽기 처리
|
||||
SocketChannel client = (SocketChannel) key.channel();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
client.read(buffer);
|
||||
buffer.flip();
|
||||
System.out.println("Received: " + new String(buffer.array()).trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- `Selector.open()` → 셀렉터 생성
|
||||
- `select()` → I/O 이벤트 발생 대기
|
||||
- `register()` → 채널을 셀렉터에 등록
|
||||
|
||||
---
|
||||
|
||||
### 정리
|
||||
자바 NIO는 **버퍼 + 채널 + 셀렉터**를 이용해
|
||||
1. **파일 입출력을 빠르게 처리**하고
|
||||
2. **네트워크 소켓을 논블로킹 방식으로 다룰 수 있게 해준다.**
|
||||
|
||||
**즉, 적은 스레드로 많은 연결을 처리하는 고성능 서버를 만들 때 강력한 도구가 된다!**
|
||||
263
docs/security/bouncy castle.md
Normal file
263
docs/security/bouncy castle.md
Normal file
@@ -0,0 +1,263 @@
|
||||
자바의 **Java Security API**(JCA 및 JCE 포함)는 암호화, 인증, 키 관리 등 보안 기능을 제공하는 다양한 클래스를 포함하고 있습니다. 여기서는 자바 시큐리티에서 자주 사용되는 주요 클래스와 그 역할에 대해 설명하고, 각 클래스의 주요 메서드를 표로 정리하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### **주요 클래스와 역할**
|
||||
1. **`java.security.MessageDigest`**
|
||||
- **역할**: 해시 함수(MD5, SHA-1, SHA-256 등)를 사용하여 데이터의 메시지 다이제스트(해시 값)를 생성. 데이터 무결성 확인에 사용.
|
||||
- **특징**: 단방향 함수로, 입력 데이터를 고정된 길이의 해시로 변환.
|
||||
|
||||
2. **`javax.crypto.Cipher`**
|
||||
- **역할**: 대칭키(AES) 및 비대칭키(RSA) 암호화/복호화 작업을 수행.
|
||||
- **특징**: 운용 모드(예: CBC, GCM)와 패딩(예: PKCS5Padding)을 지정 가능.
|
||||
|
||||
3. **`java.security.KeyPairGenerator`**
|
||||
- **역할**: 비대칭키 알고리즘(RSA, DSA, EC 등)의 공개키와 개인키 쌍을 생성.
|
||||
- **특징**: 키 크기(예: 2048비트)를 지정하여 생성.
|
||||
|
||||
4. **`javax.crypto.KeyGenerator`**
|
||||
- **역할**: 대칭키 알고리즘(AES, DES 등)의 키를 생성.
|
||||
- **특징**: 고정된 길이의 무작위 키 생성.
|
||||
|
||||
5. **`javax.crypto.SecretKeyFactory`**
|
||||
- **역할**: 패스워드 기반 키 생성(PBKDF2) 또는 키 변환 작업을 수행.
|
||||
- **특징**: `SecretKey` 객체를 생성하거나 키 사양(KeySpec)을 변환.
|
||||
|
||||
6. **`java.security.SecureRandom`**
|
||||
- **역할**: 암호화에 적합한 고품질의 난수 생성.
|
||||
- **특징**: 일반 `Random`보다 안전성이 높음.
|
||||
|
||||
7. **`java.security.KeyStore`**
|
||||
- **역할**: 키와 인증서를 저장하고 관리하는 저장소.
|
||||
- **특징**: JKS, PKCS12 등 다양한 형식 지원.
|
||||
|
||||
---
|
||||
|
||||
### **각 클래스의 주요 메서드**
|
||||
#### **1. `MessageDigest`**
|
||||
| **메서드** | **설명** |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `static getInstance(String algorithm)` | 지정된 해시 알고리즘(MD5, SHA-256 등)의 인스턴스를 생성. |
|
||||
| `update(byte[] input)` | 해시 계산에 사용할 입력 데이터를 추가. |
|
||||
| `digest()` | 입력 데이터를 해시로 변환하여 바이트 배열로 반환. |
|
||||
| `reset()` | 인스턴스를 초기 상태로 재설정. |
|
||||
|
||||
#### **2. `Cipher`**
|
||||
| **메서드** | **설명** |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `static getInstance(String transformation)` | 암호화 알고리즘, 모드, 패딩(예: "AES/GCM/NoPadding")을 지정하여 인스턴스 생성. |
|
||||
| `init(int opmode, Key key, AlgorithmParameterSpec params)` | 암호화(ENCRYPT_MODE) 또는 복호화(DECRYPT_MODE) 모드로 초기화. 매개변수(IV 등) 지정 가능. |
|
||||
| `update(byte[] input)` | 암호화/복호화할 데이터를 추가(스트리밍 처리 시 사용). |
|
||||
| `doFinal(byte[] input)` | 암호화/복호화를 완료하고 결과를 반환. |
|
||||
| `updateAAD(byte[] aad)` | GCM 모드 등에서 인증용 추가 데이터(AAD)를 추가. |
|
||||
|
||||
#### **3. `KeyPairGenerator`**
|
||||
| **메서드** | **설명** |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `static getInstance(String algorithm)` | 키 쌍 생성기 인스턴스를 반환(예: "RSA", "EC"). |
|
||||
| `initialize(int keysize)` | 키 크기(예: 2048비트)를 지정하여 초기화. |
|
||||
| `generateKeyPair()` | 공개키와 개인키 쌍(`KeyPair`)을 생성하여 반환. |
|
||||
|
||||
#### **4. `KeyGenerator`**
|
||||
| **메서드** | **설명** |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `static getInstance(String algorithm)` | 대칭키 생성기 인스턴스를 반환(예: "AES"). |
|
||||
| `init(int keysize)` | 키 길이(예: 256비트)를 지정하여 초기화. |
|
||||
| `generateKey()` | 지정된 길이의 대칭키(`SecretKey`)를 생성하여 반환. |
|
||||
|
||||
#### **5. `SecretKeyFactory`**
|
||||
| **메서드** | **설명** |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `static getInstance(String algorithm)` | 키 팩토리 인스턴스를 반환(예: "PBKDF2WithHmacSHA256"). |
|
||||
| `generateSecret(KeySpec spec)` | 키 사양(예: `PBEKeySpec`)을 기반으로 `SecretKey`를 생성. |
|
||||
| `getKeySpec(SecretKey key, Class keySpec)` | 주어진 키를 특정 사양(예: `PBEKeySpec`)으로 변환. |
|
||||
|
||||
#### **6. `SecureRandom`**
|
||||
| **메서드** | **설명** |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `SecureRandom()` | 기본 알고리즘으로 안전한 난수 생성기 인스턴스를 생성. |
|
||||
| `nextBytes(byte[] bytes)` | 지정된 바이트 배열을 무작위 값으로 채움(예: IV, 솔트 생성에 사용). |
|
||||
| `setSeed(byte[] seed)` | 난수 생성기의 시드 값을 설정(보안상 권장되지 않음). |
|
||||
|
||||
#### **7. `KeyStore`**
|
||||
| **메서드** | **설명** |
|
||||
|---------------------------|--------------------------------------------------------------------------|
|
||||
| `static getInstance(String type)` | 키 저장소 인스턴스를 반환(예: "JKS", "PKCS12"). |
|
||||
| `load(InputStream stream, char[] password)` | 지정된 스트림에서 키 저장소를 로드하며, 비밀번호로 보호. |
|
||||
| `getKey(String alias, char[] password)` | 지정된 별칭에 해당하는 키를 반환. |
|
||||
| `setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)` | 키와 인증서를 저장소에 추가. |
|
||||
| `store(OutputStream stream, char[] password)` | 키 저장소를 지정된 스트림에 저장. |
|
||||
|
||||
---
|
||||
|
||||
### **클래스 간 연계 예제**
|
||||
아래는 `MessageDigest`, `Cipher`, `KeyGenerator`, `SecureRandom`을 함께 사용하는 AES-GCM 암호화 예제입니다.
|
||||
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class SecurityExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 1. 키 생성 (KeyGenerator)
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
SecretKey key = keyGen.generateKey();
|
||||
|
||||
// 2. IV 생성 (SecureRandom)
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] iv = new byte[12];
|
||||
random.nextBytes(iv);
|
||||
|
||||
// 3. 암호화 (Cipher)
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
byte[] plaintext = "Hello, Java Security!".getBytes();
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
|
||||
// 4. 해시 생성 (MessageDigest)
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(plaintext);
|
||||
|
||||
// 출력
|
||||
System.out.println("Ciphertext: " + Base64.getEncoder().encodeToString(ciphertext));
|
||||
System.out.println("Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **추가 참고**
|
||||
- **제공자(Provider)**: `Security.getProviders()`로 사용 가능한 제공자를 확인할 수 있으며, 기본 제공자는 SunJCE입니다. Bouncy Castle 같은 외부 제공자를 추가할 수도 있습니다.
|
||||
- **확장성**: JCA는 플러그인 구조로 설계되어 새로운 알고리즘을 쉽게 추가 가능.
|
||||
|
||||
각 클래스에 대해 더 깊은 설명이나 특정 메서드의 사용 예제가 필요하면 말씀해주세요!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
사용자가 "Bouncy Castle"에 대해 물었을 때, 문맥상 자바 보안과 암호화에 관련된 질문을 이어오셨으므로, 여기서는 **Bouncy Castle 암호화 라이브러리**(The Bouncy Castle Cryptography Library)에 대해 설명하겠습니다. 만약 실제 "바운시 캐슬"(어린이 놀이기구)을 의미한 것이라면, 말씀해 주시면 그에 맞춰 수정하겠습니다!
|
||||
|
||||
---
|
||||
|
||||
### **Bouncy Castle 암호화 라이브러리란?**
|
||||
Bouncy Castle은 자바(Java)와 C#(.NET)용으로 개발된 오픈소스 암호화 라이브러리로, 다양한 암호화 알고리즘과 보안 프로토콜을 구현할 수 있는 강력한 도구입니다. 이 라이브러리는 **Legion of the Bouncy Castle Inc.**라는 호주 기반의 등록된 자선 단체에서 개발 및 유지보수하며, 2000년 5월에 처음 출시되었습니다.
|
||||
|
||||
Bouncy Castle은 자바의 기본 JCE(Java Cryptography Extension)와 JCA(Java Cryptography Architecture)를 보완하거나 대체할 수 있으며, 경량 API와 JCE 제공자(Provider)라는 두 가지 주요 구성 요소로 설계되었습니다. 이를 통해 메모리 제약이 있는 환경(예: Java ME)부터 일반 JVM 환경까지 폭넓게 사용할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
### **주요 특징**
|
||||
1. **광범위한 알고리즘 지원**:
|
||||
- 대칭키 암호화: AES, DES, Blowfish 등.
|
||||
- 비대칭키 암호화: RSA, ECDSA, Diffie-Hellman 등.
|
||||
- 해시 함수: SHA-1, SHA-256, SHA-3 등.
|
||||
- 인증 암호화: AES-GCM, CCM 등.
|
||||
- 기타: OpenPGP, CMS, TLS/DTLS, X.509 인증서 생성 등.
|
||||
|
||||
2. **경량 API와 JCE 제공자**:
|
||||
- **경량 API**: 메모리 제약 환경에서 직접 사용할 수 있는 저수준 API.
|
||||
- **JCE 제공자**: 자바의 표준 암호화 프레임워크와 통합 가능.
|
||||
|
||||
3. **FIPS 인증**:
|
||||
- Bouncy Castle은 FIPS 140-2 인증 버전을 제공하며, 높은 보안 요구 사항이 있는 환경에서도 사용 가능.
|
||||
|
||||
4. **오픈소스 및 커뮤니티 지원**:
|
||||
- MIT 기반 라이선스로 배포되며, 누구나 무료로 사용 가능.
|
||||
- GitHub를 통해 소스 코드가 공개되고, 커뮤니티 피드백을 반영.
|
||||
|
||||
5. **플랫폼 유연성**:
|
||||
- Java뿐만 아니라 C#도 지원하며, Android 환경에서는 **Spongy Castle**이라는 변형 버전으로 사용됨.
|
||||
|
||||
---
|
||||
|
||||
### **Bouncy Castle의 기원**
|
||||
Bouncy Castle은 두 명의 자바 개발자가 서버 측 애플리케이션에서 반복적으로 암호화 라이브러리를 작성해야 하는 상황에 지쳤을 때 시작되었습니다. 이들은 Java ME(J2ME)와 같은 제한된 환경에서도 동작할 수 있는 유연한 라이브러리를 목표로 설계했으며, 이는 현재의 아키텍처로 이어졌습니다. 초기 버전은 약 27,000줄의 코드로 시작했지만, 현재(최신 버전 기준) 약 39만 줄 이상으로 확장되었습니다.
|
||||
|
||||
---
|
||||
|
||||
### **주요 사용 사례**
|
||||
- **암호화 및 복호화**: AES, RSA 등을 사용한 데이터 보호.
|
||||
- **디지털 서명**: ECDSA, RSA 기반 서명 생성 및 검증.
|
||||
- **키 관리**: 키 쌍 생성, 패스워드 기반 키 생성(PBKDF2 등).
|
||||
- **프로토콜 구현**: TLS, OpenPGP, S/MIME 등 보안 프로토콜 지원.
|
||||
- **인증서 관리**: X.509 인증서 생성 및 처리.
|
||||
|
||||
---
|
||||
|
||||
### **자바에서 Bouncy Castle 사용 예제**
|
||||
Bouncy Castle을 자바 프로젝트에 추가하려면 Maven 의존성을 추가하고, JCE 제공자로 등록한 뒤 사용할 수 있습니다.
|
||||
|
||||
#### **Maven 의존성**
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
<version>1.78</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### **제공자 등록 및 AES 암호화 예제**
|
||||
```java
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.Security;
|
||||
|
||||
public class BouncyCastleExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Bouncy Castle 제공자 등록
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
// 키와 데이터 준비
|
||||
byte[] keyBytes = "ThisIsASecretKey".getBytes();
|
||||
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
|
||||
String input = "Hello, Bouncy Castle!";
|
||||
|
||||
// 암호화
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] encrypted = cipher.doFinal(input.getBytes());
|
||||
|
||||
System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **장점**
|
||||
- **풍부한 기능**: JCE보다 더 많은 알고리즘과 프로토콜 지원.
|
||||
- **유연성**: 경량 API로 임베디드 시스템에서도 사용 가능.
|
||||
- **제한 없음**: 미국 암호화 수출 제한(JCE의 과거 제약)을 피할 수 있음.
|
||||
- **커뮤니티와 지속적인 업데이트**: 활발한 개발과 최신 보안 표준 반영.
|
||||
|
||||
### **단점**
|
||||
- **복잡성**: JCE보다 학습 곡선이 다소 높을 수 있음.
|
||||
- **성능**: 일부 경우 JCE의 네이티브 최적화(AES-NI 등)에 비해 느릴 수 있음.
|
||||
- **의존성 관리**: Android 등 특정 환경에서는 별도 변형(Spongy Castle) 필요.
|
||||
|
||||
---
|
||||
|
||||
### **JCE와의 비교**
|
||||
- **JCE**: 자바 표준 라이브러리로, 간단한 사용과 기본 기능에 적합. 하지만 과거 키 길이 제한(현재는 완화됨)과 지원 알고리즘의 한계가 있음.
|
||||
- **Bouncy Castle**: 더 많은 알고리즘과 프로토콜을 제공하며, FIPS 인증과 같은 고급 요구 사항을 충족. 그러나 추가 설정이 필요.
|
||||
|
||||
---
|
||||
|
||||
Bouncy Castle은 암호화가 필요한 개발자에게 강력한 도구로 자리 잡고 있으며, 특히 보안 요구 사항이 높은 프로젝트에서 유용합니다. 추가로 궁금한 점이나 특정 기능에 대한 깊은 설명이 필요하면 말씀해 주세요!
|
||||
240
docs/security/cipher.md
Normal file
240
docs/security/cipher.md
Normal file
@@ -0,0 +1,240 @@
|
||||
자바에서 AES와 RSA 암호화 알고리즘을 사용할 때 지원되는 운용 모드, 패딩, 키 길이, 그리고 필요한 매개변수를 표로 정리하겠습니다. JCA/JCE에서 제공하는 기본 설정을 기준으로 하며, JDK 버전(예: JDK 8, 11, 17 등) 및 사용 중인 제공자(Provider, 예: SunJCE, Bouncy Castle)에 따라 일부 차이가 있을 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
### **AES (Advanced Encryption Standard)**
|
||||
|
||||
| **항목** | **설명** |
|
||||
|--------------------|--------------------------------------------------------------------------------------------|
|
||||
| **운용 모드** | - **ECB (Electronic Codebook)**: 블록 단위로 독립적 암호화 (보안성 낮음, 권장되지 않음).<br> - **CBC (Cipher Block Chaining)**: 이전 블록과 XOR 연산, IV 필요.<br> - **CTR (Counter)**: 스트림 암호처럼 동작, IV 또는 논스 필요.<br> - **GCM (Galois/Counter Mode)**: 인증 암호화, IV 및 추가 인증 데이터(AAD) 지원.<br> - **CFB (Cipher Feedback)**: 스트림 암호화, IV 필요.<br> - **OFB (Output Feedback)**: 스트림 암호화, IV 필요. |
|
||||
| **패딩** | - **PKCS5Padding** (또는 PKCS7Padding): 블록 크기에 맞게 패딩 추가 (기본값).<br> - **NoPadding**: 패딩 없음, 데이터 길이가 블록 크기의 배수여야 함.<br> - **ISO10126Padding**: 랜덤 패딩 (일부 제공자에서 지원). |
|
||||
| **키 길이** | - 128비트 (기본값).<br> - 192비트.<br> - 256비트 (JCE Unlimited Strength Policy 필요 시 설치). |
|
||||
| **필요한 매개변수** | - **IV (Initialization Vector)**: CBC, CTR, GCM, CFB, OFB 모드에서 필요 (일반적으로 16바이트).<br> - **Nonce**: CTR, GCM에서 사용 가능.<br> - **AAD (Additional Authenticated Data)**: GCM 모드에서 인증용 추가 데이터 (선택적).<br> - **Tag 길이**: GCM 모드에서 인증 태그 길이 (기본 128비트, 96, 104, 112, 120 등 조정 가능). |
|
||||
| **Cipher 형식** | `"AES/[운용모드]/[패딩]"` 예: `"AES/CBC/PKCS5Padding"`, `"AES/GCM/NoPadding"`. |
|
||||
|
||||
#### **참고**
|
||||
- AES는 블록 크기가 128비트(16바이트)로 고정되어 있습니다.
|
||||
- ECB는 보안 취약성이 있어 권장되지 않으며, GCM은 인증과 암호화를 동시에 제공해 현대 애플리케이션에서 선호됩니다.
|
||||
- 예제: `Cipher.getInstance("AES/GCM/NoPadding")`.
|
||||
|
||||
---
|
||||
|
||||
### **RSA (Rivest-Shamir-Adleman)**
|
||||
|
||||
| **항목** | **설명** |
|
||||
|--------------------|--------------------------------------------------------------------------------------------|
|
||||
| **운용 모드** | - RSA 자체는 블록 암호화로 동작하며, 별도의 운용 모드 없음.<br> - 단일 블록 암호화/복호화 또는 서명/검증 용도로 사용.<br> - 하이브리드 암호화 시 AES와 결합 가능 (RSA로 AES 키 암호화). |
|
||||
| **패딩** | - **NoPadding**: 패딩 없음 (데이터 길이가 키 크기보다 작아야 함, 권장되지 않음).<br> - **PKCS1Padding**: PKCS#1 v1.5 패딩 (암호화/서명에 사용, 기본값).<br> - **OAEPWith<해시>AndMGF1Padding**: OAEP 패딩 (예: `"OAEPWithSHA-256AndMGF1Padding"`), 더 안전.<br> - **OAEPPadding**: 기본 OAEP (구형, 권장되지 않음). |
|
||||
| **키 길이** | - 512비트 (취약, 사용 금지).<br> - 1024비트 (현재 취약, 권장되지 않음).<br> - 2048비트 (표준).<br> - 4096비트 (높은 보안 요구 시). |
|
||||
| **필요한 매개변수** | - **PublicKey/PrivateKey**: 공개키/개인키 쌍.<br> - **OAEP 매개변수**: OAEP 사용 시 `OAEPParameterSpec`로 해시 알고리즘(SHA-256 등)과 MGF1(Mask Generation Function) 지정.<br> - **서명 시 해시**: 예: `"SHA256withRSA"` (RSA와 결합된 해시 알고리즘). |
|
||||
| **Cipher 형식** | `"RSA/[운용모드]/[패딩]"` 예: `"RSA/None/PKCS1Padding"`, `"RSA/None/OAEPWithSHA-256AndMGF1Padding"`. |
|
||||
|
||||
#### **참고**
|
||||
- RSA는 비대칭 알고리즘이므로 대량 데이터 암호화에는 적합하지 않습니다. 주로 키 교환이나 디지털 서명에 사용됩니다.
|
||||
- OAEP 패딩은 PKCS#1 v1.5보다 안전하며, SHA-256 이상의 해시와 함께 사용하는 것이 권장됩니다.
|
||||
- 최대 암호화 가능한 데이터 크기는 키 길이와 패딩에 따라 달라집니다 (예: 2048비트 키 + PKCS1Padding = 최대 245바이트).
|
||||
|
||||
---
|
||||
|
||||
### **AES와 RSA 비교 요약**
|
||||
|
||||
| **특징** | **AES** | **RSA** |
|
||||
|--------------------|----------------------------------|----------------------------------|
|
||||
| **유형** | 대칭키 암호화 | 비대칭키 암호화 |
|
||||
| **주요 용도** | 데이터 암호화 | 키 교환, 디지털 서명 |
|
||||
| **속도** | 빠름 | 느림 |
|
||||
| **키 길이** | 128, 192, 256비트 | 1024, 2048, 4096비트 |
|
||||
| **운용 모드** | ECB, CBC, GCM 등 다양 | 없음 (단일 블록 암호화) |
|
||||
| **패딩** | PKCS5Padding, NoPadding 등 | PKCS1Padding, OAEP 등 |
|
||||
|
||||
---
|
||||
|
||||
### **예제 코드**
|
||||
#### **AES 암호화**
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
public class AesExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
SecretKey key = keyGen.generateKey();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
byte[] iv = new byte[16]; // IV는 랜덤으로 생성해야 함
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||
byte[] encrypted = cipher.doFinal("Hello, AES!".getBytes());
|
||||
System.out.println(new String(encrypted));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **RSA 암호화**
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
|
||||
public class RsaExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
KeyPair keyPair = keyGen.generateKeyPair();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
|
||||
byte[] encrypted = cipher.doFinal("Hello, RSA!".getBytes());
|
||||
System.out.println(new String(encrypted));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
추가로 궁금한 점이나 특정 항목에 대한 자세한 설명이 필요하면 말씀해주세요!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
AES-GCM (Galois/Counter Mode)은 AES 암호화 알고리즘의 운용 모드 중 하나로, 대칭키 암호화와 인증을 동시에 제공하는 **인증 암호화 (Authenticated Encryption, AE)** 방식입니다. 이 모드는 현대 보안 애플리케이션에서 널리 사용되며, 특히 TLS/SSL, IPsec, 디스크 암호화 등에서 표준으로 채택되고 있습니다. 아래에서 AES-GCM의 상세한 특징, 동작 방식, 매개변수, 장단점 등을 정리하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### **AES-GCM의 주요 특징**
|
||||
1. **대칭키 암호화**:
|
||||
- AES 블록 암호를 기반으로 하며, 블록 크기는 128비트(16바이트)로 고정됩니다.
|
||||
- 키 길이는 128비트, 192비트, 256비트 중 선택 가능.
|
||||
|
||||
2. **인증 제공**:
|
||||
- 암호화된 데이터의 무결성과 출처 인증을 보장하기 위해 **인증 태그 (Authentication Tag)**를 생성합니다.
|
||||
- 별도의 MAC(Message Authentication Code) 계산이 필요 없음.
|
||||
|
||||
3. **스트림 암호처럼 동작**:
|
||||
- CTR(Counter) 모드를 기반으로 하여, 입력 데이터 길이에 관계없이 암호화 가능 (블록 크기의 배수일 필요 없음).
|
||||
|
||||
4. **Galois 필드 연산**:
|
||||
- 인증 태그 생성에 GF(2^128) (Galois Field) 연산을 사용하며, 이를 통해 효율적이고 안전한 인증을 구현.
|
||||
|
||||
---
|
||||
|
||||
### **AES-GCM의 동작 방식**
|
||||
1. **입력**:
|
||||
- 평문 (Plaintext): 암호화할 데이터.
|
||||
- 키 (Key): AES 키 (128, 192, 256비트).
|
||||
- IV (Initialization Vector) 또는 Nonce: 초기화 벡터 (일반적으로 12바이트 권장).
|
||||
- AAD (Additional Authenticated Data): 인증만 필요한 추가 데이터 (선택적, 암호화되지 않음).
|
||||
|
||||
2. **과정**:
|
||||
- **CTR 모드 암호화**: IV와 카운터를 결합하여 AES로 키스트림(Key Stream)을 생성하고, 평문과 XOR 연산을 통해 암호문을 생성.
|
||||
- **인증 태그 생성**: 암호문, AAD, IV 등을 Galois 필드 연산(GMAC)을 통해 인증 태그로 변환.
|
||||
- 출력: 암호문(Ciphertext) + 인증 태그(Tag).
|
||||
|
||||
3. **출력**:
|
||||
- 암호문과 인증 태그를 함께 반환하며, 복호화 시 태그를 검증하여 데이터 무결성을 확인.
|
||||
|
||||
---
|
||||
|
||||
### **주요 매개변수**
|
||||
| **매개변수** | **설명** | **권장값/주의사항** |
|
||||
|----------------------|--------------------------------------------------------------------------------------------|---------------------------------------------|
|
||||
| **키 길이** | AES 암호화에 사용되는 키 크기. | 128, 192, 256비트 (256비트 권장). |
|
||||
| **IV (Nonce)** | 초기화 벡터로, 암호화마다 고유해야 함. | 12바이트(96비트) 권장, 재사용 절대 금지. |
|
||||
| **AAD** | 인증에만 사용되는 추가 데이터 (암호화되지 않음). | 선택적, 예: 프로토콜 헤더 등. |
|
||||
| **태그 길이** | 인증 태그의 길이로, 무결성 검증에 사용. | 128비트(기본), 96, 104, 112, 120비트 가능. |
|
||||
| **Cipher 형식** | 자바에서 GCM 모드를 지정하는 형식. | `"AES/GCM/NoPadding"` (패딩 불필요). |
|
||||
|
||||
#### **IV/Nonce에 대한 중요 참고**
|
||||
- GCM은 IV(Nonce) 재사용 시 보안이 심각하게 손상됩니다. 동일한 키와 IV 조합을 두 번 사용하면 인증 태그가 의미를 잃고, 암호문이 노출될 수 있습니다.
|
||||
- 12바이트(96비트) Nonce가 표준으로 권장되며, 더 긴 IV를 사용할 경우 내부적으로 해시 처리됩니다.
|
||||
|
||||
---
|
||||
|
||||
### **자바에서 AES-GCM 사용 예제**
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class AesGcmExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 1. AES 키 생성
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256); // 256비트 키
|
||||
SecretKey key = keyGen.generateKey();
|
||||
|
||||
// 2. IV (Nonce) 생성
|
||||
byte[] iv = new byte[12]; // 12바이트 Nonce
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(iv);
|
||||
|
||||
// 3. 암호화 설정
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv); // 128비트 태그 길이
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
|
||||
// 4. AAD 추가 (선택적)
|
||||
byte[] aad = "AuthData".getBytes(StandardCharsets.UTF_8);
|
||||
cipher.updateAAD(aad);
|
||||
|
||||
// 5. 암호화
|
||||
byte[] plaintext = "Hello, AES-GCM!".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
|
||||
// 결과 출력
|
||||
System.out.println("IV: " + bytesToHex(iv));
|
||||
System.out.println("Ciphertext (with tag): " + bytesToHex(ciphertext));
|
||||
|
||||
// 6. 복호화
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
cipher.updateAAD(aad); // AAD 동일해야 함
|
||||
byte[] decrypted = cipher.doFinal(ciphertext);
|
||||
System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **장점**
|
||||
1. **효율성**: 암호화와 인증을 단일 패스로 처리하여 속도가 빠름.
|
||||
2. **보안성**: 인증 태그로 무결성과 출처 인증을 보장.
|
||||
3. **유연성**: AAD를 통해 추가 데이터를 인증 가능.
|
||||
|
||||
### **단점 및 주의사항**
|
||||
1. **Nonce 재사용 금지**: 동일한 키와 Nonce를 재사용하면 보안이 완전히 무너질 수 있음 (키스트림 재사용 문제).
|
||||
2. **태그 길이 조정 시 주의**: 짧은 태그(예: 64비트)는 충돌 위험이 증가할 수 있음.
|
||||
3. **복잡성**: IV 관리와 AAD 처리가 추가적으로 필요.
|
||||
|
||||
---
|
||||
|
||||
### **응용 사례**
|
||||
- **TLS 1.2/1.3**: AES-GCM은 TLS에서 널리 사용되는 cipher suite입니다 (예: `TLS_AES_256_GCM_SHA384`).
|
||||
- **디스크 암호화**: 인증과 함께 데이터 보호.
|
||||
- **메시징 시스템**: 메시지 무결성과 기밀성 보장.
|
||||
|
||||
---
|
||||
|
||||
AES-GCM은 현대 암호화에서 매우 강력하고 효율적인 선택이지만, IV 관리와 키 회전에 신경 써야 합니다. 추가로 궁금한 점이나 특정 부분에 대한 더 깊은 설명이 필요하면 말씀해주세요!
|
||||
508
docs/security/jce.md
Normal file
508
docs/security/jce.md
Normal file
@@ -0,0 +1,508 @@
|
||||
Java의 `java.security` 패키지는 **보안 관련 기능**을 제공하는 핵심 패키지야. 크게 다음과 같은 기능들로 분류할 수 있어:
|
||||
|
||||
---
|
||||
|
||||
## 1. 암호화(Encryption) 및 복호화(Decryption)
|
||||
암호화는 데이터를 보호하는 데 중요한 역할을 해. Java에서는 다양한 알고리즘을 지원하는 **JCA(Java Cryptography Architecture)** 및 **JCE(Java Cryptography Extension)**를 통해 암호화 기능을 제공해.
|
||||
|
||||
- **주요 클래스 및 인터페이스**
|
||||
| 클래스/인터페이스 | 설명 |
|
||||
|------------------|------|
|
||||
| `Cipher` | 암호화 및 복호화 작업을 수행하는 클래스 |
|
||||
| `KeyGenerator` | 대칭 키를 생성하는 클래스 (AES, DES 등) |
|
||||
| `SecretKey` | 대칭 키를 나타내는 인터페이스 |
|
||||
| `KeyPairGenerator` | 공개 키 및 개인 키 쌍을 생성하는 클래스 |
|
||||
| `PublicKey`, `PrivateKey` | 비대칭 키를 나타내는 인터페이스 |
|
||||
|
||||
- **예제: AES 암호화 및 복호화**
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Base64;
|
||||
|
||||
public class AESExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(128); // 128비트 키 생성
|
||||
SecretKey secretKey = keyGen.generateKey();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
|
||||
String plaintext = "Hello, Security!";
|
||||
byte[] encrypted = cipher.doFinal(plaintext.getBytes());
|
||||
|
||||
System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
|
||||
|
||||
// 복호화
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
||||
byte[] decrypted = cipher.doFinal(encrypted);
|
||||
System.out.println("Decrypted: " + new String(decrypted));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 키(Key) 및 키스토어(KeyStore) 관리
|
||||
보안 키를 안전하게 저장하고 관리할 수 있도록 **키스토어** 기능을 제공해.
|
||||
|
||||
- **주요 클래스 및 인터페이스**
|
||||
| 클래스/인터페이스 | 설명 |
|
||||
|------------------|------|
|
||||
| `KeyStore` | 키 및 인증서를 저장하는 저장소 |
|
||||
| `KeyFactory` | 키 객체를 생성하는 클래스 |
|
||||
| `KeyPair` | 공개 키와 개인 키를 함께 저장하는 클래스 |
|
||||
|
||||
- **예제: KeyStore 생성 및 저장**
|
||||
```java
|
||||
import java.io.FileOutputStream;
|
||||
import java.security.KeyStore;
|
||||
|
||||
public class KeyStoreExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(null, null); // 새로운 KeyStore 생성
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream("keystore.jks")) {
|
||||
keyStore.store(fos, "password".toCharArray()); // 비밀번호와 함께 저장
|
||||
}
|
||||
System.out.println("KeyStore 저장 완료");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 해시(Hashing) 및 메시지 다이제스트(Message Digest)
|
||||
해시는 데이터를 변환하여 무결성을 검증하는 데 사용돼. 대표적인 알고리즘으로 **SHA-256**, **MD5** 등이 있어.
|
||||
|
||||
- **주요 클래스**
|
||||
| 클래스 | 설명 |
|
||||
|--------|------|
|
||||
| `MessageDigest` | 해시 값을 생성하는 클래스 |
|
||||
| `DigestInputStream` | 스트림을 통해 해시 계산을 수행하는 클래스 |
|
||||
|
||||
- **예제: SHA-256 해싱**
|
||||
```java
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
public class HashExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String input = "password123";
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(input.getBytes());
|
||||
|
||||
System.out.println("SHA-256 Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 전자서명(Digital Signature)
|
||||
전자서명은 데이터의 **무결성을 검증**하고 **서명자의 신원**을 확인하는 데 사용돼.
|
||||
|
||||
- **주요 클래스 및 인터페이스**
|
||||
| 클래스/인터페이스 | 설명 |
|
||||
|------------------|------|
|
||||
| `Signature` | 전자서명을 생성하고 검증하는 클래스 |
|
||||
| `KeyPairGenerator` | 전자서명을 위한 키 쌍을 생성하는 클래스 |
|
||||
|
||||
- **예제: 전자서명 생성 및 검증**
|
||||
```java
|
||||
import java.security.*;
|
||||
|
||||
public class DigitalSignatureExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
KeyPair keyPair = keyGen.generateKeyPair();
|
||||
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initSign(keyPair.getPrivate());
|
||||
|
||||
String message = "This is a secure message.";
|
||||
signature.update(message.getBytes());
|
||||
byte[] signedData = signature.sign();
|
||||
|
||||
// 검증
|
||||
signature.initVerify(keyPair.getPublic());
|
||||
signature.update(message.getBytes());
|
||||
boolean isVerified = signature.verify(signedData);
|
||||
System.out.println("Signature Verified: " + isVerified);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 인증(Authentication) 및 접근 제어(Access Control)
|
||||
사용자의 인증 및 권한 관리를 할 수 있도록 지원해.
|
||||
|
||||
- **주요 클래스 및 인터페이스**
|
||||
| 클래스/인터페이스 | 설명 |
|
||||
|------------------|------|
|
||||
| `AccessController` | 접근 제어를 수행하는 클래스 |
|
||||
| `Permission` | 특정 작업에 대한 권한을 나타내는 클래스 |
|
||||
| `Policy` | 보안 정책을 관리하는 클래스 |
|
||||
|
||||
- **예제: 접근 제어 실행**
|
||||
```java
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
public class AccessControlExample {
|
||||
public static void main(String[] args) {
|
||||
String result = AccessController.doPrivileged((PrivilegedAction<String>) () -> {
|
||||
return System.getProperty("user.home");
|
||||
});
|
||||
System.out.println("User Home Directory: " + result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 난수 생성(Secure Random)
|
||||
보안에 강한 난수를 생성하는 기능도 제공해.
|
||||
|
||||
- **주요 클래스**
|
||||
| 클래스 | 설명 |
|
||||
|--------|------|
|
||||
| `SecureRandom` | 보안 강도를 높인 난수 생성 클래스 |
|
||||
|
||||
- **예제: 난수 생성**
|
||||
```java
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class SecureRandomExample {
|
||||
public static void main(String[] args) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
int number = random.nextInt(100);
|
||||
System.out.println("Secure Random Number: " + number);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 보안 프로바이더(Security Provider)
|
||||
Java는 다양한 보안 알고리즘을 제공하는 **보안 프로바이더** 개념을 사용해.
|
||||
|
||||
- **주요 클래스 및 인터페이스**
|
||||
| 클래스/인터페이스 | 설명 |
|
||||
|------------------|------|
|
||||
| `Security` | 보안 프로바이더를 관리하는 클래스 |
|
||||
| `Provider` | 특정 보안 기능을 제공하는 클래스 |
|
||||
|
||||
- **예제: 설치된 보안 프로바이더 목록 출력**
|
||||
```java
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
public class SecurityProviderExample {
|
||||
public static void main(String[] args) {
|
||||
for (Provider provider : Security.getProviders()) {
|
||||
System.out.println("Provider: " + provider.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 결론
|
||||
Java의 `java.security` 패키지는 **암호화, 키 관리, 해싱, 전자서명, 인증, 난수 생성** 등 다양한 보안 기능을 제공해.
|
||||
각 기능을 적절히 조합하면 **네트워크 보안, 파일 보호, 인증 시스템** 등을 구축할 수 있어!
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
Java의 **JCA(Java Cryptography Architecture)** 및 **JCE(Java Cryptography Extension)**에서 지원하는 주요 알고리즘을 정리해볼게.
|
||||
|
||||
JCA는 **암호화, 키 관리, 전자서명, 해싱** 등의 보안 기능을 정의하는 **프레임워크**이고,
|
||||
JCE는 JCA를 확장하여 **대칭키 암호화, 메시지 인증 코드(MAC), 키 생성** 등을 추가 지원하는 **확장 라이브러리**야.
|
||||
|
||||
---
|
||||
|
||||
## **1. 암호화(Encryption) 및 복호화(Decryption)**
|
||||
암호화는 데이터를 보호하는 가장 기본적인 보안 기술이야. JCE에서는 **대칭키 및 비대칭키 암호화**를 지원해.
|
||||
|
||||
| **알고리즘** | **설명** | **키 길이 (비트)** | **모드** |
|
||||
|-------------|---------|-----------------|-------|
|
||||
| AES (Advanced Encryption Standard) | 현대적인 대칭키 암호화 | 128, 192, 256 | ECB, CBC, CFB, OFB, GCM |
|
||||
| DES (Data Encryption Standard) | 오래된 대칭키 암호화 (더 이상 안전하지 않음) | 56 | ECB, CBC, CFB, OFB |
|
||||
| Triple DES (DESede) | DES를 3번 수행하여 보안성 강화 | 112, 168 | ECB, CBC, CFB, OFB |
|
||||
| Blowfish | 빠르고 유연한 대칭키 알고리즘 | 32~448 | ECB, CBC, CFB, OFB |
|
||||
| RSA | 비대칭키 암호화 (공개키 암호화) | 1024, 2048, 4096 | - |
|
||||
| ElGamal | 공개키 기반 암호화 | 1024 이상 | - |
|
||||
| ECIES (Elliptic Curve Integrated Encryption Scheme) | 타원 곡선 암호화 (ECC) | 192, 224, 256, 384, 521 | - |
|
||||
|
||||
**예제: AES 암호화 및 복호화**
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Base64;
|
||||
|
||||
public class AESExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(128);
|
||||
SecretKey secretKey = keyGen.generateKey();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
|
||||
String plaintext = "Hello, JCE!";
|
||||
byte[] encrypted = cipher.doFinal(plaintext.getBytes());
|
||||
|
||||
System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **2. 해시(Hash) 및 메시지 다이제스트(Message Digest)**
|
||||
해시는 데이터를 변환하여 **무결성을 보장**하는 데 사용돼.
|
||||
|
||||
| **알고리즘** | **설명** | **출력 크기 (비트)** |
|
||||
|-------------|---------|-----------------|
|
||||
| MD5 | 오래된 해시 알고리즘 (더 이상 안전하지 않음) | 128 |
|
||||
| SHA-1 | 보안 취약점이 발견된 해시 알고리즘 | 160 |
|
||||
| SHA-256 | 가장 널리 사용되는 SHA-2 계열 해시 | 256 |
|
||||
| SHA-512 | SHA-256보다 더 강력한 해시 | 512 |
|
||||
| SHA3-256 | 최신 SHA-3 계열 해시 | 256 |
|
||||
|
||||
**예제: SHA-256 해싱**
|
||||
```java
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
public class SHA256Example {
|
||||
public static void main(String[] args) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest("password123".getBytes());
|
||||
System.out.println("SHA-256 Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **3. 전자서명(Digital Signature)**
|
||||
전자서명은 데이터의 **무결성을 보장**하고 **서명자의 신원**을 확인하는 데 사용돼.
|
||||
|
||||
| **알고리즘** | **설명** | **키 길이 (비트)** |
|
||||
|-------------|---------|-----------------|
|
||||
| SHA1withRSA | SHA-1 기반 RSA 서명 (보안 취약) | 1024, 2048, 4096 |
|
||||
| SHA256withRSA | SHA-256 기반 RSA 서명 | 2048, 4096 |
|
||||
| SHA512withRSA | SHA-512 기반 RSA 서명 | 2048, 4096 |
|
||||
| SHA256withECDSA | 타원 곡선 기반 전자서명 | 256, 384, 521 |
|
||||
|
||||
**예제: RSA 전자서명 생성 및 검증**
|
||||
```java
|
||||
import java.security.*;
|
||||
|
||||
public class DigitalSignatureExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
KeyPair keyPair = keyGen.generateKeyPair();
|
||||
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initSign(keyPair.getPrivate());
|
||||
|
||||
String message = "Secure message";
|
||||
signature.update(message.getBytes());
|
||||
byte[] signedData = signature.sign();
|
||||
|
||||
// 검증
|
||||
signature.initVerify(keyPair.getPublic());
|
||||
signature.update(message.getBytes());
|
||||
boolean isVerified = signature.verify(signedData);
|
||||
System.out.println("Signature Verified: " + isVerified);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. 메시지 인증 코드(Message Authentication Code, MAC)**
|
||||
MAC은 **데이터의 무결성과 인증을 보장**하는 데 사용돼.
|
||||
|
||||
| **알고리즘** | **설명** | **출력 크기 (비트)** |
|
||||
|-------------|---------|-----------------|
|
||||
| HmacMD5 | MD5 기반 MAC | 128 |
|
||||
| HmacSHA1 | SHA-1 기반 MAC | 160 |
|
||||
| HmacSHA256 | SHA-256 기반 MAC | 256 |
|
||||
| HmacSHA512 | SHA-512 기반 MAC | 512 |
|
||||
|
||||
**예제: HmacSHA256 사용**
|
||||
```java
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class HmacExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String key = "secret";
|
||||
String message = "Hello, MAC";
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
|
||||
byte[] hmac = mac.doFinal(message.getBytes());
|
||||
|
||||
System.out.println("HMAC: " + Base64.getEncoder().encodeToString(hmac));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **5. 난수 생성(Secure Random)**
|
||||
보안성이 높은 난수를 생성할 때 사용해.
|
||||
|
||||
| **알고리즘** | **설명** |
|
||||
|-------------|---------|
|
||||
| SHA1PRNG | 보안성이 강화된 PRNG (의사 난수 생성기) |
|
||||
| NativePRNG | OS 기본 난수 생성기 사용 |
|
||||
| Windows-PRNG | Windows용 PRNG |
|
||||
|
||||
**예제: 보안 난수 생성**
|
||||
```java
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class SecureRandomExample {
|
||||
public static void main(String[] args) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
int number = random.nextInt(100);
|
||||
System.out.println("Secure Random Number: " + number);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **결론**
|
||||
JCA 및 JCE에서는 **암호화, 해시, 전자서명, MAC, 난수 생성** 등의 다양한 알고리즘을 제공해.
|
||||
이들을 조합하면 **데이터 보호, 인증, 무결성 검증** 같은 강력한 보안 시스템을 구축할 수 있어!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
자바의 JCA (Java Cryptography Architecture)와 JCE (Java Cryptography Extension)는 암호화와 관련된 다양한 알고리즘을 제공합니다. JCA는 기본적인 암호화 프레임워크를 제공하며, JCE는 이를 확장하여 더 강력한 암호화 기능을 추가합니다. 아래는 JCA와 JCE에서 지원되는 주요 알고리즘을 정리한 내용입니다. 이 알고리즘들은 JDK 버전에 따라 다를 수 있으니, 사용하는 JDK 버전(예: JDK 8, 11, 17 등)에 따라 지원 여부를 확인하는 것이 좋습니다.
|
||||
|
||||
---
|
||||
|
||||
### 1. **대칭키 암호화 알고리즘 (Symmetric Key Algorithms)**
|
||||
- 데이터를 암호화하고 복호화할 때 동일한 키를 사용하는 알고리즘입니다.
|
||||
- 대표적인 알고리즘:
|
||||
- **AES (Advanced Encryption Standard)**: 128, 192, 256비트 키 지원.
|
||||
- 모드: CBC, ECB, CTR, GCM 등.
|
||||
- 패딩: PKCS5Padding, NoPadding 등.
|
||||
- **DES (Data Encryption Standard)**: 56비트 키 (현재는 취약하여 권장되지 않음).
|
||||
- **DESede (Triple DES)**: DES를 3번 반복 적용, 168비트 키.
|
||||
- **Blowfish**: 가변 길이 키(최대 448비트) 지원.
|
||||
- **RC2**, **RC4**: 오래된 알고리즘으로 보안성이 낮아 권장되지 않음.
|
||||
|
||||
---
|
||||
|
||||
### 2. **비대칭키 암호화 알고리즘 (Asymmetric Key Algorithms)**
|
||||
- 공개키와 개인키 쌍을 사용하여 암호화와 복호화를 수행합니다.
|
||||
- 대표적인 알고리즘:
|
||||
- **RSA**: 키 교환, 디지털 서명, 암호화에 사용. 일반적으로 1024, 2048, 4096비트 키 길이.
|
||||
- **DSA (Digital Signature Algorithm)**: 디지털 서명 전용, 1024비트 이상 권장.
|
||||
- **EC (Elliptic Curve)**: 타원 곡선 암호화, 효율적인 키 크기(예: 256비트로 RSA 3072비트 수준 보안 제공).
|
||||
- 예: ECDSA (서명), ECDH (키 교환).
|
||||
|
||||
---
|
||||
|
||||
### 3. **메시지 다이제스트 (해시 함수) 알고리즘**
|
||||
- 데이터 무결성을 확인하기 위해 고정된 길이의 해시 값을 생성합니다.
|
||||
- 대표적인 알고리즘:
|
||||
- **MD5**: 128비트 출력 (현재는 충돌 취약성 때문에 권장되지 않음).
|
||||
- **SHA-1**: 160비트 출력 (취약하여 더 이상 권장되지 않음).
|
||||
- **SHA-2**: SHA-224, SHA-256, SHA-384, SHA-512.
|
||||
- **SHA-3**: 최신 해시 알고리즘, SHA-2와 다른 구조로 보안성 강화.
|
||||
|
||||
---
|
||||
|
||||
### 4. **MAC (Message Authentication Code) 알고리즘**
|
||||
- 데이터 무결성과 인증을 보장하기 위한 keyed 해시 함수입니다.
|
||||
- 대표적인 알고리즘:
|
||||
- **HmacMD5**: MD5 기반 HMAC (취약성 때문에 권장되지 않음).
|
||||
- **HmacSHA1**: SHA-1 기반 HMAC (현재는 보안성 낮음).
|
||||
- **HmacSHA256**, **HmacSHA384**, **HmacSHA512**: SHA-2 기반, 보안성 우수.
|
||||
- **HmacSHA3-256**, **HmacSHA3-512**: SHA-3 기반 (JDK 9 이상에서 지원).
|
||||
|
||||
---
|
||||
|
||||
### 5. **디지털 서명 알고리즘**
|
||||
- 데이터의 무결성과 출처를 보장합니다.
|
||||
- 대표적인 알고리즘:
|
||||
- **RSA**: SHA-1, SHA-256 등과 결합 (예: SHA256withRSA).
|
||||
- **DSA**: SHA-1, SHA-256 등과 결합 (예: SHA256withDSA).
|
||||
- **ECDSA**: 타원 곡선 기반 서명 (예: SHA256withECDSA).
|
||||
|
||||
---
|
||||
|
||||
### 6. **키 생성 및 키 교환 알고리즘**
|
||||
- 암호화에 사용할 키를 생성하거나 교환합니다.
|
||||
- 대표적인 알고리즘:
|
||||
- **Diffie-Hellman (DH)**: 키 교환용.
|
||||
- **ECDH**: 타원 곡선 기반 키 교환.
|
||||
- 키 생성기: AES, DES, RSA, EC 등에 사용되는 `KeyGenerator` 및 `KeyPairGenerator`.
|
||||
|
||||
---
|
||||
|
||||
### 7. **기타 유틸리티**
|
||||
- **Cipher**: 암호화/복호화 작업을 위한 클래스 (위의 알고리즘과 모드/패딩 조합 사용).
|
||||
- **KeyStore**: 키와 인증서를 저장/관리.
|
||||
- **SecureRandom**: 암호화에 적합한 난수 생성 (예: SHA1PRNG).
|
||||
|
||||
---
|
||||
|
||||
### 참고사항
|
||||
- **지원 여부 확인**: JDK 버전과 제공자(Provider, 예: SunJCE, Bouncy Castle 등)에 따라 지원되는 알고리즘이 다를 수 있습니다. `Security.getProviders()`를 호출해 확인 가능.
|
||||
- **보안 권장**: MD5, SHA-1, DES 등은 취약하니 최신 애플리케이션에서는 SHA-2, SHA-3, AES, RSA(2048비트 이상) 등을 사용하는 것이 좋습니다.
|
||||
- **예제 코드**:
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import java.security.Key;
|
||||
|
||||
public class AesExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256); // 256비트 키 생성
|
||||
Key key = keyGen.generateKey();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] encrypted = cipher.doFinal("Hello, World!".getBytes());
|
||||
System.out.println(new String(encrypted));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
필요하면 특정 알고리즘에 대해 더 깊이 설명하거나 예제 코드를 추가로 제공할 수 있습니다. 질문이 더 있으면 말씀해주세요!
|
||||
164
docs/security/pbkd.md
Normal file
164
docs/security/pbkd.md
Normal file
@@ -0,0 +1,164 @@
|
||||
패스워드 기반 키 생성(Password-Based Key Derivation, PBKD)은 사용자가 제공한 패스워드(일반적으로 짧고 사람이 기억하기 쉬운 문자열)를 기반으로 암호화에 사용할 수 있는 안전하고 고정된 길이의 암호화 키를 생성하는 방법입니다. 자바에서는 JCE(Java Cryptography Extension)를 통해 이를 구현할 수 있으며, 대표적으로 **PBKDF2**(Password-Based Key Derivation Function 2)가 널리 사용됩니다.
|
||||
|
||||
아래에서 패스워드 기반 키 생성의 개념, 동작 원리, 주요 알고리즘, 자바에서의 구현 방법 등을 상세히 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### **패스워드 기반 키 생성의 개념**
|
||||
- **문제점**: 패스워드는 보통 길이가 짧고 엔트로피(무작위성)가 낮아 직접 암호화 키로 사용하기에는 부적합합니다. 예를 들어, AES 키는 128비트(16바이트) 이상이어야 하지만, "password123" 같은 패스워드는 그보다 훨씬 짧고 예측 가능합니다.
|
||||
- **해결책**: PBKD는 패스워드에 **솔트(Salt)**와 반복적인 해시 연산을 적용하여 안전하고 고정된 길이의 키를 생성합니다.
|
||||
- **목적**:
|
||||
1. 패스워드의 엔트로피를 높여 무차별 대입 공격(Brute Force)이나 사전 공격(Dictionary Attack)을 어렵게 만듦.
|
||||
2. AES, RSA 등 암호화 알고리즘이 요구하는 특정 길이의 키(예: 256비트)를 생성.
|
||||
|
||||
---
|
||||
|
||||
### **주요 구성 요소**
|
||||
1. **패스워드(Password)**:
|
||||
- 사용자가 제공한 문자열(예: "MySecretPass123").
|
||||
- 보통 유니코드 문자로 표현되며, 직접 키로 사용되지 않음.
|
||||
|
||||
2. **솔트(Salt)**:
|
||||
- 무작위로 생성된 바이트 배열.
|
||||
- 동일한 패스워드라도 다른 키를 생성하도록 보장하며, 사전 공격(Precomputed Table, Rainbow Table)을 방지.
|
||||
- 일반적으로 8바이트 이상(16바이트 권장).
|
||||
|
||||
3. **반복 횟수(Iteration Count)**:
|
||||
- 해시 함수를 반복 적용하는 횟수.
|
||||
- 계산 비용을 높여 무차별 대입 공격을 느리게 만듦.
|
||||
- 예: 10,000회 이상(현대 하드웨어 기준으로는 100,000회 이상 권장).
|
||||
|
||||
4. **키 길이(Key Length)**:
|
||||
- 출력할 키의 길이(예: AES 256비트 = 32바이트).
|
||||
- 사용하려는 암호화 알고리즘에 맞게 지정.
|
||||
|
||||
5. **해시 알고리즘**:
|
||||
- 내부적으로 사용되는 암호화 해시 함수(예: SHA-256, SHA-512 등).
|
||||
|
||||
---
|
||||
|
||||
### **대표 알고리즘: PBKDF2**
|
||||
- **정의**: NIST와 RSA에서 표준화된 알고리즘으로, HMAC(Hash-based Message Authentication Code)을 기반으로 키를 생성.
|
||||
- **동작 방식**:
|
||||
1. 패스워드와 솔트를 입력으로 HMAC 함수(예: HMAC-SHA256)에 넣음.
|
||||
2. 지정된 반복 횟수만큼 HMAC 연산을 반복.
|
||||
3. 결과로 고정된 길이의 키를 출력.
|
||||
- **표기**: 자바에서는 `"PBKDF2WithHmacSHA256"`처럼 HMAC와 결합된 해시 알고리즘을 지정.
|
||||
|
||||
#### **지원되는 해시 알고리즘**
|
||||
- `PBKDF2WithHmacSHA1` (구형, 권장되지 않음).
|
||||
- `PBKDF2WithHmacSHA256` (권장).
|
||||
- `PBKDF2WithHmacSHA512` (더 높은 보안).
|
||||
|
||||
---
|
||||
|
||||
### **자바에서 PBKDF2 구현 예제**
|
||||
자바에서는 `PBEKeySpec`와 `SecretKeyFactory`를 사용하여 PBKDF2 기반 키를 생성할 수 있습니다.
|
||||
|
||||
```java
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class Pbkdf2Example {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 1. 패스워드 정의
|
||||
String password = "MySecretPass123";
|
||||
|
||||
// 2. 솔트 생성
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] salt = new byte[16]; // 16바이트 솔트
|
||||
random.nextBytes(salt);
|
||||
|
||||
// 3. PBKDF2 매개변수 설정
|
||||
int iterationCount = 100000; // 반복 횟수
|
||||
int keyLength = 256; // 출력 키 길이 (AES 256비트)
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
|
||||
|
||||
// 4. PBKDF2 키 생성
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
SecretKey key = factory.generateSecret(spec);
|
||||
|
||||
// 5. 생성된 키 출력
|
||||
byte[] keyBytes = key.getEncoded();
|
||||
String keyBase64 = Base64.getEncoder().encodeToString(keyBytes);
|
||||
System.out.println("Generated Key: " + keyBase64);
|
||||
System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **출력 예시**
|
||||
```
|
||||
Generated Key: X7k... (32바이트 Base64 인코딩)
|
||||
Salt: abC... (16바이트 Base64 인코딩)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **AES와 결합한 예제**
|
||||
생성된 키를 AES-GCM 암호화에 사용하는 예제입니다.
|
||||
|
||||
```java
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class Pbkdf2AesGcmExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 1. PBKDF2로 키 생성 (위 예제에서 가져옴)
|
||||
String password = "MySecretPass123";
|
||||
byte[] salt = new byte[16];
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(salt);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
SecretKey pbkdf2Key = factory.generateSecret(spec);
|
||||
SecretKey key = new SecretKeySpec(pbkdf2Key.getEncoded(), "AES");
|
||||
|
||||
// 2. AES-GCM 설정
|
||||
byte[] iv = new byte[12]; // 12바이트 Nonce
|
||||
random.nextBytes(iv);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); // 128비트 태그
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
|
||||
|
||||
// 3. 암호화
|
||||
byte[] plaintext = "Hello, PBKDF2!".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
|
||||
// 출력
|
||||
System.out.println("Ciphertext: " + Base64.getEncoder().encodeToString(ciphertext));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **장점**
|
||||
1. **보안성**: 솔트와 반복 연산으로 패스워드의 약점을 보완.
|
||||
2. **유연성**: 출력 키 길이를 자유롭게 조정 가능.
|
||||
3. **표준화**: PBKDF2는 NIST SP 800-132 등에서 권장.
|
||||
|
||||
### **단점 및 주의사항**
|
||||
1. **성능**: 반복 횟수가 많을수록 계산 비용 증가 (의도된 설계이나 최적화 필요).
|
||||
2. **솔트 관리**: 솔트는 암호화된 데이터와 함께 저장해야 하며, 재사용 시 동일한 키를 생성해야 함.
|
||||
3. **현대 대안**: Argon2, bcrypt, scrypt 등이 더 강력한 대안으로 주목받고 있음(자바 기본 JCE에서는 미지원, 외부 라이브러리 필요).
|
||||
|
||||
---
|
||||
|
||||
### **권장 설정**
|
||||
- **솔트**: 최소 16바이트, SecureRandom으로 생성.
|
||||
- **반복 횟수**: 하드웨어 성능에 따라 100,000회 이상(예: 2025년 기준 1초 이내 처리 목표).
|
||||
- **해시**: SHA-256 또는 SHA-512.
|
||||
- **키 길이**: AES 사용 시 256비트 권장.
|
||||
|
||||
---
|
||||
|
||||
패스워드 기반 키 생성에 대해 더 궁금한 점이나 특정 부분에 대한 추가 설명이 필요하면 말씀해주세요!
|
||||
59
docs/swing/01_Swing Components.md
Normal file
59
docs/swing/01_Swing Components.md
Normal file
@@ -0,0 +1,59 @@
|
||||
다음은 **Swing의 모든 주요 컴포넌트**를 정리한 표이다.
|
||||
|
||||
### **1. 기본 컴포넌트**
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|---------------------------------------------|
|
||||
| `JLabel` | 텍스트 또는 아이콘을 표시하는 레이블 |
|
||||
| `JButton` | 클릭 가능한 버튼 |
|
||||
| `JToggleButton` | ON/OFF 상태를 가질 수 있는 버튼 |
|
||||
| `JCheckBox` | 다중 선택이 가능한 체크박스 |
|
||||
| `JRadioButton` | 단일 선택이 가능한 라디오 버튼 |
|
||||
| `JTextField` | 한 줄의 텍스트 입력 필드 |
|
||||
| `JPasswordField` | 비밀번호 입력 필드 (입력값이 가려짐) |
|
||||
| `JTextArea` | 여러 줄의 텍스트를 입력할 수 있는 영역 |
|
||||
| `JEditorPane` | HTML, RTF 등을 표시할 수 있는 편집 가능한 텍스트 영역 |
|
||||
| `JComboBox` | 드롭다운 목록을 제공하는 콤보 박스 |
|
||||
| `JList` | 여러 개의 아이템을 선택할 수 있는 리스트 |
|
||||
|
||||
### **2. 컨테이너 컴포넌트**
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|---------------------------------------------|
|
||||
| `JPanel` | 기본 컨테이너 패널 |
|
||||
| `JScrollPane` | 내부 컴포넌트에 스크롤 기능을 추가 |
|
||||
| `JSplitPane` | 두 개의 컴포넌트를 수평/수직으로 나눌 수 있는 패널 |
|
||||
| `JTabbedPane` | 여러 개의 탭을 제공하는 패널 |
|
||||
| `JLayeredPane` | 컴포넌트들을 여러 레이어로 배치할 수 있는 패널 |
|
||||
| `JDesktopPane` | MDI(Multiple Document Interface)를 위한 내부 프레임 지원 패널 |
|
||||
|
||||
### **3. 고급 컴포넌트**
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|---------------------------------------------|
|
||||
| `JTable` | 데이터를 표 형태로 표시하는 테이블 |
|
||||
| `JTree` | 계층 구조를 표시하는 트리 |
|
||||
| `JProgressBar` | 작업 진행 상태를 표시하는 프로그레스 바 |
|
||||
| `JSlider` | 슬라이더를 통한 값 조정 가능 |
|
||||
| `JSpinner` | 숫자 또는 값 목록을 조정할 수 있는 스피너 |
|
||||
|
||||
### **4. 메뉴 및 다이얼로그**
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|---------------------------------------------|
|
||||
| `JMenuBar` | 메뉴바를 제공하는 컨테이너 |
|
||||
| `JMenu` | 메뉴 항목을 포함하는 메뉴 |
|
||||
| `JMenuItem` | 개별적인 메뉴 항목 |
|
||||
| `JCheckBoxMenuItem` | 체크 가능한 메뉴 항목 |
|
||||
| `JRadioButtonMenuItem` | 라디오 버튼 형태의 메뉴 항목 |
|
||||
| `JPopupMenu` | 우클릭 팝업 메뉴 |
|
||||
| `JToolBar` | 도구 모음(툴바) 패널 |
|
||||
| `JOptionPane` | 알림, 확인, 입력을 위한 다이얼로그 제공 |
|
||||
| `JFileChooser` | 파일 선택 다이얼로그 |
|
||||
| `JColorChooser` | 색상 선택 다이얼로그 |
|
||||
|
||||
### **5. 윈도우 관련 컴포넌트**
|
||||
| 컴포넌트 | 설명 |
|
||||
|----------|---------------------------------------------|
|
||||
| `JFrame` | 기본 윈도우 프레임 |
|
||||
| `JDialog` | 모달 또는 모델리스 다이얼로그 창 |
|
||||
| `JWindow` | 테두리 없는 창 |
|
||||
| `JInternalFrame` | `JDesktopPane` 내에서 사용할 수 있는 내부 프레임 |
|
||||
|
||||
위 컴포넌트들을 조합하면 다양한 GUI 애플리케이션을 만들 수 있다.
|
||||
190
docs/swing/ActionListener.md
Normal file
190
docs/swing/ActionListener.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# **ActionListener와 ActionCommand 사용법**
|
||||
|
||||
`ActionListener`는 버튼 클릭, 메뉴 항목 선택 등 사용자 입력에 대한 동작을 처리하는 인터페이스입니다.
|
||||
`ActionCommand`는 특정 이벤트를 식별하는 문자열로, `ActionListener`가 어떤 동작을 수행해야 하는지 구분하는 데 사용됩니다.
|
||||
|
||||
---
|
||||
|
||||
## **1. ActionListener 기본 사용법**
|
||||
`ActionListener`를 사용하려면 `ActionListener` 인터페이스를 구현한 후 `actionPerformed()` 메서드를 오버라이드해야 합니다.
|
||||
|
||||
### **(1) 버튼 클릭 이벤트 처리 예제**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class ActionListenerExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("ActionListener Example");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
JPanel panel = new JPanel();
|
||||
|
||||
JButton button = new JButton("클릭하세요");
|
||||
|
||||
// ActionListener 추가
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JOptionPane.showMessageDialog(null, "버튼이 클릭되었습니다!");
|
||||
}
|
||||
});
|
||||
|
||||
panel.add(button);
|
||||
frame.add(panel);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
### **설명**
|
||||
1. `JButton`을 생성하고 `"클릭하세요"` 라벨을 설정.
|
||||
2. `button.addActionListener()`를 사용하여 `ActionListener` 추가.
|
||||
3. 버튼을 클릭하면 `actionPerformed()`가 호출되어 메시지 창이 표시됨.
|
||||
|
||||
---
|
||||
|
||||
## **2. ActionCommand 활용하기**
|
||||
기본적으로 버튼의 텍스트가 `ActionCommand`로 설정되지만, 직접 지정할 수도 있습니다.
|
||||
|
||||
### **(1) 여러 버튼을 구분하는 예제**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class ActionCommandExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("ActionCommand Example");
|
||||
frame.setSize(400, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new FlowLayout());
|
||||
|
||||
JButton btn1 = new JButton("버튼 1");
|
||||
JButton btn2 = new JButton("버튼 2");
|
||||
|
||||
// ActionCommand 설정
|
||||
btn1.setActionCommand("BUTTON_ONE");
|
||||
btn2.setActionCommand("BUTTON_TWO");
|
||||
|
||||
// 공통 ActionListener
|
||||
ActionListener listener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String command = e.getActionCommand();
|
||||
if ("BUTTON_ONE".equals(command)) {
|
||||
JOptionPane.showMessageDialog(null, "버튼 1이 클릭됨!");
|
||||
} else if ("BUTTON_TWO".equals(command)) {
|
||||
JOptionPane.showMessageDialog(null, "버튼 2가 클릭됨!");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
btn1.addActionListener(listener);
|
||||
btn2.addActionListener(listener);
|
||||
|
||||
frame.add(btn1);
|
||||
frame.add(btn2);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **설명**
|
||||
1. 두 개의 버튼(`btn1`, `btn2`)을 생성.
|
||||
2. `setActionCommand("BUTTON_ONE")` 및 `setActionCommand("BUTTON_TWO")`로 명령어 설정.
|
||||
3. **하나의 `ActionListener`** 에서 `e.getActionCommand()`를 사용하여 어떤 버튼이 눌렸는지 판별.
|
||||
|
||||
---
|
||||
|
||||
## **3. 별도의 ActionListener 클래스로 분리하기**
|
||||
코드를 깔끔하게 유지하려면 `ActionListener`를 별도의 클래스로 분리하는 것이 좋습니다.
|
||||
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
class MyActionListener implements ActionListener {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JOptionPane.showMessageDialog(null, "이벤트가 발생했습니다! (" + e.getActionCommand() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public class SeparateActionListenerExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("Separate ActionListener Example");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
JPanel panel = new JPanel();
|
||||
|
||||
JButton button = new JButton("클릭");
|
||||
button.setActionCommand("CLICK_BUTTON");
|
||||
|
||||
// 별도의 클래스 사용
|
||||
button.addActionListener(new MyActionListener());
|
||||
|
||||
panel.add(button);
|
||||
frame.add(panel);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **설명**
|
||||
- `MyActionListener` 클래스를 만들어 `ActionListener`를 구현.
|
||||
- `JButton`에 `MyActionListener`를 추가.
|
||||
- 클릭하면 `"이벤트가 발생했습니다! (CLICK_BUTTON)"` 메시지를 표시.
|
||||
|
||||
---
|
||||
|
||||
## **4. Lambda 표현식 사용**
|
||||
Java 8 이상에서는 람다를 사용하여 `ActionListener`를 더 간결하게 작성할 수 있습니다.
|
||||
|
||||
```java
|
||||
import javax.swing.*;
|
||||
|
||||
public class LambdaActionListenerExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("Lambda ActionListener");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
JPanel panel = new JPanel();
|
||||
|
||||
JButton button = new JButton("눌러보세요");
|
||||
|
||||
// Lambda 표현식 사용
|
||||
button.addActionListener(e -> JOptionPane.showMessageDialog(null, "람다식으로 처리된 이벤트!"));
|
||||
|
||||
panel.add(button);
|
||||
frame.add(panel);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **설명**
|
||||
- `ActionListener`를 람다 표현식(`e -> { }`)으로 간단하게 구현.
|
||||
- 코드가 더 짧고 가독성이 좋아짐.
|
||||
|
||||
---
|
||||
|
||||
## **5. 총정리**
|
||||
| 개념 | 설명 |
|
||||
|------|------------------------------------------------|
|
||||
| `ActionListener` | 버튼, 메뉴 항목 등의 액션 이벤트를 처리하는 인터페이스 |
|
||||
| `actionPerformed(ActionEvent e)` | 버튼 클릭 등 액션이 발생하면 호출됨 |
|
||||
| `setActionCommand(String command)` | 특정 동작을 식별하기 위한 명령어 설정 |
|
||||
| `getActionCommand()` | 이벤트 발생 시 설정된 `ActionCommand` 값 가져오기 |
|
||||
| **구현 방식** | - **익명 클래스** (`new ActionListener() {}`) 사용<br>- **별도 클래스** 구현<br>- **람다 표현식** (`e -> { }`) 사용 |
|
||||
|
||||
---
|
||||
|
||||
## **6. 어떤 방식을 사용할까?**
|
||||
- **버튼 수가 적다면?** → `익명 클래스` 또는 `람다 표현식`
|
||||
- **여러 개의 버튼을 한 곳에서 처리해야 한다면?** → `ActionCommand` 활용
|
||||
- **큰 프로젝트에서 여러 UI 요소가 같은 이벤트를 공유해야 한다면?** → `별도 클래스`로 `ActionListener` 분리
|
||||
|
||||
위 내용을 이해하면 버튼 클릭뿐만 아니라 메뉴 항목 선택, 키보드 단축키 처리 등 다양한 Swing 이벤트 처리에 활용할 수 있습니다.
|
||||
166
docs/swing/ComboBox, JList.md
Normal file
166
docs/swing/ComboBox, JList.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# **Swing의 `JComboBox`, `JList` 및 관련 클래스 정리**
|
||||
|
||||
## **1. 주요 메서드 정리**
|
||||
|
||||
### **(1) `JComboBox` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `addItem(E item)` | 콤보박스에 항목 추가 |
|
||||
| `removeItem(Object item)` | 특정 항목 제거 |
|
||||
| `removeItemAt(int index)` | 지정한 인덱스의 항목 제거 |
|
||||
| `removeAllItems()` | 모든 항목 제거 |
|
||||
| `setSelectedItem(Object item)` | 특정 항목을 선택 상태로 설정 |
|
||||
| `getSelectedItem()` | 현재 선택된 항목 반환 |
|
||||
| `setSelectedIndex(int index)` | 특정 인덱스를 선택 상태로 설정 |
|
||||
| `getSelectedIndex()` | 현재 선택된 항목의 인덱스 반환 |
|
||||
| `setEditable(boolean b)` | 콤보박스를 편집 가능하도록 설정 |
|
||||
| `isEditable()` | 편집 가능 여부 확인 |
|
||||
| `addActionListener(ActionListener l)` | 항목 선택 시 이벤트 리스너 추가 |
|
||||
| `addItemListener(ItemListener l)` | 아이템이 선택 또는 변경될 때 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(2) `JList` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setListData(E[] listData)` | 리스트 데이터를 설정 |
|
||||
| `getSelectedValue()` | 현재 선택된 값 반환 |
|
||||
| `getSelectedValuesList()` | 여러 개의 선택된 값 리스트 반환 |
|
||||
| `setSelectedIndex(int index)` | 특정 인덱스를 선택 |
|
||||
| `setSelectedIndices(int[] indices)` | 여러 개의 항목을 선택 |
|
||||
| `getSelectedIndex()` | 현재 선택된 인덱스 반환 |
|
||||
| `getSelectedIndices()` | 선택된 인덱스 배열 반환 |
|
||||
| `setSelectionMode(int mode)` | 선택 모드 설정 (`SINGLE_SELECTION`, `MULTIPLE_INTERVAL_SELECTION` 등) |
|
||||
| `addListSelectionListener(ListSelectionListener l)` | 항목 선택 변경 시 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(3) `DefaultComboBoxModel<E>` (콤보박스 데이터 모델)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `addElement(E item)` | 모델에 항목 추가 |
|
||||
| `insertElementAt(E item, int index)` | 특정 위치에 항목 삽입 |
|
||||
| `removeElement(E obj)` | 특정 항목 제거 |
|
||||
| `removeElementAt(int index)` | 특정 인덱스의 항목 제거 |
|
||||
| `getElementAt(int index)` | 특정 인덱스의 항목 가져오기 |
|
||||
| `getSize()` | 항목 개수 반환 |
|
||||
|
||||
---
|
||||
|
||||
### **(4) `DefaultListModel<E>` (리스트 데이터 모델)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `addElement(E item)` | 리스트에 항목 추가 |
|
||||
| `insertElementAt(E item, int index)` | 특정 위치에 항목 삽입 |
|
||||
| `removeElement(E obj)` | 특정 항목 제거 |
|
||||
| `removeElementAt(int index)` | 특정 인덱스의 항목 제거 |
|
||||
| `getElementAt(int index)` | 특정 인덱스의 항목 가져오기 |
|
||||
| `getSize()` | 항목 개수 반환 |
|
||||
|
||||
---
|
||||
|
||||
## **2. 관련 이벤트 정리**
|
||||
| 이벤트 리스너 | 관련 컴포넌트 | 설명 |
|
||||
|--------------|-------------|----------------------------------|
|
||||
| `ActionListener` | `JComboBox` | 콤보박스에서 항목이 선택될 때 발생 |
|
||||
| `ItemListener` | `JComboBox`, `JList` | 항목이 선택되거나 해제될 때 발생 |
|
||||
| `ListSelectionListener` | `JList` | 리스트의 선택 항목이 변경될 때 발생 |
|
||||
| `MouseListener` | `JList` | 리스트 항목을 마우스로 클릭할 때 발생 |
|
||||
|
||||
---
|
||||
|
||||
## **3. 예제 코드 및 설명**
|
||||
|
||||
### **(1) `JComboBox` 예제 – 항목 선택 시 라벨에 출력**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class JComboBoxExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JComboBox Example");
|
||||
frame.setSize(300, 150);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new FlowLayout());
|
||||
|
||||
JLabel label = new JLabel("선택된 항목: ");
|
||||
JComboBox<String> comboBox = new JComboBox<>(new String[]{"사과", "바나나", "체리"});
|
||||
|
||||
comboBox.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
label.setText("선택된 항목: " + comboBox.getSelectedItem());
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(comboBox);
|
||||
frame.add(label);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JComboBox`에 "사과", "바나나", "체리" 항목을 추가.
|
||||
- 사용자가 선택하면 `ActionListener`가 실행되어 선택된 항목을 `JLabel`에 출력.
|
||||
|
||||
---
|
||||
|
||||
### **(2) `JList` 예제 – 여러 개 항목 선택 가능**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
public class JListExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JList Example");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
DefaultListModel<String> listModel = new DefaultListModel<>();
|
||||
listModel.addElement("Java");
|
||||
listModel.addElement("Python");
|
||||
listModel.addElement("C++");
|
||||
listModel.addElement("JavaScript");
|
||||
|
||||
JList<String> list = new JList<>(listModel);
|
||||
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
JScrollPane scrollPane = new JScrollPane(list);
|
||||
|
||||
JButton button = new JButton("선택 확인");
|
||||
JLabel label = new JLabel("선택된 항목: ");
|
||||
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
List<String> selectedValues = list.getSelectedValuesList();
|
||||
label.setText("선택된 항목: " + selectedValues);
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(scrollPane, BorderLayout.CENTER);
|
||||
frame.add(button, BorderLayout.SOUTH);
|
||||
frame.add(label, BorderLayout.NORTH);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JList`에 "Java", "Python", "C++", "JavaScript" 항목 추가.
|
||||
- 여러 개의 항목을 선택할 수 있도록 `setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)` 설정.
|
||||
- 버튼을 누르면 선택된 항목이 `JLabel`에 출력됨.
|
||||
|
||||
---
|
||||
|
||||
## **4. 총정리**
|
||||
- **`JComboBox`**: 드롭다운 목록에서 하나의 항목 선택 가능. `ActionListener`와 `ItemListener`를 사용하여 선택 이벤트 처리.
|
||||
- **`JList`**: 여러 개의 항목을 리스트 형태로 표시. 선택 모드 설정 가능 (`SINGLE_SELECTION`, `MULTIPLE_INTERVAL_SELECTION`).
|
||||
- **`DefaultComboBoxModel`, `DefaultListModel`**: 동적으로 항목을 추가/삭제할 수 있는 모델 제공.
|
||||
- **관련 이벤트**: `ActionListener` (콤보박스 선택 이벤트), `ListSelectionListener` (리스트 선택 변경 감지).
|
||||
|
||||
위 내용을 바탕으로 다양한 UI 요소를 조합하여 인터랙티브한 프로그램을 만들 수 있다!
|
||||
177
docs/swing/JEditorPane.md
Normal file
177
docs/swing/JEditorPane.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# **Swing의 `JEditorPane` 및 `EditorKit` 정리**
|
||||
|
||||
## **1. 주요 메서드 정리**
|
||||
|
||||
### **(1) `JEditorPane` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setContentType(String type)` | 문서의 MIME 타입 설정 (`text/plain`, `text/html`, `text/rtf` 등) |
|
||||
| `getContentType()` | 현재 설정된 콘텐츠 타입 반환 |
|
||||
| `setText(String text)` | 편집기에 텍스트 설정 |
|
||||
| `getText()` | 현재 편집기의 텍스트 반환 |
|
||||
| `setPage(String url)` | 지정된 URL의 내용을 로드하여 표시 |
|
||||
| `setPage(URL url)` | `URL` 객체를 이용하여 페이지 로드 |
|
||||
| `read(Reader in, Object desc)` | 입력 스트림을 이용하여 문서 로드 |
|
||||
| `write(Writer out)` | 현재 내용을 출력 스트림에 저장 |
|
||||
| `setEditable(boolean b)` | 편집 가능 여부 설정 |
|
||||
| `isEditable()` | 편집 가능 여부 확인 |
|
||||
| `setEditorKit(EditorKit kit)` | 특정 `EditorKit`을 사용하여 편집 동작 변경 |
|
||||
| `getEditorKit()` | 현재 사용 중인 `EditorKit` 반환 |
|
||||
| `replaceSelection(String content)` | 현재 선택된 텍스트를 새 텍스트로 대체 |
|
||||
| `getSelectedText()` | 현재 선택된 텍스트 반환 |
|
||||
| `addHyperlinkListener(HyperlinkListener l)` | 하이퍼링크 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(2) `EditorKit` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `createDefaultDocument()` | 새 기본 문서 생성 |
|
||||
| `read(Reader in, Document doc, int pos)` | 주어진 리더(`Reader`)에서 문서를 읽어옴 |
|
||||
| `write(Writer out, Document doc, int pos, int len)` | 문서를 지정된 범위 내에서 출력 스트림으로 저장 |
|
||||
| `install(JEditorPane c)` | 특정 `JEditorPane`에 `EditorKit`을 설치 |
|
||||
| `deinstall(JEditorPane c)` | `JEditorPane`에서 `EditorKit`을 제거 |
|
||||
| `getViewFactory()` | 문서의 뷰를 생성하는 `ViewFactory` 반환 |
|
||||
|
||||
---
|
||||
|
||||
## **2. 관련 이벤트 정리**
|
||||
| 이벤트 리스너 | 관련 컴포넌트 | 설명 |
|
||||
|--------------|-------------|----------------------------------|
|
||||
| `HyperlinkListener` | `JEditorPane` | HTML 문서에서 하이퍼링크 클릭 시 발생 |
|
||||
| `DocumentListener` | `JEditorPane` | 문서의 변경(삽입, 삭제 등)이 발생할 때 감지 |
|
||||
| `CaretListener` | `JEditorPane` | 커서(캐럿) 위치가 변경될 때 발생 |
|
||||
| `KeyListener` | `JEditorPane` | 키보드 입력을 감지 |
|
||||
|
||||
---
|
||||
|
||||
## **3. 예제 코드 및 설명**
|
||||
|
||||
### **(1) 기본적인 `JEditorPane` 사용**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
|
||||
public class JEditorPaneExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JEditorPane Example");
|
||||
frame.setSize(500, 400);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
JEditorPane editorPane = new JEditorPane();
|
||||
editorPane.setContentType("text/plain");
|
||||
editorPane.setText("여기에 텍스트를 입력하세요.");
|
||||
editorPane.setEditable(true);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(editorPane);
|
||||
frame.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JEditorPane`을 생성하고 `setContentType("text/plain")`을 설정하여 일반 텍스트를 편집할 수 있도록 함.
|
||||
- `setEditable(true)`로 설정하여 편집 가능하도록 함.
|
||||
- `JScrollPane`을 사용하여 스크롤 가능하도록 추가.
|
||||
|
||||
---
|
||||
|
||||
### **(2) HTML 문서 표시 및 하이퍼링크 이벤트 처리**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import javax.swing.event.HyperlinkListener;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
|
||||
public class JEditorPaneHyperlinkExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JEditorPane - HTML Example");
|
||||
frame.setSize(600, 400);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
JEditorPane editorPane = new JEditorPane();
|
||||
editorPane.setContentType("text/html");
|
||||
editorPane.setText("<html><body>"
|
||||
+ "<h1>HTML 지원</h1>"
|
||||
+ "<p>이것은 <b>HTML</b> 형식의 문서입니다.</p>"
|
||||
+ "<a href='https://www.example.com'>이 링크를 클릭하세요</a>"
|
||||
+ "</body></html>");
|
||||
editorPane.setEditable(false);
|
||||
|
||||
// 하이퍼링크 이벤트 처리
|
||||
editorPane.addHyperlinkListener(new HyperlinkListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
|
||||
try {
|
||||
editorPane.setPage(e.getURL()); // 링크 클릭 시 해당 페이지 로드
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(editorPane);
|
||||
frame.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JEditorPane`을 생성하고 `setContentType("text/html")`을 설정하여 HTML을 표시할 수 있도록 함.
|
||||
- `addHyperlinkListener()`를 이용해 하이퍼링크 클릭 시 이벤트를 감지하여 `setPage(e.getURL())`로 페이지를 로드하도록 함.
|
||||
- `setEditable(false)`로 설정하여 사용자가 직접 편집하지 못하도록 함.
|
||||
|
||||
---
|
||||
|
||||
### **(3) `EditorKit`을 활용한 RTF 문서 편집**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.rtf.RTFEditorKit;
|
||||
import java.awt.*;
|
||||
|
||||
public class JEditorPaneRTFExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JEditorPane - RTF Example");
|
||||
frame.setSize(600, 400);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
JEditorPane editorPane = new JEditorPane();
|
||||
editorPane.setEditorKit(new RTFEditorKit()); // RTF 전용 EditorKit 설정
|
||||
editorPane.setText("{\\rtf1\\ansi This is RTF formatted text.}");
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(editorPane);
|
||||
frame.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `RTFEditorKit`을 사용하여 RTF(리치 텍스트 포맷) 문서를 편집할 수 있도록 설정.
|
||||
- `setEditorKit(new RTFEditorKit())`을 통해 `JEditorPane`에 적용.
|
||||
- RTF 포맷의 기본 문서를 설정하여 편집 가능하도록 함.
|
||||
|
||||
---
|
||||
|
||||
## **4. 총정리**
|
||||
- **`JEditorPane`**: `JTextPane`과 유사하지만 HTML, RTF, 일반 텍스트 등 다양한 문서 형식을 지원.
|
||||
- **`EditorKit`**: 특정 문서 포맷을 편집할 수 있도록 지원하는 클래스 (`RTFEditorKit`, `HTMLEditorKit` 등).
|
||||
- **이벤트 처리**:
|
||||
- `HyperlinkListener` → HTML 문서에서 링크 클릭 감지.
|
||||
- `DocumentListener` → 문서 변경 감지.
|
||||
- `CaretListener` → 커서 이동 감지.
|
||||
- **활용 예제**:
|
||||
- 일반 텍스트 편집기
|
||||
- HTML 문서 뷰어 및 하이퍼링크 처리
|
||||
- RTF 문서 편집기
|
||||
|
||||
위 내용을 활용하면 웹 기반 문서 편집기나 간단한 브라우저 기능을 구현할 수도 있다!
|
||||
69
docs/swing/README.md
Normal file
69
docs/swing/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
다음은 실무 위주의 **자바 Swing** 책 목차 초안이다.
|
||||
|
||||
---
|
||||
|
||||
# **실무에서 바로 쓰는 자바 Swing**
|
||||
|
||||
## **1. 개요 및 환경 설정**
|
||||
1.1. Swing이란? (AWT와의 차이)
|
||||
1.2. JavaFX와 비교: 언제 Swing을 선택할까?
|
||||
1.3. JDK 설치 및 개발 환경 설정
|
||||
1.4. IntelliJ IDEA와 Eclipse에서 Swing 프로젝트 생성
|
||||
|
||||
## **2. Swing의 기본 구성 요소**
|
||||
2.1. JFrame과 창 다루기
|
||||
2.2. JPanel과 레이아웃 기본
|
||||
2.3. JLabel, JButton, JTextField의 활용
|
||||
2.4. 이벤트 리스너 개념 및 적용
|
||||
|
||||
## **3. 레이아웃 매니저 활용**
|
||||
3.1. FlowLayout, BorderLayout, GridLayout
|
||||
3.2. BoxLayout, CardLayout, GridBagLayout
|
||||
3.3. 사용자 정의 레이아웃 적용하기
|
||||
3.4. 레이아웃 없이 직접 컴포넌트 배치하기
|
||||
|
||||
## **4. 고급 컴포넌트 활용**
|
||||
4.1. JTable을 활용한 데이터 표시
|
||||
4.2. JTree를 활용한 계층 구조 표현
|
||||
4.3. JList와 JComboBox의 활용
|
||||
4.4. JTabbedPane, JSplitPane을 활용한 UI 구성
|
||||
|
||||
## **5. 이벤트 처리 심화**
|
||||
5.1. ActionListener, MouseListener, KeyListener
|
||||
5.2. 익명 클래스와 람다 표현식 활용
|
||||
5.3. Adapter 클래스 활용
|
||||
5.4. 키보드 단축키 및 마우스 이벤트 조합
|
||||
|
||||
## **6. 멀티스레딩과 Swing**
|
||||
6.1. Swing에서의 스레드 문제 (EDT 개념)
|
||||
6.2. `SwingWorker`를 활용한 비동기 처리
|
||||
6.3. ProgressBar와 백그라운드 작업 처리
|
||||
6.4. 실시간 UI 업데이트 기법
|
||||
|
||||
## **7. 파일 및 데이터베이스 연동**
|
||||
7.1. JFileChooser를 활용한 파일 입출력
|
||||
7.2. CSV, JSON 파일 읽고 쓰기
|
||||
7.3. JDBC를 이용한 MySQL, PostgreSQL 연동
|
||||
7.4. 테이블 데이터와 JTable 연동
|
||||
|
||||
## **8. UI 커스터마이징과 테마 적용**
|
||||
8.1. 기본 Look and Feel 변경하기
|
||||
8.2. FlatLaf, JTattoo 등을 이용한 테마 적용
|
||||
8.3. 사용자 정의 컴포넌트 만들기
|
||||
8.4. 애니메이션 효과 적용
|
||||
|
||||
## **9. 네트워크 및 외부 API 연동**
|
||||
9.1. Swing에서 HTTP 요청 보내기
|
||||
9.2. WebSocket을 활용한 실시간 데이터 표시
|
||||
9.3. JSON 데이터 파싱 및 UI 적용
|
||||
9.4. RSS, REST API 데이터를 Swing UI에서 보여주기
|
||||
|
||||
## **10. 배포 및 실전 프로젝트**
|
||||
10.1. JAR 파일 및 실행 파일 생성
|
||||
10.2. Java Web Start 및 JNLP 활용
|
||||
10.3. Spring Boot와 연동하여 관리 UI 개발
|
||||
10.4. 실전 프로젝트: CRUD 기능이 포함된 데스크톱 앱 제작
|
||||
|
||||
---
|
||||
|
||||
이 책은 실무에서 Swing을 활용하여 즉시 사용할 수 있는 내용을 중심으로 구성되었다. 필요한 부분이 있으면 추가해도 좋겠다.
|
||||
192
docs/swing/입력 컴포넌트.md
Normal file
192
docs/swing/입력 컴포넌트.md
Normal file
@@ -0,0 +1,192 @@
|
||||
## **1. 주요 입력 컴포넌트 메서드 정리**
|
||||
|
||||
### **(1) `JTextField` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setText(String text)` | 텍스트 필드에 문자열을 설정 |
|
||||
| `getText()` | 현재 입력된 텍스트를 반환 |
|
||||
| `setEditable(boolean b)` | 편집 가능 여부 설정 (`false`이면 읽기 전용) |
|
||||
| `setColumns(int columns)` | 텍스트 필드의 너비(문자 개수 단위) 설정 |
|
||||
| `addActionListener(ActionListener l)` | Enter 키 입력 시 이벤트 리스너 추가 |
|
||||
| `setHorizontalAlignment(int alignment)` | 텍스트 정렬 방식 설정 (`LEFT`, `CENTER`, `RIGHT`) |
|
||||
|
||||
---
|
||||
|
||||
### **(2) `JPasswordField` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setText(String text)` | 비밀번호 필드에 문자열 설정 |
|
||||
| `getPassword()` | 현재 입력된 비밀번호를 `char[]` 배열로 반환 (보안상 `getText()` 대신 사용) |
|
||||
| `setEchoChar(char c)` | 입력된 문자 대신 표시할 마스킹 문자 설정 (`*` 등) |
|
||||
| `setEditable(boolean b)` | 편집 가능 여부 설정 |
|
||||
| `addActionListener(ActionListener l)` | Enter 키 입력 시 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(3) `JTextArea` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setText(String text)` | 텍스트 영역에 문자열 설정 |
|
||||
| `getText()` | 현재 입력된 텍스트를 반환 |
|
||||
| `append(String text)` | 현재 텍스트 뒤에 새로운 문자열 추가 |
|
||||
| `setEditable(boolean b)` | 편집 가능 여부 설정 |
|
||||
| `setRows(int rows)` | 텍스트 영역의 행 개수 설정 |
|
||||
| `setColumns(int columns)` | 텍스트 영역의 열 개수 설정 |
|
||||
| `setLineWrap(boolean b)` | 자동 줄바꿈 여부 설정 (`true`이면 줄이 길어지면 자동 개행) |
|
||||
| `setWrapStyleWord(boolean b)` | 단어 단위로 줄바꿈할지 설정 (`true`이면 단어 기준으로 개행) |
|
||||
|
||||
---
|
||||
|
||||
## **2. 관련 이벤트 정리**
|
||||
|
||||
| 이벤트 리스너 | 관련 컴포넌트 | 설명 |
|
||||
|--------------|-------------|----------------------------------|
|
||||
| `ActionListener` | `JTextField`, `JPasswordField` | 사용자가 Enter 키를 눌렀을 때 발생 |
|
||||
| `KeyListener` | `JTextField`, `JPasswordField`, `JTextArea` | 키보드 입력(키 누름, 해제, 입력) 감지 |
|
||||
| `FocusListener` | `JTextField`, `JPasswordField`, `JTextArea` | 입력 필드에 포커스가 들어오거나 빠질 때 감지 |
|
||||
| `DocumentListener` | `JTextField`, `JPasswordField`, `JTextArea` | 텍스트 변경 감지 (입력, 삭제, 변경) |
|
||||
|
||||
---
|
||||
|
||||
## **3. Swing 입력 컴포넌트 예제 코드와 설명**
|
||||
|
||||
### **(1) `JTextField` 예제 – 입력값을 버튼 클릭 시 표시**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class TextFieldExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JTextField Example");
|
||||
frame.setSize(300, 150);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new FlowLayout());
|
||||
|
||||
JLabel label = new JLabel("이름:");
|
||||
JTextField textField = new JTextField(15);
|
||||
JButton button = new JButton("확인");
|
||||
JLabel resultLabel = new JLabel("");
|
||||
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String name = textField.getText();
|
||||
resultLabel.setText("입력된 이름: " + name);
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(label);
|
||||
frame.add(textField);
|
||||
frame.add(button);
|
||||
frame.add(resultLabel);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JTextField`에 이름을 입력하고,
|
||||
- 버튼 클릭 시 `JLabel`에 입력된 값이 표시됨.
|
||||
- `ActionListener`를 사용하여 버튼 클릭 이벤트를 처리.
|
||||
|
||||
---
|
||||
|
||||
### **(2) `JPasswordField` 예제 – 비밀번호 입력 확인**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PasswordFieldExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JPasswordField Example");
|
||||
frame.setSize(300, 150);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new FlowLayout());
|
||||
|
||||
JLabel label = new JLabel("비밀번호:");
|
||||
JPasswordField passwordField = new JPasswordField(15);
|
||||
JButton button = new JButton("로그인");
|
||||
JLabel resultLabel = new JLabel("");
|
||||
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
char[] password = passwordField.getPassword();
|
||||
if (Arrays.equals(password, "1234".toCharArray())) {
|
||||
resultLabel.setText("로그인 성공!");
|
||||
} else {
|
||||
resultLabel.setText("로그인 실패!");
|
||||
}
|
||||
Arrays.fill(password, ' '); // 보안상 배열 비우기
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(label);
|
||||
frame.add(passwordField);
|
||||
frame.add(button);
|
||||
frame.add(resultLabel);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JPasswordField`에 비밀번호를 입력하고,
|
||||
- 버튼 클릭 시 비밀번호가 `"1234"`와 일치하면 "로그인 성공!", 아니면 "로그인 실패!" 출력.
|
||||
- `getPassword()`를 사용하여 보안 강화 (`getText()` 대신 사용).
|
||||
|
||||
---
|
||||
|
||||
### **(3) `JTextArea` 예제 – 여러 줄 입력 필드**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class TextAreaExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("JTextArea Example");
|
||||
frame.setSize(400, 250);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
JTextArea textArea = new JTextArea(10, 30);
|
||||
textArea.setLineWrap(true);
|
||||
textArea.setWrapStyleWord(true);
|
||||
|
||||
JButton button = new JButton("입력 내용 확인");
|
||||
JLabel resultLabel = new JLabel("출력: ");
|
||||
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
resultLabel.setText("출력: " + textArea.getText());
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(new JScrollPane(textArea), BorderLayout.CENTER);
|
||||
frame.add(button, BorderLayout.SOUTH);
|
||||
frame.add(resultLabel, BorderLayout.NORTH);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JTextArea`는 여러 줄 입력이 가능.
|
||||
- `setLineWrap(true)`와 `setWrapStyleWord(true)`를 설정하면 단어 단위로 자동 줄바꿈.
|
||||
- `JScrollPane`을 사용하면 스크롤 기능 추가 가능.
|
||||
- 버튼 클릭 시 입력된 내용을 `JLabel`에 표시.
|
||||
|
||||
---
|
||||
|
||||
### **총정리**
|
||||
- **`JTextField`**: 한 줄 입력 필드, Enter 키 감지 가능.
|
||||
- **`JPasswordField`**: 보안 입력 필드, `getPassword()`를 사용해야 함.
|
||||
- **`JTextArea`**: 여러 줄 입력 가능, 자동 줄바꿈 기능 제공.
|
||||
- **관련 이벤트**: `ActionListener` (Enter 키 감지), `KeyListener` (키 입력 감지), `FocusListener` (포커스 감지).
|
||||
|
||||
위 내용을 기반으로 다양한 입력 폼을 만들 수 있다!
|
||||
215
docs/swing/컴포넌트.md
Normal file
215
docs/swing/컴포넌트.md
Normal file
@@ -0,0 +1,215 @@
|
||||
## **1. 주요 Swing 컴포넌트 메서드 정리**
|
||||
|
||||
### **(1) `JLabel` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setText(String text)` | 레이블의 텍스트를 설정 |
|
||||
| `getText()` | 레이블의 현재 텍스트 반환 |
|
||||
| `setIcon(Icon icon)` | 아이콘을 설정 |
|
||||
| `getIcon()` | 현재 설정된 아이콘 반환 |
|
||||
| `setHorizontalAlignment(int alignment)` | 텍스트/아이콘의 수평 정렬 설정 (`SwingConstants.LEFT`, `CENTER`, `RIGHT`) |
|
||||
| `setVerticalAlignment(int alignment)` | 텍스트/아이콘의 수직 정렬 설정 (`TOP`, `CENTER`, `BOTTOM`) |
|
||||
|
||||
---
|
||||
|
||||
### **(2) `JButton` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setText(String text)` | 버튼의 텍스트 설정 |
|
||||
| `getText()` | 버튼의 현재 텍스트 반환 |
|
||||
| `setEnabled(boolean b)` | 버튼 활성화/비활성화 |
|
||||
| `setIcon(Icon icon)` | 버튼의 아이콘 설정 |
|
||||
| `addActionListener(ActionListener l)` | 클릭 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(3) `JToggleButton` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setSelected(boolean b)` | 토글 버튼의 선택 상태 설정 |
|
||||
| `isSelected()` | 현재 선택 상태 반환 |
|
||||
| `addItemListener(ItemListener l)` | 상태 변경 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(4) `JCheckBox` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setSelected(boolean b)` | 체크박스의 선택 여부 설정 |
|
||||
| `isSelected()` | 현재 선택 상태 반환 |
|
||||
| `addItemListener(ItemListener l)` | 체크박스 상태 변경 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(5) `JRadioButton` 메서드**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setSelected(boolean b)` | 라디오 버튼의 선택 상태 설정 |
|
||||
| `isSelected()` | 현재 선택 상태 반환 |
|
||||
| `setText(String text)` | 버튼의 텍스트 설정 |
|
||||
| `addItemListener(ItemListener l)` | 상태 변경 이벤트 리스너 추가 |
|
||||
|
||||
---
|
||||
|
||||
### **(6) `JComponent` 메서드 (모든 Swing 컴포넌트의 공통 메서드)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setBackground(Color c)` | 배경색 설정 |
|
||||
| `setForeground(Color c)` | 전경색(글자색) 설정 |
|
||||
| `setFont(Font f)` | 폰트 설정 |
|
||||
| `setBounds(int x, int y, int width, int height)` | 컴포넌트의 위치와 크기 설정 |
|
||||
| `setToolTipText(String text)` | 툴팁(마우스 오버 시 표시되는 설명) 설정 |
|
||||
|
||||
---
|
||||
|
||||
### **(7) `AbstractButton` 메서드 (JButton, JToggleButton, JCheckBox, JRadioButton의 공통 메서드)**
|
||||
| 메서드 | 설명 |
|
||||
|--------|------------------------------------------|
|
||||
| `setText(String text)` | 버튼의 텍스트 설정 |
|
||||
| `getText()` | 버튼의 현재 텍스트 반환 |
|
||||
| `setIcon(Icon icon)` | 버튼의 아이콘 설정 |
|
||||
| `setSelected(boolean b)` | 선택 여부 설정 (토글 버튼 및 체크박스에서 사용) |
|
||||
| `isSelected()` | 현재 선택 여부 반환 |
|
||||
| `addActionListener(ActionListener l)` | 액션 이벤트 추가 |
|
||||
|
||||
---
|
||||
|
||||
## **2. 관련 이벤트 정리**
|
||||
|
||||
| 이벤트 리스너 | 관련 컴포넌트 | 설명 |
|
||||
|--------------|-------------|----------------------------------|
|
||||
| `ActionListener` | `JButton`, `JToggleButton`, `JCheckBox`, `JRadioButton` | 버튼 클릭 시 동작 |
|
||||
| `ItemListener` | `JToggleButton`, `JCheckBox`, `JRadioButton` | 선택 상태가 변경될 때 동작 |
|
||||
| `MouseListener` | `JLabel`, `JButton`, `JToggleButton`, `JCheckBox`, `JRadioButton` | 마우스 클릭, 이동 등 감지 |
|
||||
|
||||
---
|
||||
|
||||
## **3. Swing 컴포넌트 예제 코드와 설명**
|
||||
|
||||
### **(1) `JLabel`과 `JButton`을 이용한 간단한 GUI**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class SwingExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("Swing Example");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new FlowLayout());
|
||||
|
||||
JLabel label = new JLabel("버튼을 눌러보세요!");
|
||||
JButton button = new JButton("클릭");
|
||||
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
label.setText("버튼이 클릭됨!");
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(label);
|
||||
frame.add(button);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JLabel`을 사용해 텍스트를 출력하고,
|
||||
- `JButton`을 클릭하면 `JLabel`의 텍스트가 변경된다.
|
||||
- `ActionListener`를 사용하여 버튼 이벤트를 감지한다.
|
||||
|
||||
---
|
||||
|
||||
### **(2) `JToggleButton`과 `JCheckBox` 사용 예제**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
public class ToggleExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("Toggle Example");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new FlowLayout());
|
||||
|
||||
JToggleButton toggleButton = new JToggleButton("OFF");
|
||||
JCheckBox checkBox = new JCheckBox("체크박스");
|
||||
|
||||
toggleButton.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if (toggleButton.isSelected()) {
|
||||
toggleButton.setText("ON");
|
||||
} else {
|
||||
toggleButton.setText("OFF");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(toggleButton);
|
||||
frame.add(checkBox);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JToggleButton`은 클릭할 때마다 "ON"/"OFF"로 상태가 바뀜.
|
||||
- `ItemListener`를 사용하여 상태 변경을 감지.
|
||||
- `JCheckBox`는 별도의 기능 없이 화면에 추가됨.
|
||||
|
||||
---
|
||||
|
||||
### **(3) `JRadioButton`을 사용한 단일 선택 예제**
|
||||
```java
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class RadioButtonExample {
|
||||
public static void main(String[] args) {
|
||||
JFrame frame = new JFrame("Radio Button Example");
|
||||
frame.setSize(300, 200);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLayout(new FlowLayout());
|
||||
|
||||
JRadioButton option1 = new JRadioButton("옵션 1");
|
||||
JRadioButton option2 = new JRadioButton("옵션 2");
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
group.add(option1);
|
||||
group.add(option2);
|
||||
|
||||
JButton button = new JButton("선택 확인");
|
||||
JLabel label = new JLabel("선택된 옵션: 없음");
|
||||
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (option1.isSelected()) {
|
||||
label.setText("선택된 옵션: 옵션 1");
|
||||
} else if (option2.isSelected()) {
|
||||
label.setText("선택된 옵션: 옵션 2");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frame.add(option1);
|
||||
frame.add(option2);
|
||||
frame.add(button);
|
||||
frame.add(label);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
**설명:**
|
||||
- `JRadioButton`을 사용해 하나의 옵션만 선택 가능하도록 `ButtonGroup`을 사용.
|
||||
- `JButton`을 클릭하면 선택된 옵션이 `JLabel`에 표시됨.
|
||||
|
||||
---
|
||||
|
||||
이제 **Swing의 주요 컴포넌트, 이벤트, 예제 코드**까지 완벽하게 정리되었다.
|
||||
391
docs/validation.md
Normal file
391
docs/validation.md
Normal file
@@ -0,0 +1,391 @@
|
||||
스프링 부트의 Validation에 대해 설명하는 글을 작성하겠습니다. 스프링 부트에서 Validation은 주로 Bean Validation API(Jakarta Validation, 이전 Hibernate Validator 기반)를 활용하여 요청 데이터의 유효성을 검사하는 데 사용됩니다. 이를 통해 입력값이 요구사항을 충족하는지 확인하고, 잘못된 데이터가 애플리케이션 로직에 들어가는 것을 방지할 수 있습니다. 스프링 부트는 `@Valid`와 같은 어노테이션을 통해 이를 컨트롤러에서 쉽게 적용할 수 있게 지원합니다.
|
||||
|
||||
아래에서는 Validation에 사용되는 주요 어노테이션을 표로 정리한 뒤, 각 어노테이션에 대한 설명과 예시를 상세히 다루겠습니다.
|
||||
|
||||
---
|
||||
|
||||
### 스프링 부트 Validation 주요 어노테이션 표
|
||||
|
||||
| 어노테이션 | 설명 |
|
||||
|-----------------------|----------------------------------------------------------------------------------------|
|
||||
| `@NotNull` | 값이 null이 아니어야 함 |
|
||||
| `@NotBlank` | 문자열이 null이 아니고, 공백이 아닌 문자를 포함해야 함 |
|
||||
| `@NotEmpty` | 컬렉션, 배열, 문자열 등이 null이거나 비어 있지 않아야 함 |
|
||||
| `@Size` | 문자열, 컬렉션, 배열 등의 크기가 지정된 범위 내에 있어야 함 |
|
||||
| `@Min` | 숫자가 지정된 최소값 이상이어야 함 |
|
||||
| `@Max` | 숫자가 지정된 최대값 이하여야 함 |
|
||||
| `@Pattern` | 값이 지정된 정규 표현식에 맞아야 함 |
|
||||
| `@Email` | 문자열이 유효한 이메일 형식인지 확인 |
|
||||
| `@Positive` | 숫자가 0보다 커야 함 |
|
||||
| `@PositiveOrZero` | 숫자가 0 이상이어야 함 |
|
||||
| `@Negative` | 숫자가 0보다 작아야 함 |
|
||||
| `@NegativeOrZero` | 숫자가 0 이하여야 함 |
|
||||
| `@Future` | 날짜가 현재보다 미래여야 함 |
|
||||
| `@FutureOrPresent` | 날짜가 현재 또는 미래여야 함 |
|
||||
| `@Past` | 날짜가 현재보다 과거여야 함 |
|
||||
| `@PastOrPresent` | 날짜가 현재 또는 과거여야 함 |
|
||||
| `@AssertTrue` | 값이 true여야 함 (논리 검증용) |
|
||||
| `@AssertFalse` | 값이 false여야 함 (논리 검증용) |
|
||||
| `@Digits` | 숫자가 지정된 자릿수(정수, 소수) 범위 내에 있어야 함 |
|
||||
|
||||
---
|
||||
|
||||
### 각 어노테이션 상세 설명 및 예시
|
||||
|
||||
#### 프로젝트 설정
|
||||
예시를 실행하려면 `pom.xml`에 다음 의존성을 추가해야 합니다:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 1. `@NotNull`
|
||||
- **설명**: 필드가 null이 아니어야 합니다. 공백 문자열("")은 허용됩니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public class User {
|
||||
@NotNull(message = "이름은 null일 수 없습니다.")
|
||||
private String name;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
}
|
||||
|
||||
@RestController
|
||||
public class UserController {
|
||||
@PostMapping("/user")
|
||||
public ResponseEntity<String> createUser(@Valid @RequestBody User user) {
|
||||
return ResponseEntity.ok("유저 생성 성공");
|
||||
}
|
||||
}
|
||||
// 요청: {"name": null} -> 오류: "이름은 null일 수 없습니다."
|
||||
```
|
||||
|
||||
#### 2. `@NotBlank`
|
||||
- **설명**: 문자열이 null이 아니고, 공백이 아닌 문자를 포함해야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class User {
|
||||
@NotBlank(message = "이름은 비어 있을 수 없습니다.")
|
||||
private String name;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
}
|
||||
// 요청: {"name": ""} -> 오류: "이름은 비어 있을 수 없습니다."
|
||||
```
|
||||
|
||||
#### 3. `@NotEmpty`
|
||||
- **설명**: 컬렉션, 배열, 문자열 등이 null이거나 비어 있지 않아야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
public class User {
|
||||
@NotEmpty(message = "취미 목록은 비어 있을 수 없습니다.")
|
||||
private List<String> hobbies;
|
||||
|
||||
public List<String> getHobbies() { return hobbies; }
|
||||
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
|
||||
}
|
||||
// 요청: {"hobbies": []} -> 오류: "취미 목록은 비어 있을 수 없습니다."
|
||||
```
|
||||
|
||||
#### 4. `@Size`
|
||||
- **설명**: 문자열, 컬렉션 등의 크기가 지정된 범위 내에 있어야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class User {
|
||||
@Size(min = 2, max = 10, message = "이름은 2~10자여야 합니다.")
|
||||
private String name;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
}
|
||||
// 요청: {"name": "A"} -> 오류: "이름은 2~10자여야 합니다."
|
||||
```
|
||||
|
||||
#### 5. `@Min`
|
||||
- **설명**: 숫자가 지정된 최소값 이상이어야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
public class User {
|
||||
@Min(value = 18, message = "나이는 18 이상이어야 합니다.")
|
||||
private int age;
|
||||
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
}
|
||||
// 요청: {"age": 15} -> 오류: "나이는 18 이상이어야 합니다."
|
||||
```
|
||||
|
||||
#### 6. `@Max`
|
||||
- **설명**: 숫자가 지정된 최대값 이하여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Max;
|
||||
|
||||
public class User {
|
||||
@Max(value = 100, message = "나이는 100 이하여야 합니다.")
|
||||
private int age;
|
||||
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
}
|
||||
// 요청: {"age": 150} -> 오류: "나이는 100 이하여야 합니다."
|
||||
```
|
||||
|
||||
#### 7. `@Pattern`
|
||||
- **설명**: 값이 지정된 정규 표현식과 일치해야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public class User {
|
||||
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "아이디는 영문자와 숫자만 가능합니다.")
|
||||
private String username;
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
}
|
||||
// 요청: {"username": "user#"} -> 오류: "아이디는 영문자와 숫자만 가능합니다."
|
||||
```
|
||||
|
||||
#### 8. `@Email`
|
||||
- **설명**: 문자열이 유효한 이메일 형식이어야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Email;
|
||||
|
||||
public class User {
|
||||
@Email(message = "유효한 이메일 형식이어야 합니다.")
|
||||
private String email;
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
}
|
||||
// 요청: {"email": "invalid-email"} -> 오류: "유효한 이메일 형식이어야 합니다."
|
||||
```
|
||||
|
||||
#### 9. `@Positive`
|
||||
- **설명**: 숫자가 0보다 커야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
public class User {
|
||||
@Positive(message = "나이는 양수여야 합니다.")
|
||||
private int age;
|
||||
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
}
|
||||
// 요청: {"age": -5} -> 오류: "나이는 양수여야 합니다."
|
||||
```
|
||||
|
||||
#### 10. `@PositiveOrZero`
|
||||
- **설명**: 숫자가 0 이상이어야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
|
||||
public class User {
|
||||
@PositiveOrZero(message = "나이는 0 이상이어야 합니다.")
|
||||
private int age;
|
||||
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
}
|
||||
// 요청: {"age": -1} -> 오류: "나이는 0 이상이어야 합니다."
|
||||
```
|
||||
|
||||
#### 11. `@Negative`
|
||||
- **설명**: 숫자가 0보다 작아야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Negative;
|
||||
|
||||
public class User {
|
||||
@Negative(message = "값은 음수여야 합니다.")
|
||||
private int value;
|
||||
|
||||
public int getValue() { return value; }
|
||||
public void setValue(int value) { this.value = value; }
|
||||
}
|
||||
// 요청: {"value": 5} -> 오류: "값은 음수여야 합니다."
|
||||
```
|
||||
|
||||
#### 12. `@NegativeOrZero`
|
||||
- **설명**: 숫자가 0 이하여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.NegativeOrZero;
|
||||
|
||||
public class User {
|
||||
@NegativeOrZero(message = "값은 0 이하여야 합니다.")
|
||||
private int value;
|
||||
|
||||
public int getValue() { return value; }
|
||||
public void setValue(int value) { this.value = value; }
|
||||
}
|
||||
// 요청: {"value": 1} -> 오류: "값은 0 이하여야 합니다."
|
||||
```
|
||||
|
||||
#### 13. `@Future`
|
||||
- **설명**: 날짜가 현재보다 미래여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Future;
|
||||
|
||||
public class Event {
|
||||
@Future(message = "이벤트 날짜는 미래여야 합니다.")
|
||||
private LocalDate eventDate;
|
||||
|
||||
public LocalDate getEventDate() { return eventDate; }
|
||||
public void setEventDate(LocalDate eventDate) { this.eventDate = eventDate; }
|
||||
}
|
||||
// 요청: {"eventDate": "2023-01-01"} -> 오류: "이벤트 날짜는 미래여야 합니다."
|
||||
```
|
||||
|
||||
#### 14. `@FutureOrPresent`
|
||||
- **설명**: 날짜가 현재 또는 미래여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.FutureOrPresent;
|
||||
|
||||
public class Event {
|
||||
@FutureOrPresent(message = "이벤트 날짜는 현재 또는 미래여야 합니다.")
|
||||
private LocalDate eventDate;
|
||||
|
||||
public LocalDate getEventDate() { return eventDate; }
|
||||
public void setEventDate(LocalDate eventDate) { this.eventDate = eventDate; }
|
||||
}
|
||||
// 요청: {"eventDate": "2023-01-01"} -> 오류: "이벤트 날짜는 현재 또는 미래여야 합니다."
|
||||
```
|
||||
|
||||
#### 15. `@Past`
|
||||
- **설명**: 날짜가 현재보다 과거여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Past;
|
||||
|
||||
public class User {
|
||||
@Past(message = "생일은 과거여야 합니다.")
|
||||
private LocalDate birthDate;
|
||||
|
||||
public LocalDate getBirthDate() { return birthDate; }
|
||||
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
|
||||
}
|
||||
// 요청: {"birthDate": "2026-01-01"} -> 오류: "생일은 과거여야 합니다."
|
||||
```
|
||||
|
||||
#### 16. `@PastOrPresent`
|
||||
- **설명**: 날짜가 현재 또는 과거여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.PastOrPresent;
|
||||
|
||||
public class User {
|
||||
@PastOrPresent(message = "생일은 현재 또는 과거여야 합니다.")
|
||||
private LocalDate birthDate;
|
||||
|
||||
public LocalDate getBirthDate() { return birthDate; }
|
||||
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
|
||||
}
|
||||
// 요청: {"birthDate": "2026-01-01"} -> 오류: "생일은 현재 또는 과거여야 합니다."
|
||||
```
|
||||
|
||||
#### 17. `@AssertTrue`
|
||||
- **설명**: 값이 true여야 합니다. 논리 검증에 유용합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
|
||||
public class User {
|
||||
private boolean active;
|
||||
|
||||
@AssertTrue(message = "사용자는 활성화 상태여야 합니다.")
|
||||
public boolean isActive() { return active; }
|
||||
|
||||
public void setActive(boolean active) { this.active = active; }
|
||||
}
|
||||
// 요청: {"active": false} -> 오류: "사용자는 활성화 상태여야 합니다."
|
||||
```
|
||||
|
||||
#### 18. `@AssertFalse`
|
||||
- **설명**: 값이 false여야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.AssertFalse;
|
||||
|
||||
public class User {
|
||||
private boolean deleted;
|
||||
|
||||
@AssertFalse(message = "사용자는 삭제 상태일 수 없습니다.")
|
||||
public boolean isDeleted() { return deleted; }
|
||||
|
||||
public void setDeleted(boolean deleted) { this.deleted = deleted; }
|
||||
}
|
||||
// 요청: {"deleted": true} -> 오류: "사용자는 삭제 상태일 수 없습니다."
|
||||
```
|
||||
|
||||
#### 19. `@Digits`
|
||||
- **설명**: 숫자의 정수 및 소수 자릿수가 지정된 범위 내에 있어야 합니다.
|
||||
- **예시**:
|
||||
```java
|
||||
import jakarta.validation.constraints.Digits;
|
||||
|
||||
public class Product {
|
||||
@Digits(integer = 3, fraction = 2, message = "가격은 정수 3자리, 소수 2자리여야 합니다.")
|
||||
private BigDecimal price;
|
||||
|
||||
public BigDecimal getPrice() { return price; }
|
||||
public void setPrice(BigDecimal price) { this.price = price; }
|
||||
}
|
||||
// 요청: {"price": 1234.567} -> 오류: "가격은 정수 3자리, 소수 2자리여야 합니다."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 오류 처리 예시
|
||||
Validation 오류를 처리하려면 `BindingResult`를 사용하거나, 전역 예외 처리를 설정할 수 있습니다:
|
||||
```java
|
||||
@RestController
|
||||
public class UserController {
|
||||
@PostMapping("/user")
|
||||
public ResponseEntity<String> createUser(@Valid @RequestBody User user, BindingResult result) {
|
||||
if (result.hasErrors()) {
|
||||
return ResponseEntity.badRequest().body(result.getAllErrors().get(0).getDefaultMessage());
|
||||
}
|
||||
return ResponseEntity.ok("유저 생성 성공");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 전역 예외 처리
|
||||
```java
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
return ResponseEntity.badRequest().body(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
스프링 부트의 Validation은 데이터 유효성 검사를 간편하고 체계적으로 수행할 수 있게 해줍니다. 위 어노테이션들을 활용하면 숫자, 문자열, 날짜 등 다양한 데이터 타입에 대해 유연한 검증 규칙을 적용할 수 있습니다. 프로젝트에서는 요구사항에 맞는 어노테이션을 선택하고, 오류 메시지를 사용자 친화적으로 커스터마이징하여 더 나은 사용자 경험을 제공할 수 있습니다.
|
||||
|
||||
추가 질문이 있다면 언제든 물어보세요!
|
||||
155
docs/모듈.md
Normal file
155
docs/모듈.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# **자바 모듈 시스템(Module System) 완벽 정리**
|
||||
|
||||
## **1. 자바 모듈이란?**
|
||||
자바 9에서 도입된 **모듈 시스템**은 **코드를 논리적으로 분리**하고, **불필요한 의존성을 줄이며**, **캡슐화를 강화**하는 기능이다.
|
||||
기존의 **JAR 파일을 묶는 개념**이 아니라, 모듈 자체가 **의존성 관리 및 접근 제어 기능**을 갖는다.
|
||||
|
||||
---
|
||||
|
||||
## **2. 모듈의 핵심 개념**
|
||||
### ✅ **모듈의 구성 요소**
|
||||
1. `module-info.java` 파일 (모듈의 정의 파일)
|
||||
2. 패키지 (캡슐화할 클래스 포함)
|
||||
3. 다른 모듈에 대한 의존성 선언
|
||||
|
||||
### ✅ **모듈의 주요 키워드**
|
||||
| 키워드 | 설명 |
|
||||
|--------|------|
|
||||
| `module` | 모듈을 정의하는 키워드 |
|
||||
| `requires` | 다른 모듈을 가져올 때 사용 |
|
||||
| `exports` | 특정 패키지를 외부에서 사용 가능하도록 공개 |
|
||||
| `opens` | 리플렉션(Reflection)을 허용 (ex: Jackson, Hibernate) |
|
||||
| `provides ... with` | 서비스 제공자 패턴 구현 시 사용 |
|
||||
| `uses` | 특정 서비스 인터페이스 사용 시 명시 |
|
||||
|
||||
---
|
||||
|
||||
## **3. 모듈 예제**
|
||||
### ✅ **모듈 프로젝트 구성**
|
||||
다음과 같은 두 개의 모듈이 있다고 가정하자.
|
||||
- **`com.example.moduleA`** (API를 제공하는 모듈)
|
||||
- **`com.example.moduleB`** (`moduleA`의 기능을 사용하는 모듈)
|
||||
|
||||
📁 **프로젝트 구조**
|
||||
```
|
||||
/project
|
||||
├── moduleA
|
||||
│ ├── src/com/example/moduleA/MyService.java
|
||||
│ ├── src/module-info.java
|
||||
│
|
||||
├── moduleB
|
||||
│ ├── src/com/example/moduleB/MainApp.java
|
||||
│ ├── src/module-info.java
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ **1. `moduleA` (API 제공 모듈)**
|
||||
📌 **`moduleA`는 `MyService` 클래스를 제공**하며, `moduleB`에서 사용 가능하도록 공개해야 한다.
|
||||
|
||||
#### 📌 **📄 moduleA/src/module-info.java**
|
||||
```java
|
||||
module com.example.moduleA {
|
||||
exports com.example.moduleA; // 패키지 공개
|
||||
}
|
||||
```
|
||||
|
||||
#### 📌 **📄 moduleA/src/com/example/moduleA/MyService.java**
|
||||
```java
|
||||
package com.example.moduleA;
|
||||
|
||||
public class MyService {
|
||||
public String getMessage() {
|
||||
return "Hello from Module A!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ **2. `moduleB` (API 사용 모듈)**
|
||||
📌 **`moduleB`는 `moduleA`의 클래스를 사용**하므로, `requires` 키워드로 `moduleA`를 포함해야 한다.
|
||||
|
||||
#### 📌 **📄 moduleB/src/module-info.java**
|
||||
```java
|
||||
module com.example.moduleB {
|
||||
requires com.example.moduleA; // moduleA를 사용하기 위해 선언
|
||||
}
|
||||
```
|
||||
|
||||
#### 📌 **📄 moduleB/src/com/example/moduleB/MainApp.java**
|
||||
```java
|
||||
package com.example.moduleB;
|
||||
|
||||
import com.example.moduleA.MyService;
|
||||
|
||||
public class MainApp {
|
||||
public static void main(String[] args) {
|
||||
MyService service = new MyService();
|
||||
System.out.println(service.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **4. 모듈 프로젝트 실행 방법**
|
||||
📌 터미널에서 모듈을 직접 컴파일하고 실행할 수도 있다.
|
||||
|
||||
### ✅ **1. 모듈별 컴파일**
|
||||
```sh
|
||||
# moduleA 컴파일
|
||||
javac -d out/moduleA src/moduleA/module-info.java src/moduleA/com/example/moduleA/MyService.java
|
||||
|
||||
# moduleB 컴파일 (moduleA를 classpath에 추가)
|
||||
javac -d out/moduleB --module-path out/moduleA src/moduleB/module-info.java src/moduleB/com/example/moduleB/MainApp.java
|
||||
```
|
||||
|
||||
### ✅ **2. 프로그램 실행**
|
||||
```sh
|
||||
java --module-path out/moduleA:out/moduleB --module com.example.moduleB/com.example.moduleB.MainApp
|
||||
```
|
||||
✔ `Hello from Module A!` 출력
|
||||
|
||||
---
|
||||
|
||||
## **5. `exports`와 `opens` 차이점**
|
||||
| 키워드 | 설명 |
|
||||
|--------|------|
|
||||
| `exports 패키지명;` | 패키지를 외부 모듈에서 사용할 수 있도록 공개 |
|
||||
| `opens 패키지명;` | 리플렉션(Reflection)으로 접근 가능하지만 일반적인 import는 불가능 |
|
||||
|
||||
📌 예를 들어, **Jackson, Hibernate 같은 프레임워크는 리플렉션을 사용**하므로 `opens`가 필요하다.
|
||||
```java
|
||||
module com.example.moduleA {
|
||||
opens com.example.moduleA to jackson.databind;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **6. 모듈 시스템의 장점**
|
||||
✅ **1. 강력한 캡슐화:**
|
||||
필요한 패키지만 `exports` 가능하여, **불필요한 내부 코드 노출 방지**
|
||||
|
||||
✅ **2. 명확한 의존성 관리:**
|
||||
`requires` 키워드로 **명확한 모듈 간 의존성**을 선언
|
||||
|
||||
✅ **3. 경량화된 런타임:**
|
||||
JVM이 필요 없는 모듈을 제외하고 실행 가능 (예: `jlink` 사용)
|
||||
|
||||
---
|
||||
|
||||
## **7. 모듈 시스템의 단점**
|
||||
❌ **1. 기존 라이브러리와의 호환성 문제:**
|
||||
기존 JAR 파일 중에는 **모듈 시스템을 지원하지 않는 것들이 있음**
|
||||
|
||||
❌ **2. 학습 곡선이 존재:**
|
||||
모듈 시스템을 처음 접하는 경우, **기존 classpath 방식보다 설정이 다소 복잡**
|
||||
|
||||
---
|
||||
|
||||
## **8. 결론**
|
||||
자바 모듈 시스템은 **의존성을 명확하게 관리하고 캡슐화를 강화**하는 강력한 기능이다.
|
||||
하지만, 기존 라이브러리와의 호환성을 고려해야 하며, **소규모 프로젝트에서는 필요하지 않을 수도 있다.**
|
||||
대규모 프로젝트에서는 **모듈을 도입하면 유지보수성과 성능이 향상**될 수 있다.
|
||||
156
docs/자바 스트림 API .md
Normal file
156
docs/자바 스트림 API .md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Java Stream API
|
||||
|
||||
Java의 **Stream API**는 컬렉션, 배열 등에서 요소를 효율적으로 처리할 수 있도록 제공되는 기능이다.
|
||||
Stream은 **중간 연산(Intermediate Operation)**과 **최종 연산(Terminal Operation)**으로 나뉘며,
|
||||
스트림을 사용하면 **반복문 없이 데이터 변환, 필터링, 집계 등을 간결하게 처리 가능**하다.
|
||||
|
||||
|
||||
## Stream API 메서드
|
||||
|
||||
### 스트림 생성 메서드
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `Stream.of(T... values)` | 여러 개의 값을 직접 입력하여 스트림 생성 |
|
||||
| `Stream.ofNullable(T t)` | 값이 `null`이면 비어있는 스트림 반환 |
|
||||
| `Arrays.stream(T[] array)` | 배열을 스트림으로 변환 |
|
||||
| `Collection.stream()` | 컬렉션(List, Set 등)에서 스트림 생성 |
|
||||
| `Collection.parallelStream()` | 병렬 스트림 생성 |
|
||||
| `IntStream.range(int start, int end)` | 특정 범위의 숫자 스트림 생성 (end 미포함) |
|
||||
| `IntStream.rangeClosed(int start, int end)` | 특정 범위의 숫자 스트림 생성 (end 포함) |
|
||||
| `Stream.iterate(T seed, UnaryOperator<T> f)` | 초기값과 람다식을 사용하여 무한 스트림 생성 |
|
||||
| `Stream.generate(Supplier<T> s)` | Supplier를 사용해 무한 스트림 생성 |
|
||||
|
||||
|
||||
### 필터링 및 매핑 (중간 연산)
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `filter(Predicate<T> predicate)` | 조건에 맞는 요소만 포함하는 스트림 반환 |
|
||||
| `map(Function<T, R> mapper)` | 각 요소를 변환하여 새로운 스트림 생성 |
|
||||
| `flatMap(Function<T, Stream<R>> mapper)` | 각 요소를 스트림으로 변환한 후 하나의 스트림으로 결합 |
|
||||
| `distinct()` | 중복 요소 제거 |
|
||||
| `sorted()` | 요소들을 정렬 (기본 정렬 기준 사용) |
|
||||
| `sorted(Comparator<T> comparator)` | 지정한 비교자를 사용하여 정렬 |
|
||||
| `peek(Consumer<T> action)` | 스트림의 각 요소를 확인 (디버깅용) |
|
||||
|
||||
|
||||
### 요소 제한 및 조작 (중간 연산)
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `limit(long maxSize)` | 지정된 개수만큼 요소를 제한 |
|
||||
| `skip(long n)` | 앞의 n개 요소를 건너뛰고 나머지 반환 |
|
||||
| `takeWhile(Predicate<T> predicate)` | 조건을 만족하는 동안 요소를 유지하고 이후 요소는 제외 |
|
||||
| `dropWhile(Predicate<T> predicate)` | 조건을 만족하는 동안 요소를 버리고 이후 요소만 유지 |
|
||||
|
||||
|
||||
### 집계 및 종료 연산 (최종 연산)
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `count()` | 스트림 내 요소 개수 반환 |
|
||||
| `min(Comparator<T> comparator)` | 최소값 반환 |
|
||||
| `max(Comparator<T> comparator)` | 최대값 반환 |
|
||||
| `findFirst()` | 첫 번째 요소 반환 (Optional) |
|
||||
| `findAny()` | 임의의 요소 반환 (Optional) |
|
||||
| `allMatch(Predicate<T> predicate)` | 모든 요소가 조건을 만족하는지 검사 (boolean) |
|
||||
| `anyMatch(Predicate<T> predicate)` | 하나 이상의 요소가 조건을 만족하는지 검사 (boolean) |
|
||||
| `noneMatch(Predicate<T> predicate)` | 모든 요소가 조건을 만족하지 않는지 검사 (boolean) |
|
||||
| `forEach(Consumer<T> action)` | 각 요소에 대해 작업 수행 (최종 연산) |
|
||||
|
||||
|
||||
### 스트림을 컬렉션으로 변환
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `toArray()` | 스트림을 배열로 변환 |
|
||||
| `collect(Collectors.toList())` | 스트림을 리스트로 변환 |
|
||||
| `collect(Collectors.toSet())` | 스트림을 집합(Set)으로 변환 |
|
||||
| `collect(Collectors.toMap(Function<T,K>, Function<T,V>))` | 스트림을 Map으로 변환 |
|
||||
|
||||
|
||||
### 요소를 조합하여 결과 생성
|
||||
| 메서드 | 설명 |
|
||||
|--------|------|
|
||||
| `reduce(BinaryOperator<T> accumulator)` | 요소를 하나의 값으로 축약 |
|
||||
| `reduce(T identity, BinaryOperator<T> accumulator)` | 초기값을 사용하여 축약 |
|
||||
| `reduce(T identity, BiFunction<T, U, T> accumulator, BinaryOperator<T> combiner)` | 병렬 처리용 축약 연산 |
|
||||
|
||||
|
||||
## Stream API 예제
|
||||
|
||||
### 스트림 생성 및 변환
|
||||
```java
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
|
||||
public class StreamExample {
|
||||
public static void main(String[] args) {
|
||||
// 리스트에서 스트림 생성
|
||||
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
|
||||
|
||||
// 이름 길이가 4 이상인 요소 필터링
|
||||
List<String> result = names.stream()
|
||||
.filter(name -> name.length() >= 4)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
System.out.println(result); // 출력: [Alice, Charlie, David]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### `map()`을 이용한 변환
|
||||
```java
|
||||
List<String> words = Arrays.asList("apple", "banana", "cherry");
|
||||
|
||||
List<Integer> lengths = words.stream()
|
||||
.map(String::length)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
System.out.println(lengths); // 출력: [5, 6, 6]
|
||||
```
|
||||
|
||||
|
||||
### `flatMap()`을 이용한 중첩 리스트 평탄화
|
||||
```java
|
||||
List<List<String>> listOfLists = Arrays.asList(
|
||||
Arrays.asList("a", "b"),
|
||||
Arrays.asList("c", "d"),
|
||||
Arrays.asList("e", "f")
|
||||
);
|
||||
|
||||
List<String> flatList = listOfLists.stream()
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
System.out.println(flatList); // 출력: [a, b, c, d, e, f]
|
||||
```
|
||||
|
||||
|
||||
### `reduce()`를 이용한 합산
|
||||
```java
|
||||
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
|
||||
|
||||
int sum = numbers.stream()
|
||||
.reduce(0, Integer::sum);
|
||||
|
||||
System.out.println(sum); // 출력: 15
|
||||
```
|
||||
|
||||
|
||||
### `groupingBy()`를 이용한 그룹화
|
||||
```java
|
||||
List<String> items = Arrays.asList("apple", "banana", "apple", "orange", "banana");
|
||||
|
||||
Map<String, Long> itemCount = items.stream()
|
||||
.collect(Collectors.groupingBy(s -> s, Collectors.counting()));
|
||||
|
||||
System.out.println(itemCount);
|
||||
// 출력: {banana=2, apple=2, orange=1}
|
||||
```
|
||||
|
||||
|
||||
## 정리
|
||||
- **Java Stream API**는 **중간 연산**과 **최종 연산**을 조합하여 데이터를 효과적으로 변환하고 처리하는 도구다.
|
||||
- **`filter()`**, **`map()`**, **`flatMap()`**을 사용하여 데이터를 가공할 수 있다.
|
||||
- **`collect()`**를 사용하여 스트림을 **리스트, 셋, 맵**으로 변환할 수 있다.
|
||||
- **`reduce()`**, **`groupingBy()`** 등의 메서드를 활용하면 데이터 집계를 손쉽게 수행할 수 있다.
|
||||
- 대량의 데이터를 **간결하고 효율적으로 처리하는 경우 Stream API를 적극 활용**하자!
|
||||
5
feed/README.md
Normal file
5
feed/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Feed
|
||||
|
||||
RSS and ATOM feed
|
||||
|
||||
powered by ROME
|
||||
16
feed/build.gradle.kts
Normal file
16
feed/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("elex-java")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.rometools/rome
|
||||
implementation("com.rometools:rome:2.1.0")
|
||||
|
||||
}
|
||||
20
feed/logback.xml
Normal file
20
feed/logback.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Examples for Java
|
||||
~
|
||||
~ Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
~ https://www.elex-project.com/
|
||||
-->
|
||||
|
||||
<configuration>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="TRACE">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</configuration>
|
||||
66
feed/src/main/java/kr/pe/elex/example/FeedService.java
Normal file
66
feed/src/main/java/kr/pe/elex/example/FeedService.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2024. Elex. All Rights Reesrved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.example;
|
||||
|
||||
import com.rometools.rome.feed.synd.*;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
import com.rometools.rome.io.SyndFeedInput;
|
||||
import com.rometools.rome.io.SyndFeedOutput;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
|
||||
public class FeedService {
|
||||
|
||||
public static List<String> getSupportedTypes() {
|
||||
final SyndFeed feed = new SyndFeedImpl();
|
||||
return feed.getSupportedFeedTypes();
|
||||
}
|
||||
|
||||
public static SyndFeed read(final Reader reader) throws FeedException {
|
||||
return new SyndFeedInput().build(reader);
|
||||
}
|
||||
|
||||
public static @NotNull SyndFeed create(final List<SyndEntry> entries) {
|
||||
final SyndFeed feed = new SyndFeedImpl();
|
||||
feed.setFeedType(FeedType.RSS.getValue());
|
||||
feed.setTitle("Sample Feed (created with ROME)");
|
||||
feed.setLink("http://rome.dev.java.net");
|
||||
feed.setDescription("This feed has been created using ROME (Java syndication utilities");
|
||||
feed.setEntries(entries);
|
||||
|
||||
return feed;
|
||||
}
|
||||
|
||||
public static @NotNull SyndEntry createEntry() throws ParseException {
|
||||
final SyndEntry entry = new SyndEntryImpl();
|
||||
entry.setTitle("Rome v1.0");
|
||||
entry.setLink("http://wiki.java.net/bin/view/Javawsxml/Rome01");
|
||||
entry.setPublishedDate(SimpleDateFormat.getDateInstance().parse("2004-06-08"));
|
||||
final SyndContent description = new SyndContentImpl();
|
||||
description.setType("text/plain");
|
||||
description.setValue("Initial release of Rome");
|
||||
entry.setDescription(description);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public static String write(final SyndFeed feed) throws FeedException, IOException {
|
||||
try (final StringWriter writer = new StringWriter()) {
|
||||
new SyndFeedOutput().output(feed, writer);
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
25
feed/src/main/java/kr/pe/elex/example/FeedType.java
Normal file
25
feed/src/main/java/kr/pe/elex/example/FeedType.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2024. Elex. All Rights Reesrved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.example;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum FeedType {
|
||||
RSS_1_0("rss_1.0"),
|
||||
RSS_2_0("rss_2.0"),
|
||||
RSS("rss_2.0"),
|
||||
|
||||
ATOM_1_0("atom_1.0"),
|
||||
ATOM("atom_1.0");
|
||||
|
||||
private final String value;
|
||||
|
||||
FeedType(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
32
feed/src/test/java/kr/pe/elex/example/FeedServiceTest.java
Normal file
32
feed/src/test/java/kr/pe/elex/example/FeedServiceTest.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2024. Elex. All Rights Reesrved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.example;
|
||||
|
||||
import com.rometools.rome.feed.synd.SyndFeed;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class FeedServiceTest {
|
||||
|
||||
@Test
|
||||
public void createTest() throws FeedException, IOException {
|
||||
SyndFeed feed = FeedService.create(Collections.emptyList());
|
||||
|
||||
System.out.println(FeedService.write(feed));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTypesTest(){
|
||||
for(String s : FeedService.getSupportedTypes()){
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -7,6 +7,6 @@
|
||||
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
16
gson/build.gradle.kts
Normal file
16
gson/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("elex-java")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
|
||||
}
|
||||
20
gson/logback.xml
Normal file
20
gson/logback.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Examples for Java
|
||||
~
|
||||
~ Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
~ https://www.elex-project.com/
|
||||
-->
|
||||
|
||||
<configuration>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="TRACE">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</configuration>
|
||||
32
gson/src/main/java/kr/pe/elex/examples/GsonSample.java
Normal file
32
gson/src/main/java/kr/pe/elex/examples/GsonSample.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import com.elex_project.abraxas.Console;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class GsonSample {
|
||||
|
||||
public static void main(String... args){
|
||||
Gson gson = new GsonBuilder().create();
|
||||
|
||||
// to json
|
||||
Person charlie = new Person("Charlie", 13);
|
||||
String json = gson.toJson(charlie);
|
||||
Console.writeLine(json);
|
||||
|
||||
// from json
|
||||
Person steve = gson.fromJson("{'name':'Steve', 'age':43}", Person.class);
|
||||
Console.writeLine(steve);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
22
gson/src/main/java/kr/pe/elex/examples/Person.java
Normal file
22
gson/src/main/java/kr/pe/elex/examples/Person.java
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2023. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ plugins {
|
||||
dependencies {
|
||||
implementation ("org.hibernate:hibernate-core:5.5.5.Final")
|
||||
|
||||
runtimeOnly ("com.h2database:h2:1.4.200")
|
||||
runtimeOnly ("com.h2database:h2:2.1.214")
|
||||
runtimeOnly ("org.apache.derby:derby:10.15.2.0")
|
||||
runtimeOnly ("org.xerial:sqlite-jdbc:3.36.0.1")
|
||||
//implementation("com.zsoltfabok:sqlite-dialect:1.0")
|
||||
|
||||
@@ -10,9 +10,9 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation ("com.fasterxml.jackson.core:jackson-databind:2.12.4")
|
||||
implementation ("com.fasterxml.jackson.core:jackson-databind:2.14.2")
|
||||
|
||||
implementation ("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.4")
|
||||
implementation ("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2")
|
||||
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.12.4")
|
||||
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.12.4")
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api
|
||||
implementation("io.jsonwebtoken:jjwt-api:0.11.2")
|
||||
implementation("io.jsonwebtoken:jjwt-api:0.12.5")
|
||||
|
||||
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2")
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.2")
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5")
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5")
|
||||
|
||||
// another library, good for a client side
|
||||
implementation("com.auth0:java-jwt:4.4.0")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
@Slf4j
|
||||
public class JwtSample {
|
||||
public class JwsSampleOld {
|
||||
private static final byte[] key;
|
||||
|
||||
static {
|
||||
@@ -29,12 +29,20 @@ public class JwtSample {
|
||||
}
|
||||
|
||||
public static String generateToken() throws InvalidKeyException {
|
||||
return Jwts.builder()
|
||||
/*return Jwts.builder()
|
||||
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
|
||||
.setIssuer("Elex")
|
||||
.setExpiration(Date.from(Instant.now().plus(3, ChronoUnit.HOURS)))
|
||||
.claim("userId", 3)
|
||||
.signWith(Keys.hmacShaKeyFor(key))
|
||||
.compact();*/
|
||||
return Jwts.builder()
|
||||
.header().type(Header.JWT_TYPE)
|
||||
.and()
|
||||
.issuer("Elex")
|
||||
.expiration(Date.from(Instant.now().plus(3, ChronoUnit.HOURS)))
|
||||
.claim("userId", 3)
|
||||
.signWith(Keys.hmacShaKeyFor(key))
|
||||
.compact();
|
||||
}
|
||||
|
||||
@@ -53,11 +61,16 @@ public class JwtSample {
|
||||
throws UnsupportedJwtException, MalformedJwtException, SignatureException, ExpiredJwtException,
|
||||
MissingClaimException, IncorrectClaimException {
|
||||
|
||||
return Jwts.parserBuilder()
|
||||
/*return Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.requireIssuer("Elex") // 토큰의 Issuer 일치 여부 확인
|
||||
.build()
|
||||
.parseClaimsJws(parseHeader(token));
|
||||
.parseClaimsJws(parseHeader(token));*/
|
||||
return Jwts.parser()
|
||||
.verifyWith(Keys.hmacShaKeyFor(key))
|
||||
.requireIssuer("Elex") // 토큰의 Issuer 일치 여부 확인
|
||||
.build()
|
||||
.parseSignedClaims(parseHeader(token));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +97,7 @@ public class JwtSample {
|
||||
final String authHeader = "Bearer " + token;
|
||||
Jws<Claims> claims = parseToken(authHeader);
|
||||
System.out.println(claims);
|
||||
final int userId = claims.getBody().get("userId", Integer.class);
|
||||
final int userId = claims.getPayload().get("userId", Integer.class);
|
||||
System.out.println("User Id: " + userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2024. Elex. All Rights Reesrved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class Jwt0Sample {
|
||||
private static final byte[] key;
|
||||
|
||||
static {
|
||||
// HMACSHA256을 사용하므로 키의 길이는 32바이트이다.
|
||||
key = new byte[32];
|
||||
new Random().nextBytes(key);
|
||||
}
|
||||
public static String genToken(){
|
||||
return JWT.create()
|
||||
.withIssuer("Elex")
|
||||
.withSubject("Hello")
|
||||
.sign(Algorithm.HMAC256(key));
|
||||
}
|
||||
public static DecodedJWT parseToken(String token, byte[] key){
|
||||
return JWT.require(Algorithm.HMAC256(key))
|
||||
.withIssuer("Elex")
|
||||
.build()
|
||||
.verify(token);
|
||||
}
|
||||
public static DecodedJWT parseToken(String token){
|
||||
return JWT.decode(token);
|
||||
}
|
||||
|
||||
public static void main(String... args){
|
||||
String token = genToken();
|
||||
System.out.println(token);
|
||||
|
||||
DecodedJWT jwt = parseToken(token);
|
||||
System.out.println(jwt.getSubject());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2024. Elex. All Rights Reesrved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* https://github.com/jwtk/jjwt
|
||||
*/
|
||||
public class JwteSample {
|
||||
|
||||
public static SecretKey generateSecretKey() {
|
||||
return Jwts.ENC.A256GCM.key().build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String generateToken(final SecretKey key) {
|
||||
return Jwts.builder()
|
||||
.header().type(Header.JWT_TYPE)
|
||||
.and()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.issuedAt(Date.from(ZonedDateTime.now().toInstant()))
|
||||
.issuer("Elex Company")
|
||||
.subject("Hello")
|
||||
.audience().add("Charlie")
|
||||
.and()
|
||||
.expiration(Date.from(ZonedDateTime.now().plusHours(2).toInstant()))
|
||||
.notBefore(Date.from(ZonedDateTime.now().toInstant()))
|
||||
.encryptWith(key, Jwts.ENC.A256GCM)
|
||||
.compact();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String parseToken(final String token, final SecretKey key) throws JwtException {
|
||||
try {
|
||||
return Jwts.parser().decryptWith(key).build()
|
||||
.parseEncryptedClaims(token)
|
||||
.getPayload().getSubject();
|
||||
} catch (JwtException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void main(String... args) {
|
||||
SecretKey key = generateSecretKey();
|
||||
System.out.println(Base64.getEncoder().encodeToString(key.getEncoded()));
|
||||
|
||||
String token = generateToken(key);
|
||||
System.out.println(token);
|
||||
|
||||
String subject = parseToken(token, key);
|
||||
System.out.println(subject);
|
||||
|
||||
}
|
||||
}
|
||||
139
json-web-token/src/main/java/kr/pe/elex/examples/JwtsSample.java
Normal file
139
json-web-token/src/main/java/kr/pe/elex/examples/JwtsSample.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2024. Elex. All Rights Reesrved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* https://github.com/jwtk/jjwt
|
||||
*/
|
||||
public class JwtsSample {
|
||||
|
||||
public static SecretKey generateSecretKey() {
|
||||
return Jwts.SIG.HS256.key().build();
|
||||
|
||||
}
|
||||
|
||||
public static byte[] getEncodedKey(SecretKey key) {
|
||||
return key.getEncoded();
|
||||
}
|
||||
|
||||
public static String getAlgorithmName(SecretKey key) {
|
||||
return key.getAlgorithm();
|
||||
}
|
||||
|
||||
public static SecretKey getSecretKey(byte[] encodedKey, String alg) {
|
||||
return new SecretKeySpec(encodedKey, alg);
|
||||
}
|
||||
|
||||
public static KeyPair generateKeyPair() {
|
||||
return Jwts.SIG.RS512.keyPair().build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String generateToken(final SecretKey key) {
|
||||
return Jwts.builder()
|
||||
.header().type(Header.JWT_TYPE)
|
||||
.and()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.issuedAt(Date.from(ZonedDateTime.now().toInstant()))
|
||||
.issuer("Elex Company")
|
||||
.subject("Hello")
|
||||
.audience().add("Charlie")
|
||||
.and()
|
||||
.expiration(Date.from(ZonedDateTime.now().plusHours(2).toInstant()))
|
||||
.notBefore(Date.from(ZonedDateTime.now().toInstant()))
|
||||
.signWith(key)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public static String generateToken(final PrivateKey key) {
|
||||
return Jwts.builder()
|
||||
.header().type(Header.JWT_TYPE)
|
||||
.and()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.issuedAt(Date.from(ZonedDateTime.now().toInstant()))
|
||||
.issuer("Elex Company")
|
||||
.subject("Hello")
|
||||
.audience().add("Charlie")
|
||||
.and()
|
||||
.expiration(Date.from(ZonedDateTime.now().plusHours(2).toInstant()))
|
||||
.notBefore(Date.from(ZonedDateTime.now().toInstant()))
|
||||
.signWith(key, Jwts.SIG.RS512)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public static String parseToken(final String token, final SecretKey key) throws JwtException {
|
||||
try {
|
||||
return Jwts.parser().verifyWith(key).build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload().getSubject();
|
||||
} catch (JwtException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static String decodeToken(final String token) throws JwtException {
|
||||
String[] chunks = token.split("\\.");
|
||||
Base64.Decoder decoder = Base64.getUrlDecoder();
|
||||
|
||||
String header = new String(decoder.decode(chunks[0]));
|
||||
String payload = new String(decoder.decode(chunks[1]));
|
||||
|
||||
return payload;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static String decodeToken2(final String token) throws JwtException {
|
||||
String s = Jwts.parser().unsecured().build().parseSignedClaims(token)
|
||||
.getPayload().getSubject();
|
||||
|
||||
return s;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static String parseToken(final String token, final PublicKey key) throws JwtException {
|
||||
try {
|
||||
return Jwts.parser()
|
||||
.verifyWith(key)
|
||||
.requireIssuer("Elex")
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload().getSubject();
|
||||
} catch (JwtException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
SecretKey key = generateSecretKey();
|
||||
System.out.println(Base64.getEncoder().encodeToString(key.getEncoded()));
|
||||
|
||||
String token = generateToken(key);
|
||||
System.out.println(token);
|
||||
|
||||
String subject = parseToken(token, key);
|
||||
System.out.println(subject);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,103 @@
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.io.CompressionAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SampleTest {
|
||||
|
||||
@Test
|
||||
void test(){
|
||||
System.out.println(new String());
|
||||
void keyTest(){
|
||||
SecretKey secretKey = JwtsSample.generateSecretKey();
|
||||
|
||||
byte[] rawKey = secretKey.getEncoded();
|
||||
String alg = secretKey.getAlgorithm();
|
||||
|
||||
SecretKey secKey = JwtsSample.getSecretKey(rawKey, alg);
|
||||
|
||||
String token = Jwts.builder()
|
||||
.header().type("JWT")
|
||||
.and()
|
||||
.issuer("Elex")
|
||||
.expiration(Date.from(Instant.now().plus(3, ChronoUnit.HOURS)))
|
||||
.claim("userId", 3)
|
||||
.subject("hello")
|
||||
.signWith(secretKey)
|
||||
.compact();
|
||||
|
||||
String subject = Jwts.parser()
|
||||
.verifyWith(secKey)
|
||||
.requireIssuer("Elex") // 토큰의 Issuer 일치 여부 확인
|
||||
.build().parseSignedClaims(token).getPayload().getSubject();
|
||||
System.out.println(subject);
|
||||
}
|
||||
@Test
|
||||
void test2(){
|
||||
final SecretKey signingKey = Jwts.SIG.HS384.key().build();
|
||||
String token = Jwts.builder()
|
||||
.header().type("JWT")
|
||||
.and()
|
||||
.issuer("Elex")
|
||||
.expiration(Date.from(Instant.now().plus(3, ChronoUnit.HOURS)))
|
||||
.claim("userId", 3)
|
||||
.subject("hello")
|
||||
.signWith(signingKey)
|
||||
.compact();
|
||||
String payload = JwtsSample.decodeToken(token);
|
||||
|
||||
System.out.println(payload);
|
||||
|
||||
//String sub = JwtsSample.decodeToken2(token);
|
||||
//System.out.println(sub);
|
||||
}
|
||||
@Test
|
||||
void test() throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
byte[] key = new byte[32];
|
||||
new Random().nextBytes(key);
|
||||
//final SecretKey signingKey = Keys.hmacShaKeyFor(key);
|
||||
KeyPair keyPair = Jwts.SIG.RS384.keyPair().build();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
String base64PublicKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());
|
||||
|
||||
String jwt = Jwts.builder()
|
||||
.header().type("JWT")
|
||||
.and()
|
||||
.issuer("Elex")
|
||||
.expiration(Date.from(Instant.now().plus(3, ChronoUnit.HOURS)))
|
||||
.claim("userId", 3)
|
||||
.signWith(keyPair.getPrivate())
|
||||
.compact();
|
||||
System.out.println(jwt);
|
||||
|
||||
X509EncodedKeySpec ukeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(base64PublicKey));
|
||||
PublicKey pKey = KeyFactory.getInstance("RSA").generatePublic(ukeySpec);
|
||||
|
||||
Integer userId = Jwts.parser()
|
||||
.verifyWith(pKey)
|
||||
.requireIssuer("Elex") // 토큰의 Issuer 일치 여부 확인
|
||||
.build()
|
||||
.parseSignedClaims(jwt).getPayload().get("userId", Integer.class);
|
||||
System.out.println(userId);
|
||||
String alg = Jwts.parser()
|
||||
.verifyWith(pKey)
|
||||
.requireIssuer("Elex") // 토큰의 Issuer 일치 여부 확인
|
||||
.build()
|
||||
.parseSignedClaims(jwt).getHeader().getAlgorithm();
|
||||
System.out.println(alg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.jsoup/jsoup
|
||||
implementation("org.jsoup:jsoup:1.14.1")
|
||||
implementation("org.jsoup:jsoup:1.14.2")
|
||||
}
|
||||
|
||||
@@ -11,5 +11,15 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("com.vladsch.flexmark:flexmark-all:0.62.2")
|
||||
implementation("com.vladsch.flexmark:flexmark-all:0.64.8")
|
||||
|
||||
implementation("org.commonmark:commonmark:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-gfm-tables:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-autolink:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-gfm-strikethrough:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-heading-anchor:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-ins:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-yaml-front-matter:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-image-attributes:0.21.0")
|
||||
implementation("org.commonmark:commonmark-ext-task-list-items:0.21.0")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2024. Elex. All Rights Reesrved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.commonmark.Extension;
|
||||
import org.commonmark.ext.autolink.AutolinkExtension;
|
||||
import org.commonmark.ext.front.matter.YamlFrontMatterExtension;
|
||||
import org.commonmark.ext.front.matter.YamlFrontMatterVisitor;
|
||||
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
||||
import org.commonmark.ext.gfm.tables.TablesExtension;
|
||||
import org.commonmark.ext.heading.anchor.HeadingAnchorExtension;
|
||||
import org.commonmark.ext.image.attributes.ImageAttributesExtension;
|
||||
import org.commonmark.ext.ins.InsExtension;
|
||||
import org.commonmark.ext.task.list.items.TaskListItemsExtension;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommonMarkdownParser {
|
||||
private static final Parser parser;
|
||||
private static final HtmlRenderer renderer;
|
||||
|
||||
static {
|
||||
final List<Extension> extensions = Arrays.asList(
|
||||
TablesExtension.create(),
|
||||
AutolinkExtension.create(),
|
||||
StrikethroughExtension.create(),
|
||||
TablesExtension.create(),
|
||||
HeadingAnchorExtension.create(),
|
||||
InsExtension.create(),
|
||||
YamlFrontMatterExtension.create(),
|
||||
ImageAttributesExtension.create(),
|
||||
TaskListItemsExtension.create()
|
||||
);
|
||||
parser = Parser.builder()
|
||||
.extensions(extensions)
|
||||
.build();
|
||||
renderer = HtmlRenderer.builder()
|
||||
.extensions(extensions)
|
||||
.build();
|
||||
}
|
||||
|
||||
//private Node document;
|
||||
@Getter
|
||||
private String html;
|
||||
@Getter
|
||||
private Map<String, List<String>> yaml;
|
||||
|
||||
public CommonMarkdownParser() {
|
||||
|
||||
}
|
||||
|
||||
public void parse(final String markdown) {
|
||||
final YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor();
|
||||
final Node document = parser.parse(markdown);
|
||||
document.accept(visitor);
|
||||
this.html = renderer.render(document);
|
||||
this.yaml = visitor.getData();
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args){
|
||||
final String input = "---" +
|
||||
"\nlist:" +
|
||||
"\n - value1" +
|
||||
"\n - value2" +
|
||||
"\n..." +
|
||||
"\n" +
|
||||
"\ngreat";
|
||||
CommonMarkdownParser p = new CommonMarkdownParser();
|
||||
p.parse(input);
|
||||
|
||||
System.out.println(p.getHtml());
|
||||
System.out.println(p.getYaml());
|
||||
}
|
||||
}
|
||||
@@ -8,25 +8,39 @@
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import com.vladsch.flexmark.ext.abbreviation.AbbreviationExtension;
|
||||
import com.vladsch.flexmark.ext.admonition.AdmonitionExtension;
|
||||
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension;
|
||||
import com.vladsch.flexmark.ext.aside.AsideExtension;
|
||||
import com.vladsch.flexmark.ext.attributes.AttributesExtension;
|
||||
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
|
||||
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
|
||||
import com.vladsch.flexmark.ext.emoji.EmojiExtension;
|
||||
import com.vladsch.flexmark.ext.enumerated.reference.EnumeratedReferenceExtension;
|
||||
import com.vladsch.flexmark.ext.escaped.character.EscapedCharacterExtension;
|
||||
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
|
||||
import com.vladsch.flexmark.ext.gfm.issues.GfmIssuesExtension;
|
||||
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension;
|
||||
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
|
||||
import com.vladsch.flexmark.ext.gfm.users.GfmUsersExtension;
|
||||
import com.vladsch.flexmark.ext.gitlab.GitLabExtension;
|
||||
import com.vladsch.flexmark.ext.ins.InsExtension;
|
||||
import com.vladsch.flexmark.ext.jekyll.front.matter.JekyllFrontMatterExtension;
|
||||
import com.vladsch.flexmark.ext.jekyll.tag.JekyllTagExtension;
|
||||
import com.vladsch.flexmark.ext.macros.MacrosExtension;
|
||||
import com.vladsch.flexmark.ext.media.tags.MediaTagsExtension;
|
||||
import com.vladsch.flexmark.ext.resizable.image.ResizableImageExtension;
|
||||
import com.vladsch.flexmark.ext.superscript.SuperscriptExtension;
|
||||
import com.vladsch.flexmark.ext.tables.TablesExtension;
|
||||
import com.vladsch.flexmark.ext.toc.TocExtension;
|
||||
import com.vladsch.flexmark.ext.typographic.TypographicExtension;
|
||||
import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension;
|
||||
import com.vladsch.flexmark.ext.yaml.front.matter.AbstractYamlFrontMatterVisitor;
|
||||
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
|
||||
import com.vladsch.flexmark.ext.youtube.embedded.YouTubeLinkExtension;
|
||||
import com.vladsch.flexmark.html.HtmlRenderer;
|
||||
import com.vladsch.flexmark.parser.Parser;
|
||||
import com.vladsch.flexmark.util.ast.Node;
|
||||
import com.vladsch.flexmark.util.data.MutableDataHolder;
|
||||
import com.vladsch.flexmark.util.data.MutableDataSet;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -36,52 +50,66 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class MarkdownParser {
|
||||
public class FlexMarkdownParser {
|
||||
private static final Parser PARSER;
|
||||
private static final HtmlRenderer RENDERER;
|
||||
private static final AbstractYamlFrontMatterVisitor YAML_VISITOR;
|
||||
|
||||
@Getter
|
||||
private final Node document;
|
||||
|
||||
static {
|
||||
final MutableDataSet options = new MutableDataSet();
|
||||
final MutableDataHolder options = new MutableDataSet();
|
||||
options.set(
|
||||
Parser.EXTENSIONS,
|
||||
Arrays.asList(
|
||||
AbbreviationExtension.create(),
|
||||
AdmonitionExtension.create(),
|
||||
AnchorLinkExtension.create(),
|
||||
AsideExtension.create(),
|
||||
AttributesExtension.create(),
|
||||
AutolinkExtension.create(),
|
||||
DefinitionExtension.create(),
|
||||
EmojiExtension.create(),
|
||||
EnumeratedReferenceExtension.create(),
|
||||
EscapedCharacterExtension.create(),
|
||||
FootnoteExtension.create(),
|
||||
GfmIssuesExtension.create(),
|
||||
StrikethroughSubscriptExtension.create(),
|
||||
TaskListExtension.create(),
|
||||
GfmUsersExtension.create(),
|
||||
GitLabExtension.create(),
|
||||
InsExtension.create(),
|
||||
JekyllFrontMatterExtension.create(),
|
||||
JekyllTagExtension.create(),
|
||||
MacrosExtension.create(),
|
||||
MediaTagsExtension.create(),
|
||||
ResizableImageExtension.create(),
|
||||
SuperscriptExtension.create(),
|
||||
TablesExtension.create(),
|
||||
TocExtension.create(),
|
||||
TypographicExtension.create(),
|
||||
WikiLinkExtension.create(),
|
||||
YamlFrontMatterExtension.create(),
|
||||
YouTubeLinkExtension.create()
|
||||
));
|
||||
options.set(HtmlRenderer.SOFT_BREAK, "<br />\n");
|
||||
options.set(HtmlRenderer.GENERATE_HEADER_ID, true);
|
||||
|
||||
//options.setFrom(ParserEmulationProfile.GITHUB);
|
||||
PARSER = Parser.builder(options).build();
|
||||
RENDERER = HtmlRenderer.builder(options).build();
|
||||
|
||||
YAML_VISITOR = new AbstractYamlFrontMatterVisitor();
|
||||
|
||||
}
|
||||
|
||||
public MarkdownParser(final String md) {
|
||||
public FlexMarkdownParser(final String md) {
|
||||
document = PARSER.parse(md);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getYaml() {
|
||||
YAML_VISITOR.visit(document);
|
||||
return YAML_VISITOR.getData();
|
||||
final AbstractYamlFrontMatterVisitor visitor = new AbstractYamlFrontMatterVisitor() {
|
||||
};
|
||||
visitor.visit(document);
|
||||
return visitor.getData();
|
||||
}
|
||||
|
||||
public String getHtml() {
|
||||
@@ -15,19 +15,19 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class MarkdownParserTest {
|
||||
class FlexMarkdownParserTest {
|
||||
|
||||
private static String md;
|
||||
|
||||
@BeforeAll
|
||||
private static void beforeAll() throws IOException {
|
||||
md = IOz.readStringFrom(MarkdownParserTest.class
|
||||
md = IOz.readStringFrom(FlexMarkdownParserTest.class
|
||||
.getResourceAsStream("/sample.md"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parse() throws IOException {
|
||||
MarkdownParser markdownParser = new MarkdownParser(md);
|
||||
FlexMarkdownParser markdownParser = new FlexMarkdownParser(md);
|
||||
String html = markdownParser.getHtml();
|
||||
System.out.println(html);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
plugins {
|
||||
id("elex-java")
|
||||
war
|
||||
}
|
||||
|
||||
dependencies {
|
||||
33
midi/src/main/java/kr/pe/elex/examples/Examples.java
Normal file
33
midi/src/main/java/kr/pe/elex/examples/Examples.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import com.elex_project.abraxas.Console;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.sound.midi.*;
|
||||
|
||||
@Slf4j
|
||||
public class Examples {
|
||||
public static void main(String... args) throws MidiUnavailableException {
|
||||
|
||||
for (MidiDevice.Info info : MidiSystem.getMidiDeviceInfo()) {
|
||||
Console.writeLine("{} / {} / {}",
|
||||
info.getName(), info.getVendor(), info.getDescription());
|
||||
}
|
||||
|
||||
Sequencer sequencer = MidiSystem.getSequencer();
|
||||
Console.writeLine(sequencer.toString());
|
||||
Synthesizer synthesizer = MidiSystem.getSynthesizer();
|
||||
Console.writeLine(synthesizer.toString());
|
||||
Receiver receiver = MidiSystem.getReceiver();
|
||||
Console.writeLine(receiver.toString());
|
||||
//Transmitter transmitter = MidiSystem.getTransmitter();
|
||||
//Console.writeLine(transmitter.toString());
|
||||
}
|
||||
}
|
||||
33
midi/src/main/java/kr/pe/elex/examples/MidiExample.java
Normal file
33
midi/src/main/java/kr/pe/elex/examples/MidiExample.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.sound.midi.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class MidiExample {
|
||||
public static void main(String... args) throws MidiUnavailableException, InvalidMidiDataException, IOException {
|
||||
Sequencer sequencer = MidiSystem.getSequencer();
|
||||
Synthesizer synthesizer = MidiSystem.getSynthesizer();
|
||||
Transmitter seqTransmitter = sequencer.getTransmitter();
|
||||
Receiver synthReceiver = synthesizer.getReceiver();
|
||||
|
||||
sequencer.open();
|
||||
Sequence sequence = MidiSystem.getSequence(new File("MIDI_sample.mid"));
|
||||
sequencer.setSequence(sequence);
|
||||
sequencer.start();
|
||||
|
||||
while (true){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
8
midi/src/main/java/kr/pe/elex/examples/package-info.java
Normal file
8
midi/src/main/java/kr/pe/elex/examples/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
package kr.pe.elex.examples;
|
||||
@@ -11,5 +11,5 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.mockito/mockito-core
|
||||
testImplementation("org.mockito:mockito-core:3.11.2")
|
||||
testImplementation("org.mockito:mockito-core:4.5.1")
|
||||
}
|
||||
|
||||
229
model-mapper/README.md
Normal file
229
model-mapper/README.md
Normal file
@@ -0,0 +1,229 @@
|
||||
### ModelMapper 소개 및 사용 방법
|
||||
|
||||
ModelMapper는 자바에서 객체 간 매핑을 간편하게 처리할 수 있도록 도와주는 오픈소스 라이브러리입니다. DTO(Data Transfer Object)와 엔티티(Entity) 간의 변환 작업을 수동으로 작성하는 대신, ModelMapper를 사용하면 코드의 중복을 줄이고 가독성을 높일 수 있습니다. 이 라이브러리는 객체의 필드 이름과 타입을 기반으로 자동 매핑을 수행하며, 필요에 따라 사용자 정의 매핑 규칙을 추가할 수도 있습니다.
|
||||
|
||||
ModelMapper의 주요 특징은 다음과 같습니다:
|
||||
|
||||
- **자동 매핑**: 필드 이름이 동일하면 별도의 설정 없이 매핑 가능.
|
||||
- **유연성**: 복잡한 매핑 요구사항을 커스터마이징 가능.
|
||||
- **간결함**: boilerplate 코드를 줄여 개발 생산성 향상.
|
||||
|
||||
---
|
||||
|
||||
### 의존성 추가
|
||||
|
||||
ModelMapper를 사용하려면 먼저 프로젝트에 의존성을 추가해야 합니다. Maven을 사용하는 경우 `pom.xml`에 다음 코드를 추가하세요:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.modelmapper</groupId>
|
||||
<artifactId>modelmapper</artifactId>
|
||||
<version>3.2.0</version> <!-- 최신 버전 확인 필요 -->
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Gradle을 사용하는 경우 `build.gradle`에 아래를 추가합니다:
|
||||
|
||||
```gradle
|
||||
implementation 'org.modelmapper:modelmapper:3.2.0' // 최신 버전 확인 필요
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 기본 사용 예시
|
||||
|
||||
아래는 ModelMapper를 사용해 간단한 객체 간 매핑을 수행하는 예제입니다.
|
||||
|
||||
#### 1. 엔티티와 DTO 클래스 정의
|
||||
|
||||
먼저 매핑할 두 클래스를 정의합니다:
|
||||
|
||||
```java
|
||||
// 엔티티 클래스
|
||||
public class User {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String email;
|
||||
|
||||
// Getter와 Setter
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
}
|
||||
|
||||
// DTO 클래스
|
||||
public class UserDTO {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String email;
|
||||
|
||||
// Getter와 Setter
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. ModelMapper로 매핑
|
||||
|
||||
이제 ModelMapper를 사용해 `User` 객체를 `UserDTO`로 변환해봅시다:
|
||||
|
||||
```java
|
||||
import org.modelmapper.ModelMapper;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
// ModelMapper 인스턴스 생성
|
||||
ModelMapper modelMapper = new ModelMapper();
|
||||
|
||||
// User 객체 생성 및 데이터 설정
|
||||
User user = new User();
|
||||
user.setId(1L);
|
||||
user.setName("홍길동");
|
||||
user.setEmail("hong@example.com");
|
||||
|
||||
// User -> UserDTO로 매핑
|
||||
UserDTO userDTO = modelMapper.map(user, UserDTO.class);
|
||||
|
||||
// 결과 출력
|
||||
System.out.println("ID: " + userDTO.getId());
|
||||
System.out.println("Name: " + userDTO.getName());
|
||||
System.out.println("Email: " + userDTO.getEmail());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**출력 결과:**
|
||||
|
||||
```
|
||||
ID: 1
|
||||
Name: 홍길동
|
||||
Email: hong@example.com
|
||||
```
|
||||
|
||||
위 예제에서 `modelMapper.map()` 메서드는 `User` 객체의 필드를 `UserDTO`로 자동으로 매핑합니다. 필드 이름과 타입이 동일하기 때문에 별도의 설정 없이도 동작합니다.
|
||||
|
||||
---
|
||||
|
||||
### 고급 사용 예시: 커스터마이징 매핑
|
||||
|
||||
필드 이름이 다르거나 특정 변환 로직이 필요한 경우, ModelMapper의 설정을 커스터마이징할 수 있습니다.
|
||||
|
||||
#### 1. 필드 이름이 다른 경우
|
||||
|
||||
예를 들어, `User` 클래스의 `email` 필드를 `UserDTO`의 `emailAddress`로 매핑한다고 가정해봅시다.
|
||||
|
||||
```java
|
||||
// 수정된 UserDTO 클래스
|
||||
public class UserDTO {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String emailAddress; // 필드 이름 변경
|
||||
|
||||
// Getter와 Setter
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getEmailAddress() { return emailAddress; }
|
||||
public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 커스터마이징 매핑 설정
|
||||
|
||||
ModelMapper에 매핑 규칙을 추가합니다:
|
||||
|
||||
```java
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.modelmapper.PropertyMap;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
// ModelMapper 인스턴스 생성
|
||||
ModelMapper modelMapper = new ModelMapper();
|
||||
|
||||
// 커스터마이징 매핑 규칙 추가
|
||||
modelMapper.addMappings(new PropertyMap<User, UserDTO>() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
map().setEmailAddress(source.getEmail()); // email -> emailAddress
|
||||
}
|
||||
});
|
||||
|
||||
// User 객체 생성
|
||||
User user = new User();
|
||||
user.setId(1L);
|
||||
user.setName("홍길동");
|
||||
user.setEmail("hong@example.com");
|
||||
|
||||
// 매핑 수행
|
||||
UserDTO userDTO = modelMapper.map(user, UserDTO.class);
|
||||
|
||||
// 결과 출력
|
||||
System.out.println("ID: " + userDTO.getId());
|
||||
System.out.println("Name: " + userDTO.getName());
|
||||
System.out.println("Email Address: " + userDTO.getEmailAddress());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**출력 결과:**
|
||||
|
||||
```
|
||||
ID: 1
|
||||
Name: 홍길동
|
||||
Email Address: hong@example.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 추가 기능: 리스트 매핑
|
||||
|
||||
여러 객체를 포함한 리스트를 매핑할 수도 있습니다:
|
||||
|
||||
```java
|
||||
import org.modelmapper.ModelMapper;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
ModelMapper modelMapper = new ModelMapper();
|
||||
|
||||
// User 리스트 생성
|
||||
List<User> users = Arrays.asList(
|
||||
new User(1L, "홍길동", "hong@example.com"),
|
||||
new User(2L, "김영희", "kim@example.com")
|
||||
);
|
||||
|
||||
// 리스트 매핑
|
||||
List<UserDTO> userDTOs = users.stream()
|
||||
.map(user -> modelMapper.map(user, UserDTO.class))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 결과 출력
|
||||
userDTOs.forEach(dto -> System.out.println(dto.getName() + ": " + dto.getEmail()));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**출력 결과:**
|
||||
|
||||
```
|
||||
홍길동: hong@example.com
|
||||
김영희: kim@example.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 결론
|
||||
|
||||
ModelMapper는 객체 간 매핑을 단순화하고 개발자의 시간을 절약해주는 강력한 도구입니다. 기본적인 자동 매핑부터 복잡한 커스터마이징까지 지원하므로, 프로젝트 요구사항에 따라 적절히 활용할 수 있습니다. 위 예제를 참고하여 실제 프로젝트에 적용해보세요! 추가 질문이 있다면 언제든 물어보세요.
|
||||
14
model-mapper/build.gradle.kts
Normal file
14
model-mapper/build.gradle.kts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Examples for Java
|
||||
*
|
||||
* Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
* https://www.elex-project.com/
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("elex-java")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.modelmapper:modelmapper:3.+")
|
||||
}
|
||||
20
model-mapper/logback.xml
Normal file
20
model-mapper/logback.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Examples for Java
|
||||
~
|
||||
~ Copyright (c) 2021. Elex. All Rights Reserved.
|
||||
~ https://www.elex-project.com/
|
||||
-->
|
||||
|
||||
<configuration>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="TRACE">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</configuration>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user
