一.流程控制与数组
1.顺序结构
任何编程语言中个最常见的程序结构就是顺序结构。顺序结构就是程序从上到下的逐行进行执行,中间没有任何判断和跳转。
2.分支结构
java中提供了两种常见的分支控制结构:if语句和switch语句,其中if语句使用布尔表达式或者布尔值作为分支条件来进行分支控制,而switch语句则用于对多个整型值进行匹配,从而实现分支控制。
2.1 if语句
第一种形式
if (logic expresstion )
{
statement…
}
第二种形式
if (logic expresstion )
{
statement…
}
else
{
statement…
}
第三种形式
if (logic expresstion )
{
statement…
}
else if(logic expresstion )
{
statement…
}
…//可以有零个或多个else if 语句
else//最后的else语句也可以省略
{
statement…
}
2.2 switch分支语句(Java 11改进)
switch语句由一个控制表达式和多个case标签组成,和if语句不同的是,switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种整数类型,枚举类型和java.lang.String类型(从java7才允许),不能是boolean类型。
switch往往需要在case标签后紧跟一个代码块,case标签作为这个代码块的标识。switch语句的语法格式如下:
switch(expression)
{
case condition1:
{
statement(s)
break;
}
case condition2:
{
statement(s)
break;
}
…
case conditionN:
{
statement(s)
break;
}
default:
{
statement(s)
}
}
3.循环结构
循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体。
循环语句可能包括如下四个部分:初始化语句,循环条件,循环体,迭代语句。
3.1 while循环语句
语法格式:
【init_statement】
while{test_expression}
{
statement;
【init_statement】
}
3.2 do while循环语句
语法格式:
【init_statement】
do
{
statement;
【init_statement】
}
while{test_expression};
3.3 for循环
语法格式:
for(【init_statement】;【test_expresstion】;【iteration_statement】)
{
statement;
}
3.4 嵌套循环
把一个循环放在另一个循环体内,就可以形成嵌套循环。
4.控制循环结构
Java语言中没有提供goto来控制程序的跳转,这种做法提高了流程的可读性,但降低了程序流程控制的灵活性。为了弥补这种不足,Java提供了continue和break来控制循环结构。除此之外,return可以结束整个方法,当然也就结束了一次循环。
4.1 使用break结束循环
某些时候需要强行终止循环,而不是等到循环条件为false时才退出循环,此时可以使用break来实现这个功能。
break用于完全结束一个循环,跳出循环体,不论何种循环,一旦在循环体中遇到break,系统将完全结束该循环,开始执行循环之后的代码。
4.2 使用continue忽略本次循环剩下语句
continue 的功能和break有些相似,区别是continue只是忽略本次循环剩下语句,接着开始下一次循环,并不会终止循环,而break是完全终止循环本身。
4.3 使用return结束方法
return并不是专门用于结束循环的,return的功能是结束一个方法,当一个方法执行到return语句时,这个方法将被结束。一旦在循环体中执行到一个return,return就会结束该方法,循环也自然随之结束。
5.数组
数组是编程中最常见的一种数据结构,可用于存储多个数据,每个数组元素存放一个数据,通常可以通过数组元素的索引来访问数组元素,包括为数组元素赋值和取出数组元素的值。
数组的定义:
Java支持两种定义数组的方式
Type[] arrayName; (推荐使用这一种,因为这一种不但有更好的语意,还有更好的可读性。更容易理解这是定义一个变量,变量名为arrayName,而变量类型为Type[],这是一种新类型,例如int是基本数据类型,而int[]是引用数据类型。)
Type arrayName[]; (这种方法更像是定义了一个类型为Type的变量,变量名为arrayName[],与真实的含义相去甚远。)
注:定义数组时不能指定数组的长度。(原因:数组是一种引用类型的变量,因此当使用它定义一个变量的时候,仅仅表示定义了一个引用变量,也就是定义了一个指针,这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度。)
同时,由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也无法使用,只有对数组进行初始化之后才可以开始使用。
5.1 数组的初始化
Java中的数组必须先初始化,然后才可以使用。所谓初始化,就是为数组分配有效的存储空间,并为每个数组元素赋初始值。
(即便这个内存空间存储的内容为空,这个空也是一个值null,不管以哪种方式进行数组的初始化,只要为数组元素分配了内存空间,数组元素就有了初始值。数组元素获得初始值的两种方式,一种由系统自动分配,另一种由程序员指定。)
数组的初始化有如下两种方式:
1:静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度。
2:动态初始化:初始化时程序员指制定数组长度,由系统为数组元素分配初始值。
5.1.1 静态初始化
静态初始化的语法格式如下:
arrayName = new type[] {element1,element2,element3…};
此时的type就是数组元素的数据类型,必须与定义数组变量时所使用的type相同,也可以是定义数组时所指定的type的子类。
除此之外,静态初始化还有如下简化的语法格式:
type[] arrayName = {element1,element2,element3…};
(只有在定义数组的同时执行数组初始化才支持使用简化的静态初始化。)
在实际开发过程中,可能更习惯使用数组定义和数组初始化同时完成,代码如下:
int[] a = {5,6,7,9};
5.1.2 动态初始化
动态初始化只指定数组的长度,由系统为每个数组元素指定初始值,动态初始化的语法格式如下:
arrayName = new type[length];
示例:
int[] prices = new int[5];
Object[] books = new String[4];
初始化数组元素时的类型是定义数组时元素类型的子类。
执行动态初始化时,程序员只需指定数组的疮毒,即为每个数组元素指定所需的内存空间,系统将负责为这些数组元素分配初始值,分配规则如下:
1.数组元素为整数类型,则数组元素的值为0。
2.数组元素为浮点类型,则数组元素的值为0.0。
3.数组元素为字符类型,则数组元素的值为"\u0000"。
4.数组元素为布尔类型,则数组元素的值为false。
5.数组元素为引用类型,则数组元素的值为null。
(注,不要在进行数组初始化时,既指定数组的长度,也为数组元素分配初始值)
5.2 数组的使用
Java语言的数组索引是从0开始的,也就是说,第一个数组元素的索引值为0,最后一个数组元素的索引值为数组元素长度减1.
示例:
1:输出数组元素的值:System.out.println(objArr[1]);
2:为指定数组元素赋值:objArr2[0] = “Spring”;
注:如果访问数组元素时指定的索引值小于0,或者大于等于数组的长度,编译程序不会出现任何错误,但运行时会出现异常:
java.lang.ArrayIndexOutOfBoundsException:N(数组索引越界异常),异常信息后的N就是程序员试图访问的数组索引。
5.3 foreach循环
从Java 5之后,Java提供了一种更简单的循环:foreach循环,这种循环遍历数组和集合,无需获得数组和集合长度,无须根据索引来访问数组元素和几何元素,foreach循环自动遍历数组和集合的每个元素。
foreach循环的语法格式如下:
for{type variableName : array | collection}
{
// variableName 自动迭代访问每个元素…
}
(variableName是一个形参名,foeach循环自动将数组元素,集合元素依次赋给该变量。)
注:当使用foreach循环来迭代输出数组元素或集合元素时,通常不要对循环变量进行赋值,虽然赋值在语法上是允许的,但是没有太大的实际意义,且极容易引起错误。
6:深入数组
6.1 内存中的数组
数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当该引用只想有效内存后,才可通过数组变量来访问数组元素。
实际的数组对象被存储在堆(heap)内存中
而引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中。
栈内存:当一个方法被执行时,每个方法都会建立自己的内存栈,在这个个方法内定义的变量将会逐个放入这块栈内存中,随着方法的执行结束,这个方法的内存栈也将自然销毁。因此,所有字啊方法中定义的局部变量都是存放在栈内存中的。
堆内存:在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随着方法的结束而销毁,及时方法结束后,这个对象还可能被另一个引用变量所引用(在方法的参数传递时很常见)则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收器才会在合适的时候回收它。
只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这种操作会让人产生数组长度可变的错觉。
public class demoone {
public static void main(String[] args) {
//定义并初始化数组,使用静态初始化
int[] a = {5,7,20};
//定义并初始化数组,使用动态初始化
int[] b = new int[4];
//输出数组b的长度
System.out.println("b数组的长度为:"+b.length);
//循环输出a数组的元素
for (int as:a
) {
System.out.print(as+" ");
}
System.out.println();
//循环输出b数组的元素
for (int bs:b
) {
System.out.print(bs+" ");
}
System.out.println();
//因为a是int[]类型,b也是int[]类型,所以可以将a的值赋给b
//也就是让b引用指向a引用指向的数组
b=a;
//再次输出b数组的长度
System.out.println("b数组的长度为:"+b.length);
}
}
6.2 基本类型数组的初始化
对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为数组分配内存空间,然后直接将数组元素的值存入对应数组元素当中。
6.3 引用类型数组的初始化
引用类型的数组元素是引用,因此情况变得更加复杂,每个数组元素里存储的还是引用,它指向另一块内存,这块内存中存储了有效数据。
class Person {
public int age;//年龄
public double height;//身高
//定义一个info方法
public void info(){
System.out.println("我的年龄是:"+age+"我的身高是:"+height);
}
}
public class demoone{
public static void main(String[] args) {
//定义一个students数组变量,其类型是Person[]
Person[] students;
//执行动态初始化
students = new Person[2];
//创建一个Person实例,并将这个Person实例赋给zhang变量
var zhang = new Person();
//为zhang所引用的Person对象的age、height赋值
zhang.age=15;
zhang.height=158;
//创建一个Person实例,并将这个Person实例赋给lee
var lee = new Person();
//为lee所引用的Person对象的age、height赋值
lee.age = 16;
lee.height = 161;
//将zhang变量的值赋给第一个数组元素
students[0] = zhang;
//将lee变量的值赋给第二个数组元素
students[1] = lee;
//下面两行代码的结果完全一样,因为lee和student[1]指向的是同一个Person实例
lee.info();
students[1].info();
}
}
6.4 没有多维数组
Java语言中提供了支持多维数组的语法,但是从数组底层的运行机制上来看,没有多维数组。
Java语言中的数组类型是引用数组,因此数组变量其实是一个引用,这个引用指向真实的数组内存,数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。
如果希望数组元素也是一个引用,而且指向int数组的引用,则可以把type具体成int[],如果把int这个类型扩大到Java的所有数据类型(不包括数组类型),则出现了定义二维数组的语法:
type[] [] arrName;
arrName = new type[length]【】
Java中采用这样的语法格式来定义二维数组,但它的实质还是一维数组,只是其数组元素也是引用,数组元素中保存的引用指向一维数组。
public class demoone {
public static void main(String[] args) {
//定义一个二维数组
int[] [] a;
//把a当成一位数组进行初始化,初始化a是一个长度为4的数组
//a数组的数组元素又是引用类型
a = new int[4][];
//把a当成一维数组,遍历a数组中的每个数组元素
for (int i = 0,len = a.length; i<len;i++) {
System.out.print(a[i]+" ");
}
System.out.println();
//初始化a数组的第一个元素
a[0] = new int[2];
//访问a数组的第一个元素所指数组的第二个元素
a[0][1]=6;
//a数组的第一个元素是一个一维数组,遍历这个一维数组
for (int i = 0,len = a[0].length; i <len; i++) {
System.out.println(a[0][i]);
}
}
}
6.5 操作数组的工具类
Java提供的Arrays类里包含一些static修饰的方法可以直接操作数组,这个Array类里包括了如下几个static修饰的方法。(static中修饰的方法可以直接通过类名调用。)
- int binarySearch(type[] a,type key)
使用二分查找key元素值在a数组中出现的索引,如果a数组中不包含key元素值,则返回负数。调用该方法时要求数组中元素已经按升序排列,这样才能得到正确结果。
2) int binarySearch(type[] a,int fromIndex , int toIndex , type key)
这个方法与前一个方法类似,但它只搜索a数组中fromIndex到toIndex索引的元素,调用该方法时要求数组中元素已经按升序排列,这样才能得到正确结果。
3) type[] copyOf(type[] original , int length)
这个方法会把original数组复制成一个新数组,其中length是数组的长度,如果length小于original数组的长度,则新数组就是原数组的前面length个元素;如果length大于original数组的长度,则新数组的前面元素就是原数组的所有元素,后面补充0(数值类型)、false(布尔类型)、或者null(引用类型);
4) type[] copyOfRange(type[] original, int from,int to)
这个歌方法与前面的方法类似,但这个方法只复制original数组的from索引到to索引的元素
5)boolean equals(type[] a,type[] a2)
如果a数组和a2数组的数组元素也一一相同,该方法将返回true。
5) void fill(type[] a, type val)
该方法会把a数组的所有元素都赋值为val
- void fill(type[] a, int fromIndex,int toIndex, type val)
该方法与前一个方法相同,区别只是该方法仅将a数组的fromIndex到toIndex索引的数组元素赋值为val
- void sort(type[] a)
该方法对a数组的元素进行排序
- void sort(type[] a ,int fromIndex,int toIndex)
该方法仅对fromIndex到toIndex的数组元素进行排序
- String toString(type[] a)
该方法将一个数组转换成一个字符串,该方法按顺序把多个数组元素连缀在一起,多个数组元素使用英文逗号和空格隔开。
Java8中增强了Arrays类的功能,为Arrays类增加了一些工具方法,这些工具方法可以充分利用CPU并行的能力来提高设值、排序的能力。