0%

面向对象编程(高级部分)-1

面向对象编程(高级部分)

★★★第十章 面向对象编程(高级部分)

★类变量

  • 类变量也叫静态(static)变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

  • 类变量的定义语法:

    1
    2
    3
    访问修饰符 static 数据类型 变量名;	//推荐
    //或者
    static 访问修饰符 数据类型 变量名;
  • 访问类变量:

    1
    2
    3
    类名.类变量名	//推荐
    //或者
    对象名.类变量名

    静态变量的访问修饰符的访问权限和范围和普通属性是一样的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package 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";
    }
★类变量使用细节
  1. 什么时候需要使用类变量:

    当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。

  2. 类变量与实例变量(普通属性)区别:

    类变量是该类的所有对象共享的,而实例变量是每个对象独享的

  3. 加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量。

  4. 类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但 java 设计者推荐我们使用 类名.类变量名 方式访问。【前提是满足访问修饰符的访问权限和范围】

  5. 实例变量不能通过 类名.类变量名 方式访问。

  6. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了

  7. 类变量的生命周期是随类的加载开始,随着类消亡而销毁。

★类方法

  • 类方法也叫静态方法。

  • 类方法的定义语法:

    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
    23
    package 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 + " 元");
    }
    }

    如果我们希望不创建实例,也可以调用某个方法(即当做工具来使用),这时,把方法做成静态方法非常合适。

    在程序员实际开发时,往往会将一些通用的方法设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组、冒泡排序、完成某个计算任务等。

★类方法使用细节
  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无 this 的参数,普通方法中隐含着 this 的参数。
  2. 类方法可以通过类名调用,也可以通过对象名调用。
  3. 普通方法和对象有关,需要通过对象名调用,比如 对象名.方法名(参数),不能通过类名调用。
  4. 类方法中不允许使用和对象有关的关键字,比如 this 和 super。普通方法(成员方法)可以。
  5. ★类方法(静态方法)中只能访问 静态变量 或 静态方法。 -> 静态方法只能访问静态成员
  6. ★**普通成员方法,既可以访问 普通变量(方法),也可以访问静态变量(方法)**。

注意:5、6 两点依旧需要遵守访问权限。

main

main语法说明
  • 解释 main 方法的形式:public static void main(String[] args){}

    1. main 方法是由 JVM 调用的;-> 所以 main 方法的访问修饰符是 public

    2. java 虚拟机在执行 main() 方法时不必创建对象,所以该方法必须是 static

    3. main 方法接收 String 类型的数组参数,该数组中保存执行 java 命令时传递给所运行的类的参数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      package 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

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C10-1.jpg)

main特别说明
  • 由于 main 方法也是类的静态方法,所以我们可以在 main 方法中直接调用 main 方法所在类的静态方法或静态属性。但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。

代码块

  • 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

    但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时或创建对象时隐式调用。

  • 基本语法:

    1
    2
    3
    [修饰符]{
    代码
    };

    注意:

    1. 修饰符可选,要写的话,也只能写 static
    2. 代码块分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的叫普通代码块;
    3. 代码可以为任何逻辑语句(输入、输出、方法调用、循环、判断等);
    4. ; 号可以写上,也可以省略;
    5. 代码块的调用优先于构造器
  • 好处:

    1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作;
    2. 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
    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
    package 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
    电影开始...
    第一个构造器被调用...
    电影开始...
    第二个构造器被调用...
★代码块使用细节
  1. static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。

  2. 类什么时候被加载

    1. 创建对象实例时 (new);
    2. 创建子类对象实例,父类也会被加载,而且父类先被加载,子类后被加载;
    3. 使用类的静态成员时(静态属性,静态方法)。
    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
    package 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
    28
    package 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
    39
    package 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的普通代码块...");
    }
    }
  3. 对于普通代码块,在创建对象实例时,会被隐式的调用。类对象每被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。

  4. 创建一个对象时,在一个类中的调用顺序是:

    ① 调用静态代码块和静态属性初始化(注意 : 静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用);

    ② 调用普通代码块和普通属性的初始化(注意 : 普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多 个普通属性初始化,则按定义顺序调用);

    ③ 调用构造方法。

  5. 构造方法 (构造器) 的最前面其实隐含了 super() 和调用普通代码块,而静态相关的代码块和属性初始化在类加载时就执行完毕,因此是优先于构造器和普通代码块执行的

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C10-2.jpg)

  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
    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
    67
    package 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)
    }
    }
  7. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

单例设计模式

  • 什么是设计模式?

    设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。

  • 什么是单例(单个实例)模式

    所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

单例模式饿汉式
  • 步骤如下:
    1. 构造器私有化;-> 防止直接 new
    2. 类的内部创建对象;
    3. 向外暴露一个静态的公共方法;-> getInstance
    4. 代码实现。
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
package com.f.Chapter10.singleMode;

public class SingleTon01 {
public static void main(String[] args) {
//通过方法获取对象实例
A a = A.getaInstance();
}
}

class A{
private String name;

//2. 在类的内部创建对象
private static A aInstance = new A("a");

//1. 构造器私有化,此时在 main 函数中,已经不能直接 new 对象了
private A(String name) {
this.name = name;
}

//3. 向外暴露一个静态的公共方法;-> getInstance
public static A getaInstance(){
return A.aInstance;
}
}
  • “饿汉式”名称的由来:不管程序是否需要这个对象的实例,总是在类加载的时候就先创建好实例,理解起来就像不管一个人想不想吃东西都把吃的先买好,如同饿怕了一样

    “饿汉式”可能造成创建了对象但没有使用的问题。

单例模式懒汉式
  • 步骤如下:
    1. 构造器私有化;-> 防止直接 new
    2. 类的内部定义一个 static 静态属性对象;
    3. 向外暴露一个静态的公共方法;-> getInstance
    4. 代码实现。
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
package com.f.Chapter10.singleMode;

public class SingleTon02 {
public static void main(String[] args) {
//通过方法获取对象实例
AA aa = AA.getaInstance();
System.out.println(aa);
}
}

class AA {
private String name;

//2. 类内部定义一个 static 静态属性对象
private static AA aaInstance;

//1. 构造器私有化,此时在 main 函数中,已经不能直接 new 对象了
private AA(String name) {
System.out.println("构造器 AA 被调用...");
this.name = name;
}

//3. 向外暴露一个静态的公共方法;-> getInstance
public static AA getaInstance() {
if (aaInstance == null) { //如果对象还没有创建
aaInstance = new AA("aa");
}
return aaInstance;
}
}
  • “懒汉式”名称的由来:如果一个对象使用频率不高,占用内存还特别大,明显就不合适用饿汉式了,这时就需要一种懒加载的思想,当程序需要这个实例的时候才去创建对象,就如同一个人懒的饿到不行了才去吃东西

    只有当用户使用 getInstance 时,才返回实例对象,后面再次调用时,会返回上次创建的对象,从而实现了单例。

饿汉式和懒汉式的区别
  1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,,而懒汉式是在使用时才创建
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
  3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
---------------The End---------------