🤖 진행 사항 🤖
- Java 문법 종합반
- 3주차
- Java 계산기 과제
- Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기
- Lv 2. 클래스를 적용해 기본적인 연산을 수행할 수 있는 계산기 만들기
- Java 문법 종합반 계산기 만들기 과제
Java 문법 종합반
1. 객체지향 프로그래밍 특징
- 캡슐화
- 속성(필드)와 행위(메서드)를 하나로 묶어 객체로 만든 후 실제 내부 구현은 외부에서 알 수 없게 감추는 것
- 외부 객체에서는 캡슐화된 객체의 내부 구조를 알 수 없음 ==> 보안
- 외부 객체에서 해당 필드와 메서드를 잘못 사용하여 객체가 변화하지 않게 함
- Java에서는 캡슐화된 객체의 필드와 메서드를 노출시킬지 감출지 결정하기 위해 접근제어자(pulbic/private 등)를 사용
- 상속
- 부모 객체와 자식 객체가 존재
- 부모 객체는 가지고 있는 필드와 메서드를 자식 객체에게 물려주어 자식 객체가 이를 사용할 수 있도록 만들 수 있음 (@Override)
- 상속을 하는 이유
- 각각의 객체들을 상속 관계로 묶음으로써 객체 간의 구조 파악이 쉬워짐
- 일관성 유지에 좋음
- 코드의 중복이 줄어들고 코드의 재사용성이 증
- 다형성
- 객체가 연산을 수행할 때 하나의 행위에 대해 각 객체가 가지고 있는 고유한 특성에 따라 다른 여러 가지 형태로 재구성되는 것
- ex) Car 클래스를 토대로 자동차 객체를 만들 때, A자동차 객체와 B자동차 객체의 경적 소리가 다르다면 '경적을 울리다'라는 행위, 즉 horn(); 메서드의 구현을 다르게 재정의하여 사용할 수 있다.
- 추상화
- 객체에서 공통된 부분들을 모아 상위 개념으로 새롭게 선언하는 것을 추상화라고 함
2. 클래스 설계
ex) Car라는 클래스를 생성해보기
- 클래스 선언
- 필드 정의
- 생성자 정의
- 메서드 정의
package week3;
public class Car {
// <필드 영역>
String company;
String model;
String color;
double price;
double speed;
char gear;
boolean lights;
// <생성자 영역>
// 생성자 : 처음 객체가 생성될 떄(instance화) 어떤 로직을 수행해야 하며, 어떤 값이 필수로 들어와야 하는지 정의
public Car () {
}
// <메서드 영역>
// gasPedal
// input : km/h
// output : speed
double gasPedal(double kmh) {
speed = kmh;
return speed;
}
// brakePedal
// input : x
// output : speed
double brakePedal() {
speed = 0;
return speed;
}
// changeGear
// input : gear(char type)
// output : gear
char changeGear(char type) {
gear = type;
return gear;
}
// onOffLights
// input : x
// output : lights(boolean)
boolean onOffLights() {
lights = !lights;
return lights;
}
// horn
// input : x
// output : x
void horn() {
System.out.println("빵빵");
}
}
3. 메서드 오버로딩(Overloading)
한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도, 매개변수의 개수 또는 타입, 순서가 다르면 동일한 이름을 사용해서 메서드를 정의할 수 있다.
- 매개변수의 차이로만 구현할 수 있음
📌 메서드 오버로딩 예시
public class PrintStream extends FilterOutputStream
implements Appendable, Closeable
{
...
public void println() {
newLine();
}
public void println(boolean x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(char x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(int x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(long x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(float x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(double x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(char[] x) {
if (getClass() == PrintStream.class) {
writeln(x);
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(String x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(Object x) {
String s = String.valueOf(x);
if (getClass() == PrintStream.class) {
// need to apply String.valueOf again since first invocation
// might return null
writeln(String.valueOf(s));
} else {
synchronized (this) {
print(s);
newLine();
}
}
}
...
}
4. 접근 제어자
- public : 접근 제한이 전혀 없음
- protected : 같은 패키지 내에서, 다른 패키지의 자손 클래스에서 접근 가능
- default : 같은 패키지 내에서만 접근 가능
- private : 같은 클래스 내에서만 접근 가능
사용 가능한 접근 제어자 | |
클래스 | public, default |
메서드 & 멤버 변수 | public, protected, default, private |
지역변수 | 없음 |
5. 상속
extends
public class 자식클래스 extends 부모클래스 {
}
- 부모 클래스에 새로운 필드와 메서드가 추가되면 자식 클래스는 이를 상속받아 사용할 수 있다.
- 자식 클래스에 새로운 필드와 메서드가 추가되어도 부모 클래스는 어떠한 영향도 받지 않는다.
- 따라서 자식 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많다.
- ex) Car 클래스, Car 클래스를 상속받는 SportsCar 클래스
📌 클래스 간의 관계를 분석하여 관계 설정을 할 수 있다.
상속관계 : is - a (”~은 ~(이)다”) (ex: 포유류 클래스 / 고래 클래스)
포함관계 : has - a (”~은 ~을(를) 가지고 있다”) (ex: 자동차 클래스 / 타이어 클래스, 핸들 클래스)
오버라이딩(@Override)
부모 클래스로부터 상속받은 메서드의 내용을 재정의
- 선언부가 부모 클래스의 메서드와 일치
- 접근 제어자를 부모 클래스의 메서드 보다 좁은 범위로 변경할 수 없음
- 예외는 부모 클래스의 메서드 보다 많이 선언할 수 없다.
super
부모 클래스의 멤버를 참조할 수 있는 키워드
- 객체 내부 생성자 및 메서드에서 부모 클래스의 멤버에 접근하기 위해 사용될 수 있음
- 자식 클래스 내부에서 선언한 멤버와 부모 클래스에서 상속받은 멤버와 이름이 같을 경우, 이를 구분하기 위해 사용
super()
부모 클래스의 생성자를 호출할 수 있는 키워드
- 객체 내부 생성자 및 메서드에서 해당 객체의 부모 클래스의 생성자를 호출하기 위해 사용
- 자식 클래스의 객체가 생성될 때 부모 클래스들이 모두 합쳐져서 하나의 인스턴스가 생성
- 이때 부모 클래스의 멤버들의 초기화 작업이 먼저 수행이 되어야 함
- -> 자식 클래스의 생성자에서는 부모 클래스의 생성자가 호출 됨
- -> 오버로딩된 부모 클래스의 생성자가 없다고 하더라도 부모 클래스의 기본 생성자를 호출해야 함
- -> 눈에 보이지는 않지만 컴파일러가 super();를 자식 클래스 생성자 첫 줄에 자동으로 추가해줌
6. 추상화
abstract 추상 클래스
public abstract class 추상클래스명 {
}
- 추상 클래스는 추상 메서드를 포함할 수 있다.
- (추상 메서드가 없어도 추상 클래스로 선언할 수 있습니다.)
- 추상 클래스는 자식 클래스에 상속되어 자식 클래스에 의해서만 완성될 수 있다.
- 추상 클래스는 여러 개의 자식 클래스들에서 공통적인 필드나 메서드를 추출해서 만들 수 있다.
abstract 추상 메서드
public abstract class 추상클래스명 {
abstract 리턴타입 메서드이름(매개변수, ...);
}
- 추상 메서드는 일반적인 메서드와는 다르게 블록{ }이 없다.
- 즉, 정의만 할 뿐, 실행 내용은 가지고 있지 않다.
extends 추상 클래스
public class 클래스명 extends 추상클래스명 {
@Override
public 리턴타입 메서드이름(매개변수, ...) {
// 실행문
}
}
- 상속받은 클래스에서 추상 클래스의 추상 메서드는 반드시 오버라이딩 되어야 한다.
7. 인터페이스
상속 관계가 없는 다른 클래스들이 서로 동일한 행위,
즉, 메서드를 구현해야 할 때 인터페이스는 구현 클래스들의 동일한 사용 방법과 행위를 보장
interface
public interface 인터페이스명 {
// 모든 멤버 변수는 public static final (생략 가능; 컴파일러가 자동으로 추가해줌)
// 모든 메서드는 public abstract (static 메서드와 default 메서드 외에 생략 가능)
}
implements
- 인터페이스는 추상 클래스와 마찬가지로 직접 인스턴스를 생성할 수 없기 떄문에 클래스에 구현되어 생성됨
- 인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야 함
public class 클래스명 implements 인터페이스명 {
// 추상 메서드 오버라이딩
@Override
public 리턴타입 메서드이름(매개변수, ...) {
// 실행문
}
}
extends
- 인터페이스 간의 상속은 implements가 아니라 extends를 사용
- 인터페이스는 클래스와 다르게 다중 상속이 가능 (interface C extends A, B { })
- 인터페이스 C는 A와 B를 상속받았기 때문에 a()메서드와 b()메서드를 포함하고있는 상태
- -> Main 클래스에서 인터페이스 C가 구현될 때 a()와 b()가 오버라이딩 된다.
public class Main implements C {
@Override
public void a() {
System.out.println("A");
}
@Override
public void b() {
System.out.println("B");
}
}
interface A {
void a();
}
interface B {
void b();
}
interface C extends A, B { }
extends & implements
- 인터페이스 구현은 상속과 함께 사용될 수 있다. (public class Main extends D implements C)
public class Main extends D implements C {
@Override
public void a() {
System.out.println("A");
}
@Override
public void b() {
System.out.println("B");
}
@Override
void d() {
super.d();
}
public static void main(String[] args) {
Main main = new Main();
main.a();
main.b();
main.d();
}
}
interface A {
void a();
}
interface B {
void b();
}
interface C extends A, B {
}
class D {
void d() {
System.out.println("D");
}
}
인터페이스의 default 메서드
- 추상 메서드의 기본적인 구현을 제공
- 추상 메서드가 아니기 때문에 인터페이스의 구현체들에서 필수로 재정의 할 필요 없음
interface A {
void a(); // <- 이 메서드는 구현될 때 반드시 오버라이딩 되어야 함
default void aa() { // <- 이 메서드는 오버라이딩 되지 않아도 사용 가능
System.out.println("AA");
}
}
인터페이스의 static 메서드
- 인터페이스에서 static 메서드 선언 가능
- static 특성대로 객체 없이 호출 가능
interface A {
void a();
default void aa() {
System.out.println("AA");
}
static void aaa() {
System.out.println("static method");
}
}
Java 계산기 과제
Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기
package com.example.calculator;
import java.util.Scanner;
/* Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기 */
/*
* 양의 정수(0 포함)를 입력 받기
* 사칙연산 기호(➕,➖,✖️,➗) 입력 받기
* 위에서 입력받은 양의 정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과값을 출력하기
* 반복문을 사용하되, 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지 무한으로 계산을 진행할 수 있도록 소스 코드를 수정하기
*/
public class Calculator {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String answer = "";
// 반복문을 사용
while (!answer.equals("exit")) {
// 양의 정수(0 포함)를 입력 받기
System.out.print("첫 번째 숫자를 입력하세요: ");
int num1 = sc.nextInt();
System.out.print("두 번째 숫자를 입력하세요: ");
int num2 = sc.nextInt();
// 사칙연산 기호(➕,➖,✖️,➗) 입력 받기
System.out.print("사칙연산 기호를 입력하세요: ");
char operator = sc.next().charAt(0);
int result; // 결과를 저장할 변수 (정수형)
//위에서 입력받은 양의 정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과값을 출력하기
switch (operator) {
case '➕':
case '+':
result = num1 + num2;
System.out.println("결과: " + result);
break;
case '➖':
case '-':
result = num1 - num2;
System.out.println("결과: " + result);
break;
//case '✖️': // 해당 이모지는 1bit가 넘어서 사용 불가..!
case '✖':
case '*':
result = num1 * num2;
System.out.println("결과: " + result);
break;
case '➗':
case '/':
if (num2 == 0) {
System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
break;
}
result = num1 / num2;
System.out.println("결과: " + result);
break;
default:
System.out.println("사칙연산 기호가 적절하지 않습니다.");
break;
}
// 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지 무한으로 계산을 진행
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
answer = sc.next();
System.out.println("-----------------------------------");
}
}
}
Lv 2. 클래스를 적용해 기본적인 연산을 수행할 수 있는 계산기 만들기
package com.example.calculator2;
import java.util.ArrayList;
import java.util.List;
/*
* 사칙연산을 수행 후, 결과값 반환 메서드 구현 & 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
* 1) 양의 정수 2개(0 포함)와 연산 기호를 매개변수로 받아 사칙연산(➕,➖,✖️,➗) 기능을 수행한 후
* 2) 결과 값을 반환하는 메서드와 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성합니다.
*
* App 클래스의 main 메서드에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정 (캡슐화)
*/
public class Calculator {
// 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
private List<Integer> result = new ArrayList<>();
// 사칙연산을 수행한 후, 결과값을 반환하는 메서드 구현
public int calculate(int num1, int num2, char op) {
return switch (op) {
case '➕', '+' -> num1 + num2;
case '➖', '-' -> num1 - num2;
case '✖', '*' -> num1 * num2;
case '➗', '/' -> num1 / num2;
default -> 0;
};
}
/* Getter 메서드 구현 */
public int getResult() {
return this.result.get(result.size() - 1); // 마지막 요소 리턴
}
/* Setter 메서드 구현 */
public void setResult(int r) {
this.result.add(r);
}
/* Calculator 클래스에 저장된 연산 결과들 중 가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를 구현한 후
App 클래스의 main 메서드에 삭제 메서드가 활용될 수 있도록 수정 */
public void removeResult() {
this.result.remove(0);
}
}
package com.example.calculator2;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
/* Calculator 인스턴스 생성 */
Calculator calculator = new Calculator();
String answer = "";
Scanner sc = new Scanner(System.in);
// 반복문을 사용
while (!answer.equals("exit")) {
// 양의 정수(0 포함)를 입력 받기
System.out.print("첫 번째 숫자를 입력하세요: ");
int num1 = sc.nextInt();
System.out.print("두 번째 숫자를 입력하세요: ");
int num2 = sc.nextInt();
// 사칙연산 기호(➕,➖,✖️,➗) 입력 받기
System.out.print("사칙연산 기호를 입력하세요: ");
char operator = sc.next().charAt(0);
if (operator == '+' || operator == '-' || operator == '*' || operator == '/'
|| operator == '➕' || operator == '➖' || operator == '✖' || operator == '➗') {
if ((operator == '/' || operator == '➗') && num2 == 0) {
System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
}
else {
calculator.setResult(calculator.calculate(num1, num2, operator));
System.out.println("결과: " + calculator.getResult());
calculator.removeResult();
}
}
else {
System.out.println("사칙연산 기호가 적절하지 않습니다.");
}
// 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지 무한으로 계산을 진행
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
answer = sc.next();
System.out.println("-----------------------------------");
}
}
}
구현 마지막 쯤에 필드 타입 조건을 알아차려서 뒤늦게 바꿨다 하핳
Lv 2에서 마지막 조건이 연산 결과들 중 가장 먼저 저장된 데이터를 삭제하는 것이었다.
나는 result를 ArrayList로 선언했기 때문에 calculate 메서드로 사칙연산을 수행한 결과를 .add(값)으로 list에 넣으면,
가장 먼저 저장된 값은 항상 인덱스 0이었다.
그래서 removeResult() 메서드를 인덱스 0의 값을 삭제하는 this.result.remove(0);으로 작성했는데
뭔가 과제에서 바라는 구현은 아닌거 같은 느낌이 든다,,, 내일 튜터님께 질문 해봐야겠음!
--> 이렇게 구현하는게 맞다고 하심 (25-01-03)
Java 문법 종합반 계산기 만들기 과제
Step4 구조만 먼저 짜고, Step 2까지 진행!
'TIL (Today I Learned)' 카테고리의 다른 글
[TIL] Java Enum과 Generic 활용하여 계산기 만들기 (25-01-06) (0) | 2025.01.06 |
---|---|
[TIL] Java 계산기 만들기, 계산기 예외처리 과제 / Java Error와 Exception, Generic (25-01-03) (1) | 2025.01.03 |
[TIL] 자료구조(List / Set / Map)를 활용한 입/출력 (25-01-01) (0) | 2025.01.01 |
[TIL] JVM 개념 및 JDK 설치, Git 심화 특강 (24-12-31) (0) | 2024.12.31 |
[TIL] Java 알고리즘 문제 풀이 (24-12-30) (0) | 2024.12.30 |