🚀 꼭 매개변수가 아니더라도 변수가 참조하는 객체의 타입을 확인하고자 할 때instance of 연산자를 사용할 수 있다
좌항에는 객체가 오고 우항에는 타입이 위치
좌항의 객체가 우항의 타입이 일치하면 true를 산출하고 그렇지 않으면 false를 산출
if(parent instance of Child child) {
// child 변수 사용
}
사용 예제
public class Person {
//필드 선언
public String name;
//생성자 선언
public Person(String name) {
this.name = name;
}
//메소드 선언
public void walk() {
System.out.println("걷습니다.");
}
}
public class Student extends Person {
//필드 선언
public int studentNo;
//생성자 선언
public Student(String name, int studentNo) {
super(name);
this.studentNo = studentNo;
}
//메소드 선언
public void study() {
System.out.println("공부를 합니다.");
}
}
public class InstanceofExample {
//main() 메소드에서 바로 호출하기 위해 정적 메소드 선언
public static void personInfo(Person person) {
System.out.println("name: " + person.name);
person.walk();
//person이 참조하는 객체가 Student 타입인지 확인
/*if (person instanceof Student) {
//Student 객체일 경우 강제 타입 변환
Student student = (Student) person;
//Student 객체만 가지고 있는 필드 및 메소드 사용
System.out.println("studentNo: " + student.studentNo);
student.study();
}*/
// person이 참조하는 객체가 Student 타입일 경우
// student 변수에 대입(타입 변환 발생)- java 12~
if(person instanceof Student student) {
System.out.println("studentNo: " + student.studentNo);
student.study();
}
}
public static void main(String[] args) {
//Person 객체를 매개값으로 제공하고 personInfo() 메소드 호출
Person p1 = new Person("홍길동");
personInfo(p1);
System.out.println();
//Student 객체를 매개값으로 제공하고 personInfo() 메소드 호출
Person p2 = new Student("김길동", 10);
personInfo(p2);
}
}
🚀 객체는 부품과 같아서 프로그램을 구성하는 객체를 바꾸면 프로그램의 실행 성능이 다르게 나올 수 있다.
객체 사용 방법이 동일하다는 것은 동일한 메소드를 가지고 있다는 뜻
한국 타이어와 금호 타이어는 모두 타이어를 상속하고 있으므로 부모의 메소드를 동일하게 가지고 있다고 말할 수 있다.
타이어 메소드 호출 시 오버라이딩 된 메소드가 호출되는데, 오버라이딩 된 내용은 두 타이어가 다르기 때문에 실행 결과가 다르게 나옴
이것을 '다형성' 이라고 하는데 다형성을 구현하기 위해서는 자동 타입 변환과 메소드 재정의가 필요하다.
1) 필드 다형성
👾 필드 타입은 동일하지만(사용 방법은 동일하지만) 대입되는 객체가 달라져서 실행 결과가 다양하게 나올 수 있는 것을 말한다.
public class Car {
//필드 선언
public Tire tire;
//메소드 선언
public void run() {
//tire 필드에 대입된 객체의 roll() 메소드 호출
tire.roll();
}
}
public class Tire {
//메소드 선언
public void roll() {
System.out.println("회전합니다.");
}
}
public class HankookTire extends Tire {
//메소드 재정의(오버라이딩)
@Override
public void roll() {
System.out.println("한국 타이어가 회전합니다.");
}
}
public class KumhoTire extends Tire {
//메소드 재정의(오버라이딩)
@Override
public void roll() {
System.out.println("금호 타이어가 회전합니다.");
}
}
public class CarExample {
public static void main(String[] args) {
//Car 객체 생성
Car myCar = new Car();
//Tire 객체 장착
myCar.tire = new Tire();
myCar.run();
//HankookTire 객체 장착
myCar.tire = new HankookTire();
myCar.run();
//KumhoTire 객체 장착
myCar.tire = new KumhoTire();
myCar.run();
}
}
☑️ 어떤 자식 객체가 제공하느냐에 따라 drive() 실행 결과는 달라진다. 이것이 '매개변수의 다형성'
public class Vehicle {
//메소드 선언
public void run() {
System.out.println("차량이 달립니다.");
}
}
public class Bus extends Vehicle {
//메소드 재정의(오버라이딩)
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
public class Taxi extends Vehicle {
//메소드 재정의(오버라이딩)
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
public class Driver {
//메소드 선언(클래스 타입의 매개변수를 가지고 있음)
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
public class DriverExample {
public static void main(String[] args) {
//Driver 객체 생성
Driver driver = new Driver();
//매개값으로 Bus 객체를 제공하고 driver() 메소드 호출
Bus bus = new Bus();
driver.drive(bus);
//매개값으로 Taxi 객체를 제공하고 driver() 메소드 호출
Taxi taxi = new Taxi();
driver.drive(taxi);
}
}
응용 예제
public class Product {
int price; // 제품의 가격
int bonusPoint; // 제품구매 시 제공하는 보너스 점수
Product(int price){
this.price = price;
this.bonusPoint = (int)(price / 10.0); // 보너스점수는 제품가격의 10%
}
}
public class Tv extends Product{
Tv () {
// 부모클래스의 생성자 Product(int price)를 호출한다.
super(100); // Tv의 가격을 100만원으로 한다.
}
@Override
public String toString() {
return "Tv";
}
}
public class Audio extends Product{
Audio() {
super(50);
}
@Override
public String toString() {
return "Audio";
}
}
public class Computer extends Product{
Computer () {
super(200);
}
@Override
public String toString() {
return "Computer";
}
}
1. Object 클래스 상속: - 모든 Java 클래스는 자동으로 Object 클래스를 상속받는다. Object 클래스는 모든 클래스의 최상위 부모 클래스이고, 이 클래스에서 제공하는 여러 메서드들이 모든 클래스에서 사용할 수 있게 된다. 그 중 하나가 toString() 메서드
2. toString() 메서드: - Object 클래스에는 이미 toString() 메서드가 정의되어 있기 때문에, 어떤 클래스든 toString() 메서드를 오버라이딩할 수 있다. 따라서, Product 클래스에 따로 toString() 메서드를 정의하지 않더라도, Tv 클래스에서 이 메서드를 오버라이딩하는 것이 가능
public class Buyer { // 고객, 물건을 사는 사람
int money = 1000; // 소유금액
int bonusPoint = 0; // 보너스점수
/* Product[] products = new Product[10]; // 구입한 제품을 저장하기 위한 배열
int i = 0; // Product 배열에 사용될 카운터 */
ArrayList<Product> products = new ArrayList<>();
void buy (Product product){ // 부모클래스 타입으로 매개변수 받음.
// 부모 클래스의 필드 사용. price, bonusPoint
if (money < product.price) {
System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
return;
}
money -= product.price; // 가진 돈에서 구입한 제품의 가격을 뺀다.
bonusPoint += product.bonusPoint; // 제품의 보너스 점수를 추가한다.
products.add(product); // 구입한 제품을 ArrayList에 저장한다.
System.out.println(product + " 을/를 구입하셨습니다.");
}
void summary () { // 구매한 물품에 대한 정보를 요약해서 보여 준다.
int sum = 0; // 구입한 물품의 가격합계
String itemList = ""; // 구입한 물품목록
if(products.isEmpty()) { // ArrayList가 비어있는지 확인한다.
System.out.println("구입하신 제품이 없습니다.");
return;
}
// ArrayList의 i번째에 있는 객체를 얻어 온다.
for(int i =0; i < products.size(); i++) {
Product product = products.get(i);
sum += product.price;
itemList += (i==0) ? "" + product : ", " + product;
}
/* // 반복문을 이용해서 구입한 물품의 총 가격과 목록을 만든다.
// 1) for 이용
for (int i = 0; i < products.length; i++) {
if (products[i] == null)
break;
sum += products[i].price;
itemList += products[i] + ", ";
}
// 2) foreach 사용
// for(각 요소의 타입과 요소를 담을 변수 : 배열 또는 컬렉션)
for (Product product : products) {
if (product == null)
break;
sum += product.price;
itemList += product + ", ";
}
// 3) 반복을 줄이기 위해 인스턴스 변수 사용
for (int i = 0; i < this.i; i++) {
sum += products[i].price;
itemList += products[i] + ", ";
} */
System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
System.out.println("구입하신 제품은 " + itemList + "입니다.");
}
void refund(Product product) { // 구입한 제품을 환불한다.
if (products.remove(product)) { // 제품을 ArrayList에서 제거한다.
money += product.price;
bonusPoint -= product.bonusPoint;
System.out.println(product + "을/를 반품하셨습니다.");
} else { // 제거에 실패한 경우
System.out.println("구입하신 제품 중 해당 제품이 없습니다.");
}
}
}
public class Test {
/*
코딩 순서 : Product -> Tv -> Computer -> Buyer -> Test
*/
public static void main(String[] args) {
Buyer b = new Buyer();
Tv tv = new Tv();
Computer com = new Computer();
Audio audio = new Audio();
b.buy(tv); // Tv 을/를 구입하셨습니다.
b.buy(com); // Computer 을/를 구입하셨습니다.
b.buy(audio); // Audio 을/를 구입하셨습니다.
b.summary(); // 구입하신 물품의 총금액은 350만원입니다. 구입하신 제품은 Tv, Computer, Audio입니다.
System.out.println();
b.refund(com); // Computer을/를 반품하셨습니다.
b.summary(); // 구입하신 물품의 총금액은 150만원입니다. 구입하신 제품은 Tv, Audio입니다.
}
}
class A {
}
class B extends A {
}
class C extends A {
}
class D extends B {
}
class E extends C {
}
public class PromotionExample {
public static void main(String[] args) {
B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a1 = b;
A a2 = c;
A a3 = d;
A a4 = e;
B b1 = d;
C c1 = e;
// B b3 = e;
// C c2 = d;
}
}
부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능
변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로 한정됨
자식 클래스에서 오버라이딩된 메소드가 있다면 부모 메소드 대신 오버라이딩된 메소드가 호출됨
2) 강제 타입 변환 Casting
자식타입 변수 = (자식타입) 부모타입객체;
Parent parent = new Child(); // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환
⚒️ 부모 타입은 자식 타입으로 자동 변환되지 않는다. 대신캐스팅 연산자로 강제 타입 변환을 할 수 있다.
⚒️ 자식 객체가 부모 타입으로 자동 변환하면 부모 타입에 선언된 필드와 메소드만 사용 가능
➡️ 만약 자식 타입에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식타입으로 변환해야 함
🚀 클래스를 선언할 때 final 키워드를 class 앞에 붙이면 최종적인 클래스이므로 더 이상 상속할 수 없는 클래스가 됨
2) final 메소드
public final 리턴타입 메소드( 매개변수, ...) { ... }
🚀 메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 오버라이딩할 수 없는 메소드가 됨
2. protected 접근 제한자
🚀 같은 패키지에서는 default처럼 접근이 가능하나, 다른 패키지에서는 자식 클래스만 접근을 허용
package package1;
public class A {
//필드 선언
protected String field;
//생성자 선언
protected A() {
}
//메소드 선언
protected void method() {
}
}
같은 패키지
package package1;
public class B {
//메소드 선언
public void method() {
A a = new A(); //o
a.field = "value"; //o
a.method(); //o
}
}
다른 패키지
package package2;
import package1.A;
public class C {
//메소드 선언
public void method() {
//A a = new A(); //x
//a.field = "value"; //x
//a.method(); //x
}
}
다른 패키지 & 자식 클래스
package package2;
import package1.A;
public class D extends A {
//생성자 선언
public D() {
//A() 생성자 호출
super(); //o
}
//메소드 선언
public void method1() {
//A 필드값 변경
this.field = "value"; //o
//A 메소드 호출
this.method(); //o
}
//메소드 선언
public void method2() {
//A a = new A(); //x
//a.field = "value"; //x
//a.method(); //x
}
}
⚡️ new 연산자를 사용해서 생성자를 직접 호출할 수는 없고, 자식 생성자에서 super()로 A 생성자를 호출할 수 있다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ItemSearchDTO {
// 상품등록일
private String searchDateType;
// 판매상태
private ItemSellStatus searchSellStatus;
// 삼품 조회 유형
private String searchBy;
// 조회할 검색어 저장할 변수
private String searchQuery;
}
Querydsl과 Spring Data Jpa를 함께 사용하기 위해서는 '사용자 정의 리파지토리'를 정의해야 함
사용자 정의 인터페이스 작성
사용자 정의 인터페이스 구현
Spring Data Jpa 리포지토리에서 사용자 정의 인터페이스 상속
public interface ItemRepositoryCustom {
Page<Item> getAdminItemPage(ItemSearchDTO itemSearchDTO, Pageable pageable);
}
상품 조회 조건을 담고 있는 itemSearchDTO 객체와 페이징 정보를 담고 있는 pageable 객체를 파라미터로 받는 getAdminItemPage 메소드를 정의.
public class ItemRepositoryCustomImpl implements ItemRepositoryCustom{
private JPAQueryFactory queryFactory;
public ItemRepositoryCustomImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}
동적으로 쿼리 생성하기 위해 JPAQueryFactory 클래스 사용
JPAQueryFactory의 생성자로 EntityManager 객체를 넣어줌
EntityManager를 생성자에 주입하는 것은 JPAQueryFactory의 인스턴스를 생성하고 QueryDSL을 통해 데이터베이스 쿼리를 수행하기 위한 기본적인 설정 과정
private BooleanExpression searchSellStatusEq(ItemSellStatus searchSellStatus) {
return searchSellStatus ==
null ? null : QItem.item.itemSellStatus.eq(searchSellStatus);
}
private BooleanExpression regDtsAfter(String searchDateType) {
// 해당 시간 이후로 등록된 상품만 조회
LocalDateTime dateTime = LocalDateTime.now();
if(StringUtils.equals("all", searchDateType) || searchDateType == null) {
return null;
} else if (StringUtils.equals("1d", searchDateType)) {
dateTime = dateTime.minusDays(1);
} else if (StringUtils.equals("1w", searchDateType)) {
dateTime = dateTime.minusWeeks(1);
} else if (StringUtils.equals("1m", searchDateType)) {
dateTime = dateTime.minusMonths(1);
} else if (StringUtils.equals("6m", searchDateType)) {
dateTime = dateTime.minusMonths(6);
}
return QItem.item.regDate.after(dateTime);
}
private BooleanExpression searchByLike(String searchBy, String searchQuery) {
// 상품명에 검색어를 포함하고 있는 상품 또는 상품 생성자 아이디에 검색어 포함하고 있는 상품 조회
if(StringUtils.equals("itemNm", searchBy)) {
return QItem.item.itemNm.like("%" + searchQuery + "%");
} else if(StringUtils.equals("createdBy", searchBy)) {
return QItem.item.createdBy.like("%" + searchQuery + "%");
}
return null;
}
BooleanExpression은 QueryDSL에서 쿼리의 조건을 표현하는 데 사용되는 클래스
@Override
public Page<Item> getAdminItemPage(ItemSearchDTO itemSearchDTO, Pageable pageable) {
// 조건에 따라 아이템 리스트를 조회
List<Item> content = queryFactory
.selectFrom(QItem.item)
.where(regDtsAfter(itemSearchDTO.getSearchDateType()),
searchSellStatusEq(itemSearchDTO.getSearchSellStatus()),
searchByLike(itemSearchDTO.getSearchBy(),
itemSearchDTO.getSearchQuery()))
.orderBy(QItem.item.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch(); // 쿼리를 실행하여 결과 리스트를 반환
// 전체 레코드 수를 계산
Long result = queryFactory.select(Wildcard.count).from(QItem.item)
.where(regDtsAfter(itemSearchDTO.getSearchDateType()),
searchSellStatusEq(itemSearchDTO.getSearchSellStatus()),
searchByLike(itemSearchDTO.getSearchBy(), itemSearchDTO.getSearchQuery()))
.fetchOne(); // 쿼리를 실행하여 단일 결과(전체 레코드 수)를 반환
// NullPointerException을 방지
long total = (result != null) ? result : 0L;
return new PageImpl<>(content, pageable, total);
}
queryFactory를 이용해서 쿼리를 생성
where 조건절에서 ',' 단위로 넣어줄 경우 and 조건으로 인식
PageImpl 클래스는 Page 인터페이스의 기본 구현체로, 페이징 처리된 결과를 담는 객체