Java面向对象面试题总结(上)

简介: 在Java中,重写(Override)与重载(Overload)是两个重要的概念,关联到方法的定义与调用。重写是指子类对继承自父类的方法进行新的实现,以便提供子类特有的行为,其关键在于方法签名一致但方法体不同。重载则允许在同一个类中定义多个同名方法,只要参数列表不同即可,以此提供方法调用的灵活性。重写关注多态性,而重载强调编译时多态。

一、重写和重载

在Java中,重写(Override)和重载(Overload)是面向对象编程中两个非常重要的概念,它们都与方法的定义和调用有关,但两者有着本质的区别。

1、重写(Override)

重写是子类对父类中继承来的方法进行重新定义(也就是方法签名相同,但方法体不同)。当子类需要修改从父类继承来的方法的行为时,就会使用到重写。重写的目的是允许子类提供特定于自己的实现。

重写的规则

  1. 方法名、参数列表必须相同:这是为了保持多态性,即父类类型的引用可以指向子类对象,并调用实际子类对象的方法。
  2. 返回类型:对于非静态方法,返回类型可以是父类方法的返回类型的子类型(Java 5及以后版本支持协变返回类型)。对于静态方法,返回类型必须相同。
  3. 访问修饰符:子类方法的访问级别不能低于父类方法的访问级别(但可以有更高的访问级别)。
  4. 异常:子类方法抛出的异常应该是父类方法抛出异常的子类或没有异常(Java 7及以后版本支持更灵活的异常处理规则)。

示例

class Animal {
   
    void eat() {
   
        System.out.println("This animal eats food.");
    }
}

class Dog extends Animal {
   
    @Override
    void eat() {
   
        System.out.println("Dog eats dog food.");
    }
}

在这个例子中,Dog类重写了Animal类的eat方法。

2、重载(Overload)

重载是在同一个类中,允许存在多个同名的方法,只要它们的参数列表不同即可。参数列表不同意味着参数的数量、类型或顺序至少有一项不同。重载的主要目的是提供灵活的方法调用,允许根据传递的参数类型或数量来调用不同的方法实现。

重载的规则

  1. 方法名必须相同
  2. 参数列表必须不同(参数的数量、类型或顺序不同)。
  3. 方法的返回类型、访问修饰符以及抛出的异常类型与重载无关。

示例

class MathUtils {
   
    // 方法1
    int add(int a, int b) {
   
        return a + b;
    }

    // 方法2,重载
    double add(double a, double b) {
   
        return a + b;
    }

    // 方法3,重载(参数数量不同)
    int add(int a, int b, int c) {
   
        return a + b + c;
    }
}

在这个例子中,MathUtils类有三个名为add的方法,但它们的参数列表不同,因此它们是重载关系。

总结

  • 重写是子类对父类方法的重新定义,用于提供特定于子类的实现。
  • 重载是在同一个类中允许存在多个同名但参数列表不同的方法,用于提供灵活的方法调用。
  • 重写是面向对象多态性的体现,而重载是编译时多态性的体现。

二、equals 和 hashcode

1、简述 hashCode()equals(Object obj) 的作用及其关系

hashCode() 方法用于获取对象的哈希码,即一个整数。这个哈希码在基于哈希的集合(如HashSet、HashMap等)中用于确定对象的存储位置。

equals(Object obj) 方法用于比较两个对象是否相等。默认情况下,它比较的是对象的引用地址;但在自定义类中,通常会重写该方法以比较对象的内容。

这两个方法之间的关系是:如果两个对象通过 equals(Object obj) 方法比较是相等的,那么调用这两个对象中任一对象的 hashCode() 方法必须产生相同的整数结果。这是Java集合框架正常工作的基本要求。

2、为什么要在自定义类中同时重写 hashCode()equals(Object obj) 方法?

如果只重写 equals(Object obj) 方法而不重写 hashCode() 方法,那么在基于哈希的集合中,即使两个对象通过 equals(Object obj) 方法比较是相等的,但由于它们的哈希码不同,这些集合也可能无法正确地处理它们(如无法正确去重)。

因此,为了保证自定义对象在Java集合框架中的正确性,当重写 equals(Object obj) 方法时,通常也需要重写 hashCode() 方法,以确保 equals(Object obj) 相等的对象具有相同的哈希码。

3、请解释为什么hashCode()方法可能产生哈希碰撞,以及这是否会影响equals(Object obj)方法的正确性?

哈希碰撞是指不同的对象产生相同的哈希码。由于哈希码是一个整数,而整数的范围是有限的,而对象的数量可以是无限的,因此哈希碰撞是不可避免的。

哈希碰撞本身不会影响 equals(Object obj) 方法的正确性。equals(Object obj) 方法用于比较对象的内容是否相等,而哈希码只是用于在集合中快速定位对象的一种机制。即使两个对象产生了哈希碰撞,只要它们的 equals(Object obj) 方法比较不相等,它们就不会被视为集合中的相同元素。

然而,哈希碰撞可能会影响基于哈希的集合的性能,因为它可能增加在集合中查找元素的时间复杂度。

4、在重写 hashCode() 方法时,有哪些注意事项?

在重写hashCode()方法时,应该确保在同一个Java应用程序执行期间,只要对象的equals比较中所用的信息没有被修改,那么对该对象多次调用hashCode方法必须始终如一地返回同一个整数。

如果两个对象通过 equals(Object obj) 方法比较是相等的,那么这两个对象的 hashCode() 方法必须产生相同的整数结果。

不要求如果两个对象通过 equals(Object obj) 方法比较是不相等的,那么调用这两个对象中任一对象的 hashCode() 方法必须产生不同的整数结果。但是,为不相等的对象产生不同整数结果可能会提高哈希表的性能。

三、抽象类和接口有什么区别?

抽象类和接口都不能够实例化,但是可以定义抽象和接口类型的引用。一个类如果继承了某个抽象类或者实现某个接口都需要对其中的抽象方法进行实现,否则该类仍然需要被声明为抽象类。

接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部是抽象方法。

四、抽象类是什么?它和接口有啥区别?

接口用于规范,抽象类用于共性。声明方法的存在而不去实现它的类叫做抽象类。接口时抽象类的变体。在接口中,所有的方法都是抽象的。

五、讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序

在Java中,当使用new关键字创建一个类的实例时,会遵循一个特定的顺序来初始化对象。这个顺序涉及到父类和子类的静态数据、构造函数以及字段(成员变量)的初始化。下面是这个顺序的详细解释:

1、静态块和静态变量(父类到子类)

  • 首先,会初始化父类中定义的静态变量和静态初始化块(如果有的话),按照它们在代码中出现的顺序进行。
  • 然后,会初始化子类中定义的静态变量和静态初始化块(如果有的话),也是按照它们在代码中出现的顺序进行。
  • 需要注意的是,静态初始化只会在类被加载到JVM时发生一次,与创建类的实例数量无关。

2、实例变量(父类到子类)

  • 在创建类的实例时,会首先为父类中的实例变量分配内存并默认初始化(例如,数值类型变量初始化为0,对象引用初始化为null)。
  • 然后,会执行父类的非静态初始化块(如果有的话)。
  • 接着,会执行父类的构造函数,此时父类的实例变量可以被显式初始化。
  • 同样的过程会发生在子类上,但会在父类初始化之后进行。子类中的实例变量会被分配内存并默认初始化,然后执行子类的非静态初始化块(如果有的话),最后执行子类的构造函数。

3、构造函数(父类到子类)

  • 在创建对象时,构造函数的调用是遵循从父类到子类的顺序的。这意味着在子类的构造函数中,可以通过super()(显式或隐式)调用父类的构造函数,并且这个调用必须是子类构造函数中的第一条语句(除了注释和变量声明)。
  • 如果子类没有显式调用父类的构造函数,则会自动调用父类的无参构造函数(如果父类没有定义无参构造函数且子类没有显式调用其他构造函数,则会导致编译错误)。

综上所述,当使用new关键字创建类的实例时,执行顺序大致如下:

1、父类静态变量和静态初始化块(按出现顺序)。
2、子类静态变量和静态初始化块(按出现顺序)。
3、父类实例变量默认初始化。
4、父类非静态初始化块(如果有的话)。
5、父类构造函数。
6、子类实例变量默认初始化。
7、子类非静态初始化块(如果有的话)。
8、子类构造函数。

这个顺序确保了父类在子类之前被完全初始化,从而保证了继承体系中的正确性和稳定性。

六、Java 创建对象有几种方式?

  • new 创建新对象
  • 通过反射机制创建对象
  • 采用 clone 创建对象
  • 通过序列化机制
目录
相关文章
|
4月前
|
安全 架构师 Java
Java大厂面试高频:Collection 和 Collections 到底咋回答?
Java中的`Collection`和`Collections`是两个容易混淆的概念。`Collection`是集合框架的根接口,定义了集合的基本操作方法,如添加、删除等;而`Collections`是一个工具类,提供了操作集合的静态方法,如排序、查找、同步化等。简单来说,`Collection`关注数据结构,`Collections`则提供功能增强。通过小王的面试经历,我们可以更好地理解这两者的区别及其在实际开发中的应用。希望这篇文章能帮助你掌握这个经典面试题。
89 4
|
2月前
|
缓存 安全 Java
java面试-基础语法与面向对象
本文介绍了 Java 编程中的几个核心概念。首先,详细区分了方法重载与重写的定义、发生阶段及规则;其次,分析了 `==` 与 `equals` 的区别,强调了基本类型和引用类型的比较方式;接着,对比了 `String`、`StringBuilder` 和 `StringBuffer` 的特性,包括线程安全性和性能差异;最后,讲解了 Java 异常机制,包括自定义异常的实现以及常见非检查异常的类型。这些内容对理解 Java 面向对象编程和实际开发问题解决具有重要意义。
58 15
|
4月前
|
Java 程序员
Java社招面试中的高频考点:Callable、Future与FutureTask详解
大家好,我是小米。本文主要讲解Java多线程编程中的三个重要概念:Callable、Future和FutureTask。它们在实际开发中帮助我们更灵活、高效地处理多线程任务,尤其适合社招面试场景。通过 Callable 可以定义有返回值且可能抛出异常的任务;Future 用于获取任务结果并提供取消和检查状态的功能;FutureTask 则结合了两者的优势,既可执行任务又可获取结果。掌握这些知识不仅能提升你的编程能力,还能让你在面试中脱颖而出。文中结合实例详细介绍了这三个概念的使用方法及其区别与联系。希望对大家有所帮助!
251 60
|
3月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
193 14
|
3月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
81 13
|
4月前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
146 16
|
4月前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
158 9
|
4月前
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
123 12
|
4月前
|
监控 Dubbo Java
Java Dubbo 面试题
Java Dubbo相关基础面试题
|
4月前
|
SQL Java 数据库连接
Java MyBatis 面试题
Java MyBatis相关基础面试题

热门文章

最新文章