版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://mp.csdn.net/mdeditor/100619398
目录
绪言
最近开始学习Java,主要教材是廖雪峰老师的Java教程。为了巩固学习的成果,并作为以后查阅的参考,我计划写一系列学习笔记。
这是本系列的第二篇 – Java程序基础。
本节我们将介绍Java程序的基础知识,包括:
- Java程序基本结构
- 变量和数据类型
- 整数运算
- 浮点数运算
- 布尔运算
- 字符和字符串
- 数组类型
Java程序基本结构
先看一个完整的Java程序:
/**
* 可以用来自动创建文档的注释
*/
public class Hello {
public static void main(String[] args) {
// 向屏幕输出文本:
System.out.println("Hello, world!");
/* 多行注释开始
注释内容
注释结束 */
}
} // class定义结束
类
- Java是面向对象的语言,一个程序的基本单位就是class,class是关键字,这里定义的class名字就是Hello。类名必须以英文字母开头,后接字母,数字和下划线的组合,且习惯以大写字母开头。
- public是访问修饰符,表示该class是公开的。
方法:
-
在class内部,可以定义若干方法(method),方法定义了一组执行语句,方法内部的代码将会被依次顺序执行。
-
这里的方法名是main,返回值是void,表示没有任何返回值。
-
public可以修饰方法。关键字static也是一个修饰符,它表示静态方法,后面我们会讲解方法的类型,目前,我们只需要知道,Java入口程序规定的方法必须是静态方法,方法名必须为main,括号内的参数必须是String数组。
-
方法名也有命名规则,命名和class一样,但是首字母小写:
语句
- Java的每一行语句必须以分号结束。
注释
-
List item
-
注释包括单行注释和多行注释。
-
单行注释,以双斜线开头,直到这一行的结尾结束
-
多行注释以/*星号开头,以*/结束,可以有多行
-
还有一种特殊的多行注释,以/**开头,以**/结束,如果有多行,每行通常以星号开头.
格式
- Java程序对格式没有明确的要求,多几个空格或者回车不影响程序的正确性.
变量和数据类型
变量的定义
在Java中,变量必须先定义后使用,在定义变量的时候,可以给它一个初始值。
举个栗子:
public class Main {
public static void main(String[] args) {
int x = 100; // 定义int类型变量x,并赋予初始值100
System.out.println(x); // 打印该变量的值
}
}
这是一个完整的定义变量,然后打印变量值的例子。定义了一个整型int类型的变量,名称为x,初始值为100。不写初始值,就相当于给它指定了默认值。默认值总是0。
重新赋值
变量的一个重要特点是可以重新赋值。
举个栗子:
public class Main {
public static void main(String[] args) {
int x = 100; // 定义int类型变量x,并赋予初始值100
System.out.println(x); // 打印该变量的值,观察是否为100
x = 200; // 重新赋值为200
System.out.println(x); // 打印该变量的值,观察是否为200
}
}
重新赋值的时候,变量x已经存在了,不能再重复定义,因此不能指定变量类型int,必须使用语句x = 200;。
变量不但可以重新赋值,还可以赋值给其他变量。
例如:
public class Main {
public static void main(String[] args) {
int n = 100; // 定义变量n,同时赋值为100
System.out.println("n = " + n); // 打印n的值
n = 200; // 变量n赋值为200
System.out.println("n = " + n); // 打印n的值
int x = n; // 变量x赋值为n(n的值为200,因此赋值后x的值也是200)
System.out.println("x = " + x); // 打印x的值
x = x + 100; // 变量x赋值为x+100(x的值为200,因此赋值后x的值是200+100=300)
System.out.println("x = " + x); // 打印x的值
System.out.println("n = " + n); // 再次打印n的值,n应该是200还是300?
}
}
结果:
n = 200
x = 200
x = 300
n = 200
需要注意的是:执行int x = n;时,定义了一个新的变量x,同时对x赋值,因此,JVM需要新分配一个存储单元给变量x,并写入和变量n一样的值,结果是变量x的值也变为200。这之后x和n的取值完全没有关系。
基本数据类型
基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:
- 整数类型:byte,short,int,long
- 浮点数类型:float,double
- 字符类型:char
- 布尔类型:boolean
整型
整型定义
对于整型类型,Java只定义了带符号的整型,因此,最高位的bit表示符号位(0表示正数,1表示负数)。各种整型能表示的最大范围如下:
- byte:-128 ~ 127
- short: -32768 ~ 32767
- int: -2147483648 ~ 2147483647
- long: -9223372036854775808 ~ 9223372036854775807
其中long型的结尾需要加L。
举个栗子:
public class Main {
public static void main(String[] args) {
int i = 2147483647;
int i2 = -2147483648;
int i3 = 2_000_000_000; // 加下划线更容易识别
int i4 = 0xff0000; // 十六进制表示的16711680
int i5 = 0b1000000000; // 二进制表示的512
long l = 9000000000000000000L; // long型的结尾需要加L
}
}
整型运算
Java的整数运算遵循四则运算规则,可以使用任意嵌套的小括号。四则运算规则和初等数学一致。这一点比较简单,不再赘述。
整数的数值表示不但是精确的,而且整数运算永远是精确的,即使是除法也是精确的,因为两个整数相除只能得到结果的整数部分。
例如:
int x = 10/3;
结果
3
求余运算使用%。
特别注意:整数的除法对于除数为0时运行时将报错,但编译不会报错。
溢出
整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果。
例如:
public class Main {
public static void main(String[] args) {
int x = 2147483640;
int y = 15;
int sum = x + y;
System.out.println(sum);
}
}
结果:
-2147483641
要解释上述结果,我们把整数2147483640和15换成二进制,分别是:
0111 1111 1111 1111 1111 1111 1000和0000 0000 0000 0000 0000 0000 1111
相加得到1000 0000 0000 0000 0000 0000 0111
由于最高位计算结果为1,因此,加法结果变成了一个负数。
要解决上面的问题,可以把int换成long类型,由于long可表示的整型范围更大,所以结果就不会溢出。
自增/自减
++运算和–运算,可以对一个整数进行加1和减1的操作。
需要注意的是:++写在前面和后面计算结果是不同的,++n表示先加1再引用n,n++表示先引用n再加1。不建议把++运算混入到常规运算中,容易自己把自己搞懵了。
移位运算
在计算机中,整数总是以二进制的形式表示。例如,int类型的整数7使用4字节表示的二进制如下:
00000000 0000000 0000000 00000111
可以对整数进行移位运算。对整数7左移1位将得到整数14,左移两位将得到整数28:
int n = 7; // 00000000 0000000 0000000 00000111
int a = n << 1; // 00000000 0000000 0000000 00001110 <= 14
int b = n << 2; // 00000000 0000000 0000000 00011100 <= 28
int c = n << 28; // 01110000 0000000 0000000 00000000 <= 1879048192
int d = n << 29; // 11100000 0000000 0000000 00000000 <= -536870912
左移29位时,由于最高位变成1,因此结果变成了负数。
类似的,对整数28进行右移,结果如下:
int n = 7; // 00000000 0000000 0000000 00000111
int a = n >> 1; // 00000000 0000000 0000000 00000011 <= 3
int b = n >> 2; // 00000000 0000000 0000000 00000001 <= 1
int c = n >> 3; // 00000000 0000000 0000000 00000000 <= 0
如果对一个负数进行右移,最高位的1不动,结果仍然是一个负数。
但是有一种不带符号的右移运算,使用>>>,它的特点是符号位跟着动,因此,对一个负数进行>>>右移,它会变成正数,原因是最高位的1变成了0:
int n = -536870912;
int a = n >>> 1; // 01110000 0000000 0000000 00000000 <= 1879048192
int b = n >>> 2; // 00111000 0000000 0000000 00000000 <= 939524096
int c = n >>> 29; // 00000000 0000000 0000000 00000111 <= 7
int d = n >>> 31; // 00000000 0000000 0000000 00000001 <= 1
对byte和short类型进行移位时,会首先转换为int再进行位移。
位运算
位运算是按位进行与、或、非和异或的运算。
- 与运算的规则是,必须两个数同时为1,结果才为1。运算符号为&。
- 或运算的规则是,只要任意一个为1,结果就为1。运算符号为|。
- 非运算的规则是,0和1互换。运算符号是~。
- 异或运算的规则是,如果两个数不同,结果为1,否则为0。运算符号为^。
对两个整数进行位运算,实际上就是按位对齐,然后依次对每一位进行运算。例如:
public class Main {
public static void main(String[] args) {
int i = 167776589; // 00001010 00000000 00010001 01001101
int n = 167776512; // 00001010 00000000 00010001 00000000
System.out.println(i & n); // 167776512
}
}
运算优先级
运算优先级从高到低依次是:
- ()
- ! ~ ++ –
- * / %
- + -
- << >> >>>
- &
- |
- += -= *= /=
记不住也没关系,只需要加括号就可以保证运算的优先级正确。
类型自动提升与强制转型
在运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。例如,short和int计算,结果总是int,原因是short首先自动被转型为int。
也可以将结果强制转型,即将大范围的整数转型为小范围的整数。强制转型使用(类型),例如,将int强制转型为short:
int i = 12345;
short s = (short) i; // 12345
要注意,超出范围的强制转型会得到错误的结果,原因是转型时,int的两个高位字节直接被扔掉,仅保留了低位的两个字节。例如:
public class Main {
public static void main(String[] args) {
int i1 = 1234567;
short s1 = (short) i1; // -10617
System.out.println(s1);
int i2 = 12345678;
short s2 = (short) i2; // 24910
System.out.println(s2);
}
}
浮点型
浮点类型的数就是小数,因为小数用科学计数法表示的时候,小数点是可以“浮动”的,如1234.5可以表示成12.345x102,也可以表示成1.2345x103,所以称为浮点数。
对于float类型,需要加上f后缀。
浮点数可表示的范围非常大,float类型可最大表示
3.4
×
1
0
38
3.4\times10^{38}
3.4×1038,而double类型可最大表示
1.79
×
1
0
308
1.79 \times 10^{308}
1.79×10308。
举个栗子:
float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324
无法精确
浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算。
在计算机中,浮点数虽然表示的范围大,但是,浮点数有个非常重要的特点,就是浮点数常常无法精确表示。
举个栗子:
浮点数0.1在计算机中就无法精确表示,因为十进制的0.1换算成二进制是一个无限循环小数,很显然,无论使用float还是double,都只能存储一个0.1的近似值。但是,0.5这个浮点数又可以精确地表示。
因为浮点数常常无法精确表示,因此,浮点数运算会产生误差。
所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数。
类型提升
如果参与运算的两个数其中一个是整型,那么整型可以自动提升到浮点型:
public class Main {
public static void main(String[] args) {
int n = 5;
double d = 1.2 + 24.0 / n;
System.out.println(d);
}
}
结果:
6.0
需要特别注意,在一个复杂的四则运算中,两个整数的运算不会出现自动提升的情况。例如:
double d = 1.2 + 24 / 5;
结果:
5.2
溢出
整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:
- NaN表示Not a Number
- Infinity表示无穷大
- -Infinity表示负无穷大
强制转型
可以将浮点数强制转型为整数。在转型时,浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值。例如:
int n1 = (int) 12.3; // 12
int n2 = (int) 12.7; // 12
int n3 = (int) (12.7 + 0.5); // 13
int n4 = (int) 1.2e20; // 2147483647
如果要进行四舍五入,可以对浮点数加上0.5再强制转型。
布尔类型
布尔类型boolean只有true和false两个值。
布尔运算
布尔运算是一种关系运算,包括以下几类:
- 比较运算符:>,>=,<,<=,==,!=
- 与运算 &&
- 或运算 ||
- 非运算 !
举个栗子:
boolean isGreater = 5 > 3; // true
int age = 12;
boolean isZero = age == 0; // false
boolean isNonZero = !isZero; // true
boolean isAdult = age >= 18; // false
boolean isTeenager = age >6 && age <18; // true
关系运算符的优先级从高到低依次是:
- !
- >,>=,<,<=
- ==,!=
- &&
- ||
短路运算
布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
例如false && x的结果总是false,无论x是true还是false,因此,与运算在确定第一个值为false后,不再继续计算,而是直接返回false。
举个栗子:
public class Main {
public static void main(String[] args) {
boolean b = 5 < 3;
boolean result = b && (5 / 0 > 0);
System.out.println(result);
}
}
如果没有短路运算,&&后面的表达式会由于除数为0而报错,但实际上该语句并未报错,原因在于与运算是短路运算符,提前计算出了结果false。
如果变量b的值为true,则表达式变为true && (5 / 0 > 0)。因为无法进行短路运算,该表达式必定会由于除数为0而报错,
三元运算符
Java还提供一个三元运算符b ? x : y,它根据第一个布尔表达式的结果,分别返回后续两个表达式之一的计算结果。
举个栗子:
public class Main {
public static void main(String[] args) {
int n = -100;
int x = n >= 0 ? n : -n;
System.out.println(x);
}
}
结果:
100
上述语句的意思是,判断n >= 0是否成立,如果为true,则返回n,否则返回-n。这实际上是一个求绝对值的表达式。
注意到三元运算b ? x : y会首先计算b,如果b为true,则只计算x,否则,只计算y。此外,x和y的类型必须相同,因为返回值不是boolean,而是x和y之一。
字符和字符串
在Java中,字符和字符串是两个不同的类型。
首先需要注意的是:字符是基本类型,而字符串不是,它是引用类型。
字符类型
字符类型char是基本数据类型,它是character的缩写。一个char保存一个Unicode字符。例如:
char c1 = 'A';
char c2 = '中';
因为Java在内存中总是使用Unicode表示字符,所以,一个英文字符和一个中文字符都用一个char类型表示,它们都占用两个字节。要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可:
int n1 = 'A'; // 字母“A”的Unicodde编码是65
int n2 = '中'; // 汉字“中”的Unicode编码是20013
还可以直接用转义字符\u+Unicode编码来表示一个字符:
// 注意是十六进制:
char c3 = '\u0041'; // 'A',因为十六进制0041 = 十进制65
char c4 = '\u4e2d'; // '中',因为十六进制4e2d = 十进制20013
字符串类型
和char类型不同,字符串类型String是引用类型,我们用双引号"…"表示字符串。一个字符串可以存储0个到任意个字符。
因为字符串使用双引号"…"表示开始和结束,那如果字符串本身恰好包含一个"字符,可以借助转义字符\:
String s = "abc\"xyz"; // 包含7个字符: a, b, c, ", x, y, z
注意:\表示一个\字符。
常见的转义字符包括:
- \" 表示字符"
- \’ 表示字符’
- \\ 表示字符\
- \n 表示换行符
- \r 表示回车符
- \t 表示Tab
- \u#### 表示一个Unicode编码的字符
字符串连接
Java的编译器对字符串做了特殊照顾,可以使用+连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。例如:
public class Main {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "world";
String s = s1 + " " + s2 + "!";
System.out.println(s);
}
}
结果:
Hello world!
注意:如果用+连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串,再连接。
字符串不可变
举个栗子:
public class Main {
public static void main(String[] args) {
String s = "hello";
System.out.println(s);
s = "world";
System.out.println(s);
}
}
结果:
hello
world
这个过程中,变的不是字符串,而是变量s的“指向”。
原来的字符串"hello"还在,只是我们无法通过变量s访问它而已。因此,字符串的不可变是指字符串内容不可变。
空值null
引用类型的变量可以指向一个空值null,它表示不存在,即该变量不指向任何对象。
例如:
String s1 = null; // s1是null
String s2; // 没有赋初值值,s2也是null
String s3 = s1; // s3也是null
String s4 = ""; // s4指向空字符串,不是null
注意要区分空值null和空字符串"",空字符串是一个有效的字符串对象,它不等于null。
常量
定义变量的时候,如果加上final修饰符,这个变量就变成了常量。
例如:
final double PI = 3.14; // PI是一个常量
double r = 5.0;
double area = PI * r * r;
PI = 300; // compile error!
常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。
常量的作用是用有意义的变量名来避免魔术数字(Magic number),例如,不要在代码中到处写3.14,而是定义一个常量。如果将来需要提高计算精度,我们只需要在常量的定义处修改,例如,改成3.1416,而不必在所有地方替换3.14。
根据习惯,常量名通常全部大写。`
var关键字
使用var关键字可以在定义变量时省略变量类型。
例如:
StringBuilder sb = new StringBuilder();
如果想省略变量类型,可以使用var关键字:
var sb = new StringBuilder();
变量的作用范围
在Java中,多行语句用{ }括起来。很多控制语句,例如条件判断和循环,都以{ }作为它们自身的范围。
在语句块中定义的变量,它有一个作用域,就是从定义处开始,到语句块结束。超出了作用域引用这些变量,编译器会报错。
定义变量时,要遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域,并且,不要重复使用变量名。
数组类型
定义数组
定义一个数组类型的变量,使用数组类型“类型[]”,例如,int[]。和单个基本类型变量不同,数组变量初始化必须使用new int[5]表示创建一个可容纳5个int元素的数组。
举个栗子:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
ns[0] = 68;
ns[1] = 79;
ns[2] = 91;
ns[3] = 85;
ns[4] = 62;
}
}
Java的数组有几个特点:
- 数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false;
- 数组一旦创建后,大小就不可改变。
要访问数组中的某一个元素,需要使用索引。数组索引从0开始,例如,5个元素的数组,索引范围是0~4。
可以修改数组中的某一个元素,使用赋值语句,例如,ns[1] = 79;。
可以用数组变量.length获取数组大小:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
System.out.println(ns.length);
}
}
结果:
5
数组是引用类型,在使用索引访问数组元素时,如果索引超出范围,运行时将报错。
也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小。例如:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 编译器自动推算数组大小为5
}
}
还可以进一步简写为:
int[] ns = { 68, 79, 91, 85, 62 };
注意数组是引用类型,并且数组大小不可变。
举个栗子:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns;
ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length);
ns = new int[] { 1, 2, 3 };
System.out.println(ns.length);
}
}
结果:
5
3
这个过程中,变的不是数组大小,而是ns的“指向”。
原来的数组大{ 68, 79, 91, 85, 62 }还在,只是我们无法通过ns访问它而已。
字符串数组
如果数组元素不是基本类型,而是一个引用类型,修改数组元素会有一些不同。
字符串是引用类型,我们先定义一个字符串数组:
String[] names = {
"ABC", "XYZ", "zoo"
};
对于String[]类型的数组变量names,它实际上包含3个元素,但每个元素都指向某个字符串对象。
对names[1]进行赋值,例如names[1] = “cat”;原来names[1]指向的字符串"XYZ"并没有改变,仅仅是将names[1]的引用从指向"XYZ"改成了指向"cat",其结果是字符串"XYZ"再也无法通过names[1]访问到了。
Xmind
最后,放上这一节的思维导图:
参考资料
PS: 如果觉得本篇本章对您有所帮助,欢迎关注、评论、赞!