首页 > java秋招-java基础知识常见面试题总结(附答案)
头像
滴滴出行-内推
编辑于 2021-02-23 14:46
+ 关注

java秋招-java基础知识常见面试题总结(附答案)

本贴为总结自己的秋招历程时,将个人记录的常见面试题目分类并发布出来,原帖点这里

首先祝各位学弟学妹新年快乐,都能在牛年拿到“心动的offer”。此外,我们有一个2022届春招秋招交流与内推群,群里可提供包括:百度,阿里,腾讯,字节,美团,快手,京东等大厂的内推与面试答疑。欢迎联系我进群交流,QQ群号:839065965。春招马上来了,只需发一次简历,即可获得多个大厂内推,欢迎加入!

  1. 创建字符串有两种方式:两种内存区域(pool,heap)

    1. ""方式创建的字符串在字符串池中。
    2. new 创建字符串时,首先查看池中是否有相同的字符串,如果有则拷贝一份放到堆中,然后返回堆中的地址;如果池中没有则在堆中创建一分,然后返回堆中的地址,
    3. 在对字符串赋值时,如果右操作数含有一个或一个以上的字符串引用时,则在堆中再建立一个字符串对象,返回引用如:String s= str1+"blog";
      对于基本类型变量的包装类,比如Integer,只有两个都是new关键字构建的才是false.否则都是true
  2. 修饰符 final
    可以用来修饰类、方法和变量

    1. final修饰的类,为最终类,该类不能被继承。如String 类
    2. final修饰的方法可以被继承和重载,但不能被重写
    3. final修饰的变量不能被修改,是个常量
  3. java变量
    Java变量包括成员变量和局部变量;
    成员变量(全局变量)又包括类变量和实例变量;
    类变量:定义在方法之外的变量,用static修饰。
    实例变量:定义在方法之外的变量,无static修饰。
    局部变量:定义在类方法中的变量。

    public class Demo{
     //类变量  static
     static int salary;    
     //实例变量
     String name;
     int age;  
     //main方法
     public static void main(String[] args){
         //局部变量:必须声明和初始化值
         int i = 1;
         System.out.println(i);
     }  
    }

    区别

    局部变量 实例变量 类变量
    创建、销毁时间 在方法被执行时创建、在方法执行完时销毁 在对象创建时创建、在对象销毁时销毁 在程序开始时创建,在程序结束时销毁
    能否使用访问修饰符修饰变量 不能 能,一般设为私有 能,一般声明为public/private,final和static类型的常量
    内存分配位置 栈内存 堆内存 堆内存
    默认值 无,必须初始化 数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null 数值型变量默认值是0,布尔型默认值是false,引用类型默认值是null。
    其它 类变量名称一般建议使用大写字母,常量初始化后不可改变。

4.权限访问修饰符:
default : 默认的,不使用任何修饰符时,在当前类和同一包内可访问;
private: 私有的,只有在当前类可访问;
public:共有的,所有类都可以访问。
protected:受保护的,在当前类、同一包下、子孙类内可访问。

5.抽象类:
抽象类中可以不包含抽象方法,但是含抽象方法的类一定是抽象类(或者接口)
java允许类、接口或者成员方法具有抽象属性,但不允许成员域或构造方法具有抽象属性
如果一个类不具有抽象属性,则不能在该类的类体中定义抽象成员方法

6.static 关键字
用途:在没有创建对象的情况下调用(方法/变量)。被static修饰的方法或变量不需要依赖于对象来访问,只要类被加载了就可以通过类名去访问。
1).变量
被static声明的成员变量为静态成员变量,即类变量,生命周期和类相同,在整个应用程序执行期间都有效。静态变量被所有对象共享,内存中只有一个,在类初次加载的时候被初始化,初始化顺序按照定义的顺序进行初始化;非静态变量是对象拥有的,在创建对象的时候被初始化,互不影响。
对象可以访问静态成员变量吗?静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问
2).方法
称为静态方法,由于静态方法不依赖于对象访问,因此是没有this的,即不能访问类的非静态成员变量和非静态方法。
static方法是属于类的,非实例对象,在JVM加载类时,就已经存在内存中,不会被虚拟机GC回收掉,这样内存负荷会很大,但是非static方***在运行完毕后被虚拟机GC掉,减轻内存压力。
3).static块
静态初始化块用于类的初始化操作(构造方法用于对象的初始化),静态初始化块中不能访问非static成员。将一些只需要进行一次的初始化操作都放在static代码块中进行。
程序执行过程举例

public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }

    public Test() {
        System.out.println("test constructor");
    }

    public static void main(String[] args) {
        new MyClass();
    }
}

class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}


class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }

    public MyClass() {
        System.out.println("myclass constructor");
    }
}

执行过程:

  • 找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
  • 加载Test类的时候,发现Test类有static块,而是先执行static块,输出test static结果
  • 然后执行new MyClass(),执行此代码之前,先加载MyClass类,发现MyClass类继承Test类,而是要先加载Test类,Test类之前已加载
  • 加载MyClass类,发现MyClass类有static块,而是先执行static块,输出myclass static结果
  • 然后调用MyClass类的构造器生成对象,在生成对象前,需要先初始化父类Test的成员变量,而是执行Person person = new Person("Test")代码,发现Person类没有加载
  • 加载Person类,发现Person类有static块,而是先执行static块,输出person static结果
  • 接着执行Person构造器,输出person Test结果
  • 然后调用父类Test构造器,输出test constructor结果,这样就完成了父类Test的初始化了
  • 再初始化MyClass类成员变量,执行Person构造器,输出person MyClass结果
  • 最后调用MyClass类构造器,输出myclass constructor结果,这样就完成了MyClass类的初始化了
    执行结果:
    test static
    myclass static
    person static
    person Test
    test constructor
    person MyClass
    myclass constructor

7.静态方法可以被重写吗?

  • Java中所有方法都能被继承,包括私有方法(但不可见)和静态方法。
  • 静态方法可以被继承,但不能被重写;
  • 当子类中有返回类型、方法名、参数列表均与父类静态方法相同的静态方法时,只是将父类方法隐藏,而不是重写;
  • 父类引用指向子类对象时,只会调用父类的静态方法。父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性。
  • 父类引用指向子类对象举例:Father father = new Son();
  • 静态方法是编译时绑定的,方法重写是运行时绑定的。

8.重写和重载的区别,重载是不是多态性的体现
重写即覆盖,重写是指子类重新定义父类虚函数的做法(虚函数即普通成员函数/未被static,native,final关键字修饰的函数),重载是允许多个重名函数(参数个数,参数类型,返回类型不同)存在。重写是运行时多态,重载是编译时多态。
多态分为编译时多态和运行时多态:

  • 编译时的多态,是指参数列表的不同, 来区分不同的函数, 在编译后, 就自动变成两个不同的函数名.
  • 运行时多态,用到的是后期绑定的技术, 在程序运行前不知道,会调用那个方法, 而到运行时, 通过运算程序,动态的算出被调用的地址. 动态调用在继承的时候,方法名 参数列表完全相同时才出现运行时多态!

9.jvm会不会内存泄漏
程序在向系统申请分配内存空间后,在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。

原因:栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量(基本类型变量和对象的引用)和返回地址。引用的对象保存在堆中。当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束。而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中。

综上所述,栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收,但如果对象一直被引用无法被回收,造成内存的浪费,无法再被使用。所以对象无法被GC回收就是造成内存泄露的原因!

10.怎么判断和处理内存泄漏
可以使用Runtime.getRuntime().freeMemory()进行内存泄漏查询,表示当前还有多少空闲内存
处理:
1)在list中添加元素时,将元素引用置为null,引用对象不会被回收,将list置为null时才会回收元素对象;
2)没有关闭连接会导致,需要调用close()方法,不然不会被自动回收(dataSource.getConnection数据库连接,socket网络连接和IO链接)
3)养成良好的编程习惯,比如分配内存后,一定要先写好内存释放的代码,再去开发其他逻辑。
4)当然,如果已经完成了开发任务,你还可以用 memleak 工具,检查应用程序的运行中,内存是否泄漏。如果发现了内存泄漏情况,再根据 memleak 输出的应用程序调用栈,定位内存的分配位置,从而释放不再访问的内存。

11.final关键字,加在类,方法和属性上分别啥区别,应用场景
final关键字修饰的类不能被继承;
修饰的方法可以被重载和继承,不能被重写;比非final快,因为在编译时已经静态绑定了,不需要在运行时动态绑定。
修饰的变量不能被更改;一般看作常量。
优点:
1)final关键字提高了性能。JVM和Java应用都会缓存final变量。
2)final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
3)使用final关键字,JVM会对方法、变量及类进行优化。
4)不可变类创建不可变类要使用final关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。

12.堆中的永久代存什么东西,会进行垃圾回收吗?
 永久代指的是永久保存区域。主要存放Class和Meta(元数据)的信息。Classic在被加载的时候被放入永久区域,它和存放的实例的区域不同,在Java8中,永久代已经被移除,取而代之的是一个称之为“元数据区”(元空间)的区域。
如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。其实,如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。

13.jvm类加载机制
图片说明
1)加载。
通过一个类的全限定名来获取定义此类的二进制字节流
将获取到的二进制字节流转化成一种数据结构并放进方法区
在内存中生成一个代表此类的java.lang.Class对象,作为访问方法区中各种数据的接口
Class对象虽然是在内存中,但并未明确规定是在Java堆中,对于HotSpot来说,Class对象存储在方法区中。它作为程序访问方法区中二进制字节流中所存储各种数据的接口。
2)验证。
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,从而不会危害虚拟机自身安全。
文件格式验证
元数据验证(描述代码间的关系)
字节码验证(操作码+操作数)
符号引用验证
3)准备。
正式为类变量(static)分配内存并设置类变量初始值的阶段,这些变量所使用的内存将在方法区中分配。
另外,类变量如果被final修饰,则会直接初始化为给定值。
4)解析。可能与初始化阶段交换位置
将常量池内的符号引用替换为直接引用。
常量池:在编译期被确定,并被保存在已编译的.class文件中的一些数据,包括类,方法,接口等中的常量,字符串常量等。
此处的常量池为运行时常量池,即运行期被JVM装载,并可以扩充,存在于方法区中。
符号引用:常量池中存储的描述类、方法、接口的字面量。
直接引用:直接指向目标的指针,相对偏移量,或间接定位到目标的句柄。
5)初始化。
真正开始执行java程序代码

14.volatile关键字
1)可见性,即一个线程修改了某个变量的值,新值对其他线程来说是立即可见的。
2)禁止指令重排序
使用volatile必须具备以下2个条件:(即保证原子性)
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中

15.接口
接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为
接口规范:

  • 接口中可以定义常量,不能定义变量,接口中的属性都是全局静态常量,接口中的常量必须在定义时指定初始值。
  • 接口中所有的方法都是抽象方法,接口中方法都会自动用public abstract 修饰,即接口中只有全局抽象方法。
  • 接口不能实例化,接口中不能有构造。
  • 接口之间可以通过extends实现继承关系,一个接口可以继承多个接口,但接口不能继承类。
  • 接口的实现类必须实现接口的全部方法,否则必须定义为抽象类。

接口作用:

  • 提供简单、规范性:如果一个项目比较庞大,那么就需要定义一些主要的接口,这些接口不仅告诉开发人员需要实现那些业务,而且也将命名规范限制住了。
  • 提高维护、拓展性:定义一个接口,把功能描述放在接口里,然后定义不同类实现这个接口,以实现不同功能。
  • 增强安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。

16.Java的基本数据类型和所占大小(字节)
short 2 int 4 long 8 float 4 double 8 boolean 1 char 2

17.BigInteger?
整型数据已经超过了整数的最大类型长度long的话,则此数据就无法装入,所以,此时要使用BigInteger类进行操作.
import java.math.BigInteger;
图片说明
BigDecimal
完成大的小数操作,而且也可以使用此类进行精确的四舍五入,这一点在开发中经常使用。
图片说明

18.不可变对象
对象一旦被创建后,对象所有的状态及属性在其生命周期内不会发生任何变化。
不可变对象存在的意义:1)支持线程安全,让并发编程变得简单 2)减少容器使用出错的概率,如果hashset的key可变,那么如果发生改变就会出现问题
如何创建不可变对象:1)所有成员变量必须是private 2)最好同时用final修饰(非必须) 3)不提供能够修改原有对象状态的方法 (最常见的方式是不提供setter方法;如果提供修改方法,需要新创建一个对象,并在新创建的对象上进行修改) 4)通过构造器初始化所有成员变量,引用类型的成员变量必须进行深拷贝(deep copy) 5)getter方法不能对外泄露this引用以及成员变量的引用 6)最好不允许类被继承(非必须)

19.java异常
图片说明
runtimeException一般为运行时异常,可以捕获处理,也可以不处理,可以编译通过;
IOException\SQLException一般为非运行时异常(编译异常),如不处理程序无法编译通过。
java捕获异常,try中return,finally进行了修改,返回的是修改后还是修改前的值

  • try{ return } finally{…}
    finally修改基本类型,则不影响返回值
    finally修改非基本类型,影响返回值
  • try{ return } finally{ return }
    try的return表达式执行,但不返回。在finally中返回

21.内存泄漏和溢出
内存泄漏是指无法释放已经申请的空间,即申请的空间没有释放,甚至地址已经丢弃了。

  • 资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉。
  • 集合容器中的内存泄露 ,我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。

需要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

内存溢出:系统已经不能再分配出你所需要的空间

可能发生内存溢出的情况:

  • 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
  • 检查代码中是否有死循环或递归调用。
  • 检查是否有大循环重复产生新对象实体。
  • 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

22.final、finally、finalize的区别
final可以用来修饰类,方法和变量(成员变量或局部变量)
finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。
但是并不是一定会执行,情况可能有:1)finally之前调用了System.exit (0) 语句,终止了 Java 虚拟机的运行2)在try或catch中被打断(interrupted)或者被终止(killed)3)突然死机或断电
finalize是java.lang.object中定义的,也就是说每个对象都有这个方法,但是一般情况下GC可以回收对象,特殊情况下需要程序员实现这个函数,比如关闭一个socket链接

23.hashMap在几的时候会转成红黑树,几的时候又转回来,为什么这么设置?
若桶中链表元素个数大于等于8时,链表转换成树结构;若桶中链表元素个数小于等于6时,树结构还原成链表。
1).因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。
选择6和8,中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
2).在随机哈希代码下,桶中的节点频率遵循泊松分布,文中给出了桶长度k的频率表。由频率表可以看出,桶的长度超过8的概率非常非常小。所以应该是根据概率统计而选择了8作为阀值。

24.hashmap初始值是多少,为什么扩容时总是选择2的幂次方
初始值为16;
1).在 n 为 2次幂的情况下时,(n - 1) & hash ≈ hash % n ,因为2进制的运算速度远远高于取模,所以就使用了这种计算方式,所以要求为2的幂。
2).length 为2的幂次方时,length-1 的二进制均为1,这样便保证了 hash &(length-1) 的可能为 0,也可能为 1(这取决于 hash 的值),这样便可以保证散列的均匀性。

全部评论

(2) 回帖
加载中...
话题 回帖

推荐话题

近期热帖

近期精华帖

热门推荐