Skip to content

Java基础之面向对象

更新: 5/9/2025 字数: 0 字 时长: 0 分钟

我们都说Java是一门面向对象的语言,那么什么是面向对象?

首先解释这个问题我们要解释什么是面向?

Oriented——面向

showing the direction in which something is aimed——展现某物所指向的目标

因此面向的含义可以被初略的理解为以某个东西/概念为导向/核心

重载与重写的区别

重载

重载发生在同一个类或父子类之间,要求名相同但参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。

重写

重写发生在父子类之间,是子类对父类函数进行重新编写

  1. 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。(这一点说明重写是对父类方法所应对的情景的进一步细化,符合父子类间关系)
  2. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  3. 构造方法无法被重写

面向对象的三大特性

封装

封装针对的是成员变量,将一个成员变量隐藏在类的内部,不允许外部直接访问就称为封装。但是可以提供一些可以被外界访问的方法来操作属性。

既然封装了为什么还要get和set

有这样的疑惑说明写过的项目代码的get和set还是以直接存取为主要实现。

但是我们必须要知道的是get和set不仅可以直接存取(方法体还是直接使用“点”),还可以我们人为的控制,比如set传进来的参数是非法的情况下,我们可以在set方法中对这种情况进行处理,这样就避免了直接存取所带来的麻烦。

继承

继承是指父子类之间的关系,有时我们多个类存在相同的参数,且还有多个函数是调用这些参数实现的,那么我们完全可以使用继承的方式来实现代码的复用。

多态

多态体现为使用子类/实现类来创建父类/接口的实例

java
People p=new Student() //在这个代码中Student类是People类的子类

多态成功实现了让一个类存在多种不同的特性,我们只需要跟据需要的特性去实例化一个类即可,一旦我们想要切换特性,只需要修改实例化的类即可

这样一来我们就大大减小了维护成本,同时还提高了代码的复用度。

抽象类与接口有什么区别和共同点

相同点

抽象类和接口均不能被实例化,里面均存在抽象方法。

不同点

抽象类

里面可以有参数,只能被一个类继承

接口

不可以有参数,可以被多个类实现

设计目的

接口

接口强调的是标准,即你一个类既然实现了我的接口,那就要实现我接口里所有的函数,且入参和出参我都定义好了,你不能直接改变。

通过这种方式实现了我们只需要直到一个接口的各个函数是干什么用的就可以直接使用,不需要管起内部实现。

最直接的例子:Spring框架的标准

抽象类

抽象类则更强调代码的复用,但是在此基础上抽象类还希望开放一些方法出来,自己不实现(往往是由于没有一个通用的实现方式),而是留给子类跟据情景实现。

DDD引发的思考

在DDD架构中,我们曾有过使用抽象类来对接口进行实现,接着由对具体类对抽象类再进行实现。

这一过程实则是完成代码的解耦

java
@Slf4j
public abstract class AbstractOrderService implements IOrderService {
    protected final IProductPort port;

    protected final IOrderRepository repository;


    public AbstractOrderService(IProductPort port, IOrderRepository repository) {
        this.port = port;
        this.repository = repository;
    }

    @Override
    public void changeOrderPaySuccess(String orderId) {
        repository.changeOrderPaySuccess(orderId);
    }

    @Override
    public List<String> queryNoPayNotifyOrder() {
        return repository.queryNoPayNotifyOrder();
    }

    @Override
    public List<String> queryTimeoutCloseOrderList() {
        return repository.queryTimeoutCloseOrderList();
    }

    @Override
    public boolean changeOrderClose(String orderId) {
        return repository.changeOrderClose(orderId);
    }

    @Override
    public PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception {

        OrderEntity unpaidOrderEntity=repository.queryUnPayOrder(shopCartEntity);

        if(null!=unpaidOrderEntity&& OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())){
            log.info("创建订单存在,已存在未支付订单.userId:{} productId:{} orderId:{}",
                    shopCartEntity.getUserId(),
                    shopCartEntity.getProductId(),
                    unpaidOrderEntity.getOrderId());

            return PayOrderEntity.builder()
                    .orderId(unpaidOrderEntity.getOrderId())
                    .payUrl(unpaidOrderEntity.getPayUrl())
                    .build();
        }else if(null!=unpaidOrderEntity&&OrderStatusVO.CREATE.equals(unpaidOrderEntity.getOrderStatusVO())){
            log.info("新建支付宝订单,创建订单开始:userId{} productId:{} orderId:{}",unpaidOrderEntity.getUserId(), unpaidOrderEntity.getProductId(),unpaidOrderEntity.getOrderId());
            PayOrderEntity payOrder=doPrepayOrder(unpaidOrderEntity.getUserId(),unpaidOrderEntity.getProductId(),unpaidOrderEntity.getProductName(),unpaidOrderEntity.getOrderId(),unpaidOrderEntity.getTotalAmount());
            return PayOrderEntity.builder()
                    .orderId(unpaidOrderEntity.getOrderId())
                    .payUrl(unpaidOrderEntity.getPayUrl())
                    .build();

        }

        ProductEntity productEntity=port.queryProductByProductId(shopCartEntity.getProductId());

        OrderEntity orderEntity = CreateOrderAggregate.buildOrderEntity(productEntity.getProductId(), productEntity.getProductName());
        CreateOrderAggregate orderAggregate = CreateOrderAggregate.builder()
                .productEntity(productEntity)
                .userId(shopCartEntity.getUserId())
                .orderEntity(orderEntity)
                .build();

        this.doSaveOrder(orderAggregate);

        PayOrderEntity payOrderEntity=doPrepayOrder(shopCartEntity.getUserId(),shopCartEntity.getProductId(),productEntity.getProductName(),orderEntity.getOrderId(),productEntity.getPrice());

        log.info("创建订单-完成,生成支付单.userId:{},orderId:{},payUrl:{}",shopCartEntity.getUserId(),orderEntity.getOrderId(),payOrderEntity.getPayUrl());

        return PayOrderEntity.builder()
                .orderId(orderEntity.getOrderId())
                .payUrl(payOrderEntity.getPayUrl())
                .build();
    }

    protected abstract void doSaveOrder(CreateOrderAggregate orderAggregate);

    protected abstract PayOrderEntity doPrepayOrder(String userId, String productId, String productName, String orderId, BigDecimal totalAmount) throws AlipayApiException;

}

在上述代码中我们定义了doSaveOrder和doPrepayOrder这两个方法用于实现订单的保存和预处理。

但是我们随着未来业务的拓展,我们可能有不同的保存方式(放到Redis还是MySQL里?),这样我们就可以使用多个不同的实现类来完成这一功能,降低了维护的成本。

接口的优化

JDK8引入default关键字修饰接口内方法,进而可以在接口中定义方法的实现

java
default void defaultMethod(){
        System.out.println("你好");
}

注意:即使一个接口里只有default方法,你仍然不可以直接实例化接口

同时JDK8还允许使用static修饰接口内的方法

java
static void staticMethod(){
        System.out.println("I'm staticMethod");
    }

在JDK9中为了让default方法更加美观,允许使用private定义接口内方法并给予其实现,private方法尽可以被default方法调用,不可重写也不可以在外部调用

java
public interface InterfaceObject {

    void interfaceMethod();

    default void defaultMethod(){
        System.out.println("你好");
        privateMethod();
    }

    static void staticMethod(){
        System.out.println("I'm staticMethod");
    }

    private void privateMethod(){
        System.out.println("I'm private Method");
    }
}

深拷贝,引用拷贝和浅拷贝是什么

  • 浅拷贝:浅拷贝创建的对象是新的(在内存上开辟了新的地址),但是其成员变量仍然指向被拷贝对象的成员变量,这就导致如果浅拷贝的成员变量发生了修改,被拷贝对象的成员变量也会发生修改。
  • 深拷贝:成员变量也是单独开辟了新的空间,只是该地址上的值和被拷贝对象的成员变量的值一致
  • 引用拷贝:引用拷贝拷贝出的类直接指向了被拷贝对象,引用拷贝对象的任何操作等价于在被拷贝类上进行操作

equals方法和==

  • ==比较的是地址
  • equals在Object类中的实现也是比较的地址,但不同类会有不同的重写方式,我们一般会围绕成员变量之间的区别来重写equals方法

hashcode方法的意义是什么

hashcode方法定义在Object类中,这就意味着所有的类均有hashcode方法。

hashcode方法通过类的地址计算出一个hash值并将该值返回回来(int类型)

在Object类中hashcode方法使用native关键字实现,这意味着hashcode方法是使用C++完成的实现。

注意

尽管hashcode方法使用地址计算并得到hash值,但hash值相同并不代表是同一个类,因为这个计算过程中可能出现不同的地址算出的数一样(hash碰撞)

但是如果hash值不同,那么我可以认为一定不是同一个类(不同说明了地址一定不一样)

为什么重写equals也要重写hashcode

有的库中判断相同是先去判断hash值是否相同再去判断equals是否相同,这是你如果修改了equals的判断方式,虽然你认为你的相同修改了,但是在使用这些库中,由于他们先使用了默认的hash值判断方式(使用地址计算hash值),这就导致了你的两个分别实例化的类永远不可能相同,因此我们认为应该在实例化equals的同时实例化hashcode

java
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }
   
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
java
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Alice", 30);

        Map<Person, String> map = new HashMap<>();
        map.put(p1, "Person 1");

        System.out.println(p1.equals(p2));
        System.out.println(map.get(p2));
        System.out.println(map.get(p1));
    }
}

HashMap的get方法是先比较hash值再比较equals进行判断相等,这个时候如果不重写hashcode方法(将示例中hashcode方法删掉),则map.get(p2)是无法取到对应的String的

强制要求你重写equals的时候重写hashcode其实就是为了避免你在使用某些库时出现bug,只有两个方法都重写了才能让这些库在判等的时候逻辑和你的保持一致

本站访客数 人次      本站总访问量