Java基础 高频面试题

本文详细梳理了 Java 语言的核心特性,包括面向对象的三大特性、JVM、JRE 和 JDK 的区别、关键字用法、异常处理、IO 流、反射机制、序列化、相似概念的区别以及其他常见面试问题。内容涵盖了从基础知识到高级应用,是 Java 面试的必备参考资料。
摘要由CSDN通过智能技术生成

之前以为面经只是死记硬背的东西,后来发现记住了它们,对自己对知识的理解确实有帮助,难怪语文的文章老是要求背背背。

前言

这次的面经整理分为以下几个部分,希望对大家的工作有帮助。

内容链接地址
Java 基础传送门
Java 集合传送门
Java 多线程传送门
Java虚拟机传送门
计算机网络传送门
数据结构和算法传送门
数据库传送门
JavaWeb传送门
设计模式传送门
Spring、MyBatis传送门

目录:

1 Java 语言的特点

Java 语言含有以下特点:

  • 是一门面向对象编程的语言;
  • 通过 Java 虚拟机实现了跨平台运行,即实现了一次编译,随处运行;
  • 编译与解释并存:利用 javac 将 Java 代码编译成 Java 字节码,同时保留了解释型语⾔可移植的特点。

1.1 何为面向对象

总结:

面向对象语言具有三大特性,分别是封装、继承和多态,这是面向对象与面向过程的主要区别,利用这三大特性,面向对象可以设计出低耦合的程序,使其更易维护、复用和扩展。

1)面向对象与面向过程编程的区别

  • 面向过程编程就好像是有有一只“上帝的手”通过制定一系列规则来操纵一堆“死物“,通过函数来解决问题;性能一般来说较面向对象的高;
  • 面向对象就好像是把世界描绘成有主动性的“活物”之间的交互;通过对象来解决问题;由于类调用时需要实例化,比较消耗资源,所以性能较面向过程的低,但其易维护、易复用、易扩展。

2)封装性

封装就是将一个对象的属性私有化,不允许外部程序直接访问,而是通过该对象的 setter 或者 getter 方法来实现对隐藏信息的操作。

3)继承性

继承就是使用已经存在的类的定义作为基础来建立新的类的技术,它可以使用父类的功能,也可以在其基础上增加新的数据或者功能,甚至是重写父类的功能,但不能选择性地继承父类的功能。

注意:

  • 子类拥有父类的所有属性和方法(包括私有属性和方法),但对于父类的私有属性和方法,子类是无法访问的,只是拥有。
  • 继承后 new 时的初始化顺序:父类属性的初始化 → 父类构造方法的初始化 → 子类属性的初始化 → 子类构造方法的初始化。

4)多态性

多态性就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。

即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在程序运行期间才能确定。

在 Java 中有两种形式可以实现多态:

  1. 子类继承父类;
  2. 类实现接口。

1.2 JVM、JRE 以及 JDK

JVM < JRE < JDK

1)JVM

Java 虚拟机(JVM)是运行 Java 字节码的虚拟机,JVM 有针对不同系统的特定实现,目的是当使用相同的字节码时,它们都会给出相同的结果,字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。

在 Java 中,JVM 可以理解的代码就叫做字节码 (即扩展名为 .class 的⽂件),它不⾯向任何特定的处理器,只⾯向虚拟机。Java 语⾔通过字节码的方式,在⼀定程度上解决了传统解释型语⾔执行效率低的问题,同时⼜保留了解释型语⾔可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对⼀种特定的机器,因此,Java 程序⽆须重新编译便可在多种不同操作系统的计算机上运⾏。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SKVjl42W-1634887132899)(C:\Users\Sharm\Pictures\Typora图片存储\面经\3.jpg)]

2)JRE

JRE 是 Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM)、Java 类库、java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。

3)JDK

JDK 是 Java 开发工具包,它是功能⻬全的 Java SDK,它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序。

 

2 Java 关键字总结

2.1 private、protected、public

它们是用来定义访问权限修饰符的关键字。

请添加图片描述

其中在一个 .java 文件中的两个 Class 是算同一个包里的两个类,而非同一个类。
 

2.2 abstract、final、static、synchronized

用于定义类、方法、变量的关键字。

2.2.1 abstract

abstract 可以修饰类和方法;参考文献:传送门

1)抽象类

抽象类的书写形式为:[public]abstract class 类名{};

特点:

  1. 抽象类不能被实例化。因为抽象类本身就代表了一种类型,无法确定为一个具体的类型;
  2. 虽然抽象类不能被实例化,但其可以有构造方法。因为当子类进行初始化的时候,必然会优先初始化父类的属性以及方法。同时,对于抽象类中的非 static 和非 abstract 方法中的 this 关键字代表的是它的继承类,而非抽象类本身;
  3. 抽象类不能使用 final 关键字修饰,因为 final 修饰的类是无法被继承的;
2)抽象方法
  1. 抽象方法没有自己的主体,即没有{}包起来的业务逻辑,这一点跟接口中的方法有点类似;
public abstract class Car {	
	public abstract void run();
}

class Bicycle extends Car{
	@Override
	public void run() {
		System.out.println("人踩着跑。。。");
	}
	
}
  1. 抽象方法不能用 private 修饰,因为抽象方法必须被子类重写,而 private 权限对于子类来说是不能访问的,所以就会产生矛盾;
  2. 抽象方法也不能用 static 修饰,试想一下,如果用 static 修饰了,那么我们可以直接通过类名调用,而抽象方法压根就没有主体,没有任何业务逻辑,这样做毫无意义;
  3. 抽象方法必须在抽象类中,而抽象类可以没有抽象方法,因为它可以有非抽象的方法
  4. 如果一个类继承了一个抽象类,且这个子类不是抽象类的话,那么它必须重写抽象类中全部的抽象方法。

2.2.2 final

final 可以用来修饰类、方法和变量。

  1. 当用来修饰变量时,如果该变量是基本数据类型的变量,则其数值一旦初始化后便不能更改;如果该变量是引用数据变量,则其初始化后便不能再让其指向另一个对象。
  2. 当用来修饰方法时,表示该方法是不能被子类重写的;
  3. 当用来修饰类时,表示该类是不能被继承的,同时该类中所有成员方法都会被隐式地指定为 final 方法。

2.2.3 static

static 关键字可以用来修饰成员变量、成员方法、代码块和内部类;

  1. 用 static 关键字修饰的静态变量和不用 static 关键字修饰的实例变量。静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,对静态变量的引用有两种方式,分别是“类.静态变量"和”对象.静态变量"。实例变量属于对象,只有对象被创建后,实例变量才会被分配内存空间,才能被使用,它在内存中存在多个复制,只有用“对象.实例变量”的方式来引用。
  2. static 方法是类的方法,不需要创建对象就可以被调用,而非 static 方法是对象的方法,只有对象被创建出来后才可以被使用。static 方法中不能使用 this 和 super 关键字,不能调用非 static 方法,只能访问所属类的静态成员变量和成员方法。
  3. static 代码块在类中是独立于成员变量和成员方法的代码块的,同时,这些 static 代码块只会被执行一次,即实例化的时候执行;
  4. 创建静态内部类的对象时,不需要外部类的对象,可以直接创建 内部类 对象名= new 内部类()

2.2.4 synchronized

synchronized 关键字可以用来修饰方法和代码块,其作用是防止程序的关键代码在同一时间被多个线程执行。

  1. 当应用于静态方法时,该方法由一个线程执行时,整个类将被锁定;
  2. 当应用于实例方法时,该方法由一个线程执行时,该实例将被锁定;
  3. 如果应用于对象或数组,当关联的代码块一次由一个线程执行时,对象或数组将被锁定。

参考文献:传送门

 

2.3 extends 和 implements

表示定义类与类之间关系的关键字。

2.3.1 extends

  1. extends 关键字用在 class 或 interface 声明中,用于指示所声明的类或接口是 extends 关键字后的类或接口的子类(子接口);
  2. 子类继承父类的所有 public 和 protected 变量和方法;
  3. 子类可以重写父类的任何非 final 方法;
  4. 一个类只能继承一个其它类。

2.3.2 implements

  1. implements 关键字在 class 声明中使用,以指示所声明的类提供了在 implements 关键字后面的名称所指定的接口中所声明的所有方法的实现;
  2. 一个类可以实现多个接口。

 

2.4 new、this、super 和 instanceof

表示定义建立实例、引用实例和判断实例的关键字。

2.4.1 new(新建)

  1. new 关键字用于创建类的新实例;
  2. new 关键字后面的参数必须是类名,并且类名的后面必须是一组构造方法参数(必须带括号);
  3. 参数集合必须与类的构造方法的参数匹配;
  4. = 左侧的变量的类型必须与要实例化的类具有赋值兼容关系。

2.4.2 this

  1. this 关键字用于引用当前实例
  2. this 和 super 是针对于对象实例的;

2.4.3 super

  1. super 关键字用于引用此类的父类;
  2. 作为独立语句出现的 super 表示调用父类的构造方法;
  3. super.() 表示调用父类的方法。只有在如下情况中才需要采用这种用法:要调用在该类中被重写的方法,以便指定应当调用在超类中的该方法。

2.4.4 instanceof(实例)

  1. instanceof 关键字用来确定对象实例所属的类。

 

2.5 try、catch、finally、throw 和 throws

用来处理异常的关键字。

处理异常的方式:

  • 通过使用 try catch finally 来捕获异常,自己处理;
  • 通过 throw 或 throws 来抛出异常,让别人处理;

2.5.1 try(捕获异常)

  1. try 关键字用于包含可能引发异常的语句块;
  2. 每个 try 块都必须至少有一个 catch 或 finally 子句;
  3. 如果某个特定异常类未被任何 catch 子句处理,该异常将沿着调用栈递归地传播到下一个封闭 try 块,如果任何封闭 try 块都未捕获到异常,Java 解释器将退出,并显示错误消息和堆栈跟踪信息。

2.5.2 catch(处理异常)

  1. catch 关键字用来在 try-catch 或 try-catch-finally 语句中定义异常处理块。

2.5.3 finally(必须执行)

  1. finally 关键字用来定义始终在 try-catch-finally 语句中执行的块;
  2. finally 块通常包含清理代码,用在部分执行 try 块后恢复正常运行;
  3. finally 块的 return 语句比 catch 块优先执行。

2.5.4 throw 与 throws

throwthrows
throw 是在方法体内,表示抛出一个异常;throws 用在方法的声明上,表示该方法可能要抛出的异常;
throw 则是明确了这个地方要抛出这个异常;throws E1,E2,E3 只是告诉程序这个方法可能会抛出这些异常,方法的调用者可能要处理这些异常;

参考文献:传送门

 

3 Java 的 IO 流

参考文献:传送门

3.1 为什么有了字节流,还要有字符流

问题的本质:不管是⽂件读写还是⽹络发送接收,信息的最小存储单元都是字节,那为什么 IO 流操作要分为字节流操作和字符流操呢?

虽然字符是通过字节转换而成的 ,但是这个过程非常耗时,而且编解码标准的不同会导致出现乱码错误,所以 Java 的 IO 流干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。

如果⾳频⽂件、图片等媒体⽂件⽤字节流⽐较好,如果涉及到字符的话使⽤字符流比较好。

3.2 BIO、NIO、AIO 有什么区别

1)同步与异步、阻塞与非阻塞

阻塞和非阻塞强调的是程序在等待调用结果时的状态,同步和异步强调的是消息通信机制。

  • 同步:当调用某个方法时,调用方得等待这个调用返回结果后才能继续往后执行;

  • 异步:当调用某个方法时,调用方也许不会立刻得到结果,但是调用方在调用这个方法后可以继续执行后续操作,而被调用方在另一个线程中执行完毕后会通过某种方法将结果返回给调用方。举个例子:线下买东西和线上买东西的区别。

  • 阻塞:阻塞是指调用结果返回之前,当前线程会被挂起,并在得到结果之后才会继续。

  • 非阻塞:非阻塞指的是如果调用结果不能立刻返回,则该调用者不会阻塞当前线程,因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。

2)Java 的 IO 模型

IO 即数据的读取(接收)或写入(发送)操作,通常用户进程中的一个完整IO分为两阶段:用户进程空间<–>内核空间、内核空间<–>设备空间(磁盘、网络等)。IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

  • 阻塞IO模型(Block IO):当用户线程发出 IO 请求后,内核会去查看数据是否准备就绪,如果数据还未准备就绪,则该用户线程就会处于阻塞状态,此时用户线程交出 CPU,等到数据准备就绪之后,返回结果给用户线程,此时用户线程结束阻塞状态;
  • 非阻塞IO模型(No Block IO):当用户线程发出 IO 请求后,不需要等待,而是立刻就能得到一个结果,如果结果是 error,则说明数据还没有准备好,于是其继续发出IO请求,直到返回准备好的数据。在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO 不会交出 CPU,而会一直占用 CPU;
  • 异步IO模型(AIO):当用户线程发出 IO 请求后,不需要等待,继续执行该用户线程接下来的后续操作,等到请求的数据在另一个线程准备好后,会响应给用户线程,告诉用户线程数据已经准备就绪;

3.3 用键盘输入常用的两种方式

  • 通过 Scanner

    Scanner scanner = new Scanner(System.in);
    String s = scanner.nextLine();
    scanner.close();
    
  • 通过 InputStreamReader

    BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
    String s = input.readLine();
    

4 Java 的异常处理

4.1 异常类层次结构图

请添加图片描述

如上图所示,java.lang 包中的 Throwable 类是所有异常类的父类,其中包括两大子类,分别是 Error 和 Exception。

  • Error(错误):程序无法处理的错误。表示运行应用程序中较严重的问题,绝大多数是程序运行时不允许出现的状况,同时,这些错误是不可查的,因为它们在应用程序的控制和处理能力之外。
  • Exception(异常):异常和错误的区别在于异常能被程序本身处理,而错误是无法处理的。

4.2 处理异常的方式

  • try 块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块;
  • catch 块:用于处理 try 捕获到的异常。
  • finally 块:无论是否捕获或处理异常,finally 块里的语句都会被执行,当在 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。不过当出现以下四种特殊情况时,finally 块不会被执行:
    1. 在 finally 语句块第一行发生了异常。
    2. 在前面的代码中使用了 System.exit(int) 来已退出程序,若该语句在异常语句之后,finally 会执行;
    3. 程序所在的线程死亡;
    4. 关闭了 CPU。

注意:当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:

public static int f(int value) {
    try {
        return value * value;
        } finally {
            if (value == 2) {
            return 0;
            }
    	}
}
// 如果调用 f(2),输出的结果为 0。

5 Java 的反射

5.1 反射机制介绍

在运行期间,通过 Java 的反射机制,对于任何一个类,都能够知道这个类的属性和方法;对于任何一个对象,都能够调用这个对象的属性和方法。Java 的反射机制可以让用户动态地获取类的信息以及动态调用对象的功能

反射的原理就是利用 Java 虚拟机通过类的字节码文件反向获取该类或者对象中的属性和方法。

5.2 反射机制优缺点

  • 优点: 动态加载类,提高代码灵活度;
  • 缺点:因为反射相当于一系列解释操作,因此性能比直接的 java 代码要慢;使用反射会模糊程序内部逻辑。

5.3 反射机制的应用场景

反射是框架设计的灵魂。

比如在 MyBatis 中,当使用 标签逐⼀定义列名和对象属性名之间的映射关系后,MyBatis 就可以通过反射来创建对象,同时利用反射给对象的属性逐⼀赋值并返回,那些找不到映射关系的属性,是⽆法完成赋值的;

又比如在 Spring 的 IoC,就是通过解析 XML 文件,获取到 id 属性和 class 属性里面的内容,然后利用反射原理获取到配置里面类的实例对象,并存入到 Spring 的 bean 容器中。

1 反射的应用场景

  • Spring 的 IoC 和 AOP 的实现都和反射有关;
  • 代码的逆向编译;

2 反射的实现方式

Class 类是基于反射机制的,Class 由 final 修饰,一个 Class 类的实例表示这个类的类型信息。

  • 通过Class c1 = 类名.class
  • 通过Class c2 = 对象名.getClass()
  • 通过Class c3 = Class.forName(类的全名的字符串)

 

6 Java 序列化

6.1 序列化的含义、意义和使用场景

  • 序列化:将对象写入到 IO 流中;
  • 反序列化:从 IO 流中恢复对象;
  • 意义:序列化机制允许将实现序列化的 Java 对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
  • 使用场景:所有在网络上传输的、所有保存到磁盘中的 Java 对象必须是可序列化的,且其传入的参数和返回的对象也必须是可序列化的。通常建议:程序创建的每个 JavaBean 类都实现 Serializeable 接口。

6.2 序列化实现的方法

  1. 实现 Serializable 接口:Serializable 接口是一个标记接口,不用实现任何方法,一旦实现了此接口,该类的对象就是可序列化的。
  2. 实现 Externalizable 接口:通过实现 Externalizable 接口,必须实现 writeExternal、readExternal 方法。

6.3 总结

  1. 所有在网络上传输的、所有保存到磁盘中的 Java 对象都需要实现序列化接口;
  2. 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、静态变量、transient 实例变量都不会被序列化;
  3. 如果想让某个变量不被序列化,使用 transient 修饰(transient 只能修饰变量,不能修饰类和方法);
  4. 序列化对象的引用类型的实例变量,也必须是可序列化的,否则,会报错。
  5. 反序列化时必须有序列化对象的 class 文件。
  6. 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
  7. 单例类序列化,需要重写 readResolve() 方法;否则会破坏单例原则。
  8. 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
  9. 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。

参考文档:传送门

1 如果想阻止一个对象序列化,可以采用什么方法

不让这个对象对应的类实现 Serializable 或者 Externalizable 接口即可,transient 关键字只能修饰变量,而不能修饰方法和类。

 

7 相似内容的区别

7.1 String、StringBuilder 和 StringBuffer

StringStringBufferStringBuilder
底层存储形式底层使用 final 修饰的字符数组来保存字符串,所以 String 对象是不可变的底层使用普通字符数组来保存字符串,所以 StringBuffer 对象是可变的底层使用普通字符数组来保存字符串,所以 StringBuilder 对象是可变的
修改对象方面每次对该对象进行改变时,都会生成一个新的 String 对象每次对该对象进行改变时,只是对该对象本身进行操作,而不是生成一个新的对象每次对该对象进行改变时,只是对该对象本身进行操作,而不是生成一个新的对象
线程安全方面String 对象是不可改变的,所以可以理解为常量,是线程安全的StringBuffer 对方法加了同步锁或对调用的方法加了同步锁,所以是线程安全的StringBuilder 并没有对方法加了同步锁或对调用的方法加了同步锁,所以是线程不安全的
适用范围操作少量的数据多线程下操作大量数据单线程操作大量数据

综合来说:

String 对象的底层是用 final 修饰的字符数组来保存字符串的,所以 String 对象是不可变的,每次对该对象进行改变时,实际上都是创建了一个新的对象。

StringBuilder 和 StringBuffer 底层使用普通的字符数组来保存的,所以每次对对象进行改变时,仅仅是对对象本身进行改变,而不是创建一个新的对象。

String 和 StringBuffer 是线程安全的,StringBuilder 是线程不安全的。

1 什么是线程安全

线程安全指的是当一个线程在操作一个方法或者语句时,其它线程不能对其进行操作,只能等到该线程结束后才可以进行访问。

7.2 == 和 equals

  • == :
    • 如果是基本数据类型,则比较两个数据的值是否相同;
    • 如果是引用数据类型,则比较两个数据所保存的内存地址是否相同;
  • equals :
    • 当该类没有重写 equals 方法时,equals 比较该类的两个对象时,等价于 ==;
    • 当该类重写了 equals 方法后,equals 比较该类的两个对象是为了判断这两个对象的内容是否相同;

重写 equals 方法的五大原则:

  1. **自反性原则:**对于任何的非空引用,该引用 equals 自己需要返回 true;
  2. 对称性原则:对于任意的两个引用,x.equals(y) 与 y.equals(x) 的结果必须相同;
  3. 一致性原则:如果 x 和 y 引用的对象没有发生变化,那么反复调用 x.equals(y) 应该返回同样的结果;
  4. 传递性原则:对于任意的引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true;
  5. 非空性原则:对于任意非空引用 x,x.equals(null) 应该返回 false。

示例,String 类中的 equals 方法是被重写过的,而 Object 类中的该方法是没被重写过的:

public class Test {
    public static void main(String[] args) {
        // 如下,字符串 “Sharm” 本身为一个对象,new String 又创建了一个对象,所以下面这条语句共创建了两个对象
        String a = new String("Sharm");
        String b = new String("Sharm");
        String c = "Sharm";
        String d = "Sharm";

        System.out.println(String.valueOf(a.equals(b))); // 因为 a、b 所指向的对象相同,所以为 true
        System.out.println(a == b); // a、b 这两个引用变量的地址不同,所以为 false
        System.out.println(a == c); // 同理,a、c 这两个引用变量的地址不同,所以为 false
        // 对于通过赋值方法建立的对象,Java 虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,
        // 如果有就把它赋给当前引用,如果没有就在常量池中重新建立一个 String 对象,就类似于基本数据类型的比较;
        System.out.println(c == d); // true
        System.out.println(String.valueOf(c.equals(d))); // true
    }
}

7.3 hashCode() 和 equals

1)hashCode() 的介绍

hashCode() 是定义在 Object 类中的一个方法,这意味着 Java 的任何类都包含这个方法。它的作用是获得当前对象的哈希码,即一个 int 型的数据,这个哈希码的作用是确定该对象在哈希表中的索引位置。

2)hashCode() 的作用

利用 HashSet 来说明为何需要 hashCode() 方法,当我们把某个对象加入到 HashSet 中时,HashSet 首先会判断该对象的哈希码是否与HashSet 中已经存在的对象的哈希码相同,如果不存在相同的哈希码,则该对象可以加入到该 HashSet 中;如果存在相同的哈希码,则进一步利用 equals 来判断这两个对象是否确实相同,如果相同,则无法加入,如果不同,则可以加入。这样可以减少 equals 的使用次数,提高执行效率。

  • 如果两个对象相同,那么这两个对象的 hashCode() 肯定相同;反之,则不确定;
  • hashCode() 的默认行为是对堆里的对象产生独特值,如果没有重写 hashCode(),则一个类的两个对象的哈希码无论如何都不会相同;与 equals 一样,hashCode() 在日常使用中也需要重写。

7.4 重载(overload)和重写(override)

  • 重载:重载是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理;
  • 重写:重写是子类对父类方法的重写改造,外部样子不能改变,内部逻辑可以改变;
区别点重载重写
发生范围同一个类子类中
参数列表必须修改一定不能修改
返回类型可修改一定不能修改
异常可修改可以减少或删除,但不能抛出新的或者更广的异常
访问修饰符可修改可以降低限制,但不能做更严格的限制
发生阶段编译区运行区

构造器不能重写,但可以重载。

7.5 接口和抽象类的区别

  1. 抽象类只能单继承,而接口可以多实现;
  2. 抽象类可以有构造方法,而接口中不能有构造方法;
  3. 抽象类中可以有成员变量,而接口中不能有成员变量,只允许有常量(默认是 public static final);
  4. 抽象类提供了通用实现,是对某一类事物的抽象;而接口则是规范并约束了某些行为,是对行为的抽象。

7.6 成员变量和局部变量的区别

  1. 作用域不同:成员变量的作用域在整个类内部甚至是外部都是可见的,而局部变量的作用域仅仅局限在定义它的方法中,不能被其它方法调用;
  2. 初始值不同:Java 会给成员变量赋一个初始值(被 final 修饰的成员变量除外,它必须显式地被赋值),而不会给局部变量赋初始值,必须自己赋值;
  3. 成员变量和局部变量同名时,局部变量的赋值具有更高的优先级,同时也优先取局部变量的值。

7.7 静态方法和实例方法的区别

  1. 在外部调⽤静态⽅法时,可以使⽤"类名.⽅法名"的⽅式,也可以使⽤"对象名.⽅法名"的⽅式,⽽实例⽅法只能通过后⾯这种⽅式进行调用,也就是说,调⽤静态⽅法可以⽆需创建对象。
  2. 静态⽅法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态⽅法),⽽不允许访问实例成员变量和实例⽅法;实例⽅法则⽆此限制。

7.8 Java 中字符常量和字符串常量的区别

  1. 字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符;
  2. 字符常量相当于一个整型值(ASCII 值),可以参加表达式运算,字符串常量代表一个地址值;
  3. 字符常量占两个字节,字符串常量占有若干字节;(Java 中各基本数据类型的占用空间大小并不像其它大多数语言那样随机器硬件架构的变化而变化)

7.9 浅拷贝和深拷贝

基本数据类型:数据直接存储在栈中;

引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。

  • 浅拷贝:对于基础数据类型,直接复制数据值;对于引用数据类型,只是复制了对象的引用地址,新旧引用指向的是一个相同的对象,通过一个引用修改对象的值,另一个引用指向的对象也随着改变。
  • 深拷贝:对于基础数据类型,直接复制数据值;对于引用数据类型,开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。

深拷贝相比于浅拷贝速度较慢并且花销较大。

7.10 基本类型数据和包装类型数据的区别#

  1. 包装类型可以为 null,而基本类型不可以;
  2. 包装类型可用于泛型,而基本类型不可以;
  3. 基本数据类型在虚拟机栈中存储的是具体的数值,而包装类型在虚拟机栈中存储的是堆中对象的引用;

 

8 其它问题

8.1 装箱与拆箱

参考文献:传送门

  • 装箱:将基本数据类型转换成对应的引用数据类型;
  • 拆箱:将引用数据类型转换成对应的基本数据类型。

8.2 什么是 Java 程序的主类

在 Java 应用程序中,主类指的是包含 main() 方法的类,一个程序中可以有很多个类,但只允许有一个主类,主类不一定是 public 类。

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

因为静态方法可以不通过对象进行调用,而非静态成员是属于对象的,因此在静态方法里,不能调用其它非静态成员。

8.4 创建⼀个对象用什么运算符?对象实体与对象引用有何不同?

创建一个对象使用的是 new 这个运算符。对象实体存在在堆内存中,而对象引用存放在栈内存中,一个对象引用只能指向 0 或者 1 个对象实体,而一个对象实体可以有 n 个对象引用指向它。

8.5 对象相等和指向他们的引用相等,这两者有什么不同?

对象相等,比的是堆内存中存放的内容是否相等;

引⽤相等,比较的是虚拟机栈中它们存放的引用地址是否相等。

8.6 构造方法有什么特点

  1. 名字与类名相同;
  2. 没有返回值,但不能用 void 来声明构造方法(public Name(){} );
  3. 创建类的对象时自动执行,无需调用。

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

如果父类没有显示地书写构造方法,子类在进行初始化时,会自动为父类加上一个无参、且没有任何功能的构造方法,这样子类的初始化是可以编译成功的。

但如果父类存在有参的构造方法,却不存在无参的构造方法,且子类在其构造方法中没有通过 super() 来显式地调用父类特定的构造方法,这个时候,子类在进行初始化时,会默认调用父类的没有参数的构造方法,而此时不会为父类自动加上一个无参的构造方法,所以在编译时就会报错,防止出现这种错误的方式就是在定义类时,为其加上⼀个不做事且没有参数的构造方法。

如果子类需要显式地调用构造方法,则 super() 必须在子类的构造方法的第一行;

8.8 ⼀个类的构造方法的作用是什么? 若⼀个类没有声明构造方法,该程序能正确执行吗? 为什么?

一个类的构造方法主要作用是帮助类对象完成初始化工作,当一个类没有声明构造方法时,该程序能正确执行,因为一个类即使没有显式地声明构造方法,也会有默认的不带参数的构造方法。

8.9 什么是方法的返回值,方法的返回值有什么作用

方法的返回值指的是方法体中的代码执行后产生的结果。方法的返回值用来接收该方法的结果,并将它用做其它操作。

8.10 不用第三个变量,交换两个变量的值

int a = 2;
int b = 3;
// 不用担心内存溢出的情况,但是该方法不能用于引用数据类型
a = a + b;
b = a - b;
a = a - b;

8.11 Java 运算符优先级

提醒:源代码就是一份说明文档,源代码的可读性比其运行效率更为重要。

Java 中除了单目运算符( - a)、赋值运算符( a -= 2)、三目运算符(? :)是从右向左运算的,其它运算符都是从左向右运算的。

优先级运算符结合性
1()、[]、{}从左向右
2!、+、-、~、++、–从右向左
3*、/、%从左向右
4+、-从左向右
5<<、>>、>>>从左向右
6<、<=、>、>=、instanceof从左向右
7==、!=从左向右
8&从左向右
9^从左向右
10|从左向右
11&&从左向右
12||从左向右
13?:从右向左
14=、+=、-=、*=、/=、&=、|=、^=、~=、«=、»=、>>>=从右向左

8.12 Java 的数据类型,分别占多大内存

数据类型存储空间默认值
byte8位(1byte,8bit)0
short16位0
int32位0
long64位0
float32位0.0
double64位0.0
char16位‘\u0000’
boolean位数不定(实际上一位就可以实现)flase

8.13 Object 类有哪些方法

介绍几个比较常用的方法:

方法名作用
getClass()返回对象的运行时类型
toString()返回对象的字符串表示形式
hashCode()返回对象的哈希值
equal()判断对象是否相等
wait()、notify()让当前线程等待/唤醒某个线程

8.14 Java 与 Python 的区别

  1. 就面向对象而言,Java 的面向对象特性更加彻底,就我个人使用来看,编写 Python 代码时很大部分用的还是面向过程的思想;
  2. Java 是强类型的语言,需要事先声明类型才能使用,而 Python 是弱类型的语言;
  3. Python 常采用缩进来表明每一个方法或者模块,而 Java 则是采用小括号、大括号等符号来表明;
  4. 虽然 Python 代码易读,但是其运行效率不如 Java,对用户友好就是对计算机不友好。

8.15 如何不利用第三个变量交换两个变量的值

使用算术运算的方式:

// 两头是 a,最上面是 +
a = a + b;
b = a - b;
a = a - b;

使用位运算的方式:

// 两头是 a
a = a ^ b;
b = a ^ b;
a = a ^ b;

带要注意,这种变换只针对于基本数据类型,对于引用数据类型就算了。

8.16 Java 创建对象的方式

创建对象方式是否调用构造器示例代码
new 关键字创建根据参数调用不同的构造方法
通过反射来创建 Class.newInstance对应类必须包含无参构造方法Person person = Person.class.newInstance();
通过 Clone 来创建不会调用任何构造方法,对应类必须实现Cloneable接口并重写 Object 的 clone() 方法
通过序列化、反序列化的方式来创建不会调用任何的构造方法,需要实现Serializable接口先进行对象序列化,再反序列化为新对象
// 通过克隆来创建
public class Person implements Cloneable {
	...
	// 访问权限修改为 public,并且返回值写为 Person
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
    ...
}
public class Main {
    public static void main(String[] args) throws Exception {
        Person person = new Person("fsx", 18);
        Person clone = person.clone();
    }

}

参考文献:传送门

8.17 Java 初始化时的执行顺序

  1. 类内容(静态变量 => 静态初始化块) => 实例内容(变量 => 初始化块 => 构造器)
  2. 父类的(静态变量 => 静态初始化块)=> 子类的(静态变量 => 静态初始化块)=> 父类的(变量 => 初始化块 => 构造器)=> 子类的(变量 => 初始化块 => 构造器)

初始化块可以对在它之后定义的变量赋值,但不能访问(如打印)。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值