精华内容
下载资源
问答
  • 从斐波那契数列理解动态规划

    千次阅读 2020-02-20 16:08:29
    一提到动态规划,大多数人都想到许许多多高端的名词,如什么状态转移方程什么的...不过别急,本人用我掌握动态规划的历程带你理解动态规划,让你成功进入动态规划的领域。 在使用动态规划前,先了解为什么使用动态规...

    一提到动态规划,大多数人都想到许许多多的名词,如什么状态转移方程什么的;要不就想到教材书上严谨而又晦涩难懂的对于动态规划的介绍;也有人想到高中的通项公式或数列题等等。然后脑子就开始晕了,感觉其他的入门算法都可以理解,但是到了动态规划就好难学会,也不禁产生出一种挫败的念头。不过别急,本人用我掌握动态规划的历程带你理解动态规划,让你成功进入动态规划的领域。

    在使用动态规划前,先了解为什么使用动态规划。动态规划问题的一般形式就是求最值,比如最少硬币,最大背包价值,最短路线等等。既然要求最值,那么最简单的方法是啥?当然是一个个找呗。比如从十个人中找到最高那个,把所有可能的答案找一遍就可以得到最值了。所以动态规划的基础就是穷举

    但是动态规划的穷举和一般的穷举还不太一样,因为这类问题存在重叠子问题,如果暴力穷举的话效率会极其低下,所以需要一张表来优化穷举过程,避免冗余的计算。另外,虽然动态规划的核心思想就是穷举求最值,但是问题可以千变万化,穷举所有可行解其实并不是一件容易的事,只有得到正确的状态转移方程才能正确地穷举。

    以上提到的重叠子问题状态转移方程就是动态规划中的两个要素。这两要素的意思等会具体讲解,在实际的算法问题中,写出状态转移方程是最困难的,接下来的思维方式,将会辅助你思考状态转移方程。

    斐波那契数列

    1、暴力递归

    斐波那契数列的数学形式就是递归的,写成代码就是这样:

    int fib(int N) {
        if (N == 1 || N == 2) return 1;
        return fib(N - 1) + fib(N - 2);
    }
    

    这个不用多说了,学校老师讲递归的时候似乎都是拿这个举例。我们也知道这样写代码虽然简洁易懂,但是十分低效,低效在哪里?
    在这里插入图片描述
    该图说的是想要计算原问题 f(20),我就得先计算出子问题 f(19) 和 f(18),然后要计算 f(19),我就要先算出子问题 f(18) 和 f(17),以此类推。最后遇到 f(1) 或者 f(2) 的时候,结果已知,就能直接返回结果,递归树不再向下生长了。
    这个算法的时间复杂度为 O(2^n),指数级别,爆炸。

    观察该图,很明显发现了算法低效的原因:存在大量重复计算,比如 f(18) 被计算了两次,而且你可以看到,以 f(18) 为根的这个递归树体量巨大,多算一遍,会耗费巨大的时间。更何况,还不止 f(18) 这一个节点被重复计算,所以这个算法及其低效。

    这就是动态规划问题的第一个性质:重叠子问题。下面,我们想办法解决这个问题。

    2.带数组的递归解法

    明确了问题,其实就已经把问题解决了一半。即然耗时的原因是重复计算,那么我们可以造一个数组,每次算出某个子问题的答案后别急着返回,先记到数组里再返回;每次遇到一个子问题先去数组里查一查,如果发现之前已经解决过这个问题了,直接把答案拿出来用,不要再耗时去计算了。
    当然你也可以使用哈希表(字典)代替数组,思想都是一样的。

    int fib(int N) {
        if (N < 1) return 0;
        // 数组全初始化为 0
        vector<int> memo(N + 1, 0);
        // 初始化最简情况
        return helper(memo, N);
    }
     
    int helper(vector<int>& memo, int n) {
        // base case 
        if (n == 1 || n == 2) return 1;
        // 已经计算过
        if (memo[n] != 0) return memo[n];
        memo[n] = helper(memo, n - 1) + 
                    helper(memo, n - 2);
        return memo[n];
    }
    

    现在,画出图,你就知道数组到底做了什么。
    在这里插入图片描述
    实际上,带数组的递归算法,把一棵存在巨量冗余的递归树通过裁剪,改造成了一幅不存在冗余的递归图,极大减少了子问题(即递归图中节点)的个数。
    在这里插入图片描述
    经过分析,本算法的时间复杂度是 O(n)。
    至此,带数组的递归解法的效率已经和迭代的动态规划解法一样了。实际上,这种解法和迭代的动态规划已经差不多了,只不过这种方法叫做自顶向下,动态规划叫做自底向上。关于自顶向下和自底向上的意思这里就不做描述,不懂的可以找度娘。

    3、自底向上的迭代解法

    有了上一步的启发,我们可以把这个数组独立出来成为一张dp表,在这张表上完成「自底向上」!

    int fib(int N) {
        vector<int> dp(N + 1, 0);
        // base case
        dp[1] = dp[2] = 1;
        for (int i = 3; i <= N; i++)
            dp[i] = dp[i - 1] + dp[i - 2];
        return dp[N];
    }
    

    在这里插入图片描述
    这里,引出「状态转移方程」这个名词,实际上就是描述问题结构的数学形式:
    f(n) = \begin{cases} 1, n = 1, 2 \ f(n - 1) + f(n - 2), n > 2 \end{cases}f(n)={1,n=1,2f(n−1)+f(n−2),n>2
    什么是状态转移方程?你把 f(n) 想做一个状态 n,这个状态 n 是由状态 n - 1 和状态 n - 2 相加转移而来,这就叫状态转移。

    你会发现,上面的几种解法中的所有操作,例如 return f(n - 1) + f(n - 2),dp[i] = dp[i - 1] + dp[i - 2],以及对备忘录或 DP table 的初始化操作,都是围绕这个方程式的不同表现形式。可见列出「状态转移方程」的重要性,它是解决问题的核心。很容易发现,其实状态转移方程直接代表着暴力解法。

    细心的读者会发现,根据斐波那契数列的状态转移方程,当前状态只和之前的两个状态有关,其实并不需要那么长的一个 DP table 来存储所有的状态,只要想办法存储之前的两个状态就行了。所以,可以进一步优化,把空间复杂度降为 O(1):

    int fib(int n) {
        if (n == 2 || n == 1) 
            return 1;
        int prev = 1, curr = 1;
        for (int i = 3; i <= n; i++) {
            int sum = prev + curr;
            prev = curr;
            curr = sum;
        }
        return curr;
    }
    

    至此,动态规划中三个主要特性已经讲了其中的重叠子问题状态转移方程。斐波那契数列的例子严格来说不算动态规划,因为没有涉及求最值,所以该篇文章主要是讲解如何从斐波那契数列理解动态规划。

    展开全文
  • 实例理解JDK动态代理和Cglib动态代理及其区别 深入理解设计模式之代理模式 代理商代理化妆品生产理解JDK动态代理 代理商代理汽车制造理解Cglib动态代理

    动态代理

    代理模式参考博客:

    https://blog.csdn.net/qq_42937522/article/details/105067563

    代理模式原理

    使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

    作用

    为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另外一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

    应用场景

    1)远程代理(Remote Proxy)

    为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中。也即为不同地址空间提供局部的代表。

    2)虚拟代理(Virtual Proxy)

    根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

    3)保护代理(Protection Proxy)

    控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。

    4)智能指引(Smart Reference)

    取代了简单的指针,它在访问对象时执行一些附加操作。

    5)Copy-on-Write代理

    它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

    动态代理

    动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

    动态代理相比于静态代理的优点:

    抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

    动态代理的一般用法

    在这里插入图片描述

    JDK动态代理

    使用Proxy和InvocationHandler创建动态代理

    Proxy提供用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果我们在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类:如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
    Proxy提供了如下两个方法来创建动态代理类和动态代理实例:

    • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):创建一- 个动态
      代理类所对应的Class对象,该代理类将实现interfaces 所指定的多个接口。第一个ClassLoader指定生成动态代理类的类加载器。
    • static Object newProxyInstance(ClassLoader loader,Class<?>I] interfaces, InvocationHandler h):直接创建一个动态代理对象, 该代理对象的实现类实现了interfaces 指定的一系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象invoke方法。

    实际上,即使采用第一-种方式获取了 一个动态代理类之后,当程序需要通过该代理类来创建对象时一样需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理对象都有一个与之关联的InvocationHandler对象。

    示例

    以代理商代理生产商的产品为例(JDK代理实现)

    代码实现

    需要实现的接口

    /**
     * 抽象接口——生产者
     */
    public interface Producer {
        String produce(String product);
    }
    
    /**
     * 功能描述:化妆品生产者(被代理对象)
     **/
    public class CosmeticProducer implements Producer {
    
        /**
         * 生产化妆品
         */
        @Override
        public String produce ( String product ) {
            System.out.println("正在生产化妆品" + product);
            return product;
        }
    
    }
    
    /**
     * 功能描述:提供产品的工具类
     **/
    public class ProductUtil {
    
        public static void prepareMaterial(){
            System.out.println("代理商为生产商准备材料");
        }
    
        public static void sellProduct(){
            System.out.println("代理商为生产商卖产品");
        }
        
    }
    
    /**
     * 功能描述:代理对象调用被代理对象方法,所必须实现的接口
     **/
    public class MyInvocationHandler implements InvocationHandler {
    
        /** 需要被代理的对象 */
        private Object target;
    
        public void setTarget ( Object target ) {
            this.target = target;
        }
    
        @Override
        public Object invoke ( Object proxy, Method method, Object[] args ) throws Throwable {
            //调用被代理对象方法前,执行的方法
            ProductUtil.prepareMaterial();
    
            //调用被代理对象的方法
            Object obj = method.invoke(target, args);
    
            //调用被代理对象方法后,执行的方法
            ProductUtil.sellProduct();
    
            //返回被代理对象方法的返回值
            return obj;
        }
    }
    
    /**
     * 功能描述:产品代理商(代理对象)
     *
     * 采用自定义的外部类MyInvocationHandler来处理
     *
     * @author RenShiWei
     * Date: 2020/6/19 16:46
     **/
    public class ProductProxy {
    
        //***方式一:使用自定义的外部类MyInvocationHandler实现InvocationHandler接口处理***
    
        public ProductProxy(){}
    
        //通过代理对象,调用被代理对象的方法
        public static Object getProductProxy ( Object target ) {
            MyInvocationHandler handler = new MyInvocationHandler();
            handler.setTarget(target);
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
        }
    
        //***方式二:使用匿名实现类来处理(可以减少类的数量)
    
        private Object target;
    
        public ProductProxy ( Object target ) {
            this.target = target;
        }
    
        //通过代理对象,调用被代理对象的方法
        public Object getProductProxy2 () {
            MyInvocationHandler handler = new MyInvocationHandler();
            handler.setTarget(target);
            //这里的InvocationHandler的实现类,也可以采用接口的匿名实现类来处理,减少类的数量
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke ( Object proxy, Method method, Object[] args ) throws Throwable {
                    //调用被代理对象方法前,执行的方法
                    ProductUtil.prepareMaterial();
    
                    //调用被代理对象的方法
                    Object obj = method.invoke(target, args);
    
                    //调用被代理对象方法后,执行的方法
                    ProductUtil.sellProduct();
    
                    //返回被代理对象方法的返回值
                    return obj;
                }
            });
        }
    }
    
    

    客户端调用

    /**
     * 功能描述:客户端调用
     **/
    public class client {
    
        public static void main ( String[] args ) {
            System.out.println("**方式一:使用MyInvocationHandler**");
            Producer cosmeticProducer = new CosmeticProducer();
            Producer productProxy = (Producer) ProductProxy.getProductProxy(cosmeticProducer);
            String s = productProxy.produce("欧莱雅男士洗面奶");
            System.out.println("被代理的产品:" + s);
    
            System.out.println("**方式二:使用匿名InvocationHandler接口实现类的方式**");
            ProductProxy proxy = new ProductProxy(new CosmeticProducer());
            Producer productProxy2 = (Producer) proxy.getProductProxy2();
            String s1 = productProxy2.produce("兰蔻洁面乳");
            System.out.println("被代理的产品:" + s1);
        }
    
    }
    

    结果:

    **方式一:使用MyInvocationHandler**
    代理商为生产商准备材料
    正在生产化妆品欧莱雅男士洗面奶
    代理商为生产商卖产品
    被代理的产品:欧莱雅男士洗面奶
    **方式二:使用匿名InvocationHandler接口实现类的方式**
    代理商为生产商准备材料
    正在生产化妆品兰蔻洁面乳
    代理商为生产商卖产品
    被代理的产品:兰蔻洁面乳
    

    JDK动态代理原理

    为什么要叫JDK动态代理?

    是因为代理对象是由JDK动态生成的,而不像静态代理方式写死代理对象和被代理类,不灵活。

    JDK动态代理基于拦截器和反射来实现

    使用条件

    1)必须实现InvocationHandler接口;

    2)使用Proxy.newProxyInstance产生代理对象;

    3)被代理的对象必须要实现接口;

    源码分析

    参考博客:https://blog.csdn.net/yhl_jxy/article/details/80586785

    Cglib动态代理

    1. 引入相关依赖
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
    
    1. 代理类实现MethodInterceptor接口,实现intercept方法

    2. 创建代理对象

    //返回一个代理对象:  是 target 对象的代理对象
    public Object getProxyInstance() {
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }
    

    4.客户端调用

    注意事项

    • 在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException:
    • 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

    示例

    汽车制造厂制造汽车,代理商代理准备材料和售卖汽车

    /**
     * 功能描述:被代理者 汽车制造工厂
     **/
    public class CarFactory {
    
        public void productCar(){
            System.out.println("制造汽车");
        }
    
    }
    
    /**
     * 功能描述:代理类
     **/
    public class ProxyFactory implements MethodInterceptor {
    
        //维护一个目标对象
        private Object target;
    
        //构造器,传入一个被代理的对象
        public ProxyFactory(Object target) {
            this.target = target;
        }
    
        //返回一个代理对象:  是 target 对象的代理对象
        public Object getProxyInstance() {
            //1. 创建一个工具类
            Enhancer enhancer = new Enhancer();
            //2. 设置父类
            enhancer.setSuperclass(target.getClass());
            //3. 设置回调函数
            enhancer.setCallback(this);
            //4. 创建子类对象,即代理对象
            return enhancer.create();
        }
    
        //重写  intercept 方法,会调用目标对象的方法
        @Override
        public Object intercept ( Object o, Method method, Object[] objects, MethodProxy methodProxy ) throws Throwable {
            System.out.println("---代理商准备材料");
            Object returnVal = method.invoke(target,objects);
            System.out.println("---代理商售卖汽车");
            return returnVal;
        }
    
    }
    
    /**
     * 功能描述:客户端调用
     *
     **/
    public class Client {
    
        public static void main ( String[] args ) {
            //获取代理对象,并强转成被代理对象的数据类型
            CarFactory proxyInstance = (CarFactory) new ProxyFactory(new CarFactory()).getProxyInstance();
            //执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用
            proxyInstance.productCar();
        }
    
    }
    

    结果

    ---代理商准备材料
    制造汽车
    ---代理商售卖汽车
    

    Cglib动态代理实现原理

    可以在运行期扩展Java类与实现Java接口。

    现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口

    参考博客:Cglib动态代理实现原理

    MethodInterceptor接口源码

    package net.sf.cglib.proxy;
    
    import java.lang.reflect.Method;
    
    public interface MethodInterceptor extends Callback {
        Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
    }
    

    MethodInterceptor接口只有一个intercept()方法,这个方法有4个参数:

    1)Object表示增强的对象,即实现这个接口类的一个对象;

    2)Method表示要被拦截的方法;

    3)Object[]表示要被拦截方法的参数;

    4)MethodProxy表示要触发父类的方法对象;

    JDK动态代理VSCglib动态代理

    1.从实现方式上说

    JDK动态代理利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

    CGLIB动态代理利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

    2.何时使用JDK或者CGLIB?

    1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。

    2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP。

    3)如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

    JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承。

    但是针对接口编程的环境下推荐使用JDK的代理;

    3. JDK动态代理和CGLIB字节码生成的区别?

    1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

    2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。

    4.CGlib比JDK快?

    1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,

    总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

    5.Spring如何选择用JDK还是CGLIB?

    1)当Bean实现接口时,Spring就会用JDK的动态代理。

    2)当Bean没有实现接口时,Spring使用CGlib是实现。

    3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。

    动态代理与AOP

    图解

    在这里插入图片描述

    展开全文
  • java深入理解动态绑定

    千次阅读 2017-02-26 15:38:16
    在一开始接触多态这个词的时候,我们或许会因为这个词本身而感到困惑,如果我们把多态改称作“动态绑定”,相信很多人就能理解他的深层含义。通常的,我们把动态绑定也叫做后期绑定,运行时绑定。(一)方法调用绑定...

    在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特性。多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。在一开始接触多态这个词的时候,我们或许会因为这个词本身而感到困惑,如果我们把多态改称作“动态绑定”,相信很多人就能理解他的深层含义。通常的,我们把动态绑定也叫做后期绑定,运行时绑定。

    (一)方法调用绑定

    1.绑定概念

    通常,我们将一个方法调用同一个方法主体关联起来称作绑定。如果在程序执行前进行绑定,我们将这种绑定方法称作前期绑定。在面向过程语言中,比如c,这种方法是默认的也是唯一的。如果我们在java中采用前期绑定,很有可能编译器会因为在这庞大的继承实现体系中去绑定哪个方法而感到迷惑。解决的办法就是动态绑定,这种后期绑定的方法,在运行的时候根据对象的类型进行绑定。

    在java中,动态绑定是默认的行为。但是在类中,普通的方法会采用这种动态绑定的方法,也有一些情况并不会自然的发生动态绑定。

    2.final修饰

    如果一个属性被final修饰,则含义是:在初始化之后不能被更改。
    如果一个方法被final修饰,含义则是不能被覆盖。我们常常喜欢从宏观的角度这样说,但是我们真正的被final修饰的方法为什么不能被覆盖呢?因为final修饰词其实实际上关闭了动态绑定。在java中被final修饰的内容不能采用动态绑定的方法,不能动态绑定就没有多态的概念,自然也就不能被覆盖。

    3.“覆盖”私有方法

    其实我们很少把方法设定为私有。如果我们将private方法“覆盖”掉,其实我们获得的只是一个新的方法。完全和父类没关系了。这一点要注意,或许面试的时候会被问到:在子类中“覆盖”父类私有方法是被允许而不报错的,只不过完全是两个没关系的方法罢了。

    4.域与静态方法

    当我们了解了多态性之后可能会认为所有的事物都是可以多态地发生。其实并不是,如果我们直接访问某个域,这个访问会在编译期进行解析,我们可以参考下面的例子:

    package Polymorphic;
    
    /**
     * 
     * @author QuinnNorris
     * 域不具有多态性
     */
    public class polymorphics {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Super sup = new Sub();
            System.out.println("sup.field = " + sup.field + ", sup.getField() = "
                    + sup.getField());
            Sub sub = new Sub();
            System.out.println("sub.field = " + sub.field + ", sub.getField() = "
                    + sub.getField() + ", sub.getSuperField() = "
                    + sub.getSuperField());
        }
    
    }
    
    class Super {
        public int field = 0;
    
        public int getField() {
            return field;
        }
    }
    
    class Sub extends Super {
        public int field = 1;
    
        public int getField() {
            return field;
        }
    
        public int getSuperField() {
            return super.field;
        }
    }

    输出结果:
    sup.field = 0, sup.getField() = 1
    sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0

    这个例子告诉我们,当我们调用一个方法时,去选择执行哪个方法的主体是运行时动态选择的。但是当我们直接访问实例域的时候,编译器直接按照这个对象所表示的类型来访问。于此情况完全相同的还有静态方法。所以我们可以做出这种总结:

    1. 普通方法:根据对象实体的类型动态绑定
    2. 域和静态方法:根据对象所表现的类型前期绑定

    通俗地讲,普通的方法我们看new后面的是什么类型;域和静态方法我们看=前面声明的是什么类型。
    尽管这看来好像是一个非常容易让人混悬哦的问题。但是在实践中,实际上从来(或者说很少)不会发生。首先,那些不把实例域设置为private的程序员基本上已经全都被炒鱿鱼了(实例域很少被修饰成public)。其次我们很少会将自己在子类中创建的域设置成和父类一样的名字。

    (二)构造器与多态

    通常,构造器是一个很独特的存在。涉及到多态的时候也是如此。尽管构造器并不具有多态性(实际上他们是有static来修饰的,尽管该static是被隐式声明的),但是我们还是有必要理解一下构造器的工作原理。

    1.构造器的调用顺序

    父类的构造器总是在子类构造器调用的过程中被调用,而且按照继承层次逐渐向上的链接,以使每个父类的构造器都能被正确的调用。这样做是很有必要的,因为构造器有一项特殊的任务,检查对象是否被正确的构造。子类方法只能访问自己的成员,不能访问父类中的成员。只有基类的构造器才具有恰当的权限对自己的元素进行初始化。因此必须要让每个构造器都能得到调用,否则不能构造出正确的完整的对象。

    package Polymorphic;
    
    public class Father {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            new F();
        }
    
    }
    
    class A {
        A() {
            System.out.println("A");
        }
    }
    
    class B extends A {
        B() {
            System.out.println("B");
        }
    }
    
    class C extends B {
        C() {
            System.out.println("C");
        }
    }
    
    class D {
        D() {
            System.out.println("D");
        }
    }
    
    class E {
        E() {
            System.out.println("E");
        }
    }
    
    class F extends C {
        private D d = new D();
        private E e = new E();
    
        F() {
            System.out.println("F");
        }
    }
    

    输出结果:
    A
    B
    C
    D
    E
    F

    看似偶然的“ABCDEF”的输出结果,实际上是我们精心安排的。
    这个例子非常直观的说明了构造器的调用法则,有以下三个步骤:

    1. 调用父类构造器。这个步骤会反复递归进去,直到最祖先的类,依次向下调用构造器。
    2. 按声明顺序调用成员的初始化构造器方法。
    3. 调用子类构造器的主体。

    可能我说了这个顺序,大家马上就会想到super。是的没错,super()确实可以显示的调用父类中自己想要调用的构造方法,但是super()必须放在构造器的第一行,这个是规定。我们的顺序是没有任何问题的,或者说其实在F的构造器中第一句是super()。只不过我们默认省略了。

    (三)协变返回类型特性

    java在se5中添加了协变返回类型,它表示在子类中的被覆盖方法可以返回父类这个方法的返回类型的某种子类

    package Polymorphic;
    
    /**
     * 
     * @author QuinnNorris
     * 协变返回类型
     */
    public class covariant {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            A b = new B();
            b.getC().print();
    
            A a = new A();
            a.getC().print();
        }
    
    }
    
    class A{
        public C getC() {
            return new C();
        }
    }
    
    class B extends A{
        public D getC(){
            return new D();
        }
    }
    
    class C{
        public void print(){
            System.out.println("C");
        }
    }
    
    class D extends C{
        public void print(){
            System.out.println("D");
        }
    }
    
    

    输出结果:
    D
    C

    在上面的例子中,D类是继承于C类的,B类是继承于A类的,所以在B类覆盖的getC方法中,可以将返回类型协变成,C类的某个子类(D类)的类型。

    (四)继承设计

    通常,继承并不是我们的首选,能用组合的方法尽量用组合,这种手段更灵活,如果你的代码中is-a和is-like-a过多,你就应该考虑考虑是不是该换成has-a一些了。一条通用的准则是:用继承表达行为间的差异,并用字段表达状态上的变化

    而且在用继承的时候,我们会经常涉及到向上转型和向下转型。在java中,所有的转型都会得到检查。即使我们只是进行一次普通的加括号的类型转换,在进入运行期时仍然会对其进行检查,以便保证它的确是我们希望的那种类型。如果不是,就返回一个ClassCastException(类转型异常)。这种在运行期间对类型进行检查的行为称作”运行时类型识别(RTTI)“。

    展开全文
  • 我尽量用通俗的语言来解释我对动态代理技术的理解动态代理技术动态代理涉及到了两种技术:1,反射机制;2,代理机制。这两种技术的详细解释请自行谷歌或百度。 有几篇文章大家可以看看: ...

    我尽量用通俗的语言来解释我对动态代理技术的理解

    动态代理技术

    动态代理涉及到了两种技术:1,反射机制;2,代理机制。这两种技术的详细解释请自行谷歌或百度。
    有几篇文章大家可以看看:
    http://m.blog.csdn.net/hejingyuan6/article/details/36203505
    http://www.cnblogs.com/xiaoluo501395377/p/3383130.html

    动态代理类 ————>client
    委托类(即真实类)——>server
    接口———————>指动态代理需要调用的服务
    代理类和委托类都必须实现相同的接口,因为代理真正调用的还是委托类的方法。

    client(动态代理类)先动态生成一个对象,表示一个client对象。然后这个client对象调用接口的方法A,方法A一般就是我们需要的服务,然后通过某种机制,方法A的真正调用会发生在server端,server将调用完方法A后,会得到相应的结果,将结果返回给client。那么client和server的一次交互就完成了。整个过程下来,client端申请调用,而server端真正执行调用,最后server端将调用产生的结果返回给client。这样有什么好处呢?客户端一般就是申请服务,如计算服务,而真正的计算的过程会交由服务端进行(理论上服务端的计算资源可以无限大——如集群模式)。典型的hadoop的底层通信框架RPC原理就是如此。

    我们先通过一个例子来讲动态代理的具体实现,然后再讲到RPC协议。
    例子摘抄于“深入理解java虚拟机——JVM高级特性与最佳实践(第2版)”的9.2.3节,讲的非常透彻。如下:

    这里写图片描述

    我们来分解这个例子,看它是怎么实现动态代理的。它首先做了这么几件事
    1,定义公共的接口IHello,它里面的方法sayHello()就是client将来要调用的服务。
    2,定义server端的类,即Hello,它实现了公共接口IHello,并实现了里面的sayHello方法,将来会通过server端类Hello的对象来调用sayHello()方法;
    3,DynamicProxy。注意,它并不是动态代理对象,切勿混淆。它最重要的是实现了InvocationHandler的接口。我们待会来说DynamicProxy的作用;
    4,Proxy.newProxyInstance( )。真正生成动态代理对象,也就是前面提到的client端的对象。这个方法是通过反射机制,动态生成代理对象,接下来会讲这个动态代理对象所对应的类是$Proxy0。

    再看反编译生成的代码,它主要讲的是生成client的动态代理对象。我们看看newProxyInstance()这一步到底发生了什么:
    1,在JVM中生成代理类¥Proxy0,然后会根据这个¥Proxy0对象动态生成一个对象,即动态代理对象。
    2,¥Proxy0代表的是client端。它实现了两个最主要的功能:其一,在static块中,该代理对象为传入接口的每一个方法及Object类的方法都通过使用反射技术,生成了Method对象,如sayHello()方法的Method对象是m3。其二,实现了公共接口IHello,并实现了里面的sayHello方法,即client与server都实现了公共的接口。由于¥Proxy0保存着DynamicProxy对象的引用(newProxyInstance的第三个参数),这样在main函数中调用时,即hello.sayHello(),会跳转到DynaicProxy的invoke方法执行;
    3,解释一下this.h.invoke(this,m3,null)这句代码,”this.h”就是父类Proxy中保存的InvocationHandler实例变量,即newProxyInstance的传进来的第三个参数,而第一个参数this指的是动态代理对象¥Proxy0。
    4,执行完第3步,就会跳转到DynamicProxy的invoke方法中,这时发生真正的调用method.invoke(originalObj,arg)。这里的originalObj就是指Server端的实例对象。可以看到最终落脚点是实例对象originalObj发生的调用。这是理解的核心点,一定要牢牢把握!!!

    其实关键是要抓住三个对象及他们之间的关系:client端的¥Proxy0,server端的orignalObj,以及(实现了InvocationHandler方法的)DynamicProxy对象。他们的关系是,首先server端的orignalObj绑定到DynamicProxy对象上,或者说DynamicProxy对象保存着orignalObj的引用;然后¥Proxy0的对象又保存着DynamicProxy的引用。因此,在发生方法调用的时候,跳转的顺序是从¥Proxy0 ——>DynamicProxy——>orignalObj。
    过程如下:
    hello.sayHello()==¥Proxy0Obj.sayHello()———————————————>
    this.h.invoke(this,m3,null)== InvocationHandler.invoke(¥Proxy,m3,null)——>
    method.invoke(orignalObj, args)。
    这样就实现了在client请求的服务(方法调用),实际却是通过server端的对象方法调用完成的。

    我还想谈谈我对DynamicProxy的理解,它存在的意义到底是什么?
    先给出我的理解,DynamicProxy这个类的作用是起到解耦的作用,使client不用关心server的对象长什么样子,server也不用关心client是什么。接下来进行简要的分析。
    如果没有DynamicProxy,那么对于newProxyInstance,传的第三个参数必然是服务端的对象orignalObj ,因为我们最终是要实现通过server端来实现真正的接口服务。比如对于sayHello()调用,那可能会变成:
    有DynamicProxy时,是:

    sayHello()
    {
        this.h.invoke(this, m3, args);
    }

    等价于

    sayHello()
    {
        dynamicProxyObj.invoke($Proxy0, m3, args);
    }

    没有DynamicProxy时,是:

    SayHello()
    {
        m3.(orignalObj, args);
    }

    发现,没有DynamicProxy也能实现client发起接口服务的申请,server端实现真正调用。但是这就让client完全依赖于server端的对象了,没有server对象,client对象就没法存在,耦合性太强。而有了DynamicProxy,两者就完全解耦了。所以说DynamicProxy的定位就是给client和server端解耦的。

    RPC

    那RPC是怎么回事呢?我尝试谈一下我对它的理解,可能不一定准确,欢迎大家拍砖。
    首先说RPC的协议。RPC的协议有很多种,如TaskUmbilicalProtocol,JobSubmissionProtocol等等。每个协议都是一系列的方法的组合,实际上就相当于前面例子中的sayHello()方法。client和server都需要实现这些协议中的这些方法。
    RPC的主要技术包括前面讲过的的动态代理机制、NIO技术、网络编程技术等。我们这里只关注RPC的动态代理机制的实现逻辑,其他不讲。
    RPC的动态代理机制与前面讲的有什么区别呢?有,最大的区别是前面所述的client和server都在同一个地址空间,invoke的调用发生在本地。而RPC的client和server是隔离的,两者并不在同一机器上。
    我们再来回顾一遍动态代理的逻辑:client对象要绑定一个server对象,client发起一个方法调用(或称为服务调用)后,server会执行真正的方法体,在server端得到结果后将其返回给client端,中间会插一个DynamicProxy来给两者解耦。这样一次调用完成。
    我们会发现,最重要的是绑定的这个动作。即动态生成的这个client对象要找到一个server对象,这个对象必须跟clinet的对象实现了相同的接口,然后绑定它。
    那么具体怎么实现呢?我们能想到的就是在client端指定server对象的地址,但此时client是不可能知道server端的地址的。实际的解决方案是,client端在invoke方法中,将函数的调用信息(函数名,参数列表等)打包成可以序列化的Invocation对象,指定server端的socket地址,然后申请建立socket连接。建立好连接后,会将Invocation对象通过网络发送给server端。对于server端,它会将本地创建的server对象绑定到client端过来的请求上,绑定完之后,就可以通过java的反射机制完成函数调用。重点强调一下可能的理解误区,从client发送出去的Invocation对象只是函数的调用信息(函数名,参数列表),不是client的对象,真正的client动态对象是在server端通过反射生成的。

    下面server端的动作是我自己猜测的,还没有看相关源码,大家看看就好。
    发生RPC调用时,结合NIO的模型,我们知道Reader线程是用来处理和client的数据交换的。因此,要满足一个client对应一个server端的对象,我猜测可能的一种实现方式如下,即每个ReaderTheader都会有自己的server对象,模型大体上长这个样子:
    这里写图片描述

    当然这种模型也有问题,就是serverObj对象太多,每创建一个对象都是要消耗资源的。因此改进的方式是serverObj搞成静态的,一个clientObj对应一个serverObj。如下:
    这里写图片描述

    这样clientObj在申请方法调用的时候,只需要指明要调用服务端的哪个方法即可。如果申请的调用太多,就将其塞到一个队列中,然后server端再进行后续的消费。实质上这后续的动作就属于NIO方面的知识。这里就不展开讲了,hadoop源码还没细看,这里就当瞎猜了

    就到这里吧,以后应该还会继续补充细节的。。。

    展开全文
  • 深入理解动态规划DP

    万次阅读 2016-04-16 12:38:22
    通过最近对于一些算法题的思考,越来越发现动态规划方法的在时间上高效性,往往该问题可以轻松的找到暴力破解的方法,其时间复杂度却不尽人意。下面来看看几个常见的动态规划思路的经典问题例一.有一段楼梯有10级...
  • 彻底理解java动态代理

    万次阅读 2017-07-20 10:07:34
    彻底理解JAVA动态代理 代理设计模式 定义:为其他对象提供一种代理以控制对这个对象的访问。 代理模式的结构如下图所示。 动态代理使用 java动态代理机制以巧妙的方式实现了代理模式的设计理念。 代理模式...
  • 关于如何配置动态资源分配,参见:http://spark.apache.org/docs/1.6.3/job-scheduling.html#dynamic-resource-allocation cloudera manager中的默认配置时开启了spark 动态资源分配的,也就是spark.
  • 通俗理解动态库与静态库区别

    万次阅读 多人点赞 2019-06-21 15:27:46
    引:最近做了算法产品化相关的一些工作,其中涉及到算法库封装的相关工作,封装为动态库。总结动态库和静态库区别和对应使用原则。 区别:静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。 ...
  • 动态规划】关于转移方程的简单理解

    千次阅读 多人点赞 2018-08-03 14:59:36
    什么是动态规划,我们要如何描述它? 动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度,因此它比回溯法、暴力法等要快...
  • //c为局部变量,具有动态生存期,每次进入函数时都被初始化  a=a+2;  i=i+32;  c=c+5;  cout;  cout;  b=a; } int main() {  static int a; //a为静态...
  • spring-cloud动态路由“动态”的理解

    千次阅读 2019-08-27 12:24:46
    spring-cloud动态路由“动态”的理解动态可以通过硬编码来配置路由读取yml文件配置路由动态动态路由接口RouteDefinitionRepository实现RouteDefinitionRepository接口自定义路由配置规则采用数据库+redis配置路由...
  • 看动画轻松理解「递归」与「动态规划」

    千次阅读 多人点赞 2019-11-13 15:53:22
    在学习「数据结构和算法」的过程中,因为人习惯了平铺直叙的思维方式,所以「递归」与「动态规划」这种带循环概念(绕来绕去)的往往是相对比较难以理解的两个抽象知识点。 程序员小吴打算使用动画的形式来帮助理解...
  • >>>> Person.sex = None #给类Person添加一个属性 >>> P1 = Person("xiaoke", "25") >... print(P1.sex) #如果P1这个实例对象中没有sex属性的话,那么就会访问它的类属性 ...
  • 简单理解动态内存分配和静态内存分配的区别

    千次阅读 多人点赞 2015-06-22 13:38:22
    在涉及到内存分配时,我们一般都要考虑到两种内存分配方式,一种是动态内存分配,另一种是静态内存分配,我们该怎么理解这两者的区别呢? 在我看来,静态内存分配和动态内存分配比较典型的例子就是数组和链表,数组...
  • 电子系统动态范围的理解

    千次阅读 2017-02-23 15:44:20
    为了充分理解系统中接收器或射频/微波元件的动态范围极限,首先需要理解组成动态范围的各个要素。  动态范围一般用分贝(dB)表示,是一个电路、元件或系统可以处理的最大信号电平(相对于1mW功率的dBm值)与...
  • 动态规划算法的个人理解

    千次阅读 2017-01-17 09:48:08
    我们知道,问题可以采用动态规划算法进行解决的一个重要性质即是该问题必须具备最优子结构性质,所谓的最优子结构性质用大白话说就是...多段图最短路径问题是理解动态规划算法的一个较为容易的问题。该问题的一个实例
  • Spring动态代理机制理解

    万次阅读 2018-03-17 16:22:30
    另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 879,060
精华内容 351,624
关键字:

关于理解的动态