美文网首页
201022:工厂模式-包类名一样加载?-双亲委派-类加载顺序-

201022:工厂模式-包类名一样加载?-双亲委派-类加载顺序-

作者: 弹钢琴的崽崽 | 来源:发表于2020-10-22 20:51 被阅读0次

    一. 工厂模式

    • 抽象工厂(ExportFactory)角色:担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口。在实际的系统中,这个角色也常常使用抽象类实现。
    • 具体工厂(ExportHtmlFactory、ExportPdfFactory)角色:担任这个角色的是实现了抽象工厂接口的具体JAVA类。具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建导出类(如:ExportStandardHtmlFile)。
    • 抽象导出(ExportFile)角色:工厂方法模式所创建的对象的超类,也就是所有导出类的共同父类或共同拥有的接口。在实际的系统中,这个角色也常常使用抽象类实现。
    • 具体导出(ExportStandardHtmlFile等)角色:这个角色实现了抽象导出(ExportFile)角色所声明的接口,工厂方法模式所创建的每一个对象都是某个具体导出角色的实例。

    源代码

    首先是抽象工厂角色源代码。它声明了一个工厂方法,要求所有的具体工厂角色都实现这个工厂方法。参数type表示导出的格式是哪一种结构,如:导出HTML格式有两种结构,一种是标准结构,一种是财务需要的结构。

    public interface ExportFactory {
        public ExportFile factory(String type);
    }
    

    具体工厂角色类源代码:

    public class ExportHtmlFactory implements ExportFactory{    
        @Override    
        public ExportFile factory(String type) {        
            // TODO Auto-generated method stub        
            if("standard".equals(type)){                        
                return new ExportStandardHtmlFile();                    
            }else if("financial".equals(type)){                        
                return new ExportFinancialHtmlFile();                    
            }else{            
                throw new RuntimeException("没有找到对象");        
            }    
        }
    }
    
    public class ExportPdfFactory implements ExportFactory {
    
        @Override
        public ExportFile factory(String type) {
            // TODO Auto-generated method stub
            if("standard".equals(type)){
    
                return new ExportStandardPdfFile();
    
            }else if("financial".equals(type)){
                return new ExportFinancialPdfFile();
            }else{
                throw new RuntimeException("没有找到对象");
            }
        }
    
    }
    

    抽象导出角色类源代码:

    public interface ExportFile {
        public boolean export(String data);
    }
    

    具体导出角色类源代码,通常情况下这个类会有复杂的业务逻辑。

    public class ExportFinancialHtmlFile implements ExportFile{
    
        @Override
        public boolean export(String data) {
            // TODO Auto-generated method stub
            /**
             * 业务逻辑
             */
            System.out.println("导出财务版HTML文件");
            return true;
        }
    
    }
    
    public class ExportFinancialPdfFile implements ExportFile{
    
        @Override
        public boolean export(String data) {
            // TODO Auto-generated method stub
            /**
             * 业务逻辑
             */
            System.out.println("导出财务版PDF文件");
            return true;
        }
    
    }
    
    public class ExportStandardHtmlFile implements ExportFile{
    
        @Override
        public boolean export(String data) {
            // TODO Auto-generated method stub
            /**
             * 业务逻辑
             */
            System.out.println("导出标准HTML文件");
            return true;
        }
    
    }
    
    public class ExportStandardPdfFile implements ExportFile {
    
        @Override
        public boolean export(String data) {
            // TODO Auto-generated method stub
            /**
             * 业务逻辑
             */
            System.out.println("导出标准PDF文件");
            return true;
        }
    
    }
    

    客户端角色类源代码:

    public class Test {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            String data = "";
            ExportFactory exportFactory = new ExportHtmlFactory();
            ExportFile ef = exportFactory.factory("financial");
            ef.export(data);
        }
    
    }
    

    客户端创建ExportHtmlFactory对象,这时客户端所持有变量的静态类型为ExportFactory,而实际类型为ExportHtmlFactory。然后客户端调用ExportHtmlFactory对象的工厂方法factory(),接着后者调用ExportFinancialHtmlFile的构造子创建出导出对象

    工厂方法模式和简单工厂模式

    工厂方法模式和简单工厂模式在结构上的不同很明显。工厂方法模式的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。
      工厂方法模式退化后可以变得很像简单工厂模式。设想如果非常确定一个系统只需要一个具体工厂类,那么不妨把抽象工厂类合并到具体工厂类中去。由于只有一个具体工厂类,所以不妨将工厂方法改为静态方法,这时候就得到了简单工厂模式。

    如果系统需要加入一个新的导出类型,那么所需要的就是向系统中加入一个这个导出类以及所对应的工厂类。没有必要修改客户端,也没有必要修改抽象工厂角色或者其他已有的具体工厂角色。对于增加新的导出类型而言,这个系统完全支持“开-闭原则”。

    完结

    一个应用系统是由多人开发的,导出的功能是你实现的,但是使用者(调用这个方法的人)可能却是其他人。这时候你应该设计的足够灵活并尽可能降低两者之间的耦合度,当你修改或增加一个新的功能时,使用者不需要修改任何地方。假如你的设计不够灵活,那么将无法面对客户多变的需求。可能一个极小的需求变更,都会使你的代码结构发生改变,并导致其他使用你所提供的接口的人都要修改他们的代码。牵一处而动全身,这就使得日后这个系统将难以维护。

    二. 当java的源代码中出现了和系统的lib库中的包名与类名完全一样的类时,系统应当怎么加载?

    我们都知道在JVM启动的时候会先调用bootstrap classloader 加载核心类,然后调用extClassLoader 加载系统扩展类,然后再调用APPClassLoader加载系统类,现在问题来了当我们在工程里创建一个从包名到类名都与系统已有的类完全一样的类时会发生什么事情?

    以java.util.Date为例,在实验中创建了一个与系统已有Date类一模一样的Date类

    很明显,程序在运行的时候选择了系统中原有的java.util.Date类,并没有选择我们自己写的Date类,这与先前提到的JVM的classloader的加载顺序相一致,由于java.util属于系统的核心类,在首次加载时即由bootstrape加载完毕,用户的类Date应当由AppClassLoader进行加载,但是由于双亲委托机制的存在,AppClassLoader并不是立即进行加载,它需要先向其父加载器ExtClassLoader请求,由ExtClassLoader先加载,ExtClassLoader再向bootstrapeClassLoader请求加载,由于之前名为java.util.Date的类已经被bootstrape加载过了,所以后边两个加载器就不再对Date进行加载,因此看到的是系统报错(因为系统中的Date类没有main函数)

    加载参考文章

    三. 双亲委派模型

    双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。

    双亲委派模型工作工程:

    1. 当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
    2. 当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
    3. 如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。
    4. 如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
    5. 如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
    6. 如果均加载失败,就会抛出ClassNotFoundException异常。

    双亲委派模型参考文章

    四. 类加载顺序

    类的加载过程

    一个java文件从被加载到被卸载这个生命过程,总共要经历5个阶段,JVM将类加载过程分为: (加链初使卸)
      加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载

    1. 加载
        首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。
    2. 链接:
        验证:确保被加载类的正确性;
        准备:为类的静态变量分配内存,并将其初始化为默认值;
        解析:把类中的符号引用转换为直接引用;
    3. 为类的静态变量赋予正确的初始值

    类的初始化

    (1)类什么时候才被初始化
      1)创建类的实例,也就是new一个对象
      2)访问某个类或接口的静态变量,或者对该静态变量赋值
      3)调用类的静态方法
      4)反射(Class.forName(“com.lyj.load”))
      5)初始化一个类的子类(会首先初始化子类的父类)
      6)JVM启动时标明的启动类,即文件名和类名相同的那个类
    (2)类的初始化顺序
      1)如果这个类还没有被加载和链接,那先进行加载和链接
      2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
      3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

    4)总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;

    如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法

    类的加载

    类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象

    类的加载顺序参考文章

    五. Stream流

    Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

    Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

    Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

    这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

    元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

    +--------------------+       +------+   +------+   +---+   +-------+
    | stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
    +--------------------+       +------+   +------+   +---+   +-------+
    

    1. forEach

    Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
    

    2. map

    map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

    distinct():去掉重复

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    // 获取对应的平方数
    List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
    

    3. filter

    filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

    List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
    // 获取空字符串的数量
    long count = strings.stream().filter(string -> string.isEmpty()).count();
    

    4. limit

    limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
    

    5. sorted

    sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

    Random random = new Random();
    random.ints().limit(10).sorted().forEach(System.out::println);
    

    6. Collectors

    Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

    List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
    List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
     
    System.out.println("筛选列表: " + filtered);
    String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
    System.out.println("合并字符串: " + mergedString);
    

    7. 统计

    另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
    System.out.println("列表中最大的数 : " + stats.getMax());
    System.out.println("列表中最小的数 : " + stats.getMin());
    System.out.println("所有数之和 : " + stats.getSum());
    System.out.println("平均数 : " + stats.getAverage());
    

    相关文章

      网友评论

          本文标题:201022:工厂模式-包类名一样加载?-双亲委派-类加载顺序-

          本文链接:https://www.haomeiwen.com/subject/orwpmktx.html