面向对象编程(高级部分)
★★★第十章 面向对象编程(高级部分)
★类变量
类变量也叫静态(static)变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
类变量的定义语法:
1
2
3访问修饰符 static 数据类型 变量名; //推荐
//或者
static 访问修饰符 数据类型 变量名;访问类变量:
1
2
3类名.类变量名 //推荐
//或者
对象名.类变量名静态变量的访问修饰符的访问权限和范围和普通属性是一样的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.f.Chapter10;
public class VisitStatic {
public static void main(String[] args) {
//注意:类变量是随着类的加载而创建的,所以即使没有创建对象实例也可以访问
System.out.println(A.name); //输出 "A" 推荐使用
A a = new A();
System.out.println(a.name); //输出 "A"
}
}
class A {
public static String name = "A";
}
★类变量使用细节
什么时候需要使用类变量:
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。
类变量与实例变量(普通属性)区别:
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量。
类变量可以通过
类名.类变量名
或者对象名.类变量名
来访问,但 java 设计者推荐我们使用类名.类变量名
方式访问。【前提是满足访问修饰符的访问权限和范围】实例变量不能通过
类名.类变量名
方式访问。类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
类变量的生命周期是随类的加载开始,随着类消亡而销毁。
★类方法
类方法也叫静态方法。
类方法的定义语法:
1
2
3访问修饰符 static 数据返回类型 方法名(){}; //推荐
//或者
static 访问修饰符 数据返回类型 方法名(){};类方法的调用:
1
2
3类名.类方法名(参数) //推荐
//或者
对象名.类方法名(参数)【前提是满足访问修饰符的访问权限和范围】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.f.Chapter10;
public class StaticMethod {
public static void main(String[] args) {
Student stu1 = new Student("小明", 100);
Student stu2 = new Student("小红", 200);
Student.showTotalFee(); //用 类名.类方法名 调用类方法
}
}
class Student {
private String name;
private static double fee = 0;
public Student(String name, double fee) {
this.name = name;
Student.fee += fee;
}
public static void showTotalFee() {
System.out.println("总学费有:" + Student.fee + " 元");
}
}如果我们希望不创建实例,也可以调用某个方法(即当做工具来使用),这时,把方法做成静态方法非常合适。
在程序员实际开发时,往往会将一些通用的方法设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组、冒泡排序、完成某个计算任务等。
★类方法使用细节
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无 this 的参数,普通方法中隐含着 this 的参数。
- 类方法可以通过类名调用,也可以通过对象名调用。
- 普通方法和对象有关,需要通过对象名调用,比如
对象名.方法名(参数)
,不能通过类名调用。 - 类方法中不允许使用和对象有关的关键字,比如 this 和 super。普通方法(成员方法)可以。
- ★类方法(静态方法)中只能访问 静态变量 或 静态方法。 -> 静态方法只能访问静态成员
- ★**普通成员方法,既可以访问 普通变量(方法),也可以访问静态变量(方法)**。
注意:5、6 两点依旧需要遵守访问权限。
main
main语法说明
解释
main
方法的形式:public static void main(String[] args){}
main
方法是由 JVM 调用的;-> 所以main
方法的访问修饰符是public
java 虚拟机在执行
main()
方法时不必创建对象,所以该方法必须是static
;该
main
方法接收String
类型的数组参数,该数组中保存执行 java 命令时传递给所运行的类的参数;1
2
3
4
5
6
7
8
9
10package com.f.Chapter10;
public class TestMainParam {
public static void main(String[] args) {
//遍历 args
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i + 1) + "个参数为:" + args[i]);
}
}
}若执行命令:
java TestMainParam x y z
则输出:第1个参数为:x
第2个参数为:y
第3个参数为:z

main特别说明
- 由于 main 方法也是类的静态方法,所以我们可以在 main 方法中直接调用 main 方法所在类的静态方法或静态属性。但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
代码块
代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时或创建对象时隐式调用。
基本语法:
1
2
3[修饰符]{
代码
};注意:
- 修饰符可选,要写的话,也只能写
static
; - 代码块分为两类,使用
static
修饰的叫静态代码块,没有static
修饰的叫普通代码块; - 代码可以为任何逻辑语句(输入、输出、方法调用、循环、判断等);
- ; 号可以写上,也可以省略;
- 代码块的调用优先于构造器。
- 修饰符可选,要写的话,也只能写
好处:
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作;
- 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package com.f.Chapter10;
public class CodeBlock {
public static void main(String[] args) {
Movie movie = new Movie("唐人街探案");
Movie movie2 = new Movie("唐人街探案2", 100);
}
}
class Movie {
private String name;
private double price;
{
System.out.println("电影开始...");
}
public Movie(String name) {
System.out.println("第一个构造器被调用...");
this.name = name;
}
public Movie(String name, double price) {
System.out.println("第二个构造器被调用...");
this.name = name;
this.price = price;
}
}输出为:
1
2
3
4电影开始...
第一个构造器被调用...
电影开始...
第二个构造器被调用...
★代码块使用细节
static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。
★类什么时候被加载:
- 创建对象实例时 (new);
- 创建子类对象实例,父类也会被加载,而且父类先被加载,子类后被加载;
- 使用类的静态成员时(静态属性,静态方法)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package com.f.Chapter10;
public class StaticCodeBlock {
public static void main(String[] args) {
/*1. 创建对象实例时 (new)*/
/*仅在第一种情况下,输出 "AA的静态代码块..." ,因为类的静态代码块只会被执行一次*/
//AA aa1 = new AA();
//AA aa2 = new AA();
/*2. 创建子类对象实例,父类也会被加载*/
/*仅在第二种情况下,先输出 "AA的静态代码块..." ,然后输出 "BB的静态代码块..." ,因为父类先被加载,子类后被加载*/
//BB bb = new BB();
/*3. 使用类的静态成员时(静态属性,静态方法)*/
/*仅在第三种情况下,输出 "AA的静态代码块..." ,因为使用类的静态成员时也会加载类*/
//AA.hi();
}
}
class AA {
static {
System.out.println("AA的静态代码块...");
}
public static void hi() {
}
}
class BB extends AA {
static {
System.out.println("BB的静态代码块...");
}
}有意思的是,对于第二种情况,如果父类 AA 的代码块是普通代码块,则对于下面这种情况,输出结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package com.f.Chapter10;
public class StaticCodeBlock {
public static void main(String[] args) {
/*输出结果为:
* "BB的静态代码块..."
* "AA的普通代码块..."
* "AA的普通代码块..."*/
BB bb1 = new BB();
BB bb2 = new BB();
}
}
class AA {
{
System.out.println("AA的普通代码块...");
}
public static void hi() {
}
}
class BB extends AA {
static {
System.out.println("BB的静态代码块...");
}
}由此可以推断,在创建一个类的对象时,由于会加载类信息,所以先执行父类的静态代码块,然后执行子类的静态代码块;然后由于是创建对象,所以接着执行父类的普通代码块,然后执行子类的普通代码块。(可见使用细节 6)
可以★把普通代码块看作是构造器的一个补充,这样逻辑就通顺了。因为在创建对象时,会先调用父类的构造器,然后再调用自己的构造器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39package com.f.Chapter10;
public class StaticCodeBlock {
public static void main(String[] args) {
/*输出结果为:
* "AA的静态代码块..."
* "BB的静态代码块..."
* "AA的普通代码块..."
* "BB的普通代码块..."
* "AA的普通代码块..."
* "BB的普通代码块..."*/
BB bb1 = new BB();
BB bb2 = new BB();
}
}
class AA {
static {
System.out.println("AA的静态代码块...");
}
{
System.out.println("AA的普通代码块...");
}
public static void hi() {
}
}
class BB extends AA {
static {
System.out.println("BB的静态代码块...");
}
{
System.out.println("BB的普通代码块...");
}
}对于普通代码块,在创建对象实例时,会被隐式的调用。类对象每被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。
创建一个对象时,在一个类中的调用顺序是:
① 调用静态代码块和静态属性初始化(注意 : 静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用);
② 调用普通代码块和普通属性的初始化(注意 : 普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多 个普通属性初始化,则按定义顺序调用);
③ 调用构造方法。
构造方法 (构造器) 的最前面其实隐含了 super() 和调用普通代码块,而静态相关的代码块和属性初始化在类加载时就执行完毕,因此是优先于构造器和普通代码块执行的。

★★★创建一个子类时(有继承关系),他们的静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造方法的调用顺序如下:
①父类的静态代码块和静态属性(优先级一样,按定义顺序执行);
②子类的静态代码块和静态属性(优先级一样,按定义顺序执行);
③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
④父类的构造方法;
⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
⑥子类的构造方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67package com.f.Chapter10;
public class CodeBlockDetail {
public static void main(String[] args) {
new B01();
}
}
class A01 {
private static int n1 = getVal01();
static {
System.out.println("A01的第一个静态代码块..."); //(2)
}
{
System.out.println("A01的第一个普通代码块..."); //(5)
}
public int n2 = getVal02();
public static int getVal01() {
System.out.println("getVal01()..."); //(1)
return 10;
}
public int getVal02() {
System.out.println("getVal02()..."); //(6)
return 20;
}
public A01() {
//super();
//调用普通代码块
System.out.println("A01的构造器..."); //(7)
}
}
class B01 extends A01 {
static {
System.out.println("B01的第一个静态代码块..."); //(3)
}
private static int n3 = getVal03();
public int n4 = getVal04();
{
System.out.println("B01的第一个普通代码块..."); //(9)
}
public static int getVal03() {
System.out.println("getVal03()..."); //(4)
return 30;
}
public int getVal04() {
System.out.println("getVal04()..."); //(8)
return 40;
}
public B01() {
//super();
//调用普通代码块
System.out.println("B01的构造器..."); //(10)
}
}静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
单例设计模式
什么是设计模式?
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。
什么是单例(单个实例)模式?
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式饿汉式
- 步骤如下:
- 构造器私有化;-> 防止直接 new
- 类的内部创建对象;
- 向外暴露一个静态的公共方法;-> getInstance
- 代码实现。
1 | package com.f.Chapter10.singleMode; |
“饿汉式”名称的由来:不管程序是否需要这个对象的实例,总是在类加载的时候就先创建好实例,理解起来就像不管一个人想不想吃东西都把吃的先买好,如同饿怕了一样。
“饿汉式”可能造成创建了对象但没有使用的问题。
单例模式懒汉式
- 步骤如下:
- 构造器私有化;-> 防止直接 new
- 类的内部定义一个 static 静态属性对象;
- 向外暴露一个静态的公共方法;-> getInstance
- 代码实现。
1 | package com.f.Chapter10.singleMode; |
“懒汉式”名称的由来:如果一个对象使用频率不高,占用内存还特别大,明显就不合适用饿汉式了,这时就需要一种懒加载的思想,当程序需要这个实例的时候才去创建对象,就如同一个人懒的饿到不行了才去吃东西。
只有当用户使用
getInstance
时,才返回实例对象,后面再次调用时,会返回上次创建的对象,从而实现了单例。
饿汉式和懒汉式的区别
- 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,,而懒汉式是在使用时才创建。
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
- 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。