JavaSE:基础语法入门部分
因为基础部分的东西很简单,java这里很多基本的东西的写法与C/C++没有太大区别,所以比较相似的部分会直接跳过不再阐述,仅说明与C/C++比较不同的地方。
一.数据类型与变量
在java中,数据类型主要分为两类,一类为基本数据类型,另一类为引用数据类型。
1.1基本数据类型
基本数据类型有四类八种:
- 四类:整型、浮点型、字符型、布尔型
- 八种:
| 数据类型 | 关键字 | 内存占用 | 范围 |
|---|---|---|---|
| 字节型 | byte |
1 字节 | -128 ~ 127 |
| 短整型 | short |
2 字节 | -32768 ~ 32767 |
| 整型 | int |
4 字节 | -2147483648 ~ 2147483647 |
| 长整型 | long |
8 字节 | -9223372036854775808 ~ 9223372036854775807 |
| 单精度浮点数 | float |
4 字节 | 有范围,一般不关注 |
| 双精度浮点数 | double |
8 字节 | 有范围,一般不关注 |
| 字符型 | char |
2 字节 | 0 ~ 65535 |
| 布尔型 | boolean |
没有明确规定 | true 和 false |
- 不论是在16位系统还是32位系统,
int都占用 4个字节,long都占用 8个字节。 - 整型和浮点型都是带有符号的。
- 整型默认为
int型,浮点型默认为double型。 - 字符串属于引用类型,该类型后续介绍。
除此之外,每个基本类型都对应着一个包装器类型:
| 基本类型 | 包装器类型 |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
- 包装器类型位于
java.lang包中,无需额外导入。 - 从 Java 5 开始支持自动装箱(基本类型 → 包装器)和自动拆箱(包装器 → 基本类型)。
- 包装器类提供了实用的静态方法,如
Integer.parseInt()、Double.valueOf()等。
这些我们后面都会大量的使用到,这里先做个认识即可。
1.2类型转换
需要注意,在java中,不管是显示还是隐式类型转换,不相关的类型无法进行互相转换!
比如:
boolean a = false;
a = (boolean) 100;
1.1关于隐式类型转换
像我们在使用C/C++时,有很多类型之间是可以直接相互转换的,无论是宽类型到窄类型还是反过来,甚至不相干的类型之间也可以进行转换,比如bool与整型类型之间的转换。总之C/C++中的类型转换是非常宽松的,当然也会因此引发很多问题,比如为了解决枚举引发的污染,C++11提出了一个强类型枚举。
但是,在Java中,只有从小范围类型向大范围类型转换时才能自动完成;而大范围向小范围转换时,无论实际数据是否在目标范围内,都必须显式强制转换,否则编译报错。
比如:
int a = 10;
long b = 10l;
a = b;//报错
1.2关于强制类型转换
强制类型转换:当进行操作时,代码需要经过一定的格式处理,不能自动完成。
特点:数据范围大的 → 数据范围小的
int a = 10;
long b = 100L;
b = a; // int-->long,数据范围由小到大,隐式转换
a = (int)b; // long-->int, 数据范围由大到小,需要强转,否则编译失败
float f = 3.14F;
double d = 5.12;
d = f; // float-->double,数据范围由小到大,隐式转换
f = (float)d; // double-->float, 数据范围由大到小,需要强转,否则编译失败
a = d; // 报错,类型不兼容
a = (int)d; // int没有double表示的数据范围大,需要强转,小数点之后全部丢弃
byte b1 = 100; // 100默认为int,没有超过byte范围,隐式转换
byte b2 = (byte)257; // 257默认为int,超过byte范围,需要显示转换,否则报错
boolean flag = true;
a = flag; // 编译失败:类型不兼容
flag = a; // 编译失败:类型不兼容
注意事项:
- 不同数字类型的变量之间赋值,表示范围更小的类型能隐式转换成范围较大的类型。
- 如果需要把范围大的类型赋值给范围小的,需要强制类型转换,但是可能精度丢失。
- 将一个字面值常量进行赋值时,Java 会自动针对数字范围进行检查。
- 强制类型转换不一定能成功,再次强调,不相干的类型不能互相转换!
1.3关于类型提升
java与C++一样,当小类型与大类型相加的时候,小类型会自动提升为大类型之后与大类型进行相加。当然此时的结果就是一个大类型,如果想要把这个大类型强制塞回给小类型,那就需要显示类型转换:
public static void main(String[] args)
{
int a = 100;
long b = 100;
int c = (int)(a + b);//必须显示强转否则报错
System.out.println();
}
特殊情况:byte类型与byte类型的运算
byte 与 byte 的运算
byte a = 10;
byte b = 20;
byte c = a + b; // 编译报错:不兼容的类型,从 int 转换到 byte 可能会有损失
System.out.println(c);
Test.java:5: 错误: 不兼容的类型: 从int转换到byte可能会有损失
byte c = a + b;
结论:byte 和 byte 都是相同类型,但是出现编译报错。
原因是,虽然 a 和 b 都是 byte,但是计算 a + b 会先将 a 和 b 都提升成 int,再进行计算,得到的结果也是 int,这时赋给 c(byte 类型),就会出现上述错误。
根本原因:由于计算机的 CPU 通常是按照 4 个字节为单位从内存中读写数据。为了硬件上实现方便,诸如 byte 和 short 这种低于 4 个字节的类型,会先提升成 int,再参与计算。
正确的写法:
byte a = 10;
byte b = 20;
byte c = (byte)(a + b); // 将 int 类型的结果强转为 byte
System.out.println(c);
所以总的来说关于类型提升就两个注意点:
类型提升规则:
- 不同类型的数据混合运算,范围小的会提升成范围大的。
- 对于
short、byte这种比 4 个字节小的类型,会先提升成 4 个字节的int,再运算。
二.运算符
java中的运算符和C/C++中的极为相似,我们简单的看下就ok了:
- 算术运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
+ |
加法 | a + b |
- |
减法 | a - b |
* |
乘法 | a * b |
/ |
除法 | a / b |
% |
取余(模运算) | a % b |
- 赋值运算符
| 运算符 | 描述 | 示例 | 等价于 |
|---|---|---|---|
= |
赋值 | a = b |
|
+= |
加后赋值 | a += b |
a = a + b |
-= |
减后赋值 | a -= b |
a = a - b |
*= |
乘后赋值 | a *= b |
a = a * b |
/= |
除后赋值 | a /= b |
a = a / b |
%= |
取余后赋值 | a %= b |
a = a % b |
- 关系(比较)运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
== |
等于 | a == b |
!= |
不等于 | a != b |
> |
大于 | a > b |
< |
小于 | a < b |
>= |
大于等于 | a >= b |
<= |
小于等于 | a <= b |
- 逻辑运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
&& |
逻辑与(短路与) | a && b |
|| |
逻辑或(短路或) | a || b |
! |
逻辑非 | !a |
& |
逻辑与(非短路) | a & b |
| |
逻辑或(非短路) | a | b |
^ |
逻辑异或 | a ^ b |
- 位运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
& |
按位与 | a & b |
| |
按位或 | a | b |
^ |
按位异或 | a ^ b |
~ |
按位取反 | ~a |
<< |
左移 | a << 2 |
>> |
右移(带符号) | a >> 2 |
>>> |
无符号右移 | a >>> 2 |
- 一元运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
+ |
正号 | +a |
- |
负号 | -a |
++ |
自增(前置/后置) | ++a 或 a++ |
-- |
自减(前置/后置) | --a 或 a-- |
- 条件(三元)运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
? : |
三元条件运算符 | a > b ? a : b |
说到运算符的优先级,当然不需要我们去记忆,用到了时候查下,和C/C++一样。
这里主要说一下比C/C++多出来的一个运算符>>>:
你像我们一般说负数的第一个字节位是1,那么>>去右移一个负数时,空出来的位置用1填充,而>>>右移则直接用0进行填充:
public static void main(String[] args)
{
int a = -1;
a = a >>> 1;
System.out.println(a);//输出值位INT_MAX
}
三.逻辑控制语句
这里也和C/C++极为相似,就简单的列举下,用法与C/C++基本相同:
3.1 条件判断语句
3.1.1 if 语句
| 语法格式 | 描述 | 示例 |
|---|---|---|
if (条件) { 代码块 } |
条件为 true 时执行代码块 | if (a > b) { max = a; } |
if (条件) { 代码块 } else { 代码块 } |
条件为 true 执行 if 块,否则执行 else 块 | if (a > b) { max = a; } else { max = b; } |
if (条件1) { 代码块 } else if (条件2) { 代码块 } else { 代码块 } |
多条件分支判断 | if (score >= 90) { grade = 'A'; } else if (score >= 60) { grade = 'B'; } else { grade = 'C'; } |
3.1.2 switch 语句
| 语法格式 | 描述 | 示例 |
|---|---|---|
switch (表达式) { case 值1: 代码块; break; case 值2: 代码块; break; default: 代码块; } |
根据表达式的值匹配对应的 case 执行 | switch (day) { case 1: System.out.println("周一"); break; case 2: System.out.println("周二"); break; default: System.out.println("其他"); } |
支持的数据类型: byte、short、char、int、String(Java 7+)、enum 枚举
3.2 循环语句
3.2.1 for 循环
| 语法格式 | 描述 | 示例 |
|---|---|---|
for (初始化; 条件; 迭代) { 代码块 } |
标准 for 循环,先初始化,条件为 true 时执行循环体,执行迭代 | for (int i = 0; i < 10; i++) { sum += i; } |
for (变量类型 变量名 : 数组/集合) { 代码块 } |
增强 for 循环(foreach),遍历数组或集合 | for (int num : numbers) { System.out.println(num); } |
for (;;) { 代码块 } |
无限循环(需要 break 退出) | for (;;) { if (condition) break; } |
3.2.2 while 循环
| 语法格式 | 描述 | 示例 |
|---|---|---|
while (条件) { 代码块 } |
先判断条件,条件为 true 时执行循环体 | while (i < 10) { sum += i; i++; } |
while (true) { 代码块 } |
无限循环(需要 break 退出) | while (true) { if (condition) break; } |
3.2.3 do-while 循环
| 语法格式 | 描述 | 示例 |
|---|---|---|
do { 代码块 } while (条件); |
先执行一次循环体,再判断条件,条件为 true 时继续执行 | do { sum += i; i++; } while (i < 10); |
四.在控制台进行输入输出
4.1 输出到控制台
基本语法
System.out.println(msg); // 输出一个字符串,带换行
System.out.print(msg); // 输出一个字符串,不带换行
System.out.printf(format, msg); // 格式化输出
println输出的内容自带\n,print不带\nprintf的格式化输出方式和 C 语言的printf基本一致
代码示例
System.out.println("hello world");
int x = 10;
System.out.printf("x = %d\n", x);
格式化字符串
| 转换符 | 类型 | 示例 | 输出 |
|---|---|---|---|
d |
十进制整数 | ("%d", 100) |
100 |
x |
十六进制整数 | ("%x", 100) |
64 |
o |
八进制整数 | ("%o", 100) |
144 |
f |
定点浮点数 | ("%f", 100f) |
100.000000 |
e |
指数浮点数 | ("%e", 100f) |
1.000000e+02 |
g |
通用浮点数 | ("%g", 100f) |
100.000 |
a |
十六进制浮点数 | ("%a", 100) |
0x1.9p6 |
s |
字符串 | ("%s", 100) |
100 |
c |
字符 | ("%c", '1') |
1 |
b |
布尔值 | ("%b", 100) |
true |
h |
散列码 | ("%h", 100) |
64 |
% |
百分号 | ("%.2f%%", 2/7f) |
0.29% |
提示:这个表格没必要记住,用到的时候根据需要查一下就行了。
4.2 从键盘输入
使用 Scanner 读取字符串/整数/浮点数
import java.util.Scanner; // 需要导入 util 包
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的姓名:");
String name = sc.nextLine();
System.out.println("请输入你的年龄:");
int age = sc.nextInt();
System.out.println("请输入你的工资:");
float salary = sc.nextFloat();
System.out.println("你的信息如下:");
System.out.println("姓名: " + name + "\n" + "年龄:" + age + "\n" + "工资:" + salary);
sc.close(); // 注意,要记得调用关闭方法
执行结果:
请输入你的姓名:
张三
请输入你的年龄:
18
请输入你的工资:
1000
你的信息如下:
姓名: 张三
年龄:18
工资:1000.0
使用 Scanner 循环读取 N 个数字,并求取其平均值
Scanner sc = new Scanner(System.in);
int sum = 0;
int num = 0;
while (sc.hasNextInt()) {
int tmp = sc.nextInt();
sum += tmp;
num++;
}
System.out.println("sum = " + sum);
System.out.println("avg = " + sum / num);
sc.close();
执行结果:
10
40
50
^Z
sum = 100
avg = 33
注意事项:当循环输入多个数据的时候,使用
Ctrl + Z来结束输入(Windows 上使用Ctrl + Z,Linux / Mac 上使用Ctrl + D)。
五.方法(就是C/C++中的函数)
定义一个方法的基本语法如下:
// 方法定义
修饰符 返回值类型 方法名称([参数类型 形参 ...]){
方法体代码;
[return 返回值];
}
方法定义注意事项
| 序号 | 注意事项 | 说明 |
|---|---|---|
| 1 | 修饰符 | 现阶段直接使用 public static 固定搭配 |
| 2 | 返回值类型 | 如果方法有返回值,返回值类型必须与返回的实体类型一致;如果没有返回值,必须写成 void |
| 3 | 方法名字 | 采用小驼峰命名(如 calcSum) |
| 4 | 参数列表 | 如果方法没有参数,() 中什么都不写;如果有参数,需指定参数类型,多个参数之间使用逗号隔开 |
| 5 | 方法体 | 方法内部要执行的语句 |
| 6 | 定义位置 | 在 Java 中,方法必须写在类当中 |
| 7 | 嵌套定义 | 在 Java 中,方法不能嵌套定义 |
| 8 | 方法声明 | 在 Java 中,没有方法声明一说(即不需要像 C 语言那样先声明再实现) |
比如我们来写一个判断是否为闰年的方法并调用:
public class HelloWorld {
public static boolean isLeapYear(int year){
if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){
return true;
}else{
return false;
}
}
public static void main(String[] args)
{
System.out.println(isLeapYear(400));
}
}
5.1实参和形参的关系
方法的形参相当于数学函数中的自变量。
比如1 + 2 + 3 + … + n的公式为sum(n) = ((1 + n) * n) / 2
Java 中方法的形参就相当于 sum 函数中的自变量 n,用来接收 sum 函数在调用时传递的值。形参的名字可以随意取,对方法都没有任何影响,形参只是方法在定义时需要借助的一个变量,用来保存方法在调用时传递过来的值。
注意:在 Java 中,实参的值永远都是拷贝到形参中,形参和实参本质是两个实体。
也就是说它没有像C/C++那样传值/传指针/传引用这一说法。
5.2方法重载(就是C/C++中的函数重载)
同一个类中,允许存在多个方法名相同但参数列表不同的方法。就叫方法重载。
方法重载的三大规则:
- 方法名必须相同
- 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序不同)
- 与返回值类型是否相同无关
规则上也与C/C++别无二致。
六.数组的定义与使用
6.1 数组的创建及初始化
数组的创建
T[] 数组名 = new T[N];
T:表示数组中存放元素的类型T[]:表示数组的类型N:表示数组的长度
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new String[3]; // 创建一个可以容纳3个字符串元素的数组
数组的初始化
数组的初始化主要分为动态初始化和静态初始化。
1. 动态初始化:在创建数组时,直接指定数组中元素的个数
int[] array = new int[10];
2. 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
语法格式:
T[] 数组名称 = {data1, data2, data3, ..., datan};
int[] array1 = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hell", "Java", "!!!"};
注意事项:
- 静态初始化虽然没有指定数组的长度,编译器在编译时会根据
{}中元素个数来确定数组的长度。 - 静态初始化时,
{}中数据类型必须与[]前数据类型一致。 - 静态初始化可以简写,省去后面的
new T[]。
// 简写形式
int[] array1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = {"hell", "Java", "!!!"};
6.2关于引用类型的说明
6.2.1java程序运行时的内存分布
我们先来认识下java程序在运行时的内存分布:
JVM 运行时数据区域
| 区域 | 说明 |
|---|---|
| 程序计数器 (PC Register) | 只是一个很小的空间,保存下一条执行的指令的地址。 |
| 虚拟机栈 (JVM Stack) | 与方法调用相关的一些信息。每个方法在执行时,都会先创建一个栈帧,栈帧中包含:局部变量表、操作数栈、动态链接、返回地址等。保存的都是与方法执行时相关的信息(如局部变量)。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。 |
| 本地方法栈 (Native Method Stack) | 与虚拟机栈的作用类似,只不过保存的内容是 Native 方法的局部变量。在有些版本的 JVM 实现中(如 HotSpot),本地方法栈和虚拟机栈是一起的。 |
| 堆 (Heap) | JVM 所管理的最大内存区域。使用 new 创建的对象都是在堆上保存(例如 new int[]{1, 2, 3})。堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。 |
| 方法区 (Method Area) | 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的字节码就是保存在这个区域。 |
现阶段重点关注:堆 和 虚拟机栈 这两块空间,后续 JVM 中还会更详细介绍。
6.2.2什么是引用类型
我们都知道,在C++中,无论是左值引用还是右值引用,都必须在引用变量定义时初始化,引用变量并不拥有指向的资源。
java这里也是,但是并不需要定义时就必须初始化。比如窝可以这样写:
int[] arr1;
int[] arr2 = null;
arr1 = new int[]{1,2,3,4};
arr2 = {1,2,3,4};//错误!注意初始为空的时候,如果想要对其进行初始化时必须动态初始化
上面两种方式都是一样的。
需要额外注意的是,在我们定义一个引用类型比如数组时,它在栈(准确来说是虚拟机栈)上开辟空间存储此引用类型变量,同时在堆上开空间去存储实际数据:
从上图可以看到,引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。有点类似 C 语言中的指针,但是 Java 中引用要比指针的操作更简单。
我们再来看下面这样一个例子:
public static void func() {
int[] array1 = new int[3];
array1[0] = 10;
array1[1] = 20;
array1[2] = 30;
int[] array2 = new int[]{1,2,3,4,5};
array2[0] = 100;
array2[1] = 200;
array1 = array2;
array1[2] = 300;
array1[3] = 400;
array2[4] = 500;
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i]);
}
}
实际上整个func执行时内存空间中出现了这样的变化:

除此之外,java中还有二维数组的概念,与C/C++中也大差不差。二维数组的用法和一维数组并没有明显差别, 因此我们不再赘述.
同理, 还存在 “三维数组”, “四维数组” 等更复杂的数组, 只不过出现频率都很低.
更多推荐
所有评论(0)