【问题标题】:Is there a common Java method to trim every string in an object graph?是否有一种通用的 Java 方法来修剪对象图中的每个字符串?
【发布时间】:2014-02-19 22:41:23
【问题描述】:

我希望修剪作为对象图一部分的所有字符串。

所以我有一个像这样的对象图

 RootElement
   - name (String)
   - adjective (String)
   - items ArrayOfItems
     - getItems (List<Item>)
       - get(i) (Item)
       Item
         - name (String)
         - value (double)
         - alias (String)
         - references ArrayOfReferences
           - getReferences (List<Reference>)
             - get(i) (Reference)
             Reference
               - prop1 (String)
               - prop2 (Integer)
               - prop3 (String)

此对象图中表示的每个类的每个属性都有一个 get 和 set 对。理想情况下,String 类型的每个字段最终都会被修剪,包括枚举集合中包含的任何子对象。对象图中不包含循环。

是否有任何实现某种通用对象图访问者模式的 java 库或执行此操作的 String\Reflection 实用程序库?

执行此操作的外部第三方库也可以,它不必是标准 java 库的一部分。

【问题讨论】:

  • 可以使用反射。如果你有很多类,并且不能以干净、修剪的方式构造对象,这是明智的。
  • 我意识到使用反射是可行的,但我希望有比实现我自己的反射搜索\访问者更简单的东西。
  • bean validation 可能为时已晚,而且带有@Trim 注释。

标签: java string reflection object-graph


【解决方案1】:

不,这样的事情没有内置的遍历,请记住,Java Strings 是不可变的,因此您实际上不能就地修剪——您必须修剪和替换。某些对象可能不允许修改其String 变量。

【讨论】:

    【解决方案2】:

    以下是我使用 Java Reflection API 构建的解决方案的说明。我已经在下面发布了工作代码(及其到 github 的 url)。本方案主要使用:

    1. Java 反射 API
    2. Java 集合的独立处理
    3. 递归

    首先,我使用Introspector 遍历ClassreadMethods,省略了为Object 定义的方法

    for (PropertyDescriptor propertyDescriptor : Introspector
                        .getBeanInfo(c, Object.class).getPropertyDescriptors()) {
                Method method = propertyDescriptor.getReadMethod();
    

    案例

    1. 如果Property 的当前级别是String 类型
    2. 如果它是 Object 属性数组
    3. 如果是String 数组
    4. 如果它是一种 Java Collection
    5. 单独放置 Map 并使用特殊条件处理其

    此实用程序使用 Java 反射 API 遍历具有 getterssetters 规范语法的对象图,并以递归方式修剪 Object 图中遇到的所有字符串。

    代码

    这个带有主要测试类(和自定义数据类型/pojos)的整个 util 类是here on my github

    用法:

    myObj = (MyObject) SpaceUtil.trimReflective(myObj);
    

    使用方法:

        public static Object trimReflective(Object object) throws Exception {
            if (object == null)
                return null;
    
            Class<? extends Object> c = object.getClass();
            try {
                // Introspector usage to pick the getters conveniently thereby
                // excluding the Object getters
                for (PropertyDescriptor propertyDescriptor : Introspector
                        .getBeanInfo(c, Object.class).getPropertyDescriptors()) {
                    Method method = propertyDescriptor.getReadMethod();
                    String name = method.getName();
    
                    // If the current level of Property is of type String
                    if (method.getReturnType().equals(String.class)) {
                        String property = (String) method.invoke(object);
                        if (property != null) {
                            Method setter = c.getMethod("set" + name.substring(3),
                                    new Class<?>[] { String.class });
                            if (setter != null)
                                // Setter to trim and set the trimmed String value
                                setter.invoke(object, property.trim());
                        }
                    }
    
                    // If an Object Array of Properties - added additional check to
                    // avoid getBytes returning a byte[] and process
                    if (method.getReturnType().isArray()
                            && !method.getReturnType().isPrimitive()
                            && !method.getReturnType().equals(String[].class)
                            && !method.getReturnType().equals(byte[].class)) {
                        System.out.println(method.getReturnType());
                        // Type check for primitive arrays (would fail typecasting
                        // in case of int[], char[] etc)
                        if (method.invoke(object) instanceof Object[]) {
                            Object[] objectArray = (Object[]) method.invoke(object);
                            if (objectArray != null) {
                                for (Object obj : (Object[]) objectArray) {
                                    // Recursively revisit with the current property
                                    trimReflective(obj);
                                }
                            }
                        }
                    }
                    // If a String array
                    if (method.getReturnType().equals(String[].class)) {
                        String[] propertyArray = (String[]) method.invoke(object);
                        if (propertyArray != null) {
                            Method setter = c.getMethod("set" + name.substring(3),
                                    new Class<?>[] { String[].class });
                            if (setter != null) {
                                String[] modifiedArray = new String[propertyArray.length];
                                for (int i = 0; i < propertyArray.length; i++)
                                    if (propertyArray[i] != null)
                                        modifiedArray[i] = propertyArray[i].trim();
    
                                // Explicit wrapping
                                setter.invoke(object,
                                        new Object[] { modifiedArray });
                            }
                        }
                    }
                    // Collections start
                    if (Collection.class.isAssignableFrom(method.getReturnType())) {
                        Collection collectionProperty = (Collection) method
                                .invoke(object);
                        if (collectionProperty != null) {
                            for (int index = 0; index < collectionProperty.size(); index++) {
                                if (collectionProperty.toArray()[index] instanceof String) {
                                    String element = (String) collectionProperty
                                            .toArray()[index];
    
                                    if (element != null) {
                                        // Check if List was created with
                                        // Arrays.asList (non-resizable Array)
                                        if (collectionProperty instanceof List) {
                                            ((List) collectionProperty).set(index,
                                                    element.trim());
                                        } else {
                                            collectionProperty.remove(element);
                                            collectionProperty.add(element.trim());
                                        }
                                    }
                                } else {
                                    // Recursively revisit with the current property
                                    trimReflective(collectionProperty.toArray()[index]);
                                }
                            }
                        }
                    }
                    // Separate placement for Map with special conditions to process
                    // keys and values
                    if (method.getReturnType().equals(Map.class)) {
                        Map mapProperty = (Map) method.invoke(object);
                        if (mapProperty != null) {
                            // Keys
                            for (int index = 0; index < mapProperty.keySet().size(); index++) {
                                if (mapProperty.keySet().toArray()[index] instanceof String) {
                                    String element = (String) mapProperty.keySet()
                                            .toArray()[index];
                                    if (element != null) {
                                        mapProperty.put(element.trim(),
                                                mapProperty.get(element));
                                        mapProperty.remove(element);
                                    }
                                } else {
                                    // Recursively revisit with the current property
                                    trimReflective(mapProperty.get(index));
                                }
    
                            }
                            // Values
                            for (Map.Entry entry : (Set<Map.Entry>) mapProperty
                                    .entrySet()) {
    
                                if (entry.getValue() instanceof String) {
                                    String element = (String) entry.getValue();
                                    if (element != null) {
                                        entry.setValue(element.trim());
                                    }
                                } else {
                                    // Recursively revisit with the current property
                                    trimReflective(entry.getValue());
                                }
                            }
                        }
                    } else {// Catch a custom data type as property and send through
                            // recursion
                        Object property = (Object) method.invoke(object);
                        if (property != null) {
                            trimReflective(property);
                        }
                    }
                }
    
            } catch (Exception e) {
                throw new Exception("Strings cannot be trimmed because: ", e);
            }
    
            return object;
    
        }
    

    测试

    我还有一个测试类,它创建了一个相对复杂的对象。测试类有不同的场景,涵盖:

    1. String属性
    2. 作为自定义数据类型的属性又具有String 属性
    3. 作为自定义数据类型的属性又具有作为自定义数据类型的属性,而自定义数据类型又具有String 属性
    4. List 的自定义数据类型
    5. SetStrings
    6. Array 的自定义数据类型
    7. ArrayStrings
    8. Map of String 和自定义数据类型

    对象图:

    测试对象代码片段:

    public static Music buildObj() {
            Song song1 = new Song();
            Song song2 = new Song();
            Song song3 = new Song();
    
        Artist artist1 = new Artist();
        Artist artist2 = new Artist();
    
        song1.setGenre("ROCK       ");
        song1.setSonnet("X    ");
        song1.setNotes("Y    ");
        song1.setCompostions(Arrays.asList(new String[] { "SOME X DATA  ",
                "SOME OTHER DATA X ", "SOME MORE DATA X    ", " " }));
    
        Set<String> instruments = new HashSet<String>();
        instruments.add("         GUITAR    ");
        instruments.add("         SITAR    ");
        instruments.add("         DRUMS    ");
        instruments.add("         BASS    ");
    
        song1.setInstruments(instruments);
    
        song2.setGenre("METAL       ");
        song2.setSonnet("A    ");
        song2.setNotes("B    ");
        song2.setCompostions(Arrays.asList(new String[] { "SOME Y DATA  ",
                "          SOME OTHER DATA Y ",
                "           SOME MORE DATA Y    ", " " }));
    
        song3.setGenre("POP       ");
        song3.setSonnet("DONT    ");
        song3.setNotes("KNOW     ");
        song3.setCompostions(Arrays.asList(new String[] { "SOME Z DATA  ",
                "               SOME OTHER DATA Z ",
                "          SOME MORE DATA Z   ", " " }));
    
        artist1.setSongList(Arrays.asList(new Song[] { song1, song3 }));
    
        artist2.setSongList(Arrays.asList(new Song[] { song1, song2, song3 }));
        Map<String, Person> artistMap = new HashMap<String, Person>();
        Person tutor1 = new Person();
        tutor1.setName("JOHN JACKSON DOE       ");
        artistMap.put("          Name                 ", tutor1);
    
        Person coach1 = new Person();
        coach1.setName("CARTER   ");
        artistMap.put("Coach      ", coach1);
        artist2.setTutor(artistMap);
    
        music.setSongs(Arrays.asList(new Song[] { song1, song2, song3 }));
        music.setArtists(Arrays.asList(new Artist[] { artist1, artist2 }));
    
        music.setLanguages(new String[] { "    ENGLISH    ", "FRENCH    ",
                "HINDI    " });
        Person singer1 = new Person();
        singer1.setName("DAVID      ");
    
        Person singer2 = new Person();
        singer2.setName("JACOB      ");
        music.setSingers(new Person[] { singer1, singer2 });
    
        Human man = new Human();
        Person p = new Person();
        p.setName("   JACK'S RAGING BULL   ");
        SomeGuy m = new SomeGuy();
        m.setPerson(p);
        man.setMan(m);
    
        music.setHuman(man);
    
        return music;
    }
    

    结果:

    #######BEFORE#######
    >>[>>DAVID      ---<<, >>JACOB      ---<<]---[    ENGLISH    , FRENCH    , HINDI    ]---[>>ROCK       ---X    ---Y    ---[SOME X DATA  , SOME OTHER DATA X , SOME MORE DATA X    ,  ]---[         SITAR    ,          GUITAR    ,          BASS    ,          DRUMS    ]<<, >>METAL       ---A    ---B    ---[SOME Y DATA  ,           SOME OTHER DATA Y ,            SOME MORE DATA Y    ,  ]---<<, >>POP       ---DONT    ---KNOW     ---[SOME Z DATA  ,                SOME OTHER DATA Z ,           SOME MORE DATA Z   ,  ]---<<]---[>>---[>>ROCK       ---X    ---Y    ---[SOME X DATA  , SOME OTHER DATA X , SOME MORE DATA X    ,  ]---[         SITAR    ,          GUITAR    ,          BASS    ,          DRUMS    ]<<, >>POP       ---DONT    ---KNOW     ---[SOME Z DATA  ,                SOME OTHER DATA Z ,           SOME MORE DATA Z   ,  ]---<<]<<, >>{Coach      =>>CARTER    ---<<,           Name                 =>>JOHN JACKSON DOE       ---<<}---[>>ROCK       ---X    ---Y    ---[SOME X DATA  , SOME OTHER DATA X , SOME MORE DATA X    ,  ]---[         SITAR    ,          GUITAR    ,          BASS    ,          DRUMS    ]<<, >>METAL       ---A    ---B    ---[SOME Y DATA  ,           SOME OTHER DATA Y ,            SOME MORE DATA Y    ,  ]---<<, >>POP       ---DONT    ---KNOW     ---[SOME Z DATA  ,                SOME OTHER DATA Z ,           SOME MORE DATA Z   ,  ]---<<]<<]---=>   JACK'S RAGING BULL   <=<<
    Number of spaces : 644
    #######AFTER#######
    >>[>>DAVID---<<, >>JACOB---<<]---[ENGLISH, FRENCH, HINDI]---[>>ROCK---X---Y---[SOME X DATA, SOME OTHER DATA X, SOME MORE DATA X, ]---[GUITAR, SITAR, DRUMS, BASS]<<, >>METAL---A---B---[SOME Y DATA, SOME OTHER DATA Y, SOME MORE DATA Y, ]---<<, >>POP---DONT---KNOW---[SOME Z DATA, SOME OTHER DATA Z, SOME MORE DATA Z, ]---<<]---[>>---[>>ROCK---X---Y---[SOME X DATA, SOME OTHER DATA X, SOME MORE DATA X, ]---[GUITAR, SITAR, DRUMS, BASS]<<, >>POP---DONT---KNOW---[SOME Z DATA, SOME OTHER DATA Z, SOME MORE DATA Z, ]---<<]<<, >>{Name=>>JOHN JACKSON DOE---<<, Coach=>>CARTER---<<}---[>>ROCK---X---Y---[SOME X DATA, SOME OTHER DATA X, SOME MORE DATA X, ]---[GUITAR, SITAR, DRUMS, BASS]<<, >>METAL---A---B---[SOME Y DATA, SOME OTHER DATA Y, SOME MORE DATA Y, ]---<<, >>POP---DONT---KNOW---[SOME Z DATA, SOME OTHER DATA Z, SOME MORE DATA Z, ]---<<]<<]---=>JACK'S RAGING BULL<=<<
    Number of spaces : 111
    

    上述修剪输出中的空格数计数非零,因为我没有努力覆盖任何集合的toStringList、@987654353 @) 或Map。我想要对代码进行某些改进,但对于您的情况,解决方案应该可以正常工作。

    限制(进一步改进)

    1. 无法处理不规范的属性语法(无效的 getter/setter)
    2. 无法处理链式集合:例如,List&lt;List&lt;Person&gt;&gt; - 因为独家支持严格的 getter/setter 约定
    3. 不支持 Guava 集合库

    【讨论】:

      【解决方案3】:

      在@SwissArmyKnife 的基础上,我将他简单的字符串修剪功能转换为具有默认方法的界面。所以任何你想使用 object.trim() 的对象,你只需要添加“implements Trimmable”。

      简单的字符串修剪接口:Trimmable.class

      /**
       * Utility interface that trims all String fields of the implementing class.
       */
      public interface Trimmable {
      
          /**
           * Trim all Strings
           */
          default void trim(){
              for (Field field : this.getClass().getDeclaredFields()) {
                  try {
                      field.setAccessible(true);
                      Object value = field.get(this);
                      if (value != null){
                          if (value instanceof String){
                              String trimmed = (String) value;
                              field.set(this, trimmed.trim());
                          }
                      }
                  } catch(Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      

      我们希望可修剪的对象:Person.class(实现 Trimmable 接口)

      public class Person implements Trimmable {
           private String firstName;
           private String lastName;
           private int age;
      
           // getters/setters omitted
      }
      

      现在你可以使用 person.trim()

      Person person = new Person();
      person.setFirstName("    John   ");
      person.setLastName("  Doe");
      person.setAge(30);
      person.trim();
      

      【讨论】:

        【解决方案4】:

        我用反射 API 做了一个简单的方法来修剪字符串值。

        public Object trimStringValues(Object model){
          for(Field field : model.getClass().getDeclaredFields()){
                try{
                field.setAccessible(true);
                Object value = field.get(model);
                String fieldName = field.getName();
                 if(value != null){
                    if(value instanceof String){
                        String trimmed = (String) value;
                        field.set(model, trimmed.trim());
                        }
        
                    }
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                        }
        }
        

        我还没有遇到任何问题。我知道它是一个旧线程,但它可能会帮助一些正在寻找简单事物的人。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-09
          • 1970-01-01
          • 2013-12-15
          • 1970-01-01
          • 2020-11-26
          相关资源
          最近更新 更多