来源:my.oschina.net/liughDevelop/blog/1490005

说到static,静态变量和静态方法大家随口就来,因为他们在实际开发中应用很广泛,但他们真正在使用的时候会存在很多问题,而且它的使用不只那两种:

1.静态变量。

2.静态方法。

3.静态代码块。

4.静态内部类。

5.静态导入。

接下来我们看一下这些用法。

1.静态变量

静态变量属于类,内存中只有一个实例,当类被加载,就会为该静态变量分配内存空间,跟 class 本身在一起存放在方法区中永远不会被回收,除非 JVM 退出。(方法区还存哪些东西可以看看:Java虚拟机运行时数据区域)静态变量的使用方式:【类名.变量名】和【对象.变量名】。

【实例】实际开发中的日期格式化类SimpleDateFormat会经常用到,需要的时候会new一个对象出来直接使用,但我们知道频繁的创建对象不好,所以在DateUtil中直接创建一个静态的SimpleDateFormat全局变量,直接使用这个实例进行操作,因为内存共享,所以节省了性能。但是它在高并发情况下是存在线程安全问题的。SimpleDateFormat线程安全问题代码复现:

public class OuterStatic {  
  public static class InnerStaticSimpleDateFormat  implements Runnable {
         @Override
         public void run() {
             while(true) {
                 try {
                   Thread.sleep(3000);
                     System.out.println(Thread.currentThread().getName()
                                 +":"+DateUtil.parse("2017-07-27 08:02:20"));
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         }    
     }
     public static void main(String[] args) {
         for(int i = 0; i < 3; i++){
            new Thread(new InnerStaticSimpleDateFormat(), "测试线程").start();
           
         }
             
     }
}

class DateUtil {
   
   private static  volatile SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   
   public static  String formatFromDate(Date date)throws ParseException{
       return sdf.format(date);
   }
   public static Date parseToDate(String strDate) throws ParseException{

       return sdf.parse(strDate);
   }
}

虽然有volatile使对象可见,但运行后有一定几率会报java.lang.NumberFormatException: multiple points或For input string: ""等错误,原因是多线程都去操作一个对象(本图来自于:关于 SimpleDateFormat 的非线程安全问题及其解决方案):

解决办法:

  1. 使用私有的对象。

  2. 加锁。推荐:Java 虚拟机对锁优化所做的努力

  3. ThreadLocal。推荐:多线程并发神器--ThreadLocal

  4. 使用第三方的日期处理函数。

  5. 5.Java8推出了线程安全、简易、高可靠的时间包,里面有LocalDateTime年月日十分秒;LocalDate日期;LocalTime时间三个类可供使用。推荐:JDK8之新特性扩展篇

下图是使用私有对象和ThreadLocal解决高并发状态的图解。

本文给出使用私有的对象和加锁两种实现代码,ThreadLocal方式读者可以尝试自己实现

public class DateUtil {

   private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     
   public static String formatFromDate(Date date)throws ParseException{
     //方式一:让内存不共享,到用的时候再创建私有对象,使用时注释掉全局变量sdf
     //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     //return sdf.format(date);
     //方式二:加锁,使用时打开全局变量sdf的注释
       synchronized(sdf){
           return sdf.format(date);
       }  
   }
  public static Date parseToDate(String strDate) throws ParseException{
      //方式一:使用时注释掉全局变量sdf
      //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      //return sdf.parse(strDate);
      //方式二:加锁,使用时打开全局变量sdf的注释
       synchronized(sdf){
           return sdf.parse(strDate);
       }
   }
}


2.静态方法

静态方法和非静态方法一样,都跟class 本身在一起存放在内存中,永远不会被回收,除非 JVM 退出,他们使用的区别的一个方面是非static方法需要实例调用,static方法直接用类名调用。在Java技术栈微信公众号后台回复关键字:Java,可以获取更多栈长整理的 Java 技术干货。推荐:JDK8新特性之接口默认方法与静态方法

【实例一】单例模式,它提供了一种创建对象的最佳方式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

public class Singleton {
  private static volatile Singleton instance = null;  
   static {  //静态代码块,后面讲
      instance = new Singleton();  
   }  
   private Singleton (){}  
   public static Singleton getInstance() {  
       return instance;  
   }      
}

静态的方法不必实例化就能直接使用,用法方便,不用频繁的为对象开辟空间和对象被回收,节省系统资源。是不是相较之下觉得static用的比较爽呢?但是他也会带来一些问题:

【实例二】一般工具类中的方法都写成static的,比如我们要实现一个订单导出功能,代码如下:

public class ExportExcelUtil{
   @Autowired
   private static OrderService orderService ;

   public static void exportExcel(String id){
      //查询要导出的订单的数据
      Order order =orderService.getById(id);//这里的orderService对象会是null
     //...省略导出代码...
   }
}

为什么orderService会是null?原因不是Spring没注入,而是static方法给它"清空"了。解决方案一:@PostConstruct,它修饰的方法会在服务器加载Servlet时执行一次,代码如下:

@Component //这个注解必须加
public class ExportExcelUtil{
   @Autowired
   OrderService orderService ;

   private static ExportExcelUtil  exportExcelUtil;

   //注解@PostConstruct 这个其实就是类似声明了,当你加载一个类的构造函数之后执行的代码块,
   //也就是在加载了构造函数之后,就将service复制给一个静态的service。
    @PostConstruct  
    public void init() {      
       exportExcelUtil= this;
       exportExcelUtil.orderService = this.orderService ;
    }  

   public static void exportExcel(String id){
      //是不是很像经典main方法的调用模式呢?
      Order order =exportExcelUtil.orderService .getById(id);
      //...省略导出代码...
   }
}

每个工具类都要去加上@PostConstruct注解,代码重复性高。那我们可不可以直接从Spring容器中获取Bean实例?

解决方案二:ApplicationContextAware。通过它Spring容器会自动把上下文环境对象注入到ApplicationContextAware接口的实现类中setApplicationContext方法里。推荐:Spring Aware容器感知技术

换句话说,我们在ApplicationContextAware的实现类中,就可以通过这个上下文环境对象得到Spring容器中的Bean。

首先,在web项目中的web.xml中配置加载Spring容器的Listener:

<!-- 初始化Spring容器,让Spring容器随Web应用的启动而自动启动 -->  
<listener>  
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>

然后,实现ApplicationContextAware接口:

public class SpringContextBean implements ApplicationContextAware{  private static ApplicationContext context = null;  

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException  {
   context = applicationContext;
 }  

public static <T> T getBean(String name){    
   return (T)context.getBean(name);
 }  

public static <T> T getBean(Class<T> beanClass){
   return context.getBean(beanClass);
 }
}

最后,在Spring配置文件中注册该工具类:

<bean id="springContextBean" class="com.test.SpringContextBean"></bean>

原来的导出工具类代码可以简化到如下:

public class ExportExcelUtil{
   public static void exportExcel(String id){
     OrderService orderService = SpringContextBean.getBean(OrderService.class);
     Order order =orderService .getById(id);
      //...省略导出代码...
   }
}


3.静态代码块

我们其实在工作中一直用到的代码块,所谓代码块是指使用“{}”括起来的一段代码。其中静态代码块只执行一次,构造代码块在每次创建对象是都会执行。根据位置不同,代码块可以分为四种:普通代码块、构造块、静态代码块、同步代码块。ref:Java中普通代码块,构造代码块,静态代码块区别及代码示例。在Java技术栈微信公众号后台回复关键字:Java,可以获取更多栈长整理的 Java 技术干货。推荐:JDK8新特性之接口默认方法与静态方法

【实例】因为JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配。所以实际工作中我们可以使用静态代码块初始化一些不变的属性:

//final表示此map集合是不可变得
public  static  final  Map<String,String> spuKeysMap = new HashMap<String,String>();static{
  spuKeysMap.put("spuName","男装");
  spuKeysMap.put("spuCode","男装编码");
  spuKeysMap.put("spuBrand","品牌");
  spuKeysMap.put("owner","所有者");
}

但是静态代码块和静态变量初始化有什么关系?在上文的单例模式中,我们使用了静态代码块来创建对象,为何那那样写?我在网上看到了这样一段代码:

static {  
  _i = 10;  
}  

public static int _i = 20;  
     
public static void main(String[] args) {  
   System.out.println(_i);  
}

上面的结果是10还是20?如果存在多个代码块呢?

 static {  
      _i = 10;  
 }    public static int _i =30;  static {  
     _i = 20;  
 }  
 public static void main(String[] args) {  
     ystem.out.println(_i);
 }

测试过后你会发现两个答案结果都是20。

因为其实public static int _i = 10;  和如下代码:

public static int _i;  
static {  
   _i = 10;  
}

是没有区别的,他们在编译后的字节码完全一致(读者可以使用javap -c命令查看字节码文件),所以两个例子的结果就是最后一次赋值的数值。

4.静态内部类

在定义内部类的时候,可以在其前面加上一个权限修饰符static,此时这个内部类就变为了静态内部类。在Java技术栈微信公众号后台回复关键字:Java,可以获取更多栈长整理的 Java 技术干货。

【实例一】前文中写静态方法时的实例一,我们用了static块初始化单例对象,这样做有一个弊端,在调用单例其他方法时也会初始化对象,现在我们只希望在调用getInstance方法时初始化单例对象,要怎么改进呢?因为饿汗式写法性能不太好,所以最终单例模式优化到如下:

public class Singleton {
   //使用静态内部类初始化对象
   private static class  SingletonHolder{
     private static volatile Singleton instance = new Singleton();  
   }
   private Singleton (){}  
   public static Singleton getInstance() {  
       return SingletonHolder.instance;  
   }    
   public static  void otherMothed(){
     System.out.println("调用单例的其他方法时不会创建对象.")
   }  
   public static  void  main(String [] args){
       //Singleton.otherMothed();
        Singleton.getInstance();
   }
}

【实例二】博主在内部类的实际开发中应用不多,但有时候还真不能没有它,比如LinkedList使用了如下静态内部类:

其实在数据结构中我们把next和prev称为前后节点的指针,HashMap内部也使用了静态内部类Entry的数组存放数据。为了加深理解,读者可以亲自运行以下的代码来体会一下静态内部类。

private static String name = "北京";  //静态变量
     public static void main(String[] args) {
       new StaticInternal().outMethod();
     }  
     public static void outStaticMethod(String tempName) {      
         System.out.println("外部类的静态方法 name:"+tempName);  
     }
     public void outMethod() {             // 外部类访问静态内部类的静态成员:内部类.静态成员  
       System.out.println("外部类的非静态方法调用");
         StaticInternal.InnerStaticClass inner = new   StaticInternal.InnerStaticClass();// 实例化静态内部类对象  
       inner.setInnerName("呼呼");// 访问静态内部类的非静态方法  
       InnerStaticClass.innerStaticMethod(); // 访问静态内部类的静态方法  
       System.out.println("外部类访问静态内部类的非静态方法 name:"+inner.getInnerName());
     }  
     static class InnerStaticClass {            
       String  InnerName="西安";  
         static void innerStaticMethod() {    // 静态内部类的静态方法  
             System.out.println("静态内部类访问外部类的静态变量: name = " + name);  
             outStaticMethod(new InnerStaticClass().InnerName);     // 访问外部类的静态方法  
         }  
         // 静态内部类的非静态方法  
         public void setInnerName(String name) {  
             System.out.println("静态内部类的非静态方法");  
             this.InnerName = name;  
         }  
         public String getInnerName() {  
           System.out.println("静态内部类的非静态get方法 name="+name);
             return this.InnerName;  
         }
     }

实际中的应用可以看看:SpringMvc 静态内部类 封装请求数据,在这里我们来总结一下静态内部类:

1.加强代码可读性。如:StaticInternal.InnerStaticClass inner = new   StaticInternal.InnerStaticClass();

2.多个外部类的对象可以共享同一个静态内部类的对象。

3.静态内部类无需依赖于外部类,它可以独立于外部对象而存在。因为静态类和方法只属于类本身,并不属于该类的对象,更不属于其他外部类的对象。

5.静态导入

静态导入是JKD1.5后新加的功能,一般不怎么常用,了解即可。有时候面试答出来这个会让别的觉得你热爱技术。

【实例】 回想一下,我们以前是不是这样写获取随机数:

public static void main(String[] args) {  double random = Math.random();
 System.out.println(Math.PI);
 System.out.println(Math.round(random));
}

Math出现的次数太多了,可以简化吗?现在我们可以直接使用静态导入来写,如下

import static java.lang.Math.*;public class StaticInternal {  
 public static void main(String[] args) {  double random = random();
    System.out.println(PI);
    System.out.println(round(random));
 }
}

是不是方便了许多?但别着急偷懒,因为使用它过多会导致代码可读性差:

import static java.lang.Math.*;import static java.lang.Integer.*;public class StaticInternal {
 
 public static void main(String[] args) {  double random = random();
    System.out.println(PI);
    System.out.println(round(random));
    Syste‍m.out.println(bitCount(11));
 }
}

或许你知道PI是Math类的方法,那bitCount是哪个类的方法呢?所以尽量避免使用static导入,实在要导入的话,去掉*号通配符,直接写成:java.lang.Integer.bitCount。

关注Java技术栈微信公众号,在后台回复关键字:Java,可以获取更多栈长整理的 Java 技术干货。

最近干货分享

为什么面试你要25K,HR只给你20K?

Spring 系列干货,2019最新整理版!

你真的搞懂 transient  关键字了吗?

推荐一个技术圈子,30k的offer靠它了!

分享一份Java架构师学习资料!

点击「阅读原文」和栈长学更多…

Static 关键字的 5 种用法,你会几种?的更多相关文章

  1. 子查询。ANY三种用法。ALL两种用法。HAVING中使用子查询。SELECT中使用子查询。

    子查询存在的意义是解决多表查询带来的性能问题. 子查询返回单行多列: ANY三种用法: ALL两种用法: HAVING中的子查询返回单行单列: SELECT中使用子查询:(了解就好,避免使用这种方法! ...

  2. Java中static关键字的作用和用法详细介绍

    static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念. 被static修饰的成员变量和成员方法独立于该类的任何 ...

  3. NEW关键字的三种用法

    最近面试中有一道题是写new关键字的几种用法,想了下写下我知道的两种用法 第一种 创建对象.调用构造函数,这就不用讲了 ClassA  A=new ClassA(); 第二种 是作为修饰符,显示隐藏继 ...

  4. C/C++中static关键字的用法

    1.什么是static? static 是C/C++中很常用的修饰符,它被用来控制变量的存储方式和可见性. 1.1static的引入 我们知道在函数内部定义的变量,当程序执行到它的定义处时,编译器为它 ...

  5. static关键字作用总结

    之前讲到final关键字的作用是每次面试的时候我必问求职者的两个问题之一,另外一个问题就是文本会写到的static.final和static一样,都是一个小问题可以看到一个人的基础是否扎实以及平时是否 ...

  6. static关键字(二)作用总结

    静态变量和静态方法 static关键字最基本的用法是: 1.被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来 2.被static修饰的方法属于类方法,可以通过 ...

  7. static关键字作用

    之前讲到final关键字的作用是每次面试的时候我必问求职者的两个问题之一,另外一个问题就是文本会写到的static.final和static一样,都是一个小问题可以看到一个人的基础是否扎实以及平时是否 ...

  8. 关于static 关键字的总结

    转发自:https://www.cnblogs.com/xrq730/p/4820992.html 前言 之前讲到final关键字的作用是每次面试的时候我必问求职者的两个问题之一,另外一个问题就是文本 ...

  9. Java:Java中static关键字作用

    static关键字最基本的用法是: 1.被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来 2.被static修饰的方法属于类方法,可以通过类名.方法名直接引用 ...

  10. 【转载】java static 关键字的四种用法

    原文链接点这里,感谢博主分享 在java的关键字中,static和final是两个我们必须掌握的关键字.不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构 ...

随机推荐

  1. leetcode-52-N皇后②

    题目描述: 方法一:回溯 class Solution: def totalNQueens(self, n: int) -> int: def backtrack(i,tmp,col,z_dia ...

  2. mui框架页面每次加载操作

    最近在优化自己用mui开发的app,主要还是针对交互这块儿,这里简单给大家说一下问题点场景,就是我是通过动态添加底部tabBar的方法创建了一个底部可以切换的操作区域,代码如下: mui.init() ...

  3. 拦截器一Interceptor

    import org.springframework.web.servlet.HandlerInterceptor; 前言 拦截器,在AOP(Aspect-Oriented Programming)中 ...

  4. delphi 文件存取方法与文件管理组件

    9.2  文件存取方法与文件管理组件 9.2.1  存取文件的类方法 Delphi在许多需要与文件打交道的类中定义了文件存取方法,使用这些方法可以非常方便地将类中的数据保存到文件中,或从文件中读取所需 ...

  5. Delphi中关于菜单的几个技巧

    -- 1将菜单项移到菜单栏的最右边 在一些应用程序中,常把一些特殊的菜单项放在菜单栏的最右边(如WPS2000 中的"定制界面"菜单,一些应用程序的帮助菜单),这些菜单项放在菜单栏 ...

  6. How to Add Swap on CentOS

    About Linux Swapping Linux RAM is composed of chunks of memory called pages. To free up pages of RAM ...

  7. CodeForces-1234C-Pipes-dfs

    You are given a system of pipes. It consists of two rows, each row consists of nn pipes. The top lef ...

  8. 解决jqGrid中,当前页一直显示为0的问题

    项目中,经常会见到使用 jqGrid 进行一些数据的列表展示,而且使用起来也比较方便.但是有时会遇到一些奇怪的问题,比如前几天我就遇到了在使用 jqGrid 时,当前页一直显示为 0 的问题.下图就是 ...

  9. java could not open `C|D|E|F:\jre\lib\amd64\jvm.cfg' 解决方案与原因

    因为安装了 jdk 后发现有多个 jre 一个是安装目录下的. 还有一个是安装后的自动安装的注意路径都不一样. 由于本人有强迫症所有不能容忍有两个 jre 目录的存在,所以果断删除了 D 盘下的.谨慎 ...

  10. DIV+CSS网页布局常用的一些基础知识

    CSS命名规范 一.文件命名规范 全局样式:global.css:框架布局:layout.css:字体样式:font.css:链接样式:link.css:打印样式:print.css: 二.常用类/I ...