Search code examples
javastringjava-streampredicatesupplier

Create new Objects based on a String with Java Stream


i m playing around with Java Streams and I wonder if there is any way to create a code Block like this ->

if(givenString.equals("productA")) {
    return new productA();
} else if(givenString.equals("productB") {
    return new productB();
} .....

into a Java Stream like this ->

Stream.of(givenString)
      .filter(e -> e.equal("productA)")
      .map(e -> new productA())   

i came across with this solution which works but i m not convinced...

Stream.of(givenString)
      .map(e -> e -> e.equals("productA)" ? new productA() : new productB())      
      .findAny()
      .get()

Solution

  • You don't want to do that inline in a stream. Instead, write a helper method that does just that:

    private static Product createByString(String name) {
        // I assume Product is a common superclass
        // TODO: implement
    }
    

    Now the question is: How should this method be implemented?

    1. Use a big switch statement.

      private static Product createByString(String name) {
          switch (name) {
              case "productA": new productA();
              case "productB": new productB();
              // ... maybe more?
              default: throw new IllegalArgumentException("name " + name + " is not a valid Product");
          }
      }
      

      Pro: a switch on a string is compiled into a jumptable, so you won't have n string comparisons.
      Con: You can't extend it at runtime, and you have to keep this method in sync.

    2. Use a HashMap<String,Supplier<Product>>.

      private static final Map<String,Supplier<Product>> productConstructors = new HashMap<>();
      static {
          productConstructors.put("productA", productA::new);
          productConstructors.put("productB", productB::new);
      }
      private static Product createByString(String name) {
          Supplier<Product> constructor = productConstructors.get(name);
          if (constructor == null) {
              // Handle this?
              throw new IllegalArgumentException("name " + name + " is not a valid Product");
          }
          return constructor.get();
      }
      

      Pro: with some easy modifications you can add new products to this implementation, or even replace them.
      Con: has some moderate overhead, and you still need to maintain a the mapping between "productA" and it's type.

    3. Use reflection.
      The good old hammer where every problem looks like a nail.

      private static Product createByString(String name) {
           try {
               return Class.forName("your.pkgname. " + name).asSubclass(Product.class).getConstructor().newInstance();
           } catch (ReflectiveOperationException e) {
               throw new RuntimeException(e);
           }
      }
      

      Pro: You don't need to do the binding.
      Con: It's slow.