一、类变量和类方法

1.1 类变量

1. 提出问题

有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?编写程序解决

2. 传统方法解决方案

  1. 在 main 方法中定义一个变量 count
  2. 当一个小孩加入游戏后,count++,最后输出count 就记录有多少个小孩玩游戏 ```java package com.hspedu.static_;

public class ChildGame { public static void main(String[] args) { //定义一个变量 count,统计有多少小号加入了游戏 int count = 0;

  1. Child child = new Child("白骨精");
  2. child.join();
  3. count++;
  4. Child child1 = new Child("狐狸精");
  5. child1.join();
  6. count++;
  7. Child child2 = new Child("黑熊精");
  8. child2.join();
  9. count++;
  10. System.out.println("共有" + count + "个小孩加入了游戏");
  11. }

} class Child { private String name;

  1. public Child(String name) {
  2. this.name = name;
  3. }
  4. public void join() {
  5. System.out.println(name + "加入了游戏...");
  6. }

}

  1. 问题分析:
  2. 1. count 是一个独立于对象,很尴尬
  3. 1. 以后我们访问 count 很麻烦,没有使用到 OOP
  4. 1. 因此,我们引出 类变量/静态变量
  5. <a name="OBawa"></a>
  6. #### 3. 类变量快速入门
  7. 思考:如果设计一个 int count 表示总人数,我们在创建一个小孩时,就把 count 1,并且 count 是所有对象共享的就 ok 了!我们使用类变量来解决 ChildGame.java 改进
  8. ```java
  9. package com.hspedu.static_;
  10. public class ChildGame {
  11. public static void main(String[] args) {
  12. //定义一个变量 count,统计有多少小号加入了游戏
  13. Child child = new Child("白骨精");
  14. child.join();
  15. child.count++;
  16. Child child1 = new Child("狐狸精");
  17. child1.join();
  18. child1.count++;
  19. Child child2 = new Child("黑熊精");
  20. child2.join();
  21. child2.count++;
  22. System.out.println("共有" + Child.count + "个小孩加入了游戏");
  23. System.out.println(child.count);
  24. System.out.println(child1.count);
  25. System.out.println(child2.count);
  26. }
  27. }
  28. class Child {
  29. private String name;
  30. public static int count;
  31. public Child(String name) {
  32. this.name = name;
  33. }
  34. public void join() {
  35. System.out.println(name + "加入了游戏...");
  36. }
  37. }

4. 类变量的内存布局

面向对象编程(高级部分) - 图1
面向对象编程(高级部分) - 图2
注意:static 变量是对象共享,不管 static 变量在哪里,共识

  1. static 变量是同一个类所有对象共享
  2. static 类变量,在类加载的时候就生成了

5. 类变量定义

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问。

定义语法:
访问修饰符 static 数据类型 变量名; 【推荐】
static 访问修饰符 数据类型 变量名;

6. 类变量的访问

类名.类变量名 【推荐】
对象名.类变量名
注意:静态变量的访问修饰符的访问权限和范围 与 普通属性是一样的

7. 类变量使用注意事项和细节讨论

  1. 什么时候需要用类变量
    当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):
    比如:定义学生类,统计所有学生共交多少钱

  2. 类变量与实例变量(普通变量)的区别
    类变量是该类的所有对象共享的,而实例变量是每个对象独享的

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

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

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

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

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

1.2 类方法

1. 类方法的基本介绍

类方法也叫静态方法

形式如下:
访问修饰符 static 数据返回类型 方法名 () { } 【推荐】
static 访问修饰符 数据返回类型 方法名 () { }

2. 类方法的调用

使用方式:
类名.类方法名
对象名.类方法名
注意:须满足访问修饰符的访问权限和范围

3. 类方法经典的使用场景

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率

比如:工具类中的方法 utils
Math类、Arrays类、Collections集合类

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

4. 类方法使用注意事项和细节讨论

  1. 类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区:
    类方法中无 this 的参数
    普通方法中隐藏着 this 的参数

  2. 类方法可以通过类名调用,也可以通过对象名调用

  3. 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用

  4. 类方法中不允许使用和对象有关的关键字,比如 this 和 super 。普通方法可以。

  5. 类方法(静态方法)中,只能访问静态变量 或 静态方法

  6. 普通成员方法,既可以访问普通变量/方法,也可以访问静态变量/方法。

小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)

5. 课堂练习

  1. 输出什么?
    public class Test {
    static int count = 9;
    public void count() {
    System.out.println(“count=” + (count++));
    }
    public static void main(String args[]) {
    new Test().count();
    new Test().count();
    System.out.println(Test.count);
    }
    }

  2. 看看下面代码有没有错误,如果有错误,就修改,看看输出什么?
    class Person {
    private int id;
    private static int total = 0;
    public static int getTotalPerson() {
    id++;
    return total;
    }
    public Person() {
    total++;
    id = total;
    }
    }
    public class TestPerson {
    public static void main(String[] args) {
    System.out.println(“Number of total is “ + Person.getTotalPerson());
    Person p1 = new Person();
    System.out.println(“Number of total is “ + Person.getTotalPerson());
    }
    }

  3. 看看下面代码有没有错误,如果有错误,请修改,看看total等于多少?
    class Person {
    private int id;
    private static int total = 0;
    public static void setTotalPerson(int total) {
    this.total = total;
    Person.total = total;
    }
    public Person() {
    total++;
    id = total;
    }
    }
    public class TestPerson {
    public static void main(String[] args) {
    Person.setTotalPerson(3);
    new Person();
    }
    }

二、理解main方法语法

2.1 深入理解 main方法

1. main方法解释

main方法的形式:public static void main(String[] args) { }

  1. main方法是虚拟机调用的
  2. Java虚拟机需要调用类的 main()方法没所以该方法的访问权限必须是 public
  3. Java虚拟机在执行 main()方法时不必创建对象,所以该方法必须是 static
  4. 该方法接收 String 类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
  5. 语法:java 执行的程序 参数1 参数2 参数3 (控制台运行)

面向对象编程(高级部分) - 图3
特别提示:

  1. 在 main方法中,我们可以直接调用 main方法所在类的静态方法或静态属性。
  2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员 ```java package com.hspedu.main_;

public class Main01 { //静态的变量/属性 private static String name = “韩顺平教育”; //非静态的变量/属性 private int n1 = 10000; //静态方法 public static void hi() { System.out.println(“Main01 的 hi 方法”); } //非静态方法 public void cry() { System.out.println(“Main01 的 cry 方法”); } public static void main(String[] args) { //可以直接使用 name //1. 静态方法 main 可以访问本类的静态成员 System.out.println(“name=” + name); hi(); //2. 静态方法 main 不可以访问本类的非静态成员 //System.out.println(“n1=” + n1);//错误 //cry(); //3. 静态方法 main 要访问本类的非静态成员,需要先创建对象 , 再调用即可 Main01 main01 = new Main01(); System.out.println(main01.n1);//ok main01.cry(); } }

  1. <a name="H4Sgc"></a>
  2. #### 2. IDEA 中如何给 main方法传参
  3. 演示代码:
  4. ```java
  5. public class CommandPara {
  6. public static void main(String[] args) {
  7. for (int i = 0; i < args.length; i++) {
  8. System.out.println("args[" + i + "] = " + args[i]);
  9. }
  10. }
  11. }

image.png
image.png

三、代码块

3.1 基本介绍

代码化块又称初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建类时隐式调用。

3.2 基本语法

[修饰符] {
代码
};

3.3 注意事项

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

3.4 代码块的好处

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
  2. 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性

    案例演示

    ```java package com.hspedu.codeblock_;

public class CodeBlock01 { public static void main(String[] args) { Movie movie = new Movie(“你好,李焕英”); System.out.println(“===============”); Movie movie2 = new Movie(“唐探 3”, 100, “陈思诚”); } }

class Movie { private String name; private double price; private String director;

  1. //3 个构造器-》重载
  2. //老韩解读
  3. //(1) 下面的三个构造器都有相同的语句
  4. //(2) 这样代码看起来比较冗余
  5. //(3) 这时我们可以把相同的语句,放入到一个代码块中,即可
  6. //(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
  7. //(5) 代码块调用的顺序优先于构造器..
  8. {
  9. System.out.println("电影屏幕打开...");
  10. System.out.println("广告开始...");
  11. System.out.println("电影正是开始...");
  12. }
  13. public Movie(String name) {
  14. System.out.println("Movie(String name) 被调用...");
  15. this.name = name;
  16. }
  17. public Movie(String name, double price) {
  18. this.name = name;
  19. this.price = price;
  20. }
  21. public Movie(String name, double price, String director) {
  22. System.out.println("Movie(String name, double price, String director) 被调用...");
  23. this.name = name;
  24. this.price = price;
  25. this.director = director;
  26. }

}

  1. <a name="rj9hP"></a>
  2. ### 3.5 代码块使用注意事项和细节讨论
  3. 1. static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
  4. 1. 类什么时候被加载?
  5. 1. 创建对象实例时(new)
  6. 1. 创建子类对象实例时,父类也会被加载
  7. 1. 使用类的静态成员时(静态属性,静态方法)
  8. 3. 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。静态代码块的执行优先于普通代码块。
  9. 3. 创建一个对象时,在一个类 的调用顺序是:
  10. 1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
  11. 1. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
  12. 1. 调用构造方法
  13. 5. 构造器的最前面其实隐含了 super() 和 调用普通代码块。静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
  14. 5. 我们看一下创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
  15. 1. 父类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
  16. 1. 子类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
  17. 1. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  18. 1. 父类的构造方法
  19. 1. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  20. 1. 子类的构造方法
  21. 7. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
  22. ```java
  23. package com.hspedu.codeblock;
  24. public class CodeBlockDetail {
  25. public static void main(String[] args) {
  26. // System.out.println(Cat.name);
  27. // new Cat();
  28. new BlackCat();
  29. }
  30. }
  31. class Animal {
  32. private static int n1 = getN1(); //静态属性初始化
  33. private int n2 = getN2(); //普通属性初始化
  34. {
  35. System.out.println("Animal的普通代码块被执行...");
  36. }
  37. static {
  38. System.out.println("Animal的静态代码块被执行...");
  39. }
  40. public Animal() {
  41. System.out.println("Animal的构造器被执行");
  42. }
  43. public static int getN1() {
  44. System.out.println("Animal的静态属性初始化...");
  45. return 10;
  46. }
  47. public int getN2() {
  48. System.out.println("Animal的普通属性初始化...");
  49. return 20;
  50. }
  51. }
  52. class Cat extends Animal {
  53. private static int n3 = getN3(); //静态属性初始化
  54. private int n4 = getN4(); //普通属性初始化
  55. public Cat() {
  56. System.out.println("Cat的构造器...");
  57. }
  58. {
  59. System.out.println("Cat的普通代码块被执行...");
  60. }
  61. public static String name = "小花猫";
  62. static {
  63. System.out.println("Cat的静态代码块被执行...");
  64. }
  65. public static int getN3() {
  66. System.out.println("Cat的静态属性初始化...");
  67. return 30;
  68. }
  69. public int getN4() {
  70. System.out.println("Cat的普通属性初始化...");
  71. return 40;
  72. }
  73. }
  74. class BlackCat extends Cat {
  75. private static int n3 = getN5(); //静态属性初始化
  76. private int n4 = getN6(); //普通属性初始化
  77. public BlackCat() {
  78. System.out.println("BlackCat的构造器...");
  79. }
  80. {
  81. System.out.println("BlackCat的普通代码块被执行...");
  82. }
  83. static {
  84. System.out.println("BlackCat的静态代码块被执行...");
  85. }
  86. public static int getN5() {
  87. System.out.println("BlackCat的静态属性初始化...");
  88. return 50;
  89. }
  90. public int getN6() {
  91. System.out.println("BlackCat的普通属性初始化...");
  92. return 60;
  93. }
  94. }

四、单例设计模式

4.1 什么是设计模式

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

4.2 什么是单例模式

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
  2. 单例模式有两种方式:
    1. 饿汉式
    2. 懒汉式

4.3 单例模式应用实例

1. 饿汉式

步骤:

  1. 构造器私有化
  2. 类的内部直接创建对象(该对象是static)
  3. 向外暴露一个静态的公共方法
  4. 代码实现 ```java package com.hspedu.single_;

public class SingleTon01 { public static void main(String[] args) { Wife instance = Wife.getInstance(); System.out.println(instance); } } //有一个类 //只能有一个实例 class Wife { private String name;

  1. //为了能够在静态方法中,返回xb对象,需要将其修饰为 static
  2. private static Wife xb = new Wife("小白");
  3. //如何保证只能创建一个 Wife 对象
  4. //步骤[单例模式 - 饿汉式]
  5. //1. 将构造器私有化
  6. //2. 在类的内部直接创建对象(该对象是static)
  7. //3. 提供一个公共的接口,返回 xb对象
  8. private Wife(String name) {
  9. this.name = name;
  10. }
  11. public static Wife getInstance() {
  12. return xb;
  13. }
  14. @Override
  15. public String toString() {
  16. return "Wife{" +
  17. "name='" + name + '\'' +
  18. '}';
  19. }

}

  1. 说明:饿汉式 在类加载的时候就会创建对象,不管有没有使用,所以可能会造成资源的占用或浪费
  2. <a name="lj4xL"></a>
  3. #### 2. 懒汉式
  4. 步骤:
  5. 1. 仍然构造器私有化
  6. 1. 定义一个static 静态属性对象
  7. 1. 提供一个 public static 方法,可以返回一个 Cat对象
  8. 1. 懒汉式,只有当用户使用getInstance()方法时,才返回 cat对象,再次调用时,会返回上次创建的cat对象,从而保证单例
  9. <a name="e8t4l"></a>
  10. #### 3. 饿汉式 VS 懒汉式
  11. 1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了实例对象,而懒汉式是在使用时才创建。
  12. 1. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
  13. 1. 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
  14. 1. 在我们JavaSE标准类中,java.lang.Runtime 就是典型的单例模式。
  15. ```java
  16. package com.hspedu.single_;
  17. public class SingleTon02 {
  18. public static void main(String[] args) {
  19. // System.out.println(Cat.num);
  20. Cat instance = Cat.getInstance();
  21. System.out.println(instance);
  22. Cat instance1 = Cat.getInstance();
  23. System.out.println(instance1);
  24. System.out.println(instance == instance1);
  25. }
  26. }
  27. class Cat {
  28. public static int num = 100;
  29. private String name;
  30. private static Cat cat; //默认是 Null
  31. //步骤
  32. //1. 仍然构造器私有化
  33. //2. 定义一个static 静态属性对象
  34. //3. 提供一个 public 的 static 方法,可以返回一个 Cat对象
  35. //4. 懒汉式,只有当用户使用getInstance()方法时,才返回 cat对象,再次调用时,会返回上次创建的cat对象,从而保证单例
  36. private Cat(String name) {
  37. System.out.println("构造器被调用...");
  38. this.name = name;
  39. }
  40. public static Cat getInstance() {
  41. if (cat == null) { //如果还没有创建 cat对象
  42. cat = new Cat("小可爱");
  43. }
  44. return cat;
  45. }
  46. @Override
  47. public String toString() {
  48. return "Cat{" +
  49. "name='" + name + '\'' +
  50. '}';
  51. }
  52. }

五、final 关键字

5.1 基本介绍

final 可以修饰类、属性、方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用到 final:

  1. 当不希望类被继承时,可以用 final 修饰
  2. 当不希望父类的某个方法被子类覆盖/重写时,可以用final 关键字修饰
  3. 当不希望类的某个属性的值被修改,可以用final修饰
  4. 当不希望某个局部变量被修改,可以使用 final 修饰

5.2 final 使用注意事项和细节讨论

  1. final 修饰的属性又叫常量,一般用 XX_XX_XX 来命名

  2. final 修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:
    【选择一个位置赋初值即可】

    1. 定义时:如 public final double TAX_RATE = 0.08;
    2. 在构造器中
    3. 在代码块中
  3. 如果final修饰的属性是静态的,则初始化的位置只能是

    1. 定义时
    2. 在静态代码块
    3. 不能在构造器中赋值
  4. final 类不能继承,但是可以实例化对象

  5. 如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承

  6. 一般来说,如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法。

  7. final 不能修饰 构造方法(即 构造器)

  8. final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理

  9. 包装类(Integer,Double,Float,Boolean 等都是 final),String 也是 final 类

5.3 小练习

  1. 请编写一个程序,能够计算圆形的面积。要求圆周率为3.14,赋值的位置3个方式都写一下

  2. 以下代码是否存在问题?
    public class Something {
    public int cal(final int x) {
    ++x; //错误,x 是final修饰,不能修改
    return x + 1; //正确,x参与计算,并没有修改自身的值
    }
    }

六、抽象类 abstract

6.1 问题引入

  1. class Animal {
  2. private String name;
  3. private int age;
  4. public Animal(String name, int age) {
  5. super();
  6. this.name = name;
  7. this.age = age;
  8. }
  9. //动物都有 eat的行为
  10. public void eat() {
  11. System.out.println("这是一个动物,但是目前不知道吃什么");
  12. }
  13. }

父类方法 的不确定性:
当父类的某些方法,需要声明,但是有不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

6.2 抽象类引入

当父类的一些方法不能确定时,可以用 abstract 关键字来修饰该方法,这个方法就是抽象方法,用 abstract 来修饰该类就是抽象类。
比如:将 Animal 类设计成抽象类,并让子类 Cat 类实现其 eat()方法

6.3 抽象类的介绍

  1. 用 abstract 关键字来修饰一个类时,这个类就叫抽象类
    访问修饰符 abstract 类名 {
    }

  2. 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法
    访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体

  3. 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()

  4. 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多

6.4 抽象类使用的注意事项和细节讨论

  1. 抽象类不能被实例化

  2. 抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法

  3. 一旦类包含了 abstract 方法,则这个类必须声明为 abstract

  4. abstract 只能修饰 类和方法,不能修饰属性和其他的

  5. 抽象类可以有任意成员【因为抽象类本质还是类】 比如:非抽象方法、构造器、静态属性等等

  6. 抽象方法不能有主体 { }

  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract 类

  8. 抽象方法不能使用 private、final、static 来修饰,因为这些关键字都是和重写相违背的

6.5 小练习

  1. 以下代码是否能成功编译?

    1. abstract final class A { }
    2. abstract public static void test2();
    3. abstrcat private void test3();
    4. abstract public void test4() { }
  2. 编写一个 Employee 类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。对于 Manager 类来说,他即是员工,还具有奖金(bonus)属性,请使用继承的思想,设计CommonEmployee类和 Manager类,要求类中提供必要的方法进行属性访问,实现 work(),输出”经理/普通员工 名字 工作中…”

6.6 模板设计模式

要求:

  1. 有多个类,完成不同的任务 job
  2. 要求能够统计各自完成任务的时间
  3. 请编程实现

步骤:

  1. 先用最容易想到的方法
  2. 分析问题,提出使用模板设计模式
  1. //传统解决办法
  2. class Test {
  3. public static void main(String[] args) {
  4. AA aa = new AA();
  5. aa.sum();
  6. BB bb = new BB();
  7. bb.res();
  8. }
  9. }
  10. class A {
  11. public void sum() {
  12. int count = 0;
  13. //得到开始的时间
  14. long start = System.currentTimeMillis();
  15. for (int i = 1; i <= 999999; i++) {
  16. count += i;
  17. }
  18. //得到结束的时间
  19. long end = System.currentTimeMillis();
  20. System.out.println(end - start);
  21. }
  22. }
  23. class B {
  24. public void multi() {
  25. int num = 0;
  26. //得到开始的时间
  27. long start = System.currentTimeMillis();
  28. for (int i = 1; i <= 599999; i++) {
  29. num *= i;
  30. }
  31. //得到结束的时间
  32. long end = System.currentTimeMillis();
  33. System.out.println(end - start);
  34. }
  35. }
  36. /*
  37. 存在的问题:
  38. 1. A类 和 B类 存在重复的代码,
  39. 2. 如果在 A或B类中再加入相同方法,仅更改循环上限值,还要写相同的代码
  40. 3. 所以会出现代码冗余
  41. 4. 如果我们将相同的代码提取出来,写到一个新的方法内
  42. 5. 再在新的方法内调用循环体方法
  43. 6. 就能在一定程度上改善代码冗余
  44. 7. 同样的道理,如果想在多个类间实现改善
  45. 8. 可以将重复的代码提取到一个抽象类的一个方法中
  46. 9. 再将包含循环体为主体的方法做成抽象方法用以引用
  47. 10.即可达到我们的目标
  48. */

最佳实践:
设计一个抽象类(Template),能完成如下功能:

  1. 编写方法 calculateTime(),可以计算某段代码的耗时时间
  2. 编写抽象方法 job()
  3. 编写一个子类 Sub,继承抽象类Template,并实现 job方法
  4. 编写一个测试类TestTemplate,看看是否好用 ```java //模板设计方法 public class TestTemplate { public static void main(String[] args) {
    1. Sub sub = new Sub();
    2. sub.calculateTime();
    3. BB bb = new BB();
    4. bb.calculateTime();
    } } abstract class Template { public void calculateTime() {
    1. long start = System.currentTimeMillis();
    2. job();
    3. long end = System.currentTimeMillis();
    4. System.out.println(end - start);
    } public abstract void job(); } class Sub extends Template { @Override public void job() {
    1. int num = 0;
    2. for (int i = 0; i < 800000; i++) {
    3. num += i;
    4. }
    } } class BB extends Template { @Override public void job() {
    1. int num = 0;
    2. for (int i = 0; i < 300000; i++) {
    3. num *= i;
    4. }
    } }
  1. <a name="YnSVa"></a>
  2. ## 七、接口
  3. <a name="kZWCM"></a>
  4. ### 7.1 为什么有接口
  5. 参考现实中的问题:<br />以电脑的 USB 接口为例,如果没有一个统一的规定【尺寸,形状,排线等】,usb 插槽的厂家和各个设备的厂家就没法做出完全相同的接口,比如电脑接入U盘是一种接口,电脑接入手机是一种接口,电脑接入相机是一种接口,就会造成每一种设备都要提供一个接口,势必会很混乱。<br />所以,接口的提出,可以统一规定,方便管理。
  6. <a name="kgSmE"></a>
  7. ### 7.2 接口快速入门
  8. 这样的设计需求在Java编程/PHP/.net/go 中也是会大量存在的。
  9. ```java
  10. package com.hspedu.interface_;
  11. public interface UsbInterface { //接口
  12. //规定接口的相关方法,即规范
  13. public void start();
  14. public void stop();
  15. }
  1. package com.hspedu.interface_;
  2. //Phone 类 实现 UsbInterface
  3. //解读 1. 即 Phone类需要实现 UsbInterface接口 规定/声明 的方法
  4. public class Phone implements UsbInterface{
  5. @Override
  6. public void start() {
  7. System.out.println("手机开始工作...");
  8. }
  9. @Override
  10. public void stop() {
  11. System.out.println("手机结束工作...");
  12. }
  13. }
  1. package com.hspedu.interface_;
  2. public class Camera implements UsbInterface{ //实现接口,把接口的方法实现
  3. @Override
  4. public void start() {
  5. System.out.println("相机开始工作...");
  6. }
  7. @Override
  8. public void stop() {
  9. System.out.println("相机结束工作...");
  10. }
  11. }
  1. package com.hspedu.interface_;
  2. public class Computer {
  3. //编写一个方法
  4. public void work(UsbInterface usbInterface) {
  5. usbInterface.start();
  6. usbInterface.stop();
  7. }
  8. }
  1. package com.hspedu.interface_;
  2. public class Interface01 {
  3. public static void main(String[] args) {
  4. //创建手机、相机对象
  5. Phone phone = new Phone();
  6. Camera camera = new Camera();
  7. //创建电脑对象
  8. Computer computer = new Computer();
  9. computer.work(phone); //把手机接入到计算机
  10. System.out.println("====================");
  11. computer.work(camera); //把相机接入到计算机
  12. }
  13. }

7.3 基本介绍

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。语法如下:
interface 接口名 {
//属性
//方法【1. 抽象方法 2. 默认方法 3. 静态方法】
}

class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法
}

  1. package com.hspedu.interface_;
  2. public interface Interface03 {
  3. //写属性
  4. public int n1 = 10;
  5. //写方法
  6. //在接口中,抽象方法,可以省略 abstract 关键字
  7. public void hi();
  8. //在jdk8之后,可以有默认实现方法,需要使用default关键字修饰
  9. default public void ok() {
  10. System.out.println(n1);
  11. }
  12. //在jdk8后,可以有静态方法
  13. public static void cry() {
  14. System.out.println("cry...");
  15. }
  16. }

小结

  1. 在 jdk 7.0 前,接口里所有方法都没有方法体,即都是抽象方法。
  2. 在 jdk 8.0 后,接口类可以有静态( static )方法,默认( default )方法,也就是说接口中可以有方法的具体实现。

7.4 接口深入讨论

什么时候使用接口呢?几个应用场景:

  1. 现在要制造战斗机、武装直升机。专家只需把飞机需要的功能/规格定下来即可,然后让别的人具体实现就可

  2. 现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现。

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

  1. 现有3个程序员,项目经理要求编写3个类,分别完成对MySql,Oracle,DB2数据库的连接 connect,close

    1. package com.hspedu.interface_;
    2. //项目经理写的
    3. public interface DBInterface {
    4. public void connect(); //连接方法
    5. public void close(); //关闭连接
    6. }
    1. package com.hspedu.interface_;
    2. //第一个程序员写的
    3. public class MySqlDB implements DBInterface{
    4. @Override
    5. public void connect() {
    6. System.out.println("连接数据库...");
    7. }
    8. @Override
    9. public void close() {
    10. System.out.println("关闭数据库...");
    11. }
    12. }
    1. package com.hspedu.interface_;
    2. //第二个程序员写的
    3. public class OracleDB implements DBInterface{
    4. @Override
    5. public void connect() {
    6. System.out.println("连接Oracle数据库...");
    7. }
    8. @Override
    9. public void close() {
    10. System.out.println("关闭Oracle数据库...");
    11. }
    12. }
    1. package com.hspedu.interface_;
    2. //第三个程序员写的
    3. public class DB2DB implements DBInterface{
    4. @Override
    5. public void connect() {
    6. System.out.println("连接DB2数据库...");
    7. }
    8. @Override
    9. public void close() {
    10. System.out.println("关闭DB2数据库...");
    11. }
    12. }
    1. package com.hspedu.interface_;
    2. //使用测试
    3. public class Interface04 {
    4. public static void main(String[] args) {
    5. MySqlDB mySqlDB = new MySqlDB();
    6. OracleDB oracleDB = new OracleDB();
    7. DB2DB db2DB = new DB2DB();
    8. t(mySqlDB);
    9. t(oracleDB);
    10. t(db2DB);
    11. }
    12. public static void t(DBInterface db) {
    13. db.connect();
    14. db.close();
    15. }
    16. }

7.5 接口的注意事项和细节

  1. 接口不能被实例化
  2. 接口中所有的方法是public 方法,所以可省略,接口中抽象方法,也可以不用 abstract 修饰
  3. 一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用 alt+enter 快捷键
  4. 抽象类实现接口时,可以不用实现接口的方法
  5. 一个类中同时可以实现多个接口
  6. 接口中的属性,只能是final 的,而且是 public static final 修饰符。
    比如:int a = 1; 实际上是 public static final int a = 1;
  7. 接口中属性的访问形式:接口名.属性名
  8. 一个接口不能继承其他的类,但是可以继承(extends)多个别的接口
    实现(implements)只能由其他类实现,接口不能实现接口
  9. 接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的

7.6 实现接口 VS 继承

1. 接口和继承解决的问题不同

继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这种方法

2. 接口比继承更加灵活

普通类的继承是单继承,满足 is - A 的关系 ,而接口只需满足 like - A 的关系,普通类和接口都可以继承多个接口

3. 接口在一定程度上实现代码解耦

即接口规范性 + 动态绑定机制

7.7 接口的多态特性

1. 动态参数

前面的 Usb 接口案例,UsbInterface usb,既可以接收手机对象,又可以接收相机对象,就体现了接口多态

2. 多态数组

演示一个案例:给 Usb 数组中,存放 Phone 和 Camera 对象,Phone类还有一个特别的方法 call(),请遍历 Usb数组,如果是 Phone 对象,除了调用 Usb接口定义的方法外,还需调用 Phone 特有方法 call

3. 接口存在多态传递现象

B接口 继承了 A接口,如果 C类 实现(implements)了 B接口,则 C类 也实现了 A接口

7.8 小节练习

  1. 下面代码是否存在错误?若有错请修改,并说出最后的输出结果。
    interface A {
    int x = 0; //相当于 public static final int x = 0;
    }
    class B {
    int x = 1;
    }
    class C extends B implements A {
    public void pX( ) {
    System.out.println(x); //错误,父类和接口都有x,没有明确说明x是来自哪一个的
    }
    public static void main(String[] args) {
    new C().pX();
    }
    }

八、内部类

8.1 基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员【1. 属性 2. 方法 3. 构造器 4. 代码块 5. 内部类】内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

8.2 基本语法

class Outer { //外部类
class Inner { //内部类
}
}
class Other { //外部其他类
}

8.3 内部类的分类

1. 定义在外部类局部位置上【比如方法内】

  1. 局部内部类(有类名)
  2. 匿名内部类(没有类名) 重点 !!!

2. 定义在外部类的成员位置上

  1. 成员内部类(没用 static 修饰)
  2. 静态内部类(使用 static 修饰)

8.4 局部内部类

说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名,本质仍然是一个类。

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用 final 修饰,因为局部变量也可以使用 final
  3. 作用域:仅仅在定义它的方法体或代码块中
  4. 局部内部类 — 访问 —> 外部类的成员【访问方式:直接访问】
  5. 外部类 — 访问 —> 局部内部类的成员
    访问方式:创建内部类实例对象,再访问(注意:必须在作用域内)
  6. 外部其他类 — 不能访问 —> 局部内部类 (局部内部类的地位就是一个局部变量)
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

8.5 匿名内部类

说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名

  1. 本质是类
  2. 内部类
  3. 该类没有名字
  4. 是一个对象

1. 匿名内部类的基本语法

new 类或接口(参数列表){
类体
}

2. 案例演示

  1. package com.hspedu.innerclass;
  2. /**
  3. * 演示匿名内部类的使用
  4. */
  5. public class AnonymousInnerClass {
  6. public static void main(String[] args) {
  7. Outer04 outer04 = new Outer04();
  8. outer04.method();
  9. }
  10. }
  11. class Outer04 {
  12. private int n1 = 10;
  13. public void method() {
  14. //基于接口的匿名内部类
  15. //解读:
  16. //1. 需求:想使用 IA 接口,并创建对象
  17. //2. 传统方式,是写一个类,实现该接口,并创建对象
  18. //3. 现在的需求是,Tiger 类只是使用一次,后面再不使用,所以没必要专门定义一个类
  19. //4. 可以使用匿名内部类来简化开发
  20. //5. tiger 的编译类型? IA
  21. //6. tiger 的运行类型? 匿名内部类 Outer04$1
  22. /*
  23. 我们看底层 会分配 类名 Outer04$1
  24. class Outer04$1 implements IA {
  25. @Override
  26. public void cry() {
  27. System.out.println("老虎嗷嗷叫");
  28. }
  29. }
  30. */
  31. //7. jdk底层在创建匿名内部类 Outer04$1 ,紧接着就创建了 Outer04$1的实例
  32. // 并且把地址返回给 tiger
  33. //8. 匿名内部类使用一次,就不能再使用了,因为定义在方法体/代码块中,类似于局部变量
  34. IA tiger = new IA() {
  35. @Override
  36. public void cry() {
  37. System.out.println("老虎嗷嗷叫");
  38. }
  39. };
  40. System.out.println("tiger的运行类型 = " + tiger.getClass());
  41. // Tiger tiger = new Tiger();
  42. tiger.cry();
  43. //基于类的匿名内部类
  44. //分析:
  45. //1. father 的编译类型? Father
  46. //2. father 的运行类型? 匿名内部类 Outer04$2
  47. //3. 底层会创建匿名内部类
  48. /*
  49. class Outer04$2 extends Father {
  50. @Override
  51. public void test() {
  52. System.out.println("匿名内部类重写了test()方法...");
  53. }
  54. }
  55. */
  56. //4. 同时也直接返回了 匿名内部类 Outer04$2的对象
  57. Father father = new Father("jack") {
  58. @Override
  59. public void test() {
  60. System.out.println("匿名内部类重写了test()方法...");
  61. }
  62. };
  63. System.out.println("father的运行类型 = " + father.getClass());
  64. father.test();
  65. //基于抽象类的匿名内部类
  66. //必须实现抽象方法
  67. Animal animal = new Animal() {
  68. @Override
  69. void eat() {
  70. System.out.println("动物吃东西");
  71. }
  72. };
  73. }
  74. }
  75. interface IA {
  76. void cry();
  77. }
  78. //class Tiger implements IA{
  79. // @Override
  80. // public void cry() {
  81. // System.out.println("老虎嗷嗷叫");
  82. // }
  83. //}
  84. class Father {
  85. public Father(String name) {
  86. }
  87. public void test () {
  88. }
  89. }
  90. abstract class Animal {
  91. abstract void eat();
  92. }

3. 注意事项和使用细节

  1. 匿名内部类的语法比较特别,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码的分析可以看出这个特点,因此可以调用匿名内部类的方法
  2. 可以直接访问外部类的所有成员,包括私有的
  3. 不能添加访问修饰符,因为它的地位就是一个局部变量
  4. 作用域:仅仅在定义它的方法体或代码块中
  5. 匿名内部类 — 访问 —> 外部类成员 【访问方式:直接访问】
  6. 外部其他类 —不能访问 —> 匿名内部类
  7. 如果外部类和内部类的成员重名时,内部类访问的 话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

4. 使用场景

当作实参直接传递,简洁高效。

  1. package com.hspedu.innerclass;
  2. public class InnerClassExercise {
  3. public static void main(String[] args) {
  4. //匿名内部类(对象)作为实参传入
  5. f1(new AA() {
  6. @Override
  7. public void show() {
  8. System.out.println("这是一个匿名内部类(对象)作为实参");
  9. }
  10. });
  11. //传统方法
  12. f1(new Something());
  13. }
  14. //静态方法,形参是接口类型
  15. public static void f1(AA aa) {
  16. aa.show();
  17. }
  18. }
  19. interface AA {
  20. void show();
  21. }
  22. //传统方式 - 类->实现AA接口 => 编程领域(硬编码)
  23. class Something implements AA{
  24. @Override
  25. public void show() {
  26. System.out.println("输出内容");
  27. }
  28. }

例题:完成以下要求

  1. 有一个铃声接口 Bell,里面有个 ring 方法。
  2. 有一个手机类 Cellphone,具有闹钟功能 alarmclock,参数是Bell类型。
  3. 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了。
  4. 再传入另一个匿名内部类(对象),打印:小伙伴上课了。 ```java package com.hspedu.innerclass;

public class InnerClassTest { public static void main(String[] args) { Cellphone cellphone = new Cellphone(); cellphone.alarmclock(new Bell() { @Override public void ring() { System.out.println(“懒猪起床了”); } }); cellphone.alarmclock(new Bell() { @Override public void ring() { System.out.println(“小伙伴上课了”); } }); } } interface Bell { void ring(); } class Cellphone { public void alarmclock(Bell bell) { bell.ring(); } } ```

8.6 成员内部类

说明:成员内部类是定义在外部类的成员位置,并且没有 static 修饰

  1. 可以直接访问外部类的所有成员,包括私有的
  2. 可以添加任意访问修饰符,因为它的地位就是一个成员
  3. 作用域:和外部类的其他成员一样,为整个类体
  4. 成员内部类 — 访问 —> 外部类的成员(比如:属性)【访问方式:直接访问】
  5. 外部类 — 访问 —> 成员内部类 【访问方式:创建对象,再访问】
  6. 外部其他类 — 访问 —> 成员内部类 【两种访问方式】
    1. 当作外部类的成员调用
      OuterClass.InnerClass inner = new OuterClass().new InnerClass();
    2. 在外部类中写一个返回方法,返回类型为成员内部类
      public InnerClass getInnerClass() {
      return new InnerClass();
      }
  7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

8.7 静态内部类

说明:静态内部类是定义在外部类的成员位置,并且有 static 修饰

  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
  2. 可以添加任意访问修饰符,因为它的地位就是一个成员
  3. 作用域:同其他的成员,为整个类体
  4. 静态内部类 — 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有的静态成员】
  5. 外部类 — 访问 —> 静态内部类 【访问方式:创建对象,再访问】
  6. 外部其他类 — 访问 —> 静态内部类
    1. 静态内部类,可以通过外部类名直接访问(前提是满足访问权限)
      OuterClass.InnerClass inner = new OuterClass.InnerClass();
    2. 编写一个方法,可以返回静态内部类
      public (static) InnerClass getInnerClass() {
      return new InnerClass();
      }
  7. 如果外部类和静态内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问 (因为静态内部类只能访问静态成员,静态成员不需要用 .this.)

学习参考(致谢):

  1. B站 @程序员鱼皮 Java学习一条龙
  2. B站 @韩顺平 零基础30天学会Java