第二天面试题
已经理解可以直接背每个问题的总结
1.变量的分类及区别(局部变量、成员变量、静态变量的区别)?
局部变量定义在方法内部或语句块内部; 成员变量定义在方法外部,类内部; 静态变量
定义在方法外部,类内部,由 static 修饰;
局部变量没有初始值,必须先赋值才能使用; 成员变量和静态变量都有默认的初始值;
局部变量通过变量名调用; 成员变量通过对象调用; 静态变量既可以通过对象调用,也
可以通过类名调用;
局部变量在栈内存; 成员变量在堆内存; 静态变量在方法区;
局部变量的生命周期: 与方法共存亡; 成员变量的生命周期: 与对象共存亡; 静态变
量的生命周期: 与类共存亡;
局部变量不能被访问控制修饰符及 static 所修饰; 成员变量/静态变量可以被
public,private,static 等修饰符所修饰。成员变量/静态变量和局部变量都能被 final 所修饰;
解释:
在编程中,变量根据其作用域和生命周期可以分为以下几类:
1. 局部变量(Local Variable)
- 定义:在方法、构造函数或代码块内部声明的变量。
- 作用域:仅限于声明它的方法、构造函数或代码块内部。
- 生命周期:从声明处开始,到方法、构造函数或代码块执行结束时销毁。
- 存储位置:通常存储在栈内存中。
- 默认值:局部变量没有默认值,必须显式初始化后才能使用。
示例:
public void exampleMethod() {
int localVar = 10; // 局部变量
System.out.println(localVar);
}
2. 成员变量(Instance Variable)
- 定义:在类内部但在方法、构造函数或代码块外部声明的变量,且没有
static
修饰。 - 作用域:整个类内部,可以被类中的所有方法、构造函数和代码块访问。
- 生命周期:随着对象的创建而创建,随着对象的销毁而销毁。
- 存储位置:存储在堆内存中。
- 默认值:成员变量有默认值,如
int
默认为0
,boolean
默认为false
,对象引用默认为null
。
示例:
public class MyClass {
int instanceVar; // 成员变量
public void exampleMethod() {
instanceVar = 20; // 访问成员变量
System.out.println(instanceVar);
}
}
3. 静态变量(Static Variable)
- 定义:在类内部但在方法、构造函数或代码块外部声明的变量,且使用
static
修饰。 - 作用域:整个类内部,并且可以通过类名直接访问,无需创建对象。
- 生命周期:随着类的加载而创建,随着类的卸载而销毁。
- 存储位置:存储在方法区(或称为静态存储区)。
- 默认值:静态变量有默认值,与成员变量相同。
示例:
public class MyClass {
static int staticVar; // 静态变量
public void exampleMethod() {
staticVar = 30; // 访问静态变量
System.out.println(staticVar);
}
public static void main(String[] args) {
System.out.println(MyClass.staticVar); // 通过类名访问静态变量
}
}
区别总结
特性 | 局部变量 | 成员变量 | 静态变量 |
---|---|---|---|
声明位置 | 方法、构造函数或代码块内部 | 类内部,方法外部 | 类内部,方法外部,使用 static 修饰 |
作用域 | 仅限于声明它的代码块 | 整个类内部 | 整个类内部,且可通过类名访问 |
生命周期 | 代码块执行期间 | 对象的生命周期 | 类的生命周期 |
存储位置 | 栈内存 | 堆内存 | 方法区(静态存储区) |
默认值 | 无,必须显式初始化 | 有默认值 | 有默认值 |
理解这些变量的分类及其区别有助于更好地设计和管理代码。
2.Java 数据类型分类?
基本数据类型
整数类型: byte、short、int、long
浮点类型: float、double
布尔类型: boolean
字符类型: char
注: 整形数据默认为 int 数据类型,浮点型默认为 double 数据类型;如果要表示
long 型数据或 float 型数据,要在相应的数值后面加上 l、L 或 f、F,否则会出现编译问题。
引用数据类型:类、接口、数组、枚举
Java 中的数据类型可以分为两大类:基本数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)。以下是详细的分类和说明:
1. 基本数据类型(Primitive Data Types)
基本数据类型是 Java 内置的简单数据类型,直接存储数据值,而不是对象的引用。它们分为以下四类:
(1)整数类型
-
byte
- 大小:1 字节(8 位)
- 范围:-128 到 127
- 默认值:0
- 用途:节省内存,适用于小范围整数。
-
short
- 大小:2 字节(16 位)
- 范围:-32,768 到 32,767
- 默认值:0
- 用途:比
byte
范围大,但仍节省内存。
-
int
- 大小:4 字节(32 位)
- 范围:-2^31 到 2^31-1(约 -21 亿到 21 亿)
- 默认值:0
- 用途:最常用的整数类型。
-
long
- 大小:8 字节(64 位)
- 范围:-2^63 到 2^63-1
- 默认值:0L
- 用途:用于大范围整数,需在字面量后加
L
或l
。
(2)浮点类型
-
float
- 大小:4 字节(32 位)
- 范围:约 ±3.40282347E+38F(6-7 位有效数字)
- 默认值:0.0f
- 用途:单精度浮点数,需在字面量后加
F
或f
。
-
double
- 大小:8 字节(64 位)
- 范围:约 ±1.79769313486231570E+308(15 位有效数字)
- 默认值:0.0d
- 用途:双精度浮点数,默认的浮点类型。
(3)字符类型
- char
- 大小:2 字节(16 位)
- 范围:0 到 65,535(Unicode 字符)
- 默认值:
'\u0000'
- 用途:存储单个字符。
(4)布尔类型
- boolean
- 大小:无明确大小(通常 1 位)
- 范围:
true
或false
- 默认值:
false
- 用途:表示逻辑值。
2. 引用数据类型(Reference Data Types)
引用数据类型存储的是对象的引用(内存地址),而不是实际的数据值。它们包括:
(1)类(Class)
- 例如:
String
、Integer
等。 - 默认值:
null
(2)接口(Interface)
- 例如:
List
、Map
等。 - 默认值:
null
(3)数组(Array)
- 例如:
int[]
、String[]
等。 - 默认值:
null
(4)枚举(Enum)
- 例如:自定义枚举类型。
- 默认值:
null
基本数据类型 vs 引用数据类型
特性 | 基本数据类型 | 引用数据类型 |
---|---|---|
存储内容 | 实际数据值 | 对象的引用(内存地址) |
内存分配 | 栈内存 | 堆内存 |
默认值 | 有默认值(如 int 为 0 ) | null |
性能 | 访问速度快 | 访问速度较慢(需要间接访问) |
示例 | int a = 10; | String s = "Hello"; |
数据类型总结
- 基本数据类型:直接存储数据值,效率高,但功能有限。
- 引用数据类型:存储对象的引用,功能强大,支持复杂操作。
在实际开发中,根据需求选择合适的数据类型可以提高程序的性能和可读性。
3.八种基本数据类型的取值范围?
Java 中的八种基本数据类型及其取值范围如下:
1. 整数类型
数据类型 | 大小(字节) | 位数 | 取值范围 | 默认值 |
---|---|---|---|---|
byte | 1 | 8 | -128 到 127 | 0 |
short | 2 | 16 | -32,768 到 32,767 | 0 |
int | 4 | 32 | -2^31 到 2^31-1(约 -21 亿到 21 亿) | 0 |
long | 8 | 64 | -2^63 到 2^63-1 | 0L |
2. 浮点类型
数据类型 | 大小(字节) | 位数 | 取值范围 | 默认值 |
---|---|---|---|---|
float | 4 | 32 | 约 ±3.40282347E+38F(6-7 位有效数字) | 0.0f |
double | 8 | 64 | 约 ±1.79769313486231570E+308(15 位有效数字) | 0.0d |
3. 字符类型
数据类型 | 大小(字节) | 位数 | 取值范围 | 默认值 |
---|---|---|---|---|
char | 2 | 16 | 0 到 65,535(Unicode 字符) | '\u0000' |
4. 布尔类型
数据类型 | 大小(字节) | 位数 | 取值范围 | 默认值 |
---|---|---|---|---|
boolean | 无明确大小 | 1 | true 或 false | false |
详细说明
-
整数类型:
byte
:适用于小范围整数,节省内存。short
:比byte
范围大,但仍节省内存。int
:最常用的整数类型。long
:用于大范围整数,需在字面量后加L
或l
。
-
浮点类型:
float
:单精度浮点数,需在字面量后加F
或f
。double
:双精度浮点数,默认的浮点类型。
-
字符类型:
char
:存储单个 Unicode 字符。
-
布尔类型:
boolean
:表示逻辑值,只有true
或false
。
示例代码
public class DataTypesRange {
public static void main(String[] args) {
// 整数类型
byte byteVar = 127;
short shortVar = 32767;
int intVar = 2147483647;
long longVar = 9223372036854775807L;
// 浮点类型
float floatVar = 3.4028235E38f;
double doubleVar = 1.7976931348623157E308;
// 字符类型
char charVar = 'A';
// 布尔类型
boolean booleanVar = true;
System.out.println("byte: " + byteVar);
System.out.println("short: " + shortVar);
System.out.println("int: " + intVar);
System.out.println("long: " + longVar);
System.out.println("float: " + floatVar);
System.out.println("double: " + doubleVar);
System.out.println("char: " + charVar);
System.out.println("boolean: " + booleanVar);
}
}
注意事项
- 超出数据类型的取值范围会导致溢出或编译错误。
- 浮点类型有精度限制,可能存在精度丢失问题。
char
类型使用单引号('
),而字符串使用双引号("
)。
理解这些数据类型的取值范围有助于编写高效、健壮的代码。
4.char 类型的变量能存放汉字吗?
可以,因为 java 采用 Unicode 编码
是的,char
类型的变量可以存放汉字。
原因
-
Unicode 编码:
- Java 中的
char
类型使用 Unicode 编码,占 2 个字节(16 位)。 - Unicode 编码支持全球范围内的字符,包括汉字、英文字母、符号等。
- 汉字的 Unicode 编码范围是
\u4E00
到\u9FA5
(常用汉字),以及其他扩展范围的汉字。
- Java 中的
-
汉字在 Unicode 中的表示:
- 每个汉字在 Unicode 中占用 2 个字节,因此可以被
char
类型存储。
- 每个汉字在 Unicode 中占用 2 个字节,因此可以被
示例代码
public class CharExample {
public static void main(String[] args) {
char ch = '汉'; // 存储一个汉字
System.out.println("char 变量 ch 的值: " + ch);
// 输出汉字的 Unicode 编码
System.out.println("汉字 '汉' 的 Unicode 编码: " + (int) ch);
}
}
输出:
char 变量 ch 的值: 汉
汉字 '汉' 的 Unicode 编码: 27721
注意事项
-
单个
char
只能存储一个字符:char
类型只能存储 一个字符,无论是汉字、字母还是符号。- 如果需要存储多个字符(如字符串),应使用
String
类型。
-
char
的范围:char
的范围是0
到65,535
(即\u0000
到\uffff
)。- 如果字符的 Unicode 编码超出此范围(如部分特殊符号或扩展汉字),则无法用
char
存储。
-
字符串与字符的区别:
- 字符用单引号(
'
)表示,例如'A'
或'汉'
。 - 字符串用双引号(
"
)表示,例如"Hello"
或"汉字"
。
- 字符用单引号(
总结
char
类型可以存储汉字,因为汉字在 Unicode 编码中占用 2 个字节,而char
也是 2 个字节。- 如果需要处理多个汉字或字符串,应使用
String
类型。
5.Java 数据类型转换?
自动类型转换: 容量小的类型自动转换为容量大的数据类型。它们的顺序是: byte、
short(char)、int、long、float、double
强制类型转换: 将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换
符(()),但可能造成精度降低或溢出,格外要注意。
byte、short、char 类型参与运算,直接转换为 int 类型参与运算。
大类型和小类型进行运算,得到的肯定是大类型。
在 Java 中,数据类型转换分为两种:自动类型转换(隐式转换)和强制类型转换(显式转换)。以下是详细的说明和示例:
1. 自动类型转换(隐式转换)
自动类型转换是指将取值范围小的数据类型自动转换为取值范围大的数据类型。这种转换由编译器自动完成,无需显式操作。
规则:
- 转换方向:从小类型到大类型。
- 不会导致数据丢失。
转换顺序:
byte -> short -> int -> long -> float -> double
char -> int -> long -> float -> double
示例:
public class AutoConversion {
public static void main(String[] args) {
int a = 10;
long b = a; // 自动将 int 转换为 long
float c = b; // 自动将 long 转换为 float
double d = c; // 自动将 float 转换为 double
System.out.println("a: " + a);
System.out.println("b: " + b);
System.out.println("c: " + c);
System.out.println("d: " + d);
}
}
输出:
a: 10
b: 10
c: 10.0
d: 10.0
2. 强制类型转换(显式转换)
强制类型转换是指将取值范围大的数据类型转换为取值范围小的数据类型。这种转换需要显式操作,可能会导致数据丢失或精度损失。
语法:
目标类型 变量名 = (目标类型) 源变量;
示例:
public class ForceConversion {
public static void main(String[] args) {
double a = 10.5;
int b = (int) a; // 强制将 double 转换为 int,丢失小数部分
System.out.println("a: " + a);
System.out.println("b: " + b);
}
}
输出:
a: 10.5
b: 10
3. 注意事项
-
精度损失:
- 当从浮点类型(如
double
、float
)强制转换为整数类型(如int
、long
)时,小数部分会被截断。 - 示例:
double d = 10.9; int i = (int) d; // i 的值为 10
- 当从浮点类型(如
-
数据溢出:
- 当从大范围类型强制转换为小范围类型时,可能会导致数据溢出。
- 示例:
int a = 300; byte b = (byte) a; // b 的值为 44(溢出)
-
char
与int
的转换:char
类型可以自动转换为int
类型(基于 Unicode 编码)。int
类型强制转换为char
类型时,需要确保值在0
到65,535
之间。- 示例:
char c = 'A'; int i = c; // i 的值为 65('A' 的 Unicode 编码) char c2 = (char) i; // c2 的值为 'A'
-
布尔类型不能转换:
boolean
类型不能与其他任何类型进行转换。
4. 字符串与基本数据类型的转换
(1)字符串转基本类型
使用包装类的 parseXxx()
方法。
- 示例:
String s = "123"; int i = Integer.parseInt(s); // 字符串转 int double d = Double.parseDouble(s); // 字符串转 double
(2)基本类型转字符串
使用 String.valueOf()
或直接拼接空字符串。
- 示例:
int i = 123; String s1 = String.valueOf(i); // int 转字符串 String s2 = i + ""; // 直接拼接空字符串
5. 总结
转换类型 | 方向 | 是否需要显式操作 | 可能导致的问题 |
---|---|---|---|
自动类型转换 | 小类型 -> 大类型 | 否 | 无 |
强制类型转换 | 大类型 -> 小类型 | 是 | 数据丢失、精度损失 |
字符串与基本类型 | 相互转换 | 是 | 格式错误时抛出异常 |
在实际开发中,应根据需求选择合适的转换方式,并注意可能的数据丢失或溢出问题。
6…0 可以不可以作为除数?
0 和整数类型进行除法运算,不能作为除数,否则抛出异常;
0 和浮点类型进行除法运算,可以作为除数,得到无穷大的值;
两个绝对值为 0.0 的数进行除法运算,得到的是 NaN
7.++/–在前和在后的区别?
++ 递增运算,在原有值的基础上+1
– 递减运算,在原有值的基础上-1
如果++/–是一行单独的运算,那么在前和在后没有区别;
如果++/–参与运算或调用,那么在前是先递增/递减运算再调用值,在后是先调用值再
递增/递减运算。
在编程中,++
和 --
是自增和自减运算符,它们可以放在变量前面(前缀)或后面(后缀),具体位置会影响表达式的行为。
1. 前缀形式 (++i
或 --i
)
- 自增 (
++i
): 先将变量i
的值加 1,然后返回增加后的值。 - 自减 (
--i
): 先将变量i
的值减 1,然后返回减少后的值。
示例:
int i = 5;
int j = ++i; // i 先加 1 变为 6,然后 j 被赋值为 6
// 现在 i = 6, j = 6
2. 后缀形式 (i++
或 i--
)
- 自增 (
i++
): 先返回变量i
的当前值,然后再将i
的值加 1。 - 自减 (
i--
): 先返回变量i
的当前值,然后再将i
的值减 1。
示例:
int i = 5;
int j = i++; // j 被赋值为 5,然后 i 加 1 变为 6
// 现在 i = 6, j = 5
总结
- 前缀形式:先增减,后使用。
- 后缀形式:先使用,后增减。
应用场景
- 前缀形式:当你需要立即使用增减后的值时。
- 后缀形式:当你需要先使用当前值,再进行增减时。
注意:在循环或复杂表达式中,前缀形式通常效率更高,因为它避免了创建临时变量。
8.用最有效的方式计算 2 乘以 8?
2 << 3;
用最有效的方式计算 (2 \times 8),可以直接利用位移运算(bitwise shift),因为计算机底层处理位移运算的效率非常高。
计算方式:
(2 \times 8) 可以转化为 (2 \times 2^3),即 (2) 左移 (3) 位。
代码实现(Java):
int result = 2 << 3; // 2 左移 3 位
System.out.println(result); // 输出 16
解释:
- 位移运算
<<
是将二进制数向左移动指定位数,右边补零。 - (2) 的二进制表示为
10
,左移 (3) 位后变为10000
,即 (16)。
优点:
- 高效:位移运算是计算机底层操作,速度极快。
- 简洁:代码简洁易懂。
总结:
用位移运算 (2 << 3) 是计算 (2 \times 8) 的最有效方式。
9.& 和 && 的区别?
& 按位与运算符,&& 逻辑与运算符;两者都可以进行逻辑与运算;
& 不具备短路功能;&&具备短路功能,一旦表达式出现了 false,则之后的表达式都不再
运行;
在 Java 中,&
和 &&
都是逻辑运算符,但它们的行为和用途有显著区别。以下是它们的详细对比:
1. &
(按位与 / 逻辑与)
- 作用:
- 当用于布尔表达式时,
&
是逻辑与运算符。 - 当用于整数类型(如
int
、long
)时,&
是按位与运算符。
- 当用于布尔表达式时,
- 特点:
- 逻辑与:
&
会对左右两边的表达式都进行求值,即使左边表达式为false
,右边表达式仍然会被计算。 - 按位与:对两个数的二进制位逐位进行与运算。
- 逻辑与:
- 示例:
boolean a = false; boolean b = true; boolean result = a & b; // 结果为 false,但 b 仍然会被计算 int x = 5; // 二进制: 0101 int y = 3; // 二进制: 0011 int z = x & y; // 按位与结果: 0001 (十进制 1)
2. &&
(短路与)
- 作用:
&&
是短路与运算符,仅用于布尔表达式。
- 特点:
- 如果左边表达式为
false
,则右边表达式不会被执行(短路行为)。 - 这种短路特性可以提高效率,尤其是在右边表达式计算成本较高的情况下。
- 如果左边表达式为
- 示例:
boolean a = false; boolean b = true; boolean result = a && b; // 结果为 false,b 不会被计算
3. 关键区别
特性 | & | && |
---|---|---|
类型 | 逻辑与 / 按位与 | 仅逻辑与 |
短路行为 | 无短路,两边都会计算 | 有短路,左边为 false 时不计算右边 |
适用场景 | 布尔表达式、整数按位运算 | 仅布尔表达式 |
效率 | 较低(总是计算两边) | 较高(可能跳过右边计算) |
4. 使用场景
&
:- 当你需要确保两边表达式都被计算时(例如,两边表达式有副作用)。
- 用于整数类型的按位与运算。
&&
:- 当你想利用短路行为提高效率时。
- 用于布尔表达式的逻辑判断。
5. 示例对比
public class Main {
public static void main(String[] args) {
boolean a = false;
boolean b = true;
// 使用 &,b 会被计算
boolean result1 = a & (b = false);
System.out.println("result1: " + result1); // false
System.out.println("b: " + b); // false
// 使用 &&,b 不会被计算
boolean result2 = a && (b = false);
System.out.println("result2: " + result2); // false
System.out.println("b: " + b); // true(因为 b = false 未执行)
}
}
总结
- 如果你需要短路行为(避免不必要的计算),使用
&&
。 - 如果你需要确保两边表达式都被计算,或者进行按位运算,使用
&
。
10.| 和 || 的区别?
| 按位或运算符,|| 逻辑或运算符;两者都可以进行逻辑或运算;
| 不具备短路功能,||具备短路功能,一旦表达式出现了 true,则之后的表达式都不再
运行;
在 Java 中,|
和 ||
都是逻辑运算符,但它们的行为和用途有显著区别。以下是它们的详细对比:
1. |
(按位或 / 逻辑或)
- 作用:
- 当用于布尔表达式时,
|
是逻辑或运算符。 - 当用于整数类型(如
int
、long
)时,|
是按位或运算符。
- 当用于布尔表达式时,
- 特点:
- 逻辑或:
|
会对左右两边的表达式都进行求值,即使左边表达式为true
,右边表达式仍然会被计算。 - 按位或:对两个数的二进制位逐位进行或运算。
- 逻辑或:
- 示例:
boolean a = true; boolean b = false; boolean result = a | b; // 结果为 true,但 b 仍然会被计算 int x = 5; // 二进制: 0101 int y = 3; // 二进制: 0011 int z = x | y; // 按位或结果: 0111 (十进制 7)
2. ||
(短路或)
- 作用:
||
是短路或运算符,仅用于布尔表达式。
- 特点:
- 如果左边表达式为
true
,则右边表达式不会被执行(短路行为)。 - 这种短路特性可以提高效率,尤其是在右边表达式计算成本较高的情况下。
- 如果左边表达式为
- 示例:
boolean a = true; boolean b = false; boolean result = a || b; // 结果为 true,b 不会被计算
3. 关键区别
| 特性 | |
| ||
|
| ------------ | ------------------------ | ---------------------------------- |
| 类型 | 逻辑或 / 按位或 | 仅逻辑或 |
| 短路行为 | 无短路,两边都会计算 | 有短路,左边为 true
时不计算右边 |
| 适用场景 | 布尔表达式、整数按位运算 | 仅布尔表达式 |
| 效率 | 较低(总是计算两边) | 较高(可能跳过右边计算) |
4. 使用场景
|
:- 当你需要确保两边表达式都被计算时(例如,两边表达式有副作用)。
- 用于整数类型的按位或运算。
||
:- 当你想利用短路行为提高效率时。
- 用于布尔表达式的逻辑判断。
5. 示例对比
public class Main {
public static void main(String[] args) {
boolean a = true;
boolean b = false;
// 使用 |,b 会被计算
boolean result1 = a | (b = true);
System.out.println("result1: " + result1); // true
System.out.println("b: " + b); // true
// 使用 ||,b 不会被计算
boolean result2 = a || (b = true);
System.out.println("result2: " + result2); // true
System.out.println("b: " + b); // false(因为 b = true 未执行)
}
}
总结
- 如果你需要短路行为(避免不必要的计算),使用
||
。 - 如果你需要确保两边表达式都被计算,或者进行按位运算,使用
|
。
11.short s = 10; s = s + 1 的结果是什么? s += 1 结果是什么,为什么?
s = s + 1 的结果是报错,short 在进行运算的时候,转换成 int 类型运算,int 类型给
short 类型赋值,需要强转;
s = s + 1 写法应该改为 s = (short)(s + 1);
s += 1 正常运行,s = 11;因为+=不会改变数据类型;
在 Java 中,short s = 10; s = s + 1
和 s += 1
的行为是不同的,具体原因与 Java 的类型转换规则有关。以下是详细分析:
1. s = s + 1
的结果
代码:
short s = 10;
s = s + 1; // 编译错误
原因:
s + 1
中的s
是short
类型,而1
是int
类型。- 在 Java 中,当
short
和int
进行运算时,short
会自动提升为int
,因此s + 1
的结果是int
类型。 - 将
int
类型的结果赋值给short
类型的变量s
时,会导致编译错误,因为需要显式的类型转换。
解决方法:
需要显式地将结果强制转换为 short
:
s = (short) (s + 1); // 正确
2. s += 1
的结果
代码:
short s = 10;
s += 1; // 正确,s 的值为 11
原因:
+=
是一个复合赋值运算符,它会自动进行隐式类型转换。- 在
s += 1
中,1
是int
类型,但+=
会自动将结果转换回short
类型,因此不会导致编译错误。 - 这相当于:
s = (short) (s + 1);
3. 总结对比
表达式 | 行为 | 结果 |
---|---|---|
s = s + 1 | s + 1 的结果是 int 类型,不能直接赋值给 short ,需要强制类型转换 | 编译错误 |
s += 1 | 复合赋值运算符会自动进行类型转换 | 正确,s 变为 11 |
4. 示例代码
public class Main {
public static void main(String[] args) {
short s = 10;
// s = s + 1; // 编译错误
s = (short) (s + 1); // 正确,s 变为 11
s += 1; // 正确,s 变为 12
System.out.println(s); // 输出 12
}
}
5. 关键点
- 类型提升:
short
与int
运算时,short
会提升为int
。 - 复合赋值运算符:
+=
会自动处理类型转换,避免编译错误。 - 显式类型转换:在需要时,可以使用
(short)
进行强制类型转换。
最终答案:
s = s + 1
会导致编译错误,因为需要显式类型转换。s += 1
是合法的,s
的值会变为11
,因为+=
会自动进行类型转换。
12.instanceof 运算符的作用?
instanceof 是 Java 的保留关键字。
作用是:测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
instanceof 是 Java 中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所
创建对象时,返回 true;否则,返回 false。
instanceof
是 Java 中的一个二元运算符,用于检查一个对象是否是指定类(或其子类、实现类)的实例。它的主要作用是类型检查,通常在需要判断对象的实际类型时使用。
1. 语法
对象 instanceof 类型
- 对象:需要检查的对象。
- 类型:类、接口或数组类型。
2. 返回值
- 如果对象是指定类型或其子类/实现类的实例,返回
true
。 - 否则返回
false
。 - 如果对象为
null
,返回false
。
3. 使用场景
(1)类型检查
在需要判断对象的实际类型时使用。例如:
Object obj = "Hello";
if (obj instanceof String) {
System.out.println("obj 是 String 类型");
}
(2)类型转换前的安全检查
在进行类型转换之前,通常会用 instanceof
检查对象类型,避免 ClassCastException
异常。例如:
Object obj = "Hello";
if (obj instanceof String) {
String str = (String) obj; // 安全转换
System.out.println(str.toUpperCase());
}
(3)多态场景
在多态场景中,父类引用指向子类对象时,可以用 instanceof
判断具体子类类型。例如:
class Animal {}
class Dog extends Animal {}
Animal animal = new Dog();
if (animal instanceof Dog) {
System.out.println("animal 是 Dog 类型");
}
(4)接口实现检查
检查对象是否实现了某个接口。例如:
interface Runnable {}
class Task implements Runnable {}
Runnable task = new Task();
if (task instanceof Runnable) {
System.out.println("task 实现了 Runnable 接口");
}
4. 注意事项
- 不能用于基本数据类型:
instanceof
只能用于对象类型,不能用于基本数据类型(如int
、boolean
等)。 - 继承关系:如果对象是指定类型的子类实例,
instanceof
也会返回true
。 - null 检查:如果对象为
null
,instanceof
会返回false
,不会抛出异常。
5. 示例代码
public class Main {
public static void main(String[] args) {
Object obj1 = "Hello";
Object obj2 = 123;
Object obj3 = null;
// 检查 obj1 是否是 String 类型
System.out.println(obj1 instanceof String); // true
// 检查 obj2 是否是 Integer 类型
System.out.println(obj2 instanceof Integer); // true
// 检查 obj3 是否是 String 类型
System.out.println(obj3 instanceof String); // false
// 多态场景
Animal animal = new Dog();
System.out.println(animal instanceof Dog); // true
System.out.println(animal instanceof Animal); // true
}
}
class Animal {}
class Dog extends Animal {}
6. 总结
instanceof
用于检查对象是否是指定类型或其子类/实现类的实例。- 常用于类型检查、类型转换前的安全检查、多态场景和接口实现检查。
- 如果对象为
null
,instanceof
会返回false
。
13.Java 的 switch 语句可以定义什么类型的值?
byte,short,char,int,String,枚举 enum 其中的一种。
注:
1、枚举是 jdk1.5 之后支持的,String 是 jdk1.7 之后才支持的;
2、因为有自动拆箱,switch 也可作用于 byte、short、char、int 的包装类上。
在 Java 中,switch
语句可以用于多种类型的值。随着 Java 版本的更新,switch
支持的类型逐渐扩展。以下是 switch
语句支持的类型:
1. Java 7 之前
在 Java 7 之前,switch
语句仅支持以下类型:
int
byte
short
char
- 枚举类型(enum)
示例:
int num = 2;
switch (num) {
case 1:
System.out.println("One");
break;
case 2:
System.out.println("Two");
break;
default:
System.out.println("Other");
}
2. Java 7 新增
从 Java 7 开始,switch
语句支持 String
类型。
示例:
String day = "Monday";
switch (day) {
case "Monday":
System.out.println("Start of the week");
break;
case "Friday":
System.out.println("End of the week");
break;
default:
System.out.println("Other day");
}
3. Java 12 新增(预览特性)
从 Java 12 开始,switch
语句支持表达式语法(使用 ->
箭头符号),并且可以返回值。
示例:
int num = 2;
String result = switch (num) {
case 1 -> "One";
case 2 -> "Two";
default -> "Other";
};
System.out.println(result); // 输出 "Two"
4. Java 14 正式支持
从 Java 14 开始,switch
表达式语法成为正式特性。
示例:
int num = 2;
String result = switch (num) {
case 1 -> "One";
case 2 -> "Two";
default -> "Other";
};
System.out.println(result); // 输出 "Two"
5. Java 17 新增(预览特性)
从 Java 17 开始,switch
支持模式匹配(Pattern Matching for switch
),可以更灵活地匹配类型和值。
示例:
Object obj = "Hello";
String result = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
default -> "Other";
};
System.out.println(result); // 输出 "String: Hello"
6. 总结
switch
语句支持的类型包括:
- 基本类型:
int
byte
short
char
- 引用类型:
String
- 枚举类型(enum)
- 未来特性:
- 模式匹配(Java 17 预览特性)
注意事项:
switch
不支持long
、float
、double
和boolean
类型。case
标签必须是常量表达式,且不能重复。- 从 Java 12 开始,
switch
可以作为表达式返回值,简化代码逻辑。
14.while 和 do while 的区别?
两者都是用来实现循环结构的;
while 是先判断后执行; do while 是先执行一次后判断;
如果初始条件成立,那么 while 和 do while 的执行结果一致;
如果初始条件不成立,那么 while 什么都不执行,do while 至少执行一次;
while
和 do-while
是 Java 中的两种循环结构,它们的主要区别在于循环条件的检查时机。以下是它们的详细对比:
1. while
循环
- 语法:
while (条件) { // 循环体 }
- 执行顺序:
- 先检查条件是否为
true
。 - 如果条件为
true
,执行循环体。 - 重复上述步骤,直到条件为
false
。
- 先检查条件是否为
- 特点:
- 如果条件一开始就是
false
,循环体不会执行。 - 适用于条件先检查的场景。
- 如果条件一开始就是
示例:
int i = 0;
while (i < 3) {
System.out.println("i = " + i);
i++;
}
输出:
i = 0
i = 1
i = 2
2. do-while
循环
- 语法:
do { // 循环体 } while (条件);
- 执行顺序:
- 先执行循环体。
- 然后检查条件是否为
true
。 - 如果条件为
true
,重复执行循环体。 - 直到条件为
false
,循环结束。
- 特点:
- 即使条件一开始就是
false
,循环体至少会执行一次。 - 适用于至少执行一次循环体的场景。
- 即使条件一开始就是
示例:
int i = 0;
do {
System.out.println("i = " + i);
i++;
} while (i < 3);
输出:
i = 0
i = 1
i = 2
3. 关键区别
特性 | while 循环 | do-while 循环 |
---|---|---|
条件检查时机 | 先检查条件,再执行循环体 | 先执行循环体,再检查条件 |
循环体执行次数 | 可能为 0 次(条件为 false ) | 至少执行 1 次 |
适用场景 | 条件先检查的场景 | 至少执行一次循环体的场景 |
4. 示例对比
场景:条件一开始为 false
int i = 5;
// while 循环
while (i < 3) {
System.out.println("while: i = " + i);
i++;
}
// 输出:无(循环体未执行)
// do-while 循环
do {
System.out.println("do-while: i = " + i);
i++;
} while (i < 3);
// 输出:do-while: i = 5(循环体执行一次)
5. 总结
- 如果希望先检查条件,再决定是否执行循环体,使用
while
。 - 如果希望至少执行一次循环体,再检查条件,使用
do-while
。
根据具体需求选择合适的循环结构,可以提高代码的可读性和效率。
15.break 和 continue 的区别?
使用场合不同:
break 可用于 switch 结构和循环结构中;
continue 只能用于循环结构中;
return 只能用于方法中;
作用不同
break 语句终止某个循环,程序跳转到循环块外的下一条语句。
continue 跳出本次循环,进入下一次循环
return 用来定义方法返回值或结束方法
break
和 continue
是 Java 中用于控制循环流程的关键字,但它们的作用和行为有显著区别。以下是它们的详细对比:
1. break
- 作用:
- 用于立即终止循环(
for
、while
、do-while
)或switch
语句。 - 执行
break
后,程序会跳出当前循环或switch
语句,继续执行循环或switch
之后的代码。
- 用于立即终止循环(
- 使用场景:
- 当满足某个条件时,提前结束循环。
- 在
switch
语句中,用于跳出switch
块。
示例:
for (int i = 0; i < 5; i++) {
if (i == 3) {
break; // 当 i 等于 3 时,终止循环
}
System.out.println("i = " + i);
}
System.out.println("循环结束");
输出:
i = 0
i = 1
i = 2
循环结束
2. continue
- 作用:
- 用于跳过当前循环的剩余代码,直接进入下一次循环。
- 执行
continue
后,程序会跳过本次循环中continue
之后的代码,继续执行下一次循环。
- 使用场景:
- 当满足某个条件时,跳过本次循环的剩余部分,继续下一次循环。
示例:
for (int i = 0; i < 5; i++) {
if (i == 3) {
continue; // 当 i 等于 3 时,跳过本次循环
}
System.out.println("i = " + i);
}
System.out.println("循环结束");
输出:
i = 0
i = 1
i = 2
i = 4
循环结束
3. 关键区别
特性 | break | continue |
---|---|---|
作用 | 终止整个循环或 switch 语句 | 跳过当前循环的剩余代码,继续下一次循环 |
循环行为 | 完全退出循环 | 仅跳过本次循环,继续下一次循环 |
适用场景 | 提前结束循环或 switch 语句 | 跳过某些特定条件的循环迭代 |
4. 示例对比
场景:遍历数组,找到第一个负数并停止
int[] numbers = {1, 2, -3, 4, -5};
// 使用 break
for (int num : numbers) {
if (num < 0) {
System.out.println("找到第一个负数: " + num);
break; // 找到后立即终止循环
}
}
// 使用 continue
for (int num : numbers) {
if (num >= 0) {
continue; // 跳过非负数
}
System.out.println("处理负数: " + num);
}
输出:
找到第一个负数: -3
处理负数: -3
处理负数: -5
5. 总结
break
:用于完全终止循环或switch
语句。continue
:用于跳过当前循环的剩余代码,继续下一次循环。
根据具体需求选择合适的关键字,可以更灵活地控制程序流程。
16.数组元素/成员变量的默认值?
在编程中,数组元素或成员变量的默认值取决于所使用的编程语言和数据类型。以下是一些常见编程语言中的默认值:
1. Java
- 基本数据类型:
int
:0
long
:0L
float
:0.0f
double
:0.0d
char
:\u0000
(空字符)boolean
:false
- 引用类型(如对象、数组):
null
2. C++
- 全局或静态变量:
int
:0
float
:0.0f
double
:0.0
char
:\0
(空字符)bool
:false
- 指针:
nullptr
- 局部变量:未初始化,值不确定。
3. C#
- 基本数据类型:
int
:0
float
:0.0f
double
:0.0
char
:\0
(空字符)bool
:false
- 引用类型:
null
4. Python
- Python 中的变量不需要声明类型,且没有默认值。未初始化的变量会引发
NameError
。 - 列表、字典等容器类型的元素默认值为
None
。
5. JavaScript
- 未声明的变量:
undefined
- 数组元素:未显式赋值的元素为
undefined
6. Go
- 基本数据类型:
int
:0
float64
:0.0
string
:""
(空字符串)bool
:false
- 引用类型(如切片、映射、指针):
nil
7. Rust
- 基本数据类型:
i32
:0
f64
:0.0
bool
:false
char
:'\0'
(空字符)
- 引用类型:需要显式初始化,否则会编译错误。
8. Swift
- 基本数据类型:
Int
:0
Double
:0.0
Bool
:false
String
:""
(空字符串)
- 可选类型:默认值为
nil
总结
- 基本数据类型通常有明确的默认值(如
0
、false
等)。 - 引用类型(如对象、数组、指针)通常默认为
null
或nil
。 - 局部变量在某些语言(如 C++)中未初始化时值不确定。
具体行为请参考相应语言的文档。
17.一维数组和二维数组内存分析?
二维数组的创建与初始化和一维数组类似,初始化分为完全初始化和不完全初始化。
一维数组在内存中的存储是连续的,相差4的原因是每个整型元素是占4个字节的,而一个地址只能存储一个字节,所以存储一个整形元素在内存中需要4个地址。
二维数组在内存中的存储也是连续的。
在计算机内存中,数组的存储方式与其维度密切相关。以下是对一维数组和二维数组的内存分析:
一维数组的内存分析
一维数组是最简单的数组形式,它在内存中是连续存储的。假设有一个一维数组 int arr[N]
,其中 N
是数组的长度。
- 内存布局:数组的元素在内存中是按顺序连续存储的。例如,
arr[0]
存储在第一个内存位置,arr[1]
存储在第二个内存位置,依此类推。 - 内存地址计算:假设数组的起始地址为
base_address
,每个元素占用的内存大小为sizeof(int)
,那么第i
个元素的地址可以通过以下公式计算:address_of_arr[i] = base_address + i * sizeof(int)
- 访问时间:由于内存是连续存储的,访问任意元素的时间复杂度为 O(1)。
二维数组的内存分析
二维数组可以看作是一个数组的数组。假设有一个二维数组 int arr[M][N]
,其中 M
是行数,N
是列数。
- 内存布局:二维数组在内存中也是连续存储的,通常采用行优先存储(Row-major order)或列优先存储(Column-major order)。大多数编程语言(如C、C++、Java)采用行优先存储。
- 行优先存储:先存储第一行的所有元素,然后是第二行的所有元素,依此类推。
- 列优先存储:先存储第一列的所有元素,然后是第二列的所有元素,依此类推。
- 内存地址计算:
- 行优先存储:假设数组的起始地址为
base_address
,每个元素占用的内存大小为sizeof(int)
,那么第i
行第j
列的元素地址可以通过以下公式计算:address_of_arr[i][j] = base_address + (i * N + j) * sizeof(int)
- 列优先存储:第
i
行第j
列的元素地址可以通过以下公式计算:address_of_arr[i][j] = base_address + (j * M + i) * sizeof(int)
- 行优先存储:假设数组的起始地址为
- 访问时间:与一维数组类似,由于内存是连续存储的,访问任意元素的时间复杂度为 O(1)。
总结
- 一维数组:元素在内存中连续存储,访问任意元素的时间复杂度为 O(1)。
- 二维数组:元素在内存中也是连续存储的,通常采用行优先存储或列优先存储。访问任意元素的时间复杂度为 O(1)。
理解数组的内存布局对于优化程序性能(尤其是涉及大量数据处理的程序)非常重要,因为它可以帮助你更好地利用缓存和内存带宽。
18.类的组成部分(类的成员)?
成员变量/属性/实例变量/域/字段
成员方法/方法/实例方法/函数
构造方法/构造器/构造函数
代码块
内部类