열심히 끝까지
Java1 day08 본문
[8일차 수업내용]
1. 제어자
2. 다형성
=============================================================
1. 제어자 ( modifier )
1-1 ) 제어자란?
- 제어자란 클래스, 변수 또는 메소드의 선언부에 함께 사용되어
부가적인 의미를 부여
- 제어자의 종류는 크게 접근제어자와 그 외의 제어자로 나눌 수 있음
접근제어자 public, protected, (default), private
그 외 제어자 static, final, abstract, transient, synchronized ....
- 제어자는 클래스나 멤버변수와 메소드에 주로 사용하며,
하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능
- 단, 접근제어자는 한번에 4가지 중 하나만 선택해서 사용할 수 있음
1-2 ) static - 클래스, 공통적인
- static은 '클래스의' 또는 '공통적인'의 의미를 가지고 있음
- 인스턴스변수는 하나의 클래스로부터 생성되었더라도
각기 다른 값을 유지하지만
클래스변수는 인스턴스에 관계없이 같은 값을 갖음
- static이 붙는 멤버변수와 메소드 그리고 초기화블록은 인스턴스가 아닌
클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있음
* static이 사용될 수 있는 곳 - 멤버변수, 메소드, 초기화블록
제어자 대상 의미
static 멤버변수 - 모든 인스턴스에 공통적으로 사용되는 클래스 변수
- 클래스변수 사용은 인스턴스 생성이 필요 없음
- 클래스가 메모리에 로드될 때 사용
메소드 - 인스턴스를 생성하지 않고도 호출 가능한
static 메소드가 됨
- static 메소드 내에서는 인스턴스 멤버들을
직접 사용할 수 없음
1-3 ) final - 마지막의, 변경될 수 없는
- final은 '마지막의' 또는 '변경될 수 없는'의 의미를 가지고 있으며
거의 모든 대상에 사용될 수 있음
- 변수에 사용되면 값을 변경할 수 없는 상수가 되며,
메소드에 사용되면 오버라이딩 할 수 없게 되고
클래스에 사용되면 자신을 확장하는 자손클래스를 정의하지 못함
제어자 대상 의미
final 클래스 - 변경될 수 없는 클래스, 확장될 수 없는 클래스
=> final로 지정된 클래스는
다른 클래스의 조상이 될 수 없음
메소드 - 변경될 수 없는 메소드
=> final로 지정된 메소드는 오버라이딩을 통해
재정의 될 수 없음
멤버변수, 지역변수 - 변수 앞에 final이 붙으면 값을 변경할 수 없는
상수가 됨
1-4 ) abstract - 추상의, 미완성인
- abstract는 '미완성'의 의미를 가지고 있음
- 메소드의 선언부만 작성하고 실제 수행내용은 구현하지 않는
추상메소드를 선언하는데 사용됨
* abstract가 사용될 수 있는 곳 - 클래스, 메소드
제어자 | 대상 | 의미 |
abstract | 클래스 | 클래스 내에 추상메소드가 선언되어 있음을 의미 |
메소드 | 선언부만 작성하고 구현부는 작성하지 않는 추상메소드 |
1-5 ) 접근제어자 ( access modifier )
- 접근제어자는 멤버 또는 클래스에 사용되며 해당하는
멤버 또는 클래스 외부에서 접근하지 못하도록
제한하는 역할을 함
- 접근제어자가 default임을 알리기 위해 실제로는
default를 붙이지는 않음
* 접근제어자가 사용될 수 있는 곳 - 클래스, 멤버변수, 메소드, 생성자
제어자 | 같은 클래스 | 같은 패키지 | 자손클래스 | 전체 |
public | O | O | O | O |
protected | O | O | O | X |
(default) | O | O | X | X |
private | O | X | X | X |
public > protecte > (default) > private ( 접근 범위가 넓은 쪽의 순으로 나열 )
① 접근제어자를 이용한 캡슐화(capsulation)
- 클래스나 멤버, 주로 멤버에 접근제어자를 사용하는 이유는
클래스 내부에 선언된 데이터를 보호하기 위해서
- 데이터가 유효한 값을 유지하도록, 또는 비밀번호와 같은 데이터를
외부에서 함부로 변경하지 못하도록 하기 위해서
외부로부터의 접근을 제한하는 것이 필요
- 이를 데이터 감추기(data hiding)라고 하며, 객체지향의 캡슐화(encapsulation)에
해당하는 내용
- 또 다른 이유는 클래스 내에서만 사용되는, 내부 작업을 위해 임시로 사용되는
멤버변수나 부분작업을 처리하기 위한 메소드 등의 멤버들을 내부에 감추기 위함
- 외부에서 접근할 필요가 없는 멤버들을 private으로 지정하여
외부에 노출시키지 않음으로써 복잡성을 줄일 수 있음
② 생성자의 접근제어자
- 생성자에 접근제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있음
- 보통 생성자의 접근제어자는 클래스의 접근제어자와 같지만 다르게 지정할 수 있음
- 생성자의 접근제어자를 private으로 지정하면 외부에서 생성자에 접근 할 수 없으므로
인스턴스를 생성할 수 없게 됨
- 그래도 클래스 내부에서는 인스턴스를 생성할 수 있음
1-6 ) 제어자의 조합
대상 | 사용가능한 제어자 |
클래스 | public, (default), final, abstract |
메소드 | 모든 접근 제어자, final, abstract, static |
멤버변수 | 모든 접근 제어자, final, static |
지역변수 | final |
① 메소드에 static과 abstract를 함께 사용할 수 없음
=> static 메소드는 몸통이 있는 메소드에서만 사용할 수 있기 때문
② 클래스에 abstract와 final을 동시에 사용할 수 없음
=> abstract는 상속을 통해서만 완성되어야 한다는 의미이므로 모순
③ abstract메소드의 접근제어자가 private일 수 없음
=> abstract메소드는 자손클래스에서 구현해줘야 하는데
접근제어자가 private이면 자손클래스에서 접근할 수 없기 때문
④ 메소드에 private과 final을 같이 사용할 필요는 없음
=> 접근제어자가 private인 메소드는 오버라이딩 될 수 없기 때문에
이 둘 중하나만 사용해도 의미가 충분
======================================================================
package example01;
public class Access {
public int publicVar;
protected int protectedVar;
// default int defaultVar;
int defaultVar;
private int privateVar;
void info() {
System.out.println(publicVar);
System.out.println(protectedVar);
System.out.println(defaultVar);
System.out.println(privateVar);
}
}
-------------------------
package example01;
public class AccessEx {
public static void main(String[] args) {
Access ac = new Access();
System.out.println(ac.publicVar);
// public이 붙은 멤버변수는 다른 클래스에서 접근 가능
System.out.println(ac.protectedVar);
// protected가 붙은 멤버변수는 다른 클래스에서 접근 가능
System.out.println(ac.defaultVar);
// default가 붙은 멤버변수는 다른 클래스에서 접근 가능
// System.out.println(ac.privateVar);
// private이 붙은 멤버변수는 같은 클래스에서만 접근 가능
}
}
------------------------------------------------------------
package example02;
import example01.Access;
public class AccessEx02 extends Access{
void method() {
System.out.println(this.publicVar);
// public이 붙은 멤버변수는 다른 패키지에 있는 자손클래스에서 접근 가능
System.out.println(this.protectedVar);
// protected가 붙은 멤버변수는 다른 패키지에 있는 자손클래스에서 접근 가능
// System.out.println(this.defaultVar);
// default가 붙은 멤버변수는 다른 패키지에 있는 자손클래스에서 접근 불가능
// System.out.println(this.privateVar);
// private이 붙은 멤버변수는 같은 클래스를 벗어나는 순간 접근 불가능
}
}
class AccessEx3{
Access ac = new Access();
int num = ac.publicVar;
// int num1 = ac.protectedVar;
// int num2 = ac.defaultVar;
// int num3 = ac.privateVar;
// 다른 패키지에 있는 상속관계가 없는 클래스에서는
// public 접근제어자가 붙어있는 멤버만 접근이 가능하다.
}
-------------------------------------------------------------------
package example02;
public class FinalTest extends Child{
// 1. Parent클래스를 extends 키워드로 상속하려한다면 불가능하다
/*@Override
final void hello() {
}
2. Child클래스를 extends 키워드로 상속하는 것은 불가능하다.
*/
}
final class Parent{}
class Child{
final void hello() {
System.out.println("hi");
}
}
-----------------------------------------------------------------
package example02;
// private이 붙은 멤버변수 값을 초기화하는 방법 ==> getter()/ setter() 메소드 이용
public class TimeTest {
public static void main(String[] args) {
Time t = new Time();
t.setHour(50);
System.out.println(t.getHour());
}
}
class Time{
private int hour, minute, second;
// alt + shift + s + r ==> getter/setter 생성하는 단축키
public int getHour() {
return hour;
}
public void setHour(int hour) {
if(hour < 0 || hour > 23) {
return;
}
this.hour = hour;
}
public int getMinute() {
return minute;
}
public void setMinute(int minute) {
this.minute = minute;
}
public int getSecond() {
return second;
}
public void setSecond(int second) {
this.second = second;
}
}
----------------------------------------------------------------------------
package example02;
// 객체(인스턴스)를 하나만 만들고 싶을 때 사용하는 방법 => SingleTon Pattern
class Singleton{
private static Singleton s = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
if( s == null ) {
return new Singleton();
}
return s;
}
}
public class SingletonTest {
public static void main(String[] args) {
// Singleton s = new Singleton();
Singleton s = Singleton.getInstance();
Singleton s1 = Singleton.getInstance();
System.out.println("s : " + s);
System.out.println("s1 : " + s1);
// 객체가 단 하나만 만들어져 s 와 s1은 같은 주소값을 가진다.
}
}
================================================================
2. 다형성(polymorphism)
2-1 ) 다형성이란?
- 객체지향개념에서 다형성이란 여러가지 형태를 가질 수 있는 능력을 의미하며,
자바에서는 한 타입의 참조변수로 여러 타입의 객체(인스턴스)를
참조할 수 있또록 함으로써 다형성을 프로그램적으로 구현
- 좀 더 구체적으로 말하자면 조상클래스의 타입의 참조변수로
자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것
class Tv{ class CaptionTV extend Tv{
boolean power; String text;
int channel; void caption(){}
void power(){} }
void channelUp(){}
void channelDown(){}
}
Tv t; ==> t가 가리킬 수 있는 멤버의 개수 : 5개
CaptionTv ct; ==> ct가 가리킬 수 있는 멤버의 개수 : 7개
- 지금까지 우리는 Tv t = new Tv(); CaptionTv c = new CaptionTv(); 형식으로만 사용
- 이처럼 인스턴스의 타입과 참조변수의 타입이 서로 일치하는 것이 보통이지만,
Tv와 CaptionTv처럼 클래스가 서로 상속관계에 있을 경우 다음과 같이
조상 클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조하도록 하는 것이 가능
Tv t = new CaptionTv(); (가능)
CaptionTv c = new Tv(); (불가능)
그렇다면...
CaptionTv c = new CaptionTv();
Tv t = new CaptionTv(); 이 둘의 차이는 무엇일까?
- 둘 다 같은 타입의 인스턴스지만 타입에 따라 사용할 수 있는 멤버의 개수가 달라짐
- 그럼 여기서 반대로
CaptionTv c = new Tv(); 는 가능할까에 대한 답변은 불가능하다는 점
실제 인스턴스인 Tv멤버개수(5개)보다 참조변수 c가 사용할 수 있는
멤버 개수가 더 많기 때문
- CaptionTv클래스에는 text와 caption()가 정의되어 있으므로 참조변수 c로는
c.text, c.caption()과 같은 방식으로 c가 참조하고 있는 인스턴스에서 text와 caption()을
사용하려고 시도할 수 있음
하지만, c가 참조하고 있는 인스턴스는 Tv타입이고, Tv타입의 인스턴스에는
text와 caption()이 존재하지 않기 때문에 이를 사용하려면 문제가 발생
- 그래서 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은
존재하지 않는 멤버를 사용하고자 할 가능성이 있으므로 허용하지 않는 것
* 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
(조상타입의 참조변수 <= 자손타입의 인스턴스)
* 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수 없다
(자손타입의 참조변수 >= 조상타입의 인스턴스)
Tv CaptionTv
[ ] [ ]
[ ] [ ]
[ ] [ ]
[ ] [ ]
[ ] [ ]
[ ]
[ ]
2-2 ) 참조변수의 형변환
- 기본형변수와 같이 참조변수도 형변환이 가능
- 단, 서로 상속관계에 있는 클래스에서만 가능하기 때문에
자손타입의 참조변수를 조상타입의 참조변수로,
조상타입의 참조변수를 자손타입의 참조변수로의 형변환이 가능
* 자손타입 -> 조상타입 ( Up-casting ) : 형변환 생략가능
* 조상타입 -> 자손타입 ( Down-casting) : 형변환 생략불가
- 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것이 아니기 때문에
참조변수의 형변환은 인스턴스에 아무런 영향을 주지 못함
- 단지, 참조변수의 형변환을 통해서 참조하고 있는 인스턴스에서 사용할 수 있는
멤버의 범위(개수)를 조절하는 것 뿐
==> 형변환 생략/불가가 헷갈리다면 그냥 형변환을 전부 붙이는 방법을 사용하자
2-3 ) instanceof 연산자
- 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용
- 주로 조건문에 사용되며 instanceof의 왼쪽에는 참조변수를,
오른쪽에는 타입(클래스명)이 피연산자로 위치
- 그리고 연산의 결과로 boolean값인 true와 false중 하나로 반환
- instanceof를 이용한 연산결과로 true를 얻었다는 것은
참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻
===========================================================================
package example03;
public class Car {
String color;
int door;
void drive() {
System.out.println("Drive, Brrr~");
}
void stop() {
System.out.println("stop!");
}
}
class FireEngine extends Car{
void water() {
System.out.println("water!");
}
}
---------------------------------------------------------
package example03;
public class CastingEx01 {
public static void main(String[] args) {
Car car = null; // 4개 존재
FireEngine fe = new FireEngine(); // 상속받은것 4개 + 개인 1개 = 5개
FireEngine fe2 = null; // parent <= child
fe.water();
car = (Car)fe;
// car.water(); Car타입의 참조변수는 water()를 호출할 수 없다.
System.out.println("car : " + car); // 아래와 같은 주소
System.out.println("fe : " + fe);
fe2 = (FireEngine)car;
fe.water();
System.out.println("car : " + car); // 아래 두개도 같은 주소
System.out.println("fe : " + fe);
System.out.println("fe2 : " + fe2);
}
}
-----------------------------
package example03;
public class CastingEx02 {
public static void main(String[] args) {
Car car = new Car(); // 4개
FireEngine fe = null; // 5개
car.drive();
fe = (FireEngine)car;
// 5개로 4개를 가리키는데 이건 크기가 부족 + water()이 없음 그렇기에 에러
fe.water();
}
}
-----------------------------------------------------------------
package example03;
public class PolyArgumentTest {
public static void main(String[] args) {
Buyer b = new Buyer();
b.buy(new TvProduct()); // void buy(TvProduct tv) 내가 부르는 참조형 메소드
System.out.println("현재 남은 돈 : " + b.money);
System.out.println("현재 적립금 : " + b.bonusPoint);
System.out.println();
b.buy(new ComputerProduct());
System.out.println("현재 남은 돈 : " + b.money);
System.out.println("현재 적립금 : " + b.bonusPoint);
}
}
class Product{
int price; // 제품의 가격
int bonusPoint; // 제품 당 적립금
public Product(int price) {
this.price = price;
this.bonusPoint = price / 10;
}
}
class TvProduct extends Product{
public TvProduct() { super(100); }
@Override
public String toString() { return "TvProduct"; }
}
class ComputerProduct extends Product{
public ComputerProduct() { super(200); }
@Override
public String toString() { return "ComputerProduct"; }
}
class Buyer{ // 고객, 물건을 사는 사람
int money = 1000; // 보유금액
int bonusPoint = 0; // 적립금액
// Tv를 구매하기 위한 buy()메소드
void buy(TvProduct tv) {
if(money < tv.price) {
System.out.println("잔액이 부족합니다.");
return;
}
money -= tv.price;
bonusPoint += tv.bonusPoint;
System.out.println(tv + "을/를 구매하였습니다.");
}
// Computer를 구매하기 위한 buy()메소드
void buy(ComputerProduct cp) {
if(money < cp.price) {
System.out.println("잔액이 부족합니다.");
return;
}
money -= cp.price;
bonusPoint += cp.bonusPoint;
System.out.println(cp + "을/를 구매하였습니다.");
}
// 만약, 노트북, 전자레인지, 세탁기, 냉장고, 오디오, 에어컨 ... 등등등 구매를 한다면?
}
-------------------------------------------------------
package example04;
public class PolyArgumentTest02 {
public static void main(String[] args) {
Buyer b = new Buyer();
// void buy(Product p)
b.buy(new TvProduct());
System.out.println("현재 남은 돈 : " + b.money);
System.out.println("현재 적립금 : " + b.bonusPoint);
System.out.println();
b.buy(new ComputerProduct());
System.out.println("현재 남은 돈 : " + b.money);
System.out.println("현재 적립금 : " + b.bonusPoint);
System.out.println();
b.summary();
}
}
class Product{
int price; // 제품의 가격
int bonusPoint; // 제품 당 적립금
public Product(int price) {
this.price = price;
this.bonusPoint = price / 10;
}
}
class TvProduct extends Product{
public TvProduct() { super(100); }
@Override
public String toString() { return "TvProduct"; }
}
class ComputerProduct extends Product{
public ComputerProduct() { super(200); }
@Override
public String toString() { return "ComputerProduct"; }
}
class Buyer{
int money = 1000;
int bonusPoint = 0;
Product[] item = new Product[10];
int i = 0;
// 제품을 구매하기 위한 buy()메소드
// Product p = new TvProduct(); ( O )
// product p = new ComputerProduct(); ( O )
void buy(Product p) {
if(money < p.price) {
System.out.println("잔액이 부족합니다.");
return;
}
money -= p.price;
bonusPoint += p.bonusPoint;
// Product p = new TvProduct();
// iten[0] = new Product(); ( O )
// item[0] = new Tvproduct(); ( O )
// item[0] = new ComputerProduct(); ( O )
/*
* Product p = new Product();
* Product p1 = new Product();
*
* Product[] item = new Product[2];
* item[0] = new Product();
* item[1] = new Product();
*/
item[i++] = p;
System.out.println(p + "을/를 구매하였습니다.");
}
void summary() {
int sum = 0;
String itemList = "";
for(int i = 0; i < item.length; i++) {
if(item[i] == null) {
break;
}
sum += item[i].price;
itemList += item[i] + ", ";
}
System.out.println("구매하신 물품의 총 금액 : " + sum);
System.out.println("구매하신 제품 목록 : " + itemList);
}
}
---------------------------------------
package example03;
public class InstanceofTest {
public static void main(String[] args) {
// instanceof를 이용한 연산결과를 true로 얻었다는 것은
// 참조변수가 검사한 타입으로 형변환이 가능하다는 의미이다.
FireEngine fe = new FireEngine();
if(fe instanceof FireEngine) {
System.out.println("This is a FireEngine instance");
}
if(fe instanceof Car) {
System.out.println("This is a Car instance");
Car car = (Car)fe;
}
if(fe instanceof Object) {
System.out.println("This is a Object instance");
Object obj = (Object)fe;
}
}
}
'Java1(주말)' 카테고리의 다른 글
Java1 day07 (0) | 2022.04.30 |
---|---|
Java1 day06 (0) | 2022.04.24 |
Java1 day05 보충 (0) | 2022.04.24 |
Java1 day05 (0) | 2022.04.23 |
Java1 day04 보충 (0) | 2022.04.23 |