Java 8中字段名称的硬链接

时间:2014-12-04 17:28:43

标签: java java-8

例如,我有一些实体 - 产品

 public class Product {
      ...
      private String name;
      private int count;
      private Product associatedProduct;
      ...
      // GETTERS & SETTERS

 }

我还有产品查找器,可以通过过滤器找到产品:

 public interface Finder<T> {

      Set<T> find(Filter... filters);

 }

现在我可以执行以下代码:

      Finder<Product> finder = ...;
      // find all products with name 'cucumber'
      Set<Product> finder.find(Filter.equals("name", "cucumber"));

我们不喜欢这段代码,因为我应该拥有&#39; soft&#39;链接到字段名称&#34;名称&#34;如果印刷错误或任何其他错误,我不能有编译时异常。

因此我创建了代码生成器,它生成了属性的静态链接。

生成的类看起来像:

      public final class $Product {
                private final String context;
                // some factory is used to instance creation 
                $PostEntity() {this.context = "";}
                $PostEntity(String context) {this.context = context;}
                public String name() { return context + "name";}
                public String count() { return context + "count";}
                public String associatedProduct() { return context + "associatedProduct";}
                public $Product associatedProductDot() { return new $Product( this.context + "associatedProduct.");}

      }

现在我可以做到以下几点:

      Set<Product> finder.find(Filter.equals(Links.PRODUCT.name() , "cucumber"));
      //or
      Set<Product> finder.find(Filter.equals(Links.PRODUCT.associatedProductDot().name() , "cucumber"));

它像魅力一样,我很开心。

我知道使用代理对象的替代方法,但它会在运行时增加额外的开销,并在代码中添加一些神奇的时刻,所以这个变体不适合我。

最后我的问题是:

使用java 8实现此功能有一种更优雅的方法吗?

3 个答案:

答案 0 :(得分:1)

Java 8拥有您需要的一切:

public static <C,P> Predicate<C> byProperty(Function<C,P> f, P value) {
    return component->Objects.equals(f.apply(component), value);
}
public static <C> Set<C> find(Collection<? extends C> c, Predicate<? super C> p) {
    return c.stream().filter(p).collect(Collectors.<C>toSet());
}

用于过滤的标准interface称为Predicate,上面的第一个方法允许您创建用于匹配组件类型Predicate的属性的任意C。第二种方法显示了如何使用Stream API从Set中获取Collection个匹配组件。然后你可以像这样使用它:

List<Product> list;

...

Set<Product> set=find(list, byProperty(Product::getName, "foo"));

Set<Product> set=find(list, byProperty(Product::getCount, 42));

请注意,这是类型安全的,并且包含compile-time checked references (your “hard links”)到您的媒体资源。与您要求的唯一区别在于它们引用的是getter方法而不是字段名称,因为a)不支持字段引用,b)您的字段无论如何都是private


请注意,您可以通过另一个允许提供值谓词而不是常量的工厂来扩充这些方法:

public static <C,P> Predicate<C> matchProp(
                                 Function<C,P> f, Predicate<? super P> value) {
    return component->value.test(f.apply(component));
}

这允许使用例如:

    Set<Product> set=find(list, matchProp(Product::getCount, count -> count>100));

Lambda Expressions

    Set<Product> set=find(list, matchProp(Product::getName, String::isEmpty));

答案 1 :(得分:0)

最快的事情是提供您自己的Filter接口实现。由于我不知道你的Filter界面,我不得不假设它的样子。这是我的假设:

public interface Filter<T> {
    boolean matches(T t);
}

顺便说一句,我认为界面Finder应该是这样的:

public interface Finder<T> {
    Set<T> find(Filter<? super T>... filters);
}

所以,你可以有这样一个类:

public final class ProductFilters {
    private ProductFilters() { /* Utility class */ }
    public static Filter<Product> byName(final String name) {
        return new Filter() {
             public boolean matches(Product t) {
                 return name.equals(t.getName());
             }
        }
    }
}

你甚至可以把它放在产品类中,这可以使它更好一些:

public class Product {
     private String name;
     public static final class Filters {
         private Filters() { /* Utility Class */ }
         public static Filter<Product> byName(final String name) {
             return new Filter() {
                  public boolean matches(final Product t) {
                      return name.equals(t.name);
                  }
             };
         }
     }
}

是的,Java 8使这个东西更好,显式匿名类可以在语法上被lambda替换,如下所示:

public class Product {
    private String name;
    public static final class Filters {
        private Filters() { /* Utility Class */ }
        public static Filter<Product> byName(final String name) {
            return t -> name.equals(t.name);
        }
    }
}

使用过滤器的代码现在看起来像这样:

Set<Product> cucumbers = finder.find(Product.Filters.byName("cucumber"));

Filter<T>接口存在于包含java.util.function的Java 8中。它的名字有Predicate<T>,其基本部分如下:

public interface Predicate<T> {
    boolean test(T t);
}

如果要过滤的产品可以直接或StreamCollection的形式提供,您可以使用新的java.util.stream API进行过滤。对于该示例,我假设要过滤的产品也在Set中。过滤产品的代码可能如下所示:

Set<Product> potentialCucumbers = ...;
// Inline lambda:
Set<Product> cucumbers = potentialCucumbers.stream().filter(p -> "cucumber".equals(p.getName())).collect(Collectors.toSet());
// Stored lambda as above:
Set<Product> cucumbers = potentialCucumbers.stream().filter(Product.Filters.byName("cucumber")).collect(Collectors.toSet());

我非常喜欢静态导入,因为它们可以显着缩短行长。使用静态导入时,它看起来像这样:

Set<Product> potentialCucumbers = ...;
// Inline lambda:
Set<Product> cucumbers = potentialCucumbers.stream().filter(p -> "cucumber".equals(p.getName())).collect(toSet());
// Stored lambda as above:
Set<Product> cucumbers = potentialCucumbers.stream().filter(byName("cucumber")).collect(toSet());

答案 2 :(得分:0)

我的建议是使用谓词而不是Filter类。它们使代码更清晰。我还建议将常用属性(如“name”或“owner”)添加到提供可搜索谓词的接口中。例如,对于“name”和“owner”属性,您可能有两个名为“Named”和“Owned”的接口:

public interface Named {
    public String getName();

    public void setName(String name);

    static <T extends Named> Predicate<T> nameEquals(Class<T> clazz, String s){
        return ((p) -> { 
            if (s == null){
                return p.getName() == null;
            }
            return s.equals(p.getName());
        }); 
    }
}

public interface Owned {
    public String getOwner();

    public void setOwner(String owner);

    public static <T extends Owned> Predicate<T> ownerEquals(Class<T> clazz, String s){
        return ((p) -> { 
            if (s == null){
                return p.getOwner() == null;
            }
            return s.equals(p.getOwner());
        });     
    }
}

然后您的Product类实现了这些接口,以及一些简单的方便方法来调用接口静态方法:

public class Product implements Named, Owned{
    private String name;
    private String owner;

    public String getOwner() {
        return owner;
    }

    public String getName() {
        return name;
    }

    public void setOwner(String owner){
        this.owner = owner;
    }

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

    public static Predicate<Product> nameEquals(String s){
        return Named.nameEquals(Product.class, s);  
    }

    public static Predicate<Product> ownerEquals(String s){
        return Owned.ownerEquals(Product.class, s); 
    }   
}

瞧,您的产品是可搜索的。然后你的find()方法的签名更改为一个谓​​词:

public interface Finder<T> {
    Set<T> find(Predicate p);
}

关于谓词的一个奇妙的事情是它们彼此结合和复合是多么容易。例如,假设我们想要查找()任何不属于“john”的名为“cucumber”的产品,或者“john”拥有的任何其他名称的产品。对find()的调用非常干​​净且易于理解:

finder.find(
    Product.nameEquals("cucumber")
        .and(Product.ownerEquals("john").negate())
    .or(
        Product.ownerEquals("john")
            .and(Product.nameEquals("cucumber").negate())
    )
);

我应该很清楚这段代码在做什么。我使用缩进试图让它们更清晰地结合起来。我们可以将不同的谓词结合到我们心中的内容中。