| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- 웹시큐리티
- 멀티캠퍼스IT부트캠프티
- 백준
- 애자일
- 시간 복잡도
- 스레드
- 코딩
- zod
- Java
- 별찍기10
- tanstack query
- LG유플러스 유레카 3기 프론트엔드
- 정렬
- git branch 협업
- 프론트엔드
- LG유플러스 유레카 프론트엔드 개발자
- LG유플러스 유레카 프론트엔드
- 프로세스
- 재귀
- 2775번 문제
- 부트캠프후기
- LG유플러스 유레카 부트캠프
- Do it! 자료구조와 함께 배우는 알고리즘 입문
- 유레카 부트캠프
- 소수
- 멀티캠퍼스IT부트캠프
- 프론트엔드 비대면반
- 자바
- 브루트포스
- 알고리즘
- Today
- Total
개발 일기
20250909 자바 및 과제 본문
오늘은 자바의 다형성의 오버라이딩과 오버로딩에 대해 더 배우고 프론트와 백엔드의 데이터 통신 실습을 진행했다.
오버라이딩 (Overriding)
부모 클래스의 메서드를 자식 클래스에서 재정의하는 것
실행 시점(런타임)에 실제 객체 타입에 맞는 메서드가 호출된다.
오버로딩 (Overloading)
한 클래스 내에서 메서드 이름은 같지만 매개변수 타입이나 개수가 다른 메서드를 여러 개 정의하는 것
호출 시점(컴파일 타임)에 어떤 메서드가 실행될 지 결정된다.
오버라이딩을 이해하기 위한 코드
package com.ureca;
public class OverrideTest {
public static void main(String[] args) {
System.out.println("===Object 클래스 비교===");
Object o1 = new Object();
Object o2 = new Object();
System.out.println(o1.toString()); // 주소값 스트링
System.out.println(o2.toString());
System.out.println(o1.hashCode()); // 주소값 int
System.out.println(o2.hashCode());
System.out.println(o1 == o2); // 주소값 비교
System.out.println(o1.equals(o2)); // 주소값 비교
System.out.println("===OverrideTest 클래스 비교===");
OverrideTest t1 = new OverrideTest();
OverrideTest t2 = new OverrideTest();
System.out.println(t1.toString()); // 주소값 스트링
System.out.println(t2.toString());
System.out.println(t1.hashCode()); // 주소값 int
System.out.println(t2.hashCode());
System.out.println(t1 == t2); // 주소값 비교
System.out.println(t1.equals(t2)); // 주소값 비교
System.out.println("===String 클래스 비교===");
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.toString()); // 내용값 스트링 override
System.out.println(s2.toString());
System.out.println(s1.hashCode()); // 내용값 int override
System.out.println(s2.hashCode());
System.out.println(s1 == s2); // 주소값 비교
System.out.println(s1.equals(s2)); // 내용값 비교 override
}
}
실행 결과

Object 클래스와 OverrideTest 같은 직접 만든 클래스에서는 toString(), hashCode(), equals() 메서드가 모두 Object 클래스의 기본 구현을 따른다.
반면, String 클래스는 Object를 상속하면서 이 메서드들을 오버라이딩했기 때문에 동작 방식이 다르다.
String 클래스 파일 코드
STS에서 JRE System Library 폴더 java.base안의 java.lang 패키지의 Object.class, String.class 파일을 보면 어떻게 오버라이딩 되어있는지 볼 수 있다.
toString()
// [JavaSE-17]
// Object.class파일 toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// String.class파일 toString()
public String toString() {
return this;
}
콘솔 창에 왜 저렇게 출력됐는지 알 수 있다. String 클래스는 자기 자신을 그대로 리턴한다.
hashCode()
// [JavaSE-17]
// Object class파일 hashCode()
@IntrinsicCandidate
public native int hashCode();
// String class파일 hashCode()
public int hashCode() {
// The hash or hashIsZero fields are subject to a benign data race,
// making it crucial to ensure that any observable result of the
// calculation in this method stays correct under any possible read of
// these fields. Necessary restrictions to allow this to be correct
// without explicit memory fences or similar concurrency primitives is
// that we can ever only write to one of these two fields for a given
// String instance, and that the computation is idempotent and derived
// from immutable state
int h = hash;
if (h == 0 && !hashIsZero) {
h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
if (h == 0) {
hashIsZero = true;
} else {
hash = h;
}
}
return h;
}
Object의 hashCode()는 지피티의 도움을 받아 해석을 했다.
native 키워드는 자바 코드가 아니라 JVM 내부에서 동작한다는 뜻이다. 그래서 소스코드에서 구현 내용을 볼 수 없고 JVM 벤더의 내부 구현에 들어 있다고 한다. 기본적으로 객체 참조(주소)를 바탕으로 한 해시값을 반환한다고 한다.
String의 hashCode()는 내용 기반 해시값을 계산한다.
내부적으로 hash와 hashIsZero(예외 처리 플래그)를 이용해서 성능을 높이고, 해시값이 0일 수도 있는 상황을 안전하게 처리한다.
결과적으로 String은 같은 문자열이면 항상 같은 문자열이면 항상 같은 해시코드가 나오고, 한 번 계산하면 재사용한다.
equals()
// [JavaSE-17]
// Object class 파일 equals()
public boolean equals(Object obj) {
return (this == obj);
}
// String class 파일 equals()
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
return (anObject instanceof String aString)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
&& StringLatin1.equals(value, aString.value);
}
Object의 경우는 == 을 사용해 비교하는 것이랑 다를게 없다는 것을 알 수 있다.
String의 경우 먼저 주소 값을 비교해 같으면 바로 true를 리턴하고 아닌 경우에도 추가적인 과정이 있다.
아래의 코드를 해석하면, 비교 대상이 String이 아니면 false, 인코딩 방식이 다르면 false 이고 인코딩이 같으면 내부 문자 배열을 비교한 뒤 같으면 true, 다르면 false를 반환한다.
String은 문자 값 자체가 같은 것을 체크할 수 있다는 것을 알 수 있다.
결론
오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 재정의하는 것을 의미한다.
만약 자식 클래스에서 메서드를 재정의하지 않았다면 부모 클래스의 메서드가 호출되고, 재정의한 경우에는 자식 클래스의 메서드가 호출된다.
이처럼 같은 메서드 호출이라도 객체의 실제 타입에 따라 다른 동작을 하게 되는데, 이것이 바로 자바에서 다형성이 구현되는 대표적인 방식이다.
오버로딩을 이해하기 위한 코드
package com.ureca.web.model.dto;
public class Coffee {
private String coffeeType;
private int quantity;
// 1. 기본 생성자
public Coffee() {
this.coffeeType = "아메리카노";
this.quantity = 1;
}
// 2. 커피 종류만 받는 생성자
public Coffee(String coffeeType) {
this.coffeeType = coffeeType;
this.quantity = 1;
}
// 3. 커피 종류와 수량을 받는 생성자
public Coffee(String coffeeType, int quantity) {
this.coffeeType = coffeeType;
this.quantity = quantity;
}
}
Coffee() : 아무 값도 전달하지 않고 기본값으로 객체 생성
Coffee("라떼") : 커피 종류만 지정해서 객체 생성
Coffee("카푸치노", 2) : 종류와 수량까지 지정해서 객체 생성
이처럼 같은 이름의 생성자라도 매개변수(개수, 타입)가 달라지면 오버로딩이 된다.
결론
오버로딩은 같은 이름의 메서드나 생성자를 매개변수의 개수, 타입, 순서에 따라 여러 형태로 정의하는 것을 의미한다.
호출하는 시점에서 전달되는 인자에 맞는 메서드가 선택되므로, 하나의 이름으로 다양한 형태의 동작을 제공할 수 있다.
이는 컴파일 시점에 어떤 메서드가 실행될지 결정되기 때문에 컴파일타임 다형성이라고 부른다.
Member.java에서 setter에 유효성 검사 코드 넣기
public class Member {
private String id, pw, name;
public Member() {}
public Member(String id, String pw, String name) {
super();
setId(id);
setPw(pw);
setName(name);
}
public String getId() {
return id;
}
public void setId(String id) {
// 유효성 검사
this.id = id;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
// 유효성 검사
this.pw = pw;
}
public String getName() {
return name;
}
public void setName(String name) {
// 유효성 검사
this.name = name;
}
@Override
public String toString() {
return "Member [id=" + id + ", pw=" + pw + ", name=" + name + "]";
}
}
위의 코드에 유효성 검사 코드를 작성해야 한다.
고려사항은 다음과 같다.
1. ID, PW, NAME 각각 어떤 유효성 검사를 할 지 정하는 것
2. 유효성 검사를 통과하지 못 했을 경우는 어떻게 처리할 것인지
일단 생각난 사항은 두 가지였다.
그래서 먼저 어떤 유효성 검사를 해야되는 지 지피티에 물어봤다.
유효성 검사로 ID의 경우 필수 입력 여부, 길이 제한, 사용 가능한 문자 제한, 중복 체크, 이메일 패턴 검증 등이 있다고 한다.
중복 체크는 MemberController에서 하기 때문에 굳이 하지 않고 이메일 패턴의 아이디를 받지 않고 일반 아이디를 받는다고 가정하고 필수 입력 여부, 길이 제한, 사용 가능한 문자 제한 정도를 구현하기로 했다.
PW의 경우 필수 입력 여부, 길이 제한, 문자 조합, 공백 사용 금지, 이전 비밀번호/유사 비밀번호 금지 등이 있다고 한다.
이전 비밀번호/유사 비밀번호 금지는 구현이 어려울 것 같아 제외하고 나머지를 구현하기로 했다.
name의 경우 간단하게 필수 입력 여부, 길이 제한, 특수문자 제한 정도로 구현하기로 했다.
유효성 검사를 통과하지 못하는 경우에 왜 회원가입에 실패했는지 정보를 사용자에게 알려주고 싶은데 그렇게 하기 위해서 어떤 식으로 처리해야될지 난감했다.
그래서 프론트단에 유효성 검사를 구현하고 알람을 띄우고 백엔드에서는 멤버컨트롤러에서는 간단하게 확인하는 것으로 했다.
코드 작성
사용자에게 회원가입 유효성 검사 결과를 자세히 알려주지 않기 때문에 유효성 검사를 정규식으로 최대한 한 번에 처리하도록 코드를 짰다.
ID 유효성 검사
// id 유효성 검사
public void setId(String id) {
// 유효성 검사
if (id != null && id.matches("^[a-zA-Z0-9]{4,20}$")) {
this.id = id;
} else {
this.id = null;
}
}
유효성 검사의 기준은 입력값이 없으면 안되고
문자열의 시작과 끝은 알파벳과 숫자로 이루어져 있고 4글자이상 20글자 이하인 지를 확인하는 것이다.
정규식의 ^는 문자열 시작을 의미하고 $는 문자열 끝을 의미한다.
[a-zA-Z0-9]는 소문자 a~z, 대문자 A~Z, 숫자 0 ~ 9를 의미한다.
{4, 20} 앞의 패턴이 4 ~ 20번 반복되는 것을 의미한다.
PW 유효성 검사
public void setPw(String pw) {
// 유효성 검사
if (pw != null && pw.matches("^[a-zA-Z0-9!@#$%^&*()_+-=]{8,20}$")) {
this.pw = pw;
} else {
this.pw = null;
}
}
ID와 비슷하지만 적힌 특수문자들이 들어갈 수 있고 8글자 이상 20글자 이하이다.
NAME 유효성 검사
public void setName(String name) {
// 유효성 검사
if (name != null && name.matches("^[a-zA-Z0-9가-힣]{1,10}$")) {
this.name = name;
} else {
this.name = null;
}
}
한글도 가능하도록 했다. 1글자 이상 10글자 이하.
// MemberController.java
// 유효성 검사 결과
if (m.getId() == null) {
return "id 조건 오류";
} else if (m.getPw() == null) {
return "pw 조건 오류";
} else if (m.getName() == null) {
return "닉네임 조건 오류";
}
멤버컨트롤러에 다음 코드를 추가했다.
실행 결과
회원가입 성공

ID 유효성 검사 통과 못 했을 때

PW 유효성 검사 통과 못 했을 때

Name 유효성 검사 통과 못 했을 때

MemberController.java에서 insertMember()에 id 중복 검사 코드 넣기
// id 중복 확인
for (int i = 0; i < members.length; i++) {
if (members[i] != null && m.getId().equals(members[i].getId())) {
return "중복된 id입니다.";
}
}
members[index] = m;
System.out.println("===전체 회원 목록===");
for (int i = 0; i < members.length; i++) {
System.out.println(members[i]);
}
return "회원가입 완료!";
실행 결과

CoffeeController.java에서 coffeeOrder()에 coffeeType으로 switch 넣고 서비스 컴포넌트 호출하기
// CoffeeController.java
public class CoffeeController {
// Service Component wiring
@Autowired
CoffeeService coffeeService;
@PostMapping("/coffeeOrder")
public String coffeeOrder(@RequestBody Coffee c) {
System.out.println(c);
// coffeeType으로 스위칭
switch(c.getCoffeeType()) {
case "NottedCoffee" :
c = new NottedCoffee(c.getCoffeeType());
break;
case "VividCoffee" :
c = new VividCoffee(c.getCoffeeType());
break;
case "DarkCoffee" :
c = new DarkCoffee(c.getCoffeeType());
break;
default:
System.out.println("잘못된 요청입니다.");
return "order fail";
}
// 주문 서비스 호출
coffeeService.order(c);
return "order ok";
}
}
수업 시간에 작성한 코드들을 보고 최대한 따라해보았다.
// CoffeeService.java
package com.ureca.web.model.service;
import org.springframework.stereotype.Service;
import com.ureca.web.model.dto.Coffee;
@Service
public class CoffeeService {
public void order(Coffee c) {
c.order();
}
}
자바의 오버라이딩을 이용한 다형성이다.
Service는 어떤 커피가 들어오든 c.order()로 처리할 수 있는 구조가 된다.
// Coffee.java
package com.ureca.web.model.dto;
public class Coffee {
private String coffeeType;
private int quantity;
public String getCoffeeType() {
return coffeeType;
}
public void setCoffeeType(String coffeeType) {
this.coffeeType = coffeeType;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
@Override
public String toString() {
return "Coffee [coffeeType=" + coffeeType + ", quantity=" + quantity + "]";
}
public void order() {
// empty
}
}
package com.ureca.web.model.dto;
public class DarkCoffee extends Coffee {
public DarkCoffee(String coffeeType) {
setCoffeeType(coffeeType);
}
@Override
public void order() {
System.out.println("다크 커피 주문");
}
}
각 커피의 종류에 맞게 출력된다. 다른 커피클래스도 코드는 동일.
실행 결과



느낀 점
아직 자바가 익숙하지 않아서 오래걸린 것 같다. 자바의 특성을 잘 살려보려고 했다가 골치가 아팠다.
그래도 오버라이딩과 오버로딩에 대해 조금은 이해가 됐다. 정규식도 다시 좀 공부해야겠다.
'TIL' 카테고리의 다른 글
| 250911 자바 (StringBuilder, 람다식, I/O, Thread) (0) | 2025.09.11 |
|---|---|
| 250910 자바 (0) | 2025.09.10 |
| 20250908 OOP 3대 concept (1) | 2025.09.08 |
| 20250907 자바스크립트 V8 엔진 (0) | 2025.09.07 |
| 20250904 TypeScript (0) | 2025.09.04 |