반응형

 

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#10 열거형(Enum)  (0) 2022.08.31
#5 추상 클래스 (Abstract Class)  (0) 2022.08.24
#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
반응형

열거형의 목적

열거형은 자바 5에서 추가된 자료형으로, 의미가 부여된 이름을 갖는 상수의 선언에 그 목적이 있습니다.

 

열거형 학습에 앞서, 자바 5 이전에는 의미가 부여된 이름을 갖는 상수를 어떻게 선언했는지 다음 코드로 확인해보겠습니다.

public interface Scale {
    int DO = 0;
    int RE = 1;
    int MI = 2;
    int FA = 3;
    int SO = 4;
    int RA = 5;
    int TI = 6;
}

public class InterfaceBaseConst {
    public static void main(String[] args) {
        int sc = Scale.DO;

        switch (sc) {
            case Scale.DO:
                System.out.println("음 이름 : 도");
                break;
            case Scale.RE:
                System.out.println("음 이름 : 레");
                break;
            case Scale.MI:
                System.out.println("음 이름 : 미");
                break;
            case Scale.FA:
                System.out.println("음 이름 : 파");
                break;
            default:
                System.out.println("음 이름 : 솔, 라, 시");
        }
    }
}

출력 결과값은 다음과 같습니다.

음 이름 : 도

인터페이스 내에 선언된 변수는 public, static, final 이 선언된 것으로 간주합니다.

따라서 인터페이스 Scale 을 통해 7개의 상수가 선언되었습니다.

 

인터페이스 Scale 은 음계를 표현한 상수들을 담고 있습니다.

이 경우 중요한 것은 상수의 값이 아니라 상수의 이름입니다.

즉 상수의 값이 바뀌어도 이름이 바뀌지 않는다면 코드에 아무런 영향을 주지 않습니다.

그리고 이렇게 연관된 상수들을 하나의 인터페이스로 묶어서 선언하는 것이 자바 5 이전에 사용하던 방법입니다.

그러나 이 방법에는 문제가 하나 있는데, 그 문제점을 다음 코드에서 확인하겠습니다.

public interface Animal {
    int Dog = 1;
    int CAT = 2;
}

public interface Person {
    int MAN = 1;
    int WOMAN = 2;
}

public class NonSafeConst {
    public static void main(String[] args) {
        who(Person.MAN);
        who(Animal.Dog);
    }

    public static void who(int man) {
        switch (man) {
            case Person.MAN:
                System.out.println("남자입니다.");
                break;

            case Person.WOMAN:
                System.out.println("여자입니다.");
                break;
        }
    }
}

출력 결과값은 다음과 같습니다.

남자입니다.
남자입니다.

Person.MAN 도 값이 1이고 Animal.DOG 도 값이 1이기 때문에,

위와 같은 실수를 범해도 컴파일 오류는 물론 실행 오류도 발생하지 않습니다.

그 문제점을 해결하기 위해 자바 5에서 열거형이 소개되었습니다.


열거형의 정의

열거형은 다음과 같이 정의합니다.

그 안에 위치한 이름들을 가리켜 '열거형 값' 이라고 합니다.

public enum Scale {
    DO, RE, MI, FA, SOL, RA, THI
}

 

열거형은 클래스와 성격이 유사합니다.

따라서 다음과 같이 참조변수의 선언도 가능합니다.

단 이렇게 선언된 참조변수는 해당 열거형 내에 선언된 '열거형 값'만 대입이 가능합니다.

        Scale scale = Scale.DO;

 

기본적으로 열거형 값은 Scale.DO 와 같이 표현하지만,

case 문에서는 표현의 간결함을 위해 Do 와 같이 열거형 값의 이름만 명시하기로 약속되어 있습니다.

다음은 열거형 이전의 코드를 열거형 이후의 코드로 수정한 코드입니다.

public enum Scale {
    DO, RE, MI, FA, SOL, RA, THI
}

public class SimpleEnum {
    public static void main(String[] args) {
        Scale scale = Scale.DO;
        System.out.println(scale);

        switch (scale) {
            case DO:
                System.out.println("음 이름 : 도");
                break;
            case RE:
                System.out.println("음 이름 : 레");
                break;
            case MI:
                System.out.println("음 이름 : 미");
                break;
            case FA:
                System.out.println("음 이름 : 파");
                break;
            default:
                System.out.println("음 이름 : 솔, 라, 시");
        }
    }
}

출력 결과값은 다음과 같습니다.

DO
음 이름 : 도

 

그렇다면 열거형 이전의 문제점이 해결되었는지 아래의 코드로 확인해보겠습니다.

public enum Animal {
    DOG, CAT
}

public enum Person {
    MAN, WOMAN
}

public class SafeEnum {
    public static void main(String[] args) {
        who(Person.MAN);
        who(Animal.DOG);
        Animal animal = Person.MAN;
    }

    public static void who(Person who) {
        switch (who) {
            case MAN:
                System.out.println("남자입니다.");
                break;
            case WOMAN:
                System.out.println("여자입니다.");
                break;
        }
    }
}

 

열거형 Animal 과 Person 이 정의되었습니다.

코드를 실행시켜보지만 아래의 코드에서 자료형 불일치로 인한 컴파일 오류가 발생합니다.

따라서 앞서 소개한 자바 5 이전의 문제점이 열거형을 사용할 경우 발생하지 않습니다.

        who(Animal.DOG);
        Animal animal = Person.MAN;

클래스 내에 열거형 정의

특정 클래스 내에서만 사용하고자 하는 열거형 값이 있다면, 아래의 코드와 같이 정의하면 됩니다.

public class Customer {

    enum Gender {
        MALE, FEMALE
    }

    private String name;
    private Gender gender;

    public Customer(String name, String gender) {
        this.name = name;

        if ("man".equals(gender)) {
            this.gender = Gender.MALE;
        } else {
            this.gender = Gender.FEMALE;
        }
    }

    @Override
    public String toString() {
        if (gender == Gender.MALE) {
            return "Mr " + name;
        } else {
            return "Ms " + name;
        }
    }
}

public class InnerEnum {
    public static void main(String[] args) {
        Customer customer1 = new Customer("Park", "man");
        Customer customer2 = new Customer("Han", "woman");

        System.out.println(customer1);
        System.out.println(customer2);
    }
}

 

출력 결과값은 다음과 같습니다.

Mr Park
Ms Han

열거형 값의 정체

진행하기 전에 먼저 다음과 같이 클래스 정의가 가능함을 소개하고자 합니다.

public class Person {
    public static final Person MAN = new Person();
    public static final Person WOMAN = new Person();

    @Override
    public String toString() {
        return "I am Person";
    }
}

public class InClassInst {
    public static void main(String[] args) {
        System.out.println(Person.MAN);
        System.out.println(Person.WOMAN);
    }
}

 

출력 결과값은 다음과 같습니다.

I am Person
I am Person

 

위 코드에서 보이듯이 Person 클래스 내에서 Person 형 참조변수를 선언하는 것도, Perosn 인스턴스를 생성하는 것도 가능합니다.

그럼 다음 코드를 보겠습니다. 이 코드에서는 열거형 값이 해당 자료형의 인스턴스라는 사실을 알려줍니다.

public enum Person {
    MAN, WOMAN;

    @Override
    public String toString() {
        return "Enum Person Object";
    }
}

public class EnumConst {
    public static void main(String[] args) {
        System.out.println(Person.MAN);
        System.out.println(Person.WOMAN);
    }
}

 

출력 결과값은 다음과 같습니다.

Enum Person Object
Enum Person Object

 

위 결과에서 확인할 수 있는 점은 다음과 같습니다.

  1. 열거형은 클래스입니다.
    모든 열거형은 java.lang.Enum<E> 클래스를 상속합니다.
    그리고 Enum<E> 는 Object 클래스를 상속합니다.
    이러한 측면에서 볼 때 열거형은 클래스입니다.
  2. 열거형의 값은 참조변수 입니다.
    이에 대한 증거로 출력 결과값에서 toString 메소드가 호출되었음을 보면 알 수 있습니다.

열거형의 정의에도 생성자가 따로 없다면 디폴트 생성자가 삽입됩니다.

다만 이 생성자는 private으로 선언되어 직접 인스턴스를 생성하는 것이 불가능할 뿐입니다.

이번에는 열거형 생성자를 정의해봄으로서, 열거형 값의 정체를 더 알아보겠습니다.

public enum Person {
    MAN, WOMAN;

    private Person() {
        System.out.println("Person 생성자 호출");
    }

    @Override
    public String toString() {
        return "Enum Person Object";
    }
}

public class EnumConstructor {
    public static void main(String[] args) {
        System.out.println(Person.MAN);
        System.out.println(Person.WOMAN);
    }
}

 

출력 결과값은 다음과 같습니다.

Person 생성자 호출
Person 생성자 호출
Enum Person Object
Enum Person Object

 

위 코드에서는 두 개의 열거형 값이 존재하기에 두 번의 생성자 호출이 이뤄졌습니다.

따라서 열거형 값의 정체는 다음과 같이 표현할 수 있습니다.

열거형 값은 생성자가 private이라 실제 컴파일은 되지 않습니다.

public static final Person MAN = new Person();
public static final Person WOMAN = new Person();

열거형의 생성자 정의 방법과 호출 방법

public enum Person {
    MAN(29),
    WOMAN(25);

    int age;

    private Person(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" + "age=" + age + '}';
    }
}

public class EnumParamConstructor {
    public static void main(String[] args) {
        System.out.println(Person.MAN);
        System.out.println(Person.WOMAN);
    }
}

 

출력 결과값은 다음과 같습니다.

열거형의 생성자는 무조건 private 으로 선언해야 합니다.

그리고 열거형 값의 선언에서 아래의 코드와 같이 소괄호를 통해서 생성자에 인자를 전달할 수 있습니다.

Person{age=29}
Person{age=25}

 

결론은 간단합니다.

열거형도 Object 클래스를 상속하는 일종의 클래스입니다.

따라서 생성자는 물론, 인스턴스 변수와 메소드 둘 다 가질 수 있습니다.

다만 모든 생성자를 private으로 선언해야 하기 때문에 열거형 값이 유일한 인스턴스 생성 방법이라는 차이가 있을 뿐입니다.

 

 

오늘도 좋은 하루 보내세요.

감사합니다.

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#11 Optional  (0) 2022.09.05
#5 추상 클래스 (Abstract Class)  (0) 2022.08.24
#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
반응형

추상 클래스의 특징

1. 추상 메소드를 하나라도 가진 클래스입니다.

여기서 추상 메소드란 메소드의 선언부만 존재하고, 구현부는 없는 메소드입니다.

2. 클래스이기 때문에 변수를 가질 수 있습니다.

3. 추상 클래스를 extends 한 클래스가 추상 메소드를 구현하도록 강제합니다.

4. 추상 메소드에 필수적으로 abstract 예약어를 작성해야 합니다.

자바에서 추상 메소드는 다음과 같이 작성합니다.

abstract 반환형 메소드명();

5. 추상 클래스는 인스턴스를 생성할 수 없습니다.

6. 구현 클래스에서는 extends 예약어를 사용합니다.


자바에서 추상 클래스를 사용하는 목적

중복되는 부분이나 공통적인 부분은 공통 메소드를 사용하고,

구현 클래스에 따라 메소드의 동작 방식의 변경이 필요한 경우에는 공통의 메소드를 오버라이딩하여 사용함으로서 아래의 이점을 얻을 수 있습니다.

 

1. 생산성의 향상을 도모

2. 연관된 일련의 클래스들에 대해 공통적인 규약을 정의

 

아래의 코드는 그 목적에 맞게 추상 클래스를 이용한 코드입니다.

public abstract class Animal {
    private final String name;
    private final int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void information() {
        System.out.println("이름은 " + name);
        System.out.println("나이는 " + age);
    }
    abstract void cry();
}

public class Dog extends Animal{

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    void cry() {
        System.out.println("멍멍");
    }
}

public class Cat extends Animal{

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    void cry() {
        System.out.println("야옹");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog("Park", 11);
        Animal cat = new Cat("Han", 10);

        dog.information();
        cat.information();

        dog.cry();
        cat.cry();
    }
}

 

결과값은 다음과 같습니다.

이름은 Park
나이는 11
이름은 Han
나이는 10
멍멍
야옹

추상 클래스는 말 그대로 인스턴스를 생성할 수 없습니다.

아래의 코드와 같이 익명 클래스를 구현해서 사용하는 방법 밖에는 없습니다.

Animal animal = new Animal() {
            @Override
            public void cry() {
                System.out.println("익명 클래스");
            }
        };

 

이상입니다.

 

오늘도 좋은 하루 보내세요.

감사합니다.

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#11 Optional  (0) 2022.09.05
#10 열거형(Enum)  (0) 2022.08.31
#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
반응형

Object 클래스는 모든 클래스가 직간접적으로 상속을 하는 클래스이므로 모든 클래스들의 조상이라고 할 수 있습니다.

Object 클래스 내부의 메소드 중에서도 아래의 메소드들을 순서대로 정리해보겠습니다.

1. finalize

2. equals

3. clone


1. finalize 메소드

Object 클래스에는 finalize 메소드가 다음과 같이 정의되어 있습니다.

이 메소드는 아무도 참조하지 않는 인스턴스가 가비지 컬렉션에 의해 소멸되기 전에 자동으로 호출되는 메소드입니다.

    protected void finalize() throws Throwable { }

 

따라서 인스턴스 소멸 시 반드시 실행해야 할 코드가 있다면 이 메소드의 오버라이딩을 고려해볼 수 있습니다.

아래는 간단하게 작성한 예제입니다.

public class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Instance Destroy : " + name);
    }
}

public class FinalizeMethod {
    public static void main(String[] args) {
        Person person1 = new Person("Park");
        Person person2 = new Person("Han");

        person1 = null;
        person2 = null;

        System.gc();
        System.runFinalization();
        
        System.out.println("Main Method End");
    }
}​

 

위 예제 코드에서는 인스턴스의 소멸을 강제하기 위해 GC를 강제로 진행했지만,

평소 JVM은 매우 합리적인 방법으로 GC를 수행하기 때문에 특별한 상횡이 아니라면 강제하지 않는 것이 좋습니다.

결과 출력값은 다음과 같습니다.

Instance Destroy : Park
Instance Destroy : Han
Main Method End

2. equals 메소드

equals 메소드는 간단하게  두 개의 값을 비교할 때 사용합니다.

따라서 비슷한 역할을 가진 == 연산자와 많은 비교를 하곤 합니다.

 

이 둘은 양 쪽의 값을 비교한 후 그 결과를 boolean type 으로 반환한다는 공통점을 가집니다.

하지만 가장 눈에 띄는 차이점으로는 equals() 는 메소드이고, ==은 연산자라는 점이 있습니다.

우선 자세한 내용은 아래의 코드들을 통해 알아보도록 합시다.

 

Object 클래스에는 equals 메소드가 다음과 같이 정의되어 있습니다.

    public boolean equals(Object obj) {
        return (this == obj);
    }

 

equals 메소드는 양 쪽의 내용을 비교해야하는 상황일 때 오버라이딩을 고려해볼 수 있습니다.

아래는 간단하게 작성한 예제입니다.

public class INum {
    private int num;

    public INum(int num) {
        this.num = num;
    }

    @Override
    public boolean equals(Object obj) {
        return this.num == ((INum) (obj)).num;
    }
}

public class ObjectEquality {
    public static void main(String[] args) {
        INum num1 = new INum(10);
        INum num2 = new INum(20);
        INum num3 = new INum(10);

        if (num1.equals(num2)) {
            System.out.println("num1 = num2");
        } else {
            System.out.println("num1 != num2");
        }

        if (num1.equals(num3)) {
            System.out.println("num1 = num3");
        } else {
            System.out.println("num1 != num3");
        }
    }
}

 

결과 출력값은 다음과 같습니다.

num1 != num2
num1 = num3

 

위 코드의 INum 클래스는 Object 클래스의 equals 메소드를 다음과 같이 클래스 내에 선언된 인스턴스 변수의 내용을 비교하여,

그 결과에 따라 true 또는 false의 boolean 값을 반환하도록 오버라이딩 했습니다.

@Override
    public boolean equals(Object obj) {
        return this.num == ((INum) (obj)).num;
    }

 

이렇듯 두 인스턴스의 비교 조건은 해당 클래스를 정의하는 개발자가 결정해야 합니다.

그리고 그 결정 사항을 equals 메소드를 equals 메소드의 오버라이딩을 통해 반영해야 합니다.

Object 메소드는 참조변수의 참조값을 비교하도록 정의되어 있습니다.

그런데 == 연산을 통해서도 참조값은 비교가 가능합니다.

따라서 equals 메소드의 호출을 통해 참조값을 비교할 필요는 없습니다.

즉 equals 메소드는 내용 비교가 이뤄지도록 오버라이딩 하라고 존재하는 메소드입니다.

자바에서 제공하는 표준 클래스의 경우 equals 메소드가 이미 내용 비교를 하도록 오버라이딩 된 경우가 많습니다.

그 대표적인 예가 String 클래스인데 이와 관련하여 다음 코드를 보겠습니다.

public class StringEquality {
    public static void main(String[] args) {
        String str1 = new String("Park");
        String str2 = new String("Park");

        if (str1 == str2) {
            System.out.println("str1 = str2");
        } else {
            System.out.println("str1 != str2");
        }

        if (str1.equals(str2)) {
            System.out.println("str1 = str2");
        } else {
            System.out.println("str1 != str2");
        }
    }
}

 

결과 출력은 다음과 같습니다.

str1 != str2
str1 = str2

 

분명 일반적인 equals 메소드일 경우는 str1 과 str2는 동일하지 않다고 출력할 것입니다.

하지만 결과는 그렇지 않습니다.

그 이유는 String 내부에 오버라이딩 된 equals 메소드는 내용을 비교하도록 정의되어있기 때문입니다.

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

 

결론입니다.

두 인스턴스의 내용 비교를 원한다면 Object 클래스의 equals 메소드를 오버라이딩하여 사용합니다.

자바 개발자들이 원한 설계가 바로 그것입니다.

만약 단순한 참조변수의 참조값을 비교하려면 == 연산을 사용합니다.


3. clone 메소드

Object 클래스에는 인스턴스의 복사를 위한 다음 메소드가 정의되어 있습니다.

protected native Object clone() throws CloneNotSupportedException;

 

이 메소드가 호출되면, 호출된 메소드가 속한 인스턴스의 복사본이 생성되고,

이렇게 만들어진 복사본의 참조값이 반환됩니다.

단, 다음 인터페이스를 구현한 인스턴스를 구현한 인스턴스를 대상으로만 clone 메소드를 호출할 수 있습니다.

 

만약 Cloneable 인터페이스를 구현하지 않은 클래스의 인스턴스를 대상으로 clone 메소드를 호출하면,

CloneNotSupportedException 예외가 발생합니다.

Cloneable 인터페이스의 구현은 "이 클래스의 인스턴스는 복사해도 됩니다" 와 같은 마커 인터페이스 역할을 합니다.

즉, 정의해야 할 메소드가 존재하지 않는, 복사를 해도 된다는 표식의 인터페이스입니다.

 

인스턴스의 복사는 클래스에 따라 허용해서는 안되는 작업이 될 수 있습니다.

따라서 인스턴스 복사의 허용 여부를 클래스를 정의하는 과정에서 고민하고 결정한 후 Cloneable 인터페이스를 구현해서 clone 메소드의 호출이 가능하도록 하면 됩니다.

아래의 코드로 확인해보겠습니다.

public class Point implements Cloneable{
    private int xPos;
    private int yPos;

    public Point(int xPos, int yPos) {
        this.xPos = xPos;
        this.yPos = yPos;
    }

    public void showPosition() {
        System.out.printf("[%d, %d]", xPos, yPos);
        System.out.println();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class InstanceCloning {
    public static void main(String[] args) throws CloneNotSupportedException {
        Point original = new Point(3, 5);
        Point copy;

        copy = (Point) original.clone();
        original.showPosition();
        copy.showPosition();
    }
}

 

결과 출력은 다음과 같습니다.

[3, 5]
[3, 5]

 

실행 결과는 인스턴스의 복사가 정상적으로 이뤄졌음을 보여줍니다.

이건 관련성이 없는 문제이지만, 위 코드에서는 clone 메소드를 다음과 같이 오버라이딩 했습니다.

잘보면 그저 상위 클래스의 clone 메소드를 호출한 것이 전부이기 때문에, 오버라이딩이 무의미해 보입니다.

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

그러나 중요한 차이가 있습니다.

앞서 Object 에 정의된 clone 메소드는 접근제어지시자가 protected 로 되어있었습니다.

즉 오버라이딩의 목적은 protected -> public 으로 접근 범위를 넓힌 것입니다.

하지만 반대의 경우인 public -> protected 로 접근 범위를 제한하는 형태의 오버라이딩은 불가능합니다.

 

마지막으로 얕은 복사와 깊은 복사라는 개념을 아래의 코드로 확인해보겠습니다.

public class Point implements Cloneable{
    private int xPos;
    private int yPos;

    public Point(int xPos, int yPos) {
        this.xPos = xPos;
        this.yPos = yPos;
    }

    public void showPosition() {
        System.out.printf("[%d, %d]", xPos, yPos);
        System.out.println();
    }

    public void changePos(int x, int y) {
        xPos = x;
        yPos = y;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Rectangle implements Cloneable{
    private Point upperLeft; //좌측 상단 좌표
    private Point lowerRight; //우측 상단 좌표

    public Rectangle(int x1, int y1, int x2, int y2) {
        upperLeft = new Point(x1, y1);
        lowerRight = new Point(x2, y2);
    }

    public void changePos(int x1, int y1, int x2, int y2) {
        upperLeft.changePos(x1, y1);
        lowerRight.changePos(x2, y2);
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void showPosition() {
        System.out.print("좌측 상단 좌표 : ");
        upperLeft.showPosition();

        System.out.print("우측 하단 좌표 : ");
        lowerRight.showPosition();
        System.out.println();
    }
}

public class ShallowCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Rectangle original = new Rectangle(1, 1, 9, 9);
        Rectangle copy;

        copy = (Rectangle) original.clone();
        original.changePos(2, 2, 7, 7);
        original.showPosition();
        copy.showPosition();
        
        System.out.println(original);
        System.out.println(copy);
        System.out.println(original.getUpperLeft());
        System.out.println(original.getLowerRight());
        System.out.println(copy.getUpperLeft());
        System.out.println(copy.getLowerRight());
    }
}

 

결과 기대값은 아래와 같습니다.

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

좌측 상단 좌표 : [1, 1]
우측 하단 좌표 : [9, 9]

 

실제 결과 출력값은 아래와 같습니다.

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

 

예상했던 결과와 상이합니다.

인스턴스가 정상적으로 복사된 것은 맞지만,

Rectangle 클래스 내부의 Point 형 참조변수인 upperLeft 와 lowerRight 의 참조값이 그대로 새 인스턴스에 복사가 된 것입니다.

	System.out.println(original);
        System.out.println(copy);
        System.out.println(original.getUpperLeft());
        System.out.println(original.getLowerRight());
        System.out.println(copy.getUpperLeft());
        System.out.println(copy.getLowerRight());

출력 결과값은 다음과 같습니다.

복사본의 내부 참조변수의 참조값이 원본의 참조변수의 참조값과 동일하단 것을 알 수 있습니다.

그리고 이런 현상을 '얕은 복사'라고 합니다.

또한 원래 기대하던 완전한 복사를 '깊은 복사'라고 생각하면 됩니다.

objectClass.Rectangle@60e53b93
objectClass.Rectangle@5e2de80c
objectClass.Point@1d44bcfa
objectClass.Point@266474c2
objectClass.Point@1d44bcfa
objectClass.Point@266474c2

 

깊은 복사를 하기 위해 Rectangle 에 오버라이딩 된 clone 메소드를 아래의 코드와 같이 수정합니다.

    @Override
    public Object clone() throws CloneNotSupportedException {
        Rectangle copy = (Rectangle) super.clone();

        copy.upperLeft = (Point) upperLeft.clone();
        copy.lowerRight = (Point) lowerRight.clone();

        return copy;
    }

 

출력 결과값입니다.

처음 기대한 값 그대로 결과값으로 확인할 수 있습니다.

그렇다면 String 도 참조형인데 방금과 같은 깊은 복사를 적용해야 하나? 라는 의문점이 생길 수도 있습니다.

다행히도 String 인스턴스의 내용을 이루는 문자열은 인스턴스 생성 시 결정이 되고,

변경이 불가능하기 때문에 얕은 복사를 해도 문제 없습니다.

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

좌측 상단 좌표 : [1, 1]
우측 하단 좌표 : [9, 9]

objectClass.Rectangle@60e53b93
objectClass.Rectangle@5e2de80c
objectClass.Point@1d44bcfa
objectClass.Point@266474c2
objectClass.Point@6f94fa3e
objectClass.Point@5e481248

 

이상입니다.

오늘도 좋은 하루 보내세요.

감사합니다.

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#10 열거형(Enum)  (0) 2022.08.31
#5 추상 클래스 (Abstract Class)  (0) 2022.08.24
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
#1 자바 개발 핵심 원칙  (0) 2022.07.11
반응형

자바의 예외처리 관련 내용이 의외로 적지 않습니다.

따라서 예외처리를 하지 않는 상황에서는 불필요한 내용이라 생각하고 넘어가고 싶을 수도 있지만,

자바에서는 예외처리가 차지하는 부분이 크기 때문에 마지막까지 이해하고 넘어가야 합니다.

 

예외 상황을 알리기 위해 정의된 클래스의 종류

package ExceptionHandling;

public class ArrayIndexOutOfBounds {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int i = 0; i < 4; i++) {
            System.out.println(arr[i]);
        }
    }
}

package ExceptionHandling;

class Board {
}

class PBoard extends Board {
}

public class ClassCast {
    public static void main(String[] args) {
        Board pboard1 = new PBoard();
        PBoard pBoard2 = (PBoard) pboard1;

        System.out.println("-----");

        Board board1 = new Board();
        PBoard board2 = (PBoard) board1;
    }
}

package ExceptionHandling;

public class NullPointer {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str);
        int length = str.length();
    }
}


예외 클래스의 구분

예외 클래스의 최상위 클래스는 Throwable 입니다.

그런데 이를 상속하는 예외 클래스는 다음과 같이 세 부류로 나뉩니다.

  1. Error 클래스를 상속하는 예외 클래스
  2. RuntimeException 클래스를 상속하는 예외 클래스 -> RuntimeException 클래스는 Exception 클래스를 상속합니다.
  3. Exception 클래스를 상속하는 예외 클래스

1.  Error 클래스를 상속하는 예외 클래스

예외 클래스의 예와 그 발생 상황을 정리하면 다음과 같습니다.(객체명에 Error가 포함됩니다.)

  • VirtualMachineError -> 가상머신에 심각한 오류 발생
  • IOError -> 입출력 관련해서 코드 수준 복구가 불가능한 오류 발생

VirtualMachineError의 구체적인 예

VirtualMachineError 클래스를 상속하는 예외 클래스로 OutOfMemoryError가 있습니다.

이는 프로그램의 실행에 필요한 메모리 공간이 부족한 상황에서 발생하는 예외입니다.

따라서 이 예외가 발생하면 메모리를 비효율적으로 또는 부적절하게 사용하는 부분의 코드를 수정해야 합니다.

 

IOError의 구체적인 예

자바 프로그램이 임의의 파일에 저장된 데이터를 읽는 중에 갑자기 하드디스크에 물리적 오류가 발생하여 더 이상 파일에 저장된 데이터를 읽을 수 없는 상황이 생길 수 있습니다.

그리고 이런 상항에서 발생하는 것이 IOError Exception 입니다.

즉 Error 클래스를 상속하는 예외는 처리의 대상이 아닙니다.

바꿔 말하면 처리할 수 있는 예외가 아닙니다. 따라서 이런 유형의 예외가 발생하면 그냥 프로그램이 종료되도록 놔두고 이후에 원인을 파악하는 과정이 필요합니다.

 

2. RuntimeException 클래스를 상속하는 예외 클래스

앞서 보였던 예외 클래스가 이에 해당합니다.

  • ArithmeticException
  • ClassCastException
  • IndexOutOfBoundsException
  • NegativeArraySizeException -> 배열 생성시 길이를 음수로 지정하는 예외 발생
  • NullPointerException
  • ArrayStoreException -> 배열에 적절치 않는 인스턴스를 저장하는 예외 발생

3. Exception 클래스를 상속하는 예외 클래스

마지막으로 Exception 클래스를 상속하는 예외 클래스가 있습니다.

RuntimeException 클래스를 직접 혹은 간접적으로 상속하지 않고 Exception 클래스만을 상속하는 예외클래스가 해당됩니다.

그 수로만 따지면 나머지 둘에 비해 가장 많습니다.

그리고 이들은 특정 클래스 또는 메소드에 연관되어 있어서 나열하여 설명하는 것은 의미가 없습니다.

앞으로 공부하면서 이 부류에 속한 예외 클래스를 하나씩 접하게 됩니다.

Exception을 상속하는 예외 클래스 중에서 비교적 빨리 접하게 될 클래스는 다음과 같습니다.

java.io.IOException


프로그래머가 정의하는 예외

지금까지 소개한 예외 클래스는 모두 자바에서 정의한 클래스였습니다.

그러나 프로그래머가 직접 예외 클래스를 정의하고 이를 기반으로 특정 상황에서 예외가 발생하도록 할 수 있습니다.

프로그래머가 정의하는 예외 클래스의 예의 핵심은 Exception 을 상속하는데 있습니다.

Exception 클래스를 아래의 코드로 구현해보겠습니다.

package ExceptionHandling;

public class ReadAgeException extends Exception {
    public ReadAgeException() {
        super("유효하지 않은 나이가 입력되었습니다.");
    }
}

위의 클래스는 Exception 을 상속하는 점을 제외하면 일반 클래스와 차이가 없습니다.

생성자에서는 상위 클래스의 생성자를 호출하면서 예외 상황에 대한 설명을 담고 있는 문자열을 전달하는데,

이 문자열은 앞서 보였던 Throwable 클래스에 정의된 public String getMessage() 메소드 호출 시 반환 됩니다.

 

그럼 우리가 정의한 ReadAgeException 예외 클래스를 대상으로 예외를 발생시키고 이를 처리해보겠습니다.

package ExceptionHandling;

import java.util.Scanner;

public class MyExceptionClass {
    public static void main(String[] args) {
        System.out.println("나이 입력 : ");

        try {
            int age = readAge();
            System.out.printf("입력된 나이 : %d", age);
        } catch (ReadAgeException e) {
            System.out.println(e.getMessage());
        }
    }

    public static int readAge() throws ReadAgeException {
        Scanner scanner = new Scanner(System.in);
        int age = scanner.nextInt();
        if (age < 0) {
            throw (new ReadAgeException());
        }
        return age;
    }
}

정수를 입력받았는데 그 수가 음수인 것은 문법적으로는 오류가 아닙니다.

하지만 프로그램 특성상 사람의 나이는 음수가 될 수 없으므로 오류가 맞습니다.

이러한 상황을 예외로 처리하기 위해서 예외 클래스를 직접 정의하였습니다.

이렇듯 예외 클래스의 인스턴스를 생성하고, 이를 대상으로 throw 선언을 하면 예외가 발생합니다.

물론 이렇게 발생한 예외도 Exception 을 상속하는 예외이므로 try ~ catch 문으로 처리를 하거나 throws 선언을 통해 넘겨야 합니다.


잘못된 catch 구문의 구성

다음과 같이 세 개의 예외 클래스가 정의되었다고 가정합니다.

class FirstException extends Exception {
}

class SecondException extends FirstException {
}

class ThirdException extends SecondException {
}

그리고 위의 세 종류 예외가 모두 발생 가능한 영역에 다음과 같이 try ~ catch 문을 구성하였다고 가정합니다.

 try {
            ...
        } catch (FirstException e) {

        } catch (SecondException e) {

        } catch (ThirdException e) {
            
        }

예외처리의 내용만 놓고 보면 문제가 없어보이지만 컴파일 오류가 발생합니다.

그리고 그 오류의 내용은 다음과 같습니다.

"두 번째, 세 번째 catch 구문은 실행될 일이 절대 없습니다."

그 이유는 예외 클래스 Second, Third 가 직접 혹은 간접적으로 FirstException 을 상속하고 있기 때문입니다.

그래서 catch (FirstException e) {} 구문에서 모든 예외 인스턴스를 처리할 수 있기 때문에 컴파일 오류가 발생합니다.

따라서 위와 같이 catch 문을 구성하고자 한다면 다음과 같이 순서를 변경해야 합니다.

try {
            ...
        } catch (ThirdException e) {

        } catch (SecondException e) {

        } catch (FirstException e) {
            
        }

finally 구문

try ~ catch 문은 하나의 문장이므로 try 구문 홀로 존재할 수 없습니다.

반드시 catch 구문이 하나 이상 등장해야 합니다.

그런데 try 에 이어서 다음과 같이 finally 구문을 둘 수도 있습니다.

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#5 추상 클래스 (Abstract Class)  (0) 2022.08.24
#8 Object 클래스  (0) 2022.08.23
#3 인터페이스  (0) 2022.08.22
#1 자바 개발 핵심 원칙  (0) 2022.07.11
[Java] 컴파일(Compile)의 이해  (0) 2020.11.04
반응형

인터페이스의 특징

1. class 대신 interface 선언이 붙어있고, 메소드는 구현없이 선언부만 존재합니다.

인터페이스는 다음과 같이 생겼습니다.

기본 골격은 클래스와 동일합니다.

이렇게 메소드의 구현부 없이 선언부만 존재하는 메소드를 가리켜 '추상 메소드(Abstract Methods)' 라 합니다.

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

 

2. 인터페이스를 대상으로는 인스턴스 생성이 불가능합니다.

따라서 아래의 코드처럼 다른 클래스에 의해 다음과 같이 상속이 될 뿐입니다.

이 때 키워드 implements 가 사용됩니다.

package interfaceAndAbstractClass;

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

class Printer implements Printable {

    @Override
    public void print(String doc) {
        System.out.println(doc);
    }
}

public class PrintableInterface {
    public static void main(String[] args) {
        Printable printable = new Printer();
        printable.print("abc");
    }
}

다만 아래의 코드와 같이 익명클래스를 구현할 수 있습니다.

익명 클래스의 형태로 정의하면 아래의 코드처럼 클래스의 정의와 인스턴스의 생성을 하나로 묶을 수 있습니다.

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

public class PrintableInterface {
    public static void main(String[] args) {
        Printable printable = new Printable() {
            @Override
            public void print(String doc) {
                System.out.println(doc);
            }
        };
        printable.print("abc");
    }
}

그리고 이 익명클래스는 람다표현식을 이용해 아래와 같이 간소화 될 수 있습니다.

이 때 람다표현식과 익명 클래스는 분명히 다릅니다. (내부적인 동작 원리가 다릅니다.)

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

public class PrintableInterface {
    public static void main(String[] args) {
        Printable printable = System.out::println;
        printable.print("abc");
    }
}

 

3. 한 클래스는 둘 이상의 인터페이스를 동시에 구현할 수 있습니다.

4. 상속과 구현은 동시에 가능합니다.

따라서 Robot이라는 클래스가 Machine 클래스를 상속하면서

인터페이스 Movable, Runnable 을 구현한다면 다음과 같이 정의할 수 있습니다.

class Robot extends Machine implements Movable, Runnable {}

 

5. 인터페이스의 형을 대상으로 참조변수의 선언이 가능합니다.

6. 인터페이스의 추상 메소드와 이를 구현하는 메소드 사이에 오버라이딩 관계가 성립합니다.

따라서 어노테이션 @Override 선언이 가능합니다.


 

인터페이스의 본질적 의미

인터페이스의 사전적 의미는 '연결점' 또는 '접점' 으로 둘 사이를 연결하는 매개체를 의미합니다.

이와 관련하여 간단한 예를 들면

 


인터페이스 내의 메소드와 변수의 특징

  1. 인터페이스의 모든 메소드는 public이 선언된 것으로 간주합니다.
  2. 인터페이스의 추상 메소드는 abstract 예약어를 생략해도 됩니다. (추상 클래스는 추상 메소드 선언 시 abstract 예약어 생략 불가능)
  3. 인터페이스 내에 선언되는 변수는 선언과 동시에 초기화 해야 합니다.
  4. 인터페이스 내에 선언되는 변수는 모두 public, static, final 이 선언된 것으로 간주합니다.결론적으로 인터페이스 내의 변수는 상수입니다.따라서 상수는 대문자로 이름을 짓는 관례를 따라 인터페이스 내에 변수의 이름은 대문자로 작성합니다.

인터페이스의 문법 구성과 추상 클래스

인터페이스에는 다음 세 가지 종류의 메소드만 존재할 수 있습니다.

1. 추상 메소드

2. 디폴트 메소드

3. static 메소드

 

그리고 인터페이스 간 상속도 가능하며 인터페이스의 형(Type) 이름을 대상으로 instanceof 연산을 할 수도 있습니다.

즉 많은 특성이 클래스와 유사합니다.

 

1. 추상 메소드

2. 디폴트 메소드

3. static 메소드

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#1 자바 개발 핵심 원칙  (0) 2022.07.11
[Java] 컴파일(Compile)의 이해  (0) 2020.11.04
[JAVA] 구조 개념 및 명명 규칙  (0) 2020.11.03
반응형

자바 개발의 핵심적인 기본 원칙은 바로 명료성과 단순성이다.

이 두 가지는 무엇보다 중요하다.

컴포넌트는 정해진 동작이나 예측할 수 있는 동작만 수행해야 한다.

(컴포넌트란 개별 메서드부터 여러 패키지로 이뤄진 복잡한 프레임워크까지 재사용 가능한 모든 소프트웨어 요소를 뜻한다.)

컴포넌트는 가능한 작되, 그렇다고 너무 작아서는 안된다.

코드는 복사되는 게 아니라 재사용되어야 한다.

컴포넌트 사이의 의존성은 최소로 유지해야 한다.

오류는 만들어지자마자 가능한 한 빨리 잡아야 한다. (되도록 컴파일타임에 해결되어야 한다.)

 

이 규칙들이 100% 옳을 수는 없겠지만, 거의 모든 경우에 적용되는 최고의 모범 사례일 것이다.

이 규칙들을 생각 없이 맹종하진 말아야 하나, 어겨야 할 때는 합당한 이유가 있어야 한다.

-이펙티브 자바-

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
[Java] 컴파일(Compile)의 이해  (0) 2020.11.04
[JAVA] 구조 개념 및 명명 규칙  (0) 2020.11.03
반응형

컴퓨터 프로그램 만드는 일을 프로그래밍이라 하고,

프로그램 만드는 사람을 프로그래머라고 합니다.

 

프로그래밍이란 컴퓨터가 일을 하도록 명령을 하고 그 명령을 실행하게끔 하는 과정입니다.

이 때 우리는 컴퓨터에게 1부터 100까지 더하라고 명령하고 싶습니다.

그렇다고 우리말로 '1부터 100까지 더하라.' 고 쓸 수는 없습니다.

그래서 프로그래밍 언어를 사용하는 것입니다.

 

그리고 이렇게 만든 프로그램이 프로그래밍 언어 문법에 잘 맞는지 확인하고,

컴퓨터가 이해할 수 있는 언어로 번역해야 합니다.

이 작업이 컴파일(Compile)이며 이를 수행하는 프로그램이 컴파일러(Compiler)입니다.

 

쉽게 말하면 컴파일은 프로그래밍 언어를 기계어로 번역해주는 과정입니다.

 

추가로 빌드는 컴파일러가 소스 코드를 컴파일하여 실행 파일을 만드는 과정을 말합니다.

자바는 빌드 과정을 통해 .class 파일이 만들어집니다.

그러므로, 컴파일이 빌드의 과정 중에 하나인 것이지요.

 

자바(Java)는 프로그래밍 언어 중 하나로, 자바 개발 환경을 설치하면 컴파일러도 함께 설치됩니다.

 

 


 

자바는 다른 프로그래밍 언어와는 구분되는 강점을 가지고 있습니다.

플랫폼에 영향을 받지 않고 다양한 운영체제에서 사용할 수 있다는 점입니다.

 

예를 들어 윈도우 운영체제에서 C언어로 개발한 Test라는 프로그램이 있습니다.

이 프로그램을 컴파일하면 Test.exe가 만들어집니다.

Test.exe는 윈도우에서 실행되는 '실행파일'이라고 합니다.

이 실행파일은 윈도우에서는 작동되지만 리눅스 운영체제에서는 사용할 수 없습니다.

만약 이 파일을 리눅스 운영체제에서 실행하려면, 리눅스 환경에서 다시 컴파일해서 리눅스 운영체제에

맞는 실행파일을 새로 만들어야 합니다.

 

하지만 자바에서는 Example이라는 이름의 프로그램을 만들고 컴파일하면 Example.class 파일이 생성됩니다.

이 파일을 바이트 코드라고 하는데 완벽한 실행파일이 아닙니다.

다시 말하면 운영체제에 맞는 완벽한 기계어가 아닌 중간 기계어입니다.

이 바이트 코드를 실행하려면 운영체제에 맞는 자바 가상 머신(JVM: Java Virtual Machine)이 필요합니다.

운영체제에서 직접 실행하는 게 아니라 가상 머신에서 먼저 실행하고,

이 가상 머신이 운영체제에 맞는 완벽한 실행파일로 바꿔줍니다.

즉 .class 파일을 만들어 두면 운영체제에 맞는 가상 머신을 이용하여 어느 환경에서나 실행할 수 있습니다.

 

자바 가상 머신(JVM)은 자바 실행 환경(JRE) 설치 시 함께 설치됩니다.

 

 

프로그래밍 언어는 사람이 이해하기 쉬울수록 고급언어,
컴퓨터가 이해하기 쉬울수록 저급언어입니다.

고급언어에는 C언어, Java 등이 있습니다.
저급언어에는 기계어, 어셈블리어 등이 있습니다.

이러한 특성 때문에 초기 자바는 프로그램 실행 속도가 느리다는 평이 많았지만,

하드웨어가 발전하면서 자바 컴파일러의 방식이 JIT(Just In Time)방식으로 개선되어서

실행 속도에도 문제는 없습니다.

 

컴파일의 세 분류
- 원시 코드를 바로 기계어로 변환하는 정적 컴파일(Static Compilation)
- 바이트코드 등의 중간 코드를 기계어로 변환하는 AOT 컴파일(Ahead Of Time Compilation)
- 실행시 최초 한 번에 한해 컴파일을 거치는 JIT 컴파일(Just In Time Compilation)
->JIT 컴파일러는 실행 시점에 기계어 코드를 생성하는데, 
같은 코드가 반복되면 매번 기계어 코드를 새로 생성하지 않고 이전에 만든 기계어를 재사용합니다.
또한 JIT 컴파일러는 운영체제에 맞게 바이트 실행 코드로 한 번에 변환하여 실행하기 때문에
이전의 자바 해석기방식보다 성능이 10~20배 좋습니다.

 


 

이 게시물의 내용을 그림 하나로 요약해봤습니다.

이상입니다.

좋은 하루 보내세요.

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
#1 자바 개발 핵심 원칙  (0) 2022.07.11
[JAVA] 구조 개념 및 명명 규칙  (0) 2020.11.03

+ Recent posts