자바는 버튼을 제거하는 것은 불편해지지만, 안전하다고 판단한다.
자바는 버튼이 추가하는 것은 편리해지지만, 위험하다고 판단한다.
class Main {
public static void main(String[] args) {
전사 a전사 = new 전사();
a전사.나이 = 22;
a전사.a무기 = new 칼();
a전사.a무기 = new 활();
a전사.공격();
// 전사가 활로 공격합니다.
}
}
class 전사 {
String 이름;
int 나이;
무기 a무기;
void 공격() {
a무기.작동(); // 위임
}
}
abstract class 무기 {
abstract void 작동();
}
class 칼 extends 무기 {
void 작동() {
System.out.println("전사가 칼로 공격합니다.");
}
}
class 활 extends 무기 {
void 작동() {
System.out.println("전사가 활로 공격합니다.");
}
}
=> a무기에 대해 칼, 활을 둘 다 사용해야하는데 class 전사{ 칼 a무기;}
하면 활을 못쓰고, class 전사{활 a무기;}
하면 칼을 못 씀. 그래서 둘 다 쓸 수 있는 무기 클래스를 만들어 줌
상속은 칼 is 무기
구성은 전사 has 무기
생성자를 만들지 않아도 기본 생성자가 있음
class Main {
public static void main(String[] args) {
전사 a전사 = new 전사("하하");
// a전사.이름 = "카니"; // 자동으로 NoName
// a전사.나이 = 22; // 자동으로 20살
System.out.println(a전사.이름); // 출력 : 카니
System.out.println(a전사.나이); // 출력 : 22
}
}
class 전사 {
String 이름;
int 나이;
// 생성자 메서드는 특수한 메서드 이다.
// 명시적으로 개발자가 호출 할 수 없고, 객체가 생성될 때 자동으로 호출된다.
// 생성자 메서드는 리턴타입이 없다.
전사() { //클래스명과 같아야함
// this(); 와 같은 형태의 호출은 생성자에서만 가능하다.
이름 = "NoName"; // 아래 있는 생성자를 호출
}
}
=> 전사 객체가 만들어지자마자 디폴트값으로 이름="NoName"이 들어감
두 개이상의 똑같은 메소드가 한 클래스안에서 호출되는 것. 단, 매개변수가 달라야함.
class 전사 {
String 이름;
int 나이;
// 생성자 메서드는 특수한 메서드 이다.
// 명시적으로 개발자가 호출 할 수 없고, 객체가 생성될 때 자동으로 호출된다.
// 생성자 메서드는 리턴타입이 없다.
전사() { //클래스명과 같아야함
이름 = "NoName"; // 아래 있는 생성자를 호출
}
전사(String 이름) {
this.이름 = 이름;
나이 = 20;
}
}
new 전사();
하면 전사()
가 실행
new 전사(이름);
하면 전사(String 이름)
이 실행
전사(String 이름)
메소드 사용시
class의 이름과 매개변수의 이름이 같아서 헷갈릴 수 있기 때문에this.이름
을 사용(this.이름 == class의 이름)
생성자에서 생성자를 호출할 때,
this("이름");
// this(); 와 같은 형태의 호출은 생성자에서만 가능하다.(this.이름의 this와 별개의 내용)
1단계 : 초기상태
class Main {
public static void main(String[] args) {
전사타입A a전사타입A = new 전사타입A();
a전사타입A.공격();
전사타입B a전사타입B = new 전사타입B();
a전사타입B.공격();
전사타입C a전사타입C = new 전사타입C();
a전사타입C.공격();
전사타입D a전사타입D = new 전사타입D();
a전사타입D.공격();
}
}
class 전사 {
}
class 전사타입A extends 전사 {
void 공격() {
System.out.println("칼로 공격"); //전사타입B와 칼 중복
}
}
class 전사타입B extends 전사 {
void 공격() {
System.out.println("칼로 공격"); //전사타입A와 칼 중복
}
}
class 전사타입C extends 전사 {
void 공격() {
System.out.println("활로 공격"); //전사타입D와 활 중복
}
}
class 전사타입D extends 전사 {
void 공격() {
System.out.println("활로 공격"); //전사타입C와 활 중복
}
}
2단계 : 칼, 활 클래스 만들어서 중복제거
class Main {
public static void main(String[] args) {
전사타입A a전사타입A = new 전사타입A();
a전사타입A.공격();
전사타입B a전사타입B = new 전사타입B();
a전사타입B.공격();
전사타입C a전사타입C = new 전사타입C();
a전사타입C.공격();
전사타입D a전사타입D = new 전사타입D();
a전사타입D.공격();
}
}
class 전사 {
}
class 전사타입A extends 전사 {
void 공격() {
new 칼().작동(); //칼 생성
}
}
class 전사타입B extends 전사 {
void 공격() {
new 칼().작동(); //칼 생성
}
}
class 전사타입C extends 전사 {
void 공격() {
new 활().작동(); //활 생성
}
}
class 전사타입D extends 전사 {
void 공격() {
new 활().작동(); //활 생성
}
}
class 칼 {
void 작동() {
System.out.println("칼로 공격");
}
}
class 활 {
void 작동() {
System.out.println("활로 공격");
}
}
3단계 : 전사가 공격할 때마다 무기를 생성하던 비효율을 제거(생성자로 자동 생성)
class Main {
public static void main(String[] args) {
전사타입A a전사타입A = new 전사타입A();
a전사타입A.공격();
전사타입B a전사타입B = new 전사타입B();
a전사타입B.공격();
전사타입C a전사타입C = new 전사타입C();
a전사타입C.공격();
전사타입D a전사타입D = new 전사타입D();
a전사타입D.공격();
}
}
class 전사 {
}
class 전사타입A extends 전사 {
칼 a무기;
전사타입A() {
a무기 = new 칼(); //생성자로 칼 자동 생성
}
void 공격() {
a무기.작동();
}
}
class 전사타입B extends 전사 {
칼 a무기;
전사타입B() {
a무기 = new 칼(); //생성자로 칼 자동 생성
}
void 공격() {
a무기.작동();
}
}
class 전사타입C extends 전사 {
활 a무기;
전사타입C() {
a무기 = new 활(); //생성자로 활 자동 생성
}
void 공격() {
a무기.작동();
}
}
class 전사타입D extends 전사 {
활 a무기;
전사타입D() {
a무기 = new 활(); //생성자로 활 자동 생성
}
void 공격() {
a무기.작동();
}
}
class 칼 {
void 작동() {
System.out.println("칼로 공격");
}
}
class 활 {
void 작동() {
System.out.println("활로 공격");
}
}
4단계 : 무기 클래스 도입
class Main {
public static void main(String[] args) {
전사타입A a전사타입A = new 전사타입A();
a전사타입A.공격();
전사타입B a전사타입B = new 전사타입B();
a전사타입B.공격();
전사타입C a전사타입C = new 전사타입C();
a전사타입C.공격();
전사타입D a전사타입D = new 전사타입D();
a전사타입D.공격();
}
}
class 전사 {
}
class 전사타입A extends 전사 {
무기 a무기;
전사타입A() {
a무기 = new 칼();
}
void 공격() {
a무기.작동();
}
}
class 전사타입B extends 전사 {
무기 a무기;
전사타입B() {
a무기 = new 칼();
}
void 공격() {
a무기.작동();
}
}
class 전사타입C extends 전사 {
무기 a무기;
전사타입C() {
a무기 = new 활();
}
void 공격() {
a무기.작동();
}
}
class 전사타입D extends 전사 {
무기 a무기;
전사타입D() {
a무기 = new 활();
}
void 공격() {
a무기.작동();
}
}
abstract class 무기 {
abstract void 작동();
}
class 칼 extends 무기 {
void 작동() {
System.out.println("칼로 공격");
}
}
class 활 extends 무기 {
void 작동() {
System.out.println("활로 공격");
}
}
5단계 : 전사클래스들에 있던 중복코드를 전사클래스로 모아서 중복제거
class Main {
public static void main(String[] args) {
전사타입A a전사타입A = new 전사타입A();
a전사타입A.공격();
전사타입B a전사타입B = new 전사타입B();
a전사타입B.공격();
전사타입C a전사타입C = new 전사타입C();
a전사타입C.공격();
전사타입D a전사타입D = new 전사타입D();
a전사타입D.공격();
}
}
class 전사 {
무기 a무기;
void 공격() {
a무기.작동();
}
}
class 전사타입A extends 전사 {
전사타입A() {
a무기 = new 칼();
}
}
class 전사타입B extends 전사 {
전사타입B() {
a무기 = new 칼();
}
}
class 전사타입C extends 전사 {
전사타입C() {
a무기 = new 활();
}
}
class 전사타입D extends 전사 {
전사타입D() {
a무기 = new 활();
}
}
abstract class 무기 {
abstract void 작동();
}
class 칼 extends 무기 {
void 작동() {
System.out.println("칼로 공격");
}
}
class 활 extends 무기 {
void 작동() {
System.out.println("활로 공격");
}
}
6단계 : 무기 교체도 가능
class Main {
public static void main(String[] args) {
전사타입A a전사타입A = new 전사타입A();
a전사타입A.공격();
전사타입B a전사타입B = new 전사타입B();
a전사타입B.공격();
전사타입C a전사타입C = new 전사타입C();
a전사타입C.공격();
전사타입D a전사타입D = new 전사타입D();
a전사타입D.공격();
a전사타입D.공격();
a전사타입D.a무기 = new 로켓런처();
a전사타입D.공격();
}
}
class 전사 {
무기 a무기;
void 공격() {
a무기.작동();
}
}
class 전사타입A extends 전사 {
전사타입A() {
a무기 = new 칼();
}
}
class 전사타입B extends 전사 {
전사타입B() {
a무기 = new 칼();
}
}
class 전사타입C extends 전사 {
전사타입C() {
a무기 = new 활();
}
}
class 전사타입D extends 전사 {
전사타입D() {
a무기 = new 활();
}
}
abstract class 무기 {
abstract void 작동();
}
class 칼 extends 무기 {
void 작동() {
System.out.println("칼로 공격");
}
}
class 활 extends 무기 {
void 작동() {
System.out.println("활로 공격");
}
}
class 로켓런처 extends 무기 {
void 작동() { System.out.println("로켓으로 공격"); }
}
실행순서는 부모 생성자가 먼저 호출됨
청둥오리() -> 오리() -> 동물() -> 생물() -> Object()
순으로 호출 되지만, 실질적으로 출력이 실행되는 순서는
Object() -> 생물 -> 동물() -> 오리() -> 청둥오리()
이다.
class 동물{
동물(String 이름){}
동물(String 이름, int 나이){}
}
class 사람 extends 동물{
사람(){
super("홍길동");
//super("홍길동", 20);
}
}
생성자 실행시 super()가 기본적으로 실행되는데, 부모 클래스인 동물 클래스에 매개변수가 있는 생성자만 가지고 있음. super("홍길동")을 하여 매개변수와 맞춰줘야 함
class 동물{
동물(String 이름){}
동물(String 이름, int 나이){}
}
class 사람 extends 동물{
사람(){
this(23);
}
사람(int 나이){
super("홍길동", 나이);
}
}
super()사용하지 않고 this()를 사용하여 호출에 대한 책임을 위임
this(23); 실행시 사람(int 나이){} 생성자에게 책임이 넘어감.
인터페이스 == 클래스
구현 == 상속
java에서는 다중 상속이 불가하므로 구현을 사용(extends 대신에 implements 사용)
implements는 interface에 대해서만 사용
인터페이스는 추상메소드로만 구성되어야 함
class Main {
public static void main(String[] args) {
사람 a사람 = new 홍길동();
변호사 a변호사 = (변호사)a사람;
}
}
abstract class 사람 {
void 말하다(){} //구상메소드
abstract void 일하다(){{ //추상메소드
}
class 홍길동 extends 사람 implements 변호사 {
}
interface 변호사 {
abstract void 일하다(); //추상메소드
}
public
: 접근 제한이 없음
(default)
: 같은 패키지 내에서 접근 가능
proteted
: 같은 패키지 내에서, 다른 패키지 자손 클래스에서 접근 가능
private
: 같은 클래스 내에서만 접근 가능
class Main {
public static void main(String[] args) {
사람 a사람 = new 사람();
a사람.setId(20);
System.out.println("제 번호는 " + a사람.getId() +" 입니다.");
// 출력 : 제 번호는 20 입니다.
}
}
class 사람 {
private int id;
// 세터 메서드
void setId(int id) {
this.id = id;
}
// 게터 메서드
int getId() {
return id;
}
}
private int id
이기 때문에 외부에서는 접근할 수 없으므로 getId()
, setId()
를 만들어서 접근
오류 : 컴파일 오류(문법 오류 등)
예외 : 실행중 오류
try-catch
class Main {
public static void main(String[] args) {
int rs = 계산기.나누다(10, 0);
System.out.println(rs);
}
}
class 계산기 {
static int 나누다(int a, int b) {
int rs = 0;
try {
rs = a / b;
}
catch ( ArithmeticException e ) { //0으로 나눌 경우 예외 처리
rs = 0;
}
return rs;
}
}
v1 : 경우에 따라서 main에서 try-catch가 이뤄질 수 도 있음.
그럴 경우 예외가 날 수 있는 메소드에 throws Exception
을 붙임
class Main {
public static void main(String[] args) {
try {
int rs = 계산기.나누다(10, 0);
System.out.println(rs);
}
catch ( ArithmeticException e ) { //0으로 나눌 경우 예외 처리
rs = 0;
}
}
}
class 계산기 throws ArithmeticException { //예외날 수 있음을 미리 알림
static int 나누다(int a, int b) {
int rs = a / b;
return rs;
}
}
v2 : 예외가 날 것을 예측해서 미리 보고
throw 키워드로 예외 발생 시킴. (ex. throw new IllegalArgumentException();)
class Main {
public static void main(String[] args) {
int[] datas = new int[2];
try {
work(datas);
}
catch ( IllegalArgumentException e ) { // v1 의 코드보다 원인이 좀 더 명확해진다. 즉 v1 보다 더 가독성 높은 코드이다.
System.out.println("이런.. 오류가 발생했군요.");
}
}
static void work(int[] datas) {
if ( datas.length < 3 ) {
throw new IllegalArgumentException(); // 함수가 여기서 멈춤
}
datas[0] = 10;
datas[1] = 20;
datas[2] = 30;
}
}
v3 : 직접 예외클래스 생성
class Main {
public static void main(String[] args) {
int[] datas = new int[2];
try {
work(datas);
}
catch ( 입력된_배열의_사이즈가_3보다_작은_Exception e ) { // 코드 가독성이 v2 코드보다 좋음, 단 예외클래스를 만들어야 해서 귀찮음, 그래서 실무에서는 예외클래스를 꼭 필요할 때만 직접 만듬
System.out.println("이런.. 오류가 발생했군요.");
}
}
static void work(int[] datas) {
if ( datas.length < 3 ) {
throw new 입력된_배열의_사이즈가_3보다_작은_Exception(); // 함수가 여기서 멈춤
}
datas[0] = 10;
datas[1] = 20;
datas[2] = 30;
}
}
class 입력된_배열의_사이즈가_3보다_작은_Exception extends RuntimeException { }