CODING/스파르타 내일배움캠프 TIL

19_하샤드 수_개인 과제 진행 Lv3 도전_25.1.8(수)

codingTrip 2025. 1. 8. 19:57

코트카타

21) 하샤드 수

나의 풀이

import java.util.*;
import static java.lang.Integer.parseInt;

class Solution {
    public boolean solution(int x) {
        boolean answer = true;
        String strX = ""+x;
        int[] arrX = new int[strX.length()];
        int sum=0;
        for(int i=0;i<strX.length();i++){
            arrX[i]=Integer.parseInt(String.valueOf(strX.charAt(i)));;
        }
        for(int i=0;i<arrX.length;i++){
            sum += arrX[i];
        }
        answer = (x%sum)==0?true:false;
        
        return answer;
    }
}

x를 String으로 만들어서

그 String의 크기만큼 새로운 int 배열을 만들었다.

각각의 인덱스에 자릿수 값을 할당한다.

그리고 각 자릿수를 더해서

x를 sum으로 나눈 나머지가 0이면 true아니면 false를 반환한다.

 

다른 사람의 풀이

class Solution {
    public boolean solution(int x) {
        int sum = String.valueOf(x).chars().map(ch -> ch - '0').sum();
        return x % sum == 0;
    }
}

출처:https://school.programmers.co.kr/learn/courses/30/lessons/12947/solution_groups?language=java

스트림에 대해 익숙해지고자 이 풀이를 더 살펴보기로 했다.

 

String.valueOf(x)

 : 숫자 x를 문자열로 변환
예) x = 123 → "123"

 

.chars()

 : 문자열의 각 문자를 Unicode 값(ASCII 값)으로 스트림으로 변환
예) "123".chars() → [49, 50, 51] (각 숫자 문자 '1', '2', '3'의 Unicode 값)

 

.map(ch -> ch - '0')

 : 스트림의 각 Unicode 값에서 문자 '0'의 Unicode 값을 뺌
이를 통해 문자 '9'를 정수 9로 변환가능
예: '1' - '0' → 1, '2' - '0' → 2, '3' - '0' → 3

 

.sum()

: 변환된 숫자 스트림의 합을 구함
예: [1, 2, 3].sum() → 6

 

 


팀원분의 특강 :)

팀원분께서 우리 팀에서 레벨 3 과제에 대해 매우 어려워하는 것을 보시고

특강이라는 큰 도움을 주셨습니다. :)

김영한님 자바 강의를 참고하셨다고 합니다.

저의 경우에는 구글링을 통해 일부 과제를 수행했지만

정확한 개념 정리가 잘 되지 않던 터라 정말 반가웠습니다.

 

제네릭

먼저 왜 써야 하는지? 생각해봐야 합니다.

아래의 예시를 통해 살펴보겠습니다.

public class IntegerBox {
    private Integer value;
    
    public Integer get() {
   		return value;
    }
    
    public void set(Integer value) {
    	this.value = value;
    }
}

Tip) cmd+shift+Enter : 자동완성

public class StringBox {
    private String value;
    
    public String get() {
    	return value;
    }
    
    public void set(String object) {
    	this.value = object;
    }
}

이렇게 각각 정수와 문자열을 보관하고 가져올 수 있는 클래스를 만들었습니다.

 

public class Main {
    public static void main(String[] args) {
        IntegerBox integerBox = new IntegerBox();
        integerBox.set(10); //오토 박싱
        Integer integer = integerBox.get();
        System.out.println("integer = " + integer);
        
        StringBox stringBox = new StringBox();
        stringBox.set("hello");
        String str = stringBox.get();
        System.out.println("str = " + str);
    }
}

정수 타입의 값을 IntegerBox에

문자열 타입의 값을 StringBox에 각각 넣어서 출력해보았습니다.

 

문제점

`Double` , `Boolean`등 타입만 바뀌었을 뿐 메서드 등이 동일한 이 클래스를 재사용하는 방안은 없을까요?

`DoubleBox` , `BooleanBox` 같이 클래스를 계속 새로 만들어야 할까요?

 

1차 해결

상속, 다형성 개념 이해가 필요합니다.

모든 타입을 다 받을 수 있는 Object 타입으로 Box를 만들어 줍니다.

public class ObjectBox {
    private Object value;
    
    public Object get() {
    return value;
    }
    
    public void set(Object object) {
    this.value = object;
    }
}

 

public class Main {
    public static void main(String[] args) {
        ObjectBox integerBox = new ObjectBox();
        integerBox.set(10);
        Object obj = integerBox.get(); // 에러
        Integer integer = (Integer) integerBox.get(); //Object -> Integer 캐스팅
        System.out.println("integer = " + integer);
        
        ObjectBox stringBox = new ObjectBox();
        stringBox.set("hello");
        String str = (String) stringBox.get(); //Object -> String 캐스팅
        System.out.println("str = " + str);
        
        //잘못된 타입의 인수 전달시
        integerBox.set("문자100");
        Integer result = (Integer) integerBox.get(); // String -> Integer 캐스팅 예외
        System.out.println("result = " + result);
    }
}

이렇게 Object타입을 활용할 경우, 자식은 부모를 담을 수 없기 때문에

어떤 타입으로 사용할지 강제형변환을 꼭 해야 합니다.

 

잘못된 타입의 인수를 전달할 시, 컴파일상에서는 문제를 잡아내지 못합니다.

따라서 컴파일 에러가 아닌 실행  시 에러가 발생합니다.

컴파일상에서 에러를 잡을 수 있는 것이 더 좋기 때문에

Object타입을 사용하는 것은 바람직하지 않습니다.

=> 재사용성 good, 타입 안정성 bad

 

2차 해결

따라서 컴파일상에서 타입 불일치 오류를 잡아낼 수 있는 제네릭을 사용합니다.

보통 T를 많이 사용하는데, 여기에서는 이해하기 쉽게 Type이라고 임의로 사용하겠습니다.

public class GenericBox<Type> {
    private Type value;
    
    public Type get() {
    return value;
    }
    
    public void set(Type value) {
    this.value = value;
    }
}
public class Main {
    public static void main(String[] args) {
        GenericBox<Integer> integerBox = new GenericBox<Integer>(); //생성 시점에 T의 타입 결정
        integerBox.set(10);
        //integerBox.set("문자100"); // Integer 타입만 허용, 컴파일 오류
        Integer integer = integerBox.get(); // Integer 타입 반환 (캐스팅 X)
        System.out.println("integer = " + integer);
        
        GenericBox<String> stringBox = new GenericBox<String>();
        stringBox.set("hello"); // String 타입만 허용
        String str = stringBox.get(); // String 타입만 반환
        System.out.println("str = " + str);
    }
}

위와 같이 제네릭 클래스를 활용하면, <> 꺽쇠 안에 다양한 타입을 넣어서 다양하게 확장 가능합니다.

 

(이에 대한 예시를 우리 팀은 대체로 게임을 좋아해서 게임을 예시로 들어주셨다.)

 

먼저 아무 팀이나 나타낼 수 있는 Team 클래스를 만들었습니다.

public class Team {
    private Team teamName;
    
    public String getTeamName(){
    	return "익명팀"
    }
}

 

public class T1 extends Team {
    private String teamName;
    
    @Override
    public String getTeamName(){
    	return "T1"
    }
}
public class Hwe extends Team {
    private String teamName;
    
    @Override
    public String getTeamName(){
    	return "한화생명"
    }
}

T1과 Hwe는 각각 Team 클래스를 상속받아서 오버라이딩 합니다.

 

public class Fan{
    private Team team;
    
    public Fan(Team team){
    	this.team = team;
    }
    
    public void cheer(){
    	System.out.println("화이팅" + team.getTeamName();)
    }
}

팬 클래스도 만듭니다.

팀 객체를 받아서 각 팀을 응원할 수 있는 메서드도 만듭니다.

 

public class Main {
    public static void main(String[] args) {
        Team t1 = new T1(); //부모는 자식을 품을 수 있다. 다형성 Integer->Obeject같이 상위
      //T1 team = new Team(); // 에러 자식은 부모를 품을 수 없다.
        Fan t1Fan = new Fan(t1);
        t1Fan.cheer();
        // 설정한 메서드 말고도 다른 메서드 사용가능한 이유
        // 내부적으로 Object를 상속받기 때문
        
    }
}

 

만약 제네릭을 사용한다면?

public class Fan<Type>{
    private Type team;
    
    public Fan(Type team){
    	this.team = team;
    }
    
    public void cheer(){
    	System.out.println("화이팅" + team.getTeamName();)
    }
}

위와 같은 경우 모든 타입이 가능하니까 내부적으로는 Object로 만들어집니다.

Object타입에서는 team.getTeamName()를 사용할 수 없습니다.

따라서 Team과 그 자손만 가능하도록 범위를 제한해야 합니다.

그래야 공용 메서드를 사용 가능합니다.

 

<Type> -> Object, Integer, Double... 다 가능

<Type extends Team> -> 적어도 Team 밑에만 넘어올 수 있습니다.

public class Fan<Type extends Team>{
    private Type team;
    
    public Fan(Type team){
    	this.team = team;
    }
    
    public void cheer(){
    	System.out.println("화이팅" + team.getTeamName();)
    }
}
public class Main {
    public static void main(String[] args) {
        T1 t1 = new T1();
        Fan<T1> t1Fan = new Fan<>();
        t1Fan.cheer();
    }
}

이로써 제네릭을 사용하면 재사용성도 good, 타입 안정성도 good이 됩니다.

 

추가> 다형성 개념 정리

Team t1 = new T1();

t1은 T1, Team 객체도 생성됩니다.

따라서 무조건 선언한 부모인 Team 타입을 따라 갑니다.

그런데 T1에서 오버라이드한 것이 있다면 T1의 것을 사용합니다.

 

Enum

왜 사용해야 할까요?

아래의 예시를 통해 살펴보겠습니다.

public Calss AuthService{
    public String authCheck(String authGrade) {
        if (authGrade.equals("ADMIN")) {
        	return "관리자 화면";
        } else if (authGrade.equals("LOGIN")) {
            return "메인 화면";
        } else if (authGrade.equals("GUEST")){
            return "로그인 화면";
        } else {
     	   return null;
        }
    }
}

관리자면 관리자 화면을, 회원이면 메인화면을, 일반 방문자면 로그인 화면을 보여주도록 처리하는 클래스를 만듭니다.

public class Main {
    public static void main(String[] args) {
    	AuthService as = new AuthService();
        
        String s = as.authCheck("VIP");
        String s1 = as.authCheck("ADMIN");
        String s2 = as.authCheck("GUEST");
        String login = as.authCheck("login");
    }
}

이럴 경우에 일반 신입 개발자는 내부정보(ADMIN, GUEST, LOGIN 사용한다는 사실)를 모르므로

그저 String 타입 형식만 지키면 되는 것으로 생각하고 VIP, long을 설정합니다.

 

1차해결 - 상수

public Calss AuthGrade{
	public static final String ADMIN = "ADMIN";
	public static final String LOGIN = "LOGIN";
    public static final String GUEST = "GUEST";
}

위와 같은 문제를 해결하기 위해 상수라는 개념을 생각해보았습니다.

그러나 결국 상수로 지정해도, 신입 개발자는 내부정보(ADMIN, GUEST, LOGIN 사용한다는 사실)를 모르므로

그저 String 타입으로 입력하는 실수가 발생합니다.

 

public Calss AuthGrade{
	private String grade;
	
    public static final AuthGrade ADMIN = new AuthGrade("ADMIN");
	public static final AuthGrade LOGIN = new AuthGrade("LOGIN");
    public static final AuthGrade GUEST = new AuthGrade("GUEST");
    
    public AuthGrade(String grade){
    	this.grade = grade;
    }
}

String타입을 AuthGrade로 지정해주었습니다.

 


위와 같이 사용하면 미리 정한 3개의 상수가 아닌 다른 값을 입력했을 때, 컴파일 단계에서 에러가 발생합니다.(빨간줄)

이를 통해 신입 개발자는 문제점을 확인할 수 있습니다.

public enum AuthGrade {
	private final String grade;
	
    ADMIN("ADMIN"),LOGIN("LOGIN"),GUEST("GUEST");
    
    public AuthGrade(String grade){
    	this.grade = grade;
    }
}

 

마지막으로 아래와 같이 말씀해주셨습니다.

1. 왜 써야 하는지?

2. 이런 상황 발생 시 무엇을 쓸지 판단하기


과제 이해하기

Generic

public class ArithmeticCalculator<T extends Number>{}

int와 같은 정수나 double과 같은 실수 값을 받아오기 위해서는

String과 같은 숫자가 아닌 타입은 받아오지 못하도록 제한한다.

 

Enum

public enum OperatorType {
    PLUS('+'), MINUS('-'), MULTIPLY('*'),DIVIDE('/');

    //값을 저장할 필드(인스턴스 변수)를 추가
    private final char value;
    // 생성자를 추가
    private OperatorType(char value){this.value = value; }

    public char getValue(){return value;}

    // 매개변수가 null이 아니고 enum의 값이 매개변수와 같으면 enum의 값을 리턴하라
    public static OperatorType findByVal(char val) {
        OperatorType[] values = OperatorType.values();
        for (OperatorType v : values) {
            if (val==v.value) {
                    return v;
            }
        }
        return null;
    }
}

 

OperatorType.values()

 : 모든 enum 값을 반환

배열로 만들어서 확장된 for문을 사용하여 각각의 요소를 출력할 수 있음

매개변수 val의 값과 enum의 value의 값이 같으면 enum 상수를 반환한다.

 

추가로 알아두면 좋을 enum 메서드

OperatorType.valueOf()

String input = "+";
OperatorType plus = OperatorType.valueOf(input)

String -> ENUM 변환,

잘못된 문자인 경우, IllegalArgumentException 발생한다.

그래서 내가 과거에 해당 메서드를 사용했을 때 위와 같은 오류가 발생했다.

참고:https://codingtrip.tistory.com/83