前面我们已经认识了Java语言的相关特性,并且已经成功配置好了开发环境,从这节课开始,我们就可以正式进入到Java语言的学习当中了。Java语言是一门面向对象的语言,但是在面向对象之前,我们还得先学会如何面向过程编程。
首先我们还是从最基本的Java程序基础开始讲解。
还记得我们之前使用的示例代码吗?
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
这段代码要实现的功能很简单,就是将 Hello World 输出到控制台就行。
由于我们还没有学习到类的相关性质,所以在第二章之前,各位小伙伴直接记住固定模式即可,首先我们创建的源文件名称需要为Main.java
然后编写的代码第一行:
public class Main {
}
注意需要区分大小写,Java语言严格区分大小写,如果我们没有按照规则来编写,那么就会出现红色波浪线报错:
只要源代码中存在报错的地方,就无法正常完成编译得到二进制文件,会提示构建失败:
注意最后还有一个花括号,并且此花括号是成对出现的,一一对应。
所以说各位小伙伴在编写代码时一定要注意大小写。然后第二行,准确的说是最外层花括号内部就是:
public static void main(String[] args) {
}
这是我们整个Java程序的入口点,我们称为主方法(如果你学习过C肯定能够联想到主函数,只不过Java中不叫函数,叫方法)最后也会有一个花括号成对出现,而在主方法的花括号中编写的代码,就是按照从上往下的顺序依次执行的。
比如我们之前编写的:
System.out.println("Hello World!");
这段代码的意思就是将双引号括起来的内容(字符串,我们会在后面进行讲解)输出(打印)到控制台上,可以看到最后还加上了一个;
符号,表示这一句代码结束。我们每一段代码结束时都需要加上一个分号表示这一句的结束,就像我们写作文一样。
比如下面的代码,我们就可以实现先打印Hello World!,然后再打印YYDS!到控制台。
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println("YYDS!");
}
}
效果如下:
如果两段代码没有加上分号分割,那么编译器会认为这两段代码是同一句代码中的,即使出现换行或者是空格:
这里IDEA很聪明,会提醒我们这里少加了分号,所以说这个IDEA能够在初期尽可能地帮助新手。
再比如下面的代码:
这里我们尝试在中途换行和添加空格,因为没有添加分号,所以说编译器依然会认为是一行代码,因此编译不会出现错误,能够正常通过。当然,为了代码写得工整和规范,我们一般不会随意进行换行编写或者是添加没必要的空格。
同样的,如果添加了分号,即使在同一行,也会被认为是两句代码:
如果在同一行就是从左往右的顺序,得到的结果跟上面是一样的。
我们在编写代码时,可能有些时候需要标记一下这段代码表示什么意思:
但是如果直接写上文字的话,会导致编译不通过,因为这段文字也会被认为是程序的一部分。
这种情况,我们就可以告诉编译器,这段文字是我们做的笔记,并不是程序的一部分,那么要怎么告诉编译器这不是代码呢?很简单,我们只需要在前面加上双斜杠就可以了:
添加双斜杠之后(自动变成了灰色),后续的文本内容只要没有发生换行,那么都会被认为是一段注释,并不属于程序,在编译时会被直接忽略,之后这段注释也不会存在于程序中。但是一旦发生换行那就不行了:
那要是此时注释很多,一行写不完,我们想要编写很多行的注释呢?我们可以使用多行注释标记:
public class Main {
public static void main(String[] args) {
/*
这里面的内容
无论多少行
都可以
*/
System.out.println("Hello World!");
}
}
多行可以使用/*
和*/
的组合来囊括需要编写的注释内容。
当然还有一种方式就是使用/**
来进行更加详细的文档注释:
这种注释可以用来自动生成文档,当我们鼠标移动到Main上时,会显示相关的信息,我们可以自由添加一些特殊的注释,比如作者、时间等信息,也可以是普通的文字信息。
我们的程序不可能永远都只进行上面那样的简单打印操作,有些时候可能需要计算某些数据,此时我们就需要用到变量了。
那么,什么是变量呢?我们在数学中其实已经学习过变量了:
变量,指值可以变的量。变量以非数字的符号来表达,一般用拉丁字母。变量的用处在于能一般化描述指令的方式。结果只能使用真实的值,指令只能应用于某些情况下。变量能够作为某特定种类的值中任何一个的保留器。
比如一个公式 x^2 + 6 = 22 此时x
就是一个变量,变量往往代表着某个值,比如这里的x
就代表的是4这个值。在Java中,我们也可以让变量去代表一个具体的值,并且变量的值是可以发生变化的。
要声明一个变量,我们需要使用以下格式:
[数据类型] [变量名称];
这里的数据类型我们会在下节课开始逐步讲解,比如整数就是int
类型,不同类型的变量可以存储不同的类型的值。后面的变量名称顾名思义,就像x
一样,这个名称我们可以随便起一个,但是注意要满足以下要求:
当然各位小伙伴没必要刻意去进行记忆,我们会在学习的过程中逐步认识到这些关键字。新手要辨别一个单词是否为关键字,只需要通过IDEA的高亮颜色进行区分即可,比如:
深色模式下,关键字会高亮为橙色,浅色模式下会高亮为深蓝色,普通的代码都是正常的灰白色。
比如现在我们想要定义一个变量a
,那么就可以这样编写:
public class Main {
public static void main(String[] args) {
int a; //声明一个整数类型变量a
}
}
但是这个变量一开始没有任何值,比如现在我们要让这个变量表示10,那么就可以将10赋值给这个变量:
public static void main(String[] args) {
int a = 10; //直接在定义变量后面添加 = 10,表示这个变量的初始值为10,这里的10就是一个常量数字
}
或者我们可以在使用时再对其进行赋值:
public static void main(String[] args) {
int a;
a = 10; //使用时再赋值也可以
}
是不是感觉跟数学差不多?这种写法对于我们人来说,实际上是很好理解的,意思表达很清晰。
我们可以一次性定义多个变量,比如现在我们想定义两个int
类型的变量:
public static void main(String[] args) {
int a, b; //定义变量a和变量b,中间使用逗号隔开就行了
}
或者两个变量单独声明也可以:
public static void main(String[] args) {
int a; //分两句进行声明
int b;
}
为了更直观地查看变量的值,我们可以直接将变量的值也给打印到控制台:
public static void main(String[] args) {
int a = 666;
System.out.println(a); //之前我们在小括号写的是"",现在我们直接将变量给进去就可以打印变量的值了
System.out.println(888); //甚至直接输出一个常量值都可以
}
得到结果:
变量的值也可以在中途进行修改:
public static void main(String[] args) {
int a = 666;
a = 777;
System.out.println(a); //这里打印得到的值就是777了
}
变量的值也可以直接指定为其他变量的值:
public static void main(String[] args) {
int a = 10;
int b = a; //直接让b等于a,那么a的值就会给到b
System.out.println(b); //这里输出的就是10了
}
我们还可以让变量与数值之间做加减法(运算符会在后面详细介绍):
public static void main(String[] args) {
int a = 9; //a初始值为9
a = a + 1; //a = a + 1也就是将a+1的结果赋值给a,跟数学是一样的,很好理解对吧
System.out.println(a); //最后得到的结果就是10了
}
有时候我们希望变量的值一直保持不变,我们就可以将其指定为常量,这里我们介绍Java中第一个需要认识的关键字:
public static void main(String[] args) {
final int a = 666; //在变量前面添加final关键字,表示这是一个常量
a = 777; //常量的值不允许发生修改
}
编译时出现:
常量的值只有第一次赋值可以修改,其他任何情况下都不行:
public static void main(String[] args) {
final int a;
a = 777; //第一次赋值
}
至此,Java的基础语法部分介绍完毕,下一部分我们将开始介绍Java中的几大基本数据类型。
我们的程序中可能需要表示各种各样的数据,比如整数、小数、字符等等,这一部分我们将探索Java中的八大基本数据类型。只不过在开始之前,我们还需要先补充一点简单的计算机小知识。
在计算机中,所有的内容都是二进制形式表示。十进制是以10为进位,如9+1=10;二进制则是满2进位(因为我们的计算机是电子的,电平信号只有高位和低位,你也可以暂且理解为通电和不通电,高电平代表1,低电平代表0,由于只有0和1,因此只能使用2进制表示我们的数字!)比如1+1=10=2^1+0,一个位也叫一个bit,8个bit称为1字节,16个bit称为一个字,32个bit称为一个双字,64个bit称为一个四字,我们一般采用字节来描述数据大小。
注意这里的bit跟我们生活中的网速MB/s是不一样的,小b代表的是bit,大B代表的是Byte字节(8bit = 1Byte字节),所以说我们办理宽带的时候,100Mbps这里的b是小写的,所以说实际的网速就是100/8 = 12.5 MB/s了。
十进制的7 -> 在二进制中为 111 = 2^2 + 2^1 + 2^0
现在有4个bit位,最大能够表示多大的数字呢?
在Java中,无论是小数还是整数,他们都要带有符号(和C语言不同,C语言有无符号数)所以,首位就作为我们的符号位,还是以4个bit为例,首位现在作为符号位(1代表负数,0代表正数):
现在,我们4bit能够表示的范围变为了-7~+7,这样的表示方式称为原码。虽然原码表示简单,但是原码在做加减法的时候,很麻烦!以4bit位为例:
1+(-1) = 0001 + 1001 = 怎么让计算机去计算?(虽然我们知道该去怎么算,但是计算机不知道!)
我们得创造一种更好的表示方式!于是我们引入了反码:
经过上面的定义,我们再来进行加减法:
1+(-1) = 0001 + 1110 = 1111 => -0 (直接相加,这样就简单多了!)
思考:1111代表-0,0000代表+0,在我们实数的范围内,0有正负之分吗?0既不是正数也不是负数,那么显然这样的表示依然不够合理!根据上面的问题,我们引入了最终的解决方案,那就是补码,定义如下:
比如-7原码为1111,反码为1000,补码就是1001了,-6原码为1110,反码为1001,补码就是1010。所以在补码下,原本的1000就作为新增的最小值-8存在。
所以现在就已经能够想通,-0已经被消除了!我们再来看上面的运算:
1+(-1) = 0001 + 1111 = (1)0000 => +0 (现在无论你怎么算,也不会有-0了!)
所以现在,1111代表的不再是-0,而是-1,相应的,由于消除-0,负数多出来一个可以表示的数(1000拿去表示-8了),那么此时4bit位能够表示的范围是:-8~+7(Java使用的就是补码!)在了解了计算机底层的数据表示形式之后,我们再来学习这些基本数据类型就会很轻松了。
整数类型是最容易理解的类型!既然我们知道了计算机中的二进制数字是如何表示的,那么我们就可以很轻松的以二进制的形式来表达我们十进制的内容了。
在Java中,整数类型包括以下几个:
这里我们来使用一下,其实这几种变量都可以正常表示整数:
public static void main(String[] args) {
short a = 10;
System.out.println(a);
}
因为都可以表示整数,所以说我们可以将小的整数类型值传递给大的整数类型:
public static void main(String[] args) {
short a = 10;
int b = a; //小的类型可以直接传递给表示范围更大的类型
System.out.println(b);
}
反之会出现报错:
这是由于我们在将小的整数类型传递给大的整数类型时发生了隐式类型转换,只要是从存储范围小的类型到存储范围大的类型,都支持隐式类型转换,它可以自动将某种类型的值,转换为另一种类型,比如上面就是将short类型的值转换为了int类型的值。
隐式类型转换不仅可以发生在整数之间,也可以是其他基本数据类型之间,我们后面会逐步介绍。
实际上我们在为变量赋一个常量数值时,也发生了隐式类型转换,比如:
public static void main(String[] args) {
byte b = 10; //这里的整数常量10,实际上默认情况下是int类型,但是由于正好在对应类型可以表示的范围内,所以说直接转换为了byte类型的值
}
由于直接编写的整数常量值默认为int
,这里需要特别注意一下,比如下面这种情况:
按照long
类型的规定,实际上是可以表示这么大的数字的,但是为什么这里报错了呢?这是因为我们直接在代码中写的常量数字,默认情况下就是int
类型,这么大肯定是表示不下的,如果需要将其表示为一个long类型的常量数字,那么需要在后面添加大写或是小写的L
才可以。
public static void main(String[] args) {
long a = 922337203685477580L; //这样就可以正常编译通过了
}
当然,针对于这种很长的数字,为了提升辨识度,我们可以使用下划线分割每一位:
public static void main(String[] args) {
int a = 1_000_000; //当然这里依然表示的是1000000,没什么区别,但是辨识度会更高
}
我们也可以以8进制或是16进制表示一个常量值:
public static void main(String[] args) {
System.out.println(0xA);
System.out.println(012);
}
0x
开头的都是十六进制表示法,十六进制满16进一,但是由于我们的数学只提供了0-9这十个数字,10、11、12...15该如何表示呢,我们使用英文字母A按照顺序开始表示,A表示10、B表示11...F表示15。比如上面的0xA实际上就是我们十进制中的10。我们最后再来看一个问题:
public static void main(String[] args) {
int a = 2147483647; //int最大值
a = a + 1; //继续加
System.out.println(a);
}
此时a的值已经来到了int
类型所能表示的最大值了,那么如果此时再继续+1
,各位小伙伴觉得会发生什么?可以看到结果很奇怪:
什么情况???怎么正数加1还变成负数了?请各位小伙伴回想一下我们之前讲解的原码、反码和补码。
我们先来看看,当int为最大值时,二进制表示形式为什么:
那么此时如果加1,会进位成:
各位想一想,符号位为1,那么此时表示的不就是一个负数了吗?我们回想一下负数的补码表示规则,瞬间就能明白了,这不就是补码形式下的最小值了吗?
所以说最后的结果就是int
类型的最小值:-2147483648,是不是感觉了解底层原理会更容易理解这是为什么。
前面我们介绍了整数类型,我们接着来看看浮点类型,在Java中也可以轻松地使用小数。
首先来看看Java中的小数类型包含哪些:
那么小数在计算机中又是如何存放的呢?
根据国际标准 IEEE 754,任意一个二进制浮点数 V 可以表示成下面的形式:
V = (-1)^S \times M \times 2^E
比如, 对于十进制的 5.25 对应的二进制为:101.01,相当于:1.0101 \times 2^2。所以,S 为 0,M 为 1.0101,E 为 2。因此,对于浮点类型,最大值和最小值不仅取决于符号和尾数,还有它的阶码,所以浮点类型的大致取值范围:
我们可以直接创建浮点类型的变量:
public static void main(String[] args) {
double a = 10.5, b = 66; //整数类型常量也可以隐式转换到浮点类型
}
注意,跟整数类型常量一样,小数类型常量默认都是double
类型,所以说如果我们直接给一个float类型赋值:
由于float
类型的精度不如double
,如果直接给其赋一个double类型的值,会直接出现错误。
同样的,我们可以给常量后面添加大写或小写的F来表示这是一个float
类型的常量值:
public static void main(String[] args) {
float f = 9.9F; //这样就可以正常编译通过了
}
但是反之,由于double
精度更大,所以说可以直接接收float
类型的值:
public static void main(String[] args) {
float f = 9.9F;
double a = f; //隐式类型转换为double值
System.out.println(a);
}
只不过由于精度问题,最后的打印结果:
这种情况是正常的,因为浮点类型并不保证能够精确计算,我们会在下一章介绍 BigDecimal 和 BigInteger,其中BigDecimal更适合需要精确计算的场景。
我们最后来看看下面的例子:
public static void main(String[] args) {
long l = 21731371236768L;
float f = l; //这里能编译通过吗?
System.out.println(f);
}
此时我们发现,long
类型的值居然可以直接丢给float
类型隐式类型转换,很明显float
只有32个bit位,而long
有足足64个,这是什么情况?怎么大的还可以隐式转换为小的?这是因为虽然float
空间没有那么大,但是由于是浮点类型,指数可以变化,最大的数值表示范围实际上是大于long
类型的,虽然会丢失精度,但是确实可以表示这么大的数。
所以说我们来总结一下隐式类型转换规则:byte→short(char)→int→long→float→double