你知道么?static关键字有5种用法。


声明:本文转载自https://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);     } }

     运行后会报java.lang.NumberFormatException: multiple points或For input string: ""等错误,其实你会发现volatile修饰也没用,关于volatile关键字博主会在后续文章做介绍,上面代码运行报错的原因是多线程都去操作一个对象,(本图来自于:关于 SimpleDateFormat 的非线程安全问题及其解决方案):

                               

     解决办法:1.使用私有的对象。2.加锁。3.用ThreadLocal。

                             

     上图两图是使用私有对象和ThreadLocal解决高并发状态的图解。博主给出使用私有的对象和加锁两种实现代码:

public class DateSyncUtil {      private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");            public static String formatDate(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 parse(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);         }     }  }

     本文给出的两种方式都会对性能有不同程度的影响。推荐使用ThreadLocal,因为它的性能最好,读者可以尝试自己实现。

2.静态方法

      静态方法和非静态方法一样,都跟class 本身在一起存放在内存中,永远不会被回收,除非 JVM 退出,他们使用的区别的一个方面是非static方法需要实例调用,static方法直接用类名调用。

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

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

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

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

public class ExportExcelUtil{     @Autowired     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。当一个类实现了ApplicationContextAware接口之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。

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中普通代码块,构造代码块,静态代码块区别及代码示例

      【实例】因为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,此时这个内部类就变为了静态内部类。

     【实例】由于博主在工作中很少用到,但在某些情况下,少了静态内部类还真是不行,比如LinkedList使用了如下静态内部类:

             

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

      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)); 	System.out.println(bitCount(11));   }  }

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

      博主是个普通的程序猿,水平有限,文章难免有错误,欢迎牺牲自己宝贵时间的读者,就本文内容直抒己见,我的目的仅仅是希望对读者有所启发。

 

本文发表于2017年07月27日 18:36
(c)注:本文转载自https://my.oschina.net/liughDevelop/blog/1490005,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2639 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1