컨테이너 내부 경로만 지정 호스트 서버 경로를 지정하지 않아 도커가 임의로 매핑함 사용자는 위치를 모름 심지어 접근조차 할 수 없도록 설계됨
확인 명령어 docker volume ls
컨테이너 종료 시 익명볼륨도 같이 사라짐 즉, "익명 볼륨 데이터"는 수명주기를 컨테이너와 함께함 휘발성, 일회성 하나의 익명 볼륨은 하나의 컨테이너에 강하게 연결되어 있음
명명된 볼륨 (named)
익명 볼륨과 같지만 이름을 부여한다. 큰 차이는 컨테이너 제거 시 호스트 저장소에 데이터는 안지워진다. 다만 접근하면 안되도록 설계한 것은 동일하기에 편집하지 않지만 삭제되면 안되는 데이터가 오기 좋다. 컨테이너가 종료되도 호스트 서버에 있는 볼륨은 삭제되지 않음 이 볼륨은 하나의 컨테이너에만 연결되지 않음 즉, 볼륨 이름을 컨테이너 실행 시 똑같이 입력하면 여러 컨테이너가 연결될 수 있음
컨테이너를종료해도볼륨이살아있는것을알수있다.
볼륨 삭제
명명된 볼륨은 컨테이너를 종료해도 남아있기에 제거 방법을 알아둬야 한다.
docker rm 볼륨이름 // 단건 제거
docker volume prune // 컨테이너 참조가 하나도 없는 모든 볼륨 제거
모든 명령어 공통 사항으로 제거 시 prune 가 들어가면, 참조되지 않고 단순히 존재만 할 시 제거 대상이 된다.
public class Test {
public static void main(String[] args) {
Tv tv = new SmartTv();
tv.down();
tv.up();
// tv.search(); 사용 못한다. Tv클래스를 보면 search()없기 때문
//실제 생성된 객체는 SmartTv이기에 타입을 변경하면 호출할 수 있다.
((SmartTv)tv).search();
}
}
class Tv{
void up() {}//채널 업
void down() {}//채널 다운
}
class SmartTv extends Tv{
void search() {}//인터넷 검색기능
}
원래 생성된 객체가 SmartTv니까 문제가 없다. 만약 Tv라면 어떻게 될까?
컴파일러는 오류를 못잡는다. 즉, 런타임 예외가 발생하게 된다. (실행 중 예외)
형변완 예외 발생
이 때문에 형변환 시에 무조건 instanceof 연산자로 형변환가능한 경우만 형변환되도록 하는 것이 중요하다.
위까지가 다형성의 사전 지식
public class Test {
public static void main(String[] args) {
Tv tv = new SmartTv();
tv.down();
tv.up();
}
}
class Tv{
void up() {
System.out.println("일반 TV 채널 업");
}//채널 업
void down() {
System.out.println("일반 TV 채널 다운");
}//채널 다운
}
class SmartTv extends Tv{
void up() {
System.out.println("스마트 TV 채널 업");
}//채널 업
void down() {
System.out.println("스마트 TV 채널 다운");
}//채널 다운
void search() {}//인터넷 검색기능
}
///////////////////////////////////////
스마트 TV 채널 다운
스마트 TV 채널 업
실제 동작은 생성된 객체 구현부로 동작되는 것을 알 수 있다. 이를 통한 다형성 사례
public class Test {
public static void main(String[] args) {
SmartTv tv1 = new LGSmartTv();
SmartTv tv2 = new SamsungSmartTv();
tvTest(tv1);
tvTest(tv2);
}
//앞으로 새로운 스마트 TV가 추가되어도 기존 아래 코드는 수정할 일이 없다.
static void tvTest(SmartTv smartTv) {
smartTv.down();
smartTv.up();
smartTv.search();
}
}
class Tv{
void up() {
System.out.println("일반 TV 채널 업");
}//채널 업
void down() {
System.out.println("일반 TV 채널 다운");
}//채널 다운
}
//객체 생성을 막음
abstract class SmartTv extends Tv{
void up() {
System.out.println("스마트 TV 채널 업");
}//채널 업
void down() {
System.out.println("스마트 TV 채널 다운");
}//채널 다운
void search() {
System.out.println("스마트한 검색");
}//인터넷 검색기능
}
class LGSmartTv extends SmartTv{
void up() {
System.out.print("LG");
super.up();
}
void down() {
System.out.print("LG");
super.down();
}
void search() {
System.out.print("LG의 ");
super.search();
}
}
class SamsungSmartTv extends SmartTv{
void up() {
System.out.print("삼성");
super.up();
}
void down() {
System.out.print("삼성");
super.down();
}
void search() {
System.out.print("삼성의 ");
super.search();
}
}
////////////////////////////////
LG스마트 TV 채널 다운
LG스마트 TV 채널 업
LG의 스마트한 검색
삼성스마트 TV 채널 다운
삼성스마트 TV 채널 업
삼성의 스마트한 검색
객체지향 원칙 캡슐화, 추상화, 상속, 다형성을 기반으로 다양한 디자인 패턴들이 존재한다.
인터페이스(interface)
일종의추상클래스
추상클래스처럼추상메서드를갖지만추상클래스보다추상화정도가높다
개발 코드가 객체에 종속되지 않게 하여 객체를 교체할 수 있도록 한다. (OCP, 변경엔 닫혀있고, 확장엔 열려있어야한다)
객체 간 느슨한 결합을 위한 핵심
객체지향 원칙에 구현보다는 인터페이스에 맞춰서 프로그래밍한다는 원칙도 있다.
인터페이스 모든 멤버변수는 public static final이다. 그렇기에 생략해도 컴파일러가 일괄적으로 붙여준다.
모든 메서드는 public abstract다.
인터페이스 간 상속도 당연히 된다. expands 키워드, 인터페이스를 클래스가 구현하면 implements
느슨한 결합 예시
public class Test {
public static void main(String[] args) {
LgTv lgTv = new LgTv();
tvTest(lgTv);
}
//지금 구조에서 기존 코드(tvTest)를 절대 건드리지 말고 SamSungTv로 바꿔 동작하게 할 수 있나?
public static void tvTest(LgTv lgTv) {
lgTv.volDown();
lgTv.volUp();
}
}
class LgTv{
void volUp() {}
void volDown() {}
}
class SamSungTv{
void volUp() {}
void volDown() {}
}
절대 불가능하다.
public class Test {
public static void main(String[] args) {
LgTv lgTv = new LgTv();
SamSungTv samSungTv = new SamSungTv();
//자동 형변환된다.
tvTest(lgTv);
tvTest(samSungTv);
}
//지금 구조에서 기존 코드(tvTest)를 절대 건드리지 말고 SamSungTv로 바꿔 동작하게 할 수 있나?
public static void tvTest(Tv tv) {
tv.volDown();
tv.volUp();
}
}
interface Tv{
void volUp();
/*public abstract*/void volDown();
}
class LgTv implements Tv{
public void volUp() {}
public void volDown() {}
}
class SamSungTv implements Tv{
public void volUp() {}
public void volDown() {}
}
객체지향을 시작으로 다양한 디자인 원칙, 더 나아가 디자인 패턴에서 중요한 핵심은 기존코드를 수정하지 않고 새로운 기능을 손쉽게 추가하는 것이다.
익명클래스(anonymous class)
이름없는클래스, 이유는선언과객체생성을동시에하기때문이다.
일회용으로쓰거나, 오직하나의객체만을생성한다.
public class Test {
public static void main(String[] args) {
new AnonymousTest();
}
}
class AnonymousTest {
public AnonymousTest() {
System.out.println("객체 선언과 동시에 생성");
}
}
////////////////////
객체 선언과 동시에 생성
public class Test {
public static void main(String[] args) {
Runnable runnable = ()->Test.call("1");
Thread thread = new Thread(runnable);
thread.start();
call("0");
}
static void call(String msg) {
for(int i = 0; i< 10000 ; i++) {
System.out.print(msg);
}
}
}
모든인스턴스메서드내에는 자기자신객체의주소this와 부모의주소 super 가지역변수로저장되어있다.
참고로 매개변수도 그 메서드 블록안에 로컬 변수다.
public class Test {
public static void main(String[] args) {
SubMan man = new SubMan();
man.call();
}
}
class SuperMan {
public int intValue = 1;
}
class SubMan extends SuperMan{
int intValue = 12;
void call() {
int intValue = 123;
System.out.println(intValue);
System.out.println(this.intValue);
System.out.println(super.intValue);
}
}
/////////////////
123
12
1
class Class1{
//기본 생성자와 기본 조상 클래스 생성자( super() )는 컴파일러가 자동 추가해준다.
public Class1() {
super(); // 모든 생성자(this(), super() )는 무조건 다른 생성자를 "첫줄"에 호출해야한다
}
}
//수동으로 기본생성자를 추가하는 것이 일반적으로 옳은 프로그래밍이다.
Object를 제외한 모든 클래스는 상속받은게 없다면 자동으로 Object를 상속한다.
만약 명시적으로 상속받은게 있다면, 부모 클래스가 Object를 상속받는다. 부모 클래스도 상속받은게 있다면 그 부모 클래스가 Object를 상속받는다.
//상속받은게 없다면 자동으로 Object를 상속 받는다.
class A /*extends Object*/{
}
class B extends A{
}
이유는 조금만 생각해보면 당연하다.
구조적으로 상속받는 객체가 있어야 상속을 받을 수 있다. 부모 없이 자식이 있을 수 없는 것과 같다.
A.class 파일 속을 열어보면 위와 같은 정보를 볼 수 있다.
패키지(package)
서로관련된클래스의묶음
관리편리함
이름이중복되도패키지만다르면상관없다. (네임스페이스)
import 문
컴파일러에게소스코드에사용되는클래스가속한패키지를알려준다.
클래스를사용할때패키지이름을생략할수있다.
패키지.* 시컴파일단에서시간이조금더걸리는것이지실행시성능저하는전혀없다.
단, 주의할것은해당패키지에모든클래스를표현하는것이지 그하위디렉토리는포함하지않는다
Static import 문
static 멤버(static 변수&메서드)를사용할때클래스이름을생략할수있게해준다.
제어자(modifier)
접근 제어자 : public, protected, default, private
그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp
접근 제어자는 단 하나만 사용가능
나머지는 복수도 가능
제어자들 간 순서는 상관없지만, 접근 제어자는 관례상 가장 왼쪽에 둠
static
클래스가 메모리에 로드될 때 단 한번, 생성(또는 수행)된다. 클래스에 귀속된다.
public class Test {
public static void main(String[] args) {
//바로 사용 가능
System.out.println(C.a);// 답은 뭘까?
new C();
new C();
new C();
C a = new C();
//비권장법 사용법, 클래스 변수는 무조건 클래스이름으로 접근
System.out.println(a.a);
//권장 사용법
System.out.println(C.a);
System.out.println(a.b);// 답은 뭘까?
}
}
class C{
static int a = 0;
static int b = 0;
static {
a++;
}
public C() {
b ++;
}
}
////////////////////////////
1
1
1
4
final
변경하지 못하게 한다. 이것만 알면 된다.
변수 => 상수, 메서드 => 오버라이드 불가 , 클래스 => 상속 불가
final 변수는 상수이기에 선언과 동시에 초기화해야 하나
인스턴스변수인경우에한에생성자에서초기화되도록할수있다.
abstract
메서드에만 붙는다고 생각하면 된다. 이유는 구현부가 없다는 것을 표현하기 때문이다.
클래스에 붙는 이유는 abstract 메서드가 존재함을 알리기 위한 성격이다.
abstract class C{
abstract void call();
}
//이런식으로도 사용된다. 상속을 강제한다.
//(abstract붙으면 추상 클래스로 new연산자를 사용 못한다.)
//
abstract class D{
void call() {}
}
접근제어자 final과 abstract 를 사용한 디자인 패턴도 있다.
템플릿 메서드
abstract class C{
//골격에 해당하는 메서드는 오버라이딩 못하게 막는다.
final void call() {
System.out.println("무언가... 작업중...");
call2(); // 서브클래스에게 행동을 위임한다.
call3();
hook();
}
abstract void call2();
void call3() {
System.out.println("다른 메서드 호출....");
}
//구현을 강제하지 않는 메서드를 hook메서드라 한다.
//그냥 넘어가도 되고 향후 필요할 때 hook부분을 재정의해서 쓰면된다.
//반드시 hook이름 안써도된다. 관례 아님. 이렇게 쓰는걸 hook메서드라 하는 것
void hook() {}
}
class D extends C{
@Override
void call2() {
System.err.println("나만의 메서드...");
}
}
접근 제어자
말그대로 접근 제어,
private 같은 클래스 내
default 같은 클래스 내, 같은 패키지 내
protected 같은 클래스 내, 같은 패키지 내, 다른 패키지라도 상속해서 사용 가능
public 전부 허용
접근제어자는 캡슐화에 핵심적인 역할을 한다.
데이터가유효한값을유지하도록, 또는민감한데이터를외부에서함부로변경하지못하도록제한을한다.
이를정보은닉, 캡슐화라한다.
클래스내부에서만필요한정보를외부에노출시키지않아복잡도를줄인다.
접근제어자가 public이면메서드를변경한후에오류가없는지테스트해야하는범위가넓다.
private이면그클래스내부에서만확인하면된다.
자바빈즈 패턴은 접근 제어자를 활용한 패턴이다.
모든 변수는 private으로 두고 메서드로만 접근하게 한다. 메서드는 동작을 정의하기 때문에 사용자 접근에 대한 검증과 데이터 유효성 검사 등을 수행할 수 있다.
쉽게 생각해보자, 특정 데이터를 권한 있는 사용자에게만 보여주고 싶다. 메서드 없이 변수로는 죽었다 깨어나도 구현 못한다.
생성자의 접근 제어자 prviate을 두면 객체를 외부에서 생성을 못한다.
이말은 클래스 자체적으로 객체를 생성해서 외부에 건낸다. 이를 싱글턴 패턴이라 한다.
class Singleton{
//제어자 중심으로 유심히 보자
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}