一、标识符与命名规范
- 标识符规则
组成:字母、数字、下划线
_、美元符$,且不能以数字开头。区分大小写,且不能是关键字(如
class、public)或保留字(如goto)。命名规范:驼峰命名法(类名首字母大写,变量名首字母小写),常量全大写(如
MAX_VALUE)。
- 字面量表示
字符串用双引号
"Hello",字符用单引号'A'。布尔值:
true/false;整数默认int,小数默认double(3.14F表示float)。
二、核心数据类型
- 基本数据类型(8种)
类型 大小(字节) 默认值 范围/示例
`byte` 1 0 128~127
`short` 2 0 -32768~32767
`int` 4 0 ±21亿
`long` 8 0L 极大整数(需加`L`后缀)-922亿亿——922亿亿
`float` 4 0.0f ±3.4E38(精度低)
`double` 8 0.0d ±1.7E308(默认小数,有效位数为15位)
`char` 2 '\u0000' Unicode字符(如`'A'`)
`boolean` 1位 false true/false
- 引用数据类型
类、接口、数组:存储对象内存地址,默认值为
null。字符串
String:虽然是引用类型,但因其不可变性常被特殊处理。String类是被final关键字修饰的,即不可继承。String类具有不可变性和安全性,这些特性可以防止一些潜在的问题,如字符串池中的重用和安全性漏洞。
三、变量的声明与使用
- 声明与初始化
语法:
数据类型 变量名 = 值;(如int age = 25;)。局部变量必须显式初始化,成员变量有默认值(如
int默认为0)。-
作用域:
局部变量:在代码块内有效(如方法内部)。
成员变量:类内全局有效。
- 变量输出
使用
System.out.println()或printf()格式化输出(如%d输出整数,%.2f保留两位小数)。字符串拼接自动转换:
System.out.println("结果:" + 10);(int转为String。
四、类型转换
- 自动类型提升
规则:小范围类型→大范围类型(如
byte → int → long → float → double)。-
应用场景:
表达式运算:
byte + short → int。方法调用参数匹配:
int参数传入long方法自动提升。
- 强制类型转换
语法:
(目标类型) 值(如int i = (int) 3.14;结果为3)。风险:数据丢失(如
long转int溢出)、精度损失(double转float)。-
对象类型转换:
向上转型(父类引用指向子类对象):`Animal a = new Cat();。
向下转型(需显式且可能抛出
ClassCastException):Cat c = (Cat) a;。
五、开发者需掌握的进阶知识点
- 内存与性能优化
基本类型 vs 包装类:自动装箱/拆箱的性能损耗(如
Integer与int)。栈与堆:基本类型存栈,对象存堆;避免大对象频繁创建。
- 类型安全与设计
不可变类设计:如
String的不可变性对线程安全的影响。泛型与类型擦除:编译时类型检查,运行时类型信息丢失。
- JVM底层机制
类型转换的字节码指令(如
i2l将int转long)。NaN与无穷大:浮点数特殊值的处理(如
Double.NaN)。
六、面试官常问的深入问题
- 类型提升细节
-
为什么
byte + byte结果是int?→ JVM指令集设计仅支持
int及以上类型的运算。
- 强制转换的边界问题
-
int转byte时,如何计算实际值?→ 取补码低8位,可能产生负数(如
(byte) 128 → -128)。
- 对象类型转换的陷阱
-
如何避免
ClassCastException?→ 使用
instanceof检查后再转换。
- 类型系统的设计哲学
-
为什么Java是强类型语言?
→ 编译时严格类型检查,减少运行时错误。
七,使用方法
-变量的基本使用
- 变量声明与初始化
Java要求变量必须先声明后使用,声明格式:
数据类型 变量名 = 值;(如int age = 20;)。变量作用域:在声明它的代码块(
{})内有效,超出作用域不可访问。-
分类:
成员变量:在类体内、方法体外声明,有默认值(如
int默认0,boolean默认false)。局部变量:在方法体内声明,必须显式初始化后才能使用。
-整型数据类型的使用
- 整型分类与范围
byte(1字节,-128127)、`short`(2字节,-3276832767)、int(4字节,-21亿~21亿)、long(8字节,极大范围)。默认类型:Java整型常量默认为
int,声明long需加后缀L(如long num = 100L;)。使用场景:日常开发多用
int,超大数值用long。
-浮点类型的使用及练习
- float与double的区别
float(单精度,4字节,约6~7位有效数字)需加F后缀(如float f = 3.14F;)。double(双精度,8字节,约15位有效数字)是默认类型。精度问题:浮点运算可能存在误差(如
0.1 + 0.2 ≠ 0.3),财务计算推荐BigDecimal类。
-字符类型的使用
- char特性
16位Unicode编码,用单引号声明(如
char c = 'A';),可表示中文字符。转义字符:
\n(换行)、\t(制表符)、\\(反斜杠)等。运算:字符可参与算术运算(如
'A' + 1结果为'B')。
-布尔类型的使用
- boolean规则
仅取值
true/false,用于条件判断(如if (isActive) {...})。不可与其他类型转换(如
int转boolean会报错)。逻辑运算注意短路特性(如
&&左侧为false时右侧不执行)。
-自动类型提升规则
- 提升顺序
小类型→大类型:
byte/short/char→int→long→float→double。运算时自动提升:如
byte + int结果为int,int + double结果为double。
-强制类型转换规则
- 强制转换语法与风险
语法:
(目标类型) 值(如int i = (int) 3.14;结果为3)。可能导致精度丢失(如浮点转整型截断小数)或溢出(如
long转int高位丢失)。
-String类的基本使用
- String特性
不可变性:字符串创建后不可修改,修改操作会生成新对象。
创建方式:直接赋值(
String s = "Hello";)或new String()。-
常用方法:
length():获取长度。charAt(index):获取指定位置字符。substring(start, end):截取子串。equals():比较内容(非==)。
.进制转换
- 进制表示与转换方法
二进制转十进制:按权展开求和(如
1101→13)。十进制转二进制:除2取余法。
Java API:
Integer.toBinaryString()、Integer.parseInt("1010", 2)。
运算符详解
- 运算符分类:
算术运算符:
+、-、*、/(整数除法截断)、%(取模)。赋值运算符:
=、+=(如a += 5等价于a = a + 5)。比较运算符:
==、!=、>、<,返回布尔值。逻辑运算符:
&&(短路与)、||(短路或)、!(非)。位运算符:
&(按位与)、|(按位或)、<<(左移,等价于乘2)。条件运算符:三目表达式(
条件 ? 值1 : 值2)。
常见面试题
一、标识符相关
- Java标识符的命名规则是什么?请举例说明合法和不合法的标识符。
答案:
标识符命名规则包括:
只能由字母(A-Z,a-z)、数字(0-9)、下划线(_)、美元符号($)组成。
不能以数字开头。
不能是Java关键字(如
class、public)。区分大小写,例如
Count和count是两个不同的标识符。
示例:
合法:
myVar、$value、MAX_SIZE。不合法:
123var(数字开头)、my-var(含非法字符-)、class(关键字)。
- 标识符能否使用汉字?为什么?
答案:
可以,因为Java采用Unicode字符集,但实际开发中不建议使用汉字,主要为了代码可读性和跨语言协作的便利性。
二、变量相关
- 局部变量和成员变量在作用域及初始化上有何区别?
答案:
-
作用域:
局部变量:定义在方法或代码块内,仅在其所属的
{}内有效。成员变量:定义在类体内,整个类中可见。
-
初始化:
局部变量:必须显式初始化后才能使用(形参除外)。
成员变量:有默认值(如
int默认0,boolean默认false)。
-
final修饰变量时有哪些规则?引用不可变和对象内容可变有什么区别?
答案:
-
规则:
基本类型变量:值不可变,只能赋值一次15。
引用类型变量:引用地址不可变,但对象内部属性可以修改。
-
示例:
java
final int a = 10; // 正确
final List<String> list = new ArrayList<>();
list.add("Java"); // 合法,对象内容可变
list = new ArrayList<>(); // 编译错误,引用地址不可变
- 自动类型转换和强制类型转换的应用场景是什么?举例说明可能出现的编译错误。
答案:
自动转换(隐式):小范围类型转大范围类型(如
int→double)。强制转换(显式):需手动指定,可能丢失精度(如
double→int)。-
示例:
java
byte b1 = 3, b2 = 4;
byte b = b1 + b2; // 编译错误(运算时自动提升为int)
byte b = (byte)(b1 + b2); // 正确,强制转换
三、其他高频问题
- 基本数据类型的默认值是什么?
答案:
byte/short/int/long:0float/double:0.0char:'\u0000'boolean:false引用类型:
null。
- 如何理解
final、finally、finalize的区别?
答案:
final:修饰变量(不可变)、方法(不可重写)、类(不可继承)。finally:异常处理中确保代码块必执行(除非System.exit())。finalize:Object类方法,垃圾回收前调用(不推荐依赖)。
四、代码分析题示例
- 以下代码能否编译通过?为什么?
java
float f = 5.6; // 编译错误
float f = 5.6f; // 正确
double d = 5.6f; // 正确(自动转换)
解析:
浮点数字面值默认是double类型,直接赋值给float需显式加f。
一、变量作用域与生命周期
题目:
java
public class ScopeTest {
static int classVar = 10;
int instanceVar = 20;
void method() {
int localVar = 30;
final int finalLocal = 40;
}
}
请分析以下问题:
classVar、instanceVar、localVar的存储位置和生命周期差异?为什么局部变量必须显式初始化,而成员变量不需要?
解析与知识点:
- 存储位置:
classVar(静态变量)存储在方法区(JDK 8后位于元空间),生命周期与类加载/卸载同步110。instanceVar(实例变量)存储在堆内存中,生命周期与对象实例共存亡19。localVar(局部变量)存储在栈帧的局部变量表,生命周期随方法调用结束而销毁710。
- 初始化规则:
- 成员变量(静态/实例)会被JVM赋予默认值(如int默认0),而局部变量未初始化可能导致逻辑错误,因此强制要求显式赋值18。
二、final关键字的深度应用
题目:
java
final List<String> list = new ArrayList<>();
list.add("Java"); // 是否合法?为什么?
如果尝试将list重新指向另一个对象(如list = new ArrayList<>())会发生什么?解释final在引用类型变量中的作用。
解析与知识点:
合法操作:
final修饰引用类型变量时,仅限制引用地址不可变,对象内部状态(如集合内容)仍可修改26。重新赋值:会导致编译错误,因为
final禁止重新分配引用24。设计意义:
final常用于实现不可变对象的辅助约束(如String类),保证线程安全性和代码可读性26。
三、自动装箱与缓存机制
题目:
java
Integer a = 100, b = 100;
Integer c = 200, d = 200;
System.out.println(a == b); // 输出?
System.out.println(c == d); // 输出?
解释结果差异及背后的JVM优化机制。
解析与知识点:
输出结果:
true和false。-
缓存机制:
Java对-128~127的Integer对象使用缓存池(通过
IntegerCache实现),该范围内的赋值直接复用缓存对象,而超出范围则新建对象56。 -
开发注意点:
使用
equals()代替==进行包装类比较,避免缓存范围外的逻辑错误59。
四、类型转换与运算规则
题目:
java
byte b1 = 10, b2 = 20;
byte b3 = b1 + b2; // 编译错误?
byte b4 = 10 + 20; // 是否合法?
解释原因及Java的运算类型提升规则。
解析与知识点:
- 编译错误原因:
byte/short/char类型在运算时自动提升为int类型,b1 + b2结果为int,需强制转换(byte b3 = (byte)(b1 + b2))710。
- 常量优化机制:
10 + 20为编译期常量表达式,JVM直接计算结果并判断是否在byte范围内,无需显式转换79。
五、标识符与命名规范
题目:
给出以下代码,指出所有不合法的标识符并说明原因:
java
int 2var;
String $name;
class abstract {}
final int MAX_VALUE = 100;
解析与知识点:
-
非法标识符:
2var:数字开头违反基本规则13^6。abstract:属于Java关键字,不可作为类名34。
-
规范建议:
类名使用大驼峰(
AbstractClass),常量全大写加下划线(MAX_VALUE)14。避免使用
$符号(编译器生成代码的保留符号)36。
六、并发环境中的变量可见性
题目:
在多线程环境下,如何保证共享变量的可见性?结合volatile和final的特性说明。
解析与知识点:
- volatile:
通过内存屏障禁止指令重排序,保证写操作的可见性(如单例模式的双重检查锁)610。
- final:
在对象正确构造后,final字段的初始化值对其他线程可见(JMM的happens-before规则)26。
七、变量初始化陷阱
题目:
分析以下代码的输出并解释原因:
java
public class InitTest {
static int count;
int id;
public InitTest() {
id = count++;
}
public static void main(String args) {
System.out.println(new InitTest().id); // 输出?
System.out.println(new InitTest().id); // 输出?
}
}
解析与知识点:
输出结果:0和1。
-
静态变量特性:
count作为静态变量被所有实例共享,每次构造新对象时自增19。 -
初始化顺序:
静态变量在类加载时初始化,实例变量在对象构造时初始化18。
Java中变量和常量的区别:
- 变量是可变的并且需要先声明后赋值,变量占用内存空间且值可以改变,变量用于存储会发生变化的数据。
- 常量是不可变的并且需要再定义时进行初始化赋值,常量通常会被编译器直接替换为对应的值,不占用额外的内存空间,常量用于表示不可变的数据。
Integer缓存的理解
- 对范围默认为-128到127的整数值进行缓存,意味着当创建一个Integer对象并赋值为此范围内的整数时,会直接从缓存中返回该数字对应的Integer对象,而不会每次都创建新的对象,这种设计主要是出于性能和内存优化的考虑。由于整数在编程中经常被使用,通过缓存重用Integer对象可以减少频繁创建和销毁对象带来的开销,同时节省了内存空间。缓存的范围是可以通过参数进行调整,但这个范围是有限制的,超出范围的整数仍然会创建新的Integer对象。推荐使用.equals()方法进行值的比较。
Strings(使用字符串字面量直接复制给变量) 与 new String(使用关键词new创建一个新的String对象) 的区别
- 使用字符串字面量复制给变量是,Java会使用字符串常量池来管理字符串对象,可以提高性能和节省内存。使用new String创建的字符串对象则在堆内存中独立分配内存空间,每次调用都会创建新的对象,因此内存消耗更大。
- 使用字符串字面复制给变量的字符串是不可变的,即不能改变其内容。使用new String创建的字符串对象是可变的,可以通过调用方法或者使用赋值运算符修改其内容。
- 使用字符串字面量赋值给变量的字符串比较是,如果多个变量引用相同的字符串字面量,则实际上引用同一个对象,因此比较它们的引用时将返回true。使用new String创建的字符串对象,内容相同也是不同的对象,比较引用时将返回false。
char型变量能存贮一个中文汉字吗?
- 在Java中,
char型变量可以存储一个中文汉字,但需结合Unicode编码机制和字符范围进行具体分析。以下是详细说明:
- 基本支持:Unicode编码与存储能力
- Unicode编码:Java的
char类型基于Unicode标准,采用16位(2字节)无符号整数存储字符。Unicode的基本多语言平面(BMP,范围U+0000到U+FFFF)覆盖了绝大多数常用汉字(如“中”“爱”等),因此一个char变量可以存储一个汉字。- 示例代码:
char c = '中'; // 有效,对应Unicode码点U+4E2D
- 示例代码:
- 例外情况:代理对(Surrogate Pair)
- 补充平面字符:部分生僻汉字或特殊符号(如表情符号❤️)位于Unicode的补充平面(范围U+10000到U+10FFFF),需使用两个
char类型(高代理项和低代理项)组合表示。此时单个char无法独立存储完整字符。- 示例代码(编译报错):
char c = '\u20B37'; // 无法存储,需使用String类型处理
- 示例代码(编译报错):
- 与C语言的对比
- 内存占用差异:
- Java的
char固定为2字节,而C/C++的char通常为1字节,因此C语言无法直接存储中文(需用wchar_t或其他类型)。 - Java的
char本质是无符号整数,可直接参与数值运算(如int类型转换)。
- Java的
- 编码转换与存储效率
- 外部存储与传输:
- Java内部使用UTF-16编码,但外部存储(如文件、网络传输)可能采用UTF-8等编码。例如,UTF-8中一个中文通常占3字节,但Java内部仍用2字节
char处理。 - 字符流(如
Reader/Writer)与字节流(如InputStream/OutputStream)的转换需通过InputStreamReader或OutputStreamReader实现。
总结
- Java内部使用UTF-16编码,但外部存储(如文件、网络传输)可能采用UTF-8等编码。例如,UTF-8中一个中文通常占3字节,但Java内部仍用2字节
- 常规场景:
char可存储大部分中文汉字(BMP范围)。 - 特殊场景:需代理对的字符需用
String类型处理,避免信息丢失。 - 设计考量:Java通过Unicode统一编码解决了多语言字符存储问题,但需注意编码转换和扩展字符集的边界。
在Java中,金额处理的选择取决于具体场景对精度、性能、可读性等需求的权衡。以下是Long和BigDecimal的对比分析及推荐实践:
-
BigDecimal的优势与适用场景
核心优势
- 精度保障:
BigDecimal基于十进制运算,避免了浮点数(如double)的二进制转换误差,适合需要精确到小数点后多位的金融计算(如税费、汇率)。 - 可控性:
支持自定义舍入模式(如RoundingMode.HALF_UP四舍五入),确保计算结果符合业务规则。 - 可读性:
直接以十进制形式表示金额(如new BigDecimal("100.50")),调试和维护更直观。
适用场景 - 复杂金融计算:涉及小数点后多位(如税率、利息计算)。
- 高频精度敏感场景:如电商交易、银行系统,需避免累积误差导致的账目错误。
- 动态小数位需求:若业务要求未来可能调整小数位数(如从两位扩展到四位),
BigDecimal更灵活。
示例代码
BigDecimal amount1 = new BigDecimal("100.50");
BigDecimal amount2 = new BigDecimal("50.25");
BigDecimal quotient = amount1.divide(amount2, 2, RoundingMode.HALF_UP); // 结果精确到两位小数
-
Long的优势与适用场景
核心优势
- 性能高效:
Long是基本数据类型,运算速度快,内存占用低,适合高频计算场景(如库存扣减)。 - 简单存储:
通过固定比例因子(如乘以100表示“分”)转换为整数存储,避免浮点误差,且数据库存储更高效。
适用场景 - 简单金额场景:仅需整数或固定小数位(如仅需“分”级精度)。
- 性能敏感系统:如高频交易系统,需优化计算速度和内存占用。
- 数据库存储优化:若小数位固定(如两位),
DECIMAL(18,2)或BIGINT存储更高效。
示例代码
long amount1 = 10050; // 100.50元,单位为分
long amount2 = 5025; // 50.25元
long totalCents = amount1 + amount2; // 直接相加
String formatted = String.format("¥%.2f", totalCents / 100.0); // 转换为元
- 关键权衡与最佳实践
选择依据
- 精度优先:优先使用
BigDecimal(如金融系统)。 - 性能优先:若金额为整数或固定小数位,使用
Long(如库存管理)。 - 数据库设计:
-
BigDecimal对应数据库DECIMAL类型,需指定精度和小数位(如DECIMAL(18,2))。 -
Long对应BIGINT,需手动处理单位转换。
注意事项
-
- 避免
BigDecimal(double)构造函数:
直接使用new BigDecimal(0.1)会导致精度丢失,推荐用字符串或BigDecimal.valueOf(double)。 - 除法运算必须指定舍入模式:
未指定时会抛出ArithmeticException,需明确设置如RoundingMode.HALF_UP。
- 总结
- 推荐方案:
- 代码层:统一使用
BigDecimal,确保计算准确性。 - 数据库层:若小数位固定(如两位),存储为
Long或DECIMAL(18,2);若动态调整小数位,使用DECIMAL类型。
- 代码层:统一使用
- 例外场景:
简单金额且性能敏感时(如高频扣减),可采用Long。
通过合理选择数据类型,可在精度、性能和可维护性之间取得平衡。