Let's Talk

Feel free to reach out. I'll get back to you as soon as possible.

Study 7 min

JAVA Detail(제너릭, 형 안정성, 스트림, 직렬화)

JAVA의 제네릭, 형 안정성, 스트림, 그리고 객체 직렬화에 대해 심도 있게 다룹니다.

JAVA Detail(제너릭, 형 안정성, 스트림, 직렬화)

자바를 한 단계 더 들어가면 만나는 주제들입니다. 제네릭(형 안정성) → 가변인자 → 입출력 스트림 → 객체 직렬화 순서로, 각각이 왜 필요한지와 코드 패턴을 정리합니다.

제네릭과 형 안정성(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 StreamsCharacter Streams
Source StreamsInputStreamReader
Sink StreamsOutputStreamWriter
FileFileInputStream,FileOutputStreamFileReader,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())
TypeByte streamsCharacter Streams
BufferingBufferedInputStream,BufferedOutputStreamBufferedReader,BufferedWriter
Data conversionDataInputStream,DataOutputStream
FilteringFilterInputStream,FilterOutputStreamFilterReader,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;

  1. 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;
	}
}
  1. 객체 직렬화 하여 저장하기
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) {}}
		}
	}
}

cus.data

  1. 객체 직렬화 한 데이터 읽기
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) {}}
		}
	}
}

cus.data

한 줄 요약

  • 제네릭: 컬렉션·클래스에 타입을 못박아 형 변환 없이 컴파일 시점에 타입 오류를 차단(형 안정성)
  • 가변인자(String...): 인자 개수를 고정하지 않고 받기. 내부적으로는 배열로 전달됨
  • 스트림: source→sink 데이터 흐름. 바이트는 InputStream/OutputStream, 문자는 Reader/Writer, 버퍼링은 BufferedReader/Writer
  • 객체 직렬화: Serializable(마커 인터페이스)을 구현하면 객체를 파일로 저장/복원 가능. 멤버 데이터만 저장되며 serialVersionUID로 구조 변경 호환성 관리