加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

单例和静态类的区别

(2015-06-25 18:39:54)
标签:

静态类

单例模式

设计模式

工具类

面向对象

分类: 架构

CSDN 正在进行的一个关于《静态方法和单件模式具体区别在哪里》的讨论很不错,特整理一些观点如下:

问题:
单件模式的类在运行时只有一个实例,也就是说其他对象都使用这个实例进行操作,那为什么不把这个类的所有方法都设计为静态的呢,那样岂不是更简单。


Sunmast 的回复:
.NET可以使用静态方法作为Singleton模式的实现,但是如果这么设计那么这个class就失去了面向对象的所有特性,有时候我们还是需要让这个class继承其他class,或者让其他class继承,等等类似问题

acewang 的回复:

收藏过阎宏博士在Java版的一段讲解,贴出来分享
------------------------------------------------------------
如果所有的方法都是静态的,那就根本不需要任何实例。有人把这种类叫做工具类,这种做法叫做工具类模式

工具类模式基本上不需要面向对象的概念。

当然,我不是说面向对象的技术就一定比面向过程的好很多,但是如果你是在使用Java这样的面向对象的语言,设计一个面向对象的系统,那么一个单纯 的想法就是尽量靠近面向对象的概念。我没有见过一个使用Java的人反其道而行之,努力使用面向过程的概念。如果你是面向过程概念的拥护者,不妨选择一个 像Fortran这样的结构化编程语言。

在大多数的情况下,工具类中的方法都描述一些实体,应该把这些方法移动到描述这些实体的类中去。如果没有这些类,那就不妨考虑创建它们。

我曾经见到过一个很大的工具类,都是对String的操作。经过辨认,发现这些操作实际上都是为了组合URL准备的,并不是一般性地针对任何的String。为什么不把它们放到一个CustomizedURL类中去呢?

当你这样做了之后,会发现你并不需要一个很大的工具类,或者根本就可以取消它。

单例模式并不是一个取代工具类模式的好候选人。单例模式的情况在多JVM的J2EE服务器环境中变得很复杂。单例对象一旦创建出来就不会被湮灭,浪费内存。
----------------------------------------------------------------------------------------------

观点一:(单例 
单例模式比静态方法有很多优势:
首先,单例可以继承类,实现接口,而静态类不能(可以集成类,但不能集成实例成员);
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化;
再次,单例类可以被集成,他的方法可以被覆写;
最后,或许最重要的是,单例类可以被用于多态而无需强迫用户只假定唯一的实例。举个例子,你可能在开始时只写一个配置,但是以后你可能需要支持超过一个配 置集,或者可能需要允许用户从外部从外部文件中加载一个配置对象,或者编写自己的。你的代码不需要关注全局的状态,因此你的代码会更加灵活。

观点二:(静态方法 ) 静态方法中产生的对象,会随着静态方法执行完毕而释放掉,而且执行类中的静态方法时,不会实例化静态方法所在的类。如果是用singleton,   产生的那一个唯一的实例,会一直在内存中,不会被GC清除的(原因是静态的属性变量不会被GC清除),除非整个JVM退出了。这个问题我之前也想几天,并 且自己写代码来做了个实验。

观点三:(Good! 
由于DAO的初始化,会比较占系统资源的,如果用静态方法来取,会不断地初始化和释放,所以我个人认为如果不存在比较复杂的事务管理,用 singleton会比较好。个人意见,欢迎各位高手指正。  
--------------------------------

单例模式还是静态类,这是一个老话题了,从我刚开始接触Java的时候就看到这样的讨论。在这里我总结一下,也添加一点点新东西。

 

首先要澄清和区别一些概念,“静态类”和“所有方法皆为静态方法的类”。

 

严格说来,Java中的静态类,指的是“static class”这样修饰的类定义,语法上的要求,使得这样的类一定是内部类,换言之,“静态内部类”是对它的完整定义。静态内部类最大的好处在于可以隐藏自己(只让自己被所在外层的类用到),同时又可以访问到所在外层类的属性。和“非静态”的内部类相比,它可以放置一些静态的成员变量和方法定义,而非静态类不可以;而且,静态内部类不能访问外层类非静态的属性。

 

但是,通常我们所说的“静态类”,也是下文所述的“静态类”,是指所有的方法、属性都是静态的类,同时,我们在使用它们的时候,直接调用它们的静态方法、访问其中的静态属性,而不需要对其实例化。这类所谓的“静态类”往往具备这样两个特点,一个是使用final修饰,它们往往没有子类其二是构造器都被私有化了,不允许被构造实例。

 

1、单例模式便于mock,可测性好。虽说静态方法也可以mock (比如需要使用一些特殊的注解),但是毕竟相对还是麻烦一些,也没有那么灵活。

 

2、有人说单例模式可以做到lazy load,但是静态类不行。这肯定是扯淡,静态类也完全可以做到第一次使用的时候再加载。不过,其中值得一提的是单例中的Double Check Lock,这里做一个简单介绍。看下面的代码:

 

  1. public Singleton  
  2.     private static Singleton instance;  
  3.     private Number new Number();  
  4.     public Number get()  
  5.         return this.n; //(1)  
  6.      
  7.     public static Singleton getInstance()  
  8.         if(instance == null 
  9.             synchronized(Singleton.class 
  10.                 if(instance == null 
  11.                     instance new Singleton();  
  12.             //(2)  
  13.          
  14.         return instance;  
  15.      
  16.  

 

这是很常见的一种写法,不过,由于编译器的优化,允许出现主存和线程工作内存数据不一致问题,这就是“DCL失效”的问题。上面的代码是其典型表现:

根据JMM规范,主存数据和工作内存的数据是允许存在不一致的。JDK1.2之后,分配空间、对象初始化等等操作才都放到工作内存中进行了。由于synchronized关键字的关系,执行到语句(2)的时候,走出同步块时,JVM会将主存和工作内存的instance引用的对象刷新到一致,即instance是“可见”的。但问题出在上面的(1),没有synchronized,也没有volatile、final,没有人来保证调用get方法时获得的n是正确的值,即这个n未必是“可见”的。如果n比instance晚同步到主存,就存在一个时间间隙,这个间隙内获取到的instance是一个不健康的instance,其中的this.n是取不到正确的Number对象的。

 

在JSR133中,对JMM做了一个修正,后引入或增强了synchronized、volatile和final关键字,通过它们的运用,上述问题能够得到解决。另外,还有一种解决的方法可以是使用静态的内部类:

 

  1. private static class InnerInstance  
  2.    public static Instance instance new Instance();  
  3.  
  4. public static Instance getInstance()  
  5.    return InnerInstance.instance;  
  6.  

 

3、单例可以被继承,这是一个很大的好处,这便于用户overwrite其中的某方法,当然,继承单例的场景较少见;而静态类一般不被继承。关于单例的继承细节,这里暂不讨论,有几种办法,有兴趣的同学可以自行阅读JDK的Calendar类。

 

4、单例可以实现自某接口,可以继承自某类。静态类也可以继承自某类,但是就没法使用父类里面的protect成员了。推广来说,这一点和上一点都可以看做是面向对象带来的好处:封装、继承和多态,静态类不能很好地具备其中的后两点。

 

5、单例可以比较方便地扩展为有限实例。根据需要,我可以通过工厂,生产出两个内部状态不同的单例对象——这在静态类中是难以做到的。Spring可以看做一系列大工厂,但其中的bean也只有singleton和prototype两种,生产不出static的新类型;当你的工具成为了对象,就能够保持良好的扩展性。

还有一个有趣的例子是JDK的Calendar.getInstance()方法,从方法看很像是获得一个单例,其实不是,每次都去创建了新的Calendar对象;同时,使用abstract修饰它自己,保证了无法使用new实例化,又开放了getInstance这样一个接口来获取默认实现,而获取的默认实现,又恰恰是Calendar的子类。这种形式可以看做是单例的一个变体。

 

6、有人说,单例在使用过程中申请的资源可以被及时释放并回收内存,但是静态类不行。这也是没有道理的,别忘了静态类也是可以存放状态的,在确定不再使用资源后,及时将资源的引用置为null就可以了。

 

7、如果希望在类加载的时候做复杂的操作,那么在静态类中,需要引入static块来初始化数据,如果期间抛出了异常,就可能发生一个“ClassDefNotFoundError”的诡异错误,这对问题定位是不利的。

 

文章系本人原创,转载请注明出处和作者

 

3 
0 
评论
8 楼 gaoke_71 2014-07-28  
多数不大明白
7 楼 yanqingluo 2014-01-25  
补充一下刚才我说的,单例类不应该再被继承,但是单例类是可以有父类。
6 楼 yanqingluo 2014-01-25  
我觉得calender类不是单例,它每次getInstance都NEW 了个新对象。

我分析了一下,是因为他NEW具体对象的构造方法并不是PRIVATE的。

个人意见:单例是不应该被继承的,因为,他的构造函数如果是私有的,无法被其他人调用,继承它没有意义。

5 楼 yanqingluo 2014-01-25  
好文章。转走了。
4 楼 RayChase 2012-04-07  
该用户名已经存在 写道
以前都没有思考过单例和静态类的问题,知道前不久看到一个帖子回复里一哥们问:有了静态类,我们为什么还要用单例?
贴子回复也众说纷纭,但是居然没有一个我觉得满意的答案。我也傻乎乎的疑惑在这个问题上。
不过通过一直一来的思考和火哥的文章,我可以用一句最简单的话解释了:
因为Java是面向对象的编程,因为要面向接口,因为要多态,而静态类完全违背了这些,静态类是纯面向过程的方式(其实就相当于库函数),适当的写点工具类还可以,要想游刃有余的面向对象编程,恐怕静态类就成为绊脚石了。

二者具体的不同包括我文中提到的那些,而不只是你说的这一点。
事实上,即便是单例模型,也不是纯粹意义上的面向对象;不过另一方面,也不应当过分追求面向对象的设计。终究要选择合适的工具和手段。
3 楼 该用户名已经存在 2012-04-06  
以前都没有思考过单例和静态类的问题,知道前不久看到一个帖子回复里一哥们问:有了静态类,我们为什么还要用单例? 
贴子回复也众说纷纭,但是居然没有一个我觉得满意的答案。我也傻乎乎的疑惑在这个问题上。 
不过通过一直一来的思考和火哥的文章,我可以用一句最简单的话解释了: 
因为Java是面向对象的编程,因为要面向接口,因为要多态,而静态类完全违背了这些,静态类是纯面向过程的方式(其实就相当于库函数),适当的写点工具类还可以,要想游刃有余的面向对象编程,恐怕静态类就成为绊脚石了。
2 楼 RayChase 2012-03-31  
啸笑天 写道
枚举实现单例
public enum Singleton { 
    INSTANCE { 
        public void doSomeMethod() { 
            // . . . 
       
    }; 
    protected abstract void doSomeMethod(); 
}

Good! http://raychase.iteye.com/images/smiles/icon_lol.gif
1 楼 啸笑天 2012-03-31  
枚举实现单例
public enum Singleton { 
    INSTANCE { 
        public void doSomeMethod() { 
            // . . . 
       
    }; 
    protected abstract void doSomeMethod(); 
}

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有