티스토리 뷰
서문
자바의 정석 기초편 챕터 7편을 기재합니다.
목적은 공부한 내용을 기록하는 것에 있기 때문에 완전한 문장이 아닐 수도 있습니다.
또한 모든 내용을 적은 것은 아닙니다.
- 참고 자료
자바의 정석 ( CH.7 ) - 객체지향 2
상속
- 기존의 클래스로 새로운 클래스를 작성하는 것(코드의 재사용)
- 두 클래스의 부모와 자식으로 관계를 맺어지는 것
- 자손은 조상의 모든 멤버를 상속받음(생성자, 초기화블럭 제외)
- 자손의 멤버가 조상의 멤버보다 작을 수 없음(같거나 많음)
- 자손의 변경이 조상에 영향을 끼치지 않음
class Child extends Parent {
}
-----------------------------
class Parent{
int age;
}
class Child extends Parent{ // 이미 Child는 parent의 age를 가지고 있다.
}
-----------------------------
class Parent {
int age;
}
class Child extends Parent{
void play(){
System.out.println("자식!!");
}
}
-----------------------------
// 3차원멤버를 가진 class를 만들 때 2가지 방법이 있음
class Point{ // 2차원 멤버를 가진 class
int x;
int y;
}
// 1방법. 새로운 클래스에 모든 멤버를 담는 방법
class Point3d{
int x;
int y;
int z;
}
// 2방법. Point를 상속한 후 필요한 z변수를 만들어서 구성
class Point3d extends Point{
int z;
}
- 1번 방법과 2번 방법의 차이
- 1번은 독립적인 class를 생성했기에 영향 받을 클래스가 존재하지 않음
- 2번은 부모클래스가 변경될 경우 자식 클래스도 변경의 영향을 받기 때문에 영향을 받음
Point3d p = new Point3d();
- 상속의 여부와 관련없이 객체를 생성했을 때 참조변수와 객체의 연결은 동일한 매커니즘으로 동작함
포함 관계
- 클래스의 멤버로 참조변수를 선언하는 것
- 작은 단위의 클래스를 만들고, 이 들을 조합해서 클래스를 생성
class Circle{
int x; // 원점의 x좌표
int y; // 원점의 y좌표
int r; // 원의 반지름
}
class Point{
int x; // 원점의 x좌표
int y; // 원점의 y좌표
}
class Circle{
Point c = new Point(); // 원점
int r; // 반지름
}
- Circle은 Point와 포함관계에 있다고 할 수 있음
- 상속관계와 달리 포함관계는 참조변수와 객체가 연결되는 과정이 다름 (circle은 두 번의 참조를 통해 변수 x,y,r을 가진다고 할 수 있다. c(0x100) -> Point(0x200) -> x,y)
-----------------------------------------
class Car{
Engine e = new Engine();
Door[] d = new Door[4];
}
- 상속관계 : "~은(는) ~이다." ( is - a )
- 포함관계 : "~은(는) ~을 가지고 있다." ( has - a ), 거의 대부분
- 원은 점이다. x
- 원은 점을 가지고 있다. ok
단일 상속
Java는 단일상속만을 허용 (c++은 다중 상속 허용)
인터페이스를 사용하면 단일상속처럼 하면서도 다중상속의 효과를 낼 수 있음
다중상속은 여러 문제가 있음
- 충돌
비중이 높은 클래스 하나만 상속처리하고 나머지는 포함관계로 처리함
class TvDVD extends Tv, DVD{ // ERROR, 조상은 하나만 허용
}
----------------------------
class Tv{
boolean power;
int channel;
void power(){ power != power; }
void channelUp(){ channel++; }
void channelDown(){ channel--; }
}
class DVD{
boolean power;
void power(){ power != power; }
void play(){}
void stop(){}
void rew(){}
void ff(){}
}
class TvDVD extends Tv{
DVD dvd = new DVD();
void play(){
dvd.play();
}
void stop(){
dvd.stop();
}
void rew(){
dvd.rew();
}
void ff(){
dvd.ff();
}
}
Object class - 모든 클래스의 조상
- 부모가 없는 클래스는 자동적으로 Object 클래스를 상속받음
- 모든 클래스는 Object클래스에 정의된 11개의 메서드를 상속받음
- toString(), equals(Object obj), hashCode(), ...
class Tv{
}
class Tv extends Object{}
class SmartTv extends Tv{
}
public class InheritanceTest{
public static void main(String[] args){
Circle c = new Circle();
System.out.println(c.toString());
System.out.println(c); // println는 참조변수가 들어오면 toString()이 호출된다.
}
}
> Circle@15db9742
> Circle@15db9742
오버라이딩( overriding )
- 상속받은 조상의 메서드를 자신에 맞게 변경하는 것(내용만)
class Point{
int x;
int y;
String getLocation(){
return "x : " + x + ", y : " + y;
}
}
class Point3D extends Point{
int z;
String getLocation(){ // 오버라이딩, 조상의 메서드를 z를 더해서 변경
return "x : " + x + ", y : " + y + ", z : " + z;
}
}
-----------------------------------------------------
class MyPoint extends Object{
int x;
int y;
MyPoint(){
}
MyPoint(int x, int y){
this.x = x;
this.y = y;
}
public String toString(){
return "x : " + x + ", y : " + y;
}
}
public class OverrideTest{
public static void main(String[] args){
MyPoint p = new MyPoint();
p.x = 3;
p.y = 5;
System.out.println("p.x = " + p.x);
System.out.println("p.y = " + p.y);
System.out.println(p.toString());
MyPoint p2 = new MyPoint(3,5);
System.out.println(p2);
}
}
> p.x = 3
> p.y = 5
> x : 3, y : 5
> x : 3, y : 5
오버라이딩의 조건
- 선언부가 조상 클래스의 메서드와 일치해야 함
- 접근 제어자가 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없음
- 예외는 조상 클래스의 메서드보다 많이 선언할 수 없음 (같거나 작음)
1. 조상의 메서드 선언부와 일치해야함
class Parent{
int x;
int y;
public getLocation(){
return " x : " + x ", y : " + y;
}
}
class Child extends Parent{
int z;
public getLocation(){
return " x : " + x ", y : " + y + ", z : " + z;
}
}
---------------------------------------------------
2. 접근 제어자가 조상 메서드보다 좁은 범위로 설정할 수 없음
(public, protected, (default), private)
class Parent{
public void parentMethod() {
...
}
}
class Child extends Parent{
private void parentMethod() {
...
}
}
---------------------------------------------------
3. 예외는 조상 메서드의 예외보다 작거나 같아야 함
class Parent{
void parentMethod() throws IOException, SQLException {
...
}
}
class Child extends Parent{
void parentMethod() throws IOException{
...
}
}
오버로딩 vs 오버라이딩
- 오버로딩(overloading) : 기존에 없는 새로운 메서드를 정의하는 것(new)
- 오버라이딩(overriding) : 상속받은 메서드의 내용을 변경하는 것(change, modify)
class Parent{
void parentMethod(){}
}
class Child extends Parent{
void parentMethod(){... 변경} // 오버라이딩, 조상의 메서드를 상속받은 것을 내용만 변경해서 사용
void parentMethod(int i){} // 오버로딩, 이름이 같은 것을 매개변수만 바꾸어 사용
void childMethod(){} // 메서드 새로 정의한 것
void childMethod(int i){} // 오버로딩
void childMethod(){} // 중복정의
}
참조 변수 super
- 객체 자신을 가리키는 참조변수, 인스턴스 메서드(생성자) 내에만 존재
- static 메서드에서는 사용 불가
- 조상의 멤버를 자신의 멤버와 구별할 때 사용
1. 조상에도 x가 있고 자식에게도 x가 있을 땐 this 는 자식, super는 조상으로 구별
class Ex{
public static void main(String[] args){
Child c = new Child();
c.method();
}
}
class Parent{
int x = 10; // super.x
}
class Child extends Parent{
int x = 20; // this.x
void method(){
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("super.x = " + super.x);
}
}
> x = 20
> this.x = 20
> super.x = 10
---------------------------------------------
2. 자식에 x가 없을 땐 this와 super는 다 조상을 가리키는 것
class Ex2{
public static void main(String[] args){
Child2 c = new Child2();
c.method();
}
}
class Parent2{
int x = 10;
}
class Child2 extends Parent2{
void method(){
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("super.x = " + super.x);
}
}
> x = 10
> this.x = 10
> super.x = 10
super() - 조상의 생성자
조상의 생성자를 호출할 때 사용
조상의 멤버는 조상의 생성자를 호출해서 초기화
생성자의 첫 줄에는 반드시 생성자를 호출해야 함, 그렇지 않으면 컴파일러가 생성자의 첫 줄에 super()를 호출
class를 만들 때, 꼭 기본 생성자를 만들어주자
1. 자손의 생성자에서 조상의 멤버를 초기화 ( error는 아니지만 권장 x)
class Point{
int x,y;
Point(int x, int y){
this.x = x;
this.y = y;
}
}
class Point3D extends Point{
int z;
Point3D(int x, int y, int z){
this.x = x; // 조상 멤버 초기화
this.y = y; // 조상 멤버 초기화
this.z = z;
}
}
-----------------------------------
2. 조상의 멤버 초기화는 조상이 하기로
class Point{
int x, y;
Point(int x, int y){
this.x = x;
this.y = y;
}
}
class Point3D extends Point{
int z;
Point3D(int x, int y, int z){
super(x, y); // 조상클래스의 생성자 Point(int x, int y)를 호출
this.z = z; // 자식의 멤버변수 초기화
}
}
--------------------------------
3. 생성자의 첫 줄에 생성자를 호출
class Point{
int x;
int y;
Point(){
this(0,0);
}
Point(int x, int y){
this.x = x; // 첫 줄에 생성자가 없음, 아래처럼 컴파일러가 자동으로 super()을 넣어줌
this.y = y;
}
}
class Point extends Object{
int x;
int y;
Point(){
this(0,0); // 생성자 호출
}
Point(int x, int y){
super(); // 이걸로 변환된다.
this.x = x;
this.y = y;
}
}
-----------------------------------------
3-1. 생성자를 안했을 때 error 예시
class Point{
int x;
int y;
Point(int x, int y){
// 생성자가 없음
this.x = x;
this.y = y;
}
String getLocation(){
return "x : " + x + ", y : " + y;
}
}
class Point3D extends Point{
int z;
Point3D(int x, int y, int z){
// 생성자가 없음
this.x = x;
this.y = y;
this.z = z;
}
String getLocation(){
return "x : " + x + ", y : " + y + ", z : " + z;
}
}
class PointTest{
public static void main(String[] args){
Point3D p3 = new Point3D(1,2,3); // ERROR 발생, 즉 Point3D 생성자를 호출할 때 컴파일러가 super();를 추가해주고 Point로 가보니 기본 생성자가 없기 때문에 error가 발생한 것
}
}
> error constructor Point()
> location : class Point
> Point3D(int x, int y, int z){
}
패키지(package)
- 서로 관련된 클래스의 묶음
- JAVA 8 기준, 약 4000개 클래스
- 클래스는 클래스파일(*.class), 패키지는 폴더, 하위 패키지는 하위 폴더
- 클래스의 실제 이름은 패키지를 포함(java.util.String)
- rt.jar는 클래스들을 압축한 파일(JDK설치경로\jre\lib)
- JAVA 9부터 MODULE개념으로
- 패키지의 선언
- 패키지는 소스파일의 첫 번째 문장으로 단 한 번 선언
- 같은 소스 파일의 클래스들은 모두 같은 패키지에 속함
- 패키지 선언이 없으면, 이름없는(unnamed, default) 패키지에 속함
- cmd로 java 코드를 실행할 때, 그 실행할 파일이 속한 bin폴더까지 가서 실행, 아니면 환경설정으로 설정
package com.code.book;
public class PackageTest{
public static void main(String[] args){
System.out.println("Hello, World!!")
}
}
클래스 패스 (classpath)
- 클래스파일(*.class)의 위치를 알려주는 경로
- 환경변수 classpath로 관리하며 경로간의 구분자는 ';'를 사용
- classpath에 패키지의 루트를 등록해주어야 함
- 검색창에 "시스템 환경 변수 편집"에서 등록
import 문
클래스를 사용할 때 패키지이름을 생략할 수 있음
java.lang패키지의 클래스는 import하지 않고도 사용할 수 있도록 되어 있음
String, Object, System, Thread 등등
import문은 패키지문과 클래스 선언문 사이에 선언
import문은 컴파일 시에 처리되므로 프로그램의 성능에 영향이 없음
이름이 같은 클래스가 속한 두 패키지를 import할 떄 클래스 앞에 패키지명을 붙여주어야 함
- java.sql.Date
- java.util.Date
static import
// import를 사용하지 않고 클래스 사용
class ImportTest{
java.util.Date today = new java.util.Date();
}
----------------------------------------
// import문 사용
import java.util.Date;
class ImportTest{
Date today = new Date();
}
---------------------------------------
// static import 문
import static java.lang.Math;
System.out.println(random()) // Math.random()인데 Math를 생략 가능
제어자(modifier)
클래스와 클래스 멤버(멤버변수, 메서드)에 부가적인 의미를 부여하는 것
접근 제어자 : public, protected, (default), private 중 하나만 사용
그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp
하나의 대상에 여러 제어자가 사용될 수 있음 (접근 제어자는 하나만)
보통 접근제어자 + 그 외 제어자 순으로 사용
public class ModifierTest{
public static final int WIDTH = 200;
public static void main(String[] args){
System.out.println("WIDTH = " + WIDTH);
}
}
static - 클래스의, 공통적인
대상 | 의미 |
---|---|
멤버변수 | - 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 됨 - 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능 - 클래스가 메모리에 로드될 때 생성 |
메서드 | - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 됨 - static 메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없음 |
class StaticTest{
static int width = 200; // cv ( static변수 ), 간단초기화
static int height = 120; // cv ( static변수 )
static { // 클래스 초기화 블럭, 복잡 초기화
// static 변수 초기화 수행
}
static int max(int a, int b){ // 클래스 메서드 ( static메서드 )
return a > b ? a : b;
}
}
final - 마지막의, 변경될 수 없음
- String, Math 등등
대상 | 의미 |
---|---|
클래스 | 변경될 수 없는 클래스, 확장될 수 없는 클래스를 의미, 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없음 |
메서드 | 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없음 |
멤버변수 | 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 됨 |
지역변수 | 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 됨 |
1. final이 클래스에 사용
final class FinalTest{ // 조상이 될 수 없는 클래스
final int MAX_SIZE = 10; // 값을 변경할 수 없는 멤버변수(상수)
final void getMaxSize(){ // 오버라이딩될 수 없는 메서드
final int LV = MAX_SIZE; // 값을 변경할 수 없는 지역변수(상수)
return MAX_SIZE;
}
}
abstract - 추상의, 미완성의 (추상화)
- 미완성이기 때문에 이걸로 객체를 생성할 수 없음
- 추상 클래스를 상속받아서 완성시킨 후 객체를 생성 가능
대상 | 의미 |
---|---|
클래스 | 클래스 내에 추상메서드가 선언되어 있음을 의미 |
메서드 | 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알림 |
abstract class AbstractTest{ // 추상 클래스( 추상 메서드를 포함한 클래스 )
abstract void move(); // 추상 메서드 ( 구현부가 없는 메서드 )
}
AbstractTest a = new AbstractTest(); // ERROR, 추상 클래스로 인스턴스 생성불가
접근 제어자 (access modifier)
- private : 같은 클래스 내에서만 접근이 가능
- (default) : 같은 패키지 내에서만 접근이 가능
- protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능
- public : 접근 제한이 없음
제어자 | 같은 클래스 | 같은 패키지 | 자손 클래스 | 전체 |
---|---|---|---|---|
public | 가능 | 가능 | 가능 | 가능 |
protected | 가능 | 가능 | 가능 | |
(default) | 가능 | 가능 | ||
private | 가능 |
public class AccessModifierTest{ // class 앞에는 public 또는 default
// 나머지에는 public, default, protected, private 사용 가능
int iv;
static int cv;
void method{
}
}
---------------------------------------
package pkg1;
class MyParent{
private int prv; // 같은 클래스
int dft; // 같은 패키지
protected int prt; // 같은 패키지 + 자손(다른 패키지)
public int pub; // 제한 없음
public void printMembers(){
System.out.println(prv); // ok
System.out.println(dft); // ok
System.out.println(prt); // ok
System.out.println(pub); // ok
}
}
public class MyParentTest{
public static void main(String[] args){
MyParent p = new MyParent();
System.out.println(p.prv); // error, 같은 클래스가 x
System.out.println(p.dft); // ok
System.out.println(p.prt); // ok
System.out.println(p.pub); // ok
}
}
캡슐화와 접근 제어자
접근 제어자를 사용하는 이유?
외부로부터 데이터를 보호하기 위해
- 데이터에 직접접근은 막고 메서드를 통해 간접접근하는 것을 권장
외부에는 불필요한, 내부적으로만 사용되는 부분을 숨기기 위해
public class Time{
private int hour; // 외부에서의 직접 접근을 막음
private int minute;
private int second;
public int getHour(){ return hour;} // 외부에서 메서드에 접근하는 것은 가능, 메서드를 통한 간접접근은 허용해야함
public void setHour(int hour){
if (isValidTime(hour)) return;
this.hour = hour;
}
private boolean isValidTime(int hour){
return hour < 0 || hour > 23;
}
}
다형성 (polymorphism) 가장 중요한 개념
여러 가지 형태를 가질 수 있는 능력
조상 타입 참조 변수로 자손 타입 객체를 다루는 것
객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때 차이?
- Tv t = new SmartTv(); (가능)
- Tv t 는 Tv가 가진 5개 속성과 기능만 사용할 수 있음
- SmartTv S 는 Tv가 가진 5개 속성 및 기능과 SmartTv가 가진 2개의 속성 및 기능을 사용할 수 있음
자손타입의 참조변수로 조상 타입의 객체를 가리킬 수 없음
- SmartTv S = new Tv(); (불가능)
- Tv는 5개 밖에 없는데 2개 기능이 추가될 수 없음
class Tv{
boolean power;
int channel;
void power(){ power = !power; }
void channelUp(){ ++channel; }
void channelDown(){ --channel; }
}
class SmartTv extends Tv{
String text;
void caption(){}
}
Tv t = new Tv();
SmartTv s = new SmartTv();
// 자식 타입으로
Tv t = new SmartTv(); // 불일치, 조상 자손의 관계에서 가능
SmartTv s = new Tv(); // error, 스마트티비 리모콘으로 tv를 운영하기에 쓸 수 없는 기능이 있다.
참조변수의 형변환
- 사용할 수 있는 멤버의 갯수를 조절하는 것(주소나, 값이 변하는 것은 아님, 리모콘의 기능만 바꾸는 것)
- 조상 자손 관계의 참조변수는 서로 형변환 가능
class Car{
String color;
int door;
void drive(){
System.out.println("drive");
}
void stop(){
System.out.println("stop");
}
}
class FireEngine extends Car{
void water(){
System.out.println("water");
}
}
FireEngine f = new FireEngine();
Car c = (Car) f; // upscaling, 소방차는 차이기에 차의 기능을 할 수 있음
FireEngine f2 = (FireEngine)c; // downscaling, 차에서 소방차로 바꿔서 사용 가능
Ambulance a = (Ambulance)f2; // error
-------------------------------------
Car car = null; // 참조 주소 값 null
FireEngine fe = new FireEngine(); // fe 객체 생성
FireEngine fe2 = null; // fe 참조 주소 값 null
fe.water();
car = fe; // Car car = (Car)fe와 같은 것, 기능이 감소하는 것이기 때문에 형변환 생략가능(항상 안전)
car.water(); // error, Car는 소방차의 기능을 사용할 수 없다.
fe2 = (FireEngine)car; // FireEngine fe2 = (FireEngine)car, 기능을 늘리는 건 안전하지 않기 때문에 생략 불가
fe2.water(); // Car -> FireEngine 했기 때문에 소방차 기능을 사용할 수 있음
-------------------------------------------------------
Car car3 = null;
FireEngine fe3 = null;
Car car4 = (Car) fe3; // 객체가 없어도 형변환은 가능
FireEngine fe4 = (FireEngine)car4;
car4.drive() // error, null이기 때문에
fe4.water() // error, null이기 때문에
car4 = new Car();
fe4 = new FireEngine();
car4.drive(); // 가능
fe4.water(); // 가능
--------------------------------------------------------
Car car5 = new Car(); // Car 객체 생성
FireEngine fe = (FireEngine)car5; // 형변환 실행 error, ClassCastException , 컴파일 오류가 아니다.
fe.water(); // Car 인스턴스에 water는 없다.
instanceof 연산자
- 참조변수의 형변환 가능여부 확인에 사용, 가능하면 true 반환
- 형변환하기 전에 instanceof 연산자로 확인하고 하는 것을 권장
- 조상은 다 true
- 자신도 true
void doWork(Car c){
if (c instanceof FireEngine){ // 형변환이 가능한지 확인
FireEngine fe = (FireEngine)c; // 형변환
fe.water();
}
}
FireEngine fe = new FireEngine();
System.out.println(fe instanceof Object); // 조상에 대해 다 true
System.out.println(fe instanceof Car); // 조상에 대해 다 true
System.out.println(fe instanceof FireEngine); // 자기 자신도 true
> true
> true
> true
Q. 참조변수의 형변환을 왜 할까?
- 참조변수을 변경하므로써 사용할 수 있는 멤버의 갯수를 조절하기 위해서
Q. instanceof 연산자는 언제 사용할까?
- 형변환 하기 전에 형변환 확인을 위해 사용
매개변수의 다형성
- 참조형 매개변수는 메서드 호출 시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.
- 여러 종류의 객체를 배열로 다룰 수 있음 (Vector class (가변 변수))
class Product{
int price;
int bounsPoint;
}
class Tv extends Product{
super(100);
public String toString(){
return "Tv";
}
}
class Computer extends Product{
super(200);
public String toString(){
return "Computer";
}
}
class Audio extends Product{
super(50);
public String toString(){
return "Audio";
}
}
class Buyer{
int money = 1000;
int bounsPoint = 0;
// void buy(Tv t){
// money -= t.price;
// bounsPoint += t.bounsPoint;
// }
// void buy(Computer c){
// money -= c.price;
// bounsPoint += c.bounsPoint;
// }
// void buy(Audio a){
// money -= a.price;
// bounsPoint += a.bounsPoint;
// }
void buy(Product p){ // 조상타입으로 모든 자손의 매개객체를 대표할 수 있음
money -= p.price;
bounsPoint += p.bounsPoint;
}
}
---------------------------------------------------------
public class MyClass {
public static void main(String args[]) {
Buyer b = new Buyer();
b.buy(new Tv());
b.buy(new Computer());
b.buy(new Audio());
b.summary();
}
}
class Product{
int price;
int bonusPoint;
Product(int price){
this.price = price;
this.bonusPoint = (int)(price/10.0);
}
}
class Tv extends Product{
Tv(){super(100);}
public String toString(){
return "Tv";
}
}
class Computer extends Product{
Computer(){super(200);}
public String toString(){
return "Computer";
}
}
class Audio extends Product{
Audio(){super(50);}
public String toString(){
return "Audio";
}
}
class Buyer {
int money = 1000;
int bonusPoint = 0;
Product[] cart = new Product[10];
int i = 0;
void buy(Product p){
if (money < p.price){
System.out.println("잔액부족");
return;
}
money -= p.price;
bonusPoint += p.bonusPoint;
cart[i++] = p;
}
void summary(){
int sum = 0;
String itemList = "";
for (int i = 0; i<cart.length; i++){
if(cart[i] == null){ break; }
sum += cart[i].price;
itemList += cart[i].toString() + ", ";
}
System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
System.out.println("구입하신 제품은 " + itemList + "입니다.");
}
}
> 구입하신 물품의 총금액은 350만원입니다.
> 구입하신 제품은 Tv, Computer, Audio, 입니다.
추상 클래스(abstract class)
- 미완성 설계도, 미완성 메서드를 가지고 있는 클래스
- 미완성 메서드를 가지고 있다면 미완성 설계도
- 다른 클래스 작성에 도움을 주기 위한 것, 인스턴스 생성 불가
- 상속을 통해 추상 메서드를 완성해야 인스턴스 생성가능
- 꼭 추상메서드가 없어도 추상 클래스는 만들 수 있음
abstract class Player{ // 추상 클래스
abstract void play(int pos); // 몸통{}이 없는 메서드
abstract void stop();
}
Player p = new Player(); // error
class AudioPlayer extends Player{
void play(int pos){} // 추상메서드 구현
void stop(){} // 추상메서드 구현
}
AudioPlayer ap = new AudioPlayer(); // ok
추상 메서드 ( abstract method )
- 미완성 메서드, 선언부만 존재하고 구현부는 없는 메서드
- abstract 리턴타입 메서드이름();
- 꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우
abstract class Player{
abstract void play(int pos); // 추상메서드, 구현부가 없음
abstract void stop(); // 추상메서드, 구현부가 없음
}
class AudioPlayer extends Player{
void play(int pos){/*내용 생략*/} // 추상메서드 구현
void stop(){/*내용 생략*/} // 추상메서드 구현
}
abstract class AbstractPlayer extends Player{ // 1개만 구현했기 때문에 abstract를 써줘야 함, 아직 미완성
void play(int pos){ /*내용 생략*/ } // 추상메서드 구현, 1개만 구현
}
---------------------------------------------------
abstract class Player{
boolean pause;
int currentPos;
Player(){ // 생성자
pause = false;
currentPos = 0;
}
abstract void play(int pos); // 추상 메서드
abstract void stop(); // 추상 메서드
void play(){ // 인스턴스 메서드
play(currentPos); // 추상메서드 호출 (선언부만 알아도 호출은 가능 ) 나중에 자손이 구현부를 만들어줄테니
}
}
--------------------------------------------------
abstract class Player{
abstract void play(int pos); // 추상메서드, 구현부가 없음
abstract void stop(); // 추상메서드, 구현부가 없음
}
class AudioPlayer extends Player{
void play(int pos){System.out.println(pos + "위치에서 재생을 시작합니다.")} // 추상메서드 구현
void stop(){System.out.println("재생을 멈춥니다.")} // 추상메서드 구현
}
public static void main(String[] args){
Player ap = new AudioPlayer(); // 추상클래스지만 리모콘으로 사용 가능
ap.play(100);
ap.stop();
}
> 100위치에서 재생을 시작합니다.
> 재생을 멈춥니다.
추상 클래스의 작성
- 여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나 기존클래스의 공통 부분을 뽑아서 추상클래스로 생성
1. 기존의 클래스만을 생성
class Marine{
int x, y;
void move(int x, int y){}
void stop(){}
void stimPack(){}
}
class Tank{
int x, y;
void move(int x, int y){}
void stop(){}
void changeMode(){}
}
class DropShip{
int x, y;
void move(int x, int y){}
void stop(){}
void load(){}
void unload(){}
}
--------------------------------
2. 기존 클래스의 공통부분을 추상클래스로 뽑아서 생성
abstract class Unit{
int x, y;
abstract void move(int x, int y);
void stop(){}
}
class Marine extends Unit{
void move(int x, int y){}
void stimPack(){}
}
class Tank extends Unit{
void move(int x, int y){}
void changeMode(){}
}
class DropShip extends Unit{
void move(int x, int y){}
void load(){}
void unload(){}
}
// 조상 객체로 배열만들어서 자손객체에 접근
Unit[] group = new Unit[3];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new DropShip();
for(int i = 0; i<group.length; i++ ){
group[i].move(100,200); // Unit에는 move라는 것이 존재
}
Object[] group2 = new Object[3];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new DropShip();
for(int i = 0; i<group.length; i++ ){
group[i].move(100,200); // error, object에는 move가 없다.
}
추상클래스
- 추상클래스
- 관리가 용이하다.
- 설계도를 쉽고 간결하게 작성가능.
- 중복되는 코드 제거.
- 자손클래스를 이용해서 단계별 작성이 가능하여, 중간 수정이 쉽다. (유연함)
GregorianCalendar cal = new GregorianCalendar(); // 구체클래스, 분명한 클래스를 반환
Calendar cal = Calendar.getInstance(); // 추상클래스, 어떠한 자손 클래스를 반환할 지 불분명
public static Calendar getInstance(Locale aLocale){
return createCalendar(TimeZone.getdefault(), aLocale);
}
private static Calendar createCalendar(TimeZone zone, Locale aLocale){
if ( caltype != null){
switch (caltype){
case "buddhist": // 불교력
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese": // 일본력
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory": // 서양력
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
인터페이스 (Interface)
- 추상메서드의 집합
- 구현된 것이 하나도 없는 설계도, 껍데기(모든 멤버가 public)
- 상수, static 메서드, default 메서드 등 사용 가능, 하지만 핵심은 추상 메서드
iv주위로 method가 존재
멤버로 바로 접근하지 않고 메서드를 통해 간접접근을 권장
캡슐화, 데이터를 보호하기 위해서
- t.hour x
- t.getHour() o
추상클래스와의 차이
- 추상클래스는 클래스이며 클래스의 구성요소를 다 가질 수 있지만 미완성 클래스이고 멤버, 생성자, 추상메서드 등을 가진다.
- 인터페이스는 추상메서드(상수 등도 가질 수 있다.)만 가지고 있다.
1. 인터페이스의 기본 구조
interface InterfaceEx{
public static final 타입 상수이름 = 값; // 상수
public abstract 메서드이름(매개변수목록); // 추상메서드
}
------------------------------------------
2. 인터페이스의 예시
interface PlayingCard{
public static final int SPADE = 4; // 항상 public, static, final이다. 그래서 생략 가능
final int DIAMOND = 3; // public static final int DIAMOND = 3;
static int HEART = 2; // public static final int HEART = 2;
int CLOVER = 1; // public static final int CLOVER = 1;
public abstract String getCardNumber(); // public abstract는 생략가능
String getCardKind(); // public abstract String getCardKind();
}
인터페이스의 상속
인터페이스의 조상은 인터페이스만 가능( Object가 최고 조상 아님)
다중상속이 가능
- 선언부가 같고(이름,매개변수이 같고) 내용이 다를 때 "충돌"이 발생하는데, 추상메서드는 선언부가 같으며 구현부가 없으므로 "충돌"이 발생할 가능성이 없기 때문에 다중상속 가능
interface Fightable extends Movable, Attackable{} // 다중상속이 가능
interface Movable{
void move(int x, int y);
}
interface Attackable{
void attack(Unit u);
}
인터페이스의 구현
- 인터페이스에 정의된 추상메서드를 구체화하는 것, 완성하는 것
1. 인터페이스 구현의 기본 구조
class 클래스이름 implements 인터페이스이름 {
// 인터페이스에 있는 추상메서드를 모두 완성시켜야함
}
---------------------------------------
2. 인터페이스 구현 예시
interface Fightable extends Movable, Attackable{}
interface Movable{
void move(int x, int y);
}
interface Attackable{
void attack(Unit u);
}
class Fighter implements Fightable{
public void move(int x, int y){ /* 내용 구현 */ } // Movable
public void attack(Unit u){/ /* 내용 구현 */ } // Attackable
}
-----------------------------------------
2.1 만약 다 구현하지 않았을 경우, abstract를 추가
abstract class Fighter implements Fightable{
public void move(int x, int y){ /* 내용 구현 */ } // Movable
// public void attack(Unit u){/ /* 내용 구현 */ } // Attackable
}
인터페이스를 이용한 다형성
- 인터페이스도 구현 클래스의 부모? yes, 하지만 좀 다름
- 다중상속에 대한 관점이 다름
- 매개변수로 인터페이스가 사용되었을 때, 인터페이스를 구현한 클래스의 인스턴스에만 가능
class Fighter extends Unit implements Fightable{
public void move(int x, int y){ /* 내용 생략 */ }
public void attack(Fightable f){ /* 내용 생략 */ } // **매개변수로 인터페이스가 들어있다. 이건 Fightable을 구현한 클래스의 인스턴스에만 가능하다.**
}
// Unit과 Fightable은 Fighter의 부모관계 클래스 및 인터페이스, 다형성이 적용된다.
Unit u = new Fighter();
Fightable f = new Fighter(); // Fightable에 정의된 메서드만 사용가능, Fighter가 더 많은 기능이 있어도 리모콘이 Fightable이기 때문에 Fightable이 가진 메서드만 사용
------------------------------------------------------
2. Fightable 인터페이스를 구현한 Fighter를 반환
Fightable method(){
return new Fighter();
}
class Fighter extends Unit implements Fightable{
public void move(int x, int y){ /* 내용 생략 */ }
public void attack(Fightable f){ /* 내용 생략 */ } // **매개변수로 인터페이스가 들어있다. 이건 Fightable을 구현한 클래스의 인스턴스에만 가능하다.**
}
인터페이스의 장점
두 대상(객체)간의 '연결, 대화, 소통'을 돕는 중간역할을 함
선언(설계)와 구현을 분리시킬 수 있게 해줌(변경 용이, 유연, 느슨한 결합)
- 예컨대 tv의 인터페이스는 삼성이든, LG이든 다 비슷하거나 똑같다. 구현체인 삼성이나 LG는 자신만의 구현체를 만들지만 껍데기는 tv다. 즉 인터페이스는 거의 바뀌지 않으며, 구현체만 바뀌는 것이다.
개발 시간을 단축
표준화 가능(JDBC, 여러 db(구현체)를 변경하기에 용이하게 만든 interface의 집합)
서로 관계없는 클래스들을 관계 맺어줌( 공통요소로 묶을 수 있음 )
1. 직접적인 결합
class A {
public void methodA(B b){
b.methodB(); // b를 직접 호출
}
}
class B{
public void methodB(){
System.out.println("methodB()");
}
}
class InterfaceTest{
public static void main(String[] args){
A a = new A();
a.methodA(new B());
}
}
-----------------------------------------
2. 간접적인 결합
interface I { void methodB();}
class B implements I{
public void methodB(){
System.out.println("methodB() in B class");
}
}
class A {
public void methodA(I i){
i.methodB(); // 실행부는 i로
}
}
class C implements I{
public void methodB(){
System.out.println("methodB() in C class");
}
}
public static void main(String[] args){
A a = new A();
a.methodA(new B()); // B구현체를 사용해서 메서드를 사용
a.methodA(new C()); // C구현체를 사용해서 메서드를 사용
}
----------------------------------------------------
3. 관계 없는 것들을 공통요소로 묶을 수 있음
interface Repairable{}
class SCV extends GroundUnit implements Repaireble{}
class Tank extends GroundUnit implements Repaireble{}
class DropShip extends AirUnit implements Repaireble{}
void repair(Repairable r){
if (r instanceof Unit){
Unit u = (Unit)r;
while (u.hitPoint!=u.MAX_HP){
u.hitPoint++;
}
}
}
- 변화에 빠른 대응, 코드의 변경을 줄여 인재(인간이 행하는 오류)를 최소화시키는 것이 핵심이라고 생각함.
default method, static method
- 인터페이스에 default, static method 추가 기능(JDK 1.8 이상)
- 인터페이스에 새로운 메서드(추상)를 추가하기 어려움
- 해결책 - dafault method!!
- 충돌문제가 발생, 구현체마다 다르다면?
- 여러 인터페이스의 디폴트 메서드간의 충돌문제
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩하여 사용
- 디폴트 메서드와 조상 클래스의 메서드간의 충돌
- 조상 클래스의 메서드가 우선권을 가지고 디폴트 메서드는 무시됨
interface MyInterface{
void method();
void newMethod(); // 새로운 추상 메서드를 추가하면 이와 연결된 구현체들에서 모두 구현을 진행해야 하는 불편함이 생김
}
interface MyInterface2{
void method();
default void newMethod(){ // 아예 인터페이스에서 구현을 해버려서 각각의 구현체들의 변경을 막을 수 있음 (하지만 구현체마다 다르다면..?)
}
}
내부 클래스(inner class)
- 클래스 안에 클래스
- 내부 클래스의 장점
- 내부 클래스에서 외부클래스의 멤버에 접근이 용이
- 코드의 복잡성을 줄임(캡슐화)
- class B가 class A에서만 사용되고 굳이 외부에 둘 필요가 없다면 내부클래스로 만들어서 사용할 수 있음
1. 기본적인 클래스들
class A{
}
class B{
}
----------------------
2. 내부 클래스
class A{ // 외부 클래스
class B{ // 내부 클래스
}
}
----------------------
3-1. 내부 클래스 예시 - 외부
class AAA{
int i = 100;
BBB b = new BBB();
}
class BBB{
void method(){
AAA a = new AAA();
System.out.println(a.i);
}
}
class CCC{
BBB b = new BBB(); // 접근 가능
}
public class InnerTest{
public static void main(String[] args){
BBB b = new BBB();
b.method();
}
}
> 100
-----------------------------------
3-2 내부 클래스 예시 - 내부
class AAA{
int i = 100;
BBB b = new BBB();
class BBB{
void method(){
System.out.println(i);
}
}
}
// class CCC{
// BBB b = new BBB(); // 접근 불가, 하는 방법은 있음
// }
public class InnerTest{
public static void main(String[] args){
AAA a = new AAA();
a.b.method();
}
}
> 100
----------------------------------------
4. 내부 클래스의 종류
class Outer{
int iv = 0; // 인스턴스
static int cv = 0; // 스태틱
void method(){
int lv = 0; // 로컬
}
}
class Inner{
class InstanceInnerClass{} // 인스턴스 클래스
static class StaticInnerClass{} // 스태틱 클래스
void method(){
class LocalInnerClass{} // 로컬 클래스
}
}
내부 | 특징 |
---|---|
인스턴스 클래스 Instance class | 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버처럼 다루어짐. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언 |
스태틱 클래스 Static class | 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static멤버처럼 다루어짐. 주로 외부 클래스의 static 멤버, 특히 static 메서드에서 사용될 목적으로 선언 |
지역 클래스 Local class | 외부 클래스의 메서드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용 |
익명 클래스 Anonymous class | 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용) |
내부 클래스의 제어자와 접근성
- 내부 클래스의 제어자는 변수에 사용가능한 제어자와 동일
- public protected default private
- 기본적으로 iv, cv, lv와 비슷하다고 생각하면 쉬움
1. 내부 클래스 제어자와 접근성 예시
class Ex{
class InstanceClass{
int iv = 100; // ok
static int cv = 100; // error, static 변수 선언 x
static final int CONST = 100; // final static은 상수이므로 o
}
static class StaticClass{
int iv = 200; // ok
static int cv = 200; // static클래스만 static멤버를 정의할 수 있음, 객체 생성 없이 사용할 수 있는게 static 멤버... 그러므로 static class에서만 static 멤버를 사용할 수 있음
}
void method(){
class LocalClass{
int iv = 300; // ok
static int cv = 300; // error, static변수를 선언 x
static final int CONST = 300; /// static final은 상수이므로 o
}
}
}
---------------------------------------
2. 내부 클래스 제어자와 접근성 예시 2
class Ex2 {
class InstanceClass{}
static class StaticClass{}
InstanceClass ic = new InstanceClass();
static StaticClass sc = new StaticClass();
static void staticMethod(){
InstanceClass obj1 = new InstanceClass(); // 불가, 스태틱 클래스에서는 인스턴스 클래스 사용 불가
StaticClass obj2 = new StaticClass(); // 가능
}
void instanceMethod(){
InstanceClass obj1 = new InstatnceClass(); // 가능
StaticClass obj2 = new StaticClass(); // 가능, 인스턴스에서 스태틱은 사용 가능
LocalClass obj3 = new LocalClass(); // 불가능, 내부 클래스는 메서드 안에서만
}
void myMethod(){
class LocalClass{}
LocalClass lc = new LocalClass();
}
}
----------------------------------------------------
3. 내부 클래스 제어자와 접근성 예시 3
- 내부클래스에서 외부클래스의 private 변수에 접근 가능
- 지역클래스에서는 상수에만 접근이 가능
- 상수(constant pool)이 존재해서 따로 관리
class Outer{
private int outerIv = 0;
static int outerCv = 0;
class InstanceClass{
int ilv = outerIv; // 내부클래스에서 외부클래스의 PRIVATE 변수에 접근 가능
int ilv2 = outerCv;
}
static class StaticClass{
int siv = outerIv; // 불가능, 스태틱 클래스에서는 외부 클래스의 인스턴스 멤버 접근이 불가능
static int scv = outerCv; // 가능
}
void myMethod(){
int lv = 0; // 지역변수, 메서드 종료시 소멸
final int LV = 0; // 지역상수, jdk 1.8부터는 final 생략 가능하고 final이 없으면 변수지만 변경되지 않는다면 상수 취급
class LocalClass{
int liv = outerIv; // 가능
int liv2 = outerCv; // 가능, 내부클래스의 객체가 외부 클래스의 지역변수보다 오래 살아남을 수 있으므로 변수는 불가
// int liv3 = lv; // 불가능, (JDK 1.8부터 에러는 아님)
int liv4 = LV; // 상수는 가능
}
}
}
-------------------------------------------
4. 내부 클래스의 제어자 예시 4
class Outer{
class InstanceClass{
int iv = 100;
}
static class StaticClass{
int iv = 200;
static int cv = 200;
}
void myMethod(){
class LocalClass{
int iv = 400;
}
}
}
class Ex4{
public static void main(String[] args){
Outer oc = new Outer();
Outer.InstanceClass ic = oc.new InstanceClass();
System.out.println("ic.iv = " + ic.iv);
System.out.println("Outer.StaticClass.cv = " + Outer.StaticClass.cv );
Outer.StaticClass sc = new Outer.StaticClass();
System.out.println("sc.iv = " + sc.iv);
}
}
>ic.iv = 100
>Outer.StaticClass.cv = 200
>sc.iv = 200
참고 : 생성된 class 종류;
Ex4.class
Outer.class
Outer$InstanceClass.class
Outer$StaticClass.class
Outer$1LocalClass.class // 1은 여러 개가 있을 수 있으므로
----------------------------------------
5. 내부 클래스의 제어자 예시 5
class Outer{
int value = 10;
class Inner{
int value = 20;
void method(){
int value = 30;
System.out.println(" value : " + value); // 가까운 순서
System.out.println(" this.value : " + this.value);
System.out.println(" Outer.this.value : " + Outer.this.value);
}
}
}
class Ex5{
public static void main(String[] args){
Outer ot = new Outer();
Outer.Inner inner = ot.new Inner();
inner.method();
}
}
> value : 30
> this.value : 20
> Outer.this.value : 10
익명 클래스 (Anonymous class)
- 이름이 없는 일회용 클래스, 정의와 생성을 동시에
1. 익명 클래스 기본적인 구조
new 조상클래스이름(){
// 멤버 선언
}
new 구현인터페이스이름(){
// 멤버 선언
}
--------------------------
2. 익명 클래스 예시
class Ex{
Object iv = new Object(){ void method(){} }; // 익명 클래스
static Object cv = new Object(){ void method(){} }; // 익명 클래스
void myMethod(){
Object lv = new Object(){ void method(){} }; // 익명 클래스
}
}
컴파일 후
Ex.class
Ex$1.class // 익명
Ex$2.class // 익명
Ex$3.class // 익명
--------------------------
3. 익명 클래스 예시 2
import java.awt.*; // java의 윈도우 프로그래밍
import java.awt.event.*;
class Ex2{
public static void main(String[] args){
Button b = new Button("Start");
b.addActionListener(new EventHandler());
}
}
// 한번만 사용할 것인데 클래스를 만든다는 것이 불필요
class EventHandler implements ActionListener{ // EventHandler 이름을 없애고, 부모인 ActionListener(또는 인터페이스)를 사용해서 익명 클래스 만들 수 있음
public void actionPerformed(ActionEvent e){
System.out.println("ActionEvent occured!");
}
}
b.addActionListener(new ActionListener(){ // 익명 클래스
public void actionPerformed(ActionEvent e){
System.out.println("ActionEvent occured!!!" );
}
})
- 자바의 정석 6-7장 반복 복습, 가장 핵심내용