Static 关键字的 5 种用法,你会几种?
2020-04-29 16:10:32来源:博客园 阅读 ()
Static 关键字的 5 种用法,你会几种?
作者:我叫刘半仙
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 的非线程安全问题及其解决方案):
解决办法:
-
使用私有的对象。
-
加锁。推荐:Java 虚拟机对锁优化所做的努力
-
ThreadLocal。推荐:多线程并发神器--ThreadLocal
-
使用第三方的日期处理函数。
-
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));
? ? System.out.println(bitCount(11));
?}
}
或许你知道PI是Math类的方法,那bitCount是哪个类的方法呢?所以尽量避免使用static导入,实在要导入的话,去掉*号通配符,直接写成:java.lang.Integer.bitCount。
关注Java技术栈微信公众号,在后台回复关键字:Java,可以获取更多栈长整理的 Java 技术干货。
推荐去我的博客阅读更多:
1.Java JVM、集合、多线程、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架构、阿里巴巴等大厂最新面试题
觉得不错,别忘了点赞+转发哦!
原文链接:https://www.cnblogs.com/javastack/p/12803952.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 面试官:兄弟,说说Java的static关键字吧 2020-06-04
- 面对对象(2) 2020-05-28
- Lambda表达式用法大比较: Scala和Java 8 2020-05-26
- Java并发编程volatile关键字 2020-05-24
- 技术大佬:我去,你竟然还不会用 this 关键字 2020-05-24
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash