前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第20次文章:内部类+单例设计模式

第20次文章:内部类+单例设计模式

作者头像
鹏-程-万-里
发布2019-09-28 18:05:05
4440
发布2019-09-28 18:05:05
举报

本周首先紧接上周没有写完的内部类,详情点击《第19次文章:类加载器的加密解密+内部类》,再加单例模式的实现。

一、另外两种内部类的基本用法

(3)方法类基本用法:

-方法内部类的地位和方法内的局部变量 的地位类似

因此不能修饰局部变量的修饰符也不能修饰局部内部类,譬如public、private、protected、static、transient等。

-方法内部类只能在声明的方法内是可见的

因此定义局部内部类之后,想用的话就要在此方法内直接实例化,记住这里顺序不能反了,一定是要先声明后使用,否则编译器就会找不到。

-方法内部类不能访问定义它的方法内的局部变量,除非这个变量被定义为final

本质原因:局部变量和内部类生命周期不一致所致!

-方法内部类只能包含非静态成员!

(4)匿名内部类基本用法:

-匿名内部类的实例化方式:new SomeInterfaceOrClass(){.........};

意思是创造了一个实现(继承)了SomeInterfaceOrClass的类的对象;

-根据声明的位置,判断匿名内部类是成员内部类还是方法内部类。注:一般是方法内部类,这就具备方法内部类的特性。

-三种使用方法:

(1)接口式

(2)继承式

(3)参数式

对于匿名类,我们利用一段实例进行详解:

代码语言:javascript
复制
/** * 测试匿名内部类的几种方式 */public class Demo05 {    public static void main(String[] args) {    Outer05 o5 = new Outer05();    o5.test();  }}
class Outer05{  //参数式匿名内部类  public void test02(Car car) {    car.run();  }    public void test() {    //匿名内部类(接口式),由于本内部类定义在方法中,也是方法内部类    Runnable runnable = new Runnable() {      @Override      public void run() {              }    };          //匿名内部类,继承式    Car car = new Car() {      @Override      public void run() {        System.out.println("继承式子类的汽车跑");      }    };    car.run();        //利用参数式    test02(new Car() {      @Override      public void run() {        System.out.println("参数式匿名内部类,车在跑");      }    });  }}
class Car{  public void run () {    System.out.println("汽车跑");  }}

查看一下结果:

tips:

(1)在这段代码中,我们分别介绍了匿名内部类的三种模式,对于第一种接口式,我们使用Runnable进行创建,然后重新定义其中的run方法,可以在里面增添我们需要的相关操作。

(2)对于继承式,我们首先需要创建一个父类Car,然后我们再来利用继承关系,创建一个匿名的继承式匿名内部类,在使用继承式匿名类的时候,我们同样可以更改父类的原有方法。

(3)利用参数式的匿名内部类,需要根据方法的要求,在传递参数的时候,直接创建一个匿名类,并且可以根据我们自己的需求修改其原有方法。

(4)匿名内部类的最大特点就是,我们创建的对象没有名称,这也就代表着我们对于此类只能在其创建的时候使用一次。使用结束之后,我们就无法再次利用匿名类了。在实际情况中,也的确有很多对象我们只会使用一次。此时使用匿名类就是一个很好的选择。

二、设计模式GOF23

1、创建型模式

-单列模式,工厂模式,抽象工厂模式,建造者模式,原型模式

2、结构型模式

-适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式

3、行为型模式

-模板方法模式,命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

三、单例模式

1、核心作用:

保证一个类只有一个实例,并且提供一个访问实例的全局访问点。比如说我们在打开Windows下的资源管理器的时候,无论我们打开多少次,每次打开的对象都会指向同一个资源管理器,但是QQ就不一样了啊,如果你不断的点击QQ的快捷方式,它会不断的产生新的QQ登录界面,这就不属于单例模式。

2、优点:

-由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置文件、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

-单例模式可以在系统设计全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

3、常见的5中单例模式实现方式:

(1)主要:

-饿汉式(线程安全,调用效率高。但是,不能延时加载。)

-懒汉式(线程安全,调用效率不高。但是,可以延时加载。)

(2)其他:

-双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)

-静态内部类式(线程安全,调用效率高。但是,可以延时加载)

-枚举单例(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞)

4、单例模式的实现与检测

我们对5种单例模式都进行了相应的实现。下面我们主要对饿汉式和懒汉式进行一个详解。

实现的主要思想:由于是属于单例模式,每次创建或者打开得到的是同一个对象,所以我们首先需要将构造器私有化,不能让外部的用户随意的创建对象。而每次创建对象的时候,我们就需要去查看是否已经有这个类的对象存在了,如果存在的话,就不能再创建新对象了,而是将已经创建好的对象直接返回给用户。

(1)首先看一下饿汉式:

代码语言:javascript
复制
package com.peng.singleton;/** * 测试饿汉式单例模式 *一旦此类开始初始化,就已经new出一个对象了,但是此对象后续可能没有使用到。 */public class SingletonDemo01 {
  //类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!  private static SingletonDemo01 singletonDemo01 = new SingletonDemo01();     //私有构造器  private SingletonDemo01() {  }  //方法没有同步,调用效率高!  public static SingletonDemo01 getInstance() {    return singletonDemo01;  }}

(2)再看一下懒汉式:

代码语言:javascript
复制
package com.peng.singleton;
/** * 测试懒汉式单例模式 */
public class SingletonDemo02 {    //类初始化的时候,不初始化这个对象(延迟加载,真正使用的时候再创建)  private static SingletonDemo02 instance;    //构造器私有化  private SingletonDemo02() {      }    //方法同步,调用效率低  public static synchronized SingletonDemo02 getInstance() {    if (instance == null) {      instance = new SingletonDemo02();    }    return instance;  }}

(3)我们检测一下最后的效果:

代码语言:javascript
复制
package com.peng.singleton;
/** * 检测单例模式 */public class Client {  public static void main(String[] args) {    SingletonDemo01 s1 = SingletonDemo01.getInstance();    SingletonDemo01 s2 = SingletonDemo01.getInstance();    System.out.println(s1);    System.out.println(s2);        SingletonDemo02 s3 = SingletonDemo02.getInstance();    SingletonDemo02 s4 = SingletonDemo02.getInstance();      System.out.println(s3 == s4);  }}

结果如下所示:

tips:

1.我们分别创建两个饿汉式和懒汉式的单例模式,然后对比每种单例模式最后产生的对象是否相同,由最后的结果可以看出,饿汉式的两个对象s1和s2是相同的,懒汉式的两个对象s3和s4也是相同的。

2.在SingletonDemo01中,我们在类的设计过程时,为了避免外界对构造器的调用,我们首先就将构造器私有化,然后创建一个静态对象singletonDemo01,并且将其初始化一个新对象,最后给外界留一个公开的获取该对象的方法getInstance(),直接将最开始初始化的对象返回给用户,这样就保证了用户无法自己创建该对象,只能获取该对象,而获取到的对象一直都是同一个对象。同时我们也可以看到,在加载此类的时候,局部变量singletonDemo01直接被初始化了,所以这种模式不具有延时加载的效果,被称为饿汉式单例模式。

3.在SingletonDemo02中,思想与SingletonDemo01相似,但是我们注意到两者之间的区别在于,SingletonDemo01中,一旦加载该类,静态对象singletonDemo01就会被立即加载,而在SingletonDemo02中,静态对象instance只有在我们实际调用的时候才会被加载,这就属于一个延时加载,极大的节约了资源。但是在调用的时候需要考虑多线程的问题,避免重复加载,造成单例模式的失效。所以我们在方法getInstance的前面需要加上synchronized进行方法同步。这样也就降低了SingletonDemo02的效率。由于SingletonDemo02只有在需要的时候才去初始化对象,所以我们称SingletonDemo02为懒汉式。

5、问题:

在java中拥有一种动态机制,反射和序列化,这种动态机制(详情见:第15次文章:反射+动态编译+脚本引擎)可以破解上面几种(不包含枚举式)单例实现方式。所以针对这种安全漏洞,我们需要设计相应的处理方法来进行解决。我们新建一个懒汉式类SingletonDemo06来进行说明。

(1)对于反射

在反射机制中,主要是通过获取类的一个class对象,然后通过这个class对象,调用类对象的构造器,创建相应的类。具体如下所示:

代码语言:javascript
复制
/** * 测试反射和反序列化破解单例模式 */public class Client2 {  public static void main(String[] args) throws Exception {    SingletonDemo06 s1 = SingletonDemo06.getInstance();    SingletonDemo06 s2 = SingletonDemo06.getInstance();        System.out.println(s1);    System.out.println(s2);        //通过反射的方式直接调用私有构造器    Class<SingletonDemo06> clazz =  (Class<SingletonDemo06>) Class.forName("com.peng.singleton.SingletonDemo06");        Constructor<SingletonDemo06> c = clazz.getDeclaredConstructor(null);    c.setAccessible(true);    SingletonDemo06 s3 = c.newInstance(null);    SingletonDemo06 s4 = c.newInstance(null);        System.out.println(s3);    System.out.println(s4);  }}

查看一下结果:

tips:

如图所示,我们通过反射机制,生成了两个不同的对象s3和s4。这就属于一个单例的漏洞。针对这个问题的关键还是在于懒汉式的构造器。我们的解决方案就可以在构造器中手动抛出异常,针对这个解决方案,我们将SingletonDemo06中的构造器改为:

代码语言:javascript
复制
private SingletonDemo06() {    //破解反射构造多个对象    if(instance != null) {      throw new  RuntimeException();    }  }

改造之后,我们再来查看一下结果:

tips:

当我们再利用反射机制时,一旦检测到静态变量instance不是空的时候,就会抛出异常,阻止其继续创建新对象。

(2)对于序列化

序列化的原理在于将对象转换为字节数组进行存储以及获取。我们首先对其进行测试

代码语言:javascript
复制
/** * 测试反射和反序列化破解单例模式 */public class Client2 {  public static void main(String[] args) throws Exception {    SingletonDemo06 s1 = SingletonDemo06.getInstance();    SingletonDemo06 s2 = SingletonDemo06.getInstance();        System.out.println(s1);    System.out.println(s2);        //通过反序列化的方式构造多个对象    FileOutputStream fos = new FileOutputStream("G:/java学习/test/a.txt");    ObjectOutputStream oos = new ObjectOutputStream(fos);    oos.writeObject(s1);    oos.close();    fos.close();        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("G:/java学习/test/a.txt"));    SingletonDemo06 s5 = (SingletonDemo06) ois.readObject();    System.out.println(s5);
  }}

查看一下结果:

tips:

如图所示,对于同一个对象s1,经过序列化的处理之后,获取得到的对象成为了一个新对象。所以此处也破解了单例模式的实现。针对序列化的本质核心,我们在SingletonDemo06中过定义readResolve()防止获得不同对象。反序列化时,如果对象所在类定义了readResolve()(实际是一种回调),定义返回哪个对象。在SingletonDemo06中定义的readResolve()方法如下所示:

代码语言:javascript
复制
//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!  private Object readResolve() throws ObjectStreamException{    return instance;  }

修改之后,我们再来查看最后的结果:

6、如何选用

在上面的讲解中,我们主要讲解了饿汉式和懒汉式的细节,其余的三种由于和两种主要的单例模式比较类似,枚举式较为特殊,下面直接给出5种模式的对比,便于我们使用时候的选择:

-单例对象占用资源少,不需要延时加载:枚举式好于饿汉式

-单例对象占用资源大,需要延时加载:静态内部类式好于懒汉式

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java小白成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、另外两种内部类的基本用法
    • (3)方法类基本用法:
      • (4)匿名内部类基本用法:
      • 二、设计模式GOF23
        • 1、创建型模式
          • 2、结构型模式
            • 3、行为型模式
            • 三、单例模式
              • 1、核心作用:
                • 2、优点:
                  • 3、常见的5中单例模式实现方式:
                    • 4、单例模式的实现与检测
                      • 5、问题:
                        • (1)对于反射
                        • (2)对于序列化
                      • 6、如何选用
                      相关产品与服务
                      文件存储
                      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档