Java 执行控制流程
Java 中的控制流程其实和 C 一样,在 Java 中,流程控制会涉及到包括 if-else、while、do-while、for、return、break 以及选择语句 switch
。下面以此进行分析。
条件语句
条件语句可根据不同的条件执行不同的语句。包括 if 条件语句与 switch 多分支语句。
if 条件语句
if 语句可以单独判断表达式的结果,表示表达的执行结果,例如:
int a = 10; if(a > 10){ return true; } return false;
if...else 条件语句
if 语句还可以与 else 连用,通常表现为 如果满足某种条件,就进行某种处理,否则就进行另一种处理。
int a = 10; int b = 11; if(a >= b){ System.out.println("a >= b"); }else{ System.out.println("a < b"); }
if 后的 () 内的表达式必须是 boolean 型的。如果为 true,则执行 if 后的复合语句;如果为 false,则执行 else 后的复合语句。
if...else if 多分支语句
上面中的 if...else 是单分支和两个分支的判断,如果有多个判断条件,就需要使用 if...else if。
int x = 40; if(x > 60) { System.out.println("x的值大于60"); } else if (x > 30) { System.out.println("x的值大于30但小于60"); } else if (x > 0) { System.out.println("x的值大于0但小于30"); } else { System.out.println("x的值小于等于0"); }
switch case多分支语句
一种比 **if...else if ** 语句更优雅的方式是使用 switch
多分支语句,它的示例如下:
switch (week) { case 1: System.out.println("Monday"); break; case 2: System.out.println("Tuesday"); break; case 3: System.out.println("Wednesday"); break; case 4: System.out.println("Thursday"); break; case 5: System.out.println("Friday"); break; case 6: System.out.println("Saturday"); break; case 7: System.out.println("Sunday"); break; default: System.out.println("No Else"); break; }
循环语句
循环语句就是在满足一定的条件下反复执行某一表达式的操作,直到满足循环语句的要求。使用的循环语句主要有 **for、do...while() 、 while **。
while 循环语句
while 循环语句的循环方式为利用一个条件来控制是否要继续反复执行这个语句。while 循环语句的格式如下:
while(布尔值){ 表达式 }
它的含义是,当 (布尔值) 为 true 的时候,执行下面的表达式,布尔值为 false 的时候,结束循环,布尔值其实也是一个表达式,比如:
int a = 10; while(a > 5){ a--; }
do...while 循环
while 与 do...while 循环的唯一区别是 do...while 语句至少执行一次,即使第一次的表达式为 false。而在 while 循环中,如果第一次条件为 false,那么其中的语句根本不会执行。在实际应用中,while 要比 do...while 应用的更广。它的一般形式如下:
int b = 10; // do···while循环语句 do { System.out.println("b == " + b); b--; } while(b == 1);
for 循环语句
for 循环是我们经常使用的循环方式,这种形式会在第一次迭代前进行初始化。它的形式如下:
for(初始化; 布尔表达式; 步进){}
每次迭代前会测试布尔表达式。如果获得的结果是 false,就会执行 for 语句后面的代码;每次循环结束,会按照步进的值执行下一次循环。
逗号操作符
这里不可忽略的一个就是逗号操作符,Java 里唯一用到逗号操作符的就是 for 循环控制语句。在表达式的初始化部分,可以使用一系列的逗号分隔的语句;通过逗号操作符,可以在 for 语句内定义多个变量,但它们必须具有相同的类型。
for(int i = 1,j = i + 10;i < 5;i++, j = j * 2){}
for-each 语句
在 Java JDK 1.5 中还引入了一种更加简洁的、方便对数组和集合进行遍历的方法,即 for-each
语句,例子如下:
int array[] = {7, 8, 9}; for (int arr : array) { System.out.println(arr); }
跳转语句
Java 语言中,有三种跳转语句: break、continue 和 return。
break 语句
break 语句我们在 switch 中已经见到了,它是用于终止循环的操作,实际上 break 语句在for、while、do···while循环语句中,用于强行退出当前循环,例如:
for(int i = 0;i < 10;i++){ if(i == 5){ break; } }
continue 语句
continue 也可以放在循环语句中,它与 break 语句具有相反的效果,它的作用是用于执行下一次循环,而不是退出当前循环,还以上面的例子为主:
for(int i = 0;i < 10;i++){ System.out.printl(" i = " + i ); if(i == 5){ System.out.printl("continue ... "); continue; } }
return 语句
return 语句可以从一个方法返回,并把控制权交给调用它的语句。
public void getName() { return name; }
面向对象
面向对象是学习 Java 一种非常重要的开发思想,但是面向对象并不是 Java 所特有的思想,这里大家不要搞混了。
下面我们来探讨面向对象的思想,面向对象的思想已经逐步取代了过程化的思想 --- 面向过程,Java 是面向对象的高级编程语言,面向对象语言具有如下特征
- 面向对象是一种常见的思想,比较符合人们的思考习惯;
- 面向对象可以将复杂的业务逻辑简单化,增强代码复用性;
- 面向对象具有抽象、封装、继承、多态等特性。
面向对象的编程语言主要有:C++、Java、C#等。
所以必须熟悉面向对象的思想才能编写出 Java 程序。
类也是一种对象
现在我们来认识一个面向对象的新的概念 --- 类,什么是类,它就相当于是一系列对象的抽象,就比如书籍一样,类相当于是书的封面,大多数面向对象的语言都使用 class
来定义类,它告诉你它里面定义的对象都是什么样的,我们一般使用下面来定义类
class ClassName { // body; }
代码段中涉及一个新的概念 //
,这个我们后面会说。上面,你声明了一个 class 类,现在,你就可以使用 new 来创建这个对象
ClassName classname = new ClassName();
一般,类的命名遵循驼峰原则
,它的定义如下:
骆驼式命名法(Camel-Case)又称驼峰式命名法,是电脑程式编写时的一套命名规则(惯例)。正如它的名称 CamelCase 所表示的那样,是指混合使用大小写字母来构成变量和函数的名字。程序员们为了自己的代码能更容易的在同行之间交流,所以多采取统一的可读性比较好的命名方式。
对象的创建
在 Java 中,万事万物都是对象。这句话相信你一定不陌生,尽管一切都看作是对象,但是你操纵的却是一个对象的 引用(reference)
。在这里有一个很形象的比喻:你可以把车钥匙和车看作是一组对象引用和对象的组合。当你想要开车的时候,你首先需要拿出车钥匙点击开锁的选项,停车时,你需要点击加锁来锁车。车钥匙相当于就是引用,车就是对象,由车钥匙来驱动车的加锁和开锁。并且,即使没有车的存在,车钥匙也是一个独立存在的实体,也就是说,你有一个对象引用,但你不一定需要一个对象与之关联,也就是
Car carKey;
这里创建的只是引用,而并非对象,但是如果你想要使用 s 这个引用时,会返回一个异常,告诉你需要一个对象来和这个引用进行关联。一种安全的做法是,在创建对象引用时同时把一个对象赋给它。
Car carKey = new Car();
在 Java 中,一旦创建了一个引用,就希望它能与一个新的对象进行关联,通常使用 new
操作符来实现这一目的。new 的意思是,给我一个新对象
,如果你不想相亲,自己 new 一个对象就好了。祝你下辈子幸福。
属性和方法
类一个最基本的要素就是有属性和方法。
属性也被称为字段,它是类的重要组成部分,属性可以是任意类型的对象,也可以是基本数据类型。例如下
class A{ int a; Apple apple; }
类中还应该包括方法,方法表示的是 做某些事情的方式。方法其实就是函数,只不过 Java 习惯把函数称为方法。这种叫法也体现了面向对象的概念。
方法的基本组成包括 方法名称、参数、返回值和方法体, 下面是它的示例:
public int getResult(){ // ... return 1; }
其中,getResult
就是方法名称、()
里面表示方法接收的参数、return
表示方法的返回值。有一种特殊的参数类型 --- void
表示方法无返回值。{}
包含的代码段被称为方法体。
构造方法
在 Java 中,有一种特殊的方法被称为 构造方法
,也被称为构造函数、构造器等。在 Java 中,通过提供这个构造器,来确保每个对象都被初始化。构造方法只能在对象的创建时期调用一次,保证了对象初始化的进行。构造方法比较特殊,它没有参数类型和返回值,它的名称要和类名保持一致,并且构造方法可以有多个,下面是一个构造方法的示例:
class Apple { int sum; String color; public Apple(){} public Apple(int sum){} public Apple(String color){} public Apple(int sum,String color){} }
上面定义了一个 Apple 类,你会发现这个 Apple 类没有参数类型和返回值,并且有多个以 Apple 同名的方法,而且各个 Apple 的参数列表都不一样,这其实是一种多态的体现,我们后面会说。在定义完成构造方法后,我们就能够创建 Apple 对象了。
class createApple { public static void main(String[] args) { Apple apple1 = new Apple(); Apple apple2 = new Apple(1); Apple apple3 = new Apple("red"); Apple apple4 = new Apple(2,"color"); } }
如上面所示,我们定义了四个 Apple 对象,并调用了 Apple 的四种不同的构造方法,其中,不加任何参数的构造方法被称为默认的构造方法,也就是
Apple apple1 = new Apple();
如果类中没有定义任何构造方法,那么 JVM 会为你自动生成一个构造方法,如下:
class Apple { int sum; String color; } class createApple { public static void main(String[] args) { Apple apple1 = new Apple(); } }
上面代码不会发生编译错误,因为 Apple 对象包含了一个默认的构造方法。
默认的构造方法也被称为默认构造器或者无参构造器。
这里需要注意一点的是,即使 JVM 会为你默认添加一个无参的构造器,但是如果你手动定义了任何一个构造方法,JVM 就不再为你提供默认的构造器,你必须手动指定,否则会出现编译错误。
显示的错误是,必须提供 Apple 带有 int 参数的构造函数,而默认的无参构造函数没有被允许使用。
方法重载
在 Java 中一个很重要的概念是方法的重载,它是类名的不同表现形式。我们上面说到了构造函数,其实构造函数也是重载的一种。另外一种就是方法的重载
public class Apple { int sum; String color; public Apple(){} public Apple(int sum){} public int getApple(int num){ return 1; } public String getApple(String color){ return "color"; } }
如上面所示,就有两种重载的方式,一种是 Apple 构造函数的重载,一种是 getApple 方法的重载。
但是这样就涉及到一个问题,要是有几个相同的名字,Java 如何知道你调用的是哪个方法呢?这里记住一点即可,每个重载的方法都有独一无二的参数列表。其中包括参数的类型、顺序、参数数量等,满足一种一个因素就构成了重载的必要条件。
请记住下面重载的条件
- 方法名称必须相同。
- 参数列表必须不同(个数不同、或类型不同、参数类型排列顺序不同等)。
- 方法的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为方法的重载。
- 重载是发生在编译时的,因为编译器可以根据参数的类型来选择使用哪个方法。
方法的重写
方法的重写与重载虽然名字很相似,但却完全是不同的东西。方法重写的描述是对子类和父类
之间的。而重载指的是同一类中的。例如如下代码
class Fruit { public void eat(){ System.out.printl('eat fruit'); } } class Apple extends Fruit{ @Override public void eat(){ System.out.printl('eat apple'); } }
上面这段代码描述的就是重写的代码,你可以看到,子类 Apple 中的方法和父类 Fruit 中的方法同名,所以,我们能够推断出重写的原则
- 重写的方法必须要和父类保持一致,包括返回值类型,方法名,参数列表 也都一样。
- 重写的方法可以使用
@Override
注解来标识 - 子类中重写方法的访问权限不能低于父类中方法的访问权限。
初始化
类的初始化
上面我们创建出来了一个 Car 这个对象,其实在使用 new 关键字创建一个对象的时候,其实是调用了这个对象无参数的构造方法进行的初始化,也就是如下这段代码
class Car{ public Car(){} }
这个无参数的构造函数可以隐藏,由 JVM 自动添加。也就是说,构造函数能够确保类的初始化。
成员初始化
Java 会尽量保证每个变量在使用前都会获得初始化,初始化涉及两种初始化。
- 一种是编译器默认指定的字段初始化,基本数据类型的初始化
- 一种是其他对象类型的初始化,String 也是一种对象,对象的初始值都为
null
,其中也包括基本类型的包装类。 - 一种是指定数值的初始化,例如:
int a = 11
也就是说, 指定 a 的初始化值不是 0 ,而是 11。其他基本类型和对象类型也是一样的。
构造器初始化
可以利用构造器来对某些方法和某些动作进行初始化,确定初始值,例如
public class Counter{ int i; public Counter(){ i = 11; } }
利用构造函数,能够把 i 的值初始化为 11。
初始化顺序
首先先来看一下有哪些需要探讨的初始化顺序
- 静态属性:static 开头定义的属性
- 静态方法块:static {} 包起来的代码块
- 普通属性:非 static 定义的属性
- 普通方法块:{} 包起来的代码块
- 构造函数:类名相同的方法
- 方法:普通方法
public class LifeCycle { // 静态属性 private static String staticField = getStaticField(); // 静态方法块 static { System.out.println(staticField); System.out.println("静态方法块初始化"); } // 普通属性 private String field = getField(); // 普通方法块 { System.out.println(field); } // 构造函数 public LifeCycle() { System.out.println("构造函数初始化"); } public static String getStaticField() { String statiFiled = "Static Field Initial"; return statiFiled; } public static String getField() { String filed = "Field Initial"; return filed; } // 主函数 public static void main(String[] argc) { new LifeCycle(); } }
这段代码的执行结果就反应了它的初始化顺序
输出结果:静态属性初始化、静态方法块初始化、普通属性初始化、普通方法块初始化、构造函数初始化
数组初始化
数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符 []
来定义使用。
一般数组是这么定义的
int[] a1; //或者 int a1[];
两种格式的含义是一样的。
- 直接给每个元素赋值 : int array[4] = {1,2,3,4};
- 给一部分赋值,后面的都为 0 :int array[4] = {1,2};
- 由赋值参数个数决定数组的个数 :int array[] = {1,2};
可变参数列表
Java 中一种数组冷门的用法就是可变参数
,可变参数的定义如下
public int add(int... numbers){ int sum = 0; for(int num : numbers){ sum += num; } return sum; }
然后,你可以使用下面这几种方式进行可变参数的调用
add(); // 不传参数 add(1); // 传递一个参数 add(2,1); // 传递多个参数 add(new int[] {1, 3, 2}); // 传递数组
对象的销毁
虽然 Java 语言是基于 C++ 的,但是它和 C/C++ 一个重要的特征就是不需要手动管理对象的销毁工作。在著名的一书 《深入理解 Java 虚拟机》中提到一个观点
在 Java 中,我们不再需要手动管理对象的销毁,它是由 Java 虚拟机(JVM)进行管理和销毁的。虽然我们不需要手动管理对象,但是你需要知道 对象作用域
这个概念。
对象作用域
许多数语言都有作用域(scope)
这个概念。作用域决定了其内部定义的变量名的可见性和生命周期。在 C、C++ 和 Java 中,作用域通常由 {}
的位置来决定,这也是我们常说的代码块
。例如:
{ int a = 11; { int b = 12; } }
a 变量会在两个 {}
作用域内有效,而 b 变量的值只能在它自己的 {}
内有效。
虽然存在作用域,但是不允许这样写
{ int x = 11; { int x = 12; } }
这种写法在 C/C++ 中是可以的,但是在 Java 中不允许这样写,因为 Java 设计者认为这样写会导致程序混乱。
###this 和 super
this 和 super 都是 Java 中的关键字。
this 表示的当前对象,this 可以调用方法、调用属性和指向对象本身。this 在 Java 中的使用一般有三种:指向当前对象
public class Apple { int i = 0; Apple eatApple(){ i++; return this; } public static void main(String[] args) { Apple apple = new Apple(); apple.eatApple().eatApple(); } }
这段代码比较精妙,精妙在哪呢?我一个 eatApple() 方法竟然可以调用多次,你在后面还可以继续调用,这就很神奇了,为啥呢?其实就是 this 在作祟了,我在 eatApple
方法中加了一个 return this
的返回值,也就是说哪个对象调用 eatApple 方法都能返回对象的自身。
this 还可以修饰属性,最常见的就是在构造方法中使用 this ,如下所示
public class Apple { private int num; public Apple(int num){ this.num = num; } public static void main(String[] args) { new Apple(10); } }
main 方法中传递了一个 int 值为 10 的参数,它表示的就是苹果的数量,并把这个数量赋给了 num 全局变量。所以 num 的值现在就是 10。
this 还可以和构造函数一起使用,充当一个全局关键字的效果
public class Apple { private int num; private String color; public Apple(int num){ this(num,"红色"); } public Apple(String color){ this(1,color); } public Apple(int num, String color) { this.num = num; this.color = color; } }
你会发现上面这段代码使用的不是 this, 而是 this(参数)
。它相当于调用了其他构造方法,然后传递参数进去。这里注意一点:this() 必须放在构造方法的第一行,否则编译不通过。
如果你把 this 理解为指向自身的一个引用,那么 super 就是指向父类的一个引用。super 关键字和 this 一样,你可以使用 super.对象
来引用父类的成员,如下:
public class Fruit { int num; String color; public void eat(){ System.out.println("eat Fruit"); } } public class Apple extends Fruit{ @Override public void eat() { super.num = 10; System.out.println("eat " + num + " Apple"); } }
你也可以使用 super(参数)
来调用父类的构造函数,这里不再举例子了。
下面为你汇总了 this 关键字和 super 关键字的比较。