牛客疑难题(6)

一、static可变 final才不可变

|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class Test1 { ``static int cnt = ``6``; ``static { ``cnt += ``9``; ``} ``public static void main(String[] args) { ``System.out.println(``"cnt =" + cnt); ``} ``static { ``cnt /= ``3``; ``} } |

A cnt=5

B cnt=2

C cnt=3

D cnt=6

正确答案:A

你的答案:D

官方解析:

这道题目考察了Java静态代码块的执行顺序以及静态变量的初始化过程。让我们逐步分析代码的执行流程:

  1. 首先,静态变量cnt被初始化为6

  2. 然后按照代码中静态代码块的顺序执行:

  • 第一个静态代码块: cnt += 9 执行后,cnt变为15

  • 第二个静态代码块: cnt /= 3 执行后,cnt变为5

  1. 最后执行main方法,打印cnt的值,此时cnt=5

所以最终输出结果为"cnt=5",A选项是正确答案。

分析其他选项:

B错误:输出2是不可能的,因为按照运算过程15/3=5

C错误:输出3是不可能的,没有任何运算步骤会得到3

D错误:输出6是初始值,但经过静态代码块的运算后已经被改变

需要注意的关键点:

  1. 静态代码块在类加载时执行,优先于main方法

  2. 静态代码块按照在类中出现的顺序依次执行

  3. 静态变量和静态代码块都在类加载时初始化

  4. 这些初始化操作只会执行一次

知识点:Java、JavaSE

二、集合

LinkedList是基于双向链表实现的,在中间插入或删除元素只需要改变相邻节点的引用,操作开销是固定的。而ArrayList基于数组实现,在中间插入或删除元素时,需要将插入位置后的所有元素整体向后或向前移动,操作开销与列表长度相关。

三、线程

Daemon线程(守护线程)是一种特殊的线程,其作用是为其他线程提供服务。C选项正确地描述了daemon线程的特性:当JVM中只剩下daemon线程时,JVM就会退出。这意味着当所有非守护线程都结束时,即使守护线程还在运行,JVM也会结束所有守护线程并关闭。

**Java运行时内存的结构是Java虚拟机规范中的重要内容。B选项方法区和D选项Java堆确实属于线程共享的内存区域。
具体分析:

  1. 方法区(B):用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。它是各个线程共享的内存区域。
  2. Java堆(D):是虚拟机管理的最大的一块内存区域,几乎所有的对象实例和数组都在堆上分配。Java堆是垃圾收集器管理的主要区域,也是线程共享的。**

线程共享的内存区域包括方法区和Java堆,而程序计数器和虚拟机栈则是线程私有的。这种内存结构的设计有助于保证多线程运行时的数据安全和隔离。

四、疑难:

短路:

String s=null;

下面哪个代码片段可能会抛出NullPointerException?

A

if((s!=null)&(s.length()>0))

B

if((s!=null)&&(s.length()>0))

C

if((s==null)|(s.length()==0))

D

if((s==null)||(s.length()==0))

正确答案:AC

你的答案:AB

官方解析:

这道题目考察了Java中逻辑运算符的短路特性以及空指针异常(NullPointerException)的触发条件。

A选项 if((s!=null)&(s.length()>0)) 会抛出NullPointerException:

因为使用单个&是非短路与运算符,即使第一个条件s!=null为false,也会继续执行第二个条件s.length()>0,而此时s为null,所以调用length()方法会抛出空指针异常。

B选项 if((s!=null)&&(s.length()>0)) 不会抛出异常:

因为&&是短路与运算符,当第一个条件s!=null为false时,就不会执行第二个条件,所以不会触发空指针异常。

C选项 if((s==null)|(s.length()==0)) 会抛出NullPointerException:

因为使用单个|是非短路或运算符,即使第一个条件s==null为true,也会执行第二个条件s.length()==0,此时s为null,调用length()会抛出异常。

D选项 if((s==null)||(s.length()==0)) 不会抛出异常:

因为||是短路或运算符,当第一个条件s==null为true时,就不会执行第二个条件,所以不会触发空指针异常。

核心在于理解短路运算符(&&,||)和非短路运算符(&,|)的区别:

  • 短路运算符:根据第一个条件的结果决定是否继续执行第二个条件

  • 非短路运算符:无论第一个条件结果如何,都会执行第二个条件

因此AC选项使用非短路运算符会导致空指针异常,而BD选项使用短路运算符则可以避免异常。

知识点:Java

五、接口:

Java接口的修饰符只能是public 和 abstract

六、IO:

OutputStream类中确实包含flush()方法。当我们使用带缓冲的输出流时,写入的数据通常会先保存在缓冲区中,调用flush()可以立即将缓冲区的数据写入目标设备。

七、List赋值和volatile

volatile 易变的:

volatile变量在每次被线程访问时,都强制从主内存中重新读取最新的值,而不是使用线程工作内存中的值。这确保了多线程环境下的数据可见性。

**volatile的内存语义确实遵循happens-before原则。具体体现在:

  1. 对volatile变量的写操作happens-before后续对该变量的读操作
  2. volatile变量的写操作会强制将工作内存中的数据刷新到主内存**

volatile关键字可以禁止指令重排序。编译器和处理器在不改变程序执行结果的前提下可能会对指令进行重排序,但volatile关键字会在必要时插入内存屏障,保证执行顺序。

一个标识符内不能标识字符

八、类型转换等

off-heap内存主要有以下特点:

  1. 它位于JVM堆内存之外

  2. 由JVM进程管理和申请

  3. 不受JVM垃圾回收器控制

  4. 需要手动管理内存的分配和释放

  5. 常用于存储大对象,以避免GC压力