Java를 처음 접한 지 10주가 되었다. 이 기간 동안 객체지향의 핵심 개념을 익히고 다양한 실습 과제를 수행했다. 주요 내용은 PTA 그래픽 설계, 연결 리스트 구현, 중간고사 대비 문제, 그리고 자습 내용이다.
PTA 그래픽 설계 과제
문제 7-1: 두 점 사이 거리
입력 문자열의 형식을 검증하고 두 점 간 거리를 계산하는 문제였다. 핵심은 정규표현식을 활용한 입력 검증이었다.
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DistanceCalculator {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String input = sc.nextLine().trim();
if (!isValidFormat(input)) {
System.out.println("Wrong Format");
return;
}
if (!hasCorrectPoints(input)) {
System.out.println("wrong number of points");
return;
}
double[] coords = extractNumbers(input);
double dist = Math.hypot(coords[0] - coords[2], coords[1] - coords[3]);
System.out.println(dist);
}
static boolean isValidFormat(String s) {
String pattern = "[\\+|-]?(0|[1-9]\\d*)(\\.\\d+)?,[\\+|-]?(0|[1-9]\\d*)(\\.\\d+)?( (\\+|-)?(0|[1-9]\\d*)(\\.\\d+)?,(\\+|-)?(0|[1-9]\\d*)(\\.\\d+)?)+";
return s.matches(pattern);
}
static boolean hasCorrectPoints(String s) {
int spaces = 0, commas = 0;
for (char c : s.toCharArray()) {
if (c == ' ') spaces++;
if (c == ',') commas++;
}
return spaces == 1 && commas == 2;
}
static double[] extractNumbers(String s) {
double[] result = new double[4];
Matcher m = Pattern.compile("[\\+\\-]?\\d+(?:\\.\\d+)?").matcher(s);
int idx = 0;
while (m.find() && idx < 4) {
result[idx++] = Double.parseDouble(m.group());
}
return result;
}
}
정규표현식을 처음 접했을 때 어려움이 있었지만, 텍스트 패턴 매칭과 데이터 추출에 매우 유용함을 깨달았다.
문제 7-2: 직선 관련 계산
기울기, 점과 직선 사이 거리, 점의 배치, 직선 평행 여부, 교점 계산 등 5가지 기능을 구현했다. 수학적 계산이 복잡해져서 교점 계산 부분에서 어려움을 겪었다.
import java.util.Scanner;
import java.util.regex.*;
public class LineOperations {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line = sc.nextLine().trim();
int mode = line.charAt(0) - '0';
if (!validateFormat(line)) {
System.out.println("Wrong Format");
return;
}
if (!checkPointCount(line, mode)) {
System.out.println("wrong number of points");
return;
}
double[] vals = parseValues(line);
switch (mode) {
case 1: calculateSlope(vals); break;
case 2: pointLineDistance(vals); break;
case 3: checkCollinearity(vals); break;
case 4: checkParallel(vals); break;
case 5: findIntersection(vals); break;
}
}
static void calculateSlope(double[] v) {
double x1 = v[1], y1 = v[2], x2 = v[3], y2 = v[4];
if (x1 == x2 && y1 == y2) {
System.out.println("points coincide");
} else if (x1 == x2) {
System.out.println("Slope does not exist");
} else {
System.out.println((y2 - y1) / (x2 - x1));
}
}
static void pointLineDistance(double[] v) {
double px = v[1], py = v[2], x1 = v[3], y1 = v[4], x2 = v[5], y2 = v[6];
if (x1 == x2 && y1 == y2) {
System.out.println("points coincide");
return;
}
double numerator = Math.abs((y2 - y1) * px + (x1 - x2) * py + x2 * y1 - y2 * x1);
double denominator = Math.hypot(x2 - x1, y2 - y1);
System.out.println(numerator / denominator);
}
static void checkCollinearity(double[] v) {
double x1 = v[1], y1 = v[2], x2 = v[3], y2 = v[4], x3 = v[5], y3 = v[6];
boolean coincident = (x1 == x2 && y1 == y2) || (x1 == x3 && y1 == y3) || (x2 == x3 && y2 == y3);
if (coincident) {
System.out.println("points coincide");
return;
}
double slope1 = (y2 - y1) / (x2 - x1);
double slope2 = (y3 - y2) / (x3 - x2);
System.out.println(slope1 == slope2);
}
static void checkParallel(double[] v) {
double x1 = v[1], y1 = v[2], x2 = v[3], y2 = v[4];
double x3 = v[5], y3 = v[6], x4 = v[7], y4 = v[8];
if ((x1 == x2 && y1 == y2) || (x3 == x4 && y3 == y4)) {
System.out.println("points coincide");
return;
}
double slope1 = (y2 - y1) / (x2 - x1);
double slope2 = (y4 - y3) / (x4 - x3);
System.out.println(slope1 == slope2);
}
static void findIntersection(double[] v) {
// 교점 계산 및 선분 내 포함 여부 판정
double x1 = v[1], y1 = v[2], x2 = v[3], y2 = v[4];
double x3 = v[5], y3 = v[6], x4 = v[7], y4 = v[8];
// 선형 방정식 풀이 로직
}
static boolean validateFormat(String s) {
return s.matches("[1-5]:[\\+\\-]?(0|[1-9]\\d*)(\\.\\d+)?,[\\+\\-]?(0|[1-9]\\d*)(\\.\\d+)?( [\\+\\-]?(0|[1-9]\\d*)(\\.\\d+)?,[\\+\\-]?(0|[1-9]\\d*)(\\.\\d+)?)+");
}
static boolean checkPointCount(String s, int mode) {
int expectedPairs = mode == 1 ? 2 : mode <= 3 ? 3 : 4;
int spaces = 0, commas = 0;
for (char c : s.toCharArray()) {
if (c == ' ') spaces++;
if (c == ',') commas++;
}
return spaces == expectedPairs - 1 && commas == expectedPairs;
}
static double[] parseValues(String s) {
double[] res = new double[9];
Matcher m = Pattern.compile("[\\+\\-]?\\d+(?:\\.\\d+)?").matcher(s);
int i = 0;
while (m.find() && i < 9) {
res[i++] = Double.valueOf(m.group());
}
return res;
}
}
문제 7-3: 삼각형 관련 계산
각형의 종류 판별, 둘레와 면적 및 중심 좌표 계산, 점의 위치 판정 등을 구현했다. 소수점 출력 형식 처리가 까다로웠다.
import java.util.Scanner;
import java.util.regex.*;
public class TriangleAnalyzer {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String input = sc.nextLine().trim();
int cmd = input.charAt(0) - '0';
if (!validate(input) || !checkPoints(input, cmd)) {
System.out.println(cmd == 0 ? "Wrong Format" : "wrong number of points");
return;
}
double[] nums = extract(input);
Vertex p1 = new Vertex(nums[1], nums[2]);
Vertex p2 = new Vertex(nums[3], nums[4]);
Vertex p3 = new Vertex(nums[5], nums[6]);
if (!formsTriangle(p1, p2, p3)) {
System.out.println("data error");
return;
}
switch (cmd) {
case 1: classifyTriangle(p1, p2, p3); break;
case 2: calculateProperties(p1, p2, p3); break;
case 3: determineAngleType(p1, p2, p3); break;
case 4: pointOnEdge(nums); break;
case 5: pointInside(nums); break;
}
}
static void calculateProperties(Vertex a, Vertex b, Vertex c) {
double perimeter = a.distanceTo(b) + b.distanceTo(c) + c.distanceTo(a);
double area = Math.abs((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y)) / 2.0;
double cx = (a.x + b.x + c.x) / 3.0;
double cy = (a.y + b.y + c.y) / 3.0;
System.out.print(formatDecimal(perimeter) + " ");
System.out.print(formatDecimal(area) + " ");
System.out.println(formatDecimal(cx) + "," + formatDecimal(cy));
}
static String formatDecimal(double val) {
String str = String.valueOf(val);
int dot = str.indexOf('.');
int decimals = Math.min(str.length() - dot - 1, 6);
return String.format("%." + decimals + "f", val);
}
static boolean formsTriangle(Vertex a, Vertex b, Vertex c) {
double ab = a.distanceTo(b), bc = b.distanceTo(c), ca = c.distanceTo(a);
return ab + bc > ca && ab + ca > bc && bc + ca > ab;
}
static void classifyTriangle(Vertex a, Vertex b, Vertex c) {
double ab = a.distanceTo(b), bc = b.distanceTo(c), ca = c.distanceTo(a);
boolean isoceles = ab == bc || bc == ca || ca == ab;
boolean equilateral = ab == bc && bc == ca;
System.out.println(isoceles + " " + equilateral);
}
static void determineAngleType(Vertex a, Vertex b, Vertex c) {
double ab = a.distanceTo(b), bc = b.distanceTo(c), ca = c.distanceTo(a);
double cosA = (ab * ab + ca * ca - bc * bc) / (2 * ab * ca);
double cosB = (ab * ab + bc * bc - ca * ca) / (2 * ab * bc);
double cosC = (bc * bc + ca * ca - ab * ab) / (2 * bc * ca);
if (cosA < 0 || cosB < 0 || cosC < 0) {
System.out.println("true false false");
} else if (Math.abs(cosA) < 0.001 || Math.abs(cosB) < 0.001 || Math.abs(cosC) < 0.001) {
System.out.println("false true false");
} else {
System.out.println("false false true");
}
}
static boolean validate(String s) { return s.matches("[1-5]:.*"); }
static boolean checkPoints(String s, int mode) { return true; }
static double[] extract(String s) { return new double[10]; }
static void pointOnEdge(double[] nums) {}
static void pointInside(double[] nums) {}
}
class Vertex {
double x, y;
Vertex(double x, double y) { this.x = x; this.y = y; }
double distanceTo(Vertex o) { return Math.hypot(x - o.x, y - o.y); }
}
중간고사 실습
幾何 객체 관리 시스템
추상 클래스와 상속, 컬렉션을 활용한 기하학 객체 관리 시스템을 구현했다.
import java.util.ArrayList;
import java.util.Scanner;
public class GeometryManager {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ShapeContainer container = new ShapeContainer();
int choice;
while ((choice = sc.nextInt()) != 0) {
switch (choice) {
case 1: {
double x = sc.nextDouble(), y = sc.nextDouble();
if (isValid(x, y)) container.add(new GeoPoint(x, y));
else System.out.println("Wrong Format");
break;
}
case 2: {
double x1 = sc.nextDouble(), y1 = sc.nextDouble();
double x2 = sc.nextDouble(), y2 = sc.nextDouble();
String color = sc.next();
if (isValid(x1, y1) && isValid(x2, y2)) {
container.add(new GeoLine(new GeoPoint(x1, y1), new GeoPoint(x2, y2), color));
} else {
System.out.println("Wrong Format");
}
break;
}
case 3: {
container.add(new GeoPlane(sc.next()));
break;
}
case 4: {
int idx = sc.nextInt();
container.removeAt(idx);
break;
}
}
}
container.displayAll();
}
static boolean isValid(double x, double y) {
return x > 0 && x <= 200 && y > 0 && y <= 200;
}
}
abstract class Drawable {
public abstract void display();
}
class GeoPoint extends Drawable {
private double px, py;
GeoPoint(double x, double y) { this.px = x; this.py = y; }
double getX() { return px; }
double getY() { return py; }
@Override
public void display() {
System.out.printf("(%.2f,%.2f)%n", px, py);
}
}
class GeoLine extends Drawable {
private GeoPoint start, end;
private String lineColor;
GeoLine(GeoPoint s, GeoPoint e, String c) {
this.start = s; this.end = e; this.lineColor = c;
}
@Override
public void display() {
System.out.println("The line's color is:" + lineColor);
System.out.println("The line's begin point's Coordinate is:");
start.display();
System.out.println("The line's end point's Coordinate is:");
end.display();
System.out.printf("The line's length is:%.2f%n", computeLength());
}
double computeLength() {
return Math.hypot(start.getX() - end.getX(), start.getY() - end.getY());
}
}
class GeoPlane extends Drawable {
private String fillColor;
GeoPlane(String c) { this.fillColor = c; }
@Override
public void display() {
System.out.println("The Plane's color is:" + fillColor);
}
}
class ShapeContainer {
private ArrayList<Drawable> shapes = new ArrayList<>();
void add(Drawable d) { shapes.add(d); }
void removeAt(int index) {
if (index > 0 && index <= shapes.size()) shapes.remove(index - 1);
}
void displayAll() {
for (Drawable d : shapes) d.display();
}
}
연결 리스트 직접 구현
수업 시간에 배운 내용을 바탕으로 단일 연결 리스트를 구현했다. 인터페이스 활용법을 익히는 데 집중했다.
public class CustomLinkedList<T> {
private Node<T> head;
private int length;
private static class Node<T> {
T data;
Node<T> next;
Node(T data) { this.data = data; }
}
public void append(T item) {
Node<T> newNode = new Node<>(item);
if (head == null) {
head = newNode;
} else {
Node<T> curr = head;
while (curr.next != null) curr = curr.next;
curr.next = newNode;
}
length++;
}
public T removeFirst() {
if (head == null) throw new RuntimeException("Empty list");
T data = head.data;
head = head.next;
length--;
return data;
}
public int size() { return length; }
public boolean contains(T target) {
for (Node<T> curr = head; curr != null; curr = curr.next) {
if (curr.data.equals(target)) return true;
}
return false;
}
}
컬렉션 프레임워크 학습
HashSet 활용
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
Set<String> cities = new HashSet<>();
cities.add("Seoul");
cities.add("Tokyo");
cities.add("New York");
cities.add("Seoul"); // 중복 무시
System.out.println(cities);
for (String city : cities) {
System.out.print(city.toUpperCase() + " ");
}
cities.forEach(c -> System.out.print(c.toLowerCase() + " "));
}
}
TreeSet과 정렬
import java.util.*;
public class SortedSetDemo {
public static void main(String[] args) {
Set<String> hashSet = new HashSet<>(Arrays.asList(
"Apple", "Banana", "Cherry", "Date"
));
TreeSet<String> treeSet = new TreeSet<>(hashSet);
System.out.println("Sorted: " + treeSet);
System.out.println("First: " + treeSet.first());
System.out.println("Last: " + treeSet.last());
System.out.println("HeadSet: " + treeSet.headSet("Cherry"));
System.out.println("TailSet: " + treeSet.tailSet("Cherry"));
System.out.println("Lower 'C': " + treeSet.lower("C"));
System.out.println("Higher 'C': " + treeSet.higher("C"));
}
}
Map 인터페이스 활용
import java.util.*;
public class MapComparison {
public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Kim", 90);
hashMap.put("Lee", 85);
hashMap.put("Park", 92);
Map<String, Integer> treeMap = new TreeMap<>(hashMap);
System.out.println("TreeMap (sorted): " + treeMap);
Map<String, Integer> linkedMap = new LinkedHashMap<>(16, 0.75f, true);
linkedMap.putAll(hashMap);
linkedMap.get("Lee"); // 접근 순서 갱신
treeMap.forEach((k, v) -> System.out.println(k + ": " + v));
}
}
Lambda 표현식과 Stream API
함수형 인터페이스 활용
public class LambdaBasics {
public static void main(String[] args) {
Operation add = (a, b) -> a + b;
Operation subtract = (a, b) -> a - b;
Operation multiply = (a, b) -> a * b;
Operation divide = (a, b) -> b != 0 ? a / b : 0;
System.out.println("15 + 3 = " + add.apply(15, 3));
System.out.println("15 - 3 = " + subtract.apply(15, 3));
System.out.println("15 * 3 = " + multiply.apply(15, 3));
System.out.println("15 / 3 = " + divide.apply(15, 3));
Messenger msg = text -> System.out.println("Message: " + text);
msg.send("Hello Lambda");
}
@FunctionalInterface
interface Operation {
int apply(int x, int y);
}
@FunctionalInterface
interface Messenger {
void send(String text);
}
}
Stream 파이프라인
import java.util.*;
import java.util.stream.*;
public class StreamPipeline {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(3, 7, 2, 9, 1, 8, 5);
// 필터링과 출력
values.stream()
.filter(n -> n > 5)
.forEach(System.out::println);
// 집계 연산
Optional<Integer> first = values.stream()
.filter(n -> n > 5)
.findFirst();
Optional<Integer> any = values.parallelStream()
.filter(n -> n > 5)
.findAny();
boolean hasSmall = values.stream().anyMatch(n -> n < 2);
System.out.println("First match: " + first.orElse(-1));
System.out.println("Any match: " + any.orElse(-1));
System.out.println("Has value < 2: " + hasSmall);
// 최대/최소
List<String> names = Arrays.asList("Alice", "Bob", "Christina", "Dave");
Optional<String> longest = names.stream()
.max(Comparator.comparingInt(String::length));
System.out.println("Longest name: " + longest.orElse("None"));
}
}
핵심 개념 정리
상속과 다형성
상속은 주로 일반적인 개념에서 구체적인 개념으로의 확장에 사용된다. 코드 재사용보다는 타입 계층 구조를 명확히 하는 것이 목적이다.
상 클래스 vs 인터페이스
- 추상 클래스: 공통 구현을 제공하는 템플릿 역할, 직접 인스턴스화 불가
- 인터페이스: 행위 계약 정의, 다중 구현 가능, Java 8 이후 default 메서드 지원
문제 해결 경험
대부분의 버그는細微한 실수에서 발생했다. 예를 들어 문자 비교 시 == 대신 .equals()를 사용하지 않거나, 문자 상수에 따옴표를 누락하는 경우가 있었다. 수학적 계산에서는 부동소수점 오차 처리에 주의가 필요했다.
문자열 분할로 시작했으나 요구사항을 모두 만족하지 못해 정규표현식으로 전환한 경험이 특히 기억에 는다. 복잡한 패턴 매칭이 필요한 경우 정규표현식의 우위가 명확하다.