一、Java基础面试题整理

1 、说说跨平台性
        我们希望编写好的代码和程序,最好可以在任意平台和环境下运行,而不需要根据不同的平台,编写不同的代码。
        比如,我编写的一个博客管理系统,我希望它可以在windows 中运行,也可以在 Linux 环境下运行,也可以在 MacOS 环境下运行。
        这就是跨平台特性,节省开发和运维成本。
2 Java 是如何实现跨平台性的?
        Java实现跨平台性的关键在于 JVM 虚拟机, Java 语言编写的程序会被编译成与平台无关的字节码文件,这些字节码文件可以在任何装有 Java
        拟机的系统上运行,因为Java 字节码不针对特定的操作系统或硬件,而是设计成一种中间代码,可以在不同平台上被解释执行。
        JVM是 Java 跨平台的核心组件。它作为一个运行时环境,负责加载字节码并将其解释或编译为特定平台的机器代码。每个操作系统都有其专用的JVM实现,例如 Windows Linux macOS 等。
        Java源码只需编译一次,将 java 文件编译成 class 文件,就可以通过安装在 Windows Linux 中的 JVM 中运行。
3 JDK JRE 有什么区别?
        JDK是 Java 开发工具包,它包含了 JRE 和开发工具(如 javac 编译器和 java 程序运行工具等),主要用于 Java 程序的开发。而 JRE Java 运行环
境,它只包含了运行 Java 程序所必须的环境,主要用于 Java 程序的运行。
        JDK面向的是 Java 开发者,提供了 Java 开发工具,如编译工具( javac )和打包工具( jar )等,方便开发者进行 Java 程序的开发和调试。而 JRE
面向的是 Java 程序的使用者,只要安装了 JRE ,就可以在对应的操作系统上运行 Java 程序。
        JDK由 JRE Java 开发工具组成,包含了 Java 语言所必须的类库和 Java 虚拟机( JVM ),以及编译器和调试器等开发工具。而 JRE 只包含 Java
拟机( JVM )和 Java 语言所必须的类库,这些类库对于开发来说是足够的,但对于开发过程中的编译和调试并不足够。
4 、为何要配置 Java 环境变量?
        path环境变量的作用就是告诉系统,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序
外,还应到哪些目录下去寻找。而我们通常情况下配置的 path 变量,就是这个目录的完整路径。
简而言之,就是运行某个命令需要某些参数时,如果当前目录下找不到,则自动去环境变量中寻找。
5 Java 都有哪些特性?
这是一道没有标准回答的面试题,按照下面的内容陈列即可,回答是切记刻板背诵。
1 )面向对象
Java 是一个面向对象的语言,万物皆对象。
面向对象是一种程序设计规范,其基本思想是使用对象、类、继承、封装、多态等基本概念来进行程序设计。从现实世界中客观存在的事物
(即对象)出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。
程序就是由无数个对象 + 对象的行为组装而成,当程序请求某个对象时,实际上就是通过一个请求,将 你自己的对象 传递给程序,程序经过
一系列匪夷所思的操作,再将一个新的对象返给你。
从底层角度而言,每一个对象都是由其它对象组成的,只是它们的维度、粒度不同而已。
每一个对象都有不同的属性。
比如芯片 + 电路板组成了内存,内存 + 硬盘 +cpu+ 机箱组成了电脑,芯片、电路板、内存、硬盘、 CPU 、机箱、电脑,都是一个对象,只不过
粒度不同罢了。
对象具有属性、方法,在内存中的地址都是唯一的。
2 )分布式
分布式系统对于用户而言,他们面对的就是一个服务器,提供用户需要的服务而已,而实际上这些服务是通过背后的众多服务器组成的一个
分布式系统,因此分布式系统看起来像是一个超级计算机一样。
3 )健壮性
为了获得可靠性, Java 在一些关键领域进行了限制,从而迫使程序员在程序开发中及早地发现错误。
因为 Java 是强类型化的语言,它在编译时检查代码。当然不管怎样,在运行时也检查代码。许多难以跟踪的 bug ,在运行时通 常难以再现,
这种情况在 Java 中几乎不可能产生。因为使编写好的程序在不同的运行条件下,以可预见的方式运行是 Java 的关键特性之一。
在传统的编程环境中,内存管理是一件困难、乏味的工作。例如,在 C/C++ 中,程序员必须手动分配和释放所有动态内存。 Java 通过为您管
理内存的分配和释放,可以从根本上消除这些问题 ( 事实上,释放内存完全是自动的,因为 Java 为不再使用的对象提供了垃圾回收功能 )
4 )安全性
Java 取消了强大但又危险的指针,而代之以引用。由于指针可进行移动运算,指针可随便指向一个内存区域,而不管这个区域是否可用,这
样做是危险的,因为原来这个内存地址可能存储着重要数据或者是其他程序运行所占用的,并且使用指针也容易数组越界。
垃圾回收机制:不需要程序员直接控制内存回收,由垃圾回收器在后台自动回收不再使用的内存。避免程序忘记及时回收,导致内存泄露。
避免程序错误回收程序核心类库的内存,导致系统崩溃。
异常处理机制: Java 异常机制主要依赖于 try catch finally throw throws 五个关键字。
强制类型转换:只有在满足强制转换规则的情况下才能强转成功。
Java 在字节码的传输过程中使用了公开密钥加密机制 (PKC) 在运行环境提供了四级安全性保障机制:字节码校验器 - 类装载器 - 运行时内存布局 - 文件访问限制。
5 )体系结构中立
编译器生成一个体系结构中立的目标文件格式,这是一种编译过的代码,只要有 Java 运行时系统,就可以在许多处理器上运行。 Java 编译器
通过生成与特定的计算机体系结构无关的字节码指令来实现这一特性。精心设计的字节码不仅可以很容易地在任何机器上解释执行,而且还
可以迅速地翻译成本地机器的代码。
字节码实现了结构中立,与计算机结构无关。
6 )可移植性
Java 语言之中最大的特点在于其可移植性的支持,所谓的可移植性指的是同一个程序可以在不同的操作系统之间任意的进行部署,这样就减
少了开发的难度,在 Java 里面如果要想实现可移植性的控制,那么主要依靠的是 JVM Java 虚拟机)。 Java 虚拟机是一个由软件和硬件模拟
出来的计算机,所有的程序只要有 Java 虚拟机的支持,那么就可以实现程序的执行,并且不同的操作系统上会有不同版本的 JVM 存在,这样
就可以实现移植性。
7 )解释性
有人说 Java 是编译型的。因为所有的 Java 代码都是要编译的, .java 不经过编译就无法执行。 也有人说 Java 是解释型的。因为 java 代码编译后
不能直接运行,它是解释运行在 JVM 上的,所以它是解释型的。
8 )高性能
即时编译器可以监控经常执行哪些代码并优化这些代码以提高速度。更为复杂的优化是消除函数调用(即内联)。即时编译器知道哪些类已
经被加载。基于当前加载的类集,如果特定的函数不会被覆盖,就可以使用内联。必要时,还可以撤销优化。
9 )多线程
指的是这个程序(一个进程)运行时产生了不止一个线程。
10 )动态性
Java 本质为静态语言,而不是动态语言。动态语言显著的特点是在程序运行时,可以改变程序结构或变量类型,典型的动态语言有 Python
ruby javascript 等。 Java 不是动态语言,但 Java 具有一定的动态性,表现在以下几个方面:
1. 反射机制;
2. 动态字节码操作;
3. 动态编译;
4. 执行其他脚本代码;
6 == equals 的区别是什么?
1 )类型与定义
== 是一个操作符,用于比较两个变量的值。对于基本数据类型,它比较的是变量存储的值是否相等;对于引用类型,它比较的是两个引用是
否指向内存中的同一地址(即是否是同一个对象的引用)。
equals() Object 类中的一个方法,用于比较两个对象的内容是否相等。默认情况下,它比较的是对象的内存地址(即是否是同一个对
象),但该方法可以被重写以提供自定义的比较逻辑。
2 )运行速度
== :由于只是比较引用或内存地址,所以通常比 equals() 方法更快。
equals() :由于可能需要执行更复杂的比较逻辑(尤其是在被重写的情况下),因此其运行速度可能慢于 ==
3 )可重写性
== :不可重写,其行为是固定的。
equals() :可以被重写以提供自定义的相等性判断逻辑。
下面程序的输出结果是什么?
"==" 运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运
算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型, equals 方法并不会进行类型转换。
public class Main {
public static void main ( String [] args ) {
Integer a = 1 ;
Integer b = 2 ;
Integer c = 3 ;
Integer d = 3 ;
Integer e = 321 ;
Integer f = 321 ;
Long g = 3L ;
Long h = 2L ;
System . out . println ( c == d ); //true
System . out . println ( e == f ); //false
System . out . println ( c == ( a + b )); //true
System . out . println ( c . equals ( a + b )); //true
System . out . println ( g == ( a + b )); //true
System . out . println ( g . equals ( a + b )); //false
System . out . println ( g . equals ( a + h )); //true
}
} 第一个和第二个输出结果没有什么疑问。第三句由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用 intValue 方法),因此它们比
较的是数值是否相等。而对于 c.equals(a+b) 会先触发自动拆箱过程,再触发自动装箱过程,也就是说 a+b ,会先各自调用 intValue 方法,得
到了加法运算后的数值之后,便调用 Integer.valueOf 方法,再进行 equals 比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一
个输出的结果(如果数值是 int 类型的,装箱过程调用的是 Integer.valueOf ;如果是 long 类型的,装箱调用的 Long.valueOf 方法)。
7 Java 中有哪些数学函数?
Java 中提供了一些数学函数,位于 java.lang.Math 类中。这些函数包括以下几种:
基本数学函数: abs max min
指数函数: exp log pow
三角函数: sin cos tan asin acos atan
双曲函数: sinh cosh tanh asinh acosh atanh
随机数函数: random
8 Java 中有哪些位运算符?
1 )与运算符 &
只有两个位都是 1 ,结果才是 1
x 二进制 10000001
y 二进制 10000000
根据与运算符的运算规律,只有两个位都是 1 ,结果才是 1 ,可以知道结果就是 10000000 ,即 128
2 )或运算符 |
两个位只要有一个为 1 ,那么结果就是 1 ,否则就为 0
3 )非运算符 ~
如果位为 0 ,结果是 1 ,如果位为 1 ,结果是 0
4 )异或运算符 ^
两个操作数的位中,相同则结果为 0 ,不同则结果为 1
5 )左移运算符 << 、右移运算符 >>
移位运算符的右操作数要完成模 32 的运算。
1<<35 的值等同于 1<<3 8
6 >>> 运算符会用 0 填充高位,不存在 <<< 运算符。
9 、说说运算符的优先级
这道题属实有点变态,这谁能记得住,运算符优先级只是一种约定,实际使用时应根据具体情况加上括号以明确运算顺序。
Java 中的运算符按照优先级从高到低依次为:
1. () :括号运算符,具有最高优先级;
2. ! ~ ++ -- :逻辑非、位取反、自增、自减运算符;
3. * / % :乘、除、取模运算符,优先级相同,从左向右结合;
4. + - :加、减运算符,优先级相同,从左向右结合;
5. << >> >>> :左移、右移、无符号右移运算符,优先级相同,从左向右结合;
6. < <= > >= :小于、小于等于、大于、大于等于运算符,优先级相同,从左向右结合;
7. == != :等于、不等于运算符,优先级相同,从左向右结合;
8. & :按位与运算符,优先级较低;
9. ^ :按位异或运算符,优先级更低;
package com . nezha . javase ;
public class Test {
public static void main ( String [] args ) {
int x = 129 ;
int y = 128 ;
System . out . println ( "x y 与的结果是: " + ( x & y )); // 128
}
}
package com . nezha . javase ;
public class Test {
public static void main ( String [] args ) {
int x = 129 ;
int y = 128 ;
System . out . println ( "x y 或的结果是: " + ( x | y )); // 129
}
}
public static void main ( String [] args ) {
System . out . println ( 1 << 35 ); //8
System . out . println ( 1 << 3 ); //8
} 10. | :按位或运算符,优先级最低;
11. && :逻辑与运算符,优先级较低;
12. || :逻辑或运算符,优先级更低;
13. ?: :三元条件运算符,优先级最低;
10 final java 中有什么作用?
1 )用来修饰一个引用
如果引用为基本数据类型,则该引用为常量,该值无法修改;
如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
如果引用时类的成员变量,则必须当场赋值,否则编译会报错。
2 )用来修饰一个方法
当使用 final 修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。
3 )用来修饰类
当用 final 修改类时,该类成为最终类,无法被继承。
比如常用的 String 类就是最终类。
11 、使用 final 关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
当使用 final 关键字修饰变量时,这意味着该变量的引用地址被固定,它不能再指向另一个对象或值。然而,这并不意味着该引用所指向的对
象的内容也不能改变。事实上,如果这个变量指向的是一个可变对象(如数组、集合或任何其他用户定义的可变类实例),那么对象的内容
是可以修改的。
12 this super 关键字的作用
1 this 关键字的作用
1. 对象内部指代自身的引用;
2. 解决成员变量和局部变量的同名问题;
3. 可以调用成员变量,不能调用局部变量;
4. 可以调用成员方法。
2 super 关键字的作用
1. 调用父类的成员变量或方法
2. 调用父类的构造函数
13 、在 Java 中,为什么不允许从静态方法中访问非静态变量?
1. 静态变量属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问;
2. 非静态变量属于类的对象,只有在类的对象产生时,才会分配内存,通过类的实例去访问;
3. 静态方法也属于类本身,但是此时没有类的实例,内存中没有非静态变量,所以无法调用。
14 final static 的区别?
当一个变量被声明为 final 时,它的值在初始化后不能被改变。对于引用类型,它指的是引用不可变,即不能再指向其他对象,但对象本身的
状态可能改变。当方法被声明为 final 时,该方法不能被子类重写。当一个类被声明为 final 时,该类不能被继承。
当一个变量或方法被声明为 static 时,它属于类而非类的实例。静态变量在内存中只有一份,无论创建多少个类的实例,所有实例共享同一个
静态变量。静态方法可以直接通过类名调用,无需创建类的实例。静态代码块在类加载时执行,通常用于系统初始化。
final 关键字主要用于声明常量、防止继承和阻止方法重写,而 static 关键字主要用于实现与类相关联的变量和方法,以及控制类的初始化过
程。
15 int 可以强制转换为 byte 吗?
可以进行强制转换,在 Java 中, int 32 位, byte 8 位,如果强制转换, int 类型的高 24 位将会被丢弃。
16 char 型变量中能存储一个中文汉字吗?
Java 中, char 类型占 2 个字节,而且 Java 默认采用 Unicode 编码,一个 Unicode 码占 16 位,所以一个 Unicode 码占两个字节, char
型变量可以存储一个中文汉字。
17 byte 类型 127+1 等于多少
byte 的范围是 -128~127
字节长度为 8 位,最左边的是符号位,而 127 的二进制为 01111111 ,所以执行 +1 操作时, 01111111 变为 10000000
大家知道,计算机中存储负数,存的是补码的兴衰。左边第一位为符号位。
那么负数的补码转换成十进制如下:
一个数如果为正,则它的原码、反码、补码相同;一个正数的补码,将其转化为十进制,可以直接转换。
已知一个负数的补码,将其转换为十进制数,步骤如下:
1. 先对各位取反;
2. 将其转换为十进制数;
3. 加上负号,再减去 1
例如 10000000 ,最高位是 1 ,是负数,①对各位取反得 01111111 ,转换为十进制就是 127 ,加上负号得 -127 ,再减去 1 -128
下面这段代码的输出结果是什么? 也许有些朋友会说都会输出 false ,或者也有朋友会说都会输出 true 。但是事实上输出结果是:
为什么会出现这样的结果?输出结果表明 i1 i2 指向的是同一个对象,而 i3 i4 指向的是不同的对象。此时只需一看源码便知究竟,下面这段
代码是 Integer valueOf 方法的具体实现:
通过 valueOf 方法创建 Integer 对象的时候,如果数值在 [-128,127] 之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创
建一个新的 Integer 对象。
上面的代码中 i1 i2 的数值为 100 ,因此会直接从 cache 中取已经存在的对象,所以 i1 i2 指向的是同一个对象,而 i3 i4 则是分别指向不同的
对象。
其它的引用类型,可以去查看 valueOf 的实现。
18 、为什么数组的起始索引是 0 而不是 1
这个习惯来源于机器语言,那时要计算一个数组元素的地址需要将数组的起始地址加上该元素的索引。将起始索引设为 1 要么浪费数组的第
一个元素的空间,要么会花费额外的时间来将索引减 1
19 、什么是机器语言?
机器语言是一种指令集的体系,是最早出现的计算机语言。 机器语言从属于硬件设备。 不同的计算机设备有不同的机器语言.所以机器语言
是一种面向机器的语言。 计算机指令系统中的指令是由 “0” “1” 两种符号组成的代码,并且能被机器直接理解执行,它们被称为机器指令。
一个计算机的机器指令的集,就构成了该计算机的机器语言,即计算机可以直接接受、理解的语言。
机器语言能利用机器指令精准地描述算法、且编程质量高、所占存储空间小,执行速度快。但是这种程序直观性很差,容易出错,阅读检查
和修改调试非常困难。
20 、什么是汇编语言?
汇编语言是一种低级计算机编程语言,它使用一种非常接近于计算机硬件的指令系统。因此,汇编语言也被认为是一种次级的计算机语言。
汇编语言的特点包括:
1. 汇编语言可以提供对计算机硬件的直接访问,因此它被用于编写操作系统和嵌入式系统等高性能的程序。
2. 汇编语言具有非常高的执行效率,因为它不需要进行高级语言的编译,也不需要进行解释,可以直接在硬件上执行。
3. 汇编语言的代码密度非常高,因为它的指令系统非常紧凑,可以有效地利用内存空间。
4. 汇编语言的执行速度非常快,因为它的指令可以直接被计算机硬件理解并执行。
5. 汇编语言需要程序员有更深入的计算机体系结构和硬件知识,因为它的指令系统比较复杂,编写难度也比较大。
总的来说,汇编语言是一种面向机器的低级语言,它直接访问计算机硬件,具有高执行效率和代码密度等优点。但因为它的指令系统复杂,
编写难度较大,需要程序员有较高的技术水平。
21 Java 属于什么语言?
Java 属于高级语言的一种,高级语言是一种与具体硬件和操作系统无关的编程语言,它更接近于自然语言和数学语言,具有更高的可读性和
可维护性。
高级语言的特点包括:
1. 高级语言具有更强的可读性和可维护性,因为它的语法结构和自然语言更为接近,可以更容易地被人类理解。
2. 高级语言具有更高的抽象能力,可以更容易地表达复杂的算法和逻辑结构,同时也更容易被程序员理解和维护。
3. 高级语言的指令系统通常更为复杂,需要编译器将高级语言代码转换为机器码,因此高级语言的代码通常比较大。
4. 高级语言可以提供更多的功能和特性,例如变量、函数、循环、条件语句、数组、对象等,使得程序编写更加方便和灵活。
5. 高级语言可以更好地支持面向对象编程( OOP )的特性,例如封装、继承、多态等,这使得程序更加模块化和可扩展。
高级语言是一种与具体硬件和操作系统无关的编程语言,它更接近于自然语言和数学语言,具有更高的可读性和可维护性。虽然它的指令系
统复杂,但因为它提供了更多的功能和特性,使得程序编写更加方便和灵活。
public class Main {
public static void main ( String [] args ) {
Integer i1 = 100 ;
Integer i2 = 100 ;
Integer i3 = 200 ;
Integer i4 = 200 ;
System . out . println ( i1 == i2 );
System . out . println ( i3 == i4 );
}
}
true
false
public static Interger valueOf ( int i ){
if ( i >=- 128 && i <= IntergerCache . high ){
return IntergerCache . cache [ i + 128 ];
} else {
return new Interger ( i );
}
}
22 JAVA 中有几种基本数据类型,各自占用多少字节呢?
int Integer 为例,对比一下基本数据类型和封装数据类型。
1. int 类型,直接定义一个变量名赋值即可,是 Integer 需要使用 new 关键字创建对象;
2. 基本类型和 Integer 类型混合使用时, Java 会自动通过拆箱和装箱实现类型转换;
3. Integer 的默认值是 null ,而 int 的默认值是 0
4. Integer 存储在堆内存, int 类型是直接存储在栈空间;
5. Integer 作为一个对象类型,封装了一些方法和属性;
23 、数据类型之间的如何转换?
Java 中,允许进行数值转换,有些情况会丢失一部分精度。
强制类型转换的一般表示形式为:
强制类型转换通过 (int) 进行 double 强转 int ,通过截取小数部分将浮点值转换为整形,会丢失一部分精度。
实线表示无信息丢失的转换。
虚线表示有精度丢失的转换。
24 、说说 Java 中的数据类型提升
Java 中的提升是指自动将低精度类型转换为高精度类型的过程。在 Java 中,当运算符两侧的操作数类型不同时,系统会进行自动类型转换,
将低精度类型提升到高精度类型,以避免数据丢失或计算错误。
Java 中的基本数据类型按照精度分为以下几类: byte short int long float double
其中, byte short 是最低精度的类型, double 是最高精度的类型。
在进行运算时,如果操作数的类型不一致,则系统会自动将较低精度的类型提升为较高精度的类型,以保证运算结果的正确性。
例如, int a = 10; float b = 1.5f; double c = a + b; 在这个例子中, a 会被自动提升为 float 类型,然后与 b 相加得到一个 float 类型的结果,最后
再自动提升为 double 类型并赋值给 c
25 Object 类有哪些常用方法?
Object 类是 Javajava.lang 包下的核心类, Object 类是所有类的父类,何一个类时候如果没有明确的继承一个父类的话,那么它就是 Object
子类。
1 clone()
实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常。
2 getClass()
final 方法,返回 Class 类型的对象,反射来获取对象。
double d = 10.2 ;
int i = ( int ) d ;
System . out . println ( i ); //10 3 toString()
toString() 方法返回一个字符串。
toString() 无处不在,只要对象与一个字符串通过操作符 + 拼接, Java 编译器就会自动地调用 toString 方法来获得这个对象的字符串描述。
还有我们最常用的 System.out.println(name) 方法, println 会自动的调用 name.toString() ,并打印返回的字符串。
4 finalize()
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5 equals()
判断内容是否相等,注意,这里比较的不是内存地址。
java 语言规范要求 equals 方法具有下面的特性:
1. 自反性:对于任何非空引用 x,x.equals(x) 应该返回 true;
2. 对称性:对于任何引用 x, y, 当且仅当 ,y.equals(x) 返回 true,x.equals(y) 也应该返回 true;
3. 传递性:对于任何引用 x,y,z, 如果 x.equals(y) 返回 true,y.equals(z) 返回 true, 那么 x.equals(z) 也应该返回 true;
4. 一致性:如果 x,y 引用的对象没有发生变化 , 反复调用 x.equals(y) 应该返回同样的结果 ;
5. 对于任意非空引用 x,x.equals(null) 返回 false;
6 hashCode()
该方法用于哈希查找,重写了 equals 方法一般都要重写 hashCode 方法。这个方法在一些具有哈希功能的 Collection 中用到。
hashcode() 方法主要配合基于散列的集合一起使用,比如 HashSet HashMap HashTable
当集合需要添加新的对象时,先调用这个对象的 hashcode() 方法,得到对应的 hashcode 值,实际上 hashmap 中会有一个 table 保存已经存进
去的对象的 hashcode 值,如果 table 中没有改 hashcode 值,则直接存入,如果有,就调用 equals 方法与新元素进行比较,相同就不存了,不
同就存入。
7 wait()
wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。 wait() 方法一直等待,直到获得锁或者
被中断。 wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:
1. 其他线程调用了该对象的 notify 方法;
2. 其他线程调用了该对象的 notifyAll 方法;
3. 其他线程调用了 interrupt 中断该线程;
4. 时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。
8 notify()
该方法唤醒在该对象上等待的某个线程。
9 notifyAll()
该方法唤醒在该对象上等待的所有线程。
26 equals hashcode 的关系
如果 equals true hashcode 一定相等;
如果 equals false hashcode 不一定不相等;
如果 hashcode 值相等, equals 不一定相等;
如果 hashcode 值不等, equals 一定不等;
重写 equals 方法时,一定要重写 hashcode 方法
27 String str="i" String str=new String(“i”) 一样吗?
当使用 String str="i" 时,字符串 "i" 会被存储在字符串常量池中。如果已经存在相同的字符串,则不会创建新的对象,而是返回对该字符串
的引用。
当使用 String str=new String("i") 时,会在堆内存中创建一个新的 String 对象,即使字符串常量池中已经存在 "i" 这个字符串。换句话说,
每次使用 new 关键字都会创建一个新的对象,而不管字符串常量池中是否已经存在该字符串。
由于 new String("i") 会在堆内存中创建新的对象,这可能会导致内存使用的增加,并且在某些情况下可能会降低性能(例如,当大量创建相
同的字符串对象时)。另一方面,使用字符串常量池中的字符串可以减少内存使用并提高性能。
String str="i" 是更推荐的写法,因为它可以减少内存使用并提高性能。
28 String s = "nezha";s = s + " soft"; 这两行代码执行后,原始的 String 对象中的内容到
底变了没有?
这两行代码执行后,原始的 String 对象中的内容没有变。在 Java 中, String 是不可变的,这意味着一旦创建了一个 String 对象,就不能改
变它的内容。当你使用 "+" 运算符连接两个字符串时,实际上是创建了一个新的 String 对象,然后将两个字符串的内容复制到新的对象中。
所以,原始的 String 对象( "nezha" )并没有被修改,而是创建了一个新的 String 对象( "nezha soft" ),并将变量 s 指向这个新的对象。 29 、如何将字符串反转?
使用 StringBuilder reverse() 方法。
30 String 类的常用方法都有那些?
1 )常见 String 类的获取功能
length :获取字符串长度;
charAt(int index) :获取指定索引位置的字符;
indexOf(int ch) :返回指定字符在此字符串中第一次出现处的索引;
substring(int start) :从指定位置开始截取字符串 , 默认到末尾;
substring(int start,int end) :从指定位置开始到指定位置结束截取字符串;
2 )常见 String 类的判断功能
equals(Object obj) : 比较字符串的内容是否相同 , 区分大小写;
contains(String str): 判断字符串中是否包含传递进来的字符串;
startsWith(String str): 判断字符串是否以传递进来的字符串开头;
endsWith(String str): 判断字符串是否以传递进来的字符串结尾;
isEmpty(): 判断字符串的内容是否为空串 ""
3 )常见 String 类的转换功能
byte[] getBytes(): 把字符串转换为字节数组;
char[] toCharArray(): 把字符串转换为字符数组;
String valueOf(char[] chs): 把字符数组转成字符串。 valueOf 可以将任意类型转为字符串;
toLowerCase(): 把字符串转成小写;
toUpperCase(): 把字符串转成大写;
concat(String str): 把字符串拼接;
4 )常见 String 类的其他常用功能
replace(char old,char new) 将指定字符进行互换
replace(String old,String new) 将指定字符串进行互换
trim() 去除两端空格
int compareTo(String str) 会对照 ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两
个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是 0
31 String s = new String("nezha"); 创建了几个字符串对象?
第一次调用时,会在堆内存中创建一个字符串对象,同时在字符串常量池中创建一个对象 “nezha”
第二次调用时,只会在堆内存中创建一个字符串对象,指向之前在字符串常量池中创建的对象 “nezha”
32 、想新建一个 java.lang.String 类,能建成功吗?这个类会被类加载器加载吗?为什么?
不能成功新建一个名为 java.lang.String 的类,这个类也不会被类加载器加载,因为这样做违反了 Java 的命名规范和类加载机制。
33 String 类可以被继承吗?
Java 中, String 类是一个被声明为 final 的类。由于 final 关键字的特性, String 类不能被继承。这意味着你不能创建 String 类的子类。这种设
计决策是为了确保 String 类的行为在 Java 中始终如一,防止由于继承可能引入的不可预知的行为。
final 类在 Java 中有以下特点:
1. 它不能被继承。
2. 它不能有子类。
3. 尝试创建 final 类的子类会导致编译错误。
因此,由于 String 类是 final 的,你不能创建它的子类。在需要自定义字符串行为的情况下,你可以考虑使用其他方式,例如创建包含 String
象的新类,并在其中实现所需的行为。
34 String Stringbuffer StringBuilder 的区别?
4 个角度进行对比:
1 )可变性
String 内部的 value 值是 final 修饰的,所以它是不可变类。所以每次修改 String 的值,都会产生一个新的对象。
StringBuffer StringBuilder 是可变类,字符串的变更不会产生新的对象。
2 )线程安全性
String 是不可变类,所以它是线程安全的。
StringBuffer 是线程安全的,因为它每个操作方法都加了 synchronized 同步关键字。
StringBuilder 不是线程安全的,所以在多线程环境下对字符串进行操作,应该使用 StringBuffer ,否则使用 StringBuilder
public class ReverseString {
public static void main ( String [] args ) {
String originalString = "Hello, World!" ;
StringBuilder sb = new StringBuilder ( originalString );
String reversedString = sb . reverse (). toString ();
System . out . println ( reversedString ); // 输出 : "!dlroW ,olleH"
}
} 3 )性能方面
String 的性能是最低的,因为 String 是不可变的,这就意味着在做字符串拼接和修改的时候,需要重新创建新的对象以及分配内存。
StringBuffer 因为是可变的,直接修改即可,但 StringBuffer 添加了重量级锁 synchronized ,其性能不如 StringBuilder
4 )存储方面
String 存储在字符串常量池中。
StringBuilder StringBuffer 都存储在堆内存中。
35 “+” 连接符的效率为何低?
使用 “+” 连接符时, JVM 会隐式的创建 StringBuilder 对象,这种方式在大部分情况下不会造成效率的损失,但是,在循环中进行字符串拼接时
就不一样了。
因为会创建大量的 StringBuilder 对象在堆内存中,这肯定是不允许的,所以这时就建议在循环外创建一个 StringBuilder 对象,然后循环内调
append 方法进行手动拼接。
还有一种特殊情况,如果 “+” 拼接的是字符串常量中的字符串时,编译器会进行优化,直接将两个字符串常量拼接好。
所以, “+” 连接符对于直接相加的字符串常量效率很高,因为在编译期间便确定了它的值;但对于间接相加的情况效率就会变低,建议单线程
时使用 StringBuilder ,多线程时使用 StringBuffer 替代。
36 、说说缓冲区数据结构 bytebuffer
缓冲区是由具有相同类型的数值构成的数组, Buffer 是一个抽象类,它有很多子类,包括 ByteBuffer CharBuffer DoubleBuffer
IntBuffer LongBuffer ShortBuffer
每个缓冲区都具有 4 个属性:
1. 容量 capacity ,缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,且永远不能被改变;
2. 读写位置 position ,下一个要被读或写的元素的索引。位置会自动由相应的 get() put() 函数更新。 这里需要注意的是 positon 的位置
是从 0 开始,比如,已经写入 buffer 3 个元素那那么 position 就是指向第 4 个位置,即 position 设置为 3 (数组从 0 开始计);
3. 界限 limit ,缓冲区的第一个不能被读或写的元素。缓冲区创建时, limit 的值等于 capacity 的值。假设 capacity = 1024 ,我们在程序中
设置了 limit = 512 ,说明 Buffer 的容量为 1024 ,但是从 512 之后既不能读也不能写,因此可以理解成, Buffer 的实际可用大小为
512
4. 可选的标记 mark ,标记,一个备忘位置。保存某个时刻的 position 指针的值,通过调用 mark() 实现,当 mark 被置为负值时,表示废弃
标记。标记在设定前是未定义的 (undefined) 。使用场景是,假设缓冲区中有 10 个元素, position 目前的位置为 2( 也就是如果 get 的话
是第三个元素 ) ,现在只想发送 6 - 10 之间的缓冲数据,此时我们可以 buffer.mark(buffer.position()) ;即把当前的 position 记入
mark 中,然后 buffer.postion(6) ;此时发送给 channel 的数据就是 6 - 10 的数据。发送完后,我们可以调用 buffer.reset() 使得
position = mark ,因此这里的 mark 只是用于临时记录一下位置用的。
position limit 之间的距离指定了可读 / 写的字节数。
-1 <= mark <= position <= limit <= capacity
0<= position <= limit <= capacity
使用缓冲区的主要目的是执行读写循环操作。
假设我们有一个缓冲区,在一开始,它的位置是 0 ,界限等于容量。我们不断地调用 put 将值添加到这个缓冲区中,当我们耗尽所有的数据或
者写出的数据量达到容量大小时,就该切入到读操作了。
这时可以调用 flip 方法将界限设置到当前位置,并把位置复位到 0. 现在在 remaining 方法返回正数时(它返回的值是界限 - 位置),不断地调
get 。在我们将缓冲区中所有的值都写入之后,调用 clear 使缓冲区为下一次写循环做好准备。 clear 方法将位置复位到 0 ,并将界限复位到
容量。
如果想重读缓冲区,可以使用 rewind mark/reset 进行复位。
然后可以 用某个通道的数据填充缓冲区,或者将缓冲区的内容写出到通道中。
Buffer 及其子类都不是线程安全的,若多线程操作该缓冲区,则应通过同步来控制对该缓冲区的访问。
ByteBuffer buffer = ByteBuffer . allocate ( RECORD_SIZE );
channel . read ( buffer );
channel . position ( newpos );
buffer . flip ();
channel . write ( buffer ); 37 hashcode 是什么?有什么作用?
hashCode 主要用于获取对象的哈希码值。
这个哈希码值是一个整数,主要用于数据结构(如哈希表)中,以快速定位对象的位置。
Java 的集合框架(如 HashSet HashMap 等)在内部使用了哈希表来存储元素,这些集合在添加、删除或查找元素时,都会使用到元素的
hashCode 。当我们需要查找一个对象时,哈希表会使用该对象的 hashCode 来计算对象在哈希表中的位置;
当要对对象进行比较时,可以先比较两对象的 hashcode
如果 hashcode 值相等,则再用 equals 进行比较;
如果 hashcode 值不等,则无需再进行 equals 比较,两对象一定不等。
38 Java 创建对象有几种方式
1. 使用 new 关键字,这是最常见的创建对象的方式。通过调用类的构造方法(构造器)来创建对象。
2. 使用反射,通过 Java 的反射 API 可以动态地创建对象。反射允许在运行时获取类的信息,并可以调用类的构造器来创建对象。
3. 使用克隆,如果一个类实现了 Cloneable 接口并重写了 Object 类的 clone() 方法,那么可以通过调用对象的 clone() 方法来创建该对象的一
个副本。
4. 使用序列化与反序列化,如果一个类实现了 Serializable 接口,那么可以通过序列化(将对象转换为字节流)和反序列化(将字节流转
换回对象)来创建对象。这种方式常用于对象的持久化存储和传输。
5. 使用依赖注入,在依赖注入框架(如 Spring )中,对象的创建和管理通常由框架负责。通过配置或注解,框架会自动创建所需的对象,
并将其注入到需要的地方。
6. 使用工厂模式,工厂模式是一种创建对象的设计模式,它隐藏了对象创建的具体逻辑,并通过一个统一的接口来创建对象。工厂模式可
以分为简单工厂、工厂方法和抽象工厂等。
7. 使用构建器模式,构建器模式( Builder Pattern )是一种对象构建的设计模式,它允许你以更加灵活的方式创建复杂对象。构建器模式
通常用于构造具有多个可选参数的类。
39 、说说对象创建的过程
① 创建对象时,或者第一次访问类的静态方法或静态字段时, Java 解释器会搜索类路径来定位 class 文件。
② 当 class 被加载后,将创建一个 class 对象,它的所有静态初始化工作都会执行。
因此,静态初始化只在 class 对象首次加载时发生一次。
③ 当使用 new 创建对象时,首先会在堆上为对象分配足够的存储空间,这块存储空间会被清空,然后自动将对象中的所有基本类型设置为其
默认值,比如 int 的默认值为 0 Integer 的默认值为 null
④ 执行所有初始化操作。
⑤ 执行构造器。
40 、对象间的四种关系
1 )依赖
依赖关系表示一个类依赖于另一个类的定义。例如,一个人 (Person) 可以买车 (car) 和房子 (House) Person 类依赖于 Car 类和 House 类的定
义,因为 Person 类引用了 Car House 。与关联不同的是, Person 类里并没有 Car House 类型的属性, Car House 的实例是以参量的方式
传入到 buy() 方法中去的。一般而言,依赖关系在 Java 语言中体现为局域变量、方法的形参,或者对静态方法的调用。
2 )关联
关联 (Association )关系是类与类之间的联接,它使一个类知道另一个类的属性和方法。关联可以是双向的,也可以是单向的。在 Java 语言
中,关联关系一般使用成员变量来实现。
3 )聚合
聚合 (Aggregation) 关系是关联关系的一种,是强的关联关系。聚合是整体和个体之间的关系。例如,汽车类与引擎类、轮胎类,以及其它的
零件类之间的关系便整体和个体的关系。与关联关系一样,聚合关系也是通过实例变量实现的。但是关联关系所涉及的两个类是处在同一层
次上的,而在聚合关系中,两个类是处在不平等层次上的,一个代表整体,另一个代表部分。
4 )组合
组合 (Composition) 关系是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分对象的生命周
期,组合关系是不能共享的。代表整体的对象需要负责保持部分对象和存活,在一些情况下将负责代表部分的对象湮灭掉。代表整体的对象
可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生组合
关系,由后者排他地负责生命周期。部分和整体的生命周期一样。
41 、说说隐式参数和显式参数
显式参数是我们在调用方法时明确传递的参数。这些参数在方法声明中作为方法签名的一部分出现,并且在调用时需要提供具体的值。
隐式参数不是由程序员明确传递的,而是系统自动传递的。最常见的例子就是 this 关键字,它引用了当前对象本身。
在非静态方法中,我们可以使用 this 关键字来引用当前对象的属性或方法。这个 this 就是隐式参数,因为它不需要我们显式地传递给方法,但
方法内部可以访问它。
另一个隐式参数是静态方法中的 class 参数,它指向定义该方法的类,可以通过 ClassName.class 的形式获取,这也是一种隐式参数。
42 、说说 Java 参数可变
可变参数是 Java 5 中引入的一个特性,它允许方法接受任意数量的参数,这些参数在方法内部被当作数组处理。
在方法声明中,使用三个点 ... 来表示可变参数。这意味着该方法可以接收任意数量的指定类型的参数。在方法内部,这些参数被当作数组处
理。
可变参数可以用于方法重载,但需要注意的是,如果一个方法使用了可变参数,那么它不能与只接受单个参数的方法进行重载。在重写时,
子类方法也可以使用可变参数,但需要保持参数类型的兼容性。 可变参数一般用于日志记录,数学计算等场景,但过度使用可变参数可能会导致代码难以理解和维护,因此需要谨慎使用。
43 、普通类和抽象类有哪些区别?
1. 实例化:普通类可以被实例化,即可以创建这个类的对象。然而,抽象类不能被实例化。尝试实例化抽象类会导致编译错误。这是因为
抽象类通常表示一种概念或行为,而不是具体的对象。
2. 抽象方法:抽象类可以包含抽象方法,这是没有实现的方法,只有方法的声明,没有具体的实现。普通类则不能包含抽象方法。如果一
个类包含抽象方法,那么这个类必须被声明为抽象类。
3. 继承:一个类可以从一个抽象类继承,但也可以从普通类继承。然而,一个抽象类只能被另一个类继承,而不能被实例化。这意味着抽
象类是类的基类,用于定义一些通用的属性和方法,而具体的实现则由子类来完成。
4. 设计目的:普通类通常用于表示具体的实体或对象,如一个具体的动物或人。而抽象类则用于表示一种概念或一组具有共同特性的对
象,这些对象的具体实现由子类来完成。例如,一个 " 动物 " 类可能是一个抽象类,而 " " " " 则可能是这个抽象类的具体实现。
总的来说,普通类和抽象类在实例化、包含抽象方法、继承和设计目的等方面存在明显的差异。抽象类提供了一种方式来定义一组具有共同
特性的对象的概念,而具体的实现则由子类来完成。这使得抽象类在面向对象编程中非常有用,尤其是在需要创建一组具有共同行为的对象
时。
44 、接口和抽象类有什么区别?
1 )定义与实现
接口是一种抽象类型,它定义了一组方法的规范,但不提供具体的实现。接口中的所有方法都是抽象的,没有方法体。接口主要用于定义对
象的行为。
抽象类是一种不能被实例化的类,它定义了一组抽象方法和非抽象方法。抽象方法没有具体实现,需要子类来提供实现。抽象类主要用于定
义对象的共同属性和行为,并作为子类的基类。
2 )继承与实现
一个类可以实现多个接口,这意味着一个类可以拥有多个行为规范。
一个类只能继承自一个抽象类,子类继承抽象类时,必须提供抽象类中所有抽象方法的具体实现。
3 )字段与属性
接口中不能定义字段,只能定义常量(静态的、不可变的)。
抽象类中可以定义字段、常量、抽象方法以及非抽象方法。
45 default 方法是什么?
通过 default 关键字修饰的方法就是默认方法。
如果接口中有很多方法,实现它的类就需要重写接口中的所有方法,不管是否需要用到。如果接口中的某个方法被 default 关键字修饰了,那
么具体的实现类中可以不用实现方法。
46 、说说 Java 中多态的实现原理
多态机制包括静态多态(编译时多态)和动态多态(运行时多态)。
静态多态比如说重载,动态多态一般指在运行时才能确定调用哪个方法。
我们通常所说的多态一般指运行时多态,也就编译时不确定究竟调用哪个具体方法,一直等到运行时才能确定。
多态实现方式:继承 extends 和实现 implements
多态的核心在于子类对父类方法的改写或对接口方法的实现,以取得在运行时不同的执行效果。
当调用对象的某个方法时, JVM 查找该对象类的方法表,以确定该方法的直接引用地址,有了地址后才真正调用该方法。
47 、构造器可以被重写吗?
构造器在面向对象编程中用于初始化对象的状态。构造器不是传统意义上的方法,因此它们不能被重写。重写是子类对父类中已有的方法进
行重新定义的过程,使得子类对象在调用该方法时执行的是子类中的定义,而不是父类中的定义。
然而,构造器在子类中可以被调用,并且子类可以定义自己的构造器。这通常是通过在子类的构造器中调用父类的构造器来实现的,使用
super() 关键字。这并不意味着构造器被重写,而是子类构造器在初始化对象时会包含父类构造器的初始化逻辑。
48 、说说 Java Bean 的命名规范
1 )类名
首字母大写,并且符合驼峰命名法,禁用拼音。
2 )成员变量
成员变量应该是 private 私有的,以确保封装性。
属性的命名也应该采用驼峰命名法,并且首字母小写。
每一个成员变量都有一对公有的 Getter Setter 方法,以便外部代码可以访问和修改属性的值。
如果属性是布尔类型,可以使用 is 代替 get 作为前缀,例如 isVisible
3 )构造方法
JavaBean 应该提供一个无参数的默认构造函数,这是为了使 JavaBean 在通过反射实例化时能够正常工作。
如果需要,也可以提供带参数的构造函数,以便在创建 Bean 实例时初始化其属性。
4 )其它注意事项
1. 避免使用 Java 保留字和特殊字符作为类名、属性名或方法名。 2. 在命名时,应考虑到代码的可读性和可维护性,避免使用过于复杂或难以理解的名称。
3. 保持命名的一致性,例如在整个项目中,如果某个特定的概念或对象使用了特定的命名方式,那么在其他地方也应遵循相同的命名方
式。
49 Java 内部类是什么,有哪些应用场景?
Java 内部类是定义在另一个类中的类。
内部类可以访问外部类的所有成员(包括私有成员),并且可以隐藏外部类的某些成员。内部类主要有四种类型:静态内部类、成员内部
类、局部内部类和匿名内部类。
应用场景:
1. 每个内部类都可以独立的继承一个类,所以无论外部类是否继承了某个类,内部类依然可以继承其他类,这就完美的解决了 java 没有多
继承的问题。
2. 可以有效的将有一定关系的类组织在一起,又可以对外界有所隐藏。
3. 方便编写事件驱动程序
4. 方便编写多线程代码
5. 用来装逼,代码越复杂,是不是感觉越牛逼。
50 、静态内部类与非静态内部类有什么区别
1. 定义位置与访问权限:静态内部类定义在类的内部,使用 static 关键字进行定义。它只能访问外部类的静态成员变量和方法,而不能访
问外部类的非静态成员。非静态内部类可以定义在外部类的任意位置,包括方法内部(此时称为局部内部类)。非静态内部类可以直接
访问外部类的所有成员,包括私有成员。
2. 创建实例的方式:生成一个静态内部类不需要外部类成员,静态内部类的对象可以直接生成。而对于非静态内部类,其创建依赖于外部
类的实例,不能独立存在,即需要有一个外部类的实例来引用它。
3. 应用场景:静态内部类由于其不能访问外部类的非静态成员,因此在某些情况下,如仅需要访问外部类的静态成员时,使用静态内部类
会更为合适。非静态内部类则因其可以访问外部类的所有成员,包括私有成员,所以在需要这种访问权限的场景下更为常用。
51 throw throws 的区别?
throw 代表一个动作,用于在程序中明确地抛出一个异常对象。它出现在方法体内部,可以作为单独的语句使用。 throw 用于表示一个具体的
异常类型,其后面必须跟一个异常实例。
throws :代表一种状态,用于声明一个方法可能会抛出的异常。它出现在方法声明中,跟在方法参数列表后面,不能单独使用。 throws 用于
声明在该方法内可能抛出的异常,但并不抛出具体的异常实例。
52 final finally finalize 有什么区别?
final 可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写
finally 用于抛异常, finally 代码块内语句无论是否发生异常,都会在执行 finally ,常用于一些流的关闭。
finalize 方法用于垃圾回收。
一般情况下不需要我们实现 finalize ,当对象被回收的时候需要释放一些资源,比如 socket 链接,在对象初始化时创建,整个生命周期内有
效,那么需要实现 finalize 方法,关闭这个链接。
但是当调用 finalize 方法后,并不意味着 gc 会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时
候,因为之前调用过一次,这次又不会调用了,产生问题。所以,不推荐使用 finalize 方法。
53 trycatch 的执行顺序
try 中第一行代码开始执行,执行到出现异常的代码, JVM 会创建一个异常对象。
判断 catch 是否能捕获到 jvm 创建的异常对象,① 如果捕获到就跳到 catch 代码块中,不会结束程序,继续从 catch 中的代码逻辑;② 如果捕
获不到,直接打印异常信息并结束程序。
如果 try 中没有异常,则执行完 try 中代码,跳过 catch ,进入 finally 代码块。
54 try-catch-finally 中,如果 catch return 了, finally 还会执行吗? 55 、常见的异常类有哪些?
1. NullPointerException :空指针异常;
2. SQLException :数据库相关的异常;
3. IndexOutOfBoundsException :数组下角标越界异常;
4. FileNotFoundException :打开文件失败时抛出;
5. IOException :当发生某种 IO 异常时抛出;
6. ClassCastException :当试图将对象强制转换为不是实例的子类时,抛出此异常;、
7. NoSuchMethodException :无法找到某一方法时,抛出;
8. ArrayStoreException :试图将错误类型的对象存储到一个对象数组时抛出的异常;
9. NumberFormatException :当试图将字符串转换成数字时,失败了,抛出;
10. IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
11. ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数 除以零 时,抛出此类的一个实例。
56 、开发中,你是如何处理异常的?
方法内如果抛出需要检测的异常,那么方法上必须要声明,否则必须在方法内用 try-catch 捕捉,否则编译失败。
如果调用了声明异常的函数,要么 try-catch 要么 throws ,否则编译失败。
什么时候 catch ,什么时候 throws ?功能内容可以解决,用 catch ,解决不了,用 throws 告诉调用者,有调用者解决。
如果一个功能抛出了多个异常,那么调用时必须有对应多个 catch 进行针对性的处理。
57 NoClassDefFoundError ClassNotFoundException 有什么区别?
NoClassDefFoundError JVM 运行时通过 classpath 加载类时,找不到对应的类而抛出的错误。
ClassNotFoundException :如果在编译过程中可能出现此异常,在编译过程中必须将其抛出。
NoClassDefFoundError 的发生场景:
1. 类依赖的 class jar 不存在
2. 类文件存在,但是在不同的域中,简而言之,就是找不到
ClassNotFoundException 的发生场景:
1. 调用 class forName 方法时,找不到指定的类
2. ClassLoader 中的 findSystemClass() 方法时,找不到指定的类
58 、聊聊记录日志的规范
1 )日志的可读性,日志是记录问题的,然后让维护人员看的,规范的、通俗易懂的日志才是王道。
2 )日志的性能,不管是记录到文件里,还是记录到数据库里,记录日志肯定是要消耗程序性能的,这样,哪些需要记下,哪些不用记,
需要权衡利弊。
3 )大的循环中,尽量不要记录日志。
4 )日志的级别,一般情况下,程序运行时记录 info 日志,发生异常时记录 error 日志,也可以记录警告日志,比如某些参数超过了,但是
不影响整体程序的运行。 59 、哪些内容需要记在日志里?
哪些内容需要记录在日志中,日志不是越多越好,越丰满越好,凡是要有一个度。
1. 日期;
2. 时间;
3. 日志级别;
4. 代码位置;
5. 线程号;
6. 日志内容;
7. 错误码;
8. 错误信息
9. 业务描述;
10. 关键字,比如产品 id
11. 入参、回参;
总之一句话,你认为哪些参数比较重要,有助于你排查问题,就记录哪些。
60 Log4j 有哪些日志级别?
Log4j Apache 的一个开源项目,项目中一般都是通过 Log4j 来记录日志,可以通过配置文件定义日志输出的级别、格式、存储路径等。
Log4j 中将要输出的 Log 信息定义了 6 种级别,依次为 TRACE DEBUG INFO WARN ERROR FATAL
1 TRACE
很低的日志级别,一般不会使用。
2 DEBUG
一般在项目调试阶段使用,记录的日志更细粒化,主要打印开发过程中的一些重要变量。
3 INFO
info 日志,是最常用的日志,用于记录正常运行情况下,程序的执行情况,执行轨迹,打印一些比较重要的东西,但不能滥用,避免日记记
录过多,维护运维阶段定位问题过于麻烦。
4 WARN
主要用于记录一些警告,比如你的本意是查询某些产品信息,但是没有查到,逻辑上没有错误,但业务上说不通。
5 ERROR
通常在发生异常、程序入参校验失败时,用 error 级别记录,也会单独产生 error 文件。
6 FATAL
FATAL 指出每个严重的错误事件将会导致应用程序的退出,这个级别比较高,重大错误,程序无法恢复,必须通过重启程序来解决。
61 、什么是 java 序列化?什么情况下需要序列化?
序列化就是把内存里面的对象转化为字节流,以便用来实现存储或者传输。
序列化的前提是保证通信双方对于对象的可识别性,所以很多时候,我们会把对象先转化为通用的解析格式,比如 json xml 等。然后再把
他们转化为数据流进行网络传输,从而实现跨平台和跨语言的可识别性。
序列化是通过实现 serializable 接口,该接口没有需要实现的方法, implement Serializable 只是为了标注该对象是可被序列化的。
反序列化就是根据从文件或者网络上获取到的对象的字节流,根据字节流里面保存的对象描述信息和状态。
62 、序列化使用场景有哪些?
1. 像银行卡、密码这些字段不能被序列化;
2. 将对象存储到文件中时进行序列化,从文件中读取对象时需要反序列化;
3. 分布式传递对象,或者网络传输,需要序列化;
4. 存入缓存数据库(如 Redis )时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化; 63 、使用序列化和反序列化的注意事项
1 Java 序列化的方式
实现 Serializable 接口:可以自定义 writeObject readObject writeReplace readResolve 方法,会通过反射调用。
实现 Externalizable 接口,它是 Serializable 接口的子类,用户要实现的 writeExternal() readExternal() 方法,用来决定如何序列化和反序
列化。因为序列化哪些字段,需要方法指定,所以 transient 在这里无效。
2 )序列化 ID 问题
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private
static final long serialVersionUID = 1L )。
3 )静态字段不会序列化
序列化时不保存静态变量,这是因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。
4 transient
transient 代表对象的临时数据。
如果你不想让对象中的某个成员被序列化可以在定义它的时候加上 transient 关键字进行修饰 ,这样,在对象被序列化时其就不会被序列化。
transient 修饰过的成员反序列化后将赋予默认值,即 0 null
有些时候像银行卡号这些字段是不希望在网络上传输的, transient 的作用就是把这个字段的生命周期仅存于调用者的内存中而不会写到磁盘
里持久化。
5 )父类的序列化
当一个父类实现序列化,子类自动实现序列化;而子类实现了 Serializable 接口,父类也需要实现 Serializable 接口。
6 )当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化
7 )并非所有的对象都可以序列化
① 安全方面的原因,比如一个对象拥有 private public field ,对于一个要传输的对象,比如写到文件,或者进行 RMI 传输等等,在序列化
进行传输的过程中,这个对象的 private 等域是不受保护的;
② 资源分配方面的原因,比如 socket thread 类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没
有必要这样实现;
8 )序列化解决深拷贝问题
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存,这是能用序列化解决深拷贝的重要原因。
64 、为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?
1 )什么要使用克隆?
想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了。
2 )如何实现对象克隆?
1. 实现 Cloneable 接口,重写 clone 方法;
2. 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆;
3. BeanUtils apache Spring 都提供了 bean 工具,只是这都是浅克隆。
3 )深拷贝和浅拷贝区别是什么?
1. 浅拷贝,指的是重新分配一块内存,创建一个新的对象,指针指向被复制对象的同一块内存地址,如果原对象发生改变,那么浅拷贝得
到的新对象也会反映出这些变化;
2. 深拷贝:它是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象
中。这意味着深拷贝不仅复制了对象本身,还复制了对象所引用的所有其他对象。因此,新对象和原对象没有任何关联,对其中一个对
象的修改不会影响另一个对象。
65 Java 反射是什么?有哪些应用场景
Java 反射是 Java 语言的一个核心特性,它允许程序在运行时检查类、接口、字段和方法的信息,并能动态地调用对象的方法。反射的主要作
用是增强程序的灵活性,使得程序能够在运行时动态地加载、链接和使用类。
但反射的代码比正常调用的代码更多,性能更慢,应避免使用反射。
Java 反射应用场景:
1. 在框架设计中,反射常被用于实现框架的扩展性和灵活性。框架可以通过反射在运行时加载和使用不同的类,从而实现对不同业务逻辑
的支持;
2. 通过反射,可以创建动态代理对象,这些代理对象可以在运行时代表其他对象执行操作。这在实现 AOP (面向切面编程)等高级功能时
非常有用;
3. 反射可以解析注解信息,并执行相应的操作;
4. 在如 Hibernate 这样的 ORM 框架中,对象需要被转化为实体类并存储到数据库中。这个过程中,反射被用于动态创建实体类对象、获取
类的属性和方法等;
5. Java 中,序列化和反序列化都需要使用到反射技术。序列化会将对象转化成字节流,反序列化则将字节流还原为对象。在这个过程
中,需要借助反射技术来获取对象的属性信息。
Java 反射提供了一种强大的机制,使得程序能够在运行时动态地操作类和对象,从而增强了程序的灵活性和可扩展性。然而,反射也有一些
潜在的性能开销和安全性问题,因此在使用时需要谨慎考虑。 66 java 中都有哪些引用类型?
1 )强引用
Java 中默认声明的就是强引用,比如:
只要强引用存在,垃圾回收器将永远不会回收被引用的对象。如果想被回收,可以将对象置为 null
2 )软引用( SoftReference
在内存足够的时候,软引用不会被回收,只有在内存不足时,系统才会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,
才会跑出内存溢出异常。
3 )弱引用( WeakReference
进行垃圾回收时,弱引用就会被回收。
4 )虚引用( PhantomReference
5 )引用队列( ReferenceQueue
引用队列可以与软引用、弱引用、虚引用一起配合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有引用,就会在回收对象之前,把这个引用加入到引用队列中。
程序可以通过判断引用队列中是否加入了引用,来判断被引用的对象是否将要被垃圾回收,这样可以在对象被回收之前采取一些必要的措
施。
67 、枚举是什么,有哪些特点?
1. 枚举作为一个类 , 可以有自己的属性 ( 通常应该是常量,我没遇到过不是的情况 ) 以及自己的方法 ( 否则只能用 switch 来写,实际违反原则 )
2. 枚举类型检查,有效性检查
3. 和常量相比,无需查看文档和源码就能直接知道所有可能返回值,方便编码。
4. switch 配合使用解决 ifelse 过多的问题,使用 switch 进行条件判断时,条件参数一般只能是整型,字符型。而枚举型确实也被 switch
所支持,在 java 1.7 switch 也对字符串进行了支持。
68 、泛型是什么,有哪些应用场景?
泛型是一种编程范式,其核心思想是将类型参数化,即把数据类型作为参数传递。通过泛型,可以创建灵活且可重用的代码,而无需针对每
种数据类型都编写重复的代码。泛型的主要优点包括提高代码的可读性、可维护性和类型安全性。
应用场景:
1. 集合类和数据结构:泛型最常见的用途是在集合类(如 ArrayList LinkedList HashMap 等)和数据结构中使用。通过使用泛型,可
以创建一个通用的集合类,用于存储不同类型的元素,并在编译时捕获类型错误。
2. 自定义数据结构:使用泛型可以创建自定义的数据结构,以适应不同类型的数据。这样可以编写通用的、可重用的代码,减少为不同类
型的数据编写不同实现的需求。
3. 泛型方法:除了泛型类,还有泛型方法,即在方法级别使用泛型。这对于那些只需要在特定方法中使用泛型的情况非常有用。
4. 接口和抽象类:泛型也可以用于接口和抽象类的定义,以创建通用的接口和抽象类,这些接口和类可以被不同类型的实现或子类使用。
5. 数据库操作:在数据库操作中,泛型可以用于定义数据库表中各个字段的类型,从而提高程序的类型安全性。
使用泛型的主要目的之一是提供编译时类型检查,这有助于减少在运行时出现类型错误的可能性。此外,泛型还可以提高代码的可读性和重
用性,避免不必要的类型转换和强制类型转换,从而提高代码的安全性和可维护性。
Object obj = new Object ();
obj = null ;
byte [] buff = new byte [ 1024 * 1024 ];
SoftReference < byte [] > sr = new SoftReference <> ( buff ); 69 java IO 流分为几种?
1 )字节流
I/O 操作中,数据被视为一系列按顺序排列的字节流。在 Java 中,这种字节流被称为 InputStream OutputStream
InputStream 代表一个输入流,它是一个抽象类,不能被实例化。 InputStream 定义了一些通用方法,如 read() skip() 等,用于从输入流
中读取数据。
OutputStream 代表一个输出流,它也是一个抽象类,不能被实例化。 OutputStream 定义了一些通用方法,如 write() flush() 等,用于
向输出流中写入数据。
2 )字符流
除了字节流, Java 还提供字符流,字符流类似于字节流,不同之处在于字符流是按字符读写数据,而不是按字节。 Java 中最基本的字符流是
Reader Writer ,它们是基于 InputStream OutputStream 的转换类,用于完成字节流与字符流之间的转换。
3 )缓冲流
BufferedInputStream BufferedOutputStream I/O 包中提供的缓冲输入输出流。它们可以提高 I/O 操作的效率,具有较好的缓存机
制,能够减少磁盘操作,缩短文件传输时间。使用 BufferedInputStream BufferedOutputStream 进行读取和写入时, Java 会自动调整
缓冲区的大小,使其能够适应不同的数据传输速度。
4 )对象流
可以读取或写入 Java 对象的流,比较典型的对象流包括 ObjectInputStream ObjectOutputStream
对象流需要将对象序列化和反序列化为字节序列,使用 ObjectInputStream ObjectOutputStream 可以将 Java 对象转换为字节流进行传
输或存储。
在网络传输和文件存储中, ObjectInputStream ObjectOutputStream 通常会被使用到。
70 BIO NIO AIO 有什么区别?
1 )同步阻塞 BIO
JDK1.4 之前,建立网络连接的时候采用 BIO 模式,先在启动服务端 socket ,然后启动客户端 socket ,对服务端通信,客户端发送请求后,先
判断服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝请求,如果有的话会等待请求结束后才继续执行。
线程发起 IO 请求,不管内核是否准备好 IO 操作,从发起请求起,线程一直阻塞,直到操作完成。
服务器实现模式为 一个连接一个线程 ,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不
必要的线程开销,可以通过线程池机制改善。
2 )同步非阻塞 NIO
NIO 主要是想解决 BIO 的大并发问题, BIO 是每一个请求分配一个线程,当请求过多时,每个线程占用一定的内存空间,服务器瘫痪了。
JDK1.4 开始支持 NIO ,适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中。
线程发起 IO 请求,立即返回;内核在做好 IO 操作准备之后,通过调用注册回调函数通知线程做 IO 操作,线程开始阻塞,直到操作完成。
服务器实现模式为 一个请求一个线程 ,即客户端发送连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线
程进行处理。
3 )异步非阻塞 AIO
JDK1.7 开始支持 AIO ,适用于连接数目多且连接比较长的结构,比如相册服务器,充分调用 OS 参与并发操作。
线程发起 IO 请求,立即返回;内存做好 IO 操作准备之后,做 IO 操作,直到操作完成或者失败,通过调用注册回调函数通知线程做 IO 操作完
成或者失败。
服务器实现模式为 一个有效请求一个线程 ,客户端 IO 请求都由 OS 先完成了再通知服务器应用去启动线程进行处理。
71 BIO NIO AIO 有哪些应用场景
1. BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中, JDK1.4 以前的唯一选择,但程
序简单易理解。
2. NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕 系统,服务器间通讯等。编程比较复杂, JDK1.4
始支持。
3. AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分 调用 OS 参与并发操作,编程比较复杂, JDK7 开始
支持
72 、简述一下 BIO 的编程流程
1. 服务器端启动一个 ServerSocket
2. 客户端启动 Socket 对服务器进行通 信,默认情况下服务器端需要对每 个客户 建立一个线程与之通讯;
3. 客户端发出请求后 , 先咨询服务器 是否有线程响应,如果没有则会等 待,或者被拒绝;
4. 如果有响应,客户端线程会等待请 求结束后,在继续执行;
73 NIO 的三大核心部分是什么?
Selector( 选择器 ) Channel( 通道 ) Buffer( 缓冲区 )
1. Selector 对应一个线程, 一个线程对应多个 channel( 连接 )
2. 该图反应了有三个 channel 注册到 该 selector // 程序;
3. 每个 channel 都会对应一个 Buffer
4. 程序切换到哪个 channel 是有事件决定的 , Event 就是一个重要的概念;
5. Selector 会根据不同的事件,在各个通道上切换;
6. Buffer 就是一个内存块 , 底层是有一个数组; 7. 数据的读取写入是通过 Buffer, 这个和 BIO , BIO 中要么是输入流,或者是 输出流 , 不能双向,但是 NIO Buffer 是可以读也可以写 , 需要
flip 方法切换;
8. channel 是双向的 , 可以返回底层操作系统的情况 , 比如 Linux , 底层的操作系统 通道就是双向的;
NIO 是面向缓冲区,或者说面向块编程,数据读取到一个 它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就 增加了处理过程中的灵
活性,使用它可以提供非阻塞式的高伸缩性网络。
HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求 的数量比 HTTP1.1 大了好几个数量级。
缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个 容器对象 ( 含数组 ) ,该对象提供了一组方法,可以更轻松地使用内存块,,
缓冲区对 象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。 Channel 提供从文件、 网络读取数据的渠道,但是读取或写入的数
据都必须经由 Buffer
74 NIO buffer 的四大属性是什么?
1. mark :标记
2. position :位置,下一个要被读或写的元素的索引, 每次读写缓冲区数据时都会改变改值, 为下次读写作准备。
3. limit :表示缓冲区的当前终点,不能对缓冲区 超过极限的位置进行读写操作。且极限 是可以修改的
4. capacity :容量,即可以容纳的最大数据量;在缓 冲区创建时被设定并且不能改变。
75 、对比一下 BIO NIO
1. BIO 以流的方式处理数据 , NIO 以块的方式处理数据 , I/O 的效率比流 I/O 高很多;
2. BIO 是阻塞的, NIO 则是非阻塞的;
3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel( 通道 ) Buffer( 缓冲区 ) 进 行操作,数据总是从通道读取到缓冲区中,或者从
缓冲区写入到通道中。 Selector( 选择器 ) 用于监听多个通道的事件(比如:连接请求,数据到达等),因 此使用单个线程就可以监听多
个客户端通道。
76 FileChannel 是做什么的?
FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有:
1. read ,从通道读取数据并放到缓冲区中
2. write ,把缓冲区的数据写到通道中
3. transferFrom ,从目标通道 中复制数据到当前通道
4. transferTo ,把数据从当 前通道复制给目标通道
77 、简述一下 Selector 选择器
1. Java NIO ,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连 接,就会使用到 Selector( 选择器 )
2. Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然 后针对每个事件进行相应的处理。这样就可以
只用一个单线程去管理多个 通道,也就是管理多个连接和请求。
3. 只有在 连接 / 通道 真正有读写事件发生时,才会进行读写,就大大地减少 了系统开销,并且不必为每个连接都创建一个线程,不用去维
护多个线程。
4. 避免了多线程之间的上下文切换导致的开销。
78 lambda 表达式是什么,有哪些应用场景?
Java 8 中, Lambda 表达式被引入作为一个重要新特性,使得 Java 能够进行函数式编程,并在并发性能上取得了实质性的进步。 Lambda
达式简化了匿名内部类的形式,并达到了同样的效果,使得代码更加简洁。
应用场景:
1. 集合操作: Lambda 表达式可以与集合操作方法(如 forEach filter map reduce )结合使用,对集合中的元素进行遍历、筛选、映
射、聚合等操作。
2. 排序: Lambda 表达式可以用于自定义的排序功能,通过传递不同的比较规则实现对集合中元素的排序。
3. 线程编程: Lambda 表达式可以简化线程编程中的代码,例如使用 Lambda 表达式创建 Runnable 对象,或使用 Lambda 表达式实现函数
式接口来处理线程任务。
4. GUI 事件处理: Lambda 表达式可用于简化 GUI 事件处理代码,如为按钮、菜单等组件注册事件监听器。
5. 数据处理:对于大数据集的处理,如统计、过滤、转换等, Lambda 表达式表现出色。其并行处理的能力可以提高数据处理的效率。
6. Web 开发: Lambda 表达式可以简化 Web 开发中的重复性代码,例如通过 Lambda 表达式实现控制器、过滤器、拦截器等。
79 Java8 :: 是什么,有哪些应用场景?
Java 8 中,双冒号 “::” 是一个方法引用操作符,也被称为方法引用符。它用于引用类的方法,并返回一个函数接口( function interface )。
这与 lambda 表达式有所不同,因为 lambda 表达式需要自定义一个 lambda 体,而 “::” 则直接引用一个已存在的方法。
“::” 操作符在 Java 8 中有多种用法:
1. 引用静态方法:使用 类名 :: 静态方法名 的格式。例如, Integer::parseInt 就是引用 Integer 类的 parseInt 静态方法。
2. 引用对象方法:使用 实例对象 :: 实例方法 的格式。例如,对于字符串的 substring 方法,可以写成 String::substring
3. 引用构造方法:使用 类名 ::new” 的格式。例如, User::new 就是引用 User 类的构造方法。
Java8 :: 有哪些应用场景?
由于 “::” 操作符可以方便地引用类的方法,并返回函数接口,因此在需要传递函数作为参数或者需要简化代码的场景中非常有用。例如,在集
合操作、事件处理、替代策略模式、流式编程等场景中,都可以看到它的身影。此外,由于方法引用符可以替代 lambda 表达式,因此在需要
减少代码冗余和提高可读性的地方,也可以考虑使用它。
80 Java 8 parallel 是干嘛的?
Stream.parallel() 方法是 Java 8 Stream API 提供的一种并行处理方式。在处理大量数据或者耗时操作时,使用 Stream.parallel() 方法可
以充分利用多核 CPU 的优势,提高程序的性能。
Stream.parallel() 方法是将串行流转化为并行流的方法。通过该方法可以将大量数据划分为多个子任务交由多个线程并行处理,最终将各个
子任务的计算结果合并得到最终结果。使用 Stream.parallel() 可以简化多线程编程,减少开发难度。 需要注意的是,并行处理可能会引入线程安全等问题,需要根据具体情况进行选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值