| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- LG유플러스 유레카 부트캠프
- 애자일
- 프론트엔드
- 웹시큐리티
- 프론트엔드 비대면반
- git branch 협업
- Do it! 자료구조와 함께 배우는 알고리즘 입문
- 멀티캠퍼스IT부트캠프티
- 스레드
- LG유플러스 유레카 프론트엔드 개발자
- 소수
- 정렬
- Java
- 프로세스
- 코딩
- 유레카 부트캠프
- 부트캠프후기
- LG유플러스 유레카 프론트엔드
- 재귀
- 2775번 문제
- 브루트포스
- zod
- 알고리즘
- 별찍기10
- LG유플러스 유레카 3기 프론트엔드
- 멀티캠퍼스IT부트캠프
- 시간 복잡도
- 자바
- tanstack query
- 백준
- Today
- Total
개발 일기
250911 자바 (StringBuilder, 람다식, I/O, Thread) 본문
자바의 String, StringBuffer, StringBuilder
String
String s1 = "Hello";
s1 = s1 + " World";
위의 코드에서
"Hello"라는 문자열은 절대 변하지 않는다.
s1 = s1 + " World"; 하면 기존 "Hello" 객체는 그대로 두고, 새로운 "Hello World" 객체를 새로 만들어서 s1이 그걸 가리키게 된다.
문자열을 자주 수정하면 매번 새로운 객체를 생성하고, 그 결과 메모리 낭비 + 성능 저하 문제가 생긴다.
StringBuffer
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 문자열 덧붙이기
System.out.println(sb); // "Hello World"
위의 코드를 보면
StringBuffer의 생성자에 문자열을 넣어 sb 인스턴스를 생성한다.
StringBuffer는 내부 문자열을 수정할 수 있는데 append 메서드로 문자열을 붙일 수 있고 덧붙여도 새 객체 생성 없이 같은 객체를 수정한다.
또 하나의 특징은 내부적으로 synchronized 키워드를 사용해 멀티쓰레드 환경에서도 안전하다.
단점은 동기화 때문에 조금 느리다는 것이다.
StringBuilder
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println(sb); // "Hello World"
위의 코드를 보면
StringBuffer와 이름만 다르지 다를게 없어보인다. 실제로 기능은 같다.
StringBuffer와 차이점은 동기화가 제거되서 단일 쓰레드 환경에서 훨씬 빠르다.
단점은 멀티쓰레드에서는 안전하지 않다는 것이다.
String과 StringBuffer/StringBuilder는 왜 이런 차이가 생겼을까?
자바에서 String 객체의 값은 변경할 수 없다.
자바 언어에서 불변으로 설정한 이유를 보자면 캐싱, 보안, 동기화, 성능측면 이점을 얻기 위해서이다.
1. 캐싱 : String을 불변하게 함으로써 String pool에 각 리터럴 문자열의 하나만 저장하며
다시 사용하거나 캐싱에 이용가능하며 이로 인해 힙 공간을 절약할 수 있다는 장점이 있다.
2. 보안 : 예를 들어 데이터베이스 사용자 이름, 암호는 데이터베이스 연결을 수신하기 위해
문자열로 전달되는데, 만일 번지수의 문자열 값이 변경이 가능하다면 해커가 참조 값을 변경하여
애플리케이션에 보안 문제를 일으킬 수 있다.
3. 동기화 : 불변함으로써 동시에 실행되는 여러 스레드에서 안정적이게 공유가 가능하다.
StringBuffer/StringBuilder는 내부 Buffer에 문자열을 저장해두고 그 안에서 추가, 수정, 삭제 작접을 할 수 있도록 설계되어 있기 때문이다.
목적성이 다르기 때문에 그 목적성에 맞게 사용하는 것이 좋다.
정리하자면
String은 문자열을 잘라 쓰고 수정하는 일이 거의 없으면 사용하고
StringBuffer는 문자열을 많이 합치고 수정하면서 멀티쓰레드라면 사용하고
StringBuilder는 문자열을 많이 합치고 수정하지만 단일쓰레드라면 사용한다.
익명 클래스와 람다식
익명 클래스 (Anonymous Class)
말 그대로 이름 없는 클래스이다. 일회성으로 특정 클래스나 인터페이스를 상속/구현하면서, 바로 객체를 생성할 때 사용한다.
보통 인터페이스를 구현하거나 추상 클래스를 상속할 때 많이 쓴다.
// 일반 클래스
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}
public class Main {
public static void main(String[] args) {
// 익명 클래스로 오버라이딩
Animal myAnimal = new Animal() {
public void makeSound() {
System.out.println("Woof woof");
}
}; // 객체를 생성하는 코드의 끝에 세미콜론 필요
myAnimal.makeSound(); // Woof woof
}
}
람다식 (Lambda Expression)
함수형 인터페이스(추상 메서드가 하나만 있는 인터페이스)를 간단히 구현할 때 사용한다.
// 함수형 인터페이스
interface Animal {
void makeSound();
}
public class Main {
public static void main(String[] args) {
// 람다식 사용
Animal myAnimal = () -> System.out.println("Woof woof");
myAnimal.makeSound(); // Woof woof
}
}
람다식 문법
자바스크립트의 화살표 함수랑 비슷하다.
// 기본 형태
(int x, int y) -> { return x + y; }
// 매개변수 타입 생략 가능
(x, y) -> { return x + y; }
// 실행문이 한 줄이면 {}와 return 생략 가능
(x, y) -> x +
// 매개변수가 하나면 ()도 생략 가능
x -> x * x
// 매개변수가 없을 때는 반드시 () 필요
() -> System.out.println("Hello Lambda")
I/O
I/O는 input/output의 약자로 말 그대로 입력과 출력을 의미한다. 입력이란 외부(파일, 네트워크 등)에서 프로그램으로 데이터를 가져오는 것이며, 출력이란 프로그램이 처리한 데이터를 외부(파일, 네트워크 등)에 전달하는 것이다. 데이터의 통로를 여는 것이다.
어제, 오늘 실습한 메모장 채팅방 만들기를 기준으로 이해해봤다.
자바는 Stream으로 추상화했다.
- InputStream, Reader 읽기
- OutputStream, Writer 쓰기
파일 I/O
저장(saveItem)
saveItem.addActionListener(e -> { // 람다식 사용
saveDialog.setVisible(true);
String filePath = saveDialog.getDirectory() + saveDialog.getFile();
FileWriter fw = new FileWriter(filePath);
fw.write(ta.getText()); // textarea 내용을 파일에 저장
fw.close();
});
- FileWriter는 문자 기반 출력 스트림으로 텍스트 데이터를 파일에 저장할 때 사용된다.
- fw.write() → 프로그램(TextArea 내용)을 파일에 출력(Output)
- 사용자가 선택한 경로(FileDialog)에 저장
열기(openItem)
openItem.addActionListener(e -> {
openDialog.setVisible(true);
String filePath = openDialog.getDirectory() + openDialog.getFile();
FileReader fr = new FileReader(filePath);
BufferedReader br = new BufferedReader(fr);
StringBuilder sb = new StringBuilder(1000);
String oneLine = "";
while ((oneLine = br.readLine()) != null) {
sb.append(oneLine).append("\n"); // 한 줄씩 읽어서 누적
}
ta.setText(sb.toString()); // 프로그램 화면에 표시
br.close();
fr.close();
});
- FileReader는 문자 기반 입력 스트림으로 텍스트 파일을 프로그램으로 읽어들일 때 사용한다.
- 파일 → 프로그램(TextArea) 읽기(Input)
네트워크 I/O
클라이언트 측 (NotepadFrame)
Socket s = new Socket("localhost", 9999);
dout = new DataOutputStream(s.getOutputStream());
din = new DataInputStream(s.getInputStream());
- Socket : 네트워크 연결
- dout.writeUTF() : 메시지를 서버로 전송 (Output)
- din.readUTF() : 서버로부터 메시지를 수신 (Input)
클라이언트 -> 서버 : Output
서버 -> 클라이언트 : Input
서버 측 (ChatServer)
Socket s = ss.accept(); // 클라이언트 연결 대기
din = new DataInputStream(s.getInputStream());
dout = new DataOutputStream(s.getOutputStream());
서버는 여러 클라이언트의 DataInputStream을 읽는다.
클라이언트 -> 서버 : Input
서버 -> 클라이언트 : Output
public void broadcast(String msg) {
synchronized(allClient) {
for (DataOutputStream out : allClient) {
out.writeUTF(msg); // 서버 → 클라이언트로 출력
}
}
}
받은 메시지를 broadcast()로 모든 클라이언트의 DataOutputStream에 전송한다.
둘 다 Stream을 기반이고 입력 스트림, 출력 스트림으로 방향이 구분되어 진다.
Thread
쓰레드는 프로그램 내에서 동시에 여러 작업을 실행할 수 있게 하는 실행 단위이다.
자바에서는 Thread 클래스를 상속하거나 Runnable 인터페이스를 구현해서 만든다.
클라이언트 코드에서의 Thread
class ClientThread extends Thread {
@Override
public void run() {
while(true) {
try {
String msg = din.readUTF(); // 서버에서 수신
ta.append(msg + "\n"); // 화면에 출력
} catch(IOException e) {
System.out.println("읽기 실패");
}
}
}
}
ClientThread는 서버에서 오는 메세지를 계속 읽는 전용 스레드이다.
하나는 화면 이벤트 처리를 하고 다른 하나는 서버와 통신을 한다.
이는 병렬 실행을 한다고 볼 수 있다.
서버 코드에서의 Thread
class ServerThread extends Thread {
public void run() {
try {
while(true) {
String msg = din.readUTF(); // 클라이언트 → 서버
System.out.println("받은 메세지: " + msg);
broadcast(msg); // 서버 → 모든 클라이언트
}
} catch(Exception e) {
System.out.println("접속 종료");
synchronized(allClient) {
allClient.remove(dout);
}
}
}
}
클라이언트가 접속할 때마다 ServerThread가 생성되고 실행된다.
따라서 서버는 클라이언트 수만큼 스레드가 생긴다.
각 스레드는 자기 클라이언트와 통신만을 담당한다.
결국 서버는 동시에 여러 클라이언트와 대화 가능하다.
synchronized (동기화) 키워드
synchronized는 여러 스레드가 동시에 접근하는 공유 자원(공용 데이터)을 보호하는 키워드이다.
멀티스레드 환경에서는 같은 자원을 여러 스레드가 동시에 수정하려 하면 문제가 생기기 때문이다.
ArrayList<DataOutputStream> allClient = new ArrayList();
위 코드는 모든 클라이언트의 출력 스트림을 저장한다.
여러 스레드가 동시에 allClient를 읽거나 수정할 수 있다.
public void broadcast(String msg) {
synchronized(allClient) {
for (DataOutputStream out : allClient) {
out.writeUTF(msg);
}
}
}
broadcast는 여러 스레드가 동시에 실행될 수 있는 메서드이다.
그렇기 때문에 synchronized로 아래의 코드로 한 스레드만 allClient를 건드릴 수 있도록 잠금(lock)을 거는 역할을 하게 해.
문제가 발생하지 않게 한다.
느낀 점
실습을 하면서 자바에 대한 이해를 늘려가고 I/O, Thread 같은 CS적인 내용에 대한 이해를 넓혀갈 수 있었다. 실습을 하면서 진행되기 때문에 시간을 정말 빨리 지나갔지만 그 개념을 놓치게 되는 경우도 생겼다. 다시 정리하면서 어느 정도 감은 잡았지만 완벽한 이해는 아직인 것 같다.
수업시간에 강사님께서 하신 이야기를 듣고 프론트엔드 개발자가 가져야 할 소양이 무엇인지 조금 알게 되었다.
코드 한 줄의 의미를 알고 분석, 설계할 수 있어야 한다는 점을 알게 되었고, 개발자가 갖추어야 하는 지식을 더 많이 안다는 것이 개발자로서의 철학을 갖는 데 도움이 된다는 생각이 들었다. 앞으로 더 배우려고 노력해야겠다.
'TIL' 카테고리의 다른 글
| 프론트-백 통신 기초: HttpSession과 REST API 공부하기 (0) | 2025.09.15 |
|---|---|
| 20250915 정렬 알고리즘 (0) | 2025.09.15 |
| 250910 자바 (0) | 2025.09.10 |
| 20250909 자바 및 과제 (0) | 2025.09.09 |
| 20250908 OOP 3대 concept (1) | 2025.09.08 |