→ 객체를 찍어내는 프레임
필드,메소드등을 설계해놓은 공간.?
객체를 만들때 쓰는 프레임이다.
구성요소: 필드(변수,메소드),생성자,이너클래스 ⇒ 생성자 빼고 클래스의 멤버로 부른다
이너클래스는 뭔데? → 클래스안에 클래스
필드를 속성이라고 부르고, 메소드를 기능이라고 부른다.
클래스로 인해 생성된 객체,
객체를 만드는 과정은? 인스턴스화
객체랑 인스턴스 무슨차이?
객체는 모든 인스턴스를 포함해서 말하는 의미
인스턴스는 약간 어떤 클래스에서부터 나온 지정한 객체
클래스_명 참조_변수명
참조_변수명 = new 생성자();
new 키워드는 생성된 객체를 힙메모레 넣으라는 의미
참조 변수는 실제 데이터가 있는 힙메모리의 주소를 저장하는 변수
멤버 변수 혹은 전역 변수라고도 하기 때문에 클래스
내부 중괄호 안 어느 위치에서나 선언이 가능하다. 하지만 메소드 혹은 생성자의 밖에 선언되어야 한다
파라미터 : 매개변수
클래스 변수,인스턴스 변수,지역 변수로 구분한다.
클래스 변수,인스턴스 변수,지역 변수 뭘로 구분하는데?
→클래스 변수는 static 키워드 쓰고, 지역 변수는 메소드 내에 포함된 모든 변수 즉, 쓰고 버리는 지역 변수임.
class Example { // => 클래스 영역
int instanceVariable; // 인스턴스 변수
static int classVariable; // 클래스 변수(static 변수, 공유변수)
void method() { // => 메서드 영역
int localVariable = 0; // 지역 변수. {}블록 안에서만 유효
}
}
차이점과 용도는 뭔가요?
인스턴스 변수는 인스턴스, 객체 생성될을때 각각 고유한 속성(변수)를 저장하기 위한 변수.
클래스 변수는 한 클래스에서 생겨나는 인스턴스 변수중에 값이 공통된건 static으로 이 변수를 쓴다.
그리고 인스턴스 생성 안해도 언제라도 클래스명.클래스변수명으로 사용할수 있다.
지역 변수는 메소드 내{}에서 쓰고 소멸되는 변수. 스택 메모리에 저장된다.
특정 작업을 수행하는 명령어들의 집합.
구성요소 : 메소드 시그니쳐,메소드 바디로 구분함다.
(제어구분자)(static) 반환타입 메소드명(매개변수){}
()생략 가능, 매개변수 빼고.
하나의 클래스 안에 같은 이름의 메소드를 여러 개 정의하는것이다.
조건 :
오버로딩 vs 오버라이딩
오버로딩(Overloading)은 기존에 없던 새로운 메서드를 정의하는 것이고
오버라이딩(Overriding)은 상속 받은 메서드의 내용만 변경 하는 것이다.
객체를 생성할때 쓰는 초기화 메소드다.
new 키워드로 객체를 생성할때 호출되는게 이거다.
일반 메소드와 생성자의 차이:
오버 로딩도 됨 그래서 여러개 생성자들이 있을수도 있음.
자신이 속한 클래스에서 다른 생성자를 호출하는 경우에 사용한다.
사용 조건
public class Test {
public static void main(String[] args) {
Example example = new Example();
Example example2 = new Example(5);
}
}
class Example {
public Example() {
System.out.println("Example의 기본 생성자 호출!");
};
public Example(int x) {
this();
System.out.println("Example의 두 번째 생성자 호출!");
}
}
//Output
Example의 기본 생성자 호출!
Example의 기본 생성자 호출!
Example의 두 번째 생성자 호출!
this
인스턴스 자신을 가리키고,
인스턴스 자신의 변수에 접근 할수 있는거임
기존의 클래스를 재활용해서 새로운 클래스를 작성하는 자바의 문법 요소다.
즉, 하위 클래스는 상위 클래스의 모든 멤버를 상속 받는다.
상위클래스 하위클래스 개수는 하위 클래스의 멤버 개수가 상위 클래스 보다 같거나 많다.
상속했을때 메모리에 대한건
클래스로부터 확장 되었다는 표현이 낫다.
왜 상속을 쓸까?
class Person {
String name;
int age;
void learn(){
System.out.println("공부를 합니다.");
};
void walk(){
System.out.println("걷습니다.");
};
void eat(){
System.out.println("밥을 먹습니다.");
};
}
class Programmer extends Person { // Person 클래스로부터 상속. extends 키워드 사용
String companyName;
void coding(){
System.out.println("코딩을 합니다.");
};
}
class Dancer extends Person { // Person 클래스로부터 상속
String groupName;
void dancing(){
System.out.println("춤을 춥니다.");
};
}
class Singer extends Person { // Person 클래스로부터 상속
String bandName;
void singing(){
System.out.println("노래합니다.");
};
void playGuitar(){
System.out.println("기타를 칩니다.");
};
}
public class HelloJava {
public static void main(String[] args){
//Person 객체 생성
Person p = new Person();
p.name = "김코딩";
p.age = 24;
p.learn();
p.eat();
p.walk();
System.out.println(p.name);
//Programmer 객체 생성
Programmer pg = new Programmer();
pg.name = "박해커";
pg.age = 26;
pg.learn(); // Persons 클래스에서 상속받아 사용 가능
pg.coding(); // Programmer의 개별 기능
System.out.println(pg.name);
}
}
//출력값
공부를 합니다.
밥을 먹습니다.
걷습니다.
김코딩
공부를 합니다.
코딩을 합니다.
박해커
단. 단일 상속만 허용됨 (extends) 근데 interface는 다중 상속이랑 비슷한 효과를 냄
포함이란 클래스의 멤버로 다른 클래스 타입의 참조 변수를 선언하는거다
이게 뭔말이냐?→ 다른 클래스 객체를 우리 클래스에서 선언해서 써먹는다.
public class Employee {
int id;
String name;
Address address;
public Employee(int id, String name, Address address) {
this.id = id;
this.name = name;
this.address = address;
}
void showInfo() {
System.out.println(id + " " + name);
System.out.println(address.city+ " " + address.country);
}
public static void main(String[] args) {
Address address1 = new Address("서울", "한국");
Address address2 = new Address("도쿄", "일본");
Employee e = new Employee(1, "김코딩", address1);
Employee e2 = new Employee(2, "박해커", address2);
e.showInfo();
e2.showInfo();
}
}
class Address {
String city, country;
public Address(String city, String country) {
this.city = city;
this.country = country;
}
}
// 출력값
1 김코딩
서울 한국
2 박해커
도쿄 일본
Address 클래스에 city,country 변수를 넣고 묶어서 Employee클래스 안에 참조변수를 선언해서
코드 중복을 없앤다.
이게 상속보다 더 많이 쓴데요
근데 무슨 기준으로 상속,포함관계를 나누냐?
→ 클래스 간의 관계가 ~~은 ~~이다.(Is)인지 ~~은 ~~을 가지고 있다.(has) 관계인지 생각해보면 된다.
예) Employee는 Address를 가지고 있다.
SportsCar는 Car이다.
상위 클래스 에서 원래 있던 메소드들을 상속받으면서 다시 짜는걸 말한다.(즉, 덮어쓴다.)
조건
super,super()
super :상위 클래스의 객체, super() : 상위 클래스의 생성자를 호출하는 것.
인스턴스 변수랑 상속 받은 상위 클래스 변수를 참조 변수랑 같은 이름이 되버리면
컴파일러는 인스턴스 멤버를 먼저 참조하기 때문에 따로 구분하기 위한거임.
public class Example {
public static void main(String[] args) {
SubClass subClassInstance = new SubClass();
subClassInstance.callNum();
}
}
class SuperClass {
int count = 20; // super.count
}
class SubClass extends SuperClass {
int count = 15; // this.count
void callNum() {
System.out.println("count = " + count);
System.out.println("this.count = " + this.count);
System.out.println("super.count = " + super.count);
}
}
// 출력값
count = 15
count = 15
count = 20
super()
생성자 안에서만 사용가능하고, 반드시 첫 줄에 와야
합니다.
모든 생성자의 첫 줄에는 반드시 this()
또는 super()
가 선언되어야 한다는 것
public class Test {
public static void main(String[] args) {
Student s = new Student();
}
}
class Human {
Human() {
System.out.println("휴먼 클래스 생성자");
}
}
class Student extends Human { // Human 클래스로부터 상속
Student() {
super(); // Human 클래스의 생성자 호출
System.out.println("학생 클래스 생성자");
}
}
// 출력값
휴먼 클래스 생성자
학생 클래스 생성자
캡슐화 → 접근제어자로 숨길지 정하는 거임
특정 객체 안에 관련된 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것.
쓰는 이유
패키지
목적에 따라 관련된 클래스,인터페이스를 묶어놓은 폴더같은 느낌
접근 제어자
접근 제어자를 통해 외부로부터 데이터를 보호하고, 불필요하게 데이터가 노출되는 것을 방지한다.
이때 private 제어자를 쓸때 데이터를 변경하거나 가져올때 뭘쓰냐?
getter,setter메소드를 쓴다.
public class GetterSetterTest {
public static void main(String[] args) {
Worker w = new Worker();
w.setName("김코딩");
w.setAge(30);
w.setId(5);
String name = w.getName();
System.out.println("근로자의 이름은 " + name);
int age = w.getAge();
System.out.println("근로자의 나이는 " + age);
int id = w.getId();
System.out.println("근로자의 ID는 " + id);
}
}
class Worker {
private String name; // 변수의 은닉화. 외부로부터 접근 불가
private int age;
private int id;
public String getName() { // 멤버변수의 값
return name;
}
public void setName(String name) { // 멤버변수의 값 변경
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age < 1) return;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
// 출력값
근로자의 이름은 김코딩
근로자의 나이는 30
근로자의 ID는 5
setter메소드는 외부에서 메소드에 접근해서 데이터 값을 변경 할수 있게 해준다.
→ 보통 메소드 명을 set~~이다.
getter메소드는 설정한 변수 값을 읽어오는데 사용한다.
→ 메소드 명 get~~
이렇게 활용하면 데이터를 효과적으로 보호하고 값 변경도 할수 있다.
class Friend {
public void friendInfo() {
System.out.println("나는 당신의 친구입니다.");
}
}
class BoyFriend extends Friend {
public void friendInfo() {
System.out.println("나는 당신의 남자친구입니다.");
}
}
class GirlFriend extends Friend {
public void friendInfo() {
System.out.println("나는 당신의 여자친구입니다.");
}
}
public class FriendTest {
public static void main(String[] args) {
Friend friend = new Friend(); // 객체 타입과 참조변수 타입의 일치
BoyFriend boyfriend = new BoyFriend();
Friend girlfriend = new GirlFriend(); // 객체 타입과 참조변수 타입의 불일치
friend.friendInfo();
boyfriend.friendInfo();
girlfriend.friendInfo();
}
}
// 출력값
나는 당신의 친구입니다.
나는 당신의 남자친구입니다.
나는 당신의 여자친구입니다.
Friend girlfriend = new GirlFriend();
→상위 클래스를 참조변수의 타입으로 지정했기 때문에 자연스럽게 참조변수가 사용할 수 있는 멤버의 개수는 상위 클래스의 멤버의 수
가 된다
public class FriendTest {
public static void main(String[] args) {
Friend friend = new Friend(); // 객체 타입과 참조변수 타입의 일치 -> 가능
BoyFriend boyfriend = new BoyFriend();
Friend girlfriend = new GirlFriend(); // 객체 타입과 참조변수 타입의 불일치 -> 가능
// GirlFriend friend1 = new Friend(); -> 하위클래스 타입으로 상위클래스 객체 참조 -> 불가능
friend.friendInfo();
boyfriend.friendInfo();
girlfriend.friendInfo();
}
}
하위 클래스 타입으로 상위클래스 객체 참조? 불가능하다
왜? →실제 객체인 Friend
의 멤버 개수보다 참조변수 friend1
이 사용할 수 있는 멤버 개수가 더 많기 때문이다
구체적으로 말하면
실제 참조하고 있는 인스턴스의 멤버를 기준으로 참조 변수의 타입의 멤버가 실제 인스턴스의 멤버 수보다 작은 것은 실제 사용할 수 있는 기능을 줄이는 것이기에 허용되지만, 그 반대의 경우는 참조하고 있는 인스턴스에 실제로 구현된 기능이 없어 사용이 불가하기 때문이다.
참조변수의 타입 변환
→ 사용할 수 있는 멤버의 개수 조절
사용 조건
public class VehicleTest {
public static void main(String[] args) {
Car car = new Car();
Vehicle vehicle = (Vehicle) car; // 상위 클래스 Vehicle 타입으로 변환(생략 가능)
Car car2 = (Car) vehicle; // 하위 클래스 Car타입으로 변환(생략 불가능)
MotorBike motorBike = (MotorBike) car; // 상속관계가 아니므로 타입 변환 불가 -> 에러발생
}
}
class Vehicle {
String model;
String color;
int wheels;
void startEngine() {
System.out.println("시동 걸기");
}
void accelerate() {
System.out.println("속도 올리기");
}
void brake() {
System.out.println("브레이크!");
}
}
class Car extends Vehicle {
void giveRide() {
System.out.println("다른 사람 태우기");
}
}
class MotorBike extends Vehicle {
void performance() {
System.out.println("묘기 부리기");
}
}
여기서 캐스팅이 가능한지 여부를 알려면
instanceof 연산자를 쓴다.
//참조_변수 instanceof 타입
public class InstanceOfExample {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal instanceof Object); //true
System.out.println(animal instanceof Animal); //true
System.out.println(animal instanceof Bat); //false
Animal cat = new Cat();
System.out.println(cat instanceof Object); //true
System.out.println(cat instanceof Animal); //true
System.out.println(cat instanceof Cat); //true
System.out.println(cat instanceof Bat); //false
}
}
class Animal {};
class Bat extends Animal{};
class Cat extends Animal{};
만약 가능하면 true이고, false는 불가능, 참조 변수가 null일때도 false가 나온다.
그냥 느낀점은 그냥 무조건 연결이 되었으면 (연결 = 상속)캐스팅 가능함 즛 true가 나온다.
A instanceof B = A를 B로 바꿀 수 있나? -> true = 있다
동물을 박쥐로 바꿀 수 있나? -> 다른 동물도 있음
언제 쓰느냐?
소스 코드가 길어지는 등 일일이 생성 객체의 타입을 확인하기가 어려운 상황에서 instanceof
연산자는 형변환 여부를 확인하여 에러를 최소화하는 매우 유용한 수단이 될 수 있습니다.
public class PolymorphismEx {
public static void main(String[] args) {
Customer customer = new Customer();
customer.buyCoffee(new Americano());
customer.buyCoffee(new CaffeLatte());
System.out.println("현재 잔액은 " + customer.money + "원 입니다.");
}
}
class Coffee {
int price;
public Coffee(int price) {
this.price = price;
}
}
class Americano extends Coffee {
public Americano() {
super(4000); // 상위 클래스 Coffee의 생성자를 호출
}
public String toString() {return "아메리카노";}; //Object클래스 toString()메서드 오버라이딩
};
class CaffeLatte extends Coffee {
public CaffeLatte() {
super(5000);
}
public String toString() {return "카페라떼";};
};
class Customer {
int money = 50000;
void buyCoffee(Coffee coffee) {
if (money < coffee.price) { // 물건 가격보다 돈이 없는 경우
System.out.println("잔액이 부족합니다.");
return;
}
money = money - coffee.price; // 가진 돈 - 커피 가격
System.out.println(coffee + "를 구입했습니다.");
}
}
// 출력값
아메리카노를 구입했습니다.
카페라떼를 구입했습니다.
현재 잔액은 41000원 입니다.
다형성이 가지는 특성에 따라 매개변수로 각각의 개별적인 커피의 타입이 아니라 상위클래스인 Coffee
의 타입을 매개변수로 전달받으면, 그 하위클래스 타입의 참조변수면 어느 것이나 매개변수로 전달될 수 있고 이에 따라 매번 다른 타입의 참조변수를 매개변수로 전달해주어야하는 번거로움을 훨씬 줄일 수 있습니다.
기존 클래스들의 공통 요소들을 가져와서 상위 클래스로 만들어낸것
자바에서는 주로 추상 클래스,인터페이스로 추상화를 구현한다.
abstract 제어자
→ 미완성, 추상적인
주로 클래스,메소드에 쓴다.
abstract class AbstractExample{
abstract void start();
}
AbstractExample abstractExample = new AbstractExample(); //에러
메소드 바디가 완성 되기 전까지 객체 생성이 안된다.
쓰는 이유는 만들어져 있던 클래스를 재사용하기에 코드의 중복도 없애고 개발 시간을 줄여 준다.
추상 클래스
객체 생성도 못하는 미완성 클래스인데 왜 쓰느냐?
예)
abstract class Animal {
public String kind;
public abstract void sound();
}
class Dog extends Animal { // Animal 클래스로부터 상속
public Dog() {
this.kind = "포유류";
}
public void sound() { // 메서드 오버라이딩 -> 구현부 완성
System.out.println("멍멍");
}
}
class Cat extends Animal { // Animal 클래스로부터 상속
public Cat() {
this.kind = "포유류";
}
public void sound() { // 메서드 오버라이딩 -> 구현부 완성
System.out.println("야옹");
}
}
class DogExample {
public static void main(String[] args) throws Exception {
Animal dog = new Dog();
dog.sound();
Cat cat = new Cat();
cat.sound();
}
}
// 출력값
멍멍
야옹
이렇게 하위 클래스마다 오버라이딩을 통해 각각 상황에 맞게 메소드 구현이 가능하다.
만약 여러 사람이 함께 개발하면 공통된 속성,기능 이지만 각자 다른 변수나 메소드를 쓰는 경우를 방지할수 있다.
상층부에 가까워질수록 추상화의 정도가 높고 아래로 갈수록 구체화가 된다.
→ 상층부에 가까울수록 공통적인 속성과 기능들이 정의 되어있다.
final
final class FinalEx { // 확장/상속 불가능한 클래스
final int x = 1; // 변경되지 않는 상수
final int getNum() { // 오버라이딩 불가한 메서드
final int localVar = x; // 상수
return x;
}
}
이건 그냥 final을 추가되면 이 대상은 더이상 변경이 불가하거나 확장되지 않는다. 라고 생각하면 돼겠다.
인터페이스
interface IDBConnect {
public void conncet();
public void open();
public void select();
}
class MysqlConn implements IDBConnect {
@Override
public void conncet() {}
@Override
public void open() {}
@Override
public void select() {}
}
class OracleConn implements IDBConnect {
@Override
public void conncet() {}
@Override
public void open() {}
@Override
public void select() {}
}
특정 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 모든 추상메소드를 구현해야한다.
→ 강제성을 주입
다중 구현 예)
interface Animal { // 인터페이스 선언. public abstract 생략 가능.
public abstract void cry();
}
interface Pet {
void play();
}
class Dog implements Animal, Pet { // Animal과 Pet 인터페이스 다중 구현
public void cry(){ // 메서드 오버라이딩
System.out.println("멍멍!");
}
public void play(){ // 메서드 오버라이딩
System.out.println("원반 던지기");
}
}
class Cat implements Animal, Pet { // Animal과 Pet 인터페이스 다중 구현
public void cry(){
System.out.println("야옹~!");
}
public void play(){
System.out.println("쥐 잡기");
}
}
public class MultiInheritance {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.cry();
dog.play();
cat.cry();
cat.play();
}
}
// 출력값
멍멍!
원반 던지기
야옹~!
쥐 잡기
왜 다중 구현이 가능한가?
클래스에서 다중 상속이 불가능했었던 핵심적인 이유는 만약 부모 클래스에 동일한 이름의 필드 또는 메서드가 존재하는 경우 충돌이 발생하기 때문이었습니다.
반면 인터페이스는 애초에 미완성된 멤버를 가지고 있기 때문에 충돌이 발생할 여지가 없고, 따라서 안전하게 다중 구현이 가능합니다.
클래스 상속 받으면서 인터페이스를 구현 하는 예)
abstract class Animal { // 추상 클래스
public abstract void cry();
}
interface Pet { // 인터페이스
public abstract void play();
}
class Dog extends Animal implements Pet { // Animal 클래스 상속 & Pet 인터페이스 구현
public void cry(){
System.out.println("멍멍!");
}
public void play(){
System.out.println("원반 던지기");
}
}
class Cat extends Animal implements Pet { // Animal 클래스 상속 & Pet 인터페이스 구현
public void cry(){
System.out.println("야옹~!");
}
public void play(){
System.out.println("쥐 잡기");
}
}
public class MultiInheritance {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.cry();
dog.play();
cat.cry();
cat.play();
}
}
// 출력값
멍멍!
원반 던지기
야옹~!
쥐 잡기
인터페이스 장점:
역할과 구현을 분리시켜서 사용자 입장에선 복잡한 구현의 내용, 변경과 상관없이 해당 기능을 사용 할 수있는 점.
provider1클래스→provider2로 바꿔야하는 상황일때
그냥 의존해서 쓰면
public class InterfaceExample {
public static void main(String[] args) {
User user = new User(); // User 클래스 객체 생성
user.callProvider(new Provider()); // Provider 객체 생성 후에 매개변수로 전달
}
}
class User { // User 클래스
public void callProvider(Provider provider) { // Provider 객체를 매개변수로 받는 callProvider 메서드
provider.call();
}
}
class Provider { //Provider 클래스
public void call() {
System.out.println("무야호~");
}
}
// 출력값
무야호~
provider2로 쓰면
public class InterfaceExample {
public static void main(String[] args) {
User user = new User(); // User 클래스 객체 생성
user.callProvider(new Provider2()); // Provider객체 생성 후에 매개변수로 전달
}
}
class User { // User 클래스
public void callProvider(Provider2 provider) { // Provider 객체를 매개변수로 받는 callProvider 메서드
provider.call();
}
}
class Provider2 { //Provider 클래스
public void call() {
System.out.println("야호~");
}
}
// 출력값
야호~
이렇게 되는데
인터페이스 쓰면
interface Cover { // 인터페이스 정의
public abstract void call();
}
public class Interface4 {
public static void main(String[] args) {
User user = new User();
// Provider provider = new Provider();
// user.callProvider(new Provider());
user.callProvider(new Provider2());
}
}
class User {
public void callProvider(Cover cover) { // 매개변수의 다형성 활용
cover.call();
}
}
class Provider implements Cover {
public void call() {
System.out.println("무야호~");
}
}
class Provider2 implements Cover {
public void call() {
System.out.println("야호~");
}
}
//출력값
야호~
이렇게 provider 클래스의 내용 변경 또는 교체가 발생하더라도 User
클래스는 더이상 코드를 변경해주지 않아도 같은 결과를 출력할수 있다.
결론적으로 정리하면, 인터페이스는 기능이 가지는 역할과 구현을 분리시켜 사용자로 복잡한 기능의 구현이나 교체/변경을 신경쓰지 않고도 코드 변경의 번거로움을 최소화하고 손쉽게 해당 기능을 사용할 수 있도록 합니다.
반대로 기능을 구현하는 개발자의 입장에서도 선언과 구현을 분리시켜 개발시간을 단축할 수 있고, 독립적인 프로그래밍을 통해 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화할 수 있다는 큰 장점이 있습니다.
추상화 정리
클래스A 로직을 바꿧는데 B도 바꿔야한다면 서로 너무 결합도가 높아서 캡슐화가 안된거임. 그래서 접근제어자로 막는거임
의존성 주입 ->Dependency Injection(DI)
->개중요
객체지향 프로그램에서 DI가 엄청 중요한데 추상화,다형성이 필요하다
상위클래스중에 인스턴스화 할 필요없고 상속만 하면 추상클래스할수 있다.
인터페이스에도 똑같이 할수있다.
근데 차이점은
인터페이스 ->약간 규정? 강제성을 부여
클래스들의 공통 메소드를 인터페이스로 구현해야된다고 보장해주는 것?