Java基础面试题干货系列(三)

创作不易,如果觉得这篇文章对你有帮助,欢迎各位老铁点个赞呗,您的支持是我创作的最大动力!

文章目录

  • 前言
  • 面试基础知识总结
    • 1 Java中访问控制修饰符有哪些,有何区别?
    • 2 两个对象值相同equals结果为true,可以有不同的 hashCode值?
    • 3 编写程序,从键盘上输入年月日,打印该日期对应这一年的第几天
    • 4 类和类之间的关系有哪些
    • 5 在 Java 中,如何跳出当前的多重嵌套循环
    • 6、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里是值传递还是引用传递?
    • 7 谈谈你对面向对象的理解?
    • 8 final、finally、finalize 的区别?
    • 9 谈谈你Java异常处理机制的理解?
    • 10 throw 和 throws 的区别?

前言

越来越感觉到基础知识的薄弱,所以,这里作一下总结,加深下自己对基础知识的理解。Java基础知识实在是太多了,这里打算持续更新,总结下常见的面试题和知识点。

博主打算每期更新10个面试知识点,后续慢慢迭代更新。

博主这里总结的目的在于,为童鞋们提供面试的参考知识点之外,博主也可以巩固自己的基础知识,工作几年的人都知道,如果稍微不留神,慢慢的,你的基础知识忘记的差不多了

面试基础知识总结

1 Java中访问控制修饰符有哪些,有何区别?

Java中,无论是类,变量还是方法,都会有一些访问权限控制。

Java中,可以使用访问控制符来保护对变量方法构造方法的访问。Java 支持 4 种不同的访问权限。

访问控制修饰符主要有以下四种:

  • public: 对所有类可见。
    使用对象:类、接口、变量、方法

  • protected: 对同一包内的类和所有子类可见。
    使用对象:变量、方法。 注意:不能修饰类(外部类)。

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。
    使用对象:类、接口、变量、方法。

  • private: 在同一类内可见。
    使用对象:变量、方法。 注意:不能修饰类(外部类)

我们可以通过一张表,来说明访问权限区别:

修饰符 当前类 同一包内 子孙类(同一包) 子孙类(不同包) 其他包
public Y Y Y Y Y
protected Y Y Y Y N
default Y Y Y N N
private Y N N N N

2 两个对象值相同equals结果为true,可以有不同的 hashCode值?

不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希值(hashCode)应当相同。

Java 对于equals方法和hashCode方法是这样规定的:
(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
(2)如果两个对象的 hashCode相同,它们并不一定相同。当然,你未必按照要求去做,但是如果你违背了上述原则就会发现在使用集合时,相同的对象可以出现在Set 集合中,同时增加新元素的效率会大大降低(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

关于equals和hashCode方法,很多Java程序员都知道,但很多人也就是仅仅了解而已,在Joshua Bloch的大作《Effective Java》(《Effective Java》在很多公司,是Java程序员必看书籍,如果你还没看过,那就赶紧去买一本吧)中是这样介绍 equals 方法的:

首先equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true 时,y.equals(x)也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false。

实现高质量的equals方法的技巧:

  • 使用==操作符检查"参数是否为这个对象的引用"

  • 使用 instanceof操作符检查"参数是否为正确的类型"

  • 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配

  • 编写完equals方法后,问自己它是否满足对称性、传递性、一致性

  • 重写equals时总是要重写hashCode

  • 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解

3 编写程序,从键盘上输入年月日,打印该日期对应这一年的第几天

有的公司,要求手撕代码,比如说:编程一段程序,从键盘上输入年月日,打印该日期对应这一年的第几天

要求:
输入 : 2020 5 23
输出: 2020年5月23日,是2020年的第144天

考察点:
临场应变能力、键盘输入基础、基础编码能力以及思维能力

基础实现参考如下:

public class MyCode {

    /**
     * 1、3、5、7、8、10、12月份31天
     */
    private static final int DAYS_31 = 31;
    /**
     * 4、6、9、11月份为30天
     */
    private static final int DAYS_30 = 30;
    /**
     * 平年二月28天
     */
    private static final int DAYS_28 = 28;
    /**
     * 闰年二月29天
     */
    private static final int DAYS_29 = 29;

    /**
     * 从键盘上输入年月日, 打印该日期对应这一年的第几天
     */
    private void basicMethod() {
        /**
         * 【解题思路】
         *  a:通过年份区分是闰年还是平年,平年2月28天,闰年2月29天
         *  b:1、3、5、7、8、10、12月份为31天,其余4、6、9、11月份为30天
         *  c:将每个月的天数相加即可
         */

        //从键盘上输入年月日
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入年份:");
        int year = sc.nextInt();
        System.out.print("请输入月份:");
        int month = sc.nextInt();
        System.out.print("请输入日:");
        int day = sc.nextInt();

        //计算输入的日期,对应这一年的第几天
        if (year <= 0 || month <= 0 || day <= 0) {
            System.out.println("年、月、日不能小于等于0!");
            return;
        }

        if (month > 12 || day > 31) {
            System.out.println("月、日数值不正确,请核实后重新输入!");
            return;
        }
        int calculateDays = calculateDays(year, month, day);

        //输出结果
        System.out.println(year + "年" + month + "月" + day + "日,是" + year + "年的第" + calculateDays + "天");
    }

    /**
     * 计算输入的日期,对应这一年的第几天
     *
     * @param year
     * @param month
     * @param day
     * @return
     */
    private int calculateDays(int year, int month, int day) {
        int calculateDays = 0;

        for (int i = 1; i < month; i++) {
            //计算当前月之前的月份天数,因为这个月还没有过完
            switch (i) {
                case 1:
                case 3:
                case 5:
                case 7:
                case 8:
                case 10:
                case 12:
                    calculateDays += DAYS_31;
                    break;
                case 4:
                case 6:
                case 9:
                case 11:
                    calculateDays += DAYS_30;
                    break;
                case 2:
                    //这个2月份比较特殊,需要判断是否是闰年
                    if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
                        //闰年2月份29天
                        calculateDays += DAYS_29;
                    } else {
                        //平年2月份28天
                        calculateDays += DAYS_28;
                    }

                    break;
            }
        }

        //返回计算的天数 + 当月过去的天数
        return calculateDays + day;
    }

    public static void main(String[] args) {
        MyCode myCode = new MyCode();
        myCode.basicMethod();
    }

}

输出结果:

请输入年份:2020
请输入月份:5
请输入日:23
2020年5月23日,是2020年的第144天

4 类和类之间的关系有哪些

这个知识点,面试中可能会问到,因为真正开发时,大公司比较规范,会进行需求的详细设计,设计时,少不了类图、用例图等的设计,那么,类和类之间的关系,必须掌握。

类和类之间有6种关系:
泛化(Generalization)实现(Realization)关联(Association)聚合(Aggregation)组合(Composition)依赖(Dependency)

具体的介绍,可以参考我的博客:UML类和类之间的关系详解

5 在 Java 中,如何跳出当前的多重嵌套循环

一般来说,都是终止循环,跳出本次继续下一次循环,有的时候,也会在多层嵌套循环中,直接跳出循环。

在最外层循环前加一个标记,这个标记是任意的标识符,比如说:out_for,然后用break out_for;可以跳出多重循环。
例如:

public class BaseClass {

    public static void main(String[] args) {
        out_for:
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                if (i == 1 && j == 3) {
                    System.out.println();
                    System.out.println("当j == " + j + "时,跳出嵌套循环");
                    break out_for;
                }

                System.out.println("j = " + j);
            }
        }
    }
}

执行结果:

j = 0
j = 1
j = 2
j = 3
j = 4
j = 0
j = 1
j = 2

当j == 3时,跳出嵌套循环

6、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里是值传递还是引用传递?

是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的内存地址。这个值(内存地址)被传递后,同一个内存地址指向堆内存当中的同一个对象,所以通过那个引用去操作这个对象,对象的属性都是改变的。

具体的,可以参考我的另一篇博文Java中参数传递,进行深入了解。

7 谈谈你对面向对象的理解?

所谓对象就是由一组数据结构和处理它们的方法组成的,重点“数据”包括对象的特性、状态等的静态信息;“方法” 也就是行为,包括该对象的对数据的操作、功能等能动信息。把相同行为的对象归纳为,类是一个抽象 1 的概念,对象是类的具体。简单点说:对象就是类的实例。
例如:老师就是一个类,张三这个老师,就是一个具体的对象。

面向对象编程(OOP)其实就是一种设计思想,在程序设计过程中把每一部分都尽量当成一个对象来考虑,以实现软件系统的可扩展性,可维护性和可重用性

面向对象的目的: 解决软件系统的可扩展性可维护性可重用性

面向对象有三大特性: 封装继承多态

(1)封装(对应可扩展性):通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。简单来说,就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。封装是通过访问控制符(public protected private)来实现。一个类就可以看成一个封装。

面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。

(2)继承(重用性和扩展性):子类继承父类,可以继承父类的方法和属性。可以对父类方向进行覆盖(实现了多态)。但是继承破坏了封装,因为他是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,只有明确的IS-A关系才能使用。继承要慎用,尽量优先使用组合。

提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

(3)多态(可维护性和可扩展性):多态性是指允许不同子类型的对象对同一消息作出不同的响应,接口的不同实现方式即为多态。简单的说就是用同样的对象引用,调用同样的方法但是做了不同的事情。

多态性分为编译时的多态性运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A系统访问B系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的。

方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)

运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:
第一:方法重写(子类继承父类并重写父类中已有的或抽象的方法);
第二:对象造型(用父类型引用指向子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

8 final、finally、finalize 的区别?

  • final
    用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。

  • finally
    异常处理语句结构的一部分,表示总是执行。

  • finalize
    finalize 是Object 类的一个方法,所以Java对象都有这个方法,当某Java对象没有更多的引用指向的时候,会被垃圾回收器回收,该对象被回收之前,由垃圾回收器来负责调用此方法,通常在该方法中进行回收前的准备工作。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。

9 谈谈你Java异常处理机制的理解?

Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为 java.lang.Throwable,Throwable下面又派生了两个子类:ErrorException

Error: 表示应用程序本身无法克服和恢复的一种严重问题。

Exception: 表示程序还能够克服和恢复的问题,其中又分为系统异常普通异常

  • 系统异常
    系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组下标越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException)。

  • 普通异常
    普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。

Java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try…catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以编译器不强制用try…catch处理或用throws声明,所以系统异常也称为unchecked异常

10 throw 和 throws 的区别?

  • throw
    throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。

    throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。

    throw一般用于抛出自定义异常。

  • throws
    throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。

    throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。

    throws表示出现异常的一种可能性,并不一定会发生这种异常。

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!


  1. 抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。 ↩︎

你可能感兴趣的:(Java面试题,Java基础知识)