클래스(Class)와 객체(Object)
Class는 객체를 정의하는 설계도, 틀이다.(보통 PascalCase로 만든다.) 객체는 그렇게 설계도(클래스)를 기반으로 만들어진 것들을 말한다. 클래스의 타입으로 선언되었을 때 객체라고 부르고, 그 객체가 메모리에 할당되어 실제 사용될 때 인스턴스(instance)라고 부른다.
클래스로부터 객체를 선언하는 것을 인스턴스화 한다고 하고, 어떤 객체를 어떤 클래스의 인스턴스라고 표현한다.
클래스명을 사용할 때는 클래스명을 자료형처럼 쓴다고 생각하면 된다. 클래스명 객체명(변수느낌, 식별자) = new 클래스명(); 이런느낌으로 쓴다.
여기에 접근제어자와 static이 붙어서 클래스가 만들어진다.
** this는 자기자신, this()는 생성자를 의미한다. **
클래스 내부의 메소드는 사용할 때 객체명.함수이름(필요한 인자); 이런식으로 사용한다.
클래스는 메소드와 변수(클래스(static), 인스턴스 변수, 초기화 생략 가능)로 구성되어있다.
클래스 또한 참조타입이기 때문에 배열로 선언할 수 있다.
아무것도 없고 초기화 중괄호{} 안에 변수초기화만 되어있는 것을 초기화 블록이라고 한다.
클래스 내의 멤버변수는 기본적으로 초기화를 해준다.
생성자(constructor)
new 키워드로 인스턴스(객체)를 생성할 때, 제일 먼저 실행되는, 자동으로 호출되는 메소드다.
생성자 이름은 클래스 이름과 정확히 일치해야 한다. 생성자는 리턴타입이 없어서 그냥 비워서 쓴다. 생성자를 만들어주지 않아도 디폴트로 클래스이름() {};이 있는것이기에 생략이 가능하긴 하다.
오버로딩(overloading)
하나의 클래스에서 같은 이름의 메소드 여러개를 정의하는 것이다. 각 메소드는 이름만 같고, 매개변수 개수 혹은 타입이 달라야한다. (리턴타입 차이로는 안됨)
접근 제어자(access modifier)
자바에서 패키지와 디렉토리는 비슷한 의미를 갖는다.
접근 제어자는 클래스의 변수나 메소드 접근에 제한을 두는 키워드다. 4가지가 있다.
1. private - 클래스 내에서만 접근가능
2. public - 다 가능
3. default - 해당 패키지 내에서만 접근가능, 아무것도 안써주면 이것이다. 같은 패키지, 디렉토리 내에 있으면 상속이 가능하다.
4. protected - 해당 패키지 및 상속받은 클래스에서 접근가능(외부 패키지에서 해당 클래스를 상속받은 자식 클래스도 가능)
패키지는 여러 클래스들이 묶인 각각 구분되는 영역을 말한다.
static
변수나 메소드의 특성을 바꾸는 키워드다. 변수, 메소드 앞에 사용가능하다.
static이 붙어있는 변수나 메소드는 객체가 생성되기 전부터 메모리에 한번 잡혀서 상주하게 된다.
메모리에 한번만 할당된다. 그래서 static 변수나 메소드는 서로 공유되는 특성을 갖고 있다. 즉 해당 클래스의 객체들이 static 클래스 변수값을 공유하고, 객체를 생성하지 않아도 클래스 이름만으로 static 클래스 메소드를 사용할 수 있다.(System.out.println()같은거)
static 메소드에서 사용되는 변수도 static 변수여야 한다.
상수와 리터럴
상수는 한번 선언과 초기화를 하면 재정의 할 수 없는 값을 말한다.
ex ) public static final double E = 3.12345676543456543456;
리터럴은 어떤 변수에 선언되는 값, 데이터 그 자체를 뜻한다. 위 예시에선 3.123...을 말한다. 상수는 E고.
상속(inheritance)
상속은 기존 클래스에 기능 추가 및 재정의를 통해 새로운 클래스를 만드는 것이다. 부모 클래스의 필드와 메소드가 상속된다.
(생성자, 초기화 블록(아무 구문없이 {}안에서 변수를 초기화 해주는 것, {x=3; // 이때 x는 위에 정의되어 있는 상태라고 가정})은 상속되지 않는다.)
부모 = 상위 = 기초 클래스
자식 = 하위 = 파생 클래스
부모는 여러 자식을 둘 수 있지만 자식은 하나의 부모만 가질 수 있다. 부모에서 private, default 멤버(같은 패키지의 자식은 접근가능)는 자식이 접근할 수 없다.
class 자식 클래스명 extends 부모 클래스명 {} 같이 만든다.
super는 부모를 가리키는 것이다.(this랑 비슷) 부모 자식 클래스의 이름이 같을 때 구분할 수 있다. super()는 부모 클래스의 생성자를 호출하는 것이다.
현업에서는 생각만큼 상속을 많이 쓰지는 않는다. 상속을 했을때는 오버라이드 했던것과 아닌것의 혼란이 올 수 있고 상속 오버라이드를 잘못 사용하면 로직의 충돌이 일어날 수 있기 때문이다. (Fragile base class 문제) 그리고 기능을 너무 확장, 변경하면 재활용성이 떨어진다.
그래서 상속을 피하는 방법, 잘하는 방법은 상속을 위한 설계를 한 클래스만 상속(추상클래스)하고, 부모 클래스 상속 대신 인터페이스를 활용하고, 피할 수 없다면 상속을 하지만 부모와 상호 치환이 가능하도록 만들면 된다. (부모 클래스와 동일한 기능 제공)
오버라이딩(overriding)
부모 클래스의 메소드를 자식 클래스에서 재정의하는 것이다. 메소드 선언부는 부모와 동일해야한다.
부모 클래스의 반환 타입으로 변환할 수 있는 타입으로 변경 가능하다. 부모의 메소드보다 접근 제어자를 더 좁은 범위로 변경은 불가능하다. 부모의 메소드보다 더 큰 범위의 예외 선언은 불가능하다.
다형성(polymorphism)
다형성은 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 말한다.
부모 클래스명 객체명1 = new 자식 클래스명() 같이 부모가 자식의 객체를 받아서 쓸 수 있다. (이걸 업캐스팅이라고도 함) 반대는 안된다. (부모 -> 자식 가능, 자식 -> 부모 혹은 자식끼리는 불가능)
이 경우 부모함수를 출력하면 자식쪽에 오버라이딩된 함수가 있으면 그걸로 출력된다.
자식에만 있는 함수를 쓰는 것은 불가능하다.
이걸 다시 자식 클래스명 객체명2 = (Student)객체명1 처럼 쓰면 업캐스팅된 것도 쓸 수 있다. 이걸 다운캐스팅이라고도 한다. (자식클래스끼리의 형변환은 안된다.)
다형성에 익숙해지면
이런식으로도 활용이 가능하다.
instanceof
instanceof는 실제 참조하고 있는 인스턴스의 타입을 확인하는 것이다. 다운캐스팅한 것인 경우, 해당 다운캐스팅한 자식까지는 다 instanceof가 true로 나온다. 예를 들어,
Parent p1 = new Son();
System.out.println(p1 instanceof Parent);
System.out.println(p1 instanceof Son);
이면 둘다 true를 출력한다는 의미이다.
이를 통해
이런식으로 확인하고 다운캐스팅이 가능해진다.
추상메소드(abstract method)
부모에서 만드는 것으로 자식클래스에서 반드시 오버라이딩(구현, 재정의) 해야하는 메소드를 말한다.
선언만 하고 내용구현은 없다. 만드는 법은 아래와 같다.
abstract 반환타입 메소드이름();
추상 자료형(Abstract Data Type) 같은것도 있다.
추상클래스(abstract class)
위의 추상메소드를 하나라도 포함하면 추상클래스라고 한다. 반드시 구현해야 하는 부분에 대해서 명시적으로 표현한다. 불완전한 클래스이기에 추상클래스로는 객체를 만들 수 없다.
만들 때는 abstract class 클래스명{} 이렇게 만들면 된다.
직접 쓰려고 하면 이런식으로 객체를 만드는 데에서 오버라이딩, 추상클래스를 완성해줄수도 있다.(이건 익명클래스라고 아래 내부클래스 부분에서 자세한 설명이 나와있음)
구현해야할 익명메소드가 많을 경우 자식클래스를 만들고 거기에 우클릭 -> Generate -> implement method하면 구현해야할 메소드들이 모두 자동으로 나온다.
** 추상클래스를 상속받아 자식클래스를 만들때는 추상클래스는 불완전한 클래스기 때문에 추상클래스의 변수는 this를 통해 받아들인다. 일반 클래스를 상속받았을 때는 부모 클래스의 변수는 super를 통해 만든다.(뇌피셜) **
인터페이스(interface)
인터페이스는 다중상속처럼 사용할 수 있게 해주는 기능이다.
만들때는 접근제어자 interface 인터페이스이름 {}같이 만든다. 인터페이스는 상수와 추상 메소드로 구성되어 있다.
상수는 데이터의 값 변경이 불가능한 변수로 (public static) final 타입 상수이름 = 값; 같이 final이 들어가면 된다.
인터페이스를 사용할 때는 class 클래스이름 implements 인터페이스이름1, 인터페이스이름2, 인터페이스이름3 ... {} 같이 사용할 수 있다.
class 클래스이름 extends 부모이름 implements 인터페이스이름1,2,3 {} 같이 상속과 동시에 사용하여 다중상속처럼 사용할 수도 있다.
직접 쓰려고 하면 추상클래스와 같이 이런식으로 객체를 만드는 데에서 오버라이딩, 추상클래스를 완성해줄수도 있다.
내부클래스(inner class)
내부클래스는 클래스 안에 클래스를 선언한 것을 말한다.(중첩클래스라고도 한다.)
특징으로는 내부 클래스에서는 외부 클래스 멤버에 접근이 가능하지만 외부에서는 내부에 접근이 불가능하다. 내부에서 외부 함수를 쓸때는 외부클래스이름.this.함수이름(); 이런식으로 사용한다.
내부클래스 종류로는 인스턴스 클래스, 정적 클래스, 지역 클래스, 익명 클래스가 있다.
1. 인스턴스 클래스(instance class) - 내부클래스처럼 외부클래스를 만들어야 사용할 수 있는 형태를 말한다.
외부클래스명.내부클래스명 객체명 = new 외부클래스명().new 내부클래스명();
이런식으로 외부를 만든다음에 내부를 만들어줘야한다.
2. 정적 클래스(inner static class) - 내부클래스인데 static 키워드가 붙어서 메모리에 바로 상주되는 형태다. 그래서 외부 클래스가 만들어지지 않아도 사용이 가능하다. 하지만 Outer가 static이 아니라면 바로 메모리에 올라가야 하므로 외부클래스 메소드를 사용하지 못한다.
3. 지역 클래스(local class) - 메소드 안에 클래스가 있는 것이다.
4. 익명 클래스(anonymous class) - 이름을 가지지 않는 클래스다. 선언과 동시에 객체가 생성되고 이름이 없어서 일회용이다. 클래스이름 참조변수이름 = new 클래스이름 (){ 클래스를 여기에 정의 };
여기서는 세미콜론이 붙는다.