- 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여함
- 제어자는 크게 접근 제어자와 그 외의 제어자로 나뉨
- 하나의 대상에 여러 개의 제어자를 조합해서 사용할 수 있으나, 접근제어자는 단 하나만 사용할 수 있음
사용될 수 있는 곳
멤버변수, 메서드, 초기화 블럭
사용될 수 있는 곳
클래스, 메서드, 멤버변수, 지역변수
- final이 붙는 변수는 상수이므로 보통은 선언과 초기화를 동시에 함
- 하지만 인스턴스마다 고정값을 갖는 인스턴스 변수의 경우 생성자에서 초기화함
사용될 수 있는 곳
클래스, 메서드
- 멤버 또는 클래스에 사용되어, 외부로부터의 접근을 제한함
- 일반적으로 생성자의 접근제어자는 클래스의 접근제어자와 일치
- 생성자에 접근제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있음
1. 메서드에 static과 abstract를 함께 사용할 수 없다
- static메서드는 몸통(구현부)이 있는 메서드만 사용할 수 있기 때문
2. 클래스에 abstract와 final을 동시에 사용할 수 없다
- 클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미
- abstract는 상속을 통해서 완성되어야 한다는 의미로 서로 모순되기 때문
3. abstract메서드의 접근제어자가 private일 수 없다
- abstract메서드는 자손클래스에서 구현해주어야 하는데 접근제어자가 private이면, 자손클래스에서 접근할 수 없기 때문
4. 메서드에 private와 final을 같이 사용할 필요가 없다.
- 접근제어자가 private인 메서드는 오버라이딩될 수 없기 때문
- 둘 중 하나만 사용해도 의미가 충분
- 여러 가지 형태를 가질 수 있는 능력
- 하나의 참조변수로 여러 타입의 객체를 참조할 수 있는 것
- 즉, 조상타입의 참조변수로 자손타입의 객체를 다룰 수 있는 것을 말함
- 서로 상속관계에 있는 타입간의 형변환만 가능
- 자손 타입에서 조상타입으로 형변환하는 경우, 형변환 생략가능
- 참조변수가 참조하는 인스턴스의 실제 타입을 체크하는데 사용
- 이항연산자이며 피연산자는 참조형 변수와 타입, 연산결과는 true 또는 false
- instanceof의 연산결과가 true이면, 해당 타입으로 형변환 가능
- 멤버변수가 중복정의된 경우, 참조변수의 타입에 따라 연결되는 멤버변수가 달라짐
(참조변수타입에 영향받음0
- 메서드가 중복정의된 경우, 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입에 정의된 메서드가 호출됨
(참조변수타입에 영향받지 않음)
- 참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있음
- 조상타입의 배열에 자손들의 객체를 담을 수 있음
롤을 예시로 들어서 실습해 보았다
주석으로 설명..
아래 공격력 작성할시 참고해서 사용함
package ch07;
// 모든 스킬들의 공통적인 부분을 세팅한다
// MAX_POINT 모든 스킬들이 공통적으로 사용하는 변하지 않는 값을 세팅(static final)
// 스킬마다 바뀔 수 있는 부분 name, point, champ는 필드 (멤버변수)로 작성한다.
class Skill{
static final int MAX_POINT = 5;
String name;
int point;
Champ champ;
public Skill(String name, Champ champ) {
this.name = name;
this.champ = champ;
this.point = 0;
}
public int getDamage(){
return 0;
}
public void plusPotin(){
if (point >= MAX_POINT) {
point = MAX_POINT;
System.out.println("포인트를 더 이상 올릴 수 없습니다.");
return;
}
this.point++;
}
}
// 부모가 필요로하는 정보는 넘겨주고
// 내가 부모와 다르게 커스텀 할 부분을 세팅한다
class Volly extends Skill {
public Volly(Champ champ) {
super("일제사격", champ);
}
@Override
public int getDamage() {
// 고정 데미지 20 / 35 / 50 / 65 / 80
// + 캐릭터 공격력 * 1.0
// 스킬을 찍지 않았을 때
if(point < 1){
System.out.println("스킬을 사용할 수 없습니다.");
return super.getDamage();
}
int damage = (int) (20 + (15 * (point - 1)) + (this.champ.ad * 1.0));
return damage;
}
}
// 챔피언의 공통적인 부분들 만들어준다.
class Champ {
// 물리 공격력
int ad;
Skill skill;
public Champ(int ad, Skill skill) {
this.ad = ad;
this.skill = skill;
}
}
// 챔피언의 공통적인 부분을 이어 받되
// 개별 캐릭터의 특징을 다시 세팅 해준다
class 애쉬 extends Champ{
public 애쉬(){
// 1. 상속받은 객체가 생성됨
// super를 통해서 객체 안의 필드에 값을 최기화해줌
super(100, null);
// 현재 class의 객체가 생성됨
// 현재 객체 안의 필드에 값을 넣어줌
this.skill = new Volly(this);
}
}
// 스킬이름 : 필트오버 피스메이커
// 고정 공격력 : 50 90 130 170 210
// + 캐릭터 공격력 * 1.25 / 1.45 / 1.65 / 1.85 / 2.05
class Peace extends Skill{
public Peace(Champ champ){
super("필트오버 피스메이커", champ);
}
@Override
public int getDamage(){
if (point < 1) {
System.out.println("스킬을 사용할 수 없습니다.");
return super.getDamage();
}
// int damage = (int)(20 + (15 * (point - 1)) + (this.champ.ad * 1.0));
// return damage;
int damage = (int)(50 + (40 * (point - 1)) + (this.champ.ad * (1.25 + (0.2 * (point - 1)))));
return damage;
}
}
class 케이틀린 extends Champ{
public 케이틀린(){
// 필요한 스킬 Peace
super(100, null);
this.skill = new Peace(this);
}
}
public class S04 {
public static void main(String[] args) {
// // 참조형 변수에 주조값을 아직 모르는 상태에서
// // 초기화를 해주려고 하는 경우 null을 사용한다
// String str = null;
// System.out.println(str);
// str = "홍길동";
// System.out.println(str);
// // 더 이상 쓸모 없는 객체에 null을 입력한다
// str = null;
// 애쉬 champ1 = new 애쉬();
// System.out.println(champ1.skill.getDamage());
// champ1.skill.plusPotin();
// System.out.println(champ1.skill.getDamage());
// champ1.skill.plusPotin();
// System.out.println(champ1.skill.getDamage());
// 케이틀린 생성
// 케이틀린 Peace의 데미지 가져오기
// 케이틀린 champ2 = new 케이틀린();
// System.out.println(champ2.skill.getDamage());
// champ2.skill.plusPotin();
// System.out.println(champ2.skill.getDamage());
// 상속을 받으면
// 부모의 타입으로 공통화 하여 사용할 수 있다
Champ[] champArray = new Champ[2];
champArray[0] = new 애쉬();
champArray[1] = new 케이틀린();
for (int i = 0; i < champArray.length; i++) {
champArray[i].skill.plusPotin();
// 항상 같은 메소드를 사용하기 때문에
// 메소드 이름에 대한 고민을 할 필요가 없어서
// 신뢰성이 높아진다
System.out.println(champArray[i].skill.getDamage());
}
}
}
vscode에서 생성자와 오버라이드 만들 때 쓰는 단축키
alt + insert
해당 단축키를 사용해주면 아래의 그림과 같이 창이 뜨고 선택해주면 쉽게 만들 수 있음
주석으로 설명..
package ch07;
import ch07.sub.Sub01;
public class S06 {
public static int first = 1;
public int second = 2;
public static final int third = 1;
public void printSecond(){
System.out.println(second);
}
public static void main(String[] args) {
Sub01 sub01 = new Sub01();
System.out.println(sub01.getData());
System.out.println(first); //S06.first와 같다
System.out.println(S06.first);
// System.out.println(second); //가져올 수 없다
// System.out.println(printSecond()); //실행할 수 없다.
// static이 아닌 필드는 인스턴스를 생성해서 가져와야 한다.
// (static 메소드 기준)
S06 s06 = new S06();
System.out.println(s06.second);
s06.printSecond();
S06.first = 11;
// final 값은 바꿀 수 없다
// S06.third = 33;
final String str = "홍길동";
// str = "임꺽정";
}
}
주석으로 설명..
package ch07.sub;
public class Sub01 {
// 객체지향에서 데이터는 객체만 볼 수 있는게 기본적이다.
private String data = "데이터";
// 객체의 데이터를 외부에 노출 시키는 것은
// 보통 메소드로 한다
// 원본 데이터를 넘길 수도 있고
// 숨겨야 하는 부분은 숨길 수도 있다
public String getData(){
return "숨긴 " + data;
}
}