嗨,这里是涛涛子专门为了面试八股简历的博客部分,为了精简内容 ,八股的答案部分有时简略,或者附上答案链接,遇到经典的问题会动手回答一下

java基础

Q:⾯向对象和⾯向过程的区别

Q: JDK JRE JVM

JDK:

  • Java程序的运行环境-JRE
    • JVM java虚拟机
      • jvm有自己完善的硬件架构,如处理器、栈区、寄存器等。
      • jvm是一个虚拟的中间平台,只负责将编译后的字节码文件转换成当前计算机能理解并执行的指令,其他都不关心。jvm是java“一次编译,到处执行”的原因。
    • 核心类库 java.lang java.utils 等
  • Java的基础类库(Java API)重要的语法结构和基本的线程、图形和IO等
  • Java的一些工具包(JDK的bin目录下)(其中包含了javac源码编译器,还有一些其他的命令:jdb,javah,jmp等)

Q: 面向对象语言的特点

Q: Java 和 C++的区别

都是⾯向对象的语⾔,都⽀持封装、继承和多态

  • Java 不提供指针来直接访问内存,程序内存更加安全

  • Java 的类是单继承的,C++ ⽀持多重继承;虽然 Java 的类不可以多继承,但是接⼝可以多继承

  • Java 有⾃动内存管理机制,不需要程序员⼿动释放⽆⽤内存

  • C 语⾔中,字符串或字符数组最后都会有⼀个额外的字符‘\0’来表示结束。但是,Java语⾔中没有结束符这⼀概念。原因

Q: 构造器 Constructor 是否可被 override?

A: 不能被重写父类的无参,构造函数不能被子类继承而是被子类隐式调用,即:super();

Q:重载和重写的区别
  • overload: 重载就是同样的⼀个⽅法能够根据输⼊数据的不同,做出不同的处理

    • 发⽣在同⼀个类中,⽅法名必须相同,参数类型不同、个数不同、顺序不同,⽅法返回值和访问修饰符可以不同。
  • override :重写就是当⼦类继承⾃⽗类的相同⽅法,输⼊数据⼀样,但要做出有别于⽗类的响应时,你就要覆盖⽗类⽅法

    • 重写发⽣在运⾏期,是⼦类对⽗类的允许访问的⽅法的实现过程进⾏重新编写。

    • 返回值类型、⽅法名、参数列表必须相同,抛出的异常范围⼩于等于⽗类,访问修饰符范围⼤于等于⽗类(如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法)

    • 父类的构造⽅法⽆法被重写,只能被调用

    • 总结:重写就是⼦类对⽗类⽅法的重新改造,外部样⼦不能改变,内部逻辑可以改变

image-20230328101310713

⽅法的重写要遵循“两同两⼩⼀⼤”:

  • “两同”即⽅法名相同、形参列表相同;
  • “两⼩”指的是⼦类⽅法返回值类型应⽐⽗类⽅法返回值类型更⼩或相等,⼦类⽅法声明抛出的异常类应⽐⽗类⽅法声明抛出的异常类更⼩或相等;
  • “⼀⼤”指的是⼦类⽅法的访问权限应⽐⽗类⽅法的访问权限更⼤或相等

Q: ⾃动装箱与拆箱 (语法糖,jdk1.5)

Q: 在 Java 中定义⼀个不做事且没有参数的构造⽅法的作⽤

主要是为了避免子类自动使用super()时发生编译报错;

Java 程序在执⾏⼦类的构造⽅法之前, 如果没有⽤ super() 来调⽤⽗类特定的构造⽅法,则会调⽤⽗类中“没有参数的构造⽅法”

因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中⼜没有⽤ super() 来调⽤⽗类中特定的构造⽅法,则编译时将发⽣错误,因为 Java 程序在⽗类中找不到没有参数的构造⽅法可供执⾏。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法。

Q: 接⼝和抽象类的区别是什么?

1、接⼝的⽅法默认是 public ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现),⽽抽象类可以有⾮抽象的⽅法。

2、接⼝中除了 static 、 final 变量,不能有其他变量,⽽抽象类中则不⼀定。

3、⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。

4、接⼝⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。

5、从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。

  1. 在 jdk 7 或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实现接⼝的类实现。

  2. jdk 8 的时候接⼝可以有默认⽅法和静态⽅法功能。

  3. Jdk 9 在接⼝中引⼊了私有⽅法和私有静态⽅法。
Q:静态成员变量,实例成员变量与局部变量的区别有哪些?

语法上归属, 变量(或者引用) 存储的位置 ,生命周期, 初值;

Q: == 与 equals(重要)

== : 它的作⽤是判断两个对象的地址是不是相等

equals() : 它的作⽤也是判断两个对象是否相等。但它⼀般有两种使⽤情况:

  • 情况 1:类没有覆盖 equals() ⽅法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。

  • 情况 2:类覆盖了 equals() ⽅法。⼀般,我们都覆盖 equals() ⽅法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。例如String类的equals就被重写过

Q: hashCode 与 equals (重要)

什么是hasCode() ?

hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数。这个哈希码的作⽤是确定该对象在哈希表中的索引位置hashCode() 定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含hashCode() 函数,另外需要注意的是: Object 的 hashcode ⽅法是本地⽅法,也就是⽤ c 语⾔或 c++ 实现的

为什么要有 hashCode?

一般和Map,Set 集合一起使用,目的是为了快速判断,快速查找元素, 比如 判断 添加的元素是否已经存在,

如 HashMap,当新加入对象会先计算对象的 hashcode 值来判断对象加⼊的位置,同时也会与其他已经加⼊的对象的 hashcode 值比较,如果没有相符的 hashcode, HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调⽤ equals() ⽅法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加⼊操作成功。如果不同的话,就会重新散列到其他位置。

为什么重写 equals 时必须重写 hashCode ⽅法?

为了满足hashCode的设计约定:相同对象(满足equals的)必须有相同哈希值[底线要求] , 这样设计的原因理解为更好的配合java的集合类使用, 为了如,为了判断集合有没有这个元素,就先通过hashCode 找到对应的hash表的地址, 在使用equals判断对应位置的元素和次元都是否相等,相当于用hashCode做了一个粗筛,这种粗筛避免了大量的equals操作.

也可以这样理解,equals() 的条件必须要比 hashcode更加严格!

  • 没改equals之前, 本质上通过地址来判断是否相同,这样不满足所有的业务要求,比如字符串 不应该通过地址判断是否相等而应该由字符串的内容决定
  • 改了equals 就会将等价条件削弱,可能会出现满足equals 确不满足 hashCode 相等的情况
  • 所以, 解决方案是, 将hashCode的等价条件也削弱
为什么两个对象有相同的 hashcode 值,它们也不⼀定是相等的?

也即发生了hash碰撞, 本质上来说,HashCode算是一种杂凑算法, 也就是尽量讲对象的映射关系打散,但不能把保证完全的散布

  • 也可以从数学上解释,HashCode 算法不能满足单射函数(有反函数的充要条件), 如图c,所以发生了碰撞.

img

在这里插入图片描述

  • 一般改写hashCode时, 在满足了底线要求后,也应该尽量避免碰撞的发生
Q: 如何理解Java 中只有值传递?

这是关于参数传递的一个术语: 按值调⽤ (方法调用 方法参数接收的是参数的值 );引⽤调⽤(方法调用 方法参数接收的是参数的地址)

Java 程序设计语⾔总是采⽤按值调⽤。也就是说,⽅法得到的是所有参数值的⼀个拷⻉,也就是说,⽅法不能修改传递给它的任何参数变量的内容。

举个例子

public static void main(String[] args) {
    int num1 = 10;
    int num2 = 20;
    swap(num1, num2);
    System.out.println("num1 = " + num1);
    System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    System.out.println("a = " + a);
    System.out.println("b = " + b);
} 
// 交换的是两个变量拷贝的值, 而num1 num2 本身不会改变

结果

a = 20
b = 10
num1 = 10
num2 = 20

看来, 并没有交换成功变量的值,

Java 程序设计语⾔总是采⽤按值调⽤。也就是说,⽅法得到的是所有参数值的⼀个拷⻉,也就是说,⽅法不能修改传递给它的任何参数变量的内容。

但是,如果方法参数是⼀个对象的引⽤时, 方法参数与变量是同一个的对象的引用,外部对引⽤对象的改变会反映到所对应的对象上。

举个例子

public static void main(String[] args) {
    int[] arr = { 1, 2, 3, 4, 5 };
    System.out.println(arr[0]);
    change(arr);
    System.out.println(arr[0]);
}
public static void change(int[] array) {
    // 将数组的第⼀个元素变为0
    array[0] = 0;
}

结果

1
0

理解了按值传递,就能理解为什么下面的两个对象为什么不能交换成功了

public class Test {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Student s1 = new Student("⼩张");
        Student s2 = new Student("⼩李");
        Test.swap(s1, s2);
        System.out.println("s1:" + s1.getName());
        System.out.println("s2:" + s2.getName());
    }
    public static void swap(Student x, Student y) {
        Student temp = x;
        x = y;
        y = temp;
        System.out.println("x:" + x.getName());
        System.out.println("y:" + y.getName());
    }
}
x:⼩李
y:⼩张
s1:⼩张
s2:⼩李

因为方法传入的是 对象引⽤的拷⻉ ,相当于又创建了一个引用(暂且叫他为拷贝引用),这个拷贝引用与原来的引用指向对象的地址相同,

在这个函数内部修改这个引用并不能对 原来的引用造成任何影响 . 在这个例子中,交换的只是拷贝引用的引用值, 而不是原来引用, 所以 并未交换成功, 要想交换成功 可以讲交换语句提取出来,针对引用本身使用.

所以 针对java方法参数传递方式只有值传递这种情况,注意

  • ⼀个⽅法不能修改⼀个基本数据类型的参数 ( 例子1 )
  • ⼀个⽅法可以改变⼀个对象参数的状态。(例子2)
  • ⼀个⽅法不能让对象参数引⽤⼀个新的对象。(例子3)
Q: java的数据类型

Java数据类型包括基本数据类型和引用数据类型两大类。

基本数据类型有8个,可以分为4个小类,分别是整数类型(byte/short/int/long)、浮点类型(float/double)、字符类型(char)、布尔类型(boolean)。

image-20230322173225054

Q :关于 final 关键字的⼀些总结

final 关键字主要⽤在三个地⽅:变量、⽅法、类。

  1. 对于⼀个 final 变量,如果是基本数据类型的变量,则其数值⼀旦在初始化之后便不能更改;如果是引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象。

  2. 当⽤ final 修饰⼀个类时,表明这个类不能被继承。final 类中的所有成员⽅法都会被隐式地指定为 final ⽅法。

  3. 使⽤ final ⽅法的原因有两个。第⼀个原因是把⽅法锁定,以防任何继承类修改它的含义;第⼆个原因是效率。在早期的 Java 实现版本中,会将 final ⽅法转为内嵌调⽤。但是如果⽅法过于庞⼤,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤final ⽅法进⾏这些优化了)。类中所有的 private ⽅法都隐式地指定为 final。