자바를 한 단계 더 들어가면 만나는 주제들입니다. 제네릭(형 안정성) → 가변인자 → 입출력 스트림 → 객체 직렬화 순서로, 각각이 왜 필요한지와 코드 패턴을 정리합니다.
제네릭과 형 안정성(Generic & Type Safety)
<사용법>
클래스 이름<데이터 타입>, 메서드 이름
ex) ArrayList<String> myList = new ArrayList<>();
-
제네릭 기능은 JDK1.5(JDK5)부터 도입되었음
-
이전까지는 모든 컬렉션이 Object유형의 객체를 받기 때문에 어떤 객체든지 엘리먼트로 넣을 수 있었음
하지만 엘리먼트를 사용하기 위해서는 넣기전의 상태로 되돌려야 했고 반드시 형 변환이 필요했음
-
제네릭 표현을 통해 컬렉션의 엘리먼트에 해당 데이터 타입만 들어가도록 체크할 수 있음
-
형 변환 필요X, 다양한 엘리먼트가 들어갈 때 발생할 수 있는 오류를 원천적으로 차단할 수 있음
public class Person<E> {
private E name;
private int age;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public <T> T test(T t){
return t;
}
}
클래스 선언부에 있는
는 유형 매개변수 객체 생성 시이 자리로 감 예를 들어 Person p2 = new Person<>(); 라면 이 자리로 가는 것
제네릭 메서드 : 메서드 선언에 유형 매개변수가 포함되어 있는 매개변수 - 클래스 선언부에서 정의된 유형 매개변수를 사용하는 방법(getName()) - 클래스 선언부에서 정의되지 않은 유 형 매개변수를 사용하는 방법(test(T t))
public class PersonExample {
public static void main(String[] args) {
Person p1 = new Person();
Person<String> p2 = new Person<>();
Person<Integer> p3 = new Person<>();
p1.setName(new Object());
p2.setName("123");
p3.setName(123);
int a = p2.test(1);
double b = p2.test(1.1);
String c = p2.test("String");
boolean d = p2.test(true);
}
}
가변인자
메서드에 들어갈 인자의 수를 미리 정해놓지 않고 여러 개의 인자를 받을 수 있도록 만드는 것
-
대표적으로 System.out.printf() 메서드
-
가변인자로 받은 변수들은 배열을 통해 전달됨
public class VariableLengthExample {
public static void main(String[] args) {
log("Hello"); // 로그 : Hello,
log("VariableLengthExample", "Hello"); // 로그 : VariableLengthExample, Hello,
log("a","b","c"); // 로그 : a, b, c,
log(); // 로그 :
}
public static void log(String... msg) {
System.out.print("로그 : ");
for(String message : msg) {
System.out.print(message+", ");
}
System.out.println();
}
}
IO Programming
스트림
스트림(stream)
-
스트림은 source에서 sink로의 데이터 흐름
-
source로부터 나오는 스트림은 입력 스트림, sink로 빠지는 스트림은 출력 스트림
-
입출력 스트림은 바이트 단위로 흐르는 InputStream, OutputStream 캐릭터 단위로 흐르는 Reader, Writer
| Byte Streams | Character Streams | |
|---|---|---|
| Source Streams | InputStream | Reader |
| Sink Streams | OutputStream | Writer |
| File | FileInputStream,FileOutputStream | FileReader,FileWriter |
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class NodeStreamExample {
public static void main(String[] args) {
FileReader input = null; //입력 스트림 객체 생성
FileWriter output = null;
try {
String inFile = "in.txt";
String outFile = "out.txt";
input = new FileReader(inFile);
output = new FileWriter(outFile);
char[] buffer = new char[128]; // 데이터를 저장할 변수 선언
int readCount = 0;
do {
readCount = input.read(buffer); // 파일로부터 읽은 데이터를 buffer라는 배열에 저장
if(readCount >= 0) {
output.write(buffer, 0, readCount); //파일에 데이터 쓰기
}
} while ( readCount != -1 );
System.out.println("파일이 복사되었습니다.");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(input != null) try {input.close();} catch (IOException e) {}
if(output != null) try {output.close();} catch (IOException e) {}
}
}
}
필터스트림
(node -> Node stream -> Filter stream -> readXxx())
| Type | Byte streams | Character Streams |
|---|---|---|
| Buffering | BufferedInputStream,BufferedOutputStream | BufferedReader,BufferedWriter |
| Data conversion | DataInputStream,DataOutputStream | |
| Filtering | FilterInputStream,FilterOutputStream | FilterReader,FilterWriter |
BufferedReader => 한 줄씩 읽어와서 String 형식으로 반환해 주는 readLine()메서드
import java.io.*;
public class BufferedExample {
public static void main(String[] args) {
FileReader fr = null;
BufferedReader br = null;
FileWriter fw = null;
try {
fr = new FileReader("C:\\Users\\송민지\\Desktop\\bufferExample.txt");
br = new BufferedReader(fr);
fw = new FileWriter("C:\\Users\\송민지\\Desktop\\bufferOut.txt");
String data = null;
int i = 1;
while((data=br.readLine()) != null) { //힌줄씩 읽어와서 String형식으로 반환
System.out.println(i++ +"번째 줄 읽음");
if(data.contains("gmail")) {
fw.write(data);
fw.write("\r\n");
fw.flush();
}
}
System.out.println("파일 작성 완료");
} catch(IOException e) {
e.printStackTrace();
} finally{
if(br != null) {try {br.close();}catch(IOException e) {}}
if(fw != null) {try {fw.close();}catch(IOException e) {}}
}
}
}
bufferExample.txt 파일을 바탕화면에 생성 후 내용 입력 -> BufferedReader의 readLine()메서드가 한 줄씩 읽어와서 data에 저장
-> data에 “gmail”이라는 글자가 있으면 bufferOut.txt파일에 쓰기
=> bufferOut.txt파일에는 bufferExample.txt에서 읽어온 데이터 중 “gmail”이 포함된 줄만 입력되어 있음
객체직렬화
Serializable
-
직렬화 클래스가 구현해야 할 인터페이스
-
마커 인터페이스
-
객체 단위로 파일에 저장할 수 있도록 함
-
실제로 저장되는 것은 멤버변수 데이터만 저장되고 생성자나 메서드 코드는 저장 되지 않음
(받는 쪽에서도 직렬화된 객체에 해당하는 클래스를 그대로 가지고 있어야 함)
-
객체 저장 => ObjectOutputStream의 writeObject() 메서드 이용
-
객체 읽기 => ObjectInputStream의 readObject() 메서드 이용
-
serialVersionUID
: 직렬화 클래스의 “구조가 변경될 경우” 불러오지 못하는 예외를 방지하기 위해 선언
private static final long serialVersionUID = -5831825079057132624L;
- Customer 클래스 생성 - 데이터를 저장하는 클래스
implements Serializable => Serializable 인터페이스를 상속받음
public class Customer implements Serializable{
private static final long serialVersionUID = -5831825079057132624L;
private String name;
private String sex;
private String email;
private int age;
public Customer(String name, String sex, String email, int age) {
this.name = name;
this.sex = sex;
this.email = email;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Customer [name=" + name + ", sex=" + sex + ", email=" + email + ", age=" + age;
}
}
- 객체 직렬화 하여 저장하기
public class ObjectWriteExample {
public static void main(String[] args) {
Customer c1 = new Customer("송민지","여","smj9030@naver.com",20);
Customer c2 = new Customer("홍길동","남","abc123@gmail.com",23);
Customer c3 = new Customer("김철수","남","aaa111@naver.com",25);
ArrayList<Customer> list = new ArrayList<Customer>();
list.add(c1); list.add(c2); list.add(c3);
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("cus.data");
oos = new ObjectOutputStream(fos);
oos.writeObject(list);
System.out.println("파일 작성 완료");
}catch(IOException e){
e.printStackTrace();
}finally {
if(oos!=null) {try {oos.close();}catch(IOException e) {}}
}
}
}

- 객체 직렬화 한 데이터 읽기
public class ObjectReadExample {
public static void main(String[] args) {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream("cus.data");
ois = new ObjectInputStream(fis);
ArrayList<Customer> cuslist = (ArrayList<Customer>)ois.readObject();
System.out.println("파일 로드 완료");
System.out.println(cuslist);
}catch (Exception e) {
e.printStackTrace();
}finally {
if(ois!=null) {try {ois.close();}catch (IOException e) {}}
}
}
}
한 줄 요약
- 제네릭: 컬렉션·클래스에 타입을 못박아 형 변환 없이 컴파일 시점에 타입 오류를 차단(형 안정성)
- 가변인자(
String...): 인자 개수를 고정하지 않고 받기. 내부적으로는 배열로 전달됨 - 스트림: source→sink 데이터 흐름. 바이트는
InputStream/OutputStream, 문자는Reader/Writer, 버퍼링은BufferedReader/Writer - 객체 직렬화:
Serializable(마커 인터페이스)을 구현하면 객체를 파일로 저장/복원 가능. 멤버 데이터만 저장되며serialVersionUID로 구조 변경 호환성 관리
