[Java] 객체지향 프로그래밍(3)

Minit88·2023년 2월 27일
0

[Java]

목록 보기
8/18
post-thumbnail

Lab_01 - 상속

상속이란 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 자바의 문법 요소

단순한 형태를 생각해보면, 두 클래스를 부모 클래스, 자식클래스로 나누어 이 둘 간의 공유하는 것을 의미한다.
이 두 클래스는 서로 상속 관계 있다고 한다.

" 클래스로부터 상속받았다 " 라는 표현보다는 " ~클래스로부터 확장되었다"는 표현으로 자바 문법에서는 extends 키워드로써 표현이 되고 있다.
그림1. 상속 클래스 표현
위 사진으로 학생과 직장인은 사람을 상속받았으며, 자바 문법 표현으로는 학생과 직장인은 사람으로부터 확장된 클래스이다.

코드예제

자바에서 상속을 구현하는 방법은 클래스명 다음에 extends 상위클래스명을 사용하여 정의한다.

class Person {
    String name;
    int age;

    void learn(){
        System.out.println("공부를 합니다.");
    };
    void walk(){
        System.out.println("걷습니다.");
    };
    void eat(){
        System.out.println("밥을 먹습니다.");
    };
}

class Programmer extends Person { // Person 클래스로부터 상속. extends 키워드 사용 
    String companyName;

    void coding(){
        System.out.println("코딩을 합니다.");
    };
}

class Dancer extends Person { // Person 클래스로부터 상속
    String groupName;

    void dancing(){
		    System.out.println("춤을 춥니다.");
		};
}

class Singer extends Person { // Person 클래스로부터 상속
    String bandName;

    void singing(){
		    System.out.println("노래합니다.");
		};
    void playGuitar(){
		    System.out.println("기타를 칩니다.");
		};
}

public class HelloJava {
    public static void main(String[] args){

        //Person 객체 생성
        Person p = new Person();
        p.name = "김코딩";
        p.age = 24;
        p.learn();
        p.eat();
        p.walk();
        System.out.println(p.name);

        //Programmer 객체 생성
        Programmer pg = new Programmer();
        pg.name = "박해커";
        pg.age = 26;
        pg.learn(); // Persons 클래스에서 상속받아 사용 가능
        pg.coding(); // Programmer의 개별 기능
        System.out.println(pg.name);

    }
}


[그림] 위 코드를 실행한 결과
Person 객체에서 선언한 메서드를 Programmer 객체에서 사용하고 있는 모습을 알 수 있다. 즉, Promgrammer은 Person으로 확장된 클래스임을 알 수 있다.

포함관계

포함은 상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미한다.

public class Employee {
    int id;
    String name;
    Address address;

    public Employee(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    void showInfo() {
        System.out.println(id + " " + name);
        System.out.println(address.city+ " " + address.country);
    }

    public static void main(String[] args) {
        Address address1 = new Address("서울", "한국");
        Address address2 = new Address("도쿄", "일본");

        Employee e = new Employee(1, "김코딩", address1);
        Employee e2 = new Employee(2, "박해커", address2);

        e.showInfo();
        e2.showInfo();
    }
}

class Address {
    String city, country;

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}


[그림] 위 코드의 출력 값

위 코드에서 보면 Employee 클래스의 멤버 변수로 Address 클래스가 정의 되어있다.
psvm 에서 address1에 Address 구현객체를 삽입해 새로운 인스턴스 address1을 생성하였고, 이를 e 인스턴스를 만들 때의 Employee 객체의 Address 타입의 매개변수 값인 address1로써 사용을 하게 된다.

메서드 오버라이딩

메서드 오버라이딩은 상위 클래스로부터 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것을 의미한다.

public class Main {
    public static void main(String[] args) {
        Bike bike = new Bike();
        Car car = new Car();
        MotorBike motorBike = new MotorBike();
        
        bike.run();
        car.run();
        motorBike.run();
    }
}

class Vehicle {
    void run() {
        System.out.println("Vehicle is running");
    }
}

class Bike extends Vehicle {
    void run() {
        System.out.println("Bike is running");
    }
}

class Car extends Vehicle {
    void run() {
        System.out.println("Car is running");
    }
}

class MotorBike extends Vehicle {
    void run() {
        System.out.println("MotorBike is running");
    }
}

[그림] 위 코드의 출력값

위 코드에서 Bike,Car,MotorBike 들은 상위 클래스인 Vehicle로 확장되으며, 상위 클래스 하위 클래스들은 동일한 run()메서드를 가지고 있다. 즉, 메서드 오버라이딩이 되었다.

메서드 오버라이딩이 되었지만 출력 값들은 상위 클래스의 run()이 아닌 자신의 run()으로 출력이 되고 있다.

이처럼 메서드 오버라이딩은 상위 클래스에 정의된 메서드를 하위 클래스에서 동작을 하위 클래스에 맞게 변경하고자 할 때 사용한다.

메서드 오버라이딩의 조건

  • 메서드의 선언부(메서드 이름, 매개변수, 반환타입)이 상위클래스의 그것과 완전히 일치해야한다.

  • 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.

  • 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다.

super키워드와 super()

super

super 키워드는 상위 클래스의 객체,super()는 상위 클래스의 생성자를 호출하는 것

super 키워드를 아래의 예시를 통해 알아보면,

public class Example {
    public static void main(String[] args) {
        SubClass subClassInstance = new SubClass();
        subClassInstance.callNum();
    }
}

class SuperClass {
    int count = 20; // super.count
}

class SubClass extends SuperClass {
    int count = 15; // this.count

    void callNum() {
        System.out.println("count = " + count);
        System.out.println("this.count = " + this.count);
        System.out.println("super.count = " + super.count);
    }
}


[그림] 위 코드의 출력 값

SubClass는 SuperClass를 상속받고 있다.Subclass의 count는 SuperClass의 count가 아닌 SubClass의 15인 값으로 재정의가 되어 SubClass 내에서 15가 출력이 되고, super.count를 통해 상위 클래스의 count 값을 호출하여 20이 출력되었다.

super()

예제를 통해 알아보면,

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

class Human {
    Human() {
        System.out.println("휴먼 클래스 생성자");
    }
}

class Student extends Human { // Human 클래스로부터 상속
    Student() {    
        super(); // Human 클래스의 생성자 호출
        System.out.println("학생 클래스 생성자");
    }
}


[그림] 위 코드의 출력 값
psvm에서 Student 생성자로 s 객체를 생성했고, 이때 student의 생성자는 super()을 실행해 상위 클래스 생성자 즉, Human 생성자인 Human()을 실행해 문자열을 출력하고 이 다음, Student() 생성자 내의 문자열을 출력한다.

이때, super() 는 this()와 마찬가지로 생성자 안에서만 사용가능하고, 반드시 첫 줄에 와야 한다.

클래스의 정점, Object 클래스

Object 클래스는 자바의 클래스 상속계층도에서 최상위에 위치한 상위 클래스이다. 따라서 자바의 모든 클래스는 Object 클래스로부터 확장된다.

class ParentEx {  //  컴파일러가 "extends Object" 자동 추가 

}

class ChildEx extends ParentEx {

}

자바 컴파일러는 컴파일 과정에서 다른 클래스로부터 아무런 상속을 받지 않는 클래스에 자동적으로 extends Object를 추가하여 Object 클래스를 상속받도록 한다.

위의 코드에서는 ParentEx가 아무런 상속을 받지 않으므로, extends Object가 생략되어 있음을 알 수 있다.

Object 클래스는 자바 클래스의 상속계층도에 가장 위에 위치하기 때문에 Object 클래스의 멤버들을 자동으로 상속받아 사용가능하다.

Lab_02-캡슐화

캡슐화란 특정 객체 안에 관련된 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 말한다.

이렇게 캡슐화를 해야하는 이유로 두 가지 목적이 있다.

  • 데이터 보호의 목적
  • 내부적으로만 사용되는 데이터에 대한 불필요한 외부 노출을 방지하기 위함

캡슐화의 가장 큰 장점은 정보은닉이다. 외부에서 객체접근하는데 있어서 정보를 숨기고 객체의 연산을 통해서만 접근이 가능하다는 점이다.
정보 은닉의 장점은 외부에서 특정 객체의 데이터 및 함수를 직접 접근을 막음으로써 변경을 못하게 하고 유지보수나 확장시 오류의 범위를 최소화 할 수 있다.

패키지

패키지란 특정한 목적을 공유하는 클래스와 인터페이스의 묶음을 의미한다.

클래스를 정의할 때 관련있는 속성과 기능을 묶어 데이터들을 효율적으로 관리할 수 있었듯, 패키지는 클래스들을 그룹 단위로 묶어 효과적으로 관리하기 위한 목적을 가지고 있다.

// 패키지를 생성했을 때
package practicepack.test; // 패키지 구문 포함. 패키지가 없다면 구문 필요없음

public class PackageEx {

}

[코드] 패키지 생성

import 문

import 문은 다른 패키지 내의 클래스를 사용하기 위해 사용하며, 일반적으로 패키지 구문과 클래스문 사이에 작성한다.

ackage practicepack.test;

public class ExampleImport {
		public int a = 10;
		public void print() {
			System.out.println("Import문 테스트")
		}
}
package practicepack.test2; // import문을 사용하지 않는 경우, 다른 패키지 클래스 사용방법

public class PackageImp {
		public static void main(String[] args) {
			practicepack.test.ExampleImport example = new practicepack.test.ExampleImport();
		}
}

import 문을 사용하지 않을 경우 두번째 코드의 psvm에서 처럼 해당 경로를 온점으로 구분해 정의해준다.

import 문을 사용할 때,

import 패키지명, 클래스명; 또는 import 패키지명.*;

으로 작성이 가능하다.

import 문을 사용했을 때의 예시는,

package practicepack.test;

public class ExampleImp {
		public int a = 10;
		public void print() {
			System.out.println("Import문 테스트")
}
package practicepack.test2; // import문을 사용하는 경우

import practicepack.test.ExampleImp // import문 작성

public class PackageImp {
		public static void main(String[] args) {
			ExampleImp x = new ExampleImp(); // 이제 패키지명을 생략 가능
		}
}

import 사용하고 나서의 코드가 훨씬 더 깔끔해짐을 알 수 있다.

접근 제어자

제어자

자바 프로그래밍에서 제어자는 클래스,필드,생성자 등에 부가적인 의미를 부여하는 키워드를 의미한다.

자바에서는 제어자를 크게 접근 제어자와 기타 제어자로 구분이 가능하다.

  • 접근제어자 : Public,protected,default,privat
  • 기타제어자 : static,final,abstract,native,transient,synchronized 등

접근 제어자

접근 제어자를 사용하면 클래스 외부로의 불필요한 데이터 노출을 방지할 수 있고, 외부로부터 데이터가 임의로 변경되지 않도록 막을 수 있다.
자바 접근 제어자로 다음의 4가지가 있다.
위의 내용을 접근 제한 범위에 따라서 표현하면
public(접근 제한 없음)>protected(동일 패키지 + 하위 패키지) > default(동일 패키지) >private(동일 클래스)

아래의 예제로 더 알아보면,

package package1; // 패키지명 package1 

//파일명: Parent.java
class Test { // Test 클래스의 접근 제어자는 default
    public static void main(String[] args) {
        Parent p = new Parent();

//        System.out.println(p.a); // 동일 클래스가 아니기 때문에 에러발생!
        System.out.println(p.b);
        System.out.println(p.c);
        System.out.println(p.d);
    }
}

public class Parent { // Parent 클래스의 접근 제어자는 public
    private int a = 1; // a,b,c,d에 각각 private, default, protected, public 접근제어자 지정
    int b = 2;
    protected int c = 3;
    public int d = 4;

    public void printEach() { // 동일 클래스이기 때문에 에러발생하지 않음
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }
}

// 출력값
2
3
4

Test 에서 p.a가 선언이 되지 않음을 알 수 있다. 이는 a 변수가 private로 선언이 되어 같은 클래스 내에서만 정의 되기 때문에 , 다른 클래스인 Test 내에서는 정의가 되지 않아 오류가 발생한다.

package package2; // package2 

//파일명 Test2.java
import package1.Parent;

class Child extends package1.Parent {  // package1으로부터 Parent 클래스를 상속
    public void printEach() {
        // System.out.println(a); // 에러 발생!
        // System.out.println(b);
        System.out.println(c); // 다른 패키지의 하위 클래스
        System.out.println(d);
    }
}

public class Test2 {
    public static void main(String[] args) {
        Parent p = new Parent();

//        System.out.println(p.a); // public을 제외한 모든 호출 에러!
//        System.out.println(p.b);
//        System.out.println(p.c);
        System.out.println(p.d);
    }
}

클래스 Child는 package1.Parent 를 상속받는다.
a는 private로 정의가 되어 다른 클래스인 Child에서 정의가 되지 않고,
b는 default으로 정의가 되어 동일패키지 내에서만 정의가 된다. 따라서 다른 패키지 내에서 선언되었으니 Child에서는 정의가 되지 않는다.
c는 Protected로 선언이 되어 Parent를 상속받는 Child는 Parent의 하위 클래스 이므로, 동일 패키지+ 다른 패키지의 하위 클래스의 범위인 Protected의 범위 내에 속하게 되어 정의가 된다.

클래스 Test2는 Child와 달리 어떠한 상속(Object제외)을 받지 않는다.
Child 에서는 c 를 포함한 c,d 가 선언이 되었지만, Test2에서는 이와 달리 c가 선언이 되지 않는다, 이는 c의 접근제어자 Protected 범위 내에 속하지 않기 때문이다.

getter와 setter 메서드

객체지향의 캡슐화의 목적을 달성하면서도 데이터의 변경이 필요한 경우 대표적으로, private 접근 제어자가 포함되어 있는 객체의 변수의 데이터 값을 추가하거나 수정하고 싶을 때를 생각해볼 수 있다.
이런 경우에 getter와 setter 메서드를 사용할 수 있다.

public class Main {
    public static void main(String[] args) {
        Worker w= new Worker();
        w.setName("김코딩");
        w.setAge(30);
        w.setId(5);

        String name = w.getName();
        int age = w.getAge();
        int ID = w.getId();

        System.out.println(String.format("근로자의 이름은 %S 근로자의 나이는 %d 근로자의 ID 는 %d 입니다.",name,age,ID));
    }
}

class Worker{
    private String name; //변수의 은닉화. 외부로부터 접근 불가
    private int age;
    private int id;

    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        if(age<1) return;
        this.age = age;
    }
    public int getId(){
        return id;
    }
    public void setId(int id){
        this.id =id;
    }

}


getter와 setter을 통해 private 변수를 초기화 및 출력을 하였다. 따라서 해당 변수에는 특정 메서드(getter,setter) 만으로 접근이 가능해 정보의 은닉이 가능해 짐을 알 수 있다.

profile
" To be BE "

0개의 댓글