Java Generics(제네릭)은 컴파일 시간에 타입을 체크하고 형 안전성(type safety)을 제공하는 Java의 기능이다. Generics를 사용하면 컴파일러는 타입을 지정하지 않은 경우 경고를 발생시키며, 이를 통해 프로그램의 안전성을 높일 수 있다.
타입 매개변수(Type Parameter): 클래스, 메서드 또는 인터페이스를 정의할 때, 타입을 나타내는 변수. 대표적으로 E
(Element), T
(Type), K
(Key), V
(Value) 등이 사용.
제네릭 클래스(Generic Class): 타입 매개변수를 사용하여 클래스를 정의하는 것.
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
제네릭 메서드(Generic Method): 타입 매개변수를 사용하여 메서드를 정의하는 것.
public <T> T genericMethod(T value) {
// 메서드 내용
return value;
}
와일드카드(Wildcard): ?
를 사용하여 모든 타입을 나타낼 수 있는데, 이를 활용하여 다양한 타입에 대응할 수 있다.
public void processList(List<?> list) {
// 리스트를 처리하는 로직
}
안전성(Type Safety): 컴파일 시에 타입을 체크하기 때문에 런타임에 발생할 수 있는 타입 관련 오류를 사전에 방지.
재사용성(Reusability): 타입 매개변수를 이용하여 여러 타입에서 동일한 코드를 재사용.
표현력(Expressiveness): 코드의 가독성이 향상되며, 제네릭을 사용함으로써 불필요한 형변환 코드 감소.
// 제네릭 클래스 예제
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 제네릭 메서드 예제
public class GenericMethodExample {
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
// 와일드카드 예제
public class WildcardExample {
public void processList(List<?> list) {
for (Object element : list) {
System.out.print(element + " ");
}
System.out.println();
}
}
// Generics를 사용한 예제 코드
public class GenericsExample {
public static void main(String[] args) {
// 제네릭 클래스 사용
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello, Generics!");
System.out.println("Box Content: " + stringBox.getContent());
// 제네릭 메서드 사용
GenericMethodExample genericMethodExample = new GenericMethodExample();
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"A", "B", "C", "D", "E"};
System.out.print("Integer Array: ");
genericMethodExample.printArray(intArray);
System.out.print("String Array: ");
genericMethodExample.printArray(stringArray);
// 와일드카드 사용
WildcardExample wildcardExample = new WildcardExample();
List<Integer> integerList = Arrays.asList(1, 2, 3, 4);
List<String> stringList = Arrays.asList("One", "Two", "Three", "Four");
System.out.print("Integer List: ");
wildcardExample.processList(integerList);
System.out.print("String List: ");
wildcardExample.processList(stringList);
}
}
텍스트(Text)는 문자열(String)을 다루는데 사용. Java에서 문자열은 기본적으로 java.lang.String
클래스를 통해 표현되며, 텍스트 처리에는 문자열과 관련된 여러 클래스 및 메서드가 사용된다.
String
클래스는 Java에서 문자열을 표현하는데 주로 사용되며, 불변(Immutable)한 특성을 가지고 있다. 새로운 문자열을 생성하면 이전 문자열은 변경되지 않고 새로운 문자열이 생성된다.
// 문자열 생성
String text = "Hello, Java!";
length()
: 문자열의 길이를 반환.
int length = text.length(); // 13
charAt(int index)
: 지정된 인덱스의 문자를 반환.
char firstChar = text.charAt(0); // 'H'
substring(int beginIndex)
, substring(int beginIndex, int endIndex)
: 부분 문자열을 반환.
String subString1 = text.substring(7); // "Java!"
String subString2 = text.substring(7, 11); // "Java"
concat(String str)
: 문자열을 이어붙인다.
String concatenated = text.concat(" Welcome!"); // "Hello, Java! Welcome!"
indexOf(String str)
, indexOf(String str, int fromIndex)
: 지정된 문자열이 처음으로 나타나는 인덱스를 반환.
int index = text.indexOf("Java"); // 7
startsWith(String prefix)
, endsWith(String suffix)
: 주어진 접두사 또는 접미사로 시작하는지 또는 끝나는지 여부를 확인.
boolean startsWithHello = text.startsWith("Hello"); // true
boolean endsWithJava = text.endsWith("Java"); // false
StringBuilder
및 StringBuffer
클래스는 가변적인 문자열을 다루는데 사용됩니다. 이들은 문자열을 변경할 수 있는 메서드를 제공하며, 특히 문자열을 동적으로 변경해야 하는 경우에 유용합니다.
// StringBuilder를 사용한 문자열 변경
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.append(", Java!"); // "Hello, Java!"
append(String str)
: 문자열을 뒤에 추가.
StringBuilder builder = new StringBuilder("Hello");
builder.append(", Java!"); // "Hello, Java!"
insert(int offset, String str)
: 지정된 위치에 문자열을 삽입.
StringBuilder builder = new StringBuilder("Hello");
builder.insert(5, ", Java!"); // "Hello, Java!"
delete(int start, int end)
: 지정된 범위의 문자열을 삭제.
StringBuilder builder = new StringBuilder("Hello, Java!");
builder.delete(7, 12); // "Hello!"
reverse()
: 문자열을 뒤집는다.
StringBuilder builder = new StringBuilder("Hello");
builder.reverse(); // "olleH"
StringTokenizer
클래스는 문자열을 특정 구분자(delimiter)를 기준으로 토큰(token)으로 분리하는 데 사용.
StringTokenizer tokenizer = new StringTokenizer("Apple,Orange,Banana", ",");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
System.out.println(token);
}
Java에서는 정규 표현식을 사용하여 문자열의 패턴을 검색하고 조작하는 데에 Pattern
및 Matcher
클래스를 제공.
import java.util.regex.*;
Pattern pattern = Pattern.compile("\\b\\d+\\b");
Matcher matcher = pattern.matcher("123 Java 456");
while (matcher.find()) {
System.out.println(matcher.group()); // "123", "456"
}
Java I/O(Input/Output)는 Java에서 데이터의 입력과 출력을 다루는 매우 중요한 부분. Java I/O는 java.io
패키지에 포함되어 있으며, 데이터를 읽고 쓰는 다양한 클래스와 인터페이스를 제공.
Java I/O에서 데이터는 스트림을 통해 입출력된다. 스트림은 데이터의 흐름을 나타내며, 입력 스트림(Input Stream)은 데이터를 읽어오는데 사용되고, 출력 스트림(Output Stream)은 데이터를 쓰는데 사용된다.
바이트 스트림(Byte Stream): 바이트 단위로 데이터를 처리하는 스트림. InputStream
및 OutputStream
클래스 계열이 여기에 속한다.
문자 스트림(Character Stream): 문자(char) 단위로 데이터를 처리하는 스트림. Reader
및 Writer
클래스 계열이 여기에 속한다.
InputStream
: 바이트 스트림의 최상위 클래스로, 입력 스트림을 나타낸다. read()
메서드를 통해 바이트를 읽어온다.
OutputStream
: 바이트 스트림의 최상위 클래스로, 출력 스트림을 나타낸다. write()
메서드를 통해 바이트를 쓰기한다.
FileInputStream
: 파일로부터 데이터를 바이트 단위로 읽어오는 클래스.
FileOutputStream
: 파일에 데이터를 바이트 단위로 쓰는 클래스.
try (FileInputStream inputStream = new FileInputStream("input.txt");
FileOutputStream outputStream = new FileOutputStream("output.txt")) {
int data;
while ((data = inputStream.read()) != -1) {
outputStream.write(data);
}
} catch (IOException e) {
e.printStackTrace();
}
Reader
: 문자 스트림의 최상위 클래스로, 입력 스트림을 나타낸다. read()
메서드를 통해 문자를 읽어온다.
Writer
: 문자 스트림의 최상위 클래스로, 출력 스트림을 나타낸다. write()
메서드를 통해 문자를 쓰기한다.
FileReader
: 파일로부터 데이터를 문자 단위로 읽어오는 클래스.
FileWriter
: 파일에 데이터를 문자 단위로 쓰는 클래스.
try (FileReader reader = new FileReader("input.txt");
FileWriter writer = new FileWriter("output.txt")) {
int data;
while ((data = reader.read()) != -1) {
writer.write(data);
}
} catch (IOException e) {
e.printStackTrace();
}
보조 스트림은 다른 스트림을 감싸서 추가적인 기능을 제공하는 스트림. 여러 보조 스트림을 조합하여 사용할 수 있다.
BufferedReader
: 버퍼링을 제공하여 문자를 라인 단위로 효율적으로 읽어온다.
BufferedWriter
: 버퍼링을 제공하여 문자를 라인 단위로 효율적으로 쓰기한다.
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
bufferedWriter.write(line);
bufferedWriter.newLine(); // 개행 문자 추가
}
} catch (IOException e) {
e.printStackTrace();
}
직렬화는 객체를 바이트 스트림으로 변환하는 과정이며, 역직렬화는 바이트 스트림을 객체로 변환하는 과정. ObjectInputStream
및 ObjectOutputStream
을 사용.
import java.io.*;
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class SerializationExample {
public static void main(String[] args) {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person.ser"));
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person.ser"))) {
// 직렬화
Person person = new Person("John", 25);
objectOutputStream.writeObject(person);
// 역직렬화
Person deserializedPerson = (Person) objectInputStream.readObject();
System.out.println("Deserialized Person: " + desFileInputStream("person.ser"));
Person deserializedPerson = (Person) objectInputStream.readObject();
objectInputStream.close();
System.out.println("Deserialized Person: " + deserializedPerson);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}