[JavaSE] 02 - Java 基本语法

2.1 - 变量和常量

2.1.1 - 字面量

字面量(Literal)指的是源代码中直接写出来的、固定不变的值。

整数字面量可以使用十进制、十六进制、八进制和二进制表示。在不带 L 后缀时,其类型通常为 int;使用 L 后缀则表示 long。字面量必须符合相应类型的取值范围。

类型示例说明
十进制10、100_000默认的 int 类型,:c20[可用下划线增强可读性,但不可在首尾]。
长整型100L、100l使用 L 或 l 后缀表示 long 类型(建议用大写 L 避免与数字 1 混淆)。
十六进制0xFF、0x1A以 0x 或 0X 开头。
八进制012、0777以 0 开头(现代开发中较少使用)。
二进制0b1010、0B1111以 0b 或 0B 开头(Java 7+)。

用于表示带小数点的数字或科学计数法,默认类型为 double。对于 (0,1)(0, 1) 范围内的小数可省略 0,如 .5 表示 0.5。

类型示例说明
双精度(默认)3.14、2.5e3:c20[默认是 double 类型。]支持科学计数法(e 表示 10 的幂)。
双精度(显式)3.14D、2.5e3d使用 D 或 d 后缀显式声明为 double(非必须)。
单精度3.14F、2.5e3f使用 F 或 f 后缀声明为 float。
十六进制浮点0x1.8p1以 0x 开头,p 表示 2 的幂(此例等于 3.0)。

用于表示单个字符或文本。

类型示例说明
字符'A'、'文'使用单引号包裹单个字符,本质是 16 位无符号整数。
转义字符'\n'、'\t'、'\\'、'\''表示换行、制表符、反斜杠本身、单引号本身。
Unicode 字符'\u0041'表示 Unicode 字符(此例为 'A')。
字符串"Hello"使用双引号包裹,表示文本序列。
文本块-使用三个双引号,支持多行字符串(Java 15+)。

布尔和空字面量

类型示例说明
布尔字面量true、false表示逻辑真或假,属于 boolean 类型。
空字面量null表示空引用,只能赋值给对象引用类型(不能赋给基本数据类型如 int)。

值得注意的地方:

  1. Java 中没有无符号(unsigned)整数字面量。

  2. char 类型的字面量可以参与算术运算,例如 'A' + 1 的结果是整数 66。

  3. null 不是关键字,也不是基本类型,它是一个特殊的字面量,表示“没有对象被引用”。


2.1.2 - 变量的分类和作用域

Java 变量按声明位置分为四大类:局部变量、实例变量、静态变量和参数。其中实例变量和静态变量统称为字段(field),或成员变量。

变量类型声明位置所属关系默认值
局部变量方法、构造器或语句块等内部不属于类或对象的字段无
实例变量类中,方法外,未使用 static每个对象各有一份有
静态变量类中,使用 static属于类,通常由该类的实例共享有
参数方法、构造器等的参数列表接收调用时传入的值不适用
class Student {
    String name;              // 实例变量
    static int count = 0;     // 静态变量
 
    void study(int hours) {   // hours:参数
        int minutes = hours * 60; // 局部变量
    }
}

关于不同类型变量的作用域(Scope):

  • 局部变量:作用域仅限于定义它的那一对 {} 内部;
  • 实例变量和静态变量(字段):受访问修饰符的影响(将在面向对象部分介绍);
  • 参数:作用域仅限于方法内部。

2.1.3 - 变量的声明、初始化和赋值

一、声明、初始化和赋值

Java 是强类型语言,每个变量都有固定的数据类型。

Java 变量的使用通常包括三个操作:声明(Declaration)、初始化(Initialization)和赋值(Assignment)。

  • 声明:必须指定变量的类型与名称,可不给初始值;
  • 初始化:初始化指变量首次获得初始值的过程;
  • 赋值:与 C 一致,对于普通变量,可以通过赋值运算符 = 修改其保存的值。
int age;
double score = 95.5;
char gender = 'M';
 
// 允许在一条声明语句中声明多个相同类型的变量
int a = 10, b, c = 30;

变量名属于标识符(Identifier),必须符合 Java 的标识符规则:

  • 可以使用 Unicode 字母、数字及其他允许的字符;
  • 不允许包含空格、运算符、分隔符、引号、反斜杠、@、#;
  • 不允许以数字开头;
  • 不允许使用 Java 关键字。

二、确定赋值规则

对于局部变量,因为无默认值,所以 Java 要求在读取变量之前,必须能够确定该变量已经被赋值。这称为确定赋值(Definite Assignment)。

public class Main {
    public static void main(String[] args) {
        int age;
 
        if (Math.random() > 0.5) {
            age = 20;
        }
 
        System.out.println(age); // 编译错误
    }
}

age 是局部变量,无初始值;虽然代码中存在 age = 20,但是并非所有执行路径都能保证它获得值,不属于确定赋值,因此编译器会报错。

int age;
 
if (Math.random() > 0.5) {
    age = 20;
} else {
    age = 30;
}
 
System.out.println(age); // 正确

编译器能够确定无论条件是否成立,变量在使用前都已经被赋值。所以可编译成功。

public class Main {
    static int age;
 
    public static void main(String[] args) {
        System.out.println(age); // 输出 0
    }
}

此处 age 是静态字段,Java 会为未显式初始化的字段(成员变量)提供默认值

类型默认值
byte / short / int / long0
float / double0.0
char'\u0000'
booleanfalse
引用类型(对象、数组、String 等)null

2.1.4 - 局部变量类型推断(var)

从 Java 10 开始,Java 支持使用 var 声明具有类型推断能力的局部变量。

  • 必须在声明时提供初始化表达式。
  • 不能用 null 作为唯一的初始化表达式。
  • 不能用来声明普通字段或方法参数。
  • 一次 var 声明只能声明一个变量。
  • 推断后的类型是固定的,并不是动态类型。
var x = 10;   // int
var y = 10L;  // long
var z = 10.0; // double
var price = 19.99f; // float
var name = "Alice"; // String
 
var obj = null;     // 错误:无法推断类型
var score;          // 错误:缺少初始化表达式
var a = 1, b = 2;   // 错误:不能这样同时声明两个变量

2.1.5 - 常量

常量是用 final 修饰的变量,表示在程序执行过程中只能被赋值一次。

final int MAX_SIZE = 100;
  • final:表示该变量只能被赋值一次(可以不立即赋值)。

  • int:变量的数据类型。

  • MAX_SIZE:变量名;一般情况下全部字母大写,单词之间使用下划线分隔,不过局部常量仍可以使用小驼峰命名法。

  • 100:初始值,也是一个整数字面量。

与 C 语言中 const 的区别

与 C 不同的是:

  • C 语言的 const 表达的含义是“只读”;
  • 而 Java 的 final 表示该变量只能被赋值一次(允许声明时不赋值)。

const 是 Java 中未被使用的保留关键字,以后也大概率不会使用。



2.2 - 数据类型

数据类型(Data Type)用于规定一个变量能够保存什么样的数据,以及可以对这些数据进行哪些操作。

Java 是一种静态类型语言(Statically Typed Language),变量的类型在编译时就需要确定,不能随意改变。

Java 的数据类型可以分为两大类:

  • 基本数据类型(Primitive Types):直接表示数值、字符或布尔值,共有 8 种。
  • 引用数据类型(Reference Types):通过引用访问对象或数组,例如类、接口、数组等。

2.2.1 - Java 基本数据类型

类型关键字字节数主要用途取值范围 (精简)
字节型byte1小范围整数-128 ~ 127
短整型short2小范围整数-32768 ~ 32767
整型int4最常用的整数类型$-2^{31} \sim 2^{31}-1$
长整型long8大范围整数$-2^{63} \sim 2^{63}-1$
单精度浮点型float4小数±3.4e38 (约6~7位有效数字)
双精度浮点型double8最常用的浮点类型±1.8e308 (约15~16位有效数字)
字符型char2UTF-16 代码单元*0 ~ 65535 (即 0 ~ 2^16-1)
布尔型boolean不确定**真或假true / false
注释信息

*Java 的 char 并不保证可以表示一个完整的 Unicode 字符。例如,一些表情符号需要使用两个 char(代理对)来表示。

String emoji = "😀";
System.out.println(emoji.length()); // 2

**JVM 规范没有明确规定 boolean 占用多少个字节,在数组中通常是 1 字节,而单独的布尔变量可能占用 4 字节(因为 JVM 的具体实现可能将其作为 int 处理,但与 C/C++ 不同,它在源代码中它不能与数字互相转换)。

int a = 1;
 
if (a) {   // 编译错误
    System.out.println("Hello");
}

2.2.2 - Java 引用数据类型

除了 8 种基本类型,Java 中的其他大多数数据类型都是引用类型,包括字符串。

引用类型变量保存的是对对象的引用(Reference),而不是像基本类型变量一样直接表示基本类型的值。

  • 引用类型变量可以保存 null,表示当前没有引用任何对象;
  • 引用类型赋值 = 引用的复制(指向同一对象)
  • Java 只有值传递(pass by value),在给方法传参时,传入的是引用的副本(可改变所指对象的内容,但无法修改该指向)。
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1;      // arr1 和 arr2 指向同一个数组对象
arr2[0] = 99;
System.out.println(arr1[0]); // 99
 
String s1 = new String("hi");
String s2 = new String("hi");
s1 == s2        // false(不同对象)
s1.equals(s2)   // true (内容相同)
 
String s = "a";
String t = s;
s = s + "b";    // s + "b" 是一个新的对象,因为 s 所指的对象 "a" 无法修改,t 仍是 "a"


2.3 - 基本数据类型的类型转换

2.3.1 - 赋值和传参时的转换

Java 中,赋值和传参时的基本数据类型的类型转换可以分为两种:

  1. 自动类型转换(Widening Primitive Conversion)
  2. 强制类型转换(Narrowing Primitive Conversion)

一、自动类型转换(隐式 / 拓宽转换)

当把一个表数范围小的数值或变量赋值给表数范围大的变量时,系统自动完成类型转换。

  • char 可以自动转 int 及更宽类型;

  • byte / short 不能自动转 char,因为 char 是无符号的;

  • int 转 float、long 转 float、long 转 double 可能丢失精度。

int a = 100;
long b = a;     // int 自动转换为 long
double d = a;   // int 自动转换为 double
image.png

虚线:可能发生精度损失的自动转换

二、强制类型转换(显式 / 窄化转换)

当目标类型不能通过自动转换得到时(需要将宽类型放入窄类型变量时),通常需要显式指定目标类型,进行强制转换。

  • 语法:(目标类型) 变量名。

  • 转换方法:整数直接截断高位、浮点转整数直接丢弃小数部分。

  • boolean 不能与任何数值基本类型进行类型转换。

double d = 99.99;
int i = (int) d; // 小数部分直接被舍弃,i 的值为 99
 
int bigNum = 300;
byte b = (byte) bigNum; // byte 范围是 -128 ~ 127,转换后会发生溢出,结果为 44

2.3.2 - 表达式中的类型提升

除了赋值时的类型转换,Java 还会在算术运算等表达式中自动进行数值类型提升(Numeric Promotion),直接决定了表达式结果的数据类型。

一、一元数值提升(Unary Numeric Promotion)

当 byte、short 或 char 参与某些一元运算时,会先提升为 int(例如一元正号 +)。

byte a = 10;
 
int b = +a;    // 正确
byte c = +a;   // 编译错误,因为 +a 的结果是 int 类型
操作数类型提升后类型
byte、short、char:c10[提升为 `int`]
int保持 int
long保持 long
float保持 float
double保持 double

二、二元数值提升(Binary Numeric Promotion)

适用于大部分二元运算符:

  • 算术运算符:+ - * / %

  • 比较运算符:<、<=、>、>=

  • 位运算符:& | ^

  • 数值相等运算符:==、!=

规则顺序:

  1. 如果任一操作数是 double,另一个操作数转换为 double;

  2. 否则,如果任一操作数是 float,另一个操作数转换为 float;

  3. 否则,如果任一操作数是 long,另一个操作数转换为 long;

  1. 否则,两个操作数都转换为 int(也就是在处理 byte/short的运算时,必须转为 int,这是因为 JVM 并没有 byte/short 的算术指令)
byte a = 1;
byte b = 2;
byte c = a + b;         // 编译错误
byte c = (byte)(a + b); // 编译成功
 
int d = 10;
long e = 20L;
long f = d + e; // 结果类型为 long
 
float g = 3.14F;
double h = 2.0;
double i = g + h; // 结果类型为 double

需特别注意整数除法:

int a = 5;
int b = 2;
double c = a / b; // 2.0
/**
    因为 a / b 的两个操作数都是 int,所以它执行整数除法,结果是整数 2。
    之后,再将 2 自动转换为 double,得到 2.0。
**/
double c = (double) a / b; // 2.5


2.4 - 运算符

Java 的运算符与 C 语言有很多相似之处,但在整数除法、字符串拼接、逻辑运算、位运算、类型转换和对象比较等方面,有一些值得特别注意的规则。

2.4.1 - 表达式和运算符概述

一、表达式的组成

int result = a + b;
  • a、b:操作数(Operand)

  • +:运算符(Operator)

  • a + b:表达式(Expression)

  • =:赋值运算符,将右侧表达式的结果赋给左侧变量(整个赋值也是一个表达式)

二、运算符的分类

类型说明示例
一元运算符(Unary)作用于一个操作数-a、++a、!flag
二元运算符(Binary)作用于两个操作数a + b、a > b
三元运算符(Ternary)作用于三个操作数a > b ? a : b
分类运算符
算术运算符+ - * / %
自增、自减运算符++ --
赋值运算符= += -= *= /= %= 等
关系运算符> < >= <= == !=
逻辑运算符&& || ! & | ^
位运算符& | ^ ~ << >> >>>
条件运算符? :
类型判断运算符instanceof

除此之外,之前已经介绍过,Java 还有用于类型转换的 (type)、对象创建的 new 等语法。


2.4.2 - 算术运算符

算术运算符:+ 加法、- 减法、* 乘法、/ 除法、% 取余

  • 运算结果取决于参与运算的操作数类型,而不是接收结果的变量类型;
  • Java 取余运算:a%b=a−(a/b)×ba \% b = a - (a / b) \times b (期中 / 是向 0 截断的整数除法);
  • 与 C 不同,Java 的 % 还可以用于浮点数。
System.out.println(10 / 3);     // 3
System.out.println(10.0 / 3);   // 3.3333333333333335
System.out.println(-10 / 3);    // -3,向 0 截断,而不是向下取整或四舍五入
 
double a = 10 / 3;
double b = 10.0 / 3;
 
System.out.println(a);  // 3.0
System.out.println(b);  // 3.3333333333333335
 
System.out.println(10 % 3);    // 1
System.out.println(-10 % 3);   // -1
System.out.println(10 % -3);   // 1
System.out.println(-10 % -3);  // -1
 
System.out.println(5.5 % 2.0); // 1.5

2.4.3 - 字符串拼接

Java 中的 + 还可用于字符串拼接。

System.out.println("Hello" + " World");
// Hello World
 
System.out.println(1 + 2 + "3");  // "33"
System.out.println("1" + 2 + 3);  // "123",因为按顺序会先得到“12”
System.out.println("1" + (2 + 3)); // "15"

2.4.4 - 自增和自减运算符

自增、自减运算符:++a(先加再用)、--a(先减再用)、a++(先用再加)、a--(先用再减);

Java 中,自增和自减运算可以作用于 byte、short、char 等类型,并且不需要显式类型转换:

  • 但 Java 本身无法对这三类变量进行算术运算,所以会先将其自动提升为 int 类型;
  • 运算结束后,再自动将结果强制转换回原本的类型;
  • 所以,即使运算结果超过该类型的表数范围,也不产生编译错误,而是溢出。
byte b = 10;
b = b + 1; // 编译错误
b++;       // 编译成功,等价于 b = (byte)(b + 1);

2.4.5 - 赋值运算符

一、基本赋值运算符

基本赋值运算符 = 将右侧表达式的结果赋给左侧变量。并且,在 Java 中赋值不是单纯的语句,而是表达式。

  • 赋值表达式的类型是左侧变量的类型;赋值表达式的值是赋值完成后左侧变量的值。;
  • 并且 Java 的赋值运算符都具有右结合性(运算符的结合性决定同优先级运算符连续出现时如何分组),所以 Java 支持连续赋值。
a = b = c;
// 等价于
a = (b = c);
// 而不是 (a = b) = c; 
// 因为 (a = b) 是一个赋值表达式的结果,不是变量,不能作为赋值左操作数。

二、复合赋值运算符

复合赋值运算符(Compound Assignment Operators)将运算与赋值合并,所有的四则运算和二元位运算都有复合赋值运算符。

与自增、自减运算符类似,Java 的复合赋值运算符隐含了向左侧变量类型的强制转换。

byte a = 10;
 
a = a + 5;  // 编译错误
a += 5;     // 正确,相当于 a = (byte) (a + 5);



2.5 - 基本输入输出