总结了下常见的Java面试题,题目后面的星星数量表示面试中题目出现的频率,秋招的小伙伴冲呀

文章目录

由于本文篇幅有限,我这边就把我整理好了 部分八股文 (背诵版)的高频面试题PDF,放这里,如需获取全部八股文(背诵版): 戳此传送门 获取即可

1. JVM、JRE及JDK的关系 **

JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。

Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。

JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。

简单来说就是JDK是Java的开发工具,JRE是Java程序运行所需的环境,JVM是Java虚拟机.它们之间的关系是JDK包含JRE和JVM,JRE包含JVM.

2. JAVA语言特点 **

        
  • Java是一种面向对象的语言
  •     
  • Java通过Java虚拟机实现了平台无关性,一次编译,到处运行
  •     
  • 支持多线程
  •     
  • 支持网络编程
  •     
  • 具有较高的安全性和可靠性

3. JAVA和C++的区别 **

面试时记住前四个就行了

        
  • Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。
  •     
  • Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
  •     
  • Java 支持自动垃圾回收,而 C++ 需要手动回收。
  •     
  • Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
  •     
  • Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操 作符重载,而 C++ 可以。
  •     
  • Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。

4. Java的基本数据类型  **

注意String不是基本数据类型

5. 隐式(自动)类型转换和显示(强制)类型转换 **

        
  • 隐式(自动)类型转换:从存储范围小的类型到存储范围大的类型。byte→short(char)→int→long→float→double
  •     
  • 显示(强制)类型转换:从存储范围大的类型到存储范围小的类型。double→float→long→int→short(char)→byte。该类类型转换很可能存在精度的损失。

看一个经典的代码

short s = 1;
s = s + 1;

这是会报错的,因为1是int型,s+1会自动转换为int型,将int型直接赋值给short型会报错。

做一下修改即可避免报错

short s = 1;
s = (short)(s + 1);

或这样写,因为s += 1会自动进行强制类型转换

short s = 1;
s += 1;

6. 自动装箱与拆箱 **

        
  • 装箱:将基本类型用包装器类型包装起来
  •     
  • 拆箱:将包装器类型转换为基本类型

这个地方有很多易混淆的地方,但在面试中问到的频率一般,笔试的选择题中经常出现,还有一个String创建对象和这个比较像,很容易混淆,在下文可以看到

下面这段代码的输出结果是什么?

public class Main {
    public static void main(String[] args) {

            Integer a = 100;
            Integer b = 100;
            Integer c = 128;
            Integer d = 128;

            System.out.println(a==b);
            System.out.println(c==d);
    }
}

很多人看到这个结果会很疑惑,为什么会是一个true一个flase.其实从源码中可以很容易找到原因.首先找到Integer方法中的valueOf方法

public static Integer valueOf(int i) {
      if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
  }

可以看到当不满足if语句中的条件,就会重新创建一个对象返回,那结果必然不相等。继续打开IntegerCache可以看到

private static class IntegerCache {
          static final int low = -128;
        static final int high;
          static final Integer cache[];

          static {
              // high value may be configured by property
              int h = 127;
              String integerCacheHighPropValue =
                  sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
              if (integerCacheHighPropValue != null) {
                  try {
                      int i = parseInt(integerCacheHighPropValue);
                      i = Math.max(i, 127);
                      // Maximum array size is Integer.MAX_VALUE
                      h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                  } catch( NumberFormatException nfe) {
                      // If the property cannot be parsed into an int, ignore it.
                  }
              }
              high = h;

              cache = new Integer[(high - low) + 1];
              int j = low;
              for(int k = 0; k < cache.length; k++)
                  cache[k] = new Integer(j++);

              // range [-128, 127] must be interned (JLS7 5.1.7)
              assert IntegerCache.high >= 127;
          }

          private IntegerCache() {}
      }
        
  • 代码挺长,大概说的就是在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。所以上面代码中a与b相等,c与d不相等。
  •     
  • 在看下面的代码会输出什么
public class Main {
    public static void main(String[] args) {

            Double a = 1.0;
            Double b = 1.0;
            Double c = 2.0;
            Double d = 2.0;

            System.out.println(a==b);
            System.out.println(c==d);

    }
}

采用同样的方法,可以看到Double的valueOf方法,每次返回都是重新new一个新的对象,所以上面代码中的结果都不想等。

public static Double valueOf(double d) {
          return new Double(d);
}

最后再看这段代码的输出结果

public class Main {
    public static void main(String[] args) {

        Boolean a = false;
        Boolean b = false;
        Boolean c = true;
        Boolean d = true;

        System.out.println(a==b);
        System.out.println(c==d);
    }
}
true
true

老方法继续看valueOf方法

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

再看看TRUE和FALSE是个什么东西,是两个静态成员属性。

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);

*说下结论 *:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。然后是Boolean的valueOf方法是单独一组的。

        
  • Integer i = new Integer(xxx)和Integer i =xxx的区别 这两者的区别主要是第一种会触发自动装箱,第二者不会 最后看看下面这段程序的输出结果
public class Main {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Long g = 3L;
        int int1 = 12;
        int int2 = 12;
        Integer integer1 = new Integer(12);
        Integer integer2 = new Integer(12);
        Integer integer3 = new Integer(1);

        System.out.println("c==(a+b) ->"+ (c==(a+b)));
        System.out.println("g==(a+b) ->" + (g==(a+b)));
        System.out.println( "c.equals(a+b) ->" + (c.equals(a+b)));
        System.out.println( "g.equals(a+b) ->" + (g.equals(a+b)));
        System.out.println("int1 == int2 -> " + (int1 == int2));
        System.out.println("int1 == integer1 -> " + (int1 == integer1));
        System.out.println("integer1 == integer2 -> " + (integer1 == integer2));
        System.out.println("integer3 == a1 -> " + (integer3 == a));
    }
}

下面简单解释这些结果。

1.当 "=="运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。所以c==a+b,g==a+b为true。

2.而对于equals方***先触发自动拆箱过程,再触发自动装箱过程。也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。所以c.equals(a+b)为true。而对于g.equals(a+b),a+b会先拆箱进行相加运算,在装箱进行equals比较,但是装箱后为Integer,g为Long,所以g.equals(a+b)为false。

3.int1 == int2为true无需解释,int1 == integer1,在进行比较时,integer1会先进行一个拆箱操作变成int型在进行比较,所以int1 == integer1为true。

4.integer1 == integer2->false。integer1和integer2都是通过new关键字创建的,可以看成两个对象,所以integer1 == integer2 为false。integer3 == a1 -> false , integer3是一个对象类型,而a1是一个常量它们存放内存的位置不一样,所以integer3 == a1为false,具体原因可学习下java的内存模型。

String(不是基本数据类型)

7. String的不可变性 ***

在 Java 8 中,String 内部使用 char 数组存储数据。并且被声明为final,因此它不可被继承。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {    
    private final char value[];
}

为什么String`要设计成不可变的呢(不可变性的好处):

1.可以缓存 hash 值()

因为 String 的hash值经常被使用,例如String 用做 HashMap 的 key。不可变的特性可以使得 hash值也不可变, 因此只需要进行一次计算。

2.常量池优化

String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。

3.线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

8. 字符型常量和字符串常量的区别 *

        
  1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
  2.     
  3. 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
  4.     
  5. 占内存大小 字符常量占两个字节 字符串常量占若干个字节(至少一个字符结束标志)

9. 什么是字符串常量池?*

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

10. String 类的常用方法都有那些?**

面试时一般不会问,但面试或笔试写字符串相关的算法题经常会涉及到,还是得背一背(以下大致是按使用频率优先级排序)

        
  • length():返回字符串长度
  •     
  • charAt():返回指定索引处的字符
  •     
  • substring():截取字符串
  •     
  • trim():去除字符串两端空白
  •     
  • split():分割字符串,返回一个分割后的字符串数组。
  •     
  • replace():字符串替换。
  •     
  • indexOf():返回指定字符的索引。
  •     
  • toLowerCase():将字符串转成小写字母。
  •     
  • toUpperCase():将字符串转成大写字符。

11. String和StringBuffer、StringBuilder的区别是什么?***

1.可变性

String不可变,StringBuilder和StringBuffer是可变的

2.线程安全性

String由于是不可变的,所以线程安全。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。 StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

3.性能

StringBuilder > StringBuffer > String

为了方便记忆,总结如下

12. switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上 *

switch可以作用于char byte short int及它们对应的包装类型,switch不可作用于long double float boolean及他们的包装类型。在 JDK1.5之后可以作用于枚举类型,在JDK1.7之后可作用于String类型。

13. Java语言采用何种编码方案?有何特点?*

Java语言采用Unicode编码标准,它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

14. 访问修饰符 **

在Java编程语言中有四种权限访问控制符,这四种访问权限的控制符能够控制类中成员的可见性。其中类有两种public、default。而方法和变量有 4 种:public、default、protected、private。

        
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  •     
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  •     
  • default : 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  •     
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

15. 运算符 *

        
  • &&和& &&和&都可以表示逻辑与,但他们是有区别的,共同点是他们两边的条件都成立的时候最终结果才是true;不同点是&&只要是第一个条件不成立为false,就不会再去判断第二个条件,最终结果直接为false,而&判断的是所有的条件。
  •     
  • ||和| ||和|都表示逻辑或,共同点是只要两个判断条件其中有一个成立最终的结果就是true,区别是||只要满足第一个条件,后面的条件就不再判断,而|要对所有的条件进行判断。

关键字

16. static关键字 ***

static关键字的主要用途就是方便在没有创建对象时调用方法和变量和优化程序性能

1.static变量(静态变量)

用static修饰的变量被称为静态变量,也被称为类变量,可以直接通过类名来访问它。静态变量被所有的对象共享,在内存中只有一个副本,仅当在类初次加载时会被初始化,而非静态变量在创建对象的时候被初始化,并且存在多个副本,各个对象拥有的副本互不影响。

2.static方法(静态方法)

static方法不依赖于任何对象就可以进行访问,在static方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,但是在非静态成员方法中是可以访问静态成员方法/变量的。

public class Main {
    public static String s1 = "s1";//静态变量
    String s2  = "s2";
    public void fun1(){
        System.out.println(s1);
        System.out.println(s2);
    }

    public static void fun2(){
        System.out.println(s1);
        System.out.println(s2);//此处报错,静态方法不能调用非静态变量
    }
}

3.static代码块(静态代码块)

静态代码块的主要用途是可以用来优化程序的性能,因为它只会在类加载时加载一次,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。如果程序中有多个static块,在类初次被加载的时候,会按照static块的顺序来执行每个static块。

public class Main {
    static {
        System.out.println("hello,word");
    }
    public static void main(String[] args) {
        Main m = new Main();
    }
}

4.可以通过this访问静态成员变量吗?(可以)

this代表当前对象,可以访问静态变量,而静态方法中是不能访问非静态变量,也不能使用this引用。

5.初始化顺序

静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。如果存在继承关系的话,初始化顺序为父类中的静态变量和静态代码块——子类中的静态变量和静态代码块——父类中的实例变量和普通代码块——父类的构造函数——子类的实例变量和普通代码块——子类的构造函数

17. final 关键字 ***

final关键字主要用于修饰类,变量,方法。

        
  1. 类:被final修饰的类不可以被继承
  2.     
  3. 方法:被final修饰的方法不可以被重写
  4.     
  5. 变量:被final修饰的变量是基本类型,变量的数值不能改变;被修饰的变量是引用类型,变量便不能在引用其他对象,但是变量所引用的对象本身是可以改变的。
public class Main {
    int a = 1;
    public static void main(String[] args) {
        final int b = 1;
        b = 2;//报错
        final Main m = new Main();
        m.a = 2;//不报错,可以改变引用类型变量所指向的对象
    }
}

18. final finally finalize区别 ***

        
  • final主要用于修饰类,变量,方法
  •     
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块 中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  •     
  • finalize是一个属于Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾,但Java语言规范并不保证inalize方***被及时地执行、而且根本不会保证它们会被执行。

9. this关键字 **

重点掌握前三种即可

1.this关键字可用来引用当前类的实例变量。主要用于形参与成员名字重名,用this来区分。

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

2.this关键字可用于调用当前类方法。

public class Main {
    public void fun1(){
        System.out.println("hello,word");
    }
    public void fun2(){
        this.fun1();//this可省略
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.fun2();
    }
}

3.this()可以用来调用当前类的构造函数。(注意:this()一定要放在构造函数的第一行,否则编译不通过)

class Person{
    private String name;
    private int age;

    public Person() {
    }

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

4.this关键字可作为调用方法中的参数传递。

5.this关键字可作为参数在构造函数调用中传递。

6.this关键字可用于从方法返回当前类的实例。super

20. super关键字 **

1.super可以用来引用直接父类的实例变量。和this类似,主要用于区分父类和子类中相同的字段

2.super可以用来调用直接父类构造函数。(注意:super()一定要放在构造函数的第一行,否则编译不通过)

3.super可以用来调用直接父类方法。

public class Main {
    public static void main(String[] args) {
        Child child = new Child("Father","Child");
        child.test();
    }
}

class Father{
    protected String name;

    public Father(String name) {
        this.name = name;
    }

    public void Say(){
        System.out.println("hello,child");
    }

}

class Child extends Father{
    private String name;

    public Child(String name1, String name2) {
        super(name1);      //调用直接父类构造函数
        this.name = name2;
    }

    public void test(){
        System.out.println(this.name);
        System.out.println(super.name);  //引用直接父类的实例变量
        super.Say();      //调用直接父类方法
    }
}super.namepublic class Main {
    public static void main(String[] args) {
        Child child = new Child("Father","Child");
        child.test();
    }
}

class Father{
    protected String name;

    public Father(String name) {
        this.name = name;
    }

    public void Say(){
        System.out.println("hello,child");
    }

}

class Child extends Father{
    private String name;

    public Child(String name1, String name2) {
        super(name1);      //调用直接父类构造函数
        this.name = name2;
    }

    public void test(){
        System.out.println(this.name);
        System.out.println(super.name);  //引用直接父类的实例变量
        super.Say();      //调用直接父类方法
    }
}