코딩공부/자바

java 문법 종합반 3주차

정해인3 2023. 5. 24. 20:56

자바: 객체지향 언어

객체란 세상에 존재하는 물체를 뜻한다. 물리적, 개념적인 것 모두

객체에는 속성과 행위가 있음

속성: 필드

행위: 메서드

 

객체 모델링: 현실 세계의 객체를 소프트 웨어의 객체로 설계

 

객체 간의 협력 

 

소프트웨어 객체간의 관계

사용관계

포함관계

상속 관계

 

객체지향 프로그래밍의 특징

캡슐화, 상속, 다형성, 추상화

 

 

캡슐화

속성과 행위를 하나로 묶어 객체로 만든 후 실제 내부 구현 내용은 외부에서 알 수 없게 감추는 것을 의미

보안에 중요, 혼란을 줄임, 객체가 변화하지 않게 함 

캡슐화 된 객체의 필드와 메서드를 노출 시킬지 감출지 결정하기 위해 접근제어자 사용

 

상속

부모와 자식 객체 존재

부모 객체는 가지고 있는 필드와 메서드를 자식 객체에게 물려주어(오버라이딩)  자식 객체가 이를 사용할 수 있도록 함

일관성 유지에 좋음

코드 중복 줄어듦

메서드의 구현을 자식클래스에서 다르게 재정의 하여 사용할 수 있음

 

추상화

객체에서 공통된 부분을 모아 상위 개념으로 새롭게 선언

공통적이고 중요한 것들을 모아 객체를 모델링 

 

객체를 생성하기 위해서는 설계도가 필요함

소프트웨어에서 객체를 만들기 위해서는 설계도에 해당하는 클래스가 필요

 

객체 == 인스턴스

클래스를 토대로 객체를 만들어내는 과정은 인스턴스화

 

클래스: 객체를 만들기 위한 설계도 

 

클래스를 만들기 위한 4가지 스텝

만드려는 설계도(클래스)를 선언

객체가 가지고 있어야할 속성(필드)을 정의

객체가 생성하는 방식(생성자)을 정의

객체가 가지고 있어야할 행위(메서드) 정의 

 

메서드: 변수들을 제어할 수 있게 함

생성자(constructor): 변수들을 어떻게 생성할 것인가, 클래스의 이름과 동일, 리턴 타입 쓰지 않음, 기본 생성자는 생략 가능 

 

객체를 만들 때 new키워드가 사용됨, 그때 생성자가 사용됨 (생성자를 호출하기 위해 new 키워드 사용) 

클래스로 만든 객체도 변수형태로 저장되기 때문에 배열로서 관리될 수 있음 -> 확장성이 높음

 

필드

실습에서 세 종류로 나눔

고유 데이터

상태 데이터

객체 데이터

초기화를 하지 않을 경우 정해진 default value가 들어감 (초기값을 지정하면 초기값이 들어감)

필드를 사용한다: 설계도에 있는 필드를 불러오거나 변경하는 등, 인스턴스화 시켜야 사용할 수 있음 

외부접근

외부에서 내부의 필드에 접근하여 사용 , 도트(.)연산자를 사용함

내부접근

클래스 파일 안에 있는 변수에 접근할 때 -> 그냥 접근

초기화 된 참조형 변수는 출력했을 때 주소값이 나온다. 

 

메서드

행위

객체간의 협력에도 사용

객체 간의 협력을 통해 유기적인 프로그램이 돌아감 

 

리턴타입

메서드명 앞에 있음 

void를 제외하고는 반환값이 있어야 함 

void는 기본적으로 리턴값이 없지만 return문을 사용하여 원하는 지점에서 메서드를 종료하려 할 때 사용할 수 있다.

매개변수: 메서드를 호출할 때 메서드로 전달하려는 값을 받기 위해 사용되는 변수, 순서와 타입을 맞춰줘야 함, 전달하려는 값이 없으면 생략가능, 가변길이의 매개변수도 선언할 수 있음(안의 로직 구현 필요, for문)

가변길이의 매개변수 예시

void carSpeed(double ... speeds){
        for(double v : speeds){
            System.out.println(v);
        }
    }

메서드 호출: 메서드명(매개변수) 

인스턴스를 생성해야 사용할 수 있음: 인스턴스명.메서드명(매개변수);

 내부 접근 가능

메서드의 리턴타입과 그걸 받는 변수의 타입이 동일하거나 자동 타입변환될수 있어야 한다.

 

ctrl + 메서드 클릭 -> 원본 메서드로 이동 

 

메서드 오버로딩 

하나의 메서드 이름으로 여러 기능을 구현하도록 하는 자바의 기능 

같은 이름이라도 매개변수를 다르게 정의하면서 다른 기능을 정의 

메서드 이름은 같고, 매개변수의 개수 또는 타입 또는 순서가 달라야 한다. (동일한 매개변수에 순서만 다른 것은 동일한 메서드로 인식)

 

기본형과 참조형 매개변수

기본형

인자로 넣어주는 값 자체가 복사, 원본값은 변경이 안됨(읽기 가능)

 

참조형

주소를 전달, 원본 값 변경이 가능 (읽기+변경 가능)

 

인스턴스 멤버와 클래스 멤버

멤버: 필드 + 메서드

선언하는 방법에 따라서 인스턴스 멤버와 클래스 멤버로 구분할 수 있음

인스턴스 멤버: 객체 생성 후에 사용할 수 있고, 클래스 멤버는 객체 생성 없이도 사용할 수 있다(Main 메서드).

 

객체의 인스턴스 필드는 각각의 인스턴스 마다 고유하게 값을 가질 수 있음 

-> 객체가 인스턴스화 할 때마다 객체의 메서드들은 인스턴스에 포함되어 매번 생성이 되나? -> 매번 저장한다면 중복 저장으로 인해 메모리 효율이 매우 떨어지기 때문에 메서드는 메서드 영역에 두고 모든 인스턴스들이 공유해서 사용 / 대신 무조건 객체를 생성 즉, 인스턴스를 통해서만 메서드가 사용될 수 있도록 제한을 걸어둠(생성 / 사용 분리)

(3-6복습할 것)

클래스 멤버

자바의 클래스 로더에 의해 메서드 영역에 저장되고 사용됨

메서드 영역의 클래스와 같은 위치에 고정적으로 위치하고 있는 멤버 (객체 생성 필요 없이 바로 사용 가능

선언: static 키워드 사용

static 키워드를 사용해서 참조형 변수 선언: 클래스 로더에 위치, 바로 사용 가능

특정 필드의 값이 모든 객체마다 동일 -> 클래스 필드로 만들어 공유 -> 메모리를 효율적으로 사용 

인스턴스 메서드도 클래스 멤버를 사용할 수 있음 

참조형 변수를 사용하여 클래스 멤버에 접근은 가능하지만 비추 -> 클래스 이름으로 접근

객체를 생성해서 클래스 멤버에 접근 <- 비추

 

지역변수 

해당 메서드가 실행될 때마다 독립적인 값을 저장하고 관리한다.

지역변수는 메서드 내부에서 정의될 때 생성된다.

메서드가 종료될 때 소멸한다.

 

전역변수

(static과 관련있음)

 

메인 클래스 내부에서 메인 인스턴스 생성 가능 -> 기본 생성자가 내장되어 있기 때문

public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        
    }

 

상수(final 필드)

final 키워드를 필드 앞에 붙임

프로그램이 실행하는 동안에는 수정할 수 없음

static(공용)이랑 사용하면 -> 공용의 상수

값이 반드시 한 개, 불변 

 

생성자(constructor)

객체가 생성될 때 호출되며 객체를 초기화하는 역할을 수행 

기본 생성자: 괄호 안에 아무 것도 없음

모든 클래스는 반드시 생성자가 하나 이상 존재

생성자를 하나도 선언하지 않았다면 컴파일러는 기본 생성자를 자동으로 추가 / 단 하나라도 생성자가 선언되어 있다면 컴파일러는 기본생성자를 추가하지 않음

기본 생성자는 해당 클래스의 접근 제어자를 따름

인스턴스마다 다른값을 가져야 하는 필드는 생성자를 통해 초기화 할 수 있

인스턴스마다 동일한 데이터를 가지는 필드는 초기값을 대입하는 것이 좋음 

 

this 

객체 즉, 인스턴스 자신을 표현하는 키워드 (super는 부모를 가리킴)

매개변수명과 객체의 필드명이 동일할 경우 생성자 블록 내부에서 해당 변수들은 객체의 필드가 아닌 가장 가까운 매개변수명을 가리키게 됨

되도록 사용하는 것이 좋다(convention:약속)

객체 자신을 리턴해야 하는 메서드의 경우 return this

this() : 생성자를 지칭 

코드 중복을 제거할 수 있음 

예시

public Car(String model) {
    this(model, "Blue", 50000000);
}

public Car(String model, String color) {
    this(model, color, 100000000);
}

public Car(String model, String color, double price) {
    this.model = model;
    this.color = color;
    this.price = price;
}

- 생성자를 블록 안에서 호출할 때: 다른 로직이 위에 있으면 안됨

 

패키지

같은 패키지 내에 있을 때 -> 객체 생성해서 바로 사용

다른 패키지 내에 있을 때 -> 패키지 경로를 적음 / 다른 패키지에 있는 것을 내부 소스로 가져오는 방법(import)

1번 예시

public static void main(String[] args) {
        Week03.packageExample.pk1.Car car1 = new Week03.packageExample.pk1.Car();
        Week03.packageExample.pk2.Car car2 = new Week03.packageExample.pk2.Car();

        car1.horn();
        car2.horn();
    }

클래스의 일부분이며, 하위 패키지를 도트(.)로 구분함 

 

import 방식

import Week03.packageExample.pk1.Car;

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.horn();
    }

 

상속

클래스 간의 관계와 상속

부모가 자식에게 물려주는 행위(메서드, 필드를 물려줌)

적은 양의 코드, 공통적인 코드 관리

중복제거, 재사용성 -> 유지보수 용이

extends 키워드를 사용해서 정의할 수 있음

상속을 확장의 개념으로 이해 할 것

자식클래스가 부모클래스보다 큰 범위 -> 부모클래스에 새로운 필드와 메서드가 추가되면 자식 클래스는 이를 상속받아 사용할 수 있지만 자식 클래스에 새로운 필드와 메서드가 추가되어도 부모 클래스는 영향받지 않음 -> 자식 클래스의 멤버 개수는 부모보다 항상 같거나 많다.

자식 클래스로 만들어진 인스턴스: 부모가 가진 특징을 모두 가짐 

 

ctrl + F : 코드 내용 검색

 

클래스 간의 관계 

상속: is - a (~은 ~이다)

포함: has - a (~은 ~을 가지고 있다)

 

포함 관계 

ex)자동차와 타이어, 핸들, 문 

같은 타이어 등을 여러개 가질 수도 있음 

 

단일상속과 다중 상속

java는 다중 상속을 허용하지 않음 -> 클래스 간의 관계가 복잡해지기 때문

한 부모가 여러 자식클래스를 가질 수는 있지만, 자식이 여러 부모 클래스를 가질 수는 없음

 

final클래스와 final메서드

final 키워드를 사용하면 최종적인 클래스가 됨으로서 더이상 상속할 수 없는 클래스가 됨(상속 - 오버라이딩과 함께 가는 개념이기 때문)

 

오브젝트

 Object 클래스의 메서드를 몇가지 소개

  • Object clone() : 해당 객체의 복제본을 생성하여 반환함.
  • boolean equals(Object object) : 해당 객체와 전달받은 객체가 같은지 여부를 반환함.
  • Class getClass() : 해당 객체의 클래스 타입을 반환함.
  • int hashCode() : 자바에서 객체를 식별하는 정수값인 해시 코드를 반환함.
  • String toString() : 해당 객체의 정보를 문자열 로 반환함. & Object 클래스에서는 클래스이름 @해쉬코드값 리턴함.
  • … 

오브젝트도 클래스, 최상위 부모 클래스

 

오버라이딩

부모클래스로부터 상속받은 메서드의 내용을 재정의 하는 것 

다음 조건을 만족해야 함

1. 선언부가 부모 클래스의 메서드와 일치해야 함

2. 접근 제어자를 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없다

3. 예외는 부모 클래스의 메서드보다 많이 선언할 수 없음

 

@(annotation): 주

 

super와 super()

자식 클래스 또는 객체에서 부모 클래스의 멤버를 참조할 수 있는 키워드 

super 키워드를 사용해 필드를 바꿔도 자신은 바뀌지 않고 부모 클래스의 필드가 바뀜

 

다형성 

참조변수의 타입변환

 

자동 타입변환

자동으로 부모타입으로 변환 

부모 클래스로 선언을 해도 자식 클래스로 생성 가능 (자식 클래스로 선언하고 부모 클래스로 생성은 불가능)

 

강제 타입변환

자동 형 변환이 완료된 경우에만 돌아가는 식으로 사용할 수 있음 

 

다형성이란: 여러가지 형태를 가질 수 있는 능력 

소프트웨어 - 구성하는 객체를 바꿀 수 있음 

참조변수 타입변환을 활용해서 다형성을 구현할 수 있음 

 

instanceof 

다형성 기능으로 인해 해당 클래스 객체의 원래 클래스명을 체크하는 것이 필요한데 이때 사용하는 명령어

{대상객체}instance of{클래스 이름} 형태로 사용하고 응답값은 boolean 

 

추상클래스 

미완성된 설계도 

부모 - 완성시키지 않은 메서드를 가지고 있음

자식 - 상속받아서 완성

abstaract 키워드를 사용해서 선언 

추상 클래스는 추상 메서드를 포함할 수 있음 (추상 메서드가 없어도 추상 클래스로 선언 가능)

추상 클래스는 자식 클래스에 상속되어 자식클래스에 의해서만 완성 가능함

정의만 하고 실행 내용이 없음

상속받은 클래스에서 추상클래스의 추상 메서드는 반드시 오버라이딩 되어야 함(인터페이스와의 차이)

자식클래스가 무조건 오버라이딩해 사용해야 하는 메서드를 추상 메서드로 선언 

 

인터페이스(3-17복습하기)

두 객체를 연결하는 다리 역할 

interface 키워드를 사용하여 선언, 클래스명 대신에 인터페이스명 

인터페이스의 모든 멤버변수는 public static final이어야 함(생략 가능, 컴파일러가 자동으로 추가)

인터페이스의 모든 메서드는 public abstract이어야 함(생략 가능, 컴파일러가 자동으로 추가)

인터페이스는 직접 인스턴스를 생성할 수 없기 때문에 클래스에 구현되어 생성됨 - implements키워드를 사용하여 구현, 인터페이스의 추상 메서드는 구현 될 때 반드시 오버라이딩 되어야 함 

만약 인터페이스의 추상 메서드를 일부만 구현 해야 한다면 해당 클래스를 추상 클래스로 변경해주면 됨 <-?

인터페이스 끼리도 상속이 가능 (extends 사용)

일반 클래스는 다중 상속이 불가능 하지만 인터페이스는 가능

 

디폴트 메서드와 static 메서드 

디폴트메서드

추상 메서드의 기본적인 구현을 제공 

메서드 앞에 default 키워드를 붙이며 블럭이 존재햐야 함

접근제어자가 public 

 

static 메서드 

인터페이스에서도 static메서드 선언이 가능

인터페이스의 static메서드도 객체 없이 호출 가능

선언하는 방법, 호출하는 방법은 클래스와 동일

 

다형성(3-18복습하기)

인터페이스 변수 = 구현객체;는 자동으로 타입변환이 일어남 

구현객체타입 변수 = (구현객체타입) 인터페이스 변수; 

인터페이스A에 구현체 B를 대입했으면 인터페이스 A에서 implements받은 부분만 사용가능 (강제 타입변환을 하면 B에서 구현한 부분도 사용가능)

// A 인터페이스에 구현체 B 대입
        A a1 = new B();
        a1.a();
        // a1.b(); // 불가능
interface A {
    void a();
}
class B implements A {
    @Override
    public void a() {
        System.out.println("B.a()");
    }

 

3주차 숙제

 

3단계까지 문제없이 풀었고 

4단계도 잘 풀었다고 생각했는데 답안을 확인하니 내가 쓴 코드와 전혀 달랐다

답안 코드에는 추상클래스만을 계산기 클래스에 포함시켜서 연산자 클래스를 활용했는데 내 코드는 각 연산자 클래스를 모두 계산기 클래스에 포함시켜서 간결하지 못했다. 

대신 답안 코드를 분석해본 후 직접 입력하여 계산기를 작동시키는 방향으로 코드를 수정하기로 했다.

답안코드:

Calcuator.java

public class Calculator {
//추상 클래스 선언
    private AbstractOperation operation;
    
//생성자의 매개변수로 어떤 연산자 클래스로 객체를 생성할지 정함)
    public Calculator(AbstractOperation operation){
        this.operation = operation;
    }
//연산자 클래스의 객체 변경
    public void setOperation(AbstractOperation operation){
        this.operation = operation;
    }
//계산 후 반환 
    public double calulate(int firstNumber, int secondNumber){
        double result = 0;
        result = operation.operate(firstNumber, secondNumber);
        return result;
    }
}

추상클래스를 operation 인스턴스로 선언만 하고 어떤 연산자 클래스를 사용할지는 Caculator 클래스의 객체를 생성할 때 생성자에서 받아서 정한다(생성한다). 또, 연산자를 바꾸고 싶으면 setOperation메서드를 사용해 바꾼다.

연산자 객체를 생성했으니 calculate()메서드를 통해 operate()메서드를 불러와서 계산한 값을 반환한다.

 

Main.java

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator(new AddOperation());
        System.out.println(calculator.calulate(10, 20));
        calculator.setOperation(new MultiplyOperation());
        System.out.println(calculator.calulate(10,20));
    }
}

 

수정한 계산기Main.java의 전체 코드

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String operator = sc.nextLine();
        int firstNumber = sc.nextInt();
        int secondNumber = sc.nextInt();

        double result;

        if (operator.equals("+")){*
            Calculator cal = new Calculator(new AddOperation());
            result = cal.calulate(firstNumber, secondNumber);
            System.out.println(result);
        }else if(operator.equals("-")){
            Calculator cal = new Calculator(new SubstractOperation());
            result = cal.calulate(firstNumber, secondNumber);
            System.out.println(result);
        }else if(operator.equals("*")){
            Calculator cal = new Calculator(new MultiplyOperation());
            result = cal.calulate(firstNumber, secondNumber);
            System.out.println(result);
        }else if(operator.equals("/")){
            Calculator cal = new Calculator(new DivideOperation());
            result = cal.calulate(firstNumber, secondNumber);
            System.out.println(result);
        }
    }
}