Java SE学习笔记一

192 阅读18分钟

Java语法规范

所有的Java语句必须以;结尾!

无论是()[]还是{},所有的括号必须一一匹配!

主方法的代码只能写在{}中!

Java基础语法

在学习面向对象之前,我们可以先了解下面向过程的编程思维,如果你之前学习过C语言和Python编程语言就会很轻松!

1. 变量和关键字

1.1. 变量

变量顾名思义就是一个可变的量,例如定义一个int类型的变量(int就是整数类型):


int a = 1;

a = 12;

a = 22;

我们可以随意修改它的值,也可以理解为它的值是随时可变的,我们称为变量。变量可以是类的变量,也可以是方法内部的局部变量(现阶段主要用到的是局部变量,在面向对象部分再详细讲诉类变量)。

Java中的变量和C语言中概念不同,C语言的变量是存放在内存中,Java中的变量是存放在JVM(Java虚拟机)管理的内存中。所有在某些情况下C语言需要我们手动释放内存,而Java会通过JVM自动帮我们清理变量占用的内存。Java和C++语言很相似,但是没有指针,所以Java也被称为C++--。

Java是一种强类型语言,只有在明确定义了变量之后 ,才可以使用。一旦变量被指定为某种数据类型,那么它将始终被认为是对应的数据类型,这一点和JS(Java Script)语言不一样。

定义一个变量的格式如下:


[数据类型] [标识符(变量名称)] = [初始值(可选)]

double a = 1.2;

注意:Java语言有一定的规范,故标识符不能为以下内容:

  • 标识符可以由大小写字母、数字、下划线(_)和美元符号($)组成,但是不能以数字开头。

  • 标识符大小写敏感。

  • 标识符不能出现空格、@、#、+、-、/等符号。

  • 标识符应该使用有意义的名称,做到见名知意,最好以小写字母开头。

  • 标识符符合驼峰命名法。

  • 标识符不可以是true和false。

  • 标识符不能与Java语言的关键字重名。

1.2. 关键字

Java关键字包括基本数据类型、流程控制语句等,在这里了解即可,后续会一一用到,不用死记硬背。

1.3. 常量

常量就是无法修改值的变量,常量的值只能定义一次:


final int a = 1;

a = 11; // 报错

常量的前面必须添加final关键字(C语言里面是const,虽然Java中也有该关键字,但是不能使用)。

将变量定义为常量,只是final关键字的第一个用法,后续会介绍该关键字的其他用法。

1.4. 注释

在编写代码的过程中,我们要养成注释的好习惯,不然等时间长了我们可能连自己写的代码都可能看不懂。注释包括单行注释、多行注释和文档注释:


// 我是单行注释



/*

* 我是多行注释

* 可以注释多行内容

*/



/**

* 我是文档注释

* 可以注释多条内容

*/



// TODO 代办标记

2. 基本数据类型

Java语言中的数据类型分为基本数据类型和引用数据类型,这里基本类型是重中之重,引用类型放到面向对象部分讲解。首先我们需要了解有哪些基本类型。然后,我们需要知道并不是它们的精度如何,能够表示的范围有多大,而是为什么Java会给我们定义这些类型,同时计算机是如何表示这些类型的,这样我们才可以更好的记忆它们的精度和范围。所以,我们通过计算机原理的角度介绍Java的基本数据类型。

2.1. 计算机中的二进制表示

在计算机中,所有的内容都是通过二进制的形式表示的。十进制是以10为进位,如9+1=10;二进制则是通过满2进位(因为我们的计算机是电子的,电平信号只有高位和低位,高电平代表1,低电平代表0,由于只有0和1,因此只能使用二进制表示数字!),比如,1+1=10=2^1+0,一个位也叫一个bit,8个bit称之为一个字节,16个bit称之为一个字,32个bit称之为一个双字,64个bit称之为一个四字,我们一般采用字节来描述数据的大小。

十进制中的6 ==> 在二进制中为110 = 2^2 + 2^1 + 0*2^0

现在有4个bit位,它可以表示的最大和最小数字为多少?

  • 最小:0000 ==> 0

  • 最大: 1111 ==> 2^3 + 2^2 + 2^1 + 2^0 = 8 + 4 + 2 + 1 = 15

在Java中,无论是小数还是整数,它们都要带有符号(和C语言不同,C语言有无符号数)。所有,首位就是我们的符号位,这里还是以4个bit为例,首位作为符号位(1表示负数,0表示正数):

  • 最小: 1111 ==> -(2^2 + 2^1 + 2^0) = -7

  • 最大: 0111 ==> +(2^2 + 2^1 + 2^0) ==> +7 = 7

现在,4个bit能够表示的范围变成了-7~+7,我们称这样的表示方式位原码。

2.2. 计算机中的加减法

2.2.1. 原码

通过上面样例,发现原码虽然表示简单,但是其在做加减法的时候,很麻烦。这里同样4个bit位为例:

1 + (-1) = 0001 + 1001 = ?

虽然我们知道该如何去计算,但是计算机不知道。为了让计算机去计算这个结果,我们得创造一种更好的表示方式,于是引入了反码的概念。

2.2.2. 反码

  • 正数的反码是其本身

  • 负数的反码是在其原码的基础上,符号位不变,其余各个位取反

经过引入反码的概念后,我们再来进行加减法:

1 + (-1) = 0001 + 1110 = 1111 ==> -0

直接相加,这样的计算就简单多了。但是我们需要思考下:1111代表-0,0000代表+0,在我们实数范围内,0是否有正负之分?

0既不是正数,也不是负数,那么上面的显示依然不够合理。

2.2.3. 补码

为了解决上面的问题,我们最终引入了补码的概念,补码的定义如下:

  • 正数的补码就是其本身

  • 负数的补码就是在其原码的基础上,符号位不变,其余各位取反,最后+1(即在反码的基础上+1)

现在就已经能够想通了,-0其实已经被消除了,我们再来看下上面的计算:

1 + (-1) = 0001 + 1111 = (1)0000 = +0

通过补码的形式,无论你怎么计算,也不会出现-0。所以,4个bit位能够表示的范围是:-8~+7(Java使用的就是补码!)

2.3. 整数类型

上节我们了解了计算机中的二进制数字是如何表示的,那我们就可以很轻松的将十进制内容转换为二进制形式。

Java中的整数类型一般包括以下几种:

  • byte 字节型 (8个bit,也就是1个字节)范围:-128~+127

  • short 短整形(16个bit,也就是2个字节)范围:-32768~+32767

  • int 整形(32个bit,也就是4个字节)最常用的类型!

  • long 长整形(64个bit,也就是8个字节)最后需要添加l或L

如果一个数字长度过大,连long都装不下怎么办?Java推荐使用BigInteger类型!

如果一个数字已经达到byte的最大值了,还能继续加吗?为了便于理解,以4bit为例:

0111 + 0011 = 1010 => -6(是的,你没看错,就是这样!)

整数还能使用8进制、16进制表示:

十进制为13 = 八进制表示为015 = 十六进制表示为 0xD = 二进制表示 1101 (在java代码里面不能使用二进制!)

2.4. 字符类型和字符串

在Java中,字符类型表示一个字符:

  • char 字符型(16个bit,也就是2字节,它不带符号!)范围是0 ~ 65535

  • 使用Unicode表示就是:\u0000 ~ \uffff

字符要用单引号''扩起来!比如 char a = '学';

字符其实本质也是数字,但是这些数字通过编码表进行映射,代表了不同的字符,比如字符'a'的ASCII码就是数字97, 'A'的ASCII码就是数字65,所以,char类型其实可以转换为上面的整数类型。

\

Java中的char字符类型采用Unicode编码表(不是ASCII编码!),Unicode编码表不仅包含ASCII编码的所有内容,同时还包括了全世界的语言,ASCII编码只有1字节,而Unicode编码是2字节,能够代表65536种文字,足以包含全世界的文字了!(Java编译出来的字节码文件也是使用Unicode编码的,所以利用这种特性,Java支持中文变量名称、方法名称甚至是类名)

既然char类型只能表示一个字符,那怎么才能包含一句话呢?(关于数组,我们这里先不了解,数组我们放在面向对象章节讲解)

String就是Java中的字符串类型(注意,它是一个final类型的类,创建出来的字符串本质是一个对象,不是我们的基本类型)。顾名思义,字符串代表一串字符,也就是一句完整的话。

字符串用双引号""括起来!比如:String str = "这世界这么多人";

2.5. 小数类型

Java中的小数类型包含两种类型:

  • float 单精度浮点型 (32bit,4字节)最后需要添加f或F

  • double 双精度浮点型(64bit,8字节)

即使double有64bit位数,但仍可能存在精度限制,Java推荐使用BigDecimal进行高精度的计算。

2.6. 布尔类型

布尔类型(boolean)只有truefalse这两种值,也就是非真即假。布尔类型的变量通常用作流程控制判断语句(C语言一般使用0表示false,除0以外的所有数都表示true)。Java中并未明确定义布尔类型占据的空间大小,而是根据不同的JVM会有不同的实现。

3. 类型转换

3.1. 隐式类型转换

隐式类型转换支持字节数小的类型自动转换为字节数大的类型,整数类型自动转换为小数类型,转换规则如下:

  • byte→short(char)→int→long→float→double

问题:为什么long的字节比float大,但还是能转换为float类型呢?因为小数的存储规则让float的最大值比long还大,只是某些位上的精度有可能会丢失!

所以,如下的代码就能够正常运行:


byte b = 19;

short s = b;

int i = s;

long l = i;

float f = l;

double d = f;

System.out.println(d); // 输出 19.0

3.2. 显示类型转换

显示类型转换也叫做强制类型转换,也就是说,违反隐式转换的规则,丢失一定精度强制进行类型转换。


int i = 128;

byte b = (byte)i;

System.out.println(b); //输出 -128

为什么转换结果是-128呢?因为进行强制转换了,丢失了精度!

  • int 类型的128表示:00000000 10000000

  • byte类型转换后表示:xxxxxxxx 10000000 => -128

3.3. 数据类型自动提升

在参与运算时(也可以位于表达式中时,自增自减除外),所有的byte型、short型和char的值将被提升到int型:


byte b = 25;

b = b + 10; // 报错!

System.out.println(b);

这是由 Java虚拟机规范 定义的一个特性,也是为了提高运行的效率。其他的特性还有:

  • 如果一个操作数是double型,计算结果就是double型

  • 如果一个操作数是float型,计算结果就是float型

  • 如果一个操作数是long型,计算结果就是long型

4. 运算符

4.1. 赋值和算术运算符

赋值运算符=是最常用的运算符,其作用就是将等号右边的结果,传递给等号左边的变量,例如:


int a = 1;

int b = 2 + 2;

int c = 3 * 3;

算术运算符也就是我们之前学习的+ - * / %,分别代表加减乘除还有取余,例如:


int a = 1;

int b = 2;

int c = a * b; // 结果为3

需要注意的是,+除了做加法运算外,还可以用作字符串连接符使用:


System.out.println("这世界" + "那么多人"); // 这世界那么多人

当然,字符串也可以直接连接其他类型,但是会全部当做字符串处理:


int a = 1;

int b = 2;

System.out.println("这世界那么多人" + a + b); //这世界那么多人12

System.out.println( a + b + "这世界那么多人");//3这世界那么多人

算术运算符还包括自增++和自减--


int a = 1;

a++;

System.out.println(a); // 输出为2

\

自增自减运算符放在变量的前后的返回值是有区别的:


int a = 2;

System.out.println(a++); // 输出为2 (先返回值,再自增)

System.out.println(a); // 输出为3


int a = 2;

System.out.println(++a); //输出为3 (先自增,再返回值)

System.out.println(a); //输出为3


int a = 2;

int b = 3;

System.out.println(b+++a++); //猜猜看结果是多少? (b++) + (a++)输出为5

我们可以使用扩展的赋值运算符,让代码变得更简洁。扩展的赋值运算符包括+=-=/=*=%=,和自增自减类似,先执行运算,再返回结果,同时自身改变:


int a = 2;

System.out.println(a += 3); //等价于 a = a + 3

4.2. 关系运算符

关系运算符的结果只能是布尔类型,也就是非真即假,关系运算符包括:


> < == // 大于,小于,等于

>= <= != // 大于等于,小于等于,不等于

关系运算符一般只用于基本类型的比较,运算结果只能是boolean:


int a = 10;

int b = 2;

boolean x = a <= b;

System.out.println(x); // 结果为 false

4.3. 逻辑运算符

逻辑运算符两边只能是boolean类型,或是关系/逻辑运算表达式,返回值也只能是布尔类型!

逻辑运算符包括:


&& // 与运算,要求两边同时为true,才能返回true

|| // 或运算,要求两边至少要有一个为true,才能返回true

! // 非运算,一般放在表达式最前面,表示对表达式的结果进行反转

实际案例来看看:


int a = 1;

int b = 2;

boolean x = a < b && a > b;

System.out.println(x); // 结果为false,不可能同时满足这两种情况


int a = 1;

int b = 2;

boolean x = a < b || a >= b;

System.out.println(x); // 结果为true,一定会满足一种条件


int a = 1;

int b = 2;

boolean x = !(a < b); //对结果进行反转,本来应该是true

System.out.println(x); // 结果为false

4.4. 位运算符


& // 按位与,注意,返回的是运算后的同类型值,不是布尔类型!

| // 按位或

^ // 按位异或 0 ^ 0 = 0

~ // 按位非

按位运算实际上是根据值的二进制编码来计算结果,例如按位与,以4bit为例:

0101 & 0101 = 0101 (只有同时为1对应位才得1)

0101 | 1011 = 1111 (只要有一个为1对应位就得1)


int a = 7int b = 15;

System.out.println(a & b); // 0000 0111 & 0000 1111 = 0000 0111 = 7 结果为7

4.5. 三目运算符

三目运算符同样是为了简化代码而诞生的,我们可以根据条件是否满足来决定返回值,格式如下:


int a = 1, b = 2;

String str = a > b ? "中" : "不中"; // 判断条件(只能是boolean,或返回boolean的表达式) ? 满足的返回值 : 不满足的返回值

System.out.println("代码写的中不中?"+str); // 代码写的中不中? 不中

理解三目运算符后,后面的if-else语句理解起来就比较容易了。

5. 流程控制

我们一般写的程序都是从上往下依次运行的,仅仅是这样还不够,我们还需要更加高级的控制语句来帮助我们进行更灵活的控制。比如,判断用户输入的数字,大于0则输出yes,小于等于0则输出no,这时我们就可以通过选择结构来帮助我们完成条件的判断和程序的分支走向。

5.1. 选择结构

选择结构包含if类型和switch类型,选择结构能够帮助我们根据条件判断执行哪一块代码。

5.1.1. if语句

就像上面所说,判断用户输入的数字,大于0则输出yes,小于等于0则输出no,我们首先可以采用if语句来实现这种效果:


if (判断条件) {

// 判断成功执行的内容

} else {

// 判断失败执行的内容

}

// if的内容执行完成后,后面的内容正常执行

其中,else语句不是必须的。

现在用户又提了一个新的需求,要求输入的是1打印yes,输入0,打印no,其他打印null,这样就需要我们进行多种条件的判断了,当然if能进行多分支判断:


if (判断条件1) {

// 判断成功执行的内容

} else if (判断条件2) {

// 再次判断,如果判断成功执行的内容

} else {

//上面的都没成功,只能走这里

}

同样,else语句不是必须的。

现在,又来了一个新的需求,用户输入1之后,在判断用户下一次输入的是什么,如果是1,打印yes,不是就打印no,这样就可以用嵌套if了:


if (判断条件1) {

//前提是判断条件1要成功才能进来!

if (判断条件2) {

//判断成功执行的内容

} else {

//判断失败执行的内容

}

}

5.1.2. switch语句

通过上面可以发现,虽然else-if能解决多分支判断的问题,但是效率实在是太低了,多分支if采用的是逐级向下判断,显然费时费力,那么有没有一种更专业的解决多分支判断问题的语句呢?


switch (判断主体) {

case1:

// 运行xxx

break; //break用于跳出switch语句,不添加会导致程序继续向下运行!

case2:

// 运行xxx

break;

case3:

// 运行xxx

break;

}

在上述语句中,只有判断主体等于case后面的值时,才会执行case中的语句,同时需要使用break来跳出switch语句,否则会继续向下运行!

为什么switch效率更高呢,因为switch采用二分思想进行查找(这也是为什么switch只能判断值相等的原因),能够更快地找到我们想要的结果!

5.2. 循环结构

沸羊羊想向美羊羊表白,于是他在屏幕上打印了520个 "I LOVE YOU",我们用Java该如何实现呢?

5.2.1. for语句

for语句是比较灵活的循环控制语句,一个for语句的定义如下:


for (初始条件; 循环条件; 更新){

// 循环执行的内容

}

// 循环结束后,继续执行

  • 初始条件:循环开始时的条件,一般用于定义控制循环的变量。

  • 循环条件:每轮循环开始之前,进行一次判断,如果满足则继续,不满足则结束,要求为boolean类型或是boolean表达式。

  • 更新:每轮循环结束后都会执行的内容,一般写增量表达式,也可以为减量表达式。

初始条件、循环条件、更新条件不是缺一不可的,甚至三者可以都缺!


for (int i = 0; i < 520; i++){

System.out.println("I LOVE YOU");

}


for(;;){

// 这里的内容将会永远地进行下去!

}

增强for循环在数组时再讲解!

5.2.2. while循环

while循环和for循环类似,但是它更加的简单,只需要添加维持循环的判断条件即可!


while (循环条件) {

// 循环执行的内容

}

和for一样,每次循环开始,当循环条件不满足时,自动退出!有些场景我们希望先执行业务代码再去判断怎么办呢,这时候我们可以使用do-while语句:


do {

// 执行内容

} while (循环条件);

这里一定会先执行do里面的内容,再做判断!

思考:


for(;;){

}



while(true){

}



// 它们的性能谁更高?

6. 面向过程编程实战(基础+算法)

6.1. 打印九九乘法表

简单:将九九乘法表打印到控制台。


public static void main(String[] args) {

for (int i = 0; i <= 9; i++) {

for (int j = 1; j <= i; j++) {

System.out.print(i + "*" + j + "=" + i * j + " ");

}

System.out.println();

}

}

image.png

6.2. 求1000以内的水仙花数

中等:打印1000以内所有满足水仙花的数,“水仙花数”是指一个三位数其各位数字的立方和等于该数本身,例如153是“水仙花数”,因为:153 = 1^3 + 5^3 + 3^3


public static void main(String[] args) {

// 定义标记,水仙花数起始值位0

int count = 0;

// 要找出1000以内的水仙花数,先要遍历其每个数字;因为水仙花数是三位数所以从100开始

for (int i = 100; i < 1000; i++) {

// 取出个位

int g = i % 10;

// 取出十位

int s = i / 10 % 10;

// 取出百位

int b = i / 100;

// 判断每个位上的立方和是否等于它自己,如果是则打印出该数字

if (g * g * g + s * s * s + b * b * b == i) {

// 如果是水仙花数,count加一

count++;

System.out.println(i);

}

}

System.out.println("1000以内水仙花数的个数:" + count);

}

image.png

6.3. 青蛙跳台阶问题

困难:一共有n个台阶,一只青蛙每次只能跳一阶或是两阶,那么一共有多少种跳到顶端的方案?例如n=2,那么一共有两种方案,一次性跳两阶或是每次跳一阶。

递归版本:


public static int fun(int n) {

if (n == 1) {

return 1;

}else if(n==2){

return 2;

}else{

return fun(n-2)+fun(n-1);

}

}

public static void main(String[] args) {

System.out.println(fun(4));

}

非递归版本:


public static int fun(int n) {

if (n == 1 || n == 2) {

return n;

}

int f1 = 1;

int f2 = 2;

int f3 = 0;

for (int i = 3; i <= n; i++) {

f3 = f1 + f2;

f1 = f2;

f2 = f3;

}

return f3;

}

public static void main(String[] args) {

System.out.println(fun(4));

}

image.png

动态规划:其实就是利用,上次得到的结果,给下一次作参考,下一次就能利用上次的结果快速得到结果,依次类推。