[ 3주차 - 0827 ]
금일 커리큘럼
├ 09:00 ~ 12:00 자바 프로그래밍 기초 (오브젝트 Override, final과 열거형, 내부클래스)
└ 13:00 ~ 18:00 자바 프로그래밍 기초 (오브젝트 메서드, String 클래스, String 빌더&버퍼, Math)
1. 오브젝트 Override
Object가 가진 메서드들을 자기 클래스 목적에 맞게 재정의하여 사용- 대표적으로
toString(),equals(),hashCode()등 있음
import java.util.Objects;
class Pen {
private String name; // 제품명
private String code; // 제품코드
private String type; // 타입
public Pen(String name, String code, String type) {
this.name = name;
this.code = code;
this.type = type;
}
@Override
public String toString() {
return "Pen{" +
"name='" + name + '\'' +
", code='" + code + '\'' +
", type='" + type + '\'' +
'}';
}
@Override
public boolean equals(Object obj) {
// 자기자신 비교 리턴 - true
if (this == obj) return true;
// 비교 대상이 null 이거나, 클래스 타입이 다르면 false
if (obj == null || getClass() != obj.getClass()) return false;
// 타입이 같다면 명시적 형변환
// 형변환 하는 이유는 Object 타입으로 매개변수 받고 있어서 자기 클래스 속성 접근 안됨.
Pen pen = (Pen) obj;
// 매개변수 객체와 자기자신과 비교
return name.equals(pen.name) && code.equals(pen.code) && type.equals(pen.type);
}
@Override
public int hashCode() {
int result = 17; // 초기값 (보통 17 사용) ?
// 각 필드의 해시를 누적
// 누적 방식: result = 31 * result + fieldHash
// 누적 시키는 이유 ? -> (abc) != (cba) 처럼 순서 다른것을 고려하여 누적계산
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (code != null ? code.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
// 또는
// import java.util.Objects
// return Objects.hash(name, code, type);
}
}
public class PenExam {
public static void main(String[] args) {
Pen p1 = new Pen("파이롯트", "A105", "샤프");
Pen p2 = new Pen("파이롯트", "A105", "샤프");
Pen p3 = new Pen("모나미", "B101", "볼펜");
// toString() Override
System.out.println(p1.toString()); // Pen{name='파이롯트', code='A105', type='샤프'}
System.out.println(p1); // toString() 자동 호출
// equals() Override
System.out.println(p1 == p2); // false
System.out.println(p1.equals(p2)); // true
System.out.println(p1.equals(p3)); // false
// hashCode() Override
System.out.println(p1.hashCode()); // 2019936075
System.out.println(p2.hashCode()); // 2019936075
System.out.println(p3.hashCode()); // -1657557753
}
}
2. final 키워드와 열거형(enum)
finnal 키워드 정리
- finnal 은 불변성(Immutable) 보장함
-final 변수: 값(참조) 변경 불가
-final 메소드: 오버라이딩 불가
-final 클래스: 상속 불가
import java.util.ArrayList;
public class FinalAll {
// final 변수 - 값 변경 불가
private final int MAX_SIZE = 100; // 선언과 동시에 초기화 (값 변경 불가)
private final String name; // 생성자에서 한 번만 초기화 가능
private final List<String> items; // 참조는 불변, 내부 요소(add/remove)는 변경 가능
public FinalAll(String name) {
this.name = name; // 생성자에서 초기화 (필수)
this.items = new ArrayList<>(); // 참조는 한 번만 할당 가능
}
// final 메소드 - 오버라이딩 불가
public final void display() {
System.out.println("Name: " + name);
}
public void addItem(String item) {
items.add(item); // ✅ 가능: 참조가 가리키는 리스트의 내용은 변경 가능
// items = new ArrayList<>(); // ❌ 불가능: 참조 자체를 새로운 객체로 변경 불가
}
}
// final 클래스 - 상속 불가 (자식에서 ❌ expends 안됨!)
public final class Parent { /* ... */ }
// ❌ 컴파일 오류: final 클래스는 상속할 수 없음. (String, Integer 도 파이널임)
// public class Child extends Parent { /* ... */ }
// public class Child extends String { /* ... */ }
// public class Child extends Integer { /* ... */ }
열거형(enum)
열거형은 서로 관련 있는 상수들의 집합을 하나의 타입으로 정의할 수 있게 해주는 특별한 클래스
즉, 의미 없는 숫자나 문자열 상수 대신 의미 있는 이름을 가진 상수들을 타입 안전하게 사용할 수 있도록 도와준다.
enum 특징
- 타입 안전성 : 컴파일 시점에 타입 체크가 이루어져, 잘못된 값 대입을 방지
- 상수 그룹화 : 관련 있는 상수들을 한 곳에서 관리 가능
- 예 : 방향의
동서남북, 상태의시작-진행-종료
- 예 : 방향의
- 싱글톤 보장: 각 열거 상수는 JVM 내에서 하나만 존재 (메모리 효율적)
- switch문 지원: 직관적이고 가독성 좋은 분기문 작성 가능
기본 열거형
package week_03._0827._enum;
// 기본 열거형
public enum Day {
// MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
월요일, 화요일, 수요일, 목요일, 금요일, 토요일, 일요일
}
값과 메서드 가진 열거형
package week_03._0827._enum;
// 값과 메서드 가진 열거형
public enum Order {
대기("P", "대기중"),
처리("R", "처리중"),
배송("S", "배송중"),
완료("D", "배송완료"),
취소("C", "취소");
private final String code;
private final String info;
// 생성자
Order(String code, String info) {
this.code = code;
this.info = info;
}
public String getCode() {
return code;
}
public String getInfo() {
return info;
}
// 정적 메소드
public static Order fromCode(String code) {
for (Order status : Order.values()) {
if (status.code.equals(code)) {
return status;
}
}
throw new IllegalArgumentException("Invalid code: " + code);
}
}
열거형 사용예시
package week_03._0827;
import week_03._0827._enum.Day;
import week_03._0827._enum.Order;
public class EnumTest {
public static void main(String[] args) {
// 기본열거형 사용
Day today = Day.월요일;
switch(today) {
case 월요일:
case 화요일:
case 수요일:
case 목요일:
case 금요일:
System.out.println("평일입니다.");
break;
case 토요일:
case 일요일:
System.out.println("주말입니다.");
break;
}
System.out.println("-".repeat(5));
// Order 사용
Order status = Order.대기;
System.out.println(status.getInfo()); // 대기중
System.out.println("-".repeat(5));
// Order 모든 값 순회
for (Order s : Order.values()) {
System.out.println(s.name() + ": " + s.getInfo());
}
System.out.println("-".repeat(5));
// Order 코드로 찾기
Order found = Order.fromCode("S");
System.out.println(found); // SHIPPED
}
}
# 실행 결과
평일입니다.
-----
대기중
-----
대기: 대기중
처리: 처리중
배송: 배송중
완료: 배송완료
취소: 취소
-----
배송
3. 내부 클래스
내부 클래스는 클래스 안에 선언된 클래스
- 멤버 내부 클래스 : 외부 클래스와 밀접한 관계이고 외부 인스턴스 없이 사용 불가할 때
- 정적 내부 클래스 : 외부 클래스의 인스턴스와 무관하게 사용할 때
- 지역 내부 클래스 : 특정 메소드 내에서만 사용할 때
- 익명 내부 클래스 : 일회성 구현이 필요할 때 (Java 8+ 람다 선호)
public class OuterCls {
private String message = "필드(1)";
private static String message2 = "스태틱필드(2)";
// 1. 멥버 내부
public class Inner {
public void show() {
System.out.println("이너 Class - 외부 필드: " + message); // 논필드 접근가능
}
}
// 2. 정적 - 스태틱
public static class InnerStatic {
public void show() {
// System.out.println("스태틱클래스 - 외부 필드:" + message); // 논필드 접근불가
System.out.println("스태틱 Class - 외부 스태틱필드: " + message2);
}
}
// 3. 지역 내부
public void method1() {
int a = 1; // 묵시적 final
final int b = 2; // 명시적 final
class Local { // public 안됨
public void show() {
System.out.println("메서드내부 class - 외부 필드: " + message);
System.out.println("메서드내부 class - 외부 스태틱필드: " + message2);
System.out.println("메서드내부 class - 메서드 지역변수(묵시적_final): " + a);
System.out.println("메서드내부 class - 메서드 지역변수(명시적_final): " + b);
}
}
Local local = new Local();
local.show();
}
// 4. 익명 내부
public void method2() {
int a = 2; // 묵시적 final
final int b = 3; // 명시적 final
// 인터페이스를 구현하는 익명 클래스
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("익명 클래스 실행------");
System.out.println("메서드내부 익명 - 외부 필드: " + message);
System.out.println("메서드내부 익명 - 외부 스태틱필드: " + message2);
System.out.println("메서드내부 익명 - 메서드 지역변수(묵시적_final): " + a);
System.out.println("메서드내부 익명 - 메서드 지역변수(명시적_final): " + b);
}
/*
* @FunctionalInterface
public interface Runnable {
void run(); // 추상 메서드 딱 하나
}
*/
};
runnable.run();
// Java 8+ 람다 표현식으로 실행 방법
Runnable lambdaRun = () -> {
System.out.println("람다 실행");
};
lambdaRun.run();
}
public static void main(String[] args) {
OuterCls outer = new OuterCls();
// 1. 멤버 내부 클래스 사용
OuterCls.Inner cls1 = outer.new Inner();
cls1.show();
// 2. 정적 내부 클래스 사용
OuterCls.InnerStatic cls2 = new OuterCls.InnerStatic();
cls2.show();
// 3. 지역 내부 클래스 사용
outer.method1();
// 4. 익명 내부 클래스 사용
outer.method2();
}
}
# 실행 결과
이너 Class - 외부 필드: 필드(1)
스태틱 Class - 외부 스태틱필드: 스태틱필드(2)
메서드내부 class - 외부 필드: 필드(1)
메서드내부 class - 외부 스태틱필드: 스태틱필드(2)
메서드내부 class - 메서드 지역변수(묵시적_final): 1
메서드내부 class - 메서드 지역변수(명시적_final): 2
익명 클래스 실행------
메서드내부 익명 - 외부 필드: 필드(1)
메서드내부 익명 - 외부 스태틱필드: 스태틱필드(2)
메서드내부 익명 - 메서드 지역변수(묵시적_final): 2
메서드내부 익명 - 메서드 지역변수(명시적_final): 3
람다 실행
4. 오브젝트 메서드
toString(): 객체를 사람이 읽기 좋은 문자열로 반환- 보통 오버라이딩하여 주요 필드 값을 문자열로 표현
equals(): 두 객체가 내용적으로 같은지 비교- 보통 오버라이딩하여 필드 값 비교로 변경
hashCode(): 객체의 해시코드(정수값) 반환equals()를 오버라이딩하면 반드시 함께 오버라이딩해야 함- 같은 객체라면
equals() == true→hashCode()도 같아야 함
clone(): 객체를 복제하여 새로운 객체 생성- 얕은 복제(shallow copy) 기본 제공
- 깊은 복제(deep copy)는 별도 구현 필요
import java.util.Arrays;
import java.util.Objects;
class Person implements Cloneable {
private String name;
private int age;
private int[] scores;
public Person(String name, int age, int[] scores) {
this.name = name;
this.age = age;
this.scores = scores;
}
public int[] getScores() {
return scores;
}
// 1. toString() : 객체를 문자열로 표현
@Override
public String toString() {
// Objects.toStringHelper 같은 건 없음 → 직접 포맷 작성
return "Person{name='" + name + "', age=" + age +
", scores=" + Arrays.toString(scores) + "}";
}
// 2. equals() : 내용 비교 (Objects.equals 사용으로 NPE 방지)
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age &&
Objects.equals(name, person.name) &&
Arrays.equals(scores, person.scores);
}
// 3. hashCode() : Objects.hash 사용
@Override
public int hashCode() {
// return Objects.hash(name, age); // 배열없을때
int result = Objects.hash(name, age);
result = 31 * result + Arrays.hashCode(scores);
return result;
}
// 4. clone() : 얕은 복제
// throws : 예외 발생 키워드
// CloneNotSupportedException : clone() 메서드를 사용할 때 발생할 수 있는 체크 예외
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 깊은 복사
protected Person deepCopy() {
// scores 배열도 새로 복사
return new Person(this.name, this.age, this.scores.clone());
}
}
public class ObjectMethodTest {
public static void main(String[] args) throws CloneNotSupportedException {
int[] arr1 = {1, 2, 3};
int[] arr2 = {4, 5, 6};
Person p1 = new Person("홍길동", 10, arr1);
Person p2 = new Person("홍길동", 10, arr1);
Person p3 = new Person("이순신", 50, arr2);
// toString()
System.out.println(p1.toString()); // Person{name='홍길동', age=10, scores=[10, 2, 3]}
System.out.println(p1); // toString() 자동 호출
// equals()
System.out.println(p1.equals(p2)); // true (내용 같음)
System.out.println(p1.equals(p3)); // false (내용 다름)
// hashCode()
System.out.println(p1.hashCode()); // p1과 p2는 같은 해시코드
System.out.println(p2.hashCode());
System.out.println(p3.hashCode()); // p3은 다른 해시코드
// clone() - 얕은복사
Person p4 = (Person) p1.clone(); // p4 <= p1 클론
System.out.println(p4); // Person{name='홍길동', age=10, scores=[10, 2, 3]}
System.out.println(p1 == p4); // false (객체는 다름)
System.out.println(p1.equals(p4)); // true (내용 같음)
// 깊은복사
Person p5 = p1.deepCopy(); // p5 <= p1 깊은 복사
arr1[0] = 10;
System.out.println("원본 변경 후 비교");
System.out.println("p1 : " + p1); // p1 : Person{name='홍길동', age=10, scores=[10, 2, 3]}
System.out.println("p4 : " + p4); // p4 : Person{name='홍길동', age=10, scores=[10, 2, 3]}
System.out.println("p5 : " + p5); // p5 : Person{name='홍길동', age=10, scores=[1, 2, 3]}
}
}
5. String 클래스
String은 불변(Immutable) 객체. 한 번 생성된 문자열은 변경 불가함
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println(str1 == str2); // true (String Pool)
System.out.println(str1 == str3); // false (다른 객체)
System.out.println(str1.equals(str3)); // true (내용 비교)
// 문자열 수정 시 새 객체 생성
String str4 = str1.concat(" World");
System.out.println(str1); // "Hello" (변경되지 않음)
System.out.println(str4); // "Hello World"
5.1 주요 메서드
.length(): 문자열의 길이 반환 (공백 포함).charAt(n): 인덱스 n 위치의 문자 반환 (0부터 시작).substring(n, m): 인덱스 n부터 (m-1)까지의 부분 문자열 반환.indexOf("문자"): 특정 문자열의 첫 위치 인덱스 반환, 없으면 -1.contains("문자"): 특정 문자열 포함 여부 확인 (true/false).startsWith("문자"): 지정한 문자열로 시작하는지 확인.endsWith("문자"): 지정한 문자열로 끝나는지 확인.toLowerCase(): 문자열을 모두 소문자로 변환.toUpperCase(): 문자열을 모두 대문자로 변환.replace(a, b): 문자열 내 a를 b로 치환.trim(): 문자열 앞뒤 공백 제거 (중간 공백은 그대로 둠).split(","): 지정한 구분자,를 기준으로 문자열을 배열로 분리.join("구분자", 배열): 배열 요소들을 구분자로 연결해 문자열 생성.format("포맷", 값...): 지정한 형식으로 문자열 생성 (printf와 유사String.valueOf(값): 기본형(int, double, boolean 등)이나 객체를 문자열로 변환
String str = " Hello Java ";
System.out.println(str.length()); // 11
System.out.println(str.charAt(2)); // e -> 인덱스 0=' ', 1='H', 2='e'
System.out.println(str.substring(1, 6)); // Hello -> 인덱스 1~5 까지
System.out.println(str.indexOf("Java")); // 7
System.out.println(str.contains("Java")); // true
System.out.println(str.startsWith(" He")); // true
System.out.println(str.endsWith("va ")); // true
System.out.println(str.toLowerCase()); // " hello java "
System.out.println(str.toUpperCase()); // " HELLO JAVA "
System.out.println(str.replace("Java", "World")); // " Hello World "
System.out.println(str.trim()); // "Hello Java"
String colors = "red,green,blue";
String[] arr = colors.split(",");
System.out.println(Arrays.toString(arr)); // [red, green, blue]
System.out.println(String.join("-", arr)); // red-green-blue
System.out.println(String.format("이름: %s, 나이: %d", "홍길동", 20));
// 이름: 홍길동, 나이: 20
int a = 123;
double b = 3.14;
boolean isTrue = true;
System.out.println(String.valueOf(a)); // "123"
System.out.println(String.valueOf(b)); // "3.14"
System.out.println(String.valueOf(isTrue)); // "true"
6. StringBuilder와 StringBuffer
String과 달리 변경 가능한(Mutable) 문자열을 다룬다.
6.1 설명
- StringBuilder : 동기화 지원 ❌ → 멀티스레드 환경에서는 안전하지 않음
- 한 번에 하나의 작업만 처리 → 앞 작업이 끝나야 다음 작업 실행
- 단일 스레드 환경에서 사용 시 가장 빠른 성능 제공
- StringBuffer : 동기화 지원 ⭕ → 멀티스레드 환경에서도 안전
- 여러 작업을 동시에 처리 → 효율적이지만, 스레드끼리 충돌하지 않도록 동기화 필요
- 동기화를 위해 락(lock)을 걸기 때문에 오버헤드 발생 → 단일 스레드에서는 Builder보다 느림
| 클래스 | 불변성 | 동기화 지원 | 속도 | 사용 환경 |
|---|---|---|---|---|
| String | 불변 | - | 느림 | 문자열 변경이 거의 없는 경우 |
| StringBuilder | 가변 | ❌ | 빠름 | 단일 스레드 환경 |
| StringBuffer | 가변 | ⭕ | 보통 | 멀티 스레드 환경 |
비유를 하자면?
- String : 빨래할 때마다 세탁기를 새로 구입해야 함 (불변).
- StringBuilder : 세탁기 하나에 여러 사람이 동시에 빨래를 넣음 → 빨래가 엉킴 (빠르지만 충돌).
- StringBuffer : 세탁기에 한 사람씩 순서대로 빨래 → 안전하지만 대기 시간 발생.
어떨때 사용하기 좋을까?
- String: 문자열 변경이 거의 없는 경우
- StringBuilder: 단일 스레드에서 문자열 변경이 빈번한 경우
- StringBuffer: 멀티 스레드에서 문자열 변경이 필요한 경우
6.2 시간 지연 확인하기
public class StrBuildAndBuffer {
public static void main(String[] args) {
int loop = 50000;
long start, end;
// String (불변 → 매번 새 객체 생성 → 가장 느림)
start = System.currentTimeMillis(); // 시작시간
String str = "";
// loop 만큼 돌려서 시간지연 발생시키기
for (int i = 0; i < loop; i++) {
str += "a";
}
end = System.currentTimeMillis(); // 종료시간
System.out.println("String 걸린 시간: " + (end - start) + "ms");
// StringBuilder (가변, 동기화 ❌ → 빠름)
start = System.currentTimeMillis(); // 시작시간
StringBuilder sb = new StringBuilder();
// loop 만큼 돌려서 시간지연 발생시키기
for (int i = 0; i < loop; i++) {
sb.append("a");
}
end = System.currentTimeMillis(); // 종료시간
System.out.println("StringBuilder 걸린 시간: " + (end - start) + "ms");
// StringBuffer (가변, 동기화 ⭕ → Builder보다 조금 느림)
start = System.currentTimeMillis(); // 시작시간
StringBuffer sbf = new StringBuffer();
// loop 만큼 돌려서 시간지연 발생시키기
for (int i = 0; i < loop; i++) {
sbf.append("a");
}
end = System.currentTimeMillis(); // 종료시간
System.out.println("StringBuffer 걸린 시간: " + (end - start) + "ms");
}
}
# 실행 결과
String 걸린 시간: 256ms
StringBuilder 걸린 시간: 2ms
StringBuffer 걸린 시간: 2ms
6.3 관련 메서드 사용
.append(글자): 문자열 끝에 새 문자열을 덧붙임.insert(n): 지정한 인덱스 n 위치에 문자열 삽입.delete(n, m): 지정한 범위 n ~ (m-1)의 문자열 삭제.reverse(): 문자열을 뒤집어서 반환
public class BuilderBufferOps {
public static void main(String[] args) {
// ---------- StringBuilder ----------
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println("SB append : " + sb); // Hello World
sb.insert(5, ",");
System.out.println("SB insert : " + sb); // Hello, World
sb.delete(5, 7); // [start, end) end는 미포함 → 인덱스 5~6 삭제
System.out.println("SB delete : " + sb); // HelloWorld
sb.reverse();
System.out.println("SB reverse : " + sb); // dlroWolleH
// 메서드 체이닝 예시
StringBuilder sb2 = new StringBuilder("ab");
sb2.append("cd").insert(2, "-").reverse();
System.out.println("SB chaining : " + sb2); // dc-ba
// ---------- StringBuffer ----------
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World");
System.out.println("SBF append : " + sbf); // Hello World
sbf.insert(5, ",");
System.out.println("SBF insert : " + sbf); // Hello, World
sbf.delete(5, 7);
System.out.println("SBF delete : " + sbf); // HelloWorld
sbf.reverse();
System.out.println("SBF reverse : " + sbf); // dlroWolleH
}
}
7. Math 클래스
Math 클래스는 모든 메소드가 static이므로 객체 생성 없이 사용
public class Exam {
public static void main(String[] args) {
// 기본 연산
System.out.println("절대값: " + Math.abs(-10)); // 10
System.out.println("최대값: " + Math.max(10, 20)); // 20
System.out.println("최소값: " + Math.min(10, 20)); // 10
// 제곱과 제곱근
System.out.println("2의 3제곱: " + Math.pow(2, 3)); // 8.0
System.out.println("16의 제곱근: " + Math.sqrt(16)); // 4.0
System.out.println("27의 세제곱근: " + Math.cbrt(27)); // 3.0
// 반올림, 올림, 내림
double num = 3.7;
System.out.println("반올림: " + Math.round(num)); // 4
System.out.println("올림: " + Math.ceil(num)); // 4.0
System.out.println("내림: " + Math.floor(num)); // 3.0
// 삼각함수
double angle = Math.PI / 4; // 45도
System.out.printf("sin(45°): %.5f%n", Math.sin(angle)); // 0.70711
System.out.printf("cos(45°): %.5f%n", Math.cos(angle)); // 0.70711
System.out.printf("tan(45°): %.5f%n", Math.tan(angle)); // 1.00000
// 로그
System.out.println("자연로그: " + Math.log(Math.E)); // 1.0
System.out.println("상용로그: " + Math.log10(100)); // 2.0
// 난수 생성
System.out.printf("0~1 난수: %.5f%n", Math.random()); // 0.0 ~ 0.99...
// 1~100 사이의 정수 난수
int randomInt = (int)(Math.random() * 100) + 1;
System.out.println("1~100 난수: " + randomInt); // 1 ~ 100 사이
// 상수
System.out.printf("원주율: %.3f%n", Math.PI); // 3.141
System.out.printf("자연상수: %.3f%n", Math.E); // 2.718
}
}
etc.
메서드 자동완성 TIP
- IDE : 인텔리제이
- 단축키
alt+insert
- 단축키
- IDE : Vscode
- 마우스
우측클릭->소스액션 - 단축키설정
ctrl + K후ctrl + S->source action검색 -> 키바인딩alt+insert후 사용
- 마우스
'멋사 - 부트캠프 19기 : Java > Java' 카테고리의 다른 글
| (09.02) java 기초 - Collections 활용, Generic, Iterator, 날짜시간 API, 예외처리 (0) | 2025.09.02 |
|---|---|
| (08.28) java 기초 - System 클래스, Wrapper 클래스, 컬렉션 프레임워크 : List, Set, Map (4) | 2025.08.28 |
| (08.26) java 기초 - 객체형변환, 추상클래스, 인터페이스, 디자인패턴, 오브젝트 (4) | 2025.08.26 |
| (08.25) java 기초 - 상속, getter & setter, 오버라이딩 & 오버로딩, 다형성 (0) | 2025.08.25 |
| (08.22) java 기초 - 생성자, this, 메서드체이닝, 패키지, static (2) | 2025.08.25 |