类变量

什么是类变量

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

定义类变量

  • 定义语法

访问修饰符 static 数据类型 变量名;【推荐】

static 访问修饰符 数据类型 变量名;

  • 访问类变量

类名.类变量名【推荐】

或者 对象名.类变量名【静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的。】

class Child {
    private String name;
    // 定义一个变量count,是一个类变量(静态变量)static 静态
    // 该变量的最大特点是会被Child 类的所有对象实例共享
    public static int count = 0;
    public Child(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        Child child01 = new Child("lili");
        child01.count++;
        Child child02 = new Child("huahua");
        child02.count++;
        System.out.println(child01.count); // 2
        System.out.println(child02.count); // 2
        System.out.println(Child.count);   // 2
    }
}

(1)static变量是同一个类所有对象共享

(2)static变量,在类加载的时候就生成了

类变量的使用细节

  1. 什么时候需要类变量:需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量
  2. 类变量与实例变量(普通属性)区别:类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
  3. 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
  4. 类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问【要满足访问修饰符的访问权限和范围】
  5. 实例变量不能通过 类名.类变量名 方式访问
  6. 类变量是在类加载时就初始化了,也就是说,即使没有创建对象,只要类加载了,就可以使用类变量
  7. 类变量的生命周期是随着类的加载开始,随着类消亡而销毁

类方法

类方法基本介绍

类方法也叫静态方法

语法:

访问修饰符 static 数据返回类型 方法名() {...} 【推荐】

static 访问修饰符 数据返回类型 方法名() {...}

类方法的调用

使用方法:

类名.类方法名 或者 对象名.类方法名【前提是 满足访问修饰符的访问权限和范围】

public class StaticMethod {
    public static void main(String[] args) {
        Stu tom = new Stu("tom");
        tom.payFee(100);

        Stu mary = new Stu("mary");
        mary.payFee(200);

        // 输出当前所收取到的学费
        Stu.showFee();

    }
}
class Stu{
    private String name; // 普通成员
    // 定义一个静态变量,来累积学生的学费
    private static double fee = 0;
    public Stu(String name) {
        this.name = name;
    }
    // 1. 当方法使用了static修饰后,该方法就是静态方法
    // 2. 静态方法只可以访问静态属性/变量
    public static  void payFee(double fee){
        Stu.fee += fee;
        //this.fee += fee; // 不能使用this
    }
    public static void showFee(){
        System.out.println("总学费有:" + fee);
    }
}
  1. 非静态成员(比如 name)属于【具体的对象】

    • 类加载后,并不会为非静态成员分配内存;
    • 只有当你new Stu("张三")new Stu("李四")创建对象实例时,才会为每个对象单独分配一份非静态成员的内存;
    • 每个学生对象有自己的name,张三的 name 和李四的 name 互不干扰,这就是对象的独有属性
  2. 静态成员(比如 fee、payFee ()、showFee ())属于【类本身】

    • 类第一次被加载时(还没创建任何对象),静态成员就会被初始化并分配唯一的一份内存,存放在 JVM 的静态区;
    • 所有该类的对象,共享这一份静态成员(比如所有 Stu 对象的学费都累加到同一个 Stu.fee 里);
    • 可以直接通过类名.静态成员调用(比如Stu.payFee(1000)),无需创建对象。

原因 1:为什么静态方法不能用this

this关键字的唯一作用是:代表当前执行该方法 / 构造器的【具体对象实例】,简单说 ——this指向一个实实在在的对象

而静态方法的执行根本不依赖任何对象

  • 你可以直接调用Stu.payFee(5000),此时没有创建任何 Stu 对象
  • 既然没有对象,this就没有任何指向的目标,Java 编译器自然会报错。

一句话总结:this是对象的 “代名词”,静态方法属于类、不关联对象,所以用不了this


原因 2:为什么静态方法只能访问静态属性 / 变量?

这个原因是上面概念的直接推导,核心逻辑是:静态方法执行时,可能根本不存在任何对象,而非静态成员必须依附对象才能存在

结合你的代码举例:

  • 静态方法payFee()属于 Stu 类,调用Stu.payFee(2000)时,没有任何 Stu 对象被创建;
  • 非静态变量name是对象的独有属性,此时连对象都没有,name的内存都没分配,静态方法根本 “找不到”name
  • 而静态变量Stu.fee在类加载时就已经存在,无论有没有对象,静态方法都能直接访问到这唯一的一份内存。

反过来想:非静态方法可以访问静态成员(比如在 Stu 里加一个普通方法,能直接用 fee),原因是 —— 非静态方法必须由对象调用(比如stu1.xxx()),此时对象存在,类必然已经加载,静态成员一定存在。

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

  • 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无this参数
  • 类方法可以通过类名调用,也可以通过对象名调用
  • 普通方法和对象有关,需要通过对象名调用,不能通过类名调用。
  • 类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
  • 类方法(静态方法中)只能 访问静态变量或者静态方法
  • 普通成员方法,可以访问 普通成员,即非静态成员,也可以访问静态成员。

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

main方法

深入理解main方法

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

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

特别提示

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

代码块

基本介绍

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

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

基本语法

[修饰符]{

        代码

};

说明注意:

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

代码块的好处

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
  2. 场景:如果多个构造器中有重复的语句,可以抽取到初始化块中,提高代码的重用性。
public class CodeBlock01 {
    public static void main(String[] args) {
        Movie movie = new Movie("喜羊羊与灰太狼");
    }
}
class Movie{
    private String name;
    private double price;
    private String director;
    
    //这三个语句原本是这两个构造器中都包含的语句
    // 但是看起来太过冗余
    // 把相同的语句,放入到同一个代码块中
    // 当不管调用哪个构造器,创建对象,都会先调用代码块的内容
    {
        System.out.println("A");
        System.out.println("B");
        System.out.println("C");
    }
    public Movie(String name){
        System.out.println("Movie(String name)构造器被调用!");
        this.name = name;
    }
    public Movie(String name, double price, String director){
        System.out.println("Movie(String name, double price, String director)被调用");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

注意事项

  1. static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
  2. 类什么时候被加载[重要!]
    1. 创建对象实例时(new)
      // 定义一个简单的类
      class Person {
          // 静态代码块,用来直观看到类被加载的时机
          static {
              System.out.println("Person类被加载了!");
          }
          
          String name;
          public Person(String name) {
              this.name = name;
          }
      }
      
      public class ClassLoadDemo {
          public static void main(String[] args) {
              System.out.println("准备创建Person实例");
              // 这行代码会触发Person类的加载
              Person p = new Person("张三");
          }
      }
      

      只有执行new Person()时,Person 类才被加载,而不是程序一开始就加载。

    2. 创建子类对象实例,父类也会被加载
      // 父类
      class Animal {
          static {
              System.out.println("Animal父类被加载了!");
          }
      }
      
      // 子类
      class Cat extends Animal {
          static {
              System.out.println("Cat子类被加载了!");
          }
      }
      
      public class ClassLoadDemo2 {
          public static void main(String[] args) {
              System.out.println("准备创建Cat实例");
              // 创建子类实例,触发父类+子类的加载
              Cat cat = new Cat();
          }
      }
      

      父类的加载是子类实例化的前提,哪怕只用到子类,父类也会被加载。

    3. 使用类的静态成员时(静态属性,静态方法)
      class MathUtil {
          static {
              System.out.println("MathUtil类被加载了!");
          }
          
          // 静态属性
          public static final double PI = 3.1415926;
          
          // 静态方法
          public static int add(int a, int b) {
              return a + b;
          }
      }
      
      public class ClassLoadDemo3 {
          public static void main(String[] args) {
              System.out.println("准备访问静态属性");
              // 访问静态属性,触发类加载
              System.out.println(MathUtil.PI);
              
              System.out.println("准备调用静态方法");
              // 调用静态方法(此时类已加载,不会重复加载)
              System.out.println(MathUtil.add(1, 2));
          }
      }
      

      关键点:类只会被加载一次,后续再访问静态成员,不会重复加载。

  3. 普通代码块,在创建对象实例时,会被隐式的调用。如果被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。
  4. 创建一个对象时,在一个类 调用顺序是
    1. 先调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序执行)
    2. 后调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通变量初始化,则按照他们定义的顺序执行)
    3. 最后调用构造方法
      public class CodeBlockDetail02 {
          public static void main(String[] args) {
              A a = new A();  // 输出结果:(1)getN1被调用...(2)A 静态代码块01...
                              // (3)getN2被调用...(4)A 的普通代码块01...
                              // (5) A 的构造器被调用
          }
      }
      class A{
          public A(){
              System.out.println("A 的构造器被调用...");
          }
      
          private int n2 = getN2(); //普通属性/非静态属性的初始化
          { // 普通代码块
              System.out.println("A 的普通代码块01...");
          }
      
          private static int n1 = getN1();
      
          static{
              System.out.println("A 静态代码块01...");
          }
      
          public static int getN1(){
              System.out.println("getN1被调用...");
              return 100;
          }
      
          public int getN2(){
              System.out.println("getN2被调用...");
              return 200;
          }
      }
      

  5. 构造器 的最前面隐含了super()和调用普通代码块,在类加载时,就执行完毕静态相关的代码块以及属性初始化,因此是优先于构造器和普通代码块执行的
    public class CodeBlockDetail03 {
        public static void main(String[] args) {
            BBB b = new BBB(); // 运行结果:(1)AAA() 构造器被调用...(2)BBB 的普通代码块...(3)BBB() 构造器被调用...
        }
    }
    class AAA{
        public AAA(){
            // 这里有隐藏执行要求
            // (1)super();
            // (2)调用普通代码块
            System.out.println("AAA() 构造器被调用...");
        }
    }
    
    class BBB extends AAA{
        {
            System.out.println("BBB 的普通代码块...");
        }
        public BBB(){
            // 这里有隐藏执行要求
            // (1)super();
            // (2)调用普通代码块
            System.out.println("BBB() 构造器被调用...");
        }
    }

  6. 创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
    1. 父类静态代码块和静态属性(优先级一样,按照定义顺序执行)
    2. 子类静态代码块和静态属性(优先级一样,按照定义顺序执行)
    3. 父类普通代码块和普通属性
    4. 父类的构造方法
    5. 子类普通代码块和普通属性
    6. 子类的构造方法
  7. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员

小结:

  • 静态代码块(static{}):只执行一次,在类第一次被加载时执行(无论创建多少个对象,都只执行一次);
  • 普通代码块是在创建对象时调用的,创建一次,调用一次
  • 类被加载的三种情况!
  • 先静态后普通,代码块以及属性初始化看顺序,最后执行构造器
  • 父类static代码块 > 本类静态代码块 > 父类的普通代码块 > 父类构造器> 本类的普通代码块> 本类构造器

静态方法和属性的经典使用

设计模式

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。

单例模式

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

单例模式应用实例

步骤如下:

1)构造器私有化  ---> 防止直接new

2)类的内部创建对象

3)向外暴露一个静态的公共方法   ----> getInstance

饿汉式

没有用上那个实例,但是还是创建了。可能造成创建了对象,但是没有使用,导致资源浪费

public class SingleTon01 {
    public static void main(String[] args) {
        // 通过方法可以获取对象
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);

        GirlFriend instance1 = GirlFriend.getInstance();
        System.out.println(instance1);

        System.out.println(instance1 == instance); // true

    }
}

// 类 GirlFriend
// 只能有一个女朋友
class GirlFriend{
    private String name;
    // 为了能够在静态方法中,返回gf对象,需要将其修饰为static
    private static GirlFriend gf = new GirlFriend("小红");
    // 保证我们只能创建一个girlfriend
    // 步骤:[单例模式-饿汉式]
    //      1. 将构造器私有化
    //      2. 在类的内部直接创建对象(该对象是static)
    //      3. 提供一个公共的static 方法,返回gf对象
    private GirlFriend(String name) {
        this.name = name;
    }
    public static  GirlFriend getInstance(){
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name = '" + name + '\'' +
                "}";
    }
}
懒汉式

使用时,再按照需要创建实例

// 懒汉式的单例模式
public class SingleTon02 {
    public static void main(String[] args){
        Cat instance = Cat.getInstance();
        System.out.println(instance);
        Cat instance1 = Cat.getInstance();
        System.out.println(instance1);
        System.out.println(instance1==instance); // true 表明,没有新创建2个对象
    }

}

// 希望在程序运行过程中,只能创建一个cat对象
// 使用单例模式
class Cat{
    private  String name;
    // 步骤
    // 1. 将构造器私有化
    // 2. 定义一个静态属性对象
    // 3. 提供一个公共的static方法,可以返回一个Cat对象
    // 4. 懒汉式,只有当用户使用这个方法时,才返回cat对象,当后面再次调用时,会返回上次创建的对象,
    // 从而保证了单例
    private static Cat cat;
    private Cat(String name){
        this.name = name;
    }
    public static Cat getInstance(){
        if (cat == null){// 如果还没有创建一个cat对象
            cat = new Cat("小花");
        }
        return cat;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name = '" + name + '\'' +
                "}";
    }
}
饿汉式VS懒汉式
  1. 二者最注意的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
  3. 饿汉式存在资源浪费的可能,如果一个对象实例没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐