【面试宝典】:检验是否为合格的初中级程序员的面试知识点,你都知道了吗?查漏补缺(下)

简介: String 类中使用 final 关键字字符数组保存字符串

5. 常用类


5.1 String、StringBuffer、StringBuilder


5.1.1 String、StringBuffer、StringBuilder 的区别?


可变性:


String 类中使用 final 关键字字符数组保存字符串,代码:


private final char value[],
复制代码


所以string对象是不可变的。


StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[] value ,但是没有用 final 关键字修饰,代码:


char[]value
复制代码


这两种对象都是可变的。


线程安全性:


  • String中的对象是不可变的,也就可以理解为常量,线程安全。
  • StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
  • StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。


AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。


性能:每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。


StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。


5.1.2对于三者使用的总结


  • 操作少量的数据用 = String
  • 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder,甚至有时,我们为了避免每个线程重复创建 StringBuilder 对象,会通过 ThreadLocal + StringBuilder 的方式,进行对 StringBuilder 的重用
  • 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer


5.1.3 String s = new String("xyz") 会创建几个对象?


首先,在 String 池内找,找到 "xyz" 字符串,不创建 "xyz" 对应的 String 对象,否则创建一个对象。 然后,遇到 new 关键字,在内存上创建 String 对象,并将其返回给 s ,又一个对象。 所以,总共是 1 个或者 2 个对象


5.1.4  StringTokenizer 是什么?


StringTokenizer ,是一个用来分割字符串的工具类。


示例代码如下:


StringTokenizer st = new StringTokenizer(”Hello World”);
while (st.hasMoreTokens()) {
    System.out.println(st.nextToken());
}
复制代码


输出如下:


Hello World


5.2 什么是自动拆装箱?


自动装箱和拆箱,就是基本类型和引用类型之间的转换。


5.2.1 什么要转换?


如果你在 Java5 下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合( Collection )中放入原始类型值,因为集合只接收对象。


通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用 Integer、Double、Boolean 等这些类,我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。


为了让代码简练,Java5 引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。 但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的 Bug 。


5.3 int 和 Integer 有什么区别?


  • int 是基本数据类型。
  • Integer 是其包装类,注意是一个类。


需要注意下 Integer 的缓存策略


5.2.3 理解Java Integer 的缓存策略


6. 关键字


6.1 final、finally、finalize


6.1.1 final


final ,是修饰符关键字。


  • Class 类:如果一个类被声明为 final ,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract 的,又被声明为 final 的。
  • 变量或方法声明为 final ,可以保证它们在使用中不被改变。被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为 final 的方法也同样只能使用,不能重写。


另外,在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的private 方法都隐式地指定为 final 。


6.1.2 finally


在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。


在以下 4 种特殊情况下,finally块不会被执行:


  • 在 finally 语句块中发生了异常。
  • 在前面的代码中用了 System.exit() 退出程序。
  • 程序所在的线程死亡。
  • 关闭 CPU 。


6.1.3 finalize


finalize ,是方法名。


Java 允许使用 #finalize() 方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。

它是在 Object 类中定义的,因此所有的类都继承了它。 子类覆盖 finalize() 方法,以整理系统资源或者执行其他清理工作。


finalize() 方法,是在垃圾收集器删除对象之前对这个对象调用的。 一般情况下,我们在业务中不会自己实现这个方法,更多是在一些框架中使用。


6.1.4 String 类能被继承吗,为什么?


不能,因为 String 是 final 修饰。


6.2  static


6.2.1 static特点


  • static是一个修饰符,用于修饰成员。(成员变量,成员函数)static修饰的成员变量 称之为静态变量或类变量。
  • static修饰的成员被所有的对象共享。
  • static优先于对象存在,因为static的成员随着类的加载就已经存在。
  • static修饰的成员多了一种调用方式,可以直接被类名所调用,(类名.静态成员)。
  • static修饰的数据是共享数据,对象中的存储的是特有的数据


6.2.2 是否可以在 static方法中访问非 static 变量?


static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。


由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。


如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。


6.2.3 成员变量和静态变量的区别:


  • 生命周期的不同:


  • 成员变量随着对象的创建而存在随着对象的回收而释放。
  • 静态变量随着类的加载而存在随着类的消失而消失。


  • 调用方式不同:


  • 成员变量只能被对象调用。
  • 静态变量可以被对象调用,也可以用类名调用。(推荐用类名调用)


  • 别名不同:


  • 成员变量也称为实例变量。
  • 静态变量称为类变量。


  • 数据存储位置不同:


  • 成员变量数据存储在堆内存的对象中,所以也叫对象的特有数据。
  • 静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。


6.2.4 static 关键字修饰的加载顺序


-->父类静态变量

----->父类静态代码块

--------->子类静态变量

------------->子类静态代码块

------------------>父类普通变量

----------------------->父类普通代码块

----------------------------->父类构造函数

------------------------------------>子类普通变量

------------------------------------------>子类普通代码块

------------------------------------------------->子类构造函数


6.3 transient 关键字


transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程,


transient 只能修饰变量,不能修饰类和方法。


6.3.1  Java 序列化中,如果有些字段不想进行序列化怎么办?


对于不想进行序列化的变量,使用 transient 关键字修饰,


  • 当对象被序列化时,阻止实例中那些用此关键字修饰的的变量序列化。
  • 当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。


6.4 volatile关键词


volatile 关键字用在多线程同步中,可保证读取的可见性,JVM只是保证从主内存加载到线程工作内存的值是最新的读取值,而非 cache 中


6.4.1  volatile关键字是否能保证线程安全?


不能 , 多个线程对 volatile 的写操作,无法保证线程安全。例如假如线程 1,线程 2 在进行 read,load 操作中,发现主内存中 count 的值都是 5,那么都会加载这个最新的值,在线程 1 堆 count 进行修改之后,会 write 到主内存中,主内存中的 count 变量就会变为 6;线程 2 由于已经进行 read,load 操作,在进行运算之后,也会更新主内存 count 的变量值为 6;导致两个线程及时用 volatile 关键字修改之后,还是会存在并发的情况


7. Java IO


7.1 Java IO 相关的类


Java IO 相关的类,在 java.io 包下,具体操作分成面向字节(Byte)和面向字符(Character)两种方式。如下图所示:


21.png


7.2 什么是 Java 序列化?


序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。


可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。 序列化是为了解决在对对象流进行读写操作时所引发的问题。 反序列化的过程,则是和序列化相反的过程。


我们不能将序列化局限在 Java 对象转换成二进制数组,比如,将一个 Java 对象转换成 JSON 字符串等,这也可以理解为是序列化。


7.2.1如何实现 Java 序列化?


将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的。


  • 序列化


  • 首先使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象
  • 接着,使用 ObjectOutputStream 对象的 #writeObject(Object obj) 方法,就可以将参数为 obj 的对象写出(即保存其状态)。


  • 反序列化


  • 要恢复的话则用输入流。


7.3 如何实现对象克隆(浅克隆和深克隆)?


  • 实现 Cloneable 接口,并重写 Object 类中的 #clone() 方法。可以实现浅克隆,也可以实现深克隆。
  • 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆。可以实现真正的深克隆。


实际场景下,我们使用的克隆比较少,更多是对象之间的属性克隆。例如说,将 DO 的属性复制到 DTO 中,又或者将 DTO 的属性复制到 VO 中。此时,我们一般使用 BeanUtils 工具类。


8.异常


8.1 异常机制的概述


异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。


程序错误分为三种:


  • 编译错误:因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
  • 运行时错误:因为程序在执行时,运行环境发现了不能执行的操作。
  • 逻辑错误:因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。


8.2  Throwable


Throwable 类图


22.png


Throwable有两个重要的子类 :


  • Exception(异常)
  • Error(错误)


二者都是 Java 异常处理的重要子类,各自都包含大量子类


8.2.1 Exception(异常)和 Error(错误)


  • Error(错误),表示系统级的错误和程序不必处理的异常,是 Java 运行环境中的内部错误或者硬件问题。


  • 例如:内存资源不足等。
  • 对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由 Java 虚拟机抛出的


  • Exception(异常),表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。Exception 又分为运行时异常,受检查异常。


  • RuntimeException(运行时异常),表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止逻辑,因此,编译器不检查这些异常。
  • CheckedException(受检查异常),是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理),所以称之为受检查异常


8.3 error 和 exception 有什么区别?


  • Error:Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关 。比如:


  • OutOfMemoryError
  • NoClassDefFoundError
  • LinkageError


  • Exception : 在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义 异常,比如:


  • ArrayIndexOutOfBoundsException(数组下标越界)
  • NullPointerException(空指针异常)
  • ArithmeticException(算术异常)
  • MissingResourceException(丢失资源)
  • ClassNotFoundException(找不到类)
  • BufferOverflowException
  • ClassCastException


8.4 CheckedException 和 RuntimeException 有什么区别?


  • RuntimeException运行异常:表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。
  • CheckedException受检异常:跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常


Effective Java中对异常的使用给出了以下指导原则 :


不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常) 对可以恢复的情况使用受检异常,对编程错误使用运行时异常 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生) 优先使用标准的异常 每个方法抛出的异常都要有文档 保持异常的原子性 不要在catch中忽略掉捕获到的异常


8.5 Throwable 类常用方法?


  • getMessage() 方法:返回异常发生时的详细信息。
  • getCause() 方法:获得导致当前 Throwable 异常的 Throwable 异常。
  • getStackTrace() 方法:获得 Throwable 对象封装的异常信息。
  • printStackTrace() 方法:在控制台上打印。


8.6 throw 与 throws 的区别 ?


  • throw ,用于在程序中显式地抛出一个异常。
  • throws ,用于指出在该方法中没有处理的异常。每个方法必须显式指明哪些异常没有处理,以便该方法的调用者可以预防可能发生的异常。最后,多个异常用逗号分隔。


8.7 异常处理中 finally 语句块的重要性?


不管程序是否发生了异常, finally 语句块都会被执行,甚至当没有catch 声明但抛出了一个异常时, finally 语句块也会被执行。


finally 语句块通常用于释放资源, 如 I/O 缓冲区, 数据库连接等等。


8.8 UnsupportedOperationException 是什么?


UnsupportedOperationException ,是用于表明操作不支持的异常。


在 JDK 类中已被大量运用,在集合框架java.util.Collections.UnmodifiableCollection 将会在所有 add 和 remove 操作中抛出这个异常。


9.反射


9.1 反射简介


当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。我们认为 Java 并不是动态语言,但是它却又一个非常突出的动态相关的机制


9.2 反射的用途及实现?


Java 反射机制主要提供了以下功能:


  • 在运行时构造一个类的对象。
  • 判断一个类所具有的成员变量和方法。
  • 调用一个对象的方法。
  • 生成动态代理。


反射的主要用途, 开发各种通用框架


  • Spring 框架的 IoC 基于反射创建对象和设置依赖属性。
  • Spring MVC 的请求调用对应方法,也是通过反射。
  • JDBC 的 Class#forName(String className) 方法,也是使用反射。


9.3 反射中,Class.forName 和 ClassLoader 区别?


  • Class#forName(...) 方法,除了将类的 .class 文件加载到JVM 中之外,还会对类进行解释,执行类中的 static 块。
  • ClassLoader 只干一件事情,就是将 .class 文件加载到 JVM 中,不会执行 static 中的内容,只有在 newInstance 才会去执行 static 块。
  • Class#forName(name, initialize, loader) 方法,带参函数也可控制是否加载 static 块,并且只有调用了newInstance 方法采用调用构造函数,创建类的对象。


9.4 什么时候用断言(assert)?


断言,在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。


一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。 断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为 false ,那么系统会报告一个AssertionError 错误。断言的使用如下面的代码所示:


assert(a > 0); // throws an AssertionError if a <= 0
复制代码


断言可以有两种形式:


assert Expression1; 。 assert Expression1 : Expression2;

Expression1 应该总是产生一个布尔值。 Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。


要在运行时启用断言,可以在启动 JVM 时使用 -enableassertions 或者 -ea 标记。要在运行时选择禁用断言,可以在启动 JVM 时使用 -da 或者 -disableassertions 标记。要在系统类中启用或禁用断言,可使用 -esa 或 -dsa 标记。还可以在包的基础上启用或者禁用断言。


各位看官还可以吗?喜欢的话,动动手指点个💗,点个关注呗!!谢谢支持!

目录
相关文章
|
15天前
|
SQL 分布式计算 监控
Sqoop数据迁移工具使用与优化技巧:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入解析Sqoop的使用、优化及面试策略。内容涵盖Sqoop基础,包括安装配置、命令行操作、与Hadoop生态集成和连接器配置。讨论数据迁移优化技巧,如数据切分、压缩编码、转换过滤及性能监控。此外,还涉及面试中对Sqoop与其他ETL工具的对比、实际项目挑战及未来发展趋势的讨论。通过代码示例展示了从MySQL到HDFS的数据迁移。本文旨在帮助读者在面试中展现Sqoop技术实力。
27 2
|
15天前
|
监控 负载均衡 Cloud Native
ZooKeeper分布式协调服务详解:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析ZooKeeper分布式协调服务原理,涵盖核心概念如Server、Client、ZNode、ACL、Watcher,以及ZAB协议在一致性、会话管理、Leader选举中的作用。讨论ZooKeeper数据模型、操作、会话管理、集群部署与管理、性能调优和监控。同时,文章探讨了ZooKeeper在分布式锁、队列、服务注册与发现等场景的应用,并在面试方面分析了与其它服务的区别、实战挑战及解决方案。附带Java客户端实现分布式锁的代码示例,助力提升面试表现。
30 2
|
15天前
|
数据采集 消息中间件 监控
Flume数据采集系统设计与配置实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入探讨Apache Flume的数据采集系统设计,涵盖Flume Agent、Source、Channel、Sink的核心概念及其配置实战。通过实例展示了文件日志收集、网络数据接收、命令行实时数据捕获等场景。此外,还讨论了Flume与同类工具的对比、实际项目挑战及解决方案,以及未来发展趋势。提供配置示例帮助理解Flume在数据集成、日志收集中的应用,为面试准备提供扎实的理论与实践支持。
25 1
|
3月前
|
存储 算法 程序员
【Leetcode 程序员面试金典 01.01】判定字符是否唯一 —— 位运算|哈希表
可以使用哈希表或位运算来解决此问题:由题可知s[i]仅包含小写字母,int[26]即能表示字符的出现次数;
|
3月前
|
算法 程序员 索引
【Leetcode 程序员面试金典 02.08】 —— 环路检测 |双指针
我们可以使用双指针解决本题,由数学推导可知:a 的距离为(环长度的倍数 - b),即 tmp 指针从头节点走到环开头节点等于 slow 指针走到环开头节点的距离
|
3月前
|
Java 程序员
【Leetcode 程序员面试金典 05.01】插入 —— 位运算
位运算问题,只需要把 N 的 i 到 j 位都置 0 后再和 M 左移 i 位的结果进行按位或即可
|
1月前
|
消息中间件 NoSQL 网络协议
Java面试知识点复习​_kaic
Java面试知识点复习​_kaic
|
7天前
|
数据可视化 Python
Python模型评估与选择:面试必备知识点
【4月更文挑战第17天】本文深入探讨了Python模型评估与选择在面试中的关键点,包括性能度量、过拟合与欠拟合识别、模型比较与选择、模型融合和偏差-方差权衡。强调了避免混淆评估指标、忽视模型验证和盲目追求高复杂度模型的常见错误,并提供相关代码示例,如交叉验证、网格搜索和超参数调优。通过理解这些概念和技巧,可在面试中展示出色的数据科学能力。
31 12
|
15天前
|
机器学习/深度学习 分布式计算 BI
Flink实时流处理框架原理与应用:面试经验与必备知识点解析
【4月更文挑战第9天】本文详尽探讨了Flink实时流处理框架的原理,包括运行时架构、数据流模型、状态管理和容错机制、资源调度与优化以及与外部系统的集成。此外,还介绍了Flink在实时数据管道、分析、数仓与BI、机器学习等领域的应用实践。同时,文章提供了面试经验与常见问题解析,如Flink与其他系统的对比、实际项目挑战及解决方案,并展望了Flink的未来发展趋势。附带Java DataStream API代码样例,为学习和面试准备提供了实用素材。
37 0
|
15天前
|
分布式计算 资源调度 监控
Hadoop生态系统深度剖析:面试经验与必备知识点解析
本文深入探讨了Hadoop生态系统的面试重点,涵盖Hadoop架构、HDFS、YARN和MapReduce。了解Hadoop的主从架构、HDFS的读写流程及高级特性,YARN的资源管理与调度,以及MapReduce编程模型。通过代码示例,如HDFS文件操作和WordCount程序,帮助读者巩固理解。此外,文章强调在面试中应结合个人经验、行业动态和技术进展展示技术实力。