一.static静态关键字

1.static是什么,static修饰成员变量的用法

  • static是静态的意思,可以用来修饰成员变量、成员方法。
  • static修饰成员变量之后称为静态成员变量(类变量),修饰方法之后称为静态方法(类方法)。
  • static修饰后的成员变量,可以被类的所有对象共享(访问、修改)
  • 静态成员变量(有static修饰,属于类,加载一次,内存中只有一份),访问格式:
  1. 类名.静态成员变量(推荐)
  2. 对象.静态成员变量(不推荐)
  • 实例成员变量(无static修饰,属于对象,每个对象中都存储1份),访问格式:
  1. 对象.实例成员变量
  • 静态成员变量:表示在线人数等需要被类的所有对象共享的信息时。
  • 实例成员变量:属于每个对象,且每个对象的信息不同时(如:name、age....)
package com.gch.d1static;

public class User {
    /**
        在线人数
        注意:static修饰的成员变量,静态成员变量,只在内存中存储1份,可以被所有对象共享
     */

    // 静态成员变量
    public static int onlineNumber = 161;

    // 实例成员变量:无static修饰的,属于每个对象的,必须使用对象名.访问
    private String name;
    private int age;

    public static void main(String[] args) {
        // 目标:理解static修饰成员变量的作用和访问特点
        // 1. 类名.静态成员变量
        System.out.println(User.onlineNumber); // 161

        // 2.对象名.静态成员变量
    //    System.out.println(User.name); 报错
        User u = new User();
        u.name = "张三";
        u.age = 18;
        System.out.println(u.name);
        System.out.println(u.age);


        u.onlineNumber++; // 新进来了一个人
        System.out.println(u.onlineNumber); // 162

        // 注意:同一个类中静态成员变量的访问可以省略类名
        System.out.println(onlineNumber); // 162
    }
}

 2.static修饰成员变量的内存原理

  • 实例成员变量属于每一个对象,对象在堆内存中。
  • 当这个类加载到方法区的同时,它会同步的在堆内存中开辟一块儿这个类的静态变量区,并且只加载一次。
  • 同一个类中,访问静态成员变量,类名可以省略不写。
  • 同一个类中,访问静态成员方法,类名可以省略不写。 

 3.static修饰成员方法的用法

 成员方法的分类:

  • 静态成员方法(有static修饰,归属于类,属于类和对象共享):建议用类名访问,也可以用对象访问。
  • 实例成员方法(无static修饰,归属于对象):只能用对象触发访问。
  • 同一个类中,访问静态成员方法,类名可以省略不写。 
  1. 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
  2. 如果该方法是以执行一个共用 / 通用功能为目的,则可以申明成静态方法。
package com.gch.d1static;

public class Student {
    /*
        实例成员变量:无static修饰,属于对象
     */
    private String name;

    /**
        静态成员方法:有static修饰,归属于类,可以被共享访问,用类名或者对象民都可以访问
     */
    public static int getMax(int age1,int age2){
        return age1 > age2 ? age1 : age2;
    }
    
    /**
        实例方法:属于对象的,只能用对象触发访问
     */
    public void study(){
        System.out.println(name + "正在学习!");
    }

    public static void main(String[] args) {
        // 1.类名.静态成员方法
        System.out.println(Student.getMax(10, 3));
        // 注意:同一个类中,访问静态方法,类名可以省略不写
        System.out.println(getMax(10, 30));

     //   study(); // 报错了
        // 2.对象.实例方法
        Student s = new Student();
        s.name = "小明";
        s.study();

        // 3.对象.静态方法(语法可行,但是不推荐)
        System.out.println(s.getMax(10, 23));
    }
}

 4.static访问的注意事项:

  • 静态方法只能访问静态的成员,不可以" 直接 "访问实例成员。
  • 实例方法中可以访问静态成员,也可以访问实例成员。
  • 静态方法中是不可以出现this关键字的
package com.gch.d1static;

public class Test3 {
    public static void main(String[] args) {
        // 目标:理解static访问相关的语法:面试笔试题,或者以后理解程序很重要的知识(拓展)
        // 静态成员是可以被类和对象共享的
        /* static访问注意事项:
           静态方法只能访问静态的成员,不可以"直接"访问实例成员
           实例方法可以访问静态成员,也可以访问实例成员
           静态方法中是不可以出现this关键字的
         */
    }
    /**
        静态成员
     */
    public static int onlineNumber = 10;
    public static void test2(){
        System.out.println("=====test2====");
    }

    /**
        实例成员
     */
    private String name;
    public void run(){
        System.out.println(name + "跑的快!");
    }

    // 1.静态方法只能访问静态成员,不能"直接"访问实例成员
    public static void test(){
        System.out.println(Test3.onlineNumber);
        System.out.println(onlineNumber);
        test();
     //   System.out.println(name); // 直接报错,静态方法不能直接访问实例成员。
       // run(); //直接报错,静态方法不能直接访问实例成员
    }

    // 2.实例方法可以访问静态成员,也可以访问实例成员
    public void go(){
        System.out.println(Test3.onlineNumber);
        System.out.println(onlineNumber);
        test2();
        System.out.println(this); // this可以出现在实例方法中,代表本类对象的地址
        System.out.println(name);
        run();
    }

    // 3.静态方法中不可以出现this关键字
    public static void test3(){
        // this代表本类对象,而静态方法是归属于类的
//        System.out.println(this); // 直接报错,静态方法中不可以出现this关键字
    }
}

二.static应用知识:工具类

工具类是什么?

  • 工具类类中都是一些静态方法,每个方法都是以完成一个共用的功能为目的,这个类用来给系统开发人员共同使用的。
  • 使用工具类的好处:一是调用方便,二是提高了代码的复用性,提高了开发效率(一次编写,处处可用)

 为什么工具类中的方法不用实例方法做?

  • 实例方法需要创建对象调用。
  • 而静态方法直接用类名即可访问,此时用对象只是为了调用方法,这样只会浪费内存

工具类定义时的其他要求:

  • 由于工具类里面都是静态方法,直接用类名即可访问,因此工具类无需创建对象,建议将工具类的构造器进行私有。
  • 工具类没有必要被其他类继承,因此将工具类用final修饰为最终类。

package com.gch.d2_static_util;

import java.util.Random;

/**
    工具类
    使用工具类的好处:调用方便,提高了代码的复用性
    使用工具类定义的方法只是为了完成一个公用功能,公用功能写成静态方法,调用方便,只需要通过类名来调用
    如果定义成实例方法则需要创建对象,通过对象来调用,此时用对象只是为了调用方法,这样只会浪费内存
    由于工具类里面都是静态方法,直接用类名即可访问,因此工具类无需创建对象,建议将工具类的构造器进行私有
    将工具类的构造器私有,对外就不能创建对象了
    将工具类用final修饰,不可以被其他类继承
 */
public final class Util {
    // 将工具类的构造器私有,使其不能向外创建对象
    /**
        注意:由于工具类无需创建对象,所以将其构造器私有化会显得很专业
     */
    private Util(){
    }

    /**
        静态方法:随机生成n位验证码
     */
    public static String createVerifyCode(int n){
        // 1.定义一个字符串存储可能出现的字符信息
        String datas = "abcdefghijklmnopqrstovwxyzABCDEFGHIJKLMNOPQRSTOVWXYZ0123456789";
        // 2.定义一个循环,循环n次,每次随机一个索引,提取索引位置出的字符连接起来即可
        // 3.定义一个字符串变量记录存储的字符
        String code = "";
        Random r = new Random();
        for(int i = 0;i< n;i++){
            // 4.随机一个索引
         int index = r.nextInt(datas.length());
         // 5.提取索引对应位置处的字符连接
            code += datas.charAt(index);
        }
        // 6.返回生成的随机验证码
        return code;
    }
}
package com.gch.d2_static_util;

public class Check {
    public static void main(String[] args) {
        // 开发一个验证码  直接通过类名来调用工具类里面的静态方法
        System.out.println(Util.createVerifyCode(4));
//        Util u = new Util(); 直接报错,Util工具类不能对外创建对象
    }
}
package com.gch.d2_static_util;

public class Login {
    public static void main(String[] args) {
        // 开发一个验证码   直接通过类名来调用工具类里面的静态方法
        System.out.println(Util.createVerifyCode(5));
    }
}

package com.gch.d2_static_util;

/**
    定义数组工具类
 */
public class ArrayUtils {
    /**
       构造器私有
     */
    private ArrayUtils(){
    }

    /**
     * 定义静态方法返回整型数组的内容
     * @param arr:传进来的整型数组
     * @return:返回传进来数组的内容
     */
    public static String toString(int[] arr){
        // 一些校验
        if(arr == null){
            return null;
        }
        String result = "[";
        for(int i = 0;i < arr.length;i++){
            result += i == arr.length - 1 ? arr[i] : arr[i] + ", ";
        }
        result += "]";
        return result;
    }

    /**
     * 定义静态方法返回浮点型数组的平均值
     * @param arr:传进来的数组
     * @return:返回浮点型数组去掉最高分和最低分后的平均值
     */
    public static double getAerage(double[] arr){
        // 1.定义三个变量记录当前数组的最大值,最小值,以及总分
        double arrMax = arr[0];
        double arrMin = arr[0];
        double sum = 0;
        for(int i = 0;i < arr.length;i++){
            if(arr[i] > arrMax){
                arrMax = arr[i];
            }
            if(arr[i] < arrMin){
                arrMin = arr[i];
            }
            sum += arr[i];
        }
        System.out.println("当前最高分是:" + arrMax);
        System.out.println("当前最低分是:" + arrMin);
        // 计算平均分
        double average = (sum - arrMax - arrMin) / (arr.length - 2);
        return average;
    }
}
package com.gch.d2_static_util;

/**
    定义测试类,调用该工具类的工具方法,并返回结果
 */
public class TestDemo {
    public static void main(String[] args) {
        // 静态初始化数组
        // 1.静态初始化一个整型数组
        int[] arr = {10,20,30,40};
        int[] arr2 = null;
        int[] arr3 = {};
        System.out.println(ArrayUtils.toString(arr));  // [10, 20, 30, 40]
        System.out.println(ArrayUtils.toString(arr2)); // null
        System.out.println(ArrayUtils.toString(arr3)); // []
        // 2.静态初始化一个浮点型数组
        double[] arr4 = {16.8,29.3,17.6,15.4};
        System.out.println("平均分是:" + ArrayUtils.getAerage(arr4));
                                                        //        当前最高分是:29.3
                                                        //        当前最低分是:15.4
                                                        //        平均分是:17.200000000000006

    }
}

三.static应用知识:代码块

静态代码块的作用是什么?

  • 如果要在启动系统时对静态资源进行初始化,则建议使用静态代码块完成数据的初始化操作。

package com.gch.d3_static_code;

import java.util.ArrayList;

// 先加载类,然后才把mian方法提取到栈内存里面去运行
public class StaticDemo1 {
    // 静态成员:与类一起加载,只在内存中存储1份
    public static String schoolName;
    public static ArrayList<String> cards = new ArrayList<>();
    /**
        静态代码块:由static修饰,属于类,与类一起优先加载一次,自动触发只执行一次
        作用:可以用于初始化静态资源。
     */
    static{
        System.out.println("-------静态代码块被触发执行了!------");
        schoolName = "西安电子科技大学";
        cards.add("3");
    }
    
    public static void main(String[] args) {
        // 目标:先理解静态代码块
        System.out.println("-----main方法执行!");
        System.out.println(schoolName);
    }
}

package com.gch.d3_static_code;

public class StaticDemo2 {
    // 实例成员
    private String name;
    // 无参构造器
    public StaticDemo2(){
        System.out.println("----无参构造器被触发执行----");
    }
    /**
        构造代码块/实例代码块:无static修饰,属于对象,每次创建对象时,都会触发执行一次,并且在构造器执行前执行
        构造代码块/实例代码块:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
        构造代码块/实例代码块:不属于类,不会与类一起加载
        构造代码块/实力代码块的作用:初始化实例资源
     */
    {
       // name = "张三";
        System.out.println("----实例代码块被触发执行----");
    }

    public static void main(String[] args) {
        // 目标:理解构造代码块(实例代码块),了解,见得少
        StaticDemo2 s1 = new StaticDemo2();
        System.out.println(s1.name);

        StaticDemo2 s2 = new StaticDemo2();
        System.out.println(s2.name);

    }
}

 

package com.gch.d3_static_code;

import java.util.ArrayList;

public class StaticTest3 {
    /**
        1.定义一个静态的集合,这样这个集合只加载一次,因为当前房间只需要一副牌
     */
    public static ArrayList<String> cards = new ArrayList<>();

    /**
        2.在程序真正执行main方法前,把54张牌放进去,后续游戏可以直接使用了
        静态代码块:初始化静态资源
     */
    static{
        // 3.正式做牌,放到集合里面去
        // a.定义一个数组存储全部点数:类型确定了,个数确定了
        // 静态初始化数组
        String[] sizes = {"3","4","5","6","7","8","10","J","Q","K","A","2"};
        // b.定义一个数组存储全部的花色,类型确定了,个数确定了
        String[] colors = {"♠","♥","♦","♣"};
        // c.遍历点数
        for(int i = 0;i < sizes.length;i++){
            // sizes[i]
            // d.遍历花色
            for(int j = 0;j < colors.length;j++){
                // colors[j]
                // 一张牌
                String card = sizes[i] + colors[j];
                cards.add(card);
            }
        }
        // e.单独加入大小王
        cards.add("大🃏");
        cards.add("小🃏");
    }
    public static void main(String[] args) {
        // 目标:模拟游戏启动前,初始化54张牌数据
        System.out.println("新牌:" + cards);
    }
}

 四.static应用知识:单例设计模式

 单例解决保证一个类对外只产生一个对象

 单例的实现方式很多

  • 饿汉单例模式:饿汉单例模式是在获取对象前,对象已经准备好了,所以饿汉单例模式天生就是线程安全的!
  • 懒汉单例模式:一开始不去创建对象,等到要用这个实例对象的时候,才判断它存不存在真正的实例对象,如果存在我们就返回,不存在才去创建一个对象,那么这样,就可能会有问题,在多个线程都判断它都没有实例对象的时候,可能多个线程会多次创建这个实例对象!
  • ....
  • ....

package com.gch.d4_static_singleinstance;

/**
 * 使用饿汉单例实现单例类
 */
public class SingleInstance {
    /**
     * 饿汉单例是在获取对象前,对象已经提前准备好了一个
     * 2.这个对象只能是一个,所以定义静态成员变量记住,因为静态成员变量在内存中只加载一次
     */
    public static final SingleInstance INSTANCE = new SingleInstance();

    /**
     * 1.必须把构造器私有化
     */
    private SingleInstance() {
        // 防止通过反射创建新的实例
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建~!");
        }
    }
}
package com.gch.d4_static_singleinstance;

public class Test1 {
    public static void main(String[] args) {
       // 目标:理解饿汉单例的设计步骤
        SingleInstance s1 = SingleInstance.instance;
        SingleInstance s2 = SingleInstance.instance;
        System.out.println(s1 == s2); // true,表明s1、s2是同一个对象
    }
}

package com.gch.d4_static_singleinstance;

/**
   懒汉单例
 */
public class SingleInstance2 {
    /**
        1.私有化构造器
     */
    private SingleInstance2() {
    }

    /**
        2.定义一个静态的成员变量负责存储一个对象。
        静态成员变量在内存中只加载一次,在内存中只存储一份
        注意:最好私有化,这样避免给别人挖坑
        定义静态成员变量暂时不存对象
     */
    private static SingleInstance2 instance; // null

    /**
        3.提供一个方法,对外返回单例对象
     */
    public static SingleInstance2 getInstance(){
        if(instance == null){
            // 第一次拿对象:此时需要创建对象
            instance = new SingleInstance2();
        }
        return instance;
    }
}
package com.gch.d4_static_singleinstance;

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握懒汉单例的设计,理解其思想
        SingleInstance2 s1 = SingleInstance2.getInstance();
        SingleInstance2 s2 = SingleInstance2.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2); // true
    }
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐