Java-Day-12( 类变量 + 类方法 + main 方法 + 代码块 + 单例设计模式 + final 关键字 )

发布时间 2023-04-18 23:46:19作者: 朱呀朱~

Java-Day-12

类变量

  • 定义一个变量 count,是一个类变量 ( 静态变量 ) static

    class Person {
        private String name;
    //	该静态变量 static 最大的特点就是会被 Person 所有的对象实例共享
        public static int count = 0;
        public Person(String name) {
            this.name = name;
        }
    }
    
    // main 中
    Person p1 = new Person("zhu");
    Person p2 = new Person("zhang");
    System.out.println("静态变量可在 main 中直接用类调用:" + Person.count);
    
  • jvm 中静态变量的存储

    • 如先前所讲,Person 加载时会在方法区加载类信息,栈中的 p1,p2 都指向堆里独立的空间地址,String 类型的变量放进方法区的常量池里
    • 静态 static 在堆里有一个新的空间放 count ( 后面讲到的 class 里 ),两个真正的对象都有一个空间指向着这个同在堆里的 count ,被 p1、p2 共享
    • 类信息加载时,在方法区有一个空间叫静态域,JDK 7 和 7 之前版本是把 count 放在了静态域里,而不是堆中
    • JDK 8 和 8 之后至今是放在了堆里,在堆里会根据反射机制增加一个 class 对象 ( 类加载的时候就会产生,每个类都会产生 ),静态变量就放在 Person 类的 class 对象的最后尾部。
    • 但不管版本如何,共识一致:
      • static 变量是同一个类所有对象共享的
      • static 变量是在类加载的时候就生成了
  • 定义语法

    • 访问修饰符 static 数据类型 变量名;( 推荐 )
    • static 访问修饰符 数据类型 变量名;
  • 访问方式

    • 类名.类变量名 ( 推荐 )

    • 对象名.类变量名

    • 注意:

      • 因为类变量是随着类的加载而创建的,所以即使没有创建对象实例也可以直接访问
      • 类变量的访问也要遵守访问权限 ( private 只能本类访问 )
      public static void main(String[] args) {
          System.out.println(Person.count);
      // 也是可以输出初始化值 0 的
      }
      
  • 注意细节

    • 当我们需要让某个类的所有对象都共享一个变量时
    • 类变量是该类所有对象共享的,而实例变量是每个对象独享的
    • 加上 static 就叫做静态变量或者类变量,否则可能会被称为实例变量 / 普通属性 / 普通成员变量 / 非静态属性 / 非静态成员变量
    • 推荐在满足访问权限规则的基础上使用 类名.类变量名 的表达方式
    • 实例变量不能通过 类名.实例变量 直接访问
    • 类变量是在类加载的时候就初始化了,即就算没有创建对象,只要类加载了就可以使用类变量了
    • 类变量的生命周期是随着类加载而开始,随着类的消亡而销毁

类方法

  • 类方法也叫静态方法

  • 定义语法

    • 访问修饰符 static 数据返回类型 方法名(){ } ( 推荐 )
    • static 访问修饰符 数据返回类型 方法名()
  • 调用方式

    • 类名.类方法名
    • 对象名.类方法名
    • 仍要注意修饰符访问权限范围
  • 使用场景

    • 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
      • 例如:Math 类 ( Math.sqrt() )、Arrays 类、Collections 集合类
    • 如果我们希望不创建实例也可以调用某个方法,即当作工具来使用,就用来当作静态方法
      • 实际开发时,往往将一些通用的方法设计成静态方法,就像打印一组数组、冒泡排序、完成某个计算任务等
  • 使用细节

    • 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区

      • 类方法中无 this 参数 ( 也是类名.类变量等表示方法 )
      • 普通方法中隐含 this 的参数
    • 普通方法和对象有关,需要通过对象名调用,不能用类名来调用

    • 类方法中不允许使用和对象有关的关键字 ( 如:this、super )

    • 类方法中只能访问与类变量或者类方法,即静态方法只能访问静态成员

      public class T {
          private static String duang;
          private String name;
          public void f1(){ ... }
          public static void f2(){ ... }
      
          public static void test(){
                  System.out.println(duang);
                  System.out.println(T.duang);
          //        System.out.println(this.duang); // 报错
          //        System.out.println(name); // 报错
      
          //        f1(); // 报错
                  f2();
              }
      }
      
    • 普通成员方法,既可以访问非静态成员也可以访问静态成员

      • 从而就可以通过普通方法对静态变量加以修改

main 方法

  • 形式:

    • public static void main(String[] args){}
  • main 方法是虚拟机调用

    • java 虚拟机需要调用类的 main() 方法,所以该方法的访问权限必须是 public
    • java 虚拟机在执行 main() 方法时不必创建对象,所有该方法必须是 static
    • 该方法接收 String 类型的数组参数,该数组中保存执行 java 命令时传递给所运行的类的参数
  • 案例演示

    // test.java:
    public class test {
        public static void main(String[] args) {
    //        args 是如何传入的
    //        遍历显示
            for (int i = 0; i < args.length; i++) {
                System.out.println("第" + (i+1) + "个参数=" + args[i]);
            }
        }
    }
    
    • 在命令行里传递参数

      • 此时用 DOS 编译,"javac test.java",运行时,"java test n1 n2 n3",就会输出:
        第1个参数=n1
        第2个参数=n2
        第3个参数=n3
      • 是在执行程序时,把 "n1 n2 n3" 当作字符串数组 args 传入
    • 在 idea 里面传递参数

      image-20230418161702989

      image-20230418161930875

      image-20230418162145852

    • Apply OK 后再运行 test.java 就会输出:

      第1个参数=你好呀
      第2个参数=Java
      第3个参数=朱呀朱
      第4个参数=在线
      第5个参数=为您
      第6个参数=服务

  • 注意细节

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

代码块

( codeblock )

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

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

    • 修饰符 { 代码 };
    • 注意
      • 修饰符可不写,但要写的话也只能写 static
      • 代码块分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的叫普通代码块 / 非静态代码块
      • 逻辑语句可以为任何逻辑语句 ( 输入、输出、方法调用、循环、判断等 )
      • ;号也可以写上,也可以省略
  • 代码块好处

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

    • 下面三个重载构造器都有相同的语句,出现冗余

    • 这时就把相同语句放进一个代码块中即可

    • 这样的话,不管调用哪个构造器,创建对象,都会先调用代码块的内容,再调用所需构造器

      class Movie {
          private String name;
          private double price;
          private String director;
      //		  代码块:
          {
              System.out.println("买完票了");
              System.out.println("买完吃喝了");
              System.out.println("开始看电影了");
          }
      
          public Movie(String name) {
      //        System.out.println("买完票了");
      //        System.out.println("买完吃喝了");
      //        System.out.println("开始看电影了");
              this.name = name;
          }
      
          public Movie(String name, double price) {
      //        System.out.println("买完票了");
      //        System.out.println("买完吃喝了");
      //        System.out.println("开始看电影了");
              this.name = name;
              this.price = price;
          }
      
          public Movie(String name, double price, String director) {
      //        System.out.println("买完票了");
      //        System.out.println("买完吃喝了");
      //        System.out.println("开始看电影了");
              this.name = name;
              this.price = price;
              this.director = director;
          }
      }
      
  • 注意细节

    • static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,而且只会执行一次

      • 如果是普通代码块,每创建一个对象就执行
    • 记住类什么时候被加载

      • 创建对象实例时 ( new )
      • 创建子类对象实例,或调用子类静态成员时,父类也会被加载
      • 使用类的静态成员时 ( 静态属性,静态方法 )
    • 普通代码块在创建对象实例时,会被隐式的调用,被创建一次,就调用一次

      • 而如果只是使用类的静态成员时,普通代码块并不会执行
      • static 代码块是只有类加载时才执行一次
      class AA {
          public static String a = "AA的静态属性";
          static {
              System.out.println("AA的静态代码块");
          }
          {
              System.out.println("AA的普通代码块");
          }
      }
      class BB {
          public static String a = "BB的静态属性";
          static {
              System.out.println("BB的静态代码块");
          }
          {
              System.out.println("BB的普通代码块");
          }
      }
      
      public class test {
          public static void main(String[] args) {
              System.out.println(AA.a);
              System.out.println("——————————");
              AA aa = new AA();
              System.out.println("——————————");
              BB bb = new BB();
      
          }
      }
      
      • 输出为:

        AA的静态代码块
        AA的静态属性
        ——————————
        AA的普通代码块
        ——————————
        BB的静态代码块
        BB的普通代码块

    • 创建一个对象时,在一个类的调用顺序 ( 优先级 1 ~ 3 )

      1. 调用静态代码块和静态属性初始化

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

        • 静态属性在静态代码块前面时
        public class test {
            public static void main(String[] args) {
                AA aa = new AA();
            }
        }
        
        class AA {
            public static int a = f1();
            static {
                System.out.println("静态代码块");
            }
            public static int f1(){
                System.out.println("方法f1");
                return 1;
            }
        }
        // 输出为:
        // 方法f1
        // 静态代码块
        
        // main里是System.out.println(AA.a); 的话输出为:
        // 方法f1
        // 静态代码块
        // 1
        
        • 静态代码块放静态属性前面时
        class AA {
            static {
                System.out.println("静态代码块");
            }
            public static int a = f1();
            public static int f1(){
                System.out.println("方法f1");
                return 1;
            }
        }
        
        // 输出为:
        // 静态代码块
        // 方法f1
        
        // main里是System.out.println(AA.a); 的话输出为:
        // 静态代码块
        // 方法f1
        // 1
        
      2. 调用普通代码块和普通属性的初始化

        普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用 ( 似 static,但不管顺序如何输出都是在所有静态的后面 )

      3. 调用构造方法

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

      class AA {
          public AA() {
      		//(1)super()
              //(2)调用本类的普通代码块
              System.out.println("最后轮到构造器")
          }
      }
      
    • 创建一个子类时 ( 继承关系 ),静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造方法的调用顺序如下

      1. 父类的静态代码块和静态属性 ( 优先级一样,按定义顺序执行 )
      2. 子类的静态代码块和静态属性 ( 优先级一样,按定义顺序执行 )
      3. 父类的普通代码块和普通属性初始化 ( 优先级一样,按定义顺序执行 )
      4. 父类的构造方法
      5. 子类的普通代码块和普通属性初始化 ( — 普通属性初始化会造成某方法运行输出语句的话 ) ( 优先级一样,按定义顺序执行 )
      6. 子类的构造方法
      // 主main
      public class test {
          public static void main(String[] args) {
              new BB();
          }
      }
      // 父类
      class AA {
          private static int a1 = getVal_a1();
          static {
              System.out.println("AA静态代码块"); // 2
          }
          {
              System.out.println("AA普通代码块"); // 5
          }
          public int a2 = getVal_a2();
          public static int getVal_a1(){
              System.out.println("getVal_a1"); // 1
              return 1;
          }
          public int getVal_a2(){
              System.out.println("getVal_a2"); // 6
              return 1;
          }
      
          public AA() {
              System.out.println("AA构造器"); // 7
          }
      }
      // 子类
      class BB extends AA{
          private static int b1 = getVal_b1();
          static {
              System.out.println("BB静态代码块"); // 4
          }
          public int b2 = getVal_b2();
          {
              System.out.println("BB普通代码块"); // 9
          }
          public static int getVal_b1(){
              System.out.println("getVal_b1"); // 3
              return 1;
          }
          public int getVal_b2(){
              System.out.println("getVal_b2"); // 8
              return 1;
          }
      
          public BB() {
              System.out.println("BB构造器"); // 10
          }
      }
      
    • 静态代码块只能调用静态成员,普通代码块可以调用任意成员

单例设计模式

  • 设计模式 ( 23 种 )

    • 静态方法和属性的经典使用
    • 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式
    • 设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们再思考和摸索
  • 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

  • 单例模式

    • 饿汉式

      1. 构造器私有化 ( 防止直接 new )

      2. 类是内部创建对象

      3. 向外暴露一个静态的公共方法

      4. 代码实现

      5. 实际在 main 里,但凡是涉及了加载了类 Wife ( 就算不调用实例的 get 方法 ),不管有没有用到都创建好了一个实例对象 ( 正式项目时对象一般都比较大,产生无用对象 ),所以叫饿汉

      public class test {
          public static void main(String[] args){
              // 4. 实现
              Wife instance = Wife.getInstance();
              System.out.println(instance);
          }
      }
      
      class Wife {
          private String name;
          // 2. 类内部创建对象,为了能被静态方法获取,要 static 修饰
          private static Wife w = new Wife("静静");
          // 1. 为保证只能创建一个 Wife 对象 —— 私有化
          private Wife(String name){
              this.name = name;
          }
          // 3. static 后,才能在不 new 创建的时候就能调用此方法
          public static Wife getInstance(){
              return w;
          }
      	// 没有下述代码,4.输出的就是一个地址
          // 加载类Wife,静态属性w获取new堆中对象地址,构造器得name为静静,getInstance()静态方法直接调用,返回w(堆中对象地址)给了类引用名instance,输出此地址
          @Override
          public String toString() {
              return "Wife{" +
                      "name='" + name + '\'' +
                      '}';
          }
      }
      
    • 懒汉式

      1. 构造器私有化
      2. 定义一个 static 静态属性对象
      3. 提供一个 public 的 static 方法,可以返回一个 GirlFriend 对象
      4. 就算是类加载了,也不会调用构造器,即不会创建对象
      5. 懒汉式,只有当对象调用了实例的 get 方法才能完成对象第一次的创建返回 girlfriend 对象,之后就算再调用也只会返回上次创建的对象
      public class test {
          public static void main(String[] args){
              GirlFriend instance = GirlFriend.getInstance();
              System.out.println(instance);
          }
      }
      class GirlFriend {
          private String name;
          private static GirlFriend girlfriend;
          private GirlFriend(String name){
              this.name = name;
          }
          // 通过判断对象是否为null来决定是否进行第一次的创建
          public static GirlFriend getInstance(){
              if (girlfriend == null){
                  girlfriend = new GirlFriend("静静");
              }
              return girlfriend;
          }
      
          @Override
          public String toString() {
              return "Wife{" +
                      "name='" + name + '\'' +
                      '}';
          }
      }
      
    • 饿汉式和懒汉式的区别:

      • 二者最主要的区别在于创建对象的时机不同,饿汉式类加载就创建了对象实例,而懒汉式式在使用时才创建
      • 饿汉式不存在线程安全问题,懒汉式存在线程安全问题 ( 例如在判断对象是否为 null 时若是同时进来多个线程就会都通过到 new,只不过只返回最后一个创建的对象而已 ),在学习线程时再加完善
      • 饿汉式存在资源浪费的问题,而懒汉式不存在

final 关键字

  • final 可以修饰类、属性、方法和局部变量

  • final 需求场景

    • 当不希望类被继承时

      final class A { }
      
    • 当不希望父类的某个方法被子类覆盖 / 重写时

      class A {
          public final void f1(){}
      }
      
    • 当不希望类的某个属性的值被修改

    class A {
        public final double TAX_RATE = 0.08;
    }
    
    • 当不希望某个局部变量被修改

      class A {
          public void f1(){
              final double TAX_RATE = 0.08;
      //        TAX_RATE = 0.1; 就不能改了
          }
      }
      
  • 使用细节

    • final 修饰的属性又叫常量,一般 XX_XX_XX 命名

    • final 修饰的属性在定义时必须赋值,而且以后不能再修改,赋值可以在如下位置之一:

      • 定义时

        class A {
            public final double TAX_RATE = 0.08;
        }
        
      • 在构造器中

        class A {
            public final double TAX_RATE;
            public A(){
                TAX_RATE = 0.08;
            }
        }
        
      • 在代码块中

        class A {
            public final double TAX_RATE;
            {
                TAX_RATE = 0.08;
            }
        }
        
    • 但如果 final 修饰的属性是静态的,则初始化的位置只能是

      • 定义时

        class A {
            public static final double TAX_RATE = 0.08;
        //    final 写 static 前面后面都可
        }
        
      • 在静态代码块 ( 不能在构造器中赋值 —— 静态在加载时就得创建好 )

        class A {
            public final double TAX_RATE;
            static {
                TAX_RATE = 0.08;
            }
        }
        
    • final 类不能继承,但是可以实例化对象

      final class A { } 
      // main里:
      A a = new A();
      
    • 如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承使用

      class A {
          public final void f1(){
              System.out.println("f1方法");
          }
      }
      class B extends A { }
      // main里:
      new B().f1();
      
    • 一般来说,如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法 ( 因为类 final 了就没法继承了,更别说重写方法了 )

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

    • final 和 static 往往搭配使用效率更高,底层编译器做了优化处理,不会导致类加载,只想调用一个静态属性 ( 调用静态属性时不触动静态代码块 )

      public class test {
          public static void main(String[] args){
              System.out.println(A.num);
              System.out.println(B.num);
          }
      }
      
      class A {
          public static int numA = 10;
          static {
              System.out.println("A静态代码块被执行了");
          }
      }
      class B {
          public final static int numB = 10;
          static {
              System.out.println("B静态代码块被执行了");
          }
      }
      
      // 输出:
      // A静态代码块被执行了
      // 10
      // 10
      
    • 包装类 ( Integer,Double,Float,Boolean 等都是 final ),String 也是 final 类,都不能被继承 ( 可 ctrl + B 查看 )

  • 小练习

    • 编写一个程序能够计算圆形的面积,要求圆周率为 3.14

      class Circle{
          private double radius;
      //    F1:
          private final double PI = 3.14;
      
          public Circle(double radius) {
              this.radius = radius;
      //        F2:
      //        PI = 3.14;
          }
          {
      //        F3:
      //        PI = 3.14;
          }
          public double calArea(){
              return PI * radius * radius;
          }
      }
      
    • 判断代码是否有误

      public class A {
          public int add(final int x) {  // 此处可以加final修饰
              ++x;  // 报错,不准修改
              return x + 1;  // 正确
          }
      }