본 포스팅은 개인적으로 메모한 내용입니다.
static 공간은 클래스별로 분류, static멤버는 해당 클래스 공간에 존재함.
스택에는 로컬변수(레퍼런스 주소를 가지는 변수도), 매개변수, 메소드 내용이 들어간다
스택에 존재하는 변수들을 스택변수라고 한다.
로컬 변수 재사용하기 위해서는 리턴하던지 힙에 넣어야 한다.
힙에는 생성자로 만들어진 객체와 객체의 속성(필드,메소드이름)이 있다.
main 메소드 안에서 객체가 생성된다면 포인터를 가지고있는 객체의 변수는 main스택에 있고 실제 객체는 힙에 존재한다
메소드를 한마디로 표현하자면 객체의 상태를 변경하는것.
자바에서는 메소드를 정의하기 위해 클래스가 필요하다.
다른 언어에서는 클래스 필요없이 메소드를 정의하는데 이러한 메소드를 함수라고 표현한다.
메소드 코드블록은 스택에 생성된다. 메소드 내부에 또다른 코드블록이 생긴다면 ( if, for...같은 코드블록 )
메소드 스택 내부에 또 다른 스택이 생성된다.
메소드를 만들면 재사용하기 좋아지므로 여러 코드를 메소드로 만들면 모듈화가 되어서 필요한것만 가져다 쓰기 좋아진다.
( ++ 패턴 파악 (알고리즘) -> 메소드로 만들고 (모듈화) -> 리팩토링 )
이전 버전에서 1급 객체는 클래스뿐이었지만 JAVA8 버전에서 메소드도 1급객체가 되었다. (변수에 저장 가능)
1급객체인 메소드는 메모리에 로딩이 가능하다.
메소드는 어떤 일을 할지 이름으로 알수 있어야 한다. ( 코드블록은 부연설명을 하는것 )
print() 나 random() 을 떠올려보면 이름으로 명확히 의도를 알 수가 있다.
주석을 달지 않아도 다른 사람이 코드분석을 할수 있게 명확하게 이름을 붙이는 습관을 가지자.
자바에서 클래스, 메소드는 단일 책임의 원칙을 지켜라 (기능을 분리 시켜라)
( 자바 라이브러리가 하나의 기능만 가진 메소드로 이루어졌듯이 )
객체지향의 변수는 행위로 변경하자 --> private 접근제한자를 붙이고
setter로만 접근가능하게 바꾸면 행위(메소드)로 변수를 변경하게 된다.
클래스 -> 설계
인스턴스 -> new생성자로 만들어서 heap에 존재하는것
오브젝트(객체) -> 존재 할수 있는가? ex) 고양이, 토끼
( 오브젝트 - 언제든지 만들어질 수 있는것들 )
반대로 동물은 객체가 아니다 (추상화 된것, 존재 불가)
new 가 가능해지거나 구상화가 되면 오브젝트, 개발자가 구현하면 그건 오브젝트가 된다.
abstract(추상)가 아니면 모든 클래스는 오브젝트다 (heap에 갈 가능성이 있는 모든것)
오버로딩 -> 다양한 타입을 하나의 메소드 이름으로 사용할수 있으니 편하지만 ex out.print()
매개변수의 종류가 다양해지면 오버로딩을 해야할 메소드가 기하급수적으로 늘어나는 단점이 있다.
이를 오버라이딩을 통해서 해결해보자.
업캐스팅은 < 부모타입 변수 = new 자식객체 > 구조
호출할 메소드의 매개변수가 부모타입일때 자식객체를 넣으면 매개변수 자리에서도 업캐스팅이 된다.
상속 + 업캐스팅으로 자식객체를 생성하면 변수의 포인터는 명시적으로 생성된 자식객체를 가리킨다.
자식 생성자 첫줄은 부모의 생성자이기 때문에 heap에 부모의 인스턴스가 먼저 생성되고 자식이 생성된다.
< 부모타입 = 자식객체 > 구조에서 부모의 멤버를 호출할수 있는 이유는 부모의 인스턴스가 힙에 존재하기 때문이다.
마찬가지로 매개변수가 부모타임인 메소드에 자식객체를 넣으면 동시에 힙에 부모 객체도 생성된다.
오버라이딩을 하면 부모의 메소드는 무효화가 된다. -> 자식클래스에 오버라이딩을 하고 부모메소드 호출시 부모메소드는 무효화되고 자식의 메소드가 호출됨 ( 동적 바인딩 )
힙에 부모와 자식의 인스턴스가 존재하면 오버라이딩이 적용된다.
바인딩 -> 메소드의 호출을 실제 메소드와 연결
오버로딩은 컴파일시 다형성을 지원 -> 정적바인딩
오버라이딩은 런타임시 다형성을 지원 -> 동적바인딩
동적 바인딩
런타임시 변수가 참조하는 객체의 실제 타입을 보고 메소드를 호출
다형성을 사용하여 메소드를 호출하면 구현되어 있는 메소드를 호출하게 된다.
부모의 메소드가 static이면 업캐스팅 구조에서 부모메소드를 호출시 재정의된 자식 메소드가 호출되지 않는다.
추상메소드에 abstract 를 붙이면 자식클래스에서 반드시 재정의 해야하므로 협업시에 좋을것 같다.
상속하는 자식 클래스에서 필드를 추가하면 필드에 접근할수 있는 메소드를 만들어서 개발코드에서 호출하면 자식필드를 사용할 수 있다.
상속의 근본적인 목적은 추상화에 있다. ( 공통적인 특징을 추상화로 모은다 )
상속은 강한 결합을 가져 유연함이 떨어지는 단점이 있다 -> 만약 부모가 수정되면 자식의 구조가 모조리 깨져버린다.
확장이 가능하고 종속성을 줄이고 더 유연한( 결합도가 낮아 수정시 영향이 적은) Composition을 사용하여 단점을 보완한다.
조합(composition) -> 다른 클래스의 인스턴스를 가져서 그 클래스의 멤버에 접근할 수 있다.
외부에서 객체를 생성하고 객체의 주소를 필드에 대입한다 -> 의존성을 주입한다고 표현
결합도가 높으면 생성된 인스턴스가 가진 특성이 변화되었을때 대처하기가 힘들다.
composition을 이용하면 객체는 캡슐화 상태가 유지된다.
제네릭도 다형성에 포함이 된다 -> 다형성은 하나의 코드로 다양한 타입의 객체를 처리
내부에 다른 객체를 생성하거나 메소드를 호출하면 그 객체에 의존한다고 한다.
파라미터를 전달받으면 파라미터에 의존한다고 함. (파라미터에 따라 결과가 달라짐)
즉, 의존하는 무언가가 변경되면 의존하고 있는 자신도 변경될 가능성이 있다. (영향을 미친다)
자주 변하는것을 의존 하면 유연하지 못한 프로그래밍이 되고 유지보수가 어렵다.
객체 지향의 프로그래밍은 구현객체를 변경해도 개발코드에 아무런 영향이 없어야 유지보수가 편하다.
이러한 영향은 캡슐화를 통해서 최소화 한다. ( 캡슐속의 로직만 변경될 뿐 )
개발코드와 객체사이에서 강제성을 부여해 객체사용방법을 정의함 ( 구현을 강제 )
이름을 통일하고 가독성을 높여 유지보수를 향상시킨다.
추상메소드는 내용을 가질 수 없다 (static, default 메소드 제외) -> 다이아몬드 문제가 없다.
인터페이스를 이용하면 결합도를 낮춘(종속성을 낮춘) 유연한 개발을 할 수 있다.
(상속은 강한 결합을 가진다 -> 부모를 수정하면 자식에 영향을 미친다. )
인터페이스는 필드를 가질수 없다 -> 필드가 필요하면 추상클래스 이용
상속과 다르게 < 인터페이스타입 변수 = new 구현객체 > 구조일때 변수로 구현객체의 필드에 접근할 수 없다.
인터페이스는 관련없는 것들의 공통된 동작을 표현할때 사용. ( 객체들이 공통된 특성을 가지면 추상클래스로 )
개방폐쇄원칙(OCP)을 이용한다.
확장에 열려있다 -> 다양한 구현객체를 생성해서 기능을 추가할 수 있다.
변화에 닫혀있다 -> ex) JDBC는 중간에 DB가 변경되더라도 영향을 받지 않는다 (수정이 필요없다).
---> 변하는 부분을 추상화하여 기능을 변경, 확장할 수 있으면서 그 기능을 사용하는 코드는 수정하지 않는다
의존성역전원칙(DIP)을 이용한다.
고수준 모듈에 의존해야한다. (인터페이스)
추상화된 것을 의존하면 확장이 쉬워진다 -> 구현된 객체를 변경하기 쉽다
---> 자신보다 변하기 쉬운것에 의존하지 마라, 추상화(인터페이스)에 의존해라
이러한 원칙들로 인해 인터페이스는 유연성(다양한 요구사항에 대응)을 가지고 , 재사용성이 높고, 유지보수에 편하다
객체는 상태와 동작을 가지고, 객체를 통해 코드를 구성한다
객체를 사용 -> 모듈화를 이용해 재사용성, 유지보수를 쉽게한다
단점 ! 객체의 의존관계에 의해 속도가 느리고 모듈의 관계를 구현해야 해서 복잡하다
응집도(Cohesion)를 높이고 결합도(Coupling)를 낮추는 방식으로(독립성이 좋아짐) 코드를 작성해보자
(응집 - 모듈 내부에서 처리 요소들이 서로 관련되어 있음)
(결합 - 예를들어 다른 모듈을 의존하면 결합이 되었다고 말함)
모듈화 - 목적에 맞는 하나의 기능만 있으면 좋은 모듈이다 -> 책임이 분리되면 유지보수에 편하다 - 단일책임의원칙(SRP) 준수
다시 또 읽어봐야지! 굿굿!!