Java学习笔记:2022年1月6日(补充)
Java学习笔记:2022年1月6日(补充)
摘要:这篇笔记主要记录了2022年1月6日下午的笔记,主要内容为Java语言中的基础操作,以及基础知识点,了解这些后基本上就可以使用Java写算法了。
1.语句和语句块
Java语言是由很多语句指令构成的,这些语句指令通常是用分号结尾,Java语言中存在的最多的就是变量声明语句以及变量赋值语句,还有循环语句,这些语句总体上构成了所有的Java代码。
for(int i = 0; i < 10 ; i++){
System.out.println("See you Cowboy Bebop!");
}
以上的语句中就包含了变量声明语句,赋值语句,输出语句以及循环语句,我们注意到在for循环后边跟着一个大括号,这个大括号里边的东西通常被成为循环体,实际上它也可以被称为语句块,在Java中,被大括号括起来的部分就是一个语句块,他们可以被看做一个单独的个体,而大括号括起来的范围可以被称之为作用域,任何在大括号中被声明的变量在大括号之外或者是在自己的作用域之外都无法使用,这是Java的一个特性,这一点会在之后的Java运行时详解中进行详细阐述。
2.循环语句
1.for循环
for循环是最基本的循环语句,其使用方法为for(第一部分;第二部分;第三部分){循环体},在整个循环语句中,第一部分是最先执行的,其次是第二部分,然后是循环体部分,最后才是第三部分被执行。其中第一部分通常是一些变量初始化以及定义,第二部分为循环条件,第三部分可以写在循环语句中也可以写在循环体中,第三部分通常被用来当做控制循环的相关变量的变化区,如下:
for(int i = 0; i < 10 ; i++){
System.out.println("See you Cowboy Bebop!");
}//第三部分通常为第一部分定义的变量的自增,而第二部分则是使用第一部分定义的变量根据第三部分的控制进而控制整个循环体的循环与否
如个位所见的是第二部分通常是一个布尔表达式,其结果就是真或假,当结果为真时,这个循环会继续向下进行,当结果为假,这个循环会停止并结束。
for(int i = 0; i < 10 ; ){
System.out.println("See you Cowboy Bebop!");
i++;
}//上边的代码和这个代码是等效的,第三部分实际上是最后运行的,将i++这个自增语句写在第三部分相当于将它写在循环体的末尾
因此我们其实可以通过这个特性更加灵活的改变for循环的细节,比如我们可以不再第三部分写标志位自增语句,而是写在循环体中的不同位置,这样可以灵活的让for循环得到原本以外的我们所需要的性质。
2.while循环
实际上while循环可以理解为一个最简单的循环,因为它只有一个while,while就有“当...的时候”的意思,因此它被拿来当循环也是很好理解的,while循环的用法是while(循环条件){循环体}。它的小括号中是和for循环中第二部分一样的控制循环进行与否的布尔表达式,大括号内则是循环体,通常while循环的进行也需要一个标志位进行控制,这个标志位的声明充满了灵活性,通常是根据我们的需求进行设定。
while(true){//true代表永远为真,当while循环的循环条件为true时,这个循环通常没有办法停止
int 1 = 0;//在循环体内部进行标志位的声明
i++;
if(i>10)
break;
}
如上边代码所示,我们在while循环中需要自己书写停止机制,当然i变量的声明可以在循环体外边也可以在循环体里边,这些就比较灵活了。但是我们需要注意的是这里使用到了一个重要的语句break,这个是强行停止语句,通常来讲while循环的循环控制语句部分如果没有一个写好的布尔表达式,我们通常使用它来进行循环的停止,break会导致循环立即停止并跳出循环,break下边的语句将不再执行。
和break类似的语句还有一个continue语句,此语句可以立即结束当前循环,或者说可以跳过当前循环,也就是像break一样,直接忽略之后的所有语句,但是和break语句不同的地方在于,continue语句并不会终止循环,它会让循环体直接开始下一轮循环,也就是立即终止本轮循环并直接开始下一轮循环。
需要注意的是在for循环中使用continue并不会影响到第三部分的执行,尽管第三部分是在最后执行,但是它仍然不真的属于循环体,在for循环中使用continue会忽略掉循环体中的语句,但是对于在最后执行的第三部分,for循环仍然会执行。
for (int i = 0; i < 10; i++,System.out.println(i)) {
if(i == 3)
continue;
}//输出结果为1,2,3,4,5,6,7,8,9,10。可见i等于3时的countnue语句并没有影响到第三部分的执行,这是因为for的循环体和第三部分不是一个区域。
3.do while循环
do while循环和while循环实际上有不小的差别,其使用方式为do{循环体}while(循环条件);。是不是很奇怪,do while循环中的循环体或者说循环语句会被强制执行一次,也就是说无论如何,这个循环中的循环体都会被至少执行一次。
int i = 0;
do{
if(i>10);
break;
System.out.println(i);
i++;
}while(false);
上边的代码输出结果为0,即使while循环的循环条件上来就是0,它也会被强制执行一遍,这就是do while循环。这里有一个重点,在Java中我们难免会计算一些数学相关的式子,如等差数列求和之类的,对于这些式子,通常有更加简便的数学公式,不使用循环就可以直接一步得到,对于这种情况,我们尽量使用公式,而不是使用循环增加计算机负担。
3.Switch击穿问题(面试点)
在Java中,存在一种选择语句,我们称之为switch语句,这种语句的使用方法如下:
switch(x){
case 1:
System.out.println(1);
break;
case 2:
System.out.println(2);
break;
case 3:
System.out.println(3);
break;
case 4:
System.out.println(4);
break;
case 5:
System.out.println(5);
break;
default:
System.out.println(6);
break;
}
switch语句是一种分支语句,在特定的情况下使用效果非常好,它的使用方法是在switch中指定一个已经存在的变量,这里为x,当语句执行到这里时,这个语句就会将x变量中的数值和他下边case中列举出来的数值进行对比,一旦对比成功,语句就会从对比成功的位置开始执行。注意这句话,语句就会从对比成功的位置开始执行,这里就是switch击穿问题的关键所在,我们看到在每个case后边都有一个break语句在进行“兜底”,这个break语句实际上就是防止击穿的机制,如果没有break的话,在某次匹配成功之后,这个语句就会一直向下执行,进而将下边的不符合匹配的答案也进行输出。如:
switch(x){
case 1:
System.out.println(1);
break;
case 2:
System.out.println(2);
//break;这里将break注释了
case 3:
System.out.println(3);
break;
case 4:
System.out.println(4);
break;
case 5:
System.out.println(5);
break;
default:
System.out.println(6);
break;
}
/*
switch语法就是选中一个变量,然后在其作用域和case进行对比,若对比成功则执行下面的语句,执行到break就会强行停止。
如果忘记写break,就会发生switch击穿,就会继续向下执行所有的东西,也就是说一旦匹配到,就会开始执行,直到break为止,它会不顾所有的一直往下执行,即使不匹配的也会执行,直到break为止。
*/
上边语句的执行结果就变成了2,3。语句在2处匹配成功,然后就开始向下执行,因为没有了break进行兜底,程序便直接忽略了case 3这个匹配语句并直接执行了它的语句块,直到遇见3语句块中的break才会停止。switch击穿就是在switch语句中,由于某处忽略了break语句,导致程序执行一直向下执行,不能按照设计好的逻辑正常运行。有点类似刹不住车了。另外一点需要注意的是case中可以进行匹配的变量类型为char、byte、 short 或 int 的常量表达式,也就是一些算式也是可以的,同时还可以匹配枚举型常量。从 Java SE 7开始, case 标签还可以是字符串字面量。字符串字面量就是字符串,case可以进行字符串的匹配,但是其他更复杂的引用类型就不可以了。switch可选类型目前只有这几种,这个也是一个面试题。
4.break和continue
break只能打断当前包裹自己的for循环,也就是打算一层循环。
continue是终止本次循环,或者说跳过本次循环,直接进行下一次循环。
5.大数类型
当需要真正意义上的大数精确运算时,需要大数类型,有BigInteger和BigDecimal,BigInteger专门收纳大数整数,BigDecimal专门收纳大数小数。他们的实现机制都不是普通的计算机逻辑运算,而是基于字符串的模拟运算,这两个类中提供相关的运算操作函数,总体上讲还是非常方便的。
6.数组
之前已经提到过,在Java中数组是一种非常重要的类型,需要注意的是,数组是引用类型,而非是基本类型,首先根据数组不是定长的就可以判断出数组不是基本类型这一事实。数组的定义方式有多种:
int[] a = new int[10];
int[] b = {1,2,3,4,5,6,7,10};
int[] c = new int[]{1,3,4,5,6,7,10};
基本类型的特点是单个变量空间占用不变,我们在不停的声明不同大小的同名数组时,不会报错,说明它的大小可以自如的变化。数组的大小可以动态变化,而基本类型的大小是固定的,因此可以知道数组不是基本类型,而是属于引用类型。关于引用类型,之后在Java运行时中会进行更加详细的解释。
int[] a = new int[10];//注意这种方式声明的数组不是空数组,每个上边都有0
int[] b = {1,2,3,4};
int[] c = new int[]{2,3,4,5};
注意数组a的声明方式,它并没有为数组赋初值,但在java中,只要声明数组,Java程序就会为这个数组赋予初值0,这是和C语言中不同的地方。
int[] a = new int[10];
System.out.println(Arrays.toString(a));
然而对于其他的变量来说,Java并不会为它们赋初值,只有数组类型会被赋予初值。在这里先简要提一句:Java运行时中,基本类型的变量的句柄和其值是相邻的,它们同位于栈区,而引用类型的句柄位于栈区,但是其句柄的值并没有挨着它,和他挨着的是一个地址,这个地址指向堆区中一个地址,这个地址是引用类型的值真实所在的位置。
数组的寻址方式如下:数组的索引从0开始,数组记录的是它开头第一个字符的地址,在里边每个元素都有一个首地址,第一个数的地址就是首地址,所以是首地址+0,其寻址方式就是首地址+n*单位。获取第二个n就是1,因为首地址就是第一个的地址,啥也不用加,所以是0,从零开始。数组中第n个元素就是索引n-1。数组的寻址方式实际上在之前我详细记载过,实际上就是因为数组上的元素长度都一样,只要获取数组元素的长度信息,就可以使用首地址+下标*元素长度的方式进行寻址。这个寻址过程可以直接得到地址信息,非常的快速。
7.二维数组和三维数组
同C语言一样,Java中也可以有二维数组和三维数组,上图为这两种数组的声明和初始化方式。对于这两种数组我们可以理解为:存储数组的数组,也就是说数组的元素同为数组类型,根据上面我们所定义的数组,数组元素在内存上占用的长度是一定的,可是在二维数组和三维数组中,数组中的数组元素的长度可以是不一样的,用这种方式定义的数组叫做不规则数组,也就是子数组长度不同的数组。这是为什么呢?这是因为数组中的子数组并不是存储的这个数组,而是存储了这个子数组的地址,也就是说二维数组中,是存储了多个子数组地址的数组,而三维数组中的子数组,则同为存储了字数组的数组,这个概念非常类似C语言中的重指针,实际上它们的实现方式也类似于重指针。
在变量声明中,等号左侧是句柄右侧是值,基本类型的值和句柄是在一起的,引用类型是指向自己值的地址,引用类型的等于号相当于把自己的指向换掉,java中不存在指针概念,只存在引用类型概念,但实际上引用类型的底层实现就是指针。我们可以理解为:引用类型本身相当于一个指针,或者说有着类似指针的功能。因此我们可以知道,在二维数组中,每个数组元素实际上是一个指向另一个数组的引用类型,这个引用类型的值其实是个地址,而这些地址是等长的,因此他们完全可以被存储在一个数组中,他们指向的数组的长度是否都等长实际上和父数组没有关系,因此可以实现不规则数组。如下是二维数组的内存示意图:
三维数组和二维数组在内存上的存储方式是同理的,三维数组的子数组同二维数组的子数组一样,是元素为另一个数组地址的数组,根据这个道理,我们实际上可以声明出更多维度的数组,但实际上这可能不太实用。
三维数组和二维数组非常类似操作系统中的多级索引,或者树状列表,它们的实现原理实际上就和多级索引以及树状列表类似。
8.引用类型的浅拷贝和深拷贝
字符串类型的底层是用字符数组实现的,它也是引用类型,和数组相当类似,对于引用类型,存在两种类型的拷贝,一种名曰浅拷贝,另一种名曰深拷贝。当我们希望拷贝一个引用类型到另一个变量中去时,使用“=”,如:
String a = "See you Cowboy Bebop!";
String b = a;
这个过程实际上是将a的地址拷贝给了b,b只不过是指向了a指向的字符串,此时二者指向同一个字符串,当我们通过a、b中任意一个变量修改这个字符串时,使用另一个变量访问字符串,会发现使用另一个变量输出出来的字符串也被改动过了,实际上浅拷贝就是拷贝指向,会让其他的引用类型变量指向同一个地址上的值。这种拷贝我们称之为浅拷贝。
另外一种拷贝方式我们称之为深拷贝,深拷贝实际上就是新建一个地址,然后将原变量指向的地址上的值真正的复制到这个地址上,然后再让新变量指向这个新地址,这样我们修改其中一个引用类型变量的值,另外一个不会受到影响,因为二者都有一个自己的真实值。对于深拷贝,我们通常可以自己写,对于String类型的深拷贝,可以使用构造方法进行实现。这点目前还没有深入学习,在以后的学习中我会进行更加深入的研究。
9.排序
在Java中,提供一个现成的快速排序方法,使用Arry类进行调用,调用方法为Arry.sort();这个方法是一个快速排序,在使用Java写算法时可以直接用,非常方便。
10.笔记原文
下面附上我的笔记原文:
大括号括起来的部分就是块,在主类中和主方法并行的大括号是合法的,里边可以写一些操作,这是合法的。
while循环是一个常用的循环语句,while本身就有当...时的意思,因此也就是说当while中的语句为真的时候,就循环
do while循环第一次执行不需要判断,也就是会一定会至少执行一次。
import java.util.*
public class Wt{
public static void main(String[] aaa){
for(int a = 0, b = 9;a < 10; a++){
System.out.println("================");
}
}
}
和
import java.util.*
public class Wt{
public static void main(String[] aaa){
for(int a = 0, b = 9;a < 10; System.out.println("================"),a++){
}
}
}
二者是一样的。
但是循环体内的语句是先于for参数内的第三个位置执行的。
在计算机中,有规律的计算都能归纳为公式,千万不要盲目的用for循环,比如从1加到1万,用高斯定理即可
switch击穿 面试题 !!!!
这是一个重要的面试题
switch是一种分支,
switch(x){
case 1:
System.out.println(1);
break;
case 2:
System.out.println(2);
break;
case 3:
System.out.println(3);
break;
case 4:
System.out.println(4);
break;
case 5:
System.out.println(5);
break;
default:
System.out.println(6);
break;
}
switch语法就是选中一个变量,然后在其作用域和case进行对比,若对比成功则执行下面的语句,执行到break就会强行停止。
如果忘记写break,就会发生switch击穿,就会继续向下执行所有的东西,也就是说一旦匹配到,就会开始执行,直到break为止,它会不顾所有的一直往下执行,即使不匹配的也会执行,知道break为止。
case 标签可以是:
•
类型为 char、byte、 short 或 int 的常量表达式。
•
枚举常量。
•
从 Java SE 7开始, case 标签还可以是字符串字面量。
switch可选类型目前只有这几种,这个也是一个面试题。
字符串字面量就是字符串,case可以进行字符串的匹配,但是其他更复杂的引用类型就不可以了。
break只能打断当前包裹自己的for循环,也就是打算一层循环。
continue是终止本次循环,或者说跳过本次循环,直接进行下一次循环。
当需要真正意义上的大数精确运算时,需要大数类型,有BigInteger和BigDecimal,BigInteger专门收纳大数整数,BigDecimal专门收纳大数小数。
数组时引用类型,不是基本类型,哪怕是int类型数组。
int[] a = new int[10];
基本类型的特点是单个变量空间占用不变,我们在不停的声明不同大小的同名数组时,不会报错,说明它的大小可以自如的变化。
数组的大小可以动态变化,而基本类型的大小是固定的,因此可以知道数组不是基本类型,而是属于引用类型。
数组的声明方式:
int[] a = new int[10];//注意这种方式声明的数组不是空数组,每个上边都有0
int[] b = {1,2,3,4};
int[] c = new int[]{2,3,4,5};
基本类型的句柄和值都是相邻的,而引用类型的值通常不相邻,通常是句柄与一个指针相邻,指针指向连续的空间、
数组的索引从0开始,数组记录的是它开头第一个字符的地址,在里边每个元素都有一个首地址,第一个数的地址就是首地址,所以是首地址+0,其寻址方式就是首地址+n*单位。获取第二个n就是1,因为首地址就是第一个的地址,啥也不用加,所以是0,从零开始。数组中第n个元素就是索引n-1
二维数组
二维数组,三维数组,这些语法都是没问题的。
不规则数组也是合法的
int[][] b = {
{0,0,0},
{89,0},
{0,0,0,0,0}
}
用这种方式定义的数组叫做不规则数组,也就是子数组长度不同的数组
数组声明的方式很灵活很多遍,同时声明过的数组里边的子数组也可以重新声明
等号左侧是句柄右侧是值,基本类型的值和句柄是在一起的,引用类型是指向自己值的地址,引用类型的等于号相当于把自己的指向换掉,java中没有为指针。
在java中引用类型的等于号相当于在切换自己的指向,引用类型并没有和自己的值在一起,引用类型的值是指向一个存储值得地址的,引用类型本身相当于一个指针,或者说有着类似指针的功能。
多维数组和操作系统中的多级索引类似,可能实现起来就是一个东西。
在多维数组中,数组元素中存取的值其实就是存的地址
数组名下存在着地址,这个地址指向它的值实体,二维数组的值实体是一个连续的空间,这空间之中连续排布这一串地址,这些地址则是指向了其他的连续的空间,这些空间中排布的就是数值了。
这个可以理解为空间上的矩阵,也可以理解为多级索引,总体上就是地址的多级指向,类似C语言中的重指针,即指向指针的指针,最终像树一样存取了多个数值。其结构非常类似于树,在整个结构中存取值的其实是叶子结点部分。
对于二维数组的遍历,也是O(1)的时间复杂度,因为都是随机存取,都有地址
对于二维数组中a[3][4]中,a[0] = new一个新的的行为,其实就是新开辟一个空间,然后切换a[0]的指向,C++中的指针切换指向,其实也是这个意思。只要是引用类型的句柄等于什么,一定是在切换它的指向。其他语言也是一样的。
引用类型是拿过地址,基本类型是直接拷贝复制。
其实也就是说只要这个类型是基本类型,那么给它赋值就是直接复制,而如果它是引用类型,就要引用地址。
这一点其实在C++中最为明显,C语言可以用重指针实现多维数组,Java中虽然没有指针,但是其原理十分相似。
字符串数组,字符串也都是运用类型,实际上,一维字符串数组类似于二维数组,其元素都是引用类型
注意数组的修改方式与规则,其实就是根据元素究竟是什么类型而定。
引用类型直接等于号是浅拷贝,浅拷贝就是改变地址指向,深拷贝则是直接再申请一个新空间,将数值拷贝后让新变量指向它
Java中的Arry.sort(),提供一个快速排序的功能,是一个优化的快速排序,打算法时会经常用到这个,很有用!