XML与设计模式

XML与设计模式

一、XML

1.1 概述

什么是XML?

  • XML是一种可扩展标记语言(eXtensible Markup Language)的缩写,它是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。
  • 特点
    • 一是纯文本,默认使用UTF-8编码,二是可嵌套。
    • 如果把XML内容存为文件,那么他就是一个XML文件。
  • 作用
    • XML内容经常被当成消息进行网络传输
    • 作为配置文件用于存储系统的信息。

在这里插入图片描述

1.2 XML的创建、语法规则

XML就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml

语法规则

  • XML文件的后缀名为:xml
  • 文档声明必须是第一行
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 注释内容
    version:XML默认的版本号码、该属性必须存在
    encoding:本XML文件的编码
 -->

XML的标签(元素)规则

  • 标签是由一对尖括号和合法标识符组成:,必须存在一个根标签,有且只能有一个。
  • 标签必须成对出现,有开始,有结束:
  • 特殊的标签不可以成对,但是必须有结束标记,如:
  • 标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来****
  • 标签需要正确的嵌套

在这里插入图片描述

XML的其他组成

  • XML文件中可以注释信息:

  • XML文件中可以存在以下特殊字符

在这里插入图片描述

  • XML文件中可以存在CDATA区:<![CDATA[...内容...]]>

1.3 XML文档约束

为什么需要文档约束?

  • 由于XML文件可以自定义标签,导致XML文件可以随意定义,程序在解析的时候可以出现问题。
  • 所以使用文档约束来限定XML文件中的标签以及属性怎么写,以此强制约束程序员必须按照文档约束的规定来编写XML文件
1.3.1 DTD约束[了解]

XML文档约束的作用:

  • 约束XML文件的编写
  • 不可以约束具体的数据类型

需求:利用DTD文档约束,约束一个XML文件的编写。

分析:

  • 编写DTD约束文档,后缀必须是.dtd

在这里插入图片描述

  • 在需要编写的XML文件中导入该DTD约束文档

  • 按照约束的规定编写XML文件的内容

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 导入dtd编写的约束 -->
<!DOCTYPE 书架 SYSTEM "data.dtd">
<书架>
    <>
        <书名>精通JavaSE加强</书名>
        <作者>dlei</作者>
        <!-- 由此可知dtd无法对数据类型约束 -->
        <售价>很贵</售价>
    </>
    <>
        <书名></书名>
        <作者></作者>
        <售价></售价>
    </>
    <>
        <书名></书名>
        <作者></作者>
        <售价></售价>
    </>
</书架>
<!ELEMENT 书架 (书+)>
<!ELEMENT 书 (书名,作者,售价)>
<!ELEMENT 书名 (#PCDATA)>
<!ELEMENT 作者 (#PCDATA)>
<!ELEMENT 售价 (#PCDATA)>
1.3.2 Schema约束[了解]

文档约束-schema

  • schema可以约束具体的数据类型,约束能力上更强大。
  • schema本身也是一个xml文件,本身也收到其他约束文件的要求,所以编写的更加严谨。

优点

  • 可以约束XML文件的标签内容格式,以及具体的数据类型
  • 本身也是XML文件,格式更严谨

在这里插入图片描述

需求:利用schema文档约束,约束一个XML文件的编写

分析:

  • 编写schema约束文档,后缀必须是.xsd,具体的形式到代码中观看。
  • 在需要编写的XML文件中导入该schema约束文档
  • 按照约束内容编写XML文件的标签
<?xml version="1.0" encoding="UTF-8" ?>
<书架 xmlns="http://www.baidu.cn"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.baidu.cn data.xsd">
    <!-- xmlns="http://www.baidu.cn"  基本位置
         xsi:schemaLocation="http://www.baidu.cn books02.xsd" 具体的位置 -->
    <>
        <书名>神雕侠侣</书名>
        <作者>金庸</作者>
        <售价>399.9</售价>
    </>
    <>
        <书名>神雕侠侣</书名>
        <作者>金庸</作者>
        <售价>19.5</售价>
    </>

</书架>
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.baidu.cn"
        elementFormDefault="qualified" >
    <!-- targetNamespace:申明约束文档的地址(命名空间)-->
    <element name='书架'>
        <!-- 写子元素 -->
        <complexType>
            <!-- maxOccurs='unbounded': 书架下的子元素可以有任意多个!-->
            <sequence maxOccurs='unbounded'>
                <element name=''>
                    <!-- 写子元素 -->
                    <complexType>
                        <sequence>
                            <element name='书名' type='string'/>
                            <element name='作者' type='string'/>
                            <element name='售价' type='double'/>
                        </sequence>
                    </complexType>
                </element>
            </sequence>
        </complexType>
    </element>
</schema>

二、XML解析技术

XML的数据的作用是什么,最终需要怎么处理?

  • 存储数据、做配置信息、进行数据传输。
  • 最终需要被程序进行读取,解析里面的信息。

2.1 XML解析技术概述

什么是XML解析?

  • 使用程序读取XML中的数据

两种解析方式:

  • SAX解析(一行行解析xml文件)
  • DOM解析(直接将xml文件放进内存使用树结构来解析)

在这里插入图片描述

2.1.1 DOM常见的解析工具
名称说明
JAXPSUN公司提供的一套XML的解析的API
JDOMJDOM是一个开源项目,他基于树形结构,利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。
dom4j是JDOM的升级品,用来读写XML文件的。具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom技术,同时他也是一个开源代码的软件,Hibernate也用它来读写配置文件
jsoup功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便
2.1.2 DOM解析文档对象模型

DOM解析常用技术框架:Dom4J

Document对象:整个xml文档

Element对象:标签

Attribute对象:属性

Text对象:文本内容

在这里插入图片描述

2.2 Dom4J解析XML文件

需求:使用Dom4j解析出xml文件

分析:

  • 下载Dom4j框架,官网下载。
  • 在项目中创建文件夹:lib用于存放核心依赖包
  • 将dom4j-2.1.1.jar文件复制到lib文件夹
  • 在jar文件上右击选择Add asLibary -> 点击OK
  • 在类中导包使用
2.2.1 使用Dom4j解析得到Document对象

SAXReader类

构造器/方法说明
public SAXReader()创建Dom4J的解析器对象
Document read(String url)加载XML文件成为Document对象

Domcument类

方法名说明
Element getRootElement()获得根元素对象
    public static void main(String[] args) throws Exception {
        //先创建dom4j解析器对象
        SAXReader saxReader = new SAXReader();
        //使用解析器的read方法加载xml文件为Document对象
        //getResourceAsStream中的/是直接去src下寻找的文件,就算你的项目名称改了,也不影响寻找文件
        Document read = saxReader.read(createXML.class.getResourceAsStream("/Contacts.xml"));

        //得到Document对象的根节点对象,并打印名称是否正确
        Element rootElement = read.getRootElement();
        System.out.println(rootElement.getName());
    }
<?xml version="1.0" encoding="UTF-8"?>
<contactList>
    <contact id="1" vip="true">
        <name>   孙悟空  </name>
        <gender></gender>
        <email>wukong@baidu.cn</email>
    </contact>
    <contact id="2" vip="false">
        <name>唐僧</name>
        <gender></gender>
        <email>tangtang@baidu.cn</email>
    </contact>
    <contact id="3" vip="false">
        <name>猪八戒</name>
        <gender></gender>
        <email>zhuzhu@baidu.cn</email>
    </contact>
    <contact id="4" vip="false">
        <name>白骨精</name>
        <gender></gender>
        <email>baibai@baidu.cn</email>
    </contact>
    <user>
    </user>
</contactList>

2.3 Dom4J解析XML文件中的各种节点

首先必须得到文档对象Document,从中获取元素对象和内容

Dom4J解析XML的元素、属性、文本

方法名说明
List elements()得到当前元素下所有子元素
List elements(String name)得到当前元素下指定名字的子元素返回集合
Element element(String name)得到当前元素下指定名字的子元素,如果很多名字相同的则返回第一个
String getName()得到元素名字
String attributeValue(String name)通过属性名直接得到属性值
String elementText(子元素名)得到指定名称的子元素的文本
String getText()得到文本
    public static void main(String[] args) throws Exception {
        //先创建dom4j解析器对象
        SAXReader saxReader = new SAXReader();
        //使用解析器的read方法加载xml文件为Document对象
        //getResourceAsStream中的/是直接去src下寻找的文件,就算你的项目名称改了,也不影响寻找文件
        Document read = saxReader.read(createXML.class.getResourceAsStream("/Contacts.xml"));

        //得到Document对象的根节点,并打印名称是否正确
        Element root = read.getRootElement();
        System.out.println(root.getName());

        //获取根节点下的所有子元素(获取一级子元素)
//        List<Element> elements = root.elements();

        //获取根节点下指定的一级子元素
        List<Element> elements = root.elements("contact");
        for (Element element : elements) {
            System.out.println(element.getName());
        }

        //获取根节点下指定的一级子元素对象,默认为第一个
        Element element = root.element("contact");
        //获得该元素的指定属性
        Attribute id = element.attribute("id");
        //获得该属性的值
        System.out.println(id.getText());//1

        //直接通过根节点获取指定的子元素的属性值,使用attributeValue方法
        String id1 = root.element("contact").attributeValue("id");
        System.out.println(id1);//1

        //获得该节点下的所有一级子元素
        List<Element> elements1 = element.elements();
        for (Element element1 : elements1) {
            System.out.println(element1.getName());
            //name
            //gender
            //email
        }

        //获取该节点下指定的一级子元素,默认第一个
        Element name = element.element("name");
        //打印该子元素的值
        System.out.println(name.getText());//   孙悟空
        //打印子元素的值,并去掉前后空格
        System.out.println(name.getTextTrim());//孙悟空
        
    }
<?xml version="1.0" encoding="UTF-8"?>
<contactList>
    <contact id="1" vip="true">
        <name>   孙悟空  </name>
        <gender></gender>
        <email>wukong@baidu.cn</email>
    </contact>
    <contact id="2" vip="false">
        <name>唐僧</name>
        <gender></gender>
        <email>tangtang@baidu.cn</email>
    </contact>
    <contact id="3" vip="false">
        <name>猪八戒</name>
        <gender></gender>
        <email>zhuzhu@baidu.cn</email>
    </contact>
    <contact id="4" vip="false">
        <name>白骨精</name>
        <gender></gender>
        <email>baibai@baidu.cn</email>
    </contact>
    <user>
    </user>
</contactList>

2.4 Dom4J解析XML文件-案例实战

通常数据会封装成Java的对象,如单个对象,或者集合对象形式

利用Dmo4J知识,将Contact.xml文件中的联系人数据封装成List集合,其中每一个元素都是实体类Contact。打印输出List中的每一个元素

    private static List<Contact> list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        //创建解析器对象,加载xml文件为Document对象,使用getResourceAsStream方法需要加/代表src目录
        SAXReader saxReader = new SAXReader();
        Document read = saxReader.read(createXML.class.getResourceAsStream("/Contacts.xml"));

        //获得Document对象的根节点(根节点只有一个)
        Element root = read.getRootElement();
        //获得根节点下所有的contact一级节点
        List<Element> contact = root.elements("contact");
        //遍历一级节点
        for (Element element : contact) {
            //创建一个对象用来存储遍历的值
            Contact contact1 = new Contact();
            //遍历每一个节点上的相关标签值和属性
            contact1.setName(element.element("name").getTextTrim());
            contact1.setSex(element.element("gender").getTextTrim().charAt(0));
            contact1.setEmail(element.element("email").getTextTrim());
            contact1.setId(Integer.valueOf(element.attributeValue("id")));
            contact1.setVip(Boolean.valueOf(element.attributeValue("vip")));
            //添加到list集合中
            list.add(contact1);
        }

        //遍历集合
        for (Contact contact1 : list) {
            System.out.println(contact1);
        }
        //Contact{name='孙悟空', sex=男, email='wukong@baidu.cn', id=1, vip=true}
        //Contact{name='唐僧', sex=男, email='tangtang@baidu.cn', id=2, vip=false}
        //Contact{name='猪八戒', sex=男, email='zhuzhu@baidu.cn', id=3, vip=false}
        //Contact{name='白骨精', sex=女, email='baibai@baidu.cn', id=4, vip=false}
    }


public class Contact {

    private String name ;
    private char sex;
    private String email;
    private int id;
    private boolean vip;

    //省略有参和无参构造、getter和setter方法、toString重写方法。
}

三、XML检索技术:XPath

我们使用Dom4j进行文件的全部解析,然后在搜寻数据比较繁琐,那么我们有没有更简单的方法来检索某个信息呢?

  • XPath技术更加适合做信息检索

3.1 XPath

什么是XPath?

  • XPath在解析XML文档方面提供了独树一帜的路径思想,更加优雅,高效。
  • XPath使用路径表达式来定位XML文档中的元素节点或属性节点。

注意:XPath技术也是依赖于Dom4j技术来检索

例如:

  • /元素/子元素/孙子元素
  • //子元素/孙子元素
方法名说明
Node selectSingleNode(“表达式”)获取符合表达式的唯一元素
List selectNodes(“表达式”)获取符合表达式的元素集合

3.2 XPath四大检索方案

  • 绝对路径

    • 采用绝对路径获取从根节点开始逐层的查找/contactList/contact/name节点列表并打印信息

    • 方法名说明

    | /根元素/子元素/孙元素 | 从根元素开始,逐级向下查找,不能跨级 |

  • 相对路径

    • 先得到根节点contactList

    • 再采用相对路径获取下一级contact节点的name子节点并打印信息

      • 方法名说明

      | ./子元素/孙元素 | 从当前元素开始,一级一级向下查找,不能跨级 |

  • 全文检索

    • 直接全文搜索所有的name元素并打印

      • 方法名说明

      | //contact | 找contact元素,无论在哪里 |
      | //contact/name | 全文查找contact,name一定是contact的子节点 |
      | //contact//name | 全文查找contact,name只要是contact的子孙元素都可以找到 |

  • 属性查找

    • 全文搜索属性,或者带属性的元素

      • 方法名说明

      | //@属性名 | 查找属性对象,无论是哪个元素,只要有这个属性即可 |
      | //元素[@属性名] | 查找元素对象,全文搜索指定元素名和属性名 |
      | //元素[@属性名=值] | 查找元素对象,全文搜索指定元素名和属性名,并且属性值相等 |

3.3 案例

需求:使用Dmo4j把一个XML文件的数据进行解析

分析:

  • 导入jar包(Dmo4j和jaxen-1.1.2.jar),Xpath技术依赖于Dmo4j技术
  • 通过dom4j的SAXReader获取Document对象
  • 利用XPath提供的API,结合XPath的语法完成选取XML文档元素节点进行解析操作。
public class Demo1 {

    //绝对路径检索
    @Test
    public void absolutePath() throws Exception {
        //创建Dmo4j解析器
        SAXReader s1 = new SAXReader();
        //加载xml文件为Document对象
        Document document = s1.read(Demo1.class.getResourceAsStream("/Contacts.xml"));

        //使用绝对路径检索指定路径的标签
        List<Node> node1 = document.selectNodes("/contactList/contact/name");
        for (Node node : node1) {
            //遍历打印检索到的name标签的文本内容,
            // 检索不到沙和尚是因为沙和尚的路径不是这个绝对路径
            System.out.println(node.getText());
            //   孙悟空
            //唐僧
            //猪八戒
            //白骨精
        }
    }


    //相对路径检索
    @Test
    public void relativePaths() throws Exception {
        //创建Dmo4j解析器
        SAXReader s2 = new SAXReader();
        //加载xml文件为Document对象
        Document document = s2.read(Demo1.class.getResourceAsStream("/Contacts.xml"));

        //使用相对路径检索节点信息
        //需要先得到根节点
        Element root = document.getRootElement();
        //根据根节点相对路径找节点信息
        List<Node> node2 = root.selectNodes("./contact/name");
        //遍历得到的节点然后打印值
        for (Node node : node2) {
            System.out.println(node.getText());
            //   孙悟空
            //唐僧
            //猪八戒
            //白骨精
        }
    }


    //全文检索
    @Test
    public void allRetrieve() throws Exception {

        //创建Dmo4j解析器
        SAXReader s3 = new SAXReader();
        //加载xml文件为Document对象
        Document document = s3.read(Demo1.class.getResourceAsStream("/Contacts.xml"));

        //全文检索name标签,可以检索到沙和尚,因为沙和尚也属于name标签
        List<Node> node3 = document.selectNodes("//name");
        for (Node node : node3) {
            System.out.println(node.getText());
            //   孙悟空
            //唐僧
            //猪八戒
            //白骨精
            //   沙和尚
        }

        //全文检索contact标签,且email属于contact的子节点的email标签
        //因为沙和尚没有email标签,所以检索不到
        List<Node> node4 = document.selectNodes("//contact/email");
        for (Node node : node4) {
            System.out.println(node.getText());
            //wukong@baidu.cn
            //tangtang@baidu.cn
            //zhuzhu@baidu.cn
            //baibai@baidu.cn
        }

        //全文检索user标签,在user标签下再去寻找name标签,name属于子孙类都可以
        //例如沙和尚的标签name属于user的孙子类
        //selectSingleNode方法只会搜索一个,如果搜索到很多也只会默认为第一个节点
        Node node5 = document.selectSingleNode("//user//name");
        System.out.println(node5.getText());
        //   沙和尚
    }

    //属性检索
    @Test
    public void attributesRetrieve() throws Exception {
        //创建dmo4j解析器
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(Demo1.class.getResourceAsStream("/Contacts.xml"));

        //使用//@属性名 可以定位属性对象
        List<Node> node1 = document.selectNodes("//@id");
        for (Node node : node1) {
            System.out.println(node.getText());
            //1
            //2
            //3
            //4
        }

        //使用//元素[@属性名] 可以检索该元素中带有该属性的节点对象
        List<Node> node2 = document.selectNodes("//contact[@id]");
        for (Node node : node2) {
            System.out.println(node.getName());
            //contact
            //contact
            //contact
            //contact
        }

        //使用//元素[@属性 = 值]可以检索该元素中带有该属性,且属性值为指定的节点对象
        List<Node> node3 = document.selectNodes("//contact[@id = 4]");
        for (Node node : node3) {
            //因为一个node(节点)也是一个element(标签),所以直接强转
            Element element = (Element) node;
            //查看该节点下的name文本是不是我们指定id=4的name值
            System.out.println(element.element("name").getText());
            //白骨精
        }

    }
}
<?xml version="1.0" encoding="UTF-8"?>
<contactList>
    <contact id="1" vip="true">
        <name>   孙悟空  </name>
        <gender></gender>
        <email>wukong@baidu.cn</email>
    </contact>
    <contact id="2" vip="false">
        <name>唐僧</name>
        <gender></gender>
        <email>tangtang@baidu.cn</email>
    </contact>
    <contact id="3" vip="false">
        <name>猪八戒</name>
        <gender></gender>
        <email>zhuzhu@baidu.cn</email>
    </contact>
    <contact id="4" vip="false">
        <name>白骨精</name>
        <gender></gender>
        <email>baibai@baidu.cn</email>
    </contact>
    <user>
        <contact vip="true">
            <name>   沙和尚  </name>
        </contact>
    </user>
</contactList>

四、设计模式

4.1 工厂模式

什么是工厂模式?

  • 之前我们创建类对象时,都是使用new对象的形式创建,很多业务常见也提供了不直接new的方式
  • 工厂模式(Factory Pattern)是Java常用的设计模式之一,这种类型的设计模式属于创建型模式,他提供了一种获取对象的方式。

作用:

  • 工厂的方法可以封装对象的创建细节,比如:为该对象进行加工和数据注入。
  • 可以实现类与类之间的解耦操作(核心思想)。
public class factoryPattern {

    public static void main(String[] args) {
        //此时我们创建了一个Computer接口,里面编写了电脑共同属性(name,price)和方法(start)
        //以往创建电脑的写法如下:(需要我们一个个new对象,然后给相关属性)
//        Mac mac = new Mac();
//        mac.setName("苹果 puls");
//        mac.setPrice(9999);
//        mac.start();

        //使用工厂设计模式,编写一个FactoryComputer类
        Computer huawei = FactoryComputer.createComputer("huawei");
        Computer mac = FactoryComputer.createComputer("mac");
        huawei.start();
        mac.start();
        //华为16 pro电脑开机------
        //苹果11 plus电脑启动了===
    }
}


//工厂类
public class FactoryComputer {

    //创建一个工厂方法,用来传入你选择的品牌,返回对应品牌电脑,且属性已经赋值,相当于封装了电脑的制作和属性
    public static Computer createComputer(String brand){
        switch (brand){
            case "huawei":
                HuaWei huaWei = new HuaWei();
                huaWei.setName("华为16 pro");
                huaWei.setPrice(8888);
                return huaWei;
            case "mac":
                Mac mac = new Mac();
                mac.setName("苹果11 plus");
                mac.setPrice(19999);
                return mac;
            default:
                return null;
        }
    }
}


//电脑抽象类
public abstract class Computer {
    private String name;
    private double price;

    public void start() {

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}


public class Mac extends Computer {

    @Override
    public void start() {
        System.out.println(getName() + "电脑启动了===");
    }
}


public class HuaWei extends Computer{

    @Override
    public void start() {
        System.out.println(getName() + "电脑开机------");
    }
}

4.2 装饰模式

什么是装饰设计模式?

  • 创建一个新类,包装原始类,从而在新类中提升原来类的功能。

作用:

  • 装饰模式指的是在不改变原类的基础上,动态扩展一个类的功能。

注意:装饰类和原始类必须继承同一个父类,然后装饰类是增强了原始类的功能

例如:前面我们所学的装饰类BufferedInputStream。我们可以模拟去创建装饰类。

public class DecorativePattern {
    //装饰模式,我们模拟创建BufferedInputStream装饰类
    //创建一个共同接口InputStream接口,定义公共方法read。
    //实现接口,创建原始类FileInputStream,实现InputStream接口
    //创建一个装饰类BufferedInputStream,也必须实现InputStream接口,加强FileInputStream的read功能
    public static void main(String[] args) {
        InputStream is = new BufferedInputStream(new FileInputStream());
        System.out.println(is.read());
        System.out.println(is.read(new byte[3]));
        //开启8k缓冲区,开始一个个读字节
        //低速度的读了一个字节a
        //97
        //开启8k的缓冲区,开始读一个字节数组
        //低速度的读了一个字节数组
        //3

    }

}


//InputStream接口
public interface InputStream{
    //模拟两个功能,读一个字节和一个字节数组
    public int read();
    public int read(byte[] buffer);
}


//原始类
public class FileInputStream implements InputStream{
    @Override
    public int read() {
        System.out.println("低速度的读了一个字节a");
        return 97;
    }

    @Override
    public int read(byte[] buffer) {
        buffer[0] = 'a';
        buffer[1] = 'b';
        buffer[2] = 'c';
        System.out.println("低速度的读了一个字节数组");
        //返回字节数组长度
        return 3;
    }
}


//装饰类
public class BufferedInputStream implements InputStream{
    //创建一个InputStream来保存流
    private InputStream is;

    //传入输入流并保存这个输入流
    public BufferedInputStream(InputStream is){
        this.is = is ;
    }

    @Override
    public int read() {
        //模拟提前开启缓冲区,增加性能,然后再调用低速的输入流读
        System.out.println("开启8k缓冲区,开始一个个读字节");
        return is.read();
    }

    @Override
    public int read(byte[] buffer) {
        //模拟提前开启缓冲区,增加性能,然后再调用低速的输入流读
        System.out.println("开启8k的缓冲区,开始读一个字节数组");
        return is.read(buffer);
    }
}

4.3 单例模式

设计模式中的一种,该模式可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象。

  • 例如:任务管理器对象我们只需要一个就可以解决问题了,这样可以节省内存空间,任务管理器只能打开一个,属于单例模式。
4.3.1 饿汉单例(eager)模式

在用类获取对象时,对象已经提前创建好了,提供一个公有方法返回该对象,且该类构造函数为私有,无法通过构造函数再次创建其他对象。

设计步骤:

  • 定义一个类,把构造器私有;
  • 定义一个静态变量存储一个对象;
  • 提供公有静态方法,且方法返回该对象
//单例模式(饿汉模式)
public class EagerSingleton{

    //因为static修饰的变量会跟着类一起加载,且只会加载一次,所以相当于只能new一次
    private static EagerSingleton instance = new EagerSingleton();

    //如果在new实例之前需要进行其他的初始化操作,可以将new对象放入静态代码块,因为静态代码块也只会随类加载一次
//    static {
//        instance = new EagerSingleton();
//    }

    //构造方法被私有化,私有化后就无法直接new对象,只能使用唯一的实例instance
    private EagerSingleton(){
        System.out.println("创建了有一个对象");
    }

    //提供获取实例的公有方法,返回值为类中提前创建的实例
    public static EagerSingleton getInstance(){
        return instance;
    }

}
4.3.2 懒汉单例(lazy)模式(重点)

在使用该对象时,才去创建一个对象(延迟加载对象)。

**与饿汉单例模式区别为:**将new操作放入方法中,因为方法加载时不会创建对象,当调用方法后才会创建对象;

设计步骤 :

  • 定义一个类,将构造方法私有;
  • 定义一个静态变量存储一个对象;
  • 提供一个静态方法,返回值为实例对象,且new操作会根据instance有无值来操作,当有值就不new直接返回;
//懒汉模式
class LazySingleton{
    //创建一个静态变量,用于存放实例对象
    private static LazySingleton instance ;
    //构造方法私有化,防止new对象
    private LazySingleton(){
        System.out.println("创建了一个对象");
    }

    //提供公有方法,返回实例对象
    public static LazySingleton getInstance(){
        //判断是不是第一次调用方法,第一次调用就将new实例对象并存放到静态变量中
        if (instance == null){
            //第一次调用方法会进来,后面就不会再new对象,只会new一次
            instance = new LazySingleton();
        }
        //返回实例对象
        return instance;
    }
}

注意:上面的创建懒汉单例模式可能会出现线程不安全,因为线程多了就可能会导致创建多个对象,解决线程不安全方法如下:

  • 方法一:在new对象之前加锁,保证只有一个线程能够new对象;
  • 方法二:可以将new对象放入内部类中(重点)
    • 外部类加载时,内部类不会加载,当调用内部类的方法或者变量时才会加载
    • 内部类中静态变量只会加载一次

方法一:

//懒汉模式(加锁)
class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton() {
        System.out.println("创建了一个对象");
    }


    public static LazySingleton getInstance() {

        //当第一次调用该方法就会为true,
        //外层if判断可以提高性能,因为当不是第一次调用方法时,
        //判断直接为true,就不会进入锁,以免每次都进入锁,其他线程等待,影响性能
        if (instance == null) {
            //加锁,解决线程不安全问题,锁为 类名.class
            synchronized (LazySingleton.class) {
                //内层if判断可以解决线程不安全问题,因为可能多个线程第一次都进入了if然后遇到锁就等待
                //如果没有这个内层if,那么进入外层if的线程都会new对象
                //所以内层嵌套if,可以防止多个线程进入if后,抢到锁还要判断实例是否被创建,如果被创建则后面线程都不会创建实例
                if (instance == null)
                    instance = new LazySingleton();
            }
        }

        return instance;
    }
}

方法二: 相当于饿汉模式+内部类来实现线程安全的懒汉模式

//懒汉模式(加锁)
class LazySingleton {

    private LazySingleton() {
        System.out.println("创建了一个对象");
    }

    //提供公有获取实例方法
    public static LazySingleton getInstance() {
        //返回内部类中的变量(因为在内部类中new的对象)
        return Inner.instance;
    }

    //创建内部类,将new对象封装到内部类中
    //外部类加载,内部类不会加载,只有使用到了才会加载
    private class Inner{
        //new对象,用static修饰与类一起加载,且只加载一次,相当于只会new一次(所以不会出现线程不安全问题)
        private static LazySingleton instance = new LazySingleton();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值