본 게시글은 유튜브 : 경제 TV 너무경 : 너무 쉬운 경제 윤성종 님의 유튜브 영상을 참고하였습니다. 개인적으로 정리하는 글임을 알립니다.
금융용어정리 - 출자전환
출자전환
출자전환은 간단하게 말해서 채권자가 주주가 되는 것이다.
채무자 입장에선 부채(대출)가 자본이 되는 것이다.
채권자가 주주가 된다? 채권자는 채무자에게 이자를 받고, 투자자는 주주에게 배당을 받는다. 이자와 배당중에 어느 것이 더 좋냐고 묻는다면, 돈을 더 많이 주는 것이 좋다고 말하겠지만 이자는 채권자에게 일정 기간동안 무조건 줘야 하지만, 배당은 회사가 어려우면 안 줄 수도 있다. 따라서 채권자가 주주가 되는 것은 채권자에게 불리한 상황이다
하지만 아래와 같은 경우에 울며 겨자먹기로 채권자는 출자전환을 할 수밖에 없다.
A주식회사의 자산은 100억중에 10억이 자본이고 90억이 부채이다.
이러한 재무 구조를 가진 기업은 회사를 운영하려면 돈을 어디서 또 빌려야 하는데 빌릴 곳이 없다.
꼼작 없이 망할 수밖에 없는 것이다. 이렇게 되면 채권자는 돈을 돌려받을 수 없게 된다.
이런 상황에서 채권자는 울며 겨자먹기로 출자전환을 해서 채권자에서 투자자로 바꾸는 것이다.
본 게시글은 혼자 공부하는 자바 (저자 : 신용권)의 책과 유튜브 영상을 참고하였고, 개인적으로 정리하는 글임을 알립니다.
※java version "17.0.5" 2022-10-18 LTS 기준 문법임
중첩 클래스
객체 지향 프로그래밍에서 클래스들은 서로 긴밀한 관계를 맺고 상호작용을 한다. 어떤 클래스는 여러 클래스와 관계를 맺지만 어떤 클래스는 특정 클래스와 관계를 맺는다. 클래스가 여러 클래스와 관계를 맺는 경우에는 독립적으로 선언하는 것이 좋으나, 특정 클래스와 관계를 맺을 경우에는 클래스 내부에 선언하는 것이 좋다.
중첩 클래스란 클래스 내부에 선언한 클래스를 말한다. 중첩 클래스를 사용하면 두 클래스의 멤버들을 서로 쉽게 접근할 수 있고, 외부에는 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점이 있다.
class Outter{
class Inner{ //중첩 클래스
}
}
중첩 클래스는 선언되는 위치에 따라서 두 가지로 분류된다.
멤버 클래스 : 클래스의 멤버로서 선언되는 중첩 클래스
로컬 클래스 : 생성자 또는 메소드 내부에서 선언되는 중첩 클래스
멤버 클래스는 인스턴스 멤버 클래스와 정적 멤버 클래스로 분류된다.
인스턴스 멤버 클래스
인스턴스 멤버 클래스는 바깥 클래스의 객체가 생성되어야 인스턴스 멤버 클래스 객체의 멤버를 사용할 수 있다.
->바깥 클래스의 객체가 생성되어야 안쪽 클래스 객체의 멤버를 사용할 수 있다.
class A {
A() { System.out.println("A 객체가 생성됨"); }
public class B { //인스턴스 멤버 클래스
B() { System.out.println("B 객체가 생성됨"); }
int field1; //인스턴스 필드
static int field2;
void method1() {System.out.println("B method1");}
static void method2() {System.out.println("B method2");}
}
}
public class Test {
public static void main(String[] args) {
A a = new A();
//인스턴스 멤버 클래스 객체 생성
System.out.println("인스턴스 멤버 클래스 -----------");
A.B b = a.new B();
b.field1 = 3;
System.out.println("b.field1 : " + b.field1);
b.field2 = 4;
System.out.println("b.field2 : " + b.field2);
b.method1();
b.method2();
}
}
/*
A 객체가 생성됨
인스턴스 멤버 클래스 -----------
B 객체가 생성됨
b.field1 : 3
b.field2 : 4
B method1
B method2
*/
바깥 클래스의 객체를 생성하지 않으면 오류가 발생하는 것을 볼 수 있다.
public class A {
//인스턴스 멤버 클래스
class B {
//인스턴스 필드
int field1 = 1;
//정적 필드(Java 17부터 허용)
static int field2 = 2;
//생성자
B() {
System.out.println("B-생성자 실행");
}
//인스턴스 메소드
void method1() {
System.out.println("B-method1 실행");
}
//정적 메소드(Java 17부터 허용)
static void method2() {
System.out.println("B-method2 실행");
}
}
//인스턴스 메소드
void useB() {
//B 객체 생성 및 인스턴스 필드 및 메소드 사용
B b = new B();
System.out.println(b.field1);
b.method1();
//B 클래스의 정적 필드 및 메소드 사용
System.out.println(B.field2);
B.method2();
}
}
public class AExample {
public static void main(String[] args) {
//A 객체 생성
A a = new A();
//A 인스턴스 메소드 호출
a.useB();
}
}
/*
B-생성자 실행
1
B-method1 실행
2
B-method2 실행
*/
정적 멤버 클래스
정적 멤버 클래스는 바깥 클래스의 객체가 생성되지 않아도 정적 멤버 클래스 객체의 멤버를 사용할 수 있다.
->바깥 클래스의 객체가 생성되지 않아도 안쪽 클래스 객체의 멤버를 사용할 수 있다.
class A {
A() { System.out.println("A 객체가 생성됨"); }
static class C { //정적 멤버 클래스
C() { System.out.println("C 객체가 생성됨"); }
int field1;
static int field2;
void method1() {System.out.println("C method1");}
static void method2() {System.out.println("C method2");}
}
}
public class Test {
public static void main(String[] args) {
A a = new A();
//정적 멤버 클래스 객체 생성
System.out.println("정적 멤버 클래스 -----------");
A.C c = new A.C();
c.field1 = 3;
System.out.println("c.field1 : " + c.field1);
c.field2 = 4;
System.out.println("c.field2 : " + c.field2);
c.method1();
c.method2();
A.C.field2 = 5;
System.out.println("A.C.field2 : " + A.C.field2);
A.C.method2();
}
}
/*
A 객체가 생성됨
정적 멤버 클래스 -----------
C 객체가 생성됨
c.field1 : 3
c.field2 : 4
C method1
C method2
A.C.field2 : 5
C method2
*/
바깥 클래스의 객체를 생성하지 않아도 정적 멤버 클래스 객체의 멤버를 사용할 수 있다.
public class A {
//인스턴스 멤버 클래스
static class B {}
//인스턴스 필드 값으로 B 객체 대입
B field1 = new B();
//정적 필드 값으로 B 객체 대입
static B field2 = new B();
//생성자
A() {
B b = new B();
}
//인스턴스 메소드
void method1() {
B b = new B();
}
//정적 메소드
static void method2() {
B b = new B();
}
}
public class AExample {
public static void main(String[] args) {
//B 객체 생성
A.B b = new A.B();
}
}
로컬 클래스
메소드 내에 선언된 클래스를 로컬 클래스라고 하며, 로컬 클래스는 접근 제한자(public, private) 및 static을 붙일 수 없다.
로컬 클래스는 메소드 내부에만 사용되므로 접근을 제한할 필요가 없기 때문이다.
로컬 클래스는 바깥 클래스의 객체가 생성되어야 로컬 클래스 객체의 멤버를 사용할 수 있다. 또한 메소드가 종료되면 객체가 소멸되므로 객체 멤버를 사용할 수 없다. 로컬 클래스는 메소드가 실행될 때 메소드 내에서 객체를 생성하고 사용해야 한다.
->바깥 클래스의 객체가 생성되어야 안쪽 클래스 객체의 멤버를 사용할 수 있다. 메소드가 종료되면 객체가 소멸된다.
class A {
A() { System.out.println("A 객체가 생성됨"); }
void method() {
class D { //로컬 클래스
D() { System.out.println("D 객체가 생성됨"); }
int field1;
static int field2;
void method1() {System.out.println("D method1");}
static void method2() {System.out.println("D method2");}
}
D d = new D();
d.field1 = 3;
System.out.println("d.field1 : " + d.field1);
d.field2 = 4;
System.out.println("d.field2 : " + d.field2);
d.method1();
d.method2();
//A.D.field2 = 5; 불가능
}
}
package TestPackage;
public class Test {
public static void main(String[] args) {
A a = new A();
//로컬 클래스 객체 생성을 위한 메소드 호출
System.out.println("로컬 클래스 -----------");
a.method();
}
}
/*
A 객체가 생성됨
로컬 클래스 -----------
D 객체가 생성됨
d.field1 : 3
d.field2 : 4
D method1
D method2
*/
바깥 클래스의 객체를 생성하지 않으면 오류가 발생하는 것을 볼 수 있다.
중첩 클래스의 접근 제한
멤버 클래스 내부에서 바깥 클래스의 필드와 메소드에 접근할 때는 제한이 따른다. 또한 메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때도 제한이 따른다.
바깥 필드와 메소드에서 사용 제한
바깥 클래스에서 인스턴스 멤버 클래스를 사용할 때 제한이 있다.
class A {
class B {} //인스턴스 멤버 클래스
static class C {} //정적 멤버 클래스
//인스턴스 필드
B field1 = new B();
C field2 = new C();
void method1() { //인스턴스 메소드
B var1 = new B();
C var2 = new C();
}
//정적 필드 초기화
//static B field3 = new B(); 컴파일 에러
static C field4 = new C();
static void method2() { //정적 메소드
//B var1 = new B(); 컴파일 에러
C var2 = new C();
}
}
인스턴스 멤버 클래스(B)는 인스턴스 필드의 초기값이나 인스턴스 메소드에서 객체를 생성할 수 있으나, 정적필드의 초기값이나 정적 메소드에서는 객체를 생성할 수 없다.
반면 정적 멤버 클래스(C)는 모든 필드의 초기값이나 모든 메소드에서 객체를 생성할 수 있다.
static B field3 = new B();가 컴파일 오류가 발생하는 이유는 B객체는 A객체가 생성되어야 생성되는데, static으로 선언하려고 하니까 나는 오류이다.
B var1 = new B();가 컴파일 오류가 발생하는 이유는 B객체는 A객체가 생성되어야 생성되는데 static으로 선언된 메소드가 B와 관련된 행동을 하려고 하기 때문이다.
static(정적) static(정적) 멤버는 클래스의 객체가 생성되지 않아도 접근할 수 있는 멤버를 뜻한다.
멤버 클래스에서 사용 제한
멤버 클래스가 인스턴스 또는 정적으로 선언됨에 따라 멤버 클래스 내부에서 바깥 클래스의 필드와 메소드에 접근할 때에도 제한이 따른다.
class A {
int field1;
void method1() { }
static int field2;
static void method2() { }
class B {
void method() {
field1 = 10;
method1();
field2 = 10;
method2();
}
}
static class C {
void method() {
//field1 = 10;
//method1();
field2 = 10;
method2();
}
}
}
인스턴스 멤버 클래스(B) 안에서는 바깥 클래스의 모든 필드와 모든 메소드에 접근할 수 있지만, 정적 멤버 클래스(C) 안에서는 바깥 클래스의 정적 필드와 정적 메소드에만 접근할 수 있고 인스턴스 필드와 인스턴스 메소드에는 접근할 수 없다.
C에서 field1 = 10; 을 사용할 수 없는 이유는 C는 정적 클래스인데 field1 = 10;은 A객체가 생성되어야 하기 때문이다
C에서 method1(); 을 사용할 수 없는 이유는 C는 정적 클래스인데 method1();은 A객체가 생성되어야 하기 때문이다.
로컬 클래스에서 사용 제한
메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때 제한이 있다.(로컬 변수를 로컬 클래스에서 사용하지 않으면 해당 X)
로컬 클래스의 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만, 메소드가 종료되어도 계속 실행 상태로 존재할 수 있다. 예를 들어 로컬 스레드 객체를 사용할 때이다. 메소드를 실행하는 스레드와 다르므로 메소드가 종료된 후에도 로컬 스레드 객체는 실행 상태로 존재할 수 있다.
자바는 이 문제를 해결하기 위해 컴파일 시 로컬 클래스에서 사용하는 매개 변수나 로컬 변수의 값을 로컬 클래스 내부에 복사해두고 사용한다. 그리고 매개 변수나 로컬 변수가 수정되어 값이 변경되면 로컬 클래스에 복사해둔 값과 달라지므로 로컬 변수를 final로 선언할 것을 요구한다.
하지만 자바 8부터는 final 키워드 없이 선언된 매개 변수와 로컬 변수를 사용해도 컴파일 에러가 발생하지 않는다. final선언을 하지 않아도 값이 수정될 수 없도록 final의 특성을 부여하기 때문이다.
로컬 변수를 로컬 클래스에서 사용할 경우, 명시적으로 final 키워드를 붙이지 않아도 되지만 로컬 변수에 final 키워드를 추가해서 final 변수임을 명확히 할 수도 있다.
public class A {
//메소드
public void method1(int arg) {
//로컬 변수
int var = 1;
//로컬 클래스
class B {
//메소드
void method2() {
//로컬 변수를 로컬 클래스에서 사용하는 경우
System.out.println("arg: " + arg); //(o)
System.out.println("var: " + var); //(o)
//로컬 변수 수정
//로컬 변수를 로컬 클래스에서 사용하기 때문에 final 특성을 가짐
//arg = 2; //(x)
//var = 2; //(x)
}
}
//로컬 객체 생성
B b = new B();
//로컬 객체 메소드 호출
b.method2();
//로컬 변수 수정
//arg = 3; //(x)
//var = 3; //(x)
}
}
중첩 클래스에서 바깥 클래스 참조 얻기
클래스에서 this는 객체 자신의 참조이다.내부 클래스에서 this 키워드를 사용하면 바깥 클래스의 객체 참조가 아니라, 내부 클래스 객체 참조가 된다.따라서 내부 클래스에서 바깥 클래스의 객체 참조를 얻고 싶다면 '바깥클래스이름.this'를 사용하면 된다.
아래의 예제와 같이 내부 클래스(Nested)에서 바깥 클래스(Outter)의 멤버를 사용하려면 Outter.this.field 와 Outter.this.method() 처럼 사용하면 된다.
public class Outter {
String field = "Outter-field";
void method() {
System.out.println("Outter-method");
}
class Nested {
String field = "Nested-field";
void method() {
System.out.println("Nested-method");
}
void print() {
System.out.println(this.field); //내부 객체 참조
this.method(); //내부 객체 참조
System.out.println(Outter.this.field); // 바깥 객체 참조
Outter.this.method(); //바깥 객체 참조
}
}
}
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
Outter.Nested nested = outter.new Nested();
nested.print();
}
}
/*
Nested-field
Nested-method
Outter-field
Outter-method
*/
본 게시글은 혼자 공부하는 자바 (저자 : 신용권)의 책과 유튜브 영상을 참고하였고, 개인적으로 정리하는 글임을 알립니다.
다형성을 구현하기 위해서는 메소드 재정의와 타입 변환이 필요하다. 인터페이스 역시 이 두 가지 기능이 제공되므로 상속과 더불어 다형성을 구현하는 데 많이 사용된다. 상속은 같은 종류의 하위 클래스를 만드는 기술이고, 인터페이스는 사용 방법이 동일한 클래스를 만드는 기술이라는 개념상 차이가 있지만 둘 다 다형성을 구현하는 방법은 비슷하다.
프로그램 소스 코드는 변함이 없는데, 구현 객체(클래스)를 교체함으로써 프로그램의 실행결과가 다양해지는 것을 인터페이스의 다형성이다.
위 그림에서 개발코드에서는 인터페이스를 통해서 객체 1을 사용하고 있었는데, 객체 1에 문제가 있음을 확인하면 객체 1의 사용을 중지하고 개발 코드에서 간단한 수정만 해서 객체 2를 사용하면 된다.
자동 타입 변환
구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환에 해당한다.
인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 타입 변환할 수 있다.
D와 E는 구현 클래스가 아님에도 타입 변환이 가능한 이유는 각각의 부모 클래스가 인터페이스의 구현 클래스이기 때문에, 각각의 부모 클래스로부터 상속을 받았기 때문이다.
B b = new B();
C C = new C();
D d = new D();
E e = new E();
A a1 = b; //가능
A a2 = c; //가능
A a3 = d; //가능
A a4 = e; //가능
필드의 다형성
Tire.java(인터페이스)
package TestPackage;
public interface Tire {
public void roll();
}
HankookTire.java(구현 클래스)
package TestPackage;
public class HankookTire implements Tire{
@Override
public void roll() {
System.out.println("한국 타이어가 회전중");
}
}
KumhoTire.java(구현 클래스)
package TestPackage;
public class KumhoTire implements Tire{
@Override
public void roll() {
System.out.println("금호 타이어가 회전중");
}
}
Car.java
package TestPackage;
public class Car {
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();
void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
}
Test.java
package TestPackage;
public class Test {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
System.out.println("----------------------------------");
myCar.frontLeftTire = new KumhoTire(); //타이어 인터페이스의 객체 교체
myCar.frontRightTire = new KumhoTire();
myCar.run();
}
}
/*
한국 타이어가 회전중
한국 타이어가 회전중
한국 타이어가 회전중
한국 타이어가 회전중
----------------------------------
금호 타이어가 회전중
금호 타이어가 회전중
한국 타이어가 회전중
한국 타이어가 회전중
*/
Car의 run()메소드를 수정하지 않아도 다양한 roll()메소드의 실행결과를 얻을 수 있게 되었다. 이것이 필드의 다형성이다.
매개 변수의 다형성
자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
매개값을 다양화하기 위해서 상속에서는 매개 변수를 부모 타입으로 선언하고 호출할 때에는 자식 객체를 대입한다.
매개 변수의 타입이 인터페이스일 경우 어떠한 구현 객체도 매개값으로 사용할 수 있고, 어떤 구현 객체가 제공되느냐에 따라 메소드의 실행결과가 다양해진다. 이것이 인터페이스 매개 변수의 다형성이다.
Driver.java
package TestPackage;
public class Driver {
public void drive(Vehicle vehicle)
{
vehicle.run();
}
}
Vehicle.java(인터페이스)
package TestPackage;
public interface Vehicle {
public void run();
}
Bus.java(구현 클래스)
package TestPackage;
public class Bus implements Vehicle{
@Override
public void run() {
System.out.println("버스 주행중");
}
}
Taxi.java(구현 클래스)
package TestPackage;
public class Taxi implements Vehicle{
@Override
public void run() {
System.out.println("택시가 주행중");
}
}
Test.java
package TestPackage;
public class Test {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus); //자동 타입 변환
driver.drive(taxi); //자동 타입 변환
}
}
/*
버스 주행중
택시가 주행중
*/
강제 타입 변환
구현 객체가 인터페이스 타입으로 자동 타입 변환하면, 인터페이스에 선언된 메소드만 사용 가능하다는 제약 사항이 따른다.
하지만 경우에 따라서는 구현 클래스에 선언된 필드와 메소드를 사용해야 할 경우도 발생한다. 이때 강제 타입 변환을 해서 다시 구현 클래스 타입으로 변환한 다음, 구현 클래스의 멤버를 사용할 수 있다.
Vehicle.java(인터페이스)
package TestPackage;
public interface Vehicle {
public void run();
}
Bus.java(구현 클래스)
package TestPackage;
public class Bus implements Vehicle{
@Override
public void run() {
System.out.println("버스 주행중");
}
public void checkFare() {
System.out.println("승차요금 확인");
}
}
Test.java
package TestPackage;
public class Test {
public static void main(String[] args) {
Vehicle vehicle = new Bus();
vehicle.run();
//vehicle.checkFare(); 컴파일 에러
Bus bus = (Bus)vehicle; // 강제 타입 변환
bus.run();
bus.checkFare();
}
}
/*
버스 주행중
버스 주행중
승차요금 확인
*/
객체 타입 확인
강제 타입 변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다. 그러나 어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서 무작정 강제 타입 변환할 경우 예외(Exception)이 발생한다.
이러한 예외에 대처하기 위해서 instanceof 연산자를 이용하면 된다.
package TestPackage;
public interface Vehicle {
public void run();
}
package TestPackage;
public class Bus implements Vehicle{
@Override
public void run() {
System.out.println("버스 주행중");
}
public void checkFare() {
System.out.println("승차요금 확인");
}
}
package TestPackage;
public class Taxi implements Vehicle{
@Override
public void run() {
System.out.println("택시 주행중");
}
}
package TestPackage;
public class Driver {
public void drive(Vehicle vehicle) {
if(vehicle instanceof Bus) { //vehicle 매개 변수가 참조하는 객체가 Bus 인지 확인
Bus bus = (Bus) vehicle; //조건문이 참일경우 강제 타입변환
bus.checkFare();
}
vehicle.run();
}
}
package TestPackage;
public class Test {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus);
driver.drive(taxi);
}
}
/*
승차요금 확인
버스 주행중
택시 주행중
*/
인터페이스 상속
인터페이스도 다른 인터페이스를 상속할 수 있다. 인터페이스는 클래스와는 달리 다중상속을 허용한다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2{}
하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대해 실체 메소드를 가지고 있어야 한다.
그렇기 때문에 구현 클래스로부터 객체를 생성한 후에 하위 및 상위 인터페이스 타입으로 변환이 가능하다.
하위 인터페이스로 타입 변환이 되면 상위 및 하위 인터페이스에 선언된 모든 메소드를 사용할 수 있으나, 상위 인터페이스로 타입 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능하고 하위 인터페이스에 선언된 메소드는 사용할 수 없다
.
InterfaceC 인터페이스 변수는 methodA(), methodB(), methodC()를 모두 호출할 수 있지만, InterfaceA와 InterfaceB 변수는 각각 methodA(), methodB()만 호출할 수 있다.
package TestPackage;
public interface InterfaceA {
public void methodA();
}
package TestPackage;
public interface InterfaceB {
public void methodB();
}
package TestPackage;
public interface InterfaceC extends InterfaceA, InterfaceB {
public void methodC();
}
package TestPackage;
public class ImplementationC implements InterfaceC{
public void methodA() {
System.out.println("ImplementationC-methodA() 실행");
}
public void methodB() {
System.out.println("ImplementationC-methodB() 실행");
}
public void methodC() {
System.out.println("ImplementationC-methodC() 실행");
}
}
package TestPackage;
public class Test {
public static void main(String[] args) {
ImplementationC impl = new ImplementationC();
InterfaceA ia = impl; // methodA()만 호출 가능
ia.methodA();
System.out.println();
InterfaceB ib = impl; // methodB()만 호출 가능
ib.methodB();
System.out.println();
InterfaceC ic = impl; // methodA(), methodB(),methodC() 모두 호출 가능
ic.methodA();
ic.methodB();
ic.methodC();
}
}
/*
ImplementationC-methodA() 실행
ImplementationC-methodB() 실행
ImplementationC-methodA() 실행
ImplementationC-methodB() 실행
ImplementationC-methodC() 실행
*/
본 게시글은 혼자 공부하는 자바 (저자 : 신용권)의 책과 유튜브 영상을 참고하였고, 개인적으로 정리하는 글임을 알립니다.
자바에서 인터페이스는 객체의 사용 방법을 정의한 타입이다. 인터페이스를 통해 다양한 객체를 동일한 사용 방법으로 이용할 수 있다.
인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다. 개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출시킨다. 그렇기 때문에 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 된다.
개발 코드가 직접 객체의 메소드를 호출하면 간단한데 왜 중간에 인터페이스를 두는지 의문점이 생긴다. 그 이유는 개발 코드를 수정하지 않고 사용하는 객체를 변경할 수 있도록 하기 위해서이다. 인터페이스는 하나의 객체가 아니라 여러 객체들과 사용이 가능하므로 어떤 객체를 사용하느냐에 따라서 실행 내용과 리턴값이 다를 수 있다. 따라서 개발 코드 측면에서는 코드 변경 없이 실행 내용과 리턴값을 다양화할 수 있다는 장점을 가지게 된다.
인터페이스는 *.java 형태의 소스 파일로 작성되고 컴파일러를 통해 *.class 형태로 컴파일되기 때문에 물리적 형태는 클래스와 동일하다.
인터페이스 선언
패키지에 클래스를 추가하는 것과 같이 패키지에 인터페이스를 추가해준다.
RemoteControl이라는 이름을 가진 인터페이스를 선언하는 방법은 아래와 같다.
public interface RemoteControl {
}
클래스는 필드, 생성자, 메소드를 멤버로 가질 수 있지만, 인터페이스는 상수 필드와 추상 메소드만을 멤버로 가질 수 있다.
인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 없다.
인터페이스는 객체 사용 방법을 정의한 것이므로 실행 시 데이터를 저장할 수 있는 인스턴스 또는 정적 필드를 선언할 수 없다. 그러나 상수 필드는 선언이 가능하다.
인터페이스에 선언된 필드는 모두 상수의 특징을 가지므로 public static final을 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.
인터페이스 상수는 반드시 선언과 동시에 초기값을 지정해야한다.
상수 이름은 대문자로 작성하되, 서로 다른 단어로 구성되어 있을 경우에는 언더바로 연결하는 것이 관례이다.
인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행된다. 그렇기 때문에 인터페이스의 메소드는 추상 메소드로 선언한다. 인터페이스에 선언된 메소드는 모두 추상 메소드의 특징을 가지므로 public abstract를 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.
public interface RemoteControl {
//상수(public static final 생략 가능)
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드(public abstract 생략 가능)
void turnOn();
void turnOff();
void setVolume(int volume);
}
인터페이스 구현
개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다. 객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 한다. 이러한 객체를 인터페이스의 구현 객체라고 하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 한다.
구현 클래스는 인터페이스 타입으로 사용할 수 있음을 알려주기 위해 클래스 선언부에 implements 키워드를 추가하고 인터페이스 이름을 명시해야 한다. 그리고 인터페이스에 선언된 추상 메소드의 실체 메소드를 선언해야 한다.
인터페이스 RemoteControl의 구현 클래스 이름이 Television이면 아래와 같이 선언한다.
public class Television implements RemoteControl{
}
아래의 코드들은 리모콘을 인터페이스로, 구현 객체를 TV와 스피커로 구현한 것이다.
RemoteControl.java(인터페이스)
package TestPackage;
public interface RemoteControl {
//상수(public static final 생략 가능)
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드(public abstract 생략 가능)
void turnOn();
void turnOff();
void setVolume(int volume);
}
Television.java(구현 클래스)
package TestPackage;
public class Television implements RemoteControl{
private int volume;
//turnOn() 추상 메소드의 실체 메소드
public void turnOn() {
System.out.println("TV ON");
}
//turnOff() 추상 메소드의 실체 메소드
public void turnOff() {
System.out.println("TV OFF");
}
//setVolume() 추상 메소드의 실체 메소드
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 TV 볼륨: " + this.volume);
}
}
package TestPackage;
public class Test {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn();
rc.setVolume(5);
rc.turnOff();
rc = new Speaker();
rc.turnOn();
rc.setVolume(5);
rc.turnOff();
}
}
/*
TV ON
현재 TV 볼륨: 5
TV OFF
Speaker ON
현재 Speaker 볼륨: 5
Speaker OFF
*/
보는 것과 같이 인터페이스 변수에 어떤 객체가 들어가느냐에 따라서 실행결과가 달라짐을 볼 수 있다.
public을 생략하면 컴파일 에러 인터페이스의 모든 멤버는 기본적으로 public 접근 제한을 갖기 때문에 구현 클래스에서는 public보다 더 강한 접근제한으로 작성할 수 없다. 따라서 public을 생략하면 default 접근제한을 갖기 때문에 컴파일 에러가 뜬다.
다중 인터페이스
인터페이스 A와 인터페이스 B가 객체의 메소드를 호출할 수 있으려면 객체는 이 두 인터페이스를 모두 구현해야 한다.
다중 인터페이스를 구현할 경우, 구현 클래스는 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야 한다.
아래 코드는 리모콘 인터페이스와 인터넷 검색을 가능하게 해주는 인터페이스, 방금 나열한 인터페이스들을 구현한 스마트 TV 객체 코드이다.
Seachable.java
package TestPackage;
public interface Searchable {
void search(String url);
}
RemoteControl.java
package TestPackage;
public interface RemoteControl {
//상수(public static final 생략 가능)
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드(public abstract 생략 가능)
void turnOn();
void turnOff();
void setVolume(int volume);
}
SmartTelevision.java
package TestPackage;
public class SmartTelevision implements RemoteControl, Searchable{
private int volume;
//RemoteControl 인터페이스 메소드
public void turnOn() {
System.out.println("TV ON");
}
public void turnOff() {
System.out.println("TV OFF");
}
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 TV 볼륨: " + this.volume);
}
//Searchable 인터페이스 메소드
public void search(String url) {
System.out.println(url + "을 검색");
}
}
Test.java
package TestPackage;
public class Test {
public static void main(String[] args) {
SmartTelevision tv = new SmartTelevision();
RemoteControl rc = tv;
Searchable searchable = tv;
tv.turnOn();
tv.turnOff();
tv.setVolume(5);
tv.search("google.com");
}
}
/*
TV ON
TV OFF
현재 TV 볼륨: 5
google.com을 검색
*/
tv객체는 RemoteControl, Searchable 인터페이스에서 선언한 추상 메소드를 모두 실행할 수 있는 것을 볼 수 있다.
인터페이스 사용
인터페이스가 필드 타입으로 사용될 경우, 필드에 구현 객체를 대입할 수 있다.
인터페이스가 생성자의 매개 변수 타입으로 사용될 경우, new 연산자로 객체를 생성할 때 구현 객체를 생성자의 매개값으로 대입할 수 있다.
인터페이스가 로컬 변수 타입으로 사용될 경우, 변수에 구현 객체를 대입할 수 있다.
인터페이스가 메소드의 매개 변수 타입으로 사용될 경우, 메소드 호출 시 구현 객체를 매개값으로 대입할 수 있다.
RemoteControl.java
package TestPackage;
public interface RemoteControl {
//상수(public static final 생략 가능)
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드(public abstract 생략 가능)
void turnOn();
void turnOff();
void setVolume(int volume);
}
Television.java
package TestPackage;
public class Television implements RemoteControl{
private int volume;
public void turnOn() {
System.out.println("TV ON");
}
public void turnOff() {
System.out.println("TV OFF");
}
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 TV 볼륨: " + this.volume);
}
}
Speaker.java
package TestPackage;
public class Speaker implements RemoteControl{
private int volume;
public void turnOn() {
System.out.println("Speaker ON");
}
public void turnOff() {
System.out.println("Speaker OFF");
}
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 Speaker 볼륨: " + this.volume);
}
}
package TestPackage;
public class Test {
public static void main(String[] args) {
System.out.println("1)----------------");
MyClass myClass1 = new MyClass();
myClass1.rc.turnOn();
myClass1.rc.setVolume(5);
System.out.println("2)----------------");
MyClass myClass2 = new MyClass(new Speaker());
System.out.println("3)----------------");
MyClass myClass3 = new MyClass();
myClass3.methodA();
System.out.println("4)----------------");
MyClass myClass4 = new MyClass();
myClass4.methodB(new Television());
}
}
/*
1)----------------
TV ON
현재 TV 볼륨: 5
2)----------------
Speaker ON
현재 Speaker 볼륨: 5
3)----------------
Speaker ON
현재 Speaker 볼륨: 5
4)----------------
TV ON
현재 TV 볼륨: 5
*/
본 게시글은 유튜브 : 경제 TV 너무경 : 너무 쉬운 경제 윤성종 님의 유튜브 영상을 참고하였습니다. 개인적으로 정리하는 글임을 알립니다.
금융용어정리 - 재무상태표, 포괄손익계산서
자산, 자본, 부채 복습
자산은 자본과 부채를 더한 것이다.
자산, 자본, 부채 자본 + 부채 = 자산 쉽게 말하면 자본은 내가 가지고 있는 돈이고, 부채는 남에게 빌린 돈이다. 내가 가지고 있는 돈과 남에게 빌린돈을 합친 것이 자산이다.
자본은 자본금과 이익잉여금을 더한 것이다.(물론 자본잉여금 등등 여러가지가 더 있다.)
예를 들어 1억 원을 가지고 회사를 창업해서 1년 동안 5000만 원의 순이익을 얻었다면 1억 원은 자본금이고, 5000만원은 잉여이익금이다.
재무제표
재무제표는 현재 기업에 투자하고있는 투자자들, 아직 기업에 투자를 하지 않은 예비 투자자들에게 기업의 재무상태와 경영성과를 보여주는 보고서의 묶음이다.
재무제표에는 재무상태표(대차대조표), 포괄손익계산서, 현금흐름표, 자본변동표 등이 있다.
재무제표의 사전적 정의 기업의 재무상태와 경영성과를 회계기준에 따라 보여주는 보고서의 묶음을 말하며, 재무상태표, 포괄손익계산서 등이 있다.
기업은 보유한 경제적 자원을 활용하여 기업을 운영하고 그 결과에 대한 보고서를 주기적으로 주주, 채권자, 종업원 등 여러 이해관계자에게 제공한다. 이 일련의 과정을 재무보고(FinancialReporting)라 하고, 이때 제시되는 보고서의 묶음을 재무제표라 한다. [네이버 지식백과] 재무제표 [Financial Statement] (두산백과 두피디아, 두산백과)
재무상태표
일정시점의 재무상태(자산, 부채, 자본의 상태)를 표로 정리한 것을 말한다.
일정시점(예를 들어 1월 9일)의 상태를 보는 것으로 사물에 비유한다면 사진기와 같다.
재무상태표의 사전적 정의 특정시점 현재 기업이 보유하고 있는 자산(경제적 자원)과 부채(경제적 의무), 자본의 잔액에 대한 정보를 보고하는 보고서.
재무상태표는 일정한 시점에 현재 기업이 보유하고 있는 재무 상태를 나타내는 회계보고서로 차변에 자산과 대변에 부채 및 자본으로 구성되어 있으며, 기업 활동에 필요한 자금을 어디서 얼마나 조달하여 투자했는지 등을 알 수 있게 해준다.재무상태표는 정보이용자들이 기업의 유동성, 재무적 탄력성, 기업의 수익성과 위험도 등을 평가하는 데 유용한 정보를 제공하는 기본적인 회계자료로 상법에서는 기업에 대하여 의무적으로 작성하도록 하고 있다.
재무상태표는 일정 시점 현재 기업이 보유하고 있는 경제적 자원인 자산과 경제적 의무인 부채, 그리고 자본에 대한 정보를 제공하는 재무보고서로서 정보이용자들이 기업의 유동성, 재무적 탄력성, 수익성과 위험 등을 평가하는 데 유용한 정보를 제공한다.
회계의 가장 기본적인 등식의 형식을 갖고 있으며, 총자산(자산총계)의 합계는 항상 총부채(부채총계)와 총자본(자본총계)의 합계액과 정확하게 일치한다. 재무상태표는 차변과 대변으로 구성되어 있으며 차변의 자산은 자금이 어떻게 사용되고 얼마나 남아 있는지를 보여주며 대변의 부채와 자본항목은 자금이 어떻게 조달되었는지를 알 수 있게 해준다. [네이버 지식백과] 재무상태표 [statement of financial position] (예스폼 서식사전, 2013.)
포괄손익계산서
일정기간의 재무활동(수익, 비용, 순이익)을 표로 정리한 것을 말한다.
일정기간(예를 들어 2월 1일 ~ 2월 28일)의 재무활동을 보는 것이므로 사물에 비유한다면 비디오 카메라와 같다.
포괄손익계산서의 사전적 정의 일정기간 동안 기업이 달성한 경영성과를 보여주기 위해 한국채택국제회계기준에서 요구하는 재무제표로, 총포괄손익(주주와의 자본거래를 제외한 모든 거래로 인해 발생한 자본의 변동)에 해당하는 당기순손익과 기타포괄손익 정보(총포괄손익에 포함되지만 당기손익이 아닌 수익과 비용)를 제공한다. [네이버 지식백과] 포괄손익계산서 [Statement of comprehensive income] (두산백과 두피디아, 두산백과)
본 게시글은 혼자 공부하는 자바 (저자 : 신용권)의 책과 유튜브 영상을 참고하였고, 개인적으로 정리하는 글임을 알립니다.
사전적 의미로 추상은 실체 간에 공통되는 특성을 추출한 것을 말한다. 예를 들어 새, 곤충, 물고기 등의 실체에서 공통되는 특성을 추출해보면 동물이라는 공통점이 있다.
여기서 동물은 구체적인 실체라기보다는 실체들의 공통되는 특성을 가지고 있는 추상적인 것이라고 볼 수 있다.
추상 클래스
객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면 이 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다. 추상 클래스와 실체 클래스는 상속의 관계를 가지고 있다. 추상 클래스가 부모, 실체 클래스가 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성을 물려받고, 추가적인 특성을 가질 수 있다.
여기서 특성이란 필드와 메소드를 말한다.
추상 클래스의 용도
공통된 필드와 메소드의 이름을 통일할 목적
실체 클래스를 설계하는 사람이 여러 사람일 경우, 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있다.
데이터와 기능이 모두 동일함에도 불구하고 이름이 다르다 보니, 객체마다 사용 방법이 달라진다. 이때, 추상 클래스를 상속함으로써 필드와 메소드 이름을 통일할 수 있다.
실체 클래스를 작성할 때 시간 절약
공통적인 필드와 메소드는 추상 클래스에 모두 선언해 두고, 다른 점만 실체 클래스에 선언하면 실체 클래스를 작성하는 데 시간을 절약할 수 있다.
예를 들어 추상 클래스인 전화 클래스가 있고 전화 클래스를 상속받은 실체 클래스인 휴대전화 클래스와 스마트폰 클래스가 있다고 하면 전화 클래스의 기능인 전화 송수신 기능을 선언하고 휴대전화 클래스와 스마트폰 클래스는 각각 자신만의 필드와 메소드를 작성하면 된다.
추상 클래스 선언
추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙이면 된다.
abstract를 붙이면 new 연산자를 이용해서 객체를 만들지 못하고, 상속을 통해 자식 클래스만 만들 수 있다.
추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메소드 선언을 할 수 있다. new 연산자로 직접 생성자를 호출할 수 없지만 자식 객체가 생성될 때 super를 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 한다.
Phone.java
package TestPackage;
public abstract class Phone {
//필드
public String owner;
//생성자
public Phone(String owner) {
this.owner = owner;
}
//메소드
public void turnOn() {
System.out.println("폰 전원 ON");
}
public void turnOff() {
System.out.println("폰 전원 OFF");
}
}
SmartPhone.java
package TestPackage;
public class SmartPhone extends Phone {
//생성자
public SmartPhone(String owner) {
super(owner);
}
//메소드
public void internetSearch() {
System.out.println("인터넷 검색");
}
}
Test.java
package TestPackage;
public class Test {
public static void main(String[] args) {
//Phone phone = new Phone(); (x)
SmartPhone smartPhone = new SmartPhone("홍길동");
smartPhone.turnOn();
smartPhone.internetSearch();
smartPhone.turnOff();
}
}
/*
폰 전원 ON
인터넷 검색
폰 전원 OFF
*/
추상 메소드
추상 클래스는 실체 클래스가 공통적으로 가져야 할 멤버를 정의해놓은 추상적인 클래스로, 실체 클래스의 멤버를 통일하는데 목적이 있다. 모든 실체들이 가지고 있는 메소드의 실행 내용이 동일하다면 추상 클래스에 메소드를 작성하는 것이 좋다.
하지만 메소드의 선언만 통일하고, 실행 내용은 실체 클래스마다 달라야 하는 경우가 있다. 예를 들어, 모든 동물은 소리를 내기 때문에 Animal 추상 클래스에서 sound()라는 메소드를 정의 했다고 가정하면, 어떤 소리를 내도록 해야하는데, 이것은 실체 클래스에서 직접 작성해야 될 부분이다. 왜냐하면 동물은 다양한 소리를 내므로 이것을 추상 클래스에서 통일적으로 작성할 수 없다. 그렇다고 해서 sound() 메소드를 실체 클래스에서 작성하도록 하면 sound() 메소드를 잊어버리고 작성하지 않을 경우 동물은 소리를 낸다는 것에 위배된다.
이런 경우를 위해서 추상 클래스는 추상 메소드를 선언할 수 있다.
추상 메소드의 강력한 기능중 하나는 추상 메소드가 선언되면 자식 클래스에서는 반드시 추상 메소드를 오버라이딩해서 실행내용을 작성해야한다. 오버라이딩하지 않으면 컴파일 에러가 발생한다.
Animal.java
package TestPackage;
public abstract class Animal {
public String kind;
public void breathe() {
System.out.println("숨쉬는중 ");
}
public abstract void sound(); //추상 메소드 선언
}
Cat.java
package TestPackage;
public class Cat extends Animal{
public Cat() {
this.kind = "포유류";
}
@Override
public void sound() { // 추상 메소드 오버라이딩
System.out.println("야옹");
}
}
Dog.java
package TestPackage;
public class Dog extends Animal{
public Dog() {
this.kind = "포유류";
}
@Override
public void sound() { //추상 메소드 오버라이딩
System.out.println("멍멍");
}
}
Test.java
package TestPackage;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.sound();
cat.sound();
System.out.println("-----");
//변수의 자동 타입 변환
Animal animal = null;
animal = new Dog();
animal.sound();
animal = new Cat();
animal.sound();
System.out.println("-----");
//메소드의 다형성
animalSound(new Dog());
animalSound(new Cat());
}
public static void animalSound(Animal animal) {
animal.sound();
}
}
/*
멍멍
야옹
-----
멍멍
야옹
-----
멍멍
야옹
*/
Animal 변수로 타입 변환해서 sound() 메소드를 호출했다. 자식은 부모 타입으로 자동 타입 변환이 될 수 있고, 메소드가 오버라이딩되어 있을 경우 재정의된 자식 메소드가 호출되는 다형성의 특징이 그대로 적용된다.
부모 타입의 매개 변수에 자식 객체를 대입해서 메소드의 다형성을 적용했다. 이것은 위의 내용과 같은 원리로 자식 객체가 부모 타입으로 자동 타입 변환되어 재정의된 sound()메소드가 호출된다.