乐趣区

关于java:Java基础面试常问知识点

一 JAVA 根底精选

1. JAVA 语言根底

1.1 JVM、JRE 和 JDK 的关系

JVM

Java Virtual Machine 是 Java 虚拟机,是运行 Java 字节码的虚拟机,在 JVM 中不同的平台有本人的虚拟机,因而 Java 语言能够实现跨平台。

什么是字节码?采纳字节码最大的益处是什么?

字节码: Java 源代码通过虚拟机编译器编译后产生的文件(即扩大为.class 的文件)艰深的讲就是 JVM 能够了解的代码叫做字节码,它不面向任何特定的处理器,只面向虚拟机。

** 采纳字节码的益处:**Java 语言通过字节码的形式,在肯定水平上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比拟高效,而且,因为字节码并不专对一种特定的机器,因而,Java 程序毋庸从新编译便可在多种不同的计算机上运行。

Java 程序从源代码到运行的过程:

强调一点的是,.class 文件 –> 可执行的机器码,在这一步,JVM 类加载器首先加载字节码文件,而后通过解释器逐行解释执行,这只形式的执行速度绝对比较慢,而且有些办法和代码块是常常须要被调用的,所以前面引进了 JIT 属于运行时编译,而 JIT 属于运行时编译,当 JIT 编译器实现第一次编译后,就会将字节码对应的机器码保留下来,下一次间接能够应用,而咱们晓得机器码的运行效率高于 Java 解释器的,这也解释了咱们为什么常常会说 Java 是编译和解释共存的语言。

JRE
Java 的运行环境, 包含 Java 虚拟机,Java 类库,Java 命令和其余的一些根底组件,但时不鞥创立新程序,如果想要运行一个开发好的 Java 程序,计算机中只须要装置 JRE 即可。

JDK
它是功能齐全的 Java SDK, 它蕴含 JRE,还有编译器(javac)和工具(javadoc 和 jdb), 它可能创立和编译程序。

JDK 和 JRE 的区别

  • JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
  • JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。

具体来说 JDK 其实蕴含了 JRE,同时还蕴含了编译 java 源码的编译器 javac,还蕴含了很多 java 程序调试和剖析的工具。简略来说:如果你须要运行 java 程序,只需装置 JRE 就能够了,如果你须要编写 java 程序,须要装置 JDK。

JVM&JRE&JDK 关系图

1.2 什么是主动装箱和主动拆箱

主动装箱就是主动将根本数据类型转换为包装器类型;拆箱就是主动将包装器类型转换为根本数据类型。比方:把 int 转化成 Integer,double 转化成 Double,等等。反之就是主动拆箱。

  • 根本数据类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
// 主动装箱
Integer total = 99;
// 主动拆箱
int totalprim = total;

综上所述,装箱就是主动将根本数据类型转换为包装器类型;拆箱就是主动将包装器类型转换为根本数据类型。

谈谈 int 和 Integer 的区别

  • Integer 是 int 的包装类,int 则是 java 的一种根本数据类型
  • Integer 变量必须实例化后能力应用,而 int 变量不须要
  • Integer 理论是对象的援用,当 new 一个 Integer 时,实际上是生成一个指针指向此对象;而 int 则是间接存储数据值
  • Integer 的默认值是 null,int 的默认值是 0

延长:

对于 Integer 和 int 的比拟

因为 Integer 变量实际上是对一个 Integer 对象的援用,所以两个通过 new 生成的 Integer 变量永远是不相等的(因为 new 生成的是两个对象,其内存地址不同)。

Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false

Integer 变量和 int 变量比拟时,只有两个变量的值是相等的,则后果为 true(因为包装类 Integer 和根本数据类型 int 比拟时,java 会主动拆包装为 int,而后进行比拟,实际上就变为两个 int 变量的比拟)

Integer i = new Integer(100);
int j = 100;System.out.print(i == j); //true

非 new 生成的 Integer 变量和 new Integer()生成的变量比拟时,后果为 false。(因为非 new 生成的 Integer 变量指向的是 java 常量池中的对象,而 new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)

Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false

对于两个非 new 生成的 Integer 对象,进行比拟时,如果两个变量的值在区间 -128 到 127 之间,则比拟后果为 true,如果两个变量的值不在此区间,则比拟后果为 false

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false

对于第 4 条的起因:java 在编译 Integer i = 100 ; 时,会翻译成为 Integer i = Integer.valueOf(100);,而 java API 中对 Integer 类型的 valueOf 的定义如下:

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

java 对于 -128 到 127 之间的数,会进行缓存,Integer i = 127 时,会将 127 进行缓存,下次再写 Integer j = 127 时,就会间接从缓存中取,就不会 new 了。

具体参考:[深刻分析 Java 中的装箱和拆箱]()

1.3 Java 面向对象编程三大个性(封装、继承、多态)

封装:暗藏对象的属性和实现细节,仅对外提供公共拜访形式,将变动隔离,便于应用,进步复用性和安全性。即封装就是把一个对象的属性私有化,同时

提供一些能够被外界拜访的属性的办法。

对于 Java 类而言:封装的是本人的属性和办法,所以它是不须要依赖其余对象就能够实现本人的操作,封装有几点益处:

  • 良好的封装可能缩小耦合。
  • 类外部的构造能够自在批改。
  • 能够对成员进行更准确的管制。
  • 暗藏信息,实现细节。

继承

继承是从已有类失去继承信息创立新类的过程,提供继承信息的类称为父类,失去继承信息的类称为子类。同时通过应用继承咱们可能十分不便地复用以

前的代码,可能大大的进步开发的效率。

应用继承的时候记住三点:

  • 子类领有父类非 private 的属性和办法。
  • 子类能够领有本人属性和办法,即子类能够对父类进行扩大。
  • 子类能够用本人的形式实现父类的办法。

多态

所谓多态就是指程序中定义的援用变量所指向的具体类型和通过该援用变量收回的办法调用在编程时并不确定,而是在程序运行期间才确定,即一个援用变

量到底会指向哪个类的实例对象,该援用变量收回的办法调用到底是哪个类中实现的办法,必须在由程序运行期间能力决定。即 父类或接口定义的援用变量

能够指向子类或具体实现类的实例对象。进步了程序的拓展性。

在 Java 中有两种模式能够实现多态:继承(多个子类对同一办法的重写)和接口(实现接口并笼罩接口中同一办法)。

基于继承实现的多态

  • 基于继承的实现机制次要体现在父类和继承该父类的一个或多个子类对某些办法的重写,多个子类对同一办法的重写能够体现出不同的行为。
  • 基于继承实现的多态能够总结如下:对于援用子类的父类类型,在解决该援用时,它实用于继承该父类的所有子类,子类对象的不同,对办法的实现也

就不同,执行雷同动作产生的行为也就不同。

  • 如果父类是抽象类,那么子类必须要实现父类中所有的形象办法,这样该父类所有的子类肯定存在对立的对外接口,但其外部的具体实现能够各异。这

样咱们就能够应用顶层类提供的对立接口来解决该档次的办法。
基于接口实现的多态

  • 继承是通过重写父类的同一办法的几个不同子类来体现的,那么就可就是通过实现接口并笼罩接口中同一办法的几不同的类体现的。
  • 在接口的多态中,指向接口的援用必须是指定这实现了该接口的一个类的实例程序,在运行时,依据对象援用的理论类型来执行对应的办法。
  • 继承都是单继承,只能为一组相干的类提供统一的服务接口。然而接口能够是多继承多实现,它可能利用一组相干或者不相干的接口进行组合与裁减,

可能对外提供统一的服务接口。所以它绝对于继承来说有更好的灵活性。

多态的实现条件 继承、重写、向上转型

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些办法进行从新定义,在调用这些办法时就会调用子类的办法。
  • 向上转型:在多态中须要将子类的援用赋给父类对象,只有这样该援用才可能具备技能调用父类的办法和子类的办法。

只有满足了上述三个条件,咱们才可能在同一个继承构造中应用对立的逻辑实现代码解决不同的对象,从而达到执行不同的行为。

综上所述:

子类 Child 继承父类 Father,咱们能够编写一个指向子类的父类类型援用,该援用既能够解决父类,Father 对象,也能够解决子类 Child 对象,当雷同的

音讯发送给子类或者父类对象时,该对象就会依据本人所属的援用而执行不同的行为,这就是多态。即多态性就是雷同的音讯使得不同的类做出不同的响应。

1.4 重载和重写的区别

  • 重载:产生在同一个类中,办法名雷同参数列表不同(参数类型不同、个数不同、程序不同),与办法返回值和拜访修饰符无关,即重载的办法不能依据返回类型进行辨别
  • 重写:产生在父子类中,办法名、参数列表必须雷同,返回值小于等于父类,抛出的异样小于等于父类,拜访修饰符大于等于父类(里氏代换准则);如果父类办法拜访修饰符为 private 则子类中就不是重写。

1.5 Java 创建对象的四种形式

  • 应用 new 创建对象 应用 new 关键字创建对象应该是最常见的一种形式,但咱们应该晓得,应用 new 创建对象会减少耦合度。无论应用什么框架,都要缩小 new 的应用以升高耦合度。
  • 应用反射的机制创建对象 应用 Class 类的 newInstance 办法
  • 采纳 clone clone 时,须要曾经有一个调配了内存的源对象,创立新对象时,首先应该调配一个和源对象一样大的内存空间。要调用 clone 办法须要实现 Cloneable 接口
  • 采纳序列化机制 应用序列化时,要实现实现 Serializable 接口,将一个对象序列化到磁盘上,而采纳反序列化能够将磁盘上的对象信息转化到内存中。

1.6 equals 和 == 区别

对于 ==

  • 根本数据类型(也称原始数据类型):byte,short,char,int,long,float,double,boolean。则间接比拟其存储的 ” 值 ” 是否相等。
  • 援用数据类型(String,Date):当他们用(==)进行比拟的时候,比拟的是他们在内存中的寄存地址(确切的说,是 堆内存 地址)。

注:对于第二种类型,除非是同一个 new 进去的对象,他们的比拟后的后果为 true,否则比拟后后果为 false。因为每 new 一次,都会从新开拓堆内存空间。

equals

  • equals 办法是基类 Object 中的办法,因而对于所有的继承于 Object 的类都会有该办法。在 Object 类中,equals 办法是用来比拟两个对象的援用是否相等,即是否指向同一个对象。
  • 然而在 java 中很多类重写 equals 办法, 比方 String、Integer,Date 等把它变成了值比拟,所以个别状况下 equals 比拟的是值是否相等。

阐明:

  • String 中的 equals 办法是被重写过的,因为 object 的 equals 办法是比拟的对象的内存地址,而 String 的 equals 办法比拟的是对象的值。
  • 当创立 String 类型的对象时,虚构机会在常量池中查找有没有曾经存在的值和要创立的值雷同的对象,如果有就把它赋给以后援用。如果没有就在常量池中从新创立一个 String 对象。

hashCode 与 equals

两个对象的 hashCode() 雷同,则 equals() 也肯定为 true,对吗?

hashCode 和 equals 办法的关系

面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 办法?”

hashCode()介绍

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引地位。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都蕴含有 hashCode()函数。

散列表存储的是键值对(key-value),它的特点是:能依据“键”疾速的检索出对应的“值”。这其中就利用到了散列码!(能够疾速找到所须要的对象)

为什么要有 hashCode

咱们以“HashSet 如何查看反复”为例子来阐明为什么要有 hashCode

当你把对象退出 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象退出的地位,同时也会与其余曾经退出的对象的 hashcode 值作比拟,如果没有相符的 hashcode,HashSet 会假如对象没有反复呈现。然而如果发现有雷同 hashcode 值的对象,这时会调用 equals()办法来查看 hashcode 相等的对象是否真的雷同。如果两者雷同,HashSet 就不会让其退出操作胜利。如果不同的话,就会从新散列到其余地位。(摘自我的 Java 启蒙书《Head first java》第二版)。这样咱们就大大减少了 equals 的次数,相应就大大提高了执行速度。

hashCode()与 equals()的相干规定

  • 如果两个对象相等,则 hashcode 肯定也是雷同的
  • 两个对象相等,对两个对象别离调用 equals 办法都返回 true
  • 两个对象有雷同的 hashcode 值,它们也不肯定是相等的
  • 因而,equals 办法被笼罩过,则 hashCode 办法也必须被笼罩
  • hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即便这两个对象指向雷同的数据)

具体参考:Java hashCode() 和 equals()的若干问题解答

1.7 String、StringBuffer 和 StringBuilder 的区别

可变形

简略的来说: String 类中应用 final 关键字润饰字符数组来保留字符串,private final char value[],所以 String 对象是不可变的。

而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是应用字符数组保留字符串 char[]value 然而没有用

final 关键字修 饰,所以这两种对象都是可变的。

线程安全性

String 中的对象是不可变的,也就能够了解为常量,线程平安。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的

基本操作,如 expandCapacity、append、insert、indexOf 等公共办法。StringBuffer 对办法加了同步锁或者对调用的办法加了同步 锁,所以是线程平安

的。StringBuilder 并没有对办法进行加同步锁,所以是非线程平安的。

性能

每次对 String 类型进行扭转的时候,都会生成一个新的 String 对象,而后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象自身进行

操作,而不是生成新的对象并扭转对象 援用。雷同状况下应用 StringBuilder 相比应用 StringBuffer 仅能取得 10%\~15% 左右的性能晋升,但却要冒多线程

不平安的⻛险。

对于三者应用的总结:

  • 操作大量的数据: 实用 String
  • 单线程操作字符串缓冲区下操作大量数据: 实用 StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据: 实用 StringBuffer

String Pool

字符串常量池(String Pool)保留着所有字符串字面量(literal strings),这些字面量在编译期间就确定。不仅如此,还能够应用 String 的 intern() 办法在运行过程中将字符串增加到 String Pool 中。

当一个字符串调用 intern() 办法时,如果 String Pool 中曾经存在一个字符串和该字符串值相等(应用 equals() 办法进行确定),那么就会返回 String Pool 中字符串的援用;否则,就会在 String Pool 中增加一个新的字符串,并返回这个新字符串的援用。

上面示例中,s1 和 s2 采纳 new String() 的形式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 办法获得一个字符串援用。intern() 首先把 s1 援用的字符串放到 String Pool 中,而后返回这个字符串援用。因而 s3 和 s4 援用的是同一个字符串。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4);           // true

如果是采纳 “bbb” 这种字面量的模式创立字符串,会主动地将字符串放入 String Pool 中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永恒代。而在 Java 7 后,String Pool 被移到堆中。这是因为永恒代的空间无限,在大量应用字符串的场景下会导致 OutOfMemoryError 谬误

String str=”i” 与 String str=new String(“i”)一样吗?

不一样,因为内存的调配形式不一样。String str=”i” 的形式,java 虚构机会将其调配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

具体解析参考:深刻了解 Java 中的 String

1.8 接口和抽象类有什么区别?

相同点

  • 接口和抽象类都不能实例化
  • 都位于继承的顶端,用于被其余实现或继承
  • 都蕴含形象办法,其子类都必须覆写这些形象办法

不同点

参数 抽象类 接口
申明 抽象类应用 abstract 关键字申明 接口应用 interface 关键字申明
实现 子类应用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它须要提供抽象类中所有申明的办法的实现 子类应用 implements 关键字来实现接口。它须要提供接口中所有申明的办法的实现
结构器 抽象类能够有结构器 接口不能有结构器
拜访修饰符 抽象类中的办法能够是任意拜访修饰符 接口办法默认修饰符是 public。并且不容许定义为 private 或者 protected
多继承 一个类最多只能继承一个抽象类 一个类能够实现多个接口
字段申明 抽象类的字段申明能够是任意的 接口的字段默认都是 static 和 final 的

备注:Java8 中接口中引入默认办法和静态方法,以此来缩小抽象类和接口之间的差别。

当初,咱们能够为接口提供默认实现的办法了,并且不必强制子类来实现它。

接口和抽象类各有优缺点,在接口和抽象类的抉择上,必须恪守这样一个准则:

  • 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
  • 抉择抽象类的时候通常是如下状况:须要定义子类的行为,又要为子类提供通用的性能。

一般类和抽象类有哪些区别

  • 一般类不能蕴含形象办法,抽象类能够蕴含形象办法。
  • 抽象类不能间接实例化,一般类能够间接实例化。

抽象类能应用 final 润饰吗?

不能,定义抽象类就是让其余类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能润饰抽象类

1.9 static 和 final 区别

Static

  • 润饰变量:动态变量随着类加载时被实现初始,内存中只有一个,且 jvm 只会为它调配一次内存,所有类共存动态变量。
  • 润饰办法:在类加载的时候就存在,不依赖任何实例 static 办法必须实现,不能用 abstract 润饰。
  • 润饰代码块:在类加载之后就会执行代码块的内容。

final

  • 润饰变量:
    • 编译器常量:类加载的过程实现初始化,编译后带入到任何计算式中,只能是根本类型。
    • 运行时常量:根本数据类型或援用数据类型,援用不可变,但援用的对象内容可变。
  • 润饰办法:不能被继承,不能被子类批改。
  • 润饰类:不能被继承
  • 润饰形参:final 形参不可变。

final 的益处

  • final 关键字进步了性能。JVM 和 Java 利用都会缓存 final 变量。
  • final 变量能够平安的在多线程环境下进行共享,而不须要额定的同步开销。
  • 应用 final 关键字,JVM 会对办法、变量及类进行优化。

static 办法是否能够笼罩?

static 办法不能被笼罩,因为办法笼罩是基于运行时动静绑定的,而 static 办法是编译时动态绑定的。static 办法跟类的任何实例都不相干,所以概念上不实用。

finalize 用处

答:垃圾回收器 (garbage colector) 决定回收某对象时,就会运行该对象的 finalize()办法 然而在 Java 中很可怜,如果内存总是短缺的,那么垃圾回收可能永远不会进行,也就是说 filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。那么 finalize()到底是做什么的呢?它最次要的用处是回收非凡渠道申请的内存。Java 程序有垃圾回收器,所以个别状况下内存问题不必程序员操心。但有一种 JNI(Java Native Interface)调用 non-Java 程序(C 或 C ++),finalize()的工作就是回收这部分的内存。

finally

finally 肯定会被执行,如果 finally 里有 return 语句,则笼罩 try/catch 里的 return,比拟爱考的是 finally 里没有 return 语句,这时尽管 finally 里对 return 的值进行了批改,但 return 的值并不扭转这种状况

finally 代码块和 finalize()办法有什么区别?

无论是否抛出异样,finally 代码块都会执行,它次要是用来开释利用占用的资源。finalize()办法是 Object 类的一个 protected 办法,它是在对象被垃圾回收之前由 Java 虚拟机来调用的。

1.10 父子类的加载程序?

类的加载程序。

  • (1) 父类动态代码块(包含动态初始化块,动态属性,但不包含静态方法)
  • (2) 子类动态代码块(包含动态初始化块,动态属性,但不包含静态方法)
  • (3) 父类非动态代码块(包含非动态初始化块,非动态属性)
  • (4) 父类构造函数
  • (5) 子类非动态代码块 (包含非动态初始化块,非动态属性)
  • (6) 子类构造函数

具体解析参考:Java 中的父子类的执行程序到底是怎么一回事?

看到这里明天的分享就完结了,如果感觉这篇文章还不错,来个 分享、点赞、在看 三连吧,让更多的人也看到~

欢送关注集体公众号 「JavaClub」,定期为你分享一些面试干货。

退出移动版