static静态关键字详解(工具类、代码块、单例模式)
静态成员变量(类变量)静态方法(类方法)。可以被类的所有对象共享(访问、修改)
·
一.static静态关键字
1.static是什么,static修饰成员变量的用法
- static是静态的意思,可以用来修饰成员变量、成员方法。
- static修饰成员变量之后称为静态成员变量(类变量),修饰方法之后称为静态方法(类方法)。
- static修饰后的成员变量,可以被类的所有对象共享(访问、修改)。
- 静态成员变量(有static修饰,属于类,加载一次,内存中只有一份),访问格式:
- 类名.静态成员变量(推荐)
- 对象.静态成员变量(不推荐)
- 实例成员变量(无static修饰,属于对象,每个对象中都存储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修饰,归属于对象):只能用对象触发访问。
- 同一个类中,访问静态成员方法,类名可以省略不写。
- 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
- 如果该方法是以执行一个共用 / 通用功能为目的,则可以申明成静态方法。
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
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
已为社区贡献4条内容
所有评论(0)