Java基础常见面试题一

 参考:

1、Java 语言有哪些特点?

  1. 面向对象(封装,继承,多态);
  2. 跨平台( Java 虚拟机实现平台无关性);
  3. 可靠性;
  4. 安全性;
  5. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
  6. 编译与解释并存;

2、排序算法

3、 双重指针

一种算法解题思想,利用两个指针同时移动,达到O(n)的复杂度解决问题。比如归并排序,快速排序等等就利用这种思想。

4、Java Object类有什么方法

hashcode(), equals(), clone(), toString(), getClass(), finalize(),  notify(), notifyAll(), wait()

5、解释一下 JVM 

Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是只要使用相同的字节码,在任何平台都会给出相同的结果。不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。

6、 JRE和JDK的区别

JRE是Java的运行环境,其中包含了JVM 和 运行java所需的核心类库,
JDK是Java开发工具包,除有Javac这样的开发工具还包含了JRE这个运行环境
为什么JDK中包含一个JRE呢?
       开发完的程序,需要运行一下看看效果

7、Java 与 C++ 的区别

  1. Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 既支持面向对象也支持面向过程。
  2. Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。
  3. Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
  4. Java 支持自动垃圾回收,而 C++ 需要手动回收。
  5. Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
  6. Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
  7. Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。
  8. Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。

8、面向对象和面向过程的区别

面向对象: 数据与操作分离
面向过程:数据与操作紧密结合

9、c++11新特性 

long long类型,nullptr常量,auto关键字,范围for循环,lambda表达式,string数值转换函数,智能指针

10、C++里的HashMap底层是怎么实现的

数组加红黑树

11、C++的内存分区

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区
1.栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。
2.堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
3.自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)

12、觉得编码时要遵循哪些设计原则

  1. 1.模块化编程,提高模块的独立性,从而增强代码的可读性和可维护性
  2. 2. 模块规模应该适中,且应该极力降低模块接口的复杂程度
  3. 3. 命名规范,比如使用对应的英文单词或者使用驼峰命名
  4. 4. 注释规范,比如注释不能有歧义等

13、JDK 5 - JDK 8 的新特性

JDK1.5新特性:

1.自动装箱与拆箱:

2.枚举

3.静态导入,如:import staticjava.lang.System.out

4.可变参数(Varargs)

5.泛型(Generic)(包括通配类型/边界类型等)

6.For-Each循环

7.注解

JDK1.7 新特性

1.对Java集合(Collections)的增强支持,可直接采用[]、{}的形式存入对象,采用[]的形式按照索引、键值来获取集合中的对象。如:

     List<String>list=[“item1”,”item2”];//
     Stringitem=list[0];//直接取
      Set<String>set={“item1”,”item2”,”item3”};//
      Map<String,Integer> map={“key1”:1,”key2”:2};//
      Intvalue=map[“key1”];//

2.在Switch中可用String

3.数值可加下划线用作分隔符(编译时自动被忽略)

4.支持二进制数字,如:int binary= 0b1001_1001;

JDK1.8 新特性
1. 简化了Lamda 表达式的语法
没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法
Collections.sort(names, (String a, String b) -> {
                 return b.compareTo(a);
                 })

对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字。如:

         Collections.sort(names,(String a, String b) -> b.compareTo(a));

或:Collections.sort(names, (a, b) -> b.compareTo(a));

2. 一组全新的时间日期API。

3 .多重注解:允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可

4. 接口的默认方法

5. 并行Streams

前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。

参考:jdk5-jdk10各个版本的新特性

14、字符型常量和字符串常量的区别?

  1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符
  2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
  3. 占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意: char 在 Java 中占两个字节)
汉字在GBK编码中占3个字节,在utf-8编码中占2个字节

15、 构造器 Constructor 是否可被 override?

Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

16、重载与重写

重写:
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里式替换原则,重写有以下三个限制:
  • 子类方法的访问权限必须大于等于父类方法;
  • 子类方法的返回类型必须是父类方法返回类型或为其子类型。
  • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型
重载:

存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。

17、 怎么解释“重载是编译时的多态性,重写是运行时的多态性”

在 Class 文件常量池里,每个方法有它的名称、描述符(参数类型+返回值类型)。名称加上描述符等于方法的签名,因为重载在编译时每个方法的签名就已经确定是不一样的,所以在编译时就能确定调用哪个方法,所以说重载是编译时的多态
而重写的话,因为是父类引用指向子类对象,通过这个父类引用去调用被子类重写了的方法。因为只有在运行时才知道指向是的哪个子类对象,所以只有在运行时才能确定执行哪个方法,所以说重写是运行时的多态。

18、 Java 面向对象编程三大特性: 封装 继承 多态

封装

封装把一个对象的属性私有化,同时提供一些可以被外界访问属性的方法,保证了属性的安全

继承
继承一个类使用 extends 关键字与另一个类建立的某种联系,子类的定义可以增加新的数据或新的功能,也可以用父类的功能,不能继承多个父类。通过使用继承我们能够非常方便地复用以前的代码。
多态

就是用基类的引用指向子类的对象, 具体访问时实现方法的动态绑定。在需要使用子类的地方声明为父类的引用,这样传入不同的子类对象,可以有不同的解释,产生不同的执行结果,这就是多态。

多态有什么好处?
  1. 多态使用了继承,所以有继承的好处,即子类可以复用父类的代码,大大提高程序的可复用性//继承 
  2. 父类的方法或引用变量可以调用被子类重写的功能在需要使用子类的地方声明为父类的引用,可以提高可扩充性可维护性,降低程序与对象的耦合度 //多态的真正作用

19、简述一下 快排的过程

1. 首先对整个数组选定一个主元,然后根据这个主元将整个数组分成小于主元的左区间,和所有元素都大于主元的右区间,然后分别对左右区间递归进行刚才的选主元划分左右集合的操作。

快排的最坏复杂度是多少

快排最好的情况是,每次正好中分,复杂度为O(nlogn)。最差情况,就是已经有序的情况下,如果每次选定的主元是区间的第一个元素,那么复杂度为O(n^2),退化成冒泡排序,因为每次快排都必须遍历整个区间的所有元素,所以这里就有一个O(n)的算法复杂度,但是因为它不能将元素划分成左右两个集合,所以不能将递归深度降到O(logn), 而是O(n),所以复杂度是O(n^2).

为什么快排的复杂度是O(nlogn)

对每次选定一个主元后都必须遍历这个区间内的所有元素,所以这里就有一个O(n)的算法复杂度,另外,对左右集合分别递归分治进行快排操作,这会产生一个深度为O(logn)的复杂度,所以总的来说快排的复杂度是 O(nlogn) 

如何优化快排

1. 随机选取主元
可以避免数组已经有序而退化成冒泡排序
2. 小数组切换到插入排序
对于小数组,插入排序比快速排序的性能更好
3. 三数取中法
最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。一种折中方法是取 3 个元素,并将大小居中的元素作为主元。

20、==equals的区别?

== 判断是否是同一个对象,equals 判段对象是否等价

21、讲一讲你对hashcodeequal这两个函数的认识

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。其中 “等价的两个对象散列值一定相同” 是一个约定,而不是一个定理。等价的两个对象散列值也可以不相同,比如直接让 equals 返回 true ,这样所有对象都是等价的,但是他们的 hash 值不相等。但是我们约定在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
比如新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 没有实现hashCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象

因为存在 hash 冲突的情况,所以hash  值相同的两个对象不一定等价。

22、StringStringBufferStringBuilder三者的区别?

1. 可变性
String 不可变
StringBuffer 和 StringBuilder 可变
2. 线程安全
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步

23、抽象类与接口的区别

 比较
  • 从设计层面上看,抽象类提供了一种 IS-A 关系(父子关系),那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象,子类会继承父类所有的属性和方法。而接口更像是一种 LIKE-A 关系(师徒关系),它只是提供一种方法实现契约,需要实现什么样的功能就实现什么样的接口
  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
  • 在 jdk8 以前,接口中的方法默认全是抽象方法,不能有方法体,而 抽象类中可以有不声明为抽象方法的方法,这些方法可以有方法体。但是jdk 8以后,接口也支持有方法体的默认方法了。
  • 接口的体量相对都比较小,而类的体量相对就比较大,体量大的话会增大维护成本和降低使用效率

使用选择

使用接口的场景:
需要让不相关的类都实现一个方法,增加某个功能时。例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
需要使用多重继承。
使用抽象类的场景:
需要在几个相关的类中共享代码。
需要能控制继承来的成员的访问权限,而不是都为 public。
需要继承非静态和非常量字段。
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低

24、 基础数据类型和包装类对象,需要使用包装类的场景

有了基本类型为什么还要有包装类型呢?
我们知道Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了。

基本类型与包装类型的异同

1、在Java中,一切皆对象,但八大基本类型却不是对象。

2、声明方式的不同,基本类型无需通过new关键字来创建,而封装类型则需new关键字。

3、存储方式及位置的不同,基本类型是直接存储变量的值,保存在堆栈中能高效的存取;封装类型需要通过引用指向实例,具体的实例保存在堆中;

4、初始值的不同,封装类型的初始值为null,基本类型的的初始值视具体的类型而定,比如int类型的初始值为0,boolean类型为false;

5、使用场景的不同,包装类的使用场景更加多样。比如与集合类合作使用时只能使用包装类型。

6、什么时候该用包装类,什么时候该用基本类型,看基本的业务来定:这个字段允不允许null值,如果允许,则必然要用封装类;否则,基本类型就可以了。如果用到比如泛型和反射调用函数,就需要用包装类! 

包装类的使用场景:
  1. 集合类中只能使用包装类
  2. 泛型中使用包装类
  3. 反射调用函数中使用包装类
  4. 数据库查询的结果为null时,不能赋值给基本类型,应该使用包装类

25、在一个静态方法内调用一个非静态成员为什么是非法的?

因为非静态成员只有存在于一个对象中才有意义,但是由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态成员方法。

26、 在 Java 中定义一个不做事且没有参数的构造方法的作用

为了子类对象能够正确的初始化。在执行子类的构造方法之前,如果没有显示的调用 super()函数来指定所调用的父类特定的构造方法,则会默认调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

27、构造方法作用和有哪些特性?

作用是完成对类实例对象的初始化工作
特性:
  1. 名字与类名相同。
  2. 没有返回值,但不能用 void 声明构造函数。
  3. 生成类的实例
  4. 对象时自动执行,无需调用。

28、静态方法和实例方法有何不同

  1. 静态方法属于类,而实例方法属于对象,实例方法只能通过对象调用,而静态方法还可以通过类名.静态方法来使用
  2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。

29、final 关键字的作用

final 关键字主要用在三个地方:变量、方法、类。
1. 变量
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
  • 对于基本类型,final 使数值不变;
  • 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
2. 方法
声明方法不能被子类重写。
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
3. 类
声明类不允许被继承。

30、异常体系 

    1. 所有异常继承公共祖先【throwable类】
    2. 异常分为两大类  Exception & Error
        (1) Error 是【程序无法处理】的错误,大部分是代码在JVM运行时出现问题。代码逻辑错误或者外部环境错误
             程序申请内存时,内存资源不足。抛出OOM
            Error发生时,JVM一般会【终止】线程
        (2) Exception是【程序可以处理】的错误
            常见Exception 
                ArithmeticException 算数异常
                NullPointException 空指针异常
                ArrayIndexOutOfBoundsException 下标越界

31、内存泄漏

  1. 程序没有释放已经不再使用的内存,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,因此这段内存一直被占用,无法释放,造成空间的浪费。
  2. 长生命周期的引用指向了短生命周期的对象

内存泄漏案例

    1. 单例模式 
        Instance可能早已不被使用,
        但是类仍持有Instance的【引用】。
        因此Intance【生命周期】和引用相同,造成内存泄漏。
        
    2. 容器 
        容器内的【键值对】不被使用时,
        Map仍持有key对象& value对象的【引用】,
        则会造成内存泄漏。
怎么查内存泄漏

《Java线上内存溢出问题排查步骤》

32、内存溢出

要求分配的内存超过了系统能给我的,系统不能满足需求。内存泄漏的堆积如果不及时处理最终会导致内存溢出

33、Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
34、既然有了字节流,为什么还要有字符流?

35、为什么 Java 不支持运算符重载? 另一个类似棘手的 Java 问题。为什么 C++ 支持运算符重载而 Java 不支持? 

有人可能会说+运算符在 Java 中已被重载用于字符串连接,不要被这些论据所欺骗。 与 C++ 不同,Java 不支持运算符重载。Java 不能为程序员提供自由的标准算术运算符重载,例如+, - ,*和/等。如果你以前用过 C++,那么 Java 与 C++ 相比少了很多功能,例如 Java 不支持多重继承,Java 中没有指针,Java 中没有引用传递。另一个类似的问题是关于 Java 通过引用传递,这主要表现为 Java 是通过值还是引用传参。虽然我不知道背后的真正原因,但我认为以下说法有些道理,为什么 Java 不支持运算符重载。 

(1)简单性和清晰性。

清晰性是 Java 设计者的目标之一。设计者不是只想复制语言,而是希望拥有一种清晰,真正面向对象的语言。添加运算符重载比没有它肯定会使设计更复杂,并且它可能导致更复杂的编译器, 或减慢 JVM,因为它需要做额外的工作来识别运算符的实际含义,并减少优化的机会, 以保证 Java 中运算符的行为。 

(2)避免编程错误。

Java 不允许用户定义的运算符重载,因为如果允许程序员进行运算符重载,将为同一运算符赋予多种含义,这将使任何开发人员的学习曲线变得陡峭,事情变得更加混乱。据观察,当语言支持运算符重载时,编程错误会增加,从而增加了开发和交付时间。由于 Java 和 JVM 已经承担了大多数开发人员的责任,如在通过提供垃圾收集器进行内存管理时,因为这个功能增加污染代码的机会, 成为编程错误之源, 因此没有多大意义。 

(3)JVM 复杂性。

从 JVM 的角度来看,支持运算符重载使问题变得更加困难。通过更直观,更干净的方式使用方法重载也能实现同样的事情,因此不支持 Java 中的运算符重载是有意义的。与相对简单的 JVM 相比,复杂的 JVM 可能导致 JVM 更慢,并为保证在 Java 中运算符行为的确定性从而减少了优化代码的机会。 4)让开发工具处理更容易。这是在 Java 中不支持运算符重载的另一个好处。省略运算符重载使语言更容易处理,这反过来又更容易开发处理语言的工具,例如 IDE 或重构工具。Java 中的重构工具远胜于 C++。

36、假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化, 会发生什么情况 

这取决于类是否具有其自己的 serialVersionUID。正如我们从上面的问题知道, 如果我们不提供 serialVersionUID, 则 Java 编译器将生成它, 通常它等于对象的哈希代码。通过添加任何新字段, 有可能为该类新版本生成的新 serialVersionUID 与已序列化的对象不同, 在这种情况下, Java 序列化 API 将引发 java.io.InvalidClassException, 因此建议在代码中拥有自己的 serialVersionUID, 并确保在单个类中始终保持不变。 

37、Java 序列化机制中的兼容更改和不兼容更改是什么? 

真正的挑战在于通过添加任何字段、方法或删除任何字段或方法来更改类结构, 方法是使用已序列化的对象。根据 Java 序列化规范, 添加任何字段或方法都面临兼容的更改和更改类层次结构或取消实现的可序列化接口, 有些接口在非兼容更改下。对于兼容和非兼容更改的完整列表, 我建议阅读 Java 序列化规范。 4.我们可以通过网络传输一个序列化的对象吗 是的 ,你可以通过网络传输序列化对象, 因为 Java 序列化对象仍以字节的形式保留, 字节可以通过网络发送。你还可以将序列化对象存储在磁盘或数据库中作为 Blob。  在 Java 序列化期间,哪些变量未序列化? 这个问题问得不同, 但目的还是一样的, Java 开发人员是否知道静态和瞬态变量的细节。由于静态变量属于类, 而不是对象, 因此它们不是对象状态的一部分, 因此在 Java 序列化过程中不会保存它们。由于 Java 序列化仅保留对象的状态,而不是对象本身。瞬态变量也不包含在 Java 序列化过程中, 并且不是对象的序列化状态的一部分。在提出这个问题之后,面试官会询问后续内容, 如果你不存储这些变量的值, 那么一旦对这些对象进行反序列化并重新创建这些变量, 这些变量的价值是多少?这是你们要考虑的。

38、Java 中,嵌套公共静态类与顶级类有什么不同?

类的内部可以有多个嵌套公共静态类,但是一个 Java 源文件只能有一个顶级公共类,并且顶级公共类的名称与源文件名称必须一致。

原文地址:https://www.cnblogs.com/hi3254014978/p/14146448.html