override java final methods via reflection or other means?

前端 未结 6 1639
醉梦人生
醉梦人生 2020-12-15 09:28

This question arise while trying to write test cases. Foo is a class within the framework library which I dont have source access to.

public class Foo{
  pub         


        
相关标签:
6条回答
  • 2020-12-15 09:40

    As this was one of the top results for "override final method java" in google. I thought I would leave my solution. This class shows a simple solution using the example "Bagel" class and a free to use javassist library:

    /**
     * This class shows how you can override a final method of a super class using the Javassist's bytecode toolkit
     * The library can be found here: http://jboss-javassist.github.io/javassist/
     * 
     * The basic idea is that you get the super class and reset the modifiers so the modifiers of the method don't include final.
     * Then you add in a new method to the sub class which overrides the now non final method of the super class.
     * 
     * The only "catch" is you have to do the class manipulation before any calls to the class happen in your code. So put the
     * manipulation as early in your code as you can otherwise you will get exceptions.
     */
    
    package packagename;
    
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;
    import javassist.CtNewMethod;
    import javassist.Modifier;
    
    /** 
     * A simple class to show how to use the library
     */
    public class TestCt {
    
        /** 
         * The starting point for the application
         */
        public static void main(String[] args) {
    
            // in order for us to override the final method we must manipulate the class using the Javassist library.
            // we need to do this FIRST because once we initialize the class it will no longer be editable.
            try
            {
                // get the super class
                CtClass bagel = ClassPool.getDefault().get("packagename.TestCt$Bagel");
    
                // get the method you want to override
                CtMethod originalMethod = bagel.getDeclaredMethod("getDescription");
    
                // set the modifier. This will remove the 'final' modifier from the method.
                // If for whatever reason you needed more than one modifier just add them together
                originalMethod.setModifiers(Modifier.PUBLIC);
    
                // save the changes to the super class
                bagel.toClass();
    
                // get the subclass
                CtClass bagelsolver = ClassPool.getDefault().get("packagename.TestCt$BagelWithOptions");
    
                // create the method that will override the super class's method and include the options in the output
                CtMethod overrideMethod = CtNewMethod.make("public String getDescription() { return super.getDescription() + \" with \" + getOptions(); }", bagelsolver);
    
                // add the new method to the sub class
                bagelsolver.addMethod(overrideMethod);
    
                // save the changes to the sub class
                bagelsolver.toClass();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
    
            // now that we have edited the classes with the new methods, we can create an instance and see if it worked
    
            // create a new instance of BagelWithOptions
            BagelWithOptions myBagel = new BagelWithOptions();
    
            // give it some options
            myBagel.setOptions("cheese, bacon and eggs");
    
            // print the description of the bagel to the console.
            // This should now use our new code when calling getDescription() which will include the options in the output.
            System.out.println("My bagel is: " + myBagel.getDescription());
    
            // The output should be:
            // **My bagel is: a plain bagel with cheese, bacon and eggs**
        }
    
        /**
         * A plain bagel class which has a final method which we want to override
         */
        public static class Bagel {
    
            /**
             * return a description for this bagel
             */
            public final String getDescription() {
                return "a plain bagel";
            }
        }
    
        /**
         * A sub class of bagel which adds some extra options for the bagel.
         */
        public static class BagelWithOptions extends Bagel {
    
            /**
             * A string that will contain any extra options for the bagel
             */
            String  options;
    
            /**
             * Initiate the bagel with no extra options
             */
            public BagelWithOptions() {
                options = "nothing else";
            }
    
            /**
             * Set the options for the bagel
             * @param options - a string with the new options for this bagel
             */
            public void setOptions(String options) {
                this.options = options;
            }
    
            /**
             * return the current options for this bagel
             */
            public String getOptions() {
                return options;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-15 09:41

    you could create another method which you could override in your test:

    public class Bar extends Foo {
      protected Object doGetX() {
        return getX();
      }
      public int process(){
        Object value = doGetX();
        ...
      }
    }
    

    then, you could override doGetX in BarTest.

    0 讨论(0)
  • 2020-12-15 09:45

    If your unit test case can't create Foo due to other dependencies, that might be a sign that you're not making your unit test right in the first place.

    Unit tests are meant to test under the same circumstances a production code would run, so I'd suggest recreating the same production environment inside your tests. Otherwise, your tests wouldn't be complete.

    0 讨论(0)
  • 2020-12-15 09:48

    Seb is correct, and just to ensure that you get an answer to your question, short of doing something in native code (and I am pretty sure that would not work) or modifying the bytecode of the class at runtime, and creating the class that overrides the method at runtime, I cannot see a way to alter the "finalness" of a method. Reflection will not help you here.

    0 讨论(0)
  • 2020-12-15 09:50

    If the variable returned by getX() is not final you can use the technique explained in What’s the best way of unit testing private methods? for changing the value of the private variable through Reflection.

    0 讨论(0)
  • 2020-12-15 09:55
    public class Bar extends Foo{
      public int process(){
        Object value = getX();
        return process2(value);
      }
      public int process2(Object value){
      ...
      }
    }
    
    public class BarTest extends TestCase{
      public testProcess(){
        Bar bar = new Bar();   
        Mockobj mo = new Mockobj();     
        int result = bar.process2(mo);
        ...
      }
    }
    

    what i did eventually was the above. it is a bit ugly... James solution is definitely much better than this...

    0 讨论(0)
提交回复
热议问题