16일차 22.10.13
클래스의 타입을 확인하는 키워드이다.
package day16.poly.instanceof_;
public class Student extends Person {
String studentId; //학번
Student(String name, int age, String studentId){
super(name, age);
this.studentId = studentId;
}
String info() {
return super.info() + ", 학번:" + studentId;
}
}
package day16.poly.instanceof_;
public class Teacher extends Person {
String subject; //과목
Teacher(String name, int age, String subject){
super(name, age);
this.subject = subject;
}
String info() {
return super.info() + ", 과목:" + subject;
}
}
package day16.poly.instanceof_;
public class Employee extends Person {
String department; //부서
Employee(String name, int age, String department){
super(name, age);
this.department = department;
}
String info() {
return super.info() + ", 부서:" + department;
}
}
package day16.poly.instanceof_;
public class Person {
String name;
int age;
Person(String name){
this(name,1);// 두개짜리 생성자 호출
}
Person(String name, int age){
this.name = name.toUpperCase();
this.age = age;
}
String info() {
return "이름:" + name + ", 나이:" + age;
}
}
package day16.poly.instanceof_;
public class MainClass {
public static void main(String[] args) {
//instanceof - 객체의 모형을 확인
Person s = new Student("홍길동", 10, "1234");
Person t = new Teacher("이순신", 20, "프로그램");
//Teacher s2 = (Teacher)s;
//이렇게 되면 런타임에러가 뜬다
//실제 아래처럼 해주어야 한다
//Student s2 = (Student)s;
//그래서 아래 처럼 확인을 할 수 있다
casting(s);
casting(t);
}
public static void casting(Person p) {
if(p instanceof Student) { //p가 Student라면 true, 아니면 false
Student s = (Student)p;
System.out.println(s.info());
}else if(p instanceof Teacher) {
Teacher t = (Teacher)p;
System.out.println(t.info());
}
}
}
실제 부모클래스 타입으로 자식클래스 객체를 형성할 수 있다. 어제 배운 내용인데, 부모클래스 변수명 = new 자식클래스()
이런경우에는 어제 배웠듯이 자식클래스의 자체 기능을 제외하고는 다 사용할 수 있다. 하지만 자식 클래스의 자체 기능을 사용하기 위해 캐스팅을 하는 경우가 있는데 instanceof는 캐스팅이 가능한지 확인해주는 키워드이다.
Person(부모클래스) 객체 p가 student라면 true가 나오는 형식이다. true여야만 정보를 출력하는 메서드.
static은 생성자에는 사용이 불가하며, 공유하는 변수라고 생각하면 이해하기 쉬울 거 같다. 실제 static 메서드에서는 일반 변수로 객체생성을 하지 않고는 직접 접근이 불가능하다. static 제한자는 추가로 객체를 생성하지 않고 즉시 접근이 가능하기 때문에 this라는 키워드를 사용할 수 없다. 여태까지 main 함수는 static이었기 때문에 그냥 메서드를 만들어 주었을 때는 객체를 생성하여 메서드를 불러오거나, static을 사용하여 그냥 즉시 사용할 수 있었던 것이다.
package day16.static_.var;
public class Count {
public int a ;
public static int b;
}
package day16.static_.var;
public class MainClass {
public static void main(String[] args) {
Count c1 = new Count();
c1.a++;
c1.b++;
System.out.println("일반변수: " + c1.a);
System.out.println("정적변수: " + c1.b);
Count c2 = new Count();
c2.a++;
c2.b++;
System.out.println("일반변수: " + c2.a);
System.out.println("정적변수: " + c2.b);
Count c3 = new Count();
c3.a++;
c3.b++;
System.out.println("일반변수: " + c3.a);
System.out.println("정적변수: " + c3.b);
//지금 c1.b는 몇일까 - 3
//지금 c2.b는 몇일까 - 3
//static은 객체 생성없이 바로 접근 가능하다.
Count.b++;
System.out.println(Count.b);
}
}
실제 일반 변수는 객체를 생성해서 사용이 되기 때문에 다른 객체에서 값들이 변하지 않지만, 정적변수는 값이 하나이기 때문에 다른 객체에서 변화를 주더라도 변한다.
정적 변수는 객체를 활용하여 사용하지 않고 클래스명만을 사용해서 호출할 수 있다.
package day16.static_.method;
public class Count {
public int a;
public static int b;
//일반 메서드 - 일반변수, 정정변수 둘다 사용가능
public int some1() {
a = 10; //ok
return ++b;//ok
}
//정적 메서드 - 정적변수만 사용가능(단, 객체생성을 통해서는 사용이 가능)
public static int some2() {
//a = 10; //no
Count c = new Count();
c.a = 10; //ok
return ++b;//ok
}
}
package day16.static_.method;
import java.util.Arrays;
public class MainClass {
public static void main(String[] args) {
Count c = new Count();
c.some1();//일반메서드
c.some2();//정적 메서드
//정적메서드 - 객체생성 없이 사용
Count.some2();
//현재 b는? 3 - 메서드가 3번 호출됨
System.out.println(Count.b);
//main은 static이기 때문에
MainClass m = new MainClass();
m.a();
b(); // MainClass.b(); 와 같다
Math.random();
Integer.parseInt("3");
Arrays.toString(new int[3]);
//전부 스테틱 메서드들이기 때문에 객체 생성 없이 바로 사용가능
}
public void a() {
}
public static void b() {
}
}
static은 메서드에도 동일하게 적용이 된다.
일반 메서드 같은 경우에는 일반 변수와 정적 변수가 둘다 사용이 가능하지만, 실제 정적메서드에서는 객체생성을 하지 않고는 직접적으로 일반 변수를 사용할 수 없다.
실제 Count c = new Count();를 하여 객체를 생성하고 c.a 를 통해서 일반 변수에 접근이 가능하다.
정적 메서드는 메인에서 사용할 때 Count c = new Count();를 통해서 사용할수도 있지만, 보통은 클래스명인 Count.메서드명을 사용하여 호출 할 수 있다.
메인 클래스의 메인함수 밖에서 만들어지는 메서드들을 사용할 때 static을 사용하면 즉시 호출할 수 있지만 그렇지 않은 경우에는 메인클래스 객체를 생성하여 호출해줘야한다. 같은 클래스에 있는 경우 static을 사용하면 같은 클래스내이기 때문에 따로 클래스명을 사용할 필요없이 즉시 호출이 되는 것이다.
package day16.static_.basic;
public class Calculator {//사용자 클래스
//계산기마다 다른 값을 지닌다면 일반변수
//계산기마다 똑같은 값이라면 정적변수
private String model;
private int result;
public static double pi;
//일반 멤버변수를 사용하는 메서드는 static이면 안된다.
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public int getResult() {
return result;
}
public void setResult(int result) {
this.result = result;
}
/*
* 일반변수를 사용하지 않고, 범용성 있게 사용할 메서드는 static을 붙이면 좋다.
*/
public static double circle(int r) {
return Math.PI * r * r;
}
}
static은 같은 값을 유지해야 되는 변수 및 함수에 사용할 수 있다. 한마디로 값이 변하지 않는 값들을 정적 변수 혹은 메서드로 활용해서 고정 시키는 느낌이라고 보면 될 거 같다.
package day16.static_.basic;
/*
Arrays.toString() 의 기능을 따라하기
PrintArray클래스
1. toArray() - int[]을 매개변수로 입력받아 배열의 모형을 문자열로 리턴하는 메서드
2. toArray() - 1번과 내용은 같고, char[]을 받도록 overloading
2. toArray() - 1번과 내용은 같고, String[]을 받도록 overloading
*/
public class PrintArray {
private PrintArray() {} // 객체생성 불가
// PrintArray(){
//
// }
static String toArray(int[] a) {
String s = "[";
for(int i = 0; i < a.length; i++) {
if(i == a.length -1) {
s += a[i] + "]";
break;
}
s += a[i] + ", ";
}
return s;
}
static String toArray(char[] a) {
String s = "[";
for(int i = 0; i < a.length; i++) {
if(i == a.length -1) {
s += a[i] + "]";
break;
}
s += a[i] + ", ";
}
return s;
}
static String toArray(String[] a) {
String s = "[";
for(int i = 0; i < a.length; i++) {
if(i == a.length -1) {
s += a[i] + "]";
break;
}
s += a[i] + ", ";
}
return s;
}
}
package day16.static_.basic;
import java.util.Arrays;
public class MainClass {
public static void main(String[] args) {
//PrintArray p = new PrintArray();
int[] a = {1,2,3,4,5};
char[] c = {'a', 'b', 'c'};
String[] str = {"A","B","C"};
System.out.println(PrintArray.toArray(a));
System.out.println(PrintArray.toArray(c));
System.out.println(PrintArray.toArray(str));
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(c));
System.out.println(Arrays.toString(str));
}
}
public class PrintArray {
private PrintArray() {} // 객체생성 불가
}
이런식으로 생성자를 제어해버리면 객체 생성이 불가해진다. 기존에 만들었던 여러 배열을 문자열로 표현해주는 오버로딩이된 메서드들인데 이를 객체 생성없이 사용하기 위해서 각각의 함수에 아래와 같이 static을 붙여준다.
static String toArray(int[] a) {
String s = "[";
for(int i = 0; i < a.length; i++) {
if(i == a.length -1) {
s += a[i] + "]";
break;
}
s += a[i] + ", ";
}
return s;
}
int[] a = {1,2,3,4,5};
char[] c = {'a', 'b', 'c'};
String[] str = {"A","B","C"};
System.out.println(PrintArray.toArray(a));
System.out.println(PrintArray.toArray(c));
System.out.println(PrintArray.toArray(str));
이런식으로 메인에서 클래스명. 메서드명()을 사용한다면 객체를 생성하지 않고도 메서드를 사용할 수 있다. static이라는 공간에 즉시 접근이 가능하기 때문. 기존과 같이 잘 출력이 되는 것을 볼 수 있다.
정적 초기화자는 main() 메서드에 앞서 딱 한번만 실행이 되기 때문에 반드시 한번만 실행이 되어야할 로직이 있는 경우에 사용 가능
package day16.static_.singleton;
public class Computer {
public static int a = 10;
//정적초기화자 - 1회만 실행됨
static {
System.out.println("단 1번 실행 - 클래스명이 호출될 때");
}
}
package day16.static_.singleton;
public class MainClass {
public static void main(String[] args) {
System.out.println(Computer.a);
System.out.println(Computer.a);
System.out.println(Computer.a);
System.out.println(Computer.a);
}
}
이런식으로 여러 번 호출을 하여도 한 번만 실행이된다.
싱글톤 패턴은 객체를 딱 하나만 생성하고 전역의 개념으로 객체를 사용할 수 있게하는 것이다. 객체의 생성을 제한하기 위해서 사용한다. 실제 객체를 private static으로 만들어주고 생성자는 private으로 제한해준다. 마지막으로 객체타입의 getter를 public static으로 사용할 수 있게 해줘서 딱 하나만 생성이 될 수 있게 한다.
package day16.static_.singleton;
public class Singleton {
//디자인패턴 - 클래스를 설계하는 기법
//싱글톤패턴 - 객체를 1개만 생성되도록 설계하는 기법
//1. 나 자신의 객체를 멤버변수로 선언하고, 1개로 고정
private static Singleton instance = new Singleton();
//2. 객체생성을 못하도록 생성자를 private 처리
private Singleton() {}
//3. s 변수를 getter로 반환
public static Singleton getInstance() {
return instance;
}
public String site ="aaa";
}
package day16.static_.singleton;
public class MainClass {
public static void main(String[] args) {
//Singleton s = new Singleton(); 2번으로 인해 막힘
Singleton s = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
Singleton s3 = Singleton.getInstance();
System.out.println(s == s2 && s2 == s3);
s.site = "이순신";
System.out.println(s2.site);
}
}
이런식으로 생성자가 제한되어 무분별한 객체 생성이 불가능해지고, 여러 개의 객체를 getter를 통해서 생성하더라도 같은 주소값을 가지게된다. s, s2 , s3는 서로 다른 객체 이름의 가지지만 같은 주소값을 가지며 한개의 값을 변화 시키더라도 모든 곳에서 값이 변하게 된다.
package day16.final_.method;
public class Child extends Parent{
public void method01() {};
//public void method02(){}; // 오버라이딩 금지
}
package day16.final_.method;
public class Parent {//class에 final을 사용하면 상속할 수 없다.
public void method01() {}
public final void method02() {}
}
이런식으로 부모 클래스 메서드에서 final을 사용하면 자식 클래스에서의 사용이 제한된다.
package day16.final_.field;
public class Constant {
public static final double PI = 3.14;//상수 대문자
public static final long EARTH_RADIUS = 6371L;
public static final int O2 = 32;
}
상수는 변하지 않는 값이다. 여러 곳에서 사용해야 하는 경우 public static final 로 변수를 지정해주면 값이 변하지 않고 고정된 값으로 여러 곳에서 사용할 수 있다.
package day16.final_.field;
public class Person {
/*
* final 변수는 값을 변경할 수 없기 때문에
* 직접 초기값을 지정 or 생성자를 통해서 초기화
*/
public final String nation = "대한민국";
public final String ssn;
public String name;
Person(String ssn, String name){
this.ssn = ssn;
this.name = name;
}
}
package day16.final_.field;
public class MainClass {
public static void main(String[] args) {
Person hong = new Person("123123-123123","홍길동");
// hong.nation = "아메라카"; //값의 변경 금지
// hong.ssn = "333333-333333";
Person park = new Person("222222-222222","박찬호");
System.out.println(hong.ssn);
System.out.println(park.ssn);
/////////////////////////////////
//상수의 사용
System.out.println(Constant.EARTH_RADIUS);
System.out.println(Constant.PI);
System.out.println(Math.PI);
System.out.println(Math.E);
}
}
final은 값을 변경할 수 없기 때문에 초기에 직접 초기화를 하거나, 생성자를 통해서 초기화를 할 수 있다.
package day16.abs.bad;
public class Store {
public void apple() {
System.out.println("자식에서 재정의 하세요");
}
public void melon() {
System.out.println("자식에서 재정의 하세요");
}
public void orange() {
System.out.println("자식에서 재정의 하세요");
}
}
package day16.abs.bad;
public class SeoulStore extends Store {
//부모님에 있는 3개의 메서드를 반드시 오버라이딩 해야하는데, 깜빡한다면?
public void apple() {
System.out.println("서울의 사과는 500원");
}
public void melon() {
System.out.println("서울의 멜론은 600원");
}
}
package day16.abs.bad;
public class MainClass {
public static void main(String[] args) {
SeoulStore s = new SeoulStore();
s.apple();
s.melon();
s.orange();//오버라이딩을 반드시 해야하는데,
//하지 않았다면 잘못된 메서드가 실행될 수 있다.
}
}
실제 abstract을 사용하는 것은 자식 클래스의 오버라이딩이 필수로 요구되어야 한다. 위의 케이스의 경우에는 실수로 오버라이딩을 하지 않은 orange 메서드가 있다. 이런경우를 방지하기 위해서 추상메서드 및 추상클래스를 사용한다. 위를 보면 사과, 멜론은 재정의가 된 반면에 오렌지는 재정의가 되지 않았다. 추상클래스 및 추상 메서드를 사용한다면 이런 케이스를 미연에 방지할 수 있다.
package day16.abs.good;
public abstract class Store {
/*
* 1.메서드에 abstract를 붙이면 추상메서드가 된다({}가 없는 메서드)
* 2.추상메서드를 사용하고 싶다면, 추상클래스가 되어야 한다.
*/
public abstract void apple();
public abstract void melon();
public abstract void orange();
//멤버변수, 생성자, 일반메서드 전부 사용 가능
private String name = "호식이네";
//생성자 사용 가능
public Store() {
System.out.println("추상클래스 생성자 호출");
}
//일반메서드 사용 가능
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package day16.abs.good;
//1.자식 클래스도 abstract이 되거나
//2.추상메서드들을 override해주어야함
public class SeoulStore extends Store {
@Override
public void apple() {
System.out.println("서울지점 사과는 500원");
}
@Override
public void melon() {
System.out.println("서울지점 멜론은 600원");
}
@Override
public void orange() {
System.out.println("서울지점 오렌지는 700원");
}
}
package day16.abs.good;
public class BusanStore extends Store{
@Override
public void apple() {
System.out.println("부산지점 사과는 500원");
}
@Override
public void melon() {
System.out.println("부산지점 멜론은 600원");
}
@Override
public void orange() {
System.out.println("부산지점 오렌지는 700원");
}
}
package day16.abs.good;
public class MainClass {
public static void main(String[] args) {
//1.추상클래스는 객체생성이 안된다.
// 반드시 자식으로 구체화 된다.
// Store s = new Store();
Store s = new BusanStore();//new SeoulStore();
s.apple();
s.melon();
s.orange();
System.out.println(s.getName()); //상속받은
Store s1 = new SeoulStore();//new SeoulStore();
s1.apple();
s1.melon();
s1.orange();
System.out.println(s.getName()); //상속받은
}
}
이런식으로 추상 메서드를 만들기 위해서는 클래스 또한 추상 클래스여야한다. 추상 클래스는 객체 생성이 안되기 때문에 자식으로 구체화를 하여야 하며, 추상클래스에서 멤버변수, 생성자, 일반메서드 또한 생성할 수 있다. 하지만 추상클래스를 상속받는 자식 클래스는 자식 클래스 또한 추상 클래스 이거나, 부모클래스에 있는 추상 메서드들을 오버라이딩을 해야한다. 이런식으로 모든 추상메서드들이 자식클래스에서 오버라이딩이 되어 수정이 되었다.
부모클래스에서 추상메서드는 public abstract 메서드명(); 이다 { } 중괄호가 들어가지 않고 생성이 된다.
package quiz13;
public abstract class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
//오버라이딩을 강제화 한다면 추상메서드로 선언
public abstract double getArea();
//오버라이딩을 강제화 하지않는다면 일반메서드로 선언
public String getName() {
return "도형이름:" + name;
}
}
package quiz13;
public class Rect extends Shape{
//사각형은 생성될 때 이름과, 변의 길이를 받도록 처리하고,
//getArea()는 사각형의 넓이를 계산하도록 오버라이딩 처리
//main에서 확인
private int side;
public Rect(String name, int side) {
super(name);
this.side = side;
}
@Override
public double getArea() {
return side*side;
}
}
package quiz13;
public class Circle extends Shape {
//원은 생성될 때 이름과, 반지름의 길이를 받도록 처리하고,
//getArea()는 원의 넓이를 계산하도록 오버라이딩 처리
//main에서 확인
private int radius;
public Circle(String name, int radius) {
super(name);
this.radius = radius;
}
@Override
public double getArea() {
return radius * radius * Math.PI;
}
}
package quiz13;
public class MainClass {
public static void main(String[] args) {
Shape s = new Rect("사각형",10);
System.out.println(s.getName());
System.out.println("넓이: " + s.getArea());
System.out.println();
Shape s1 = new Circle("원", 10);
System.out.println(s1.getName());
System.out.println("넓이: " + s1.getArea());
}
}
이런식으로 추상 클래스를 활용하여 자식클래스에서 재정의를 해줄 수 있다.