5 Nisan 2020 Pazar

Predicate Arayüzü

Giriş
Şu satırı dahil ederiz.
import java.util.function.Predicate;
Predicate true veya false dönen bir function'dan ibaret. filter gibi metodlarda kullanılır. Şöyle düşünülebilir.
         _ _ _ _ _ _ _ 
        |             |
  T --> |  predicate  | --> boolean
        |_ _ _ _ _ _ _|   
Aslında true dönen function ile aynı işlevi görür. Şöyle düşünebiliriz.
Predicate<String> predicateTest  = (s)-> s.length() > 5;       
System.out.println(predicateTest.test("Predicate"));

Function<String, Boolean> functionTest = str -> str.length()> 5;      
System.out.println(functionTest.apply("Function"));
DoublePredicate, LongPredicate, IntPredicate gibi özelleşmiş arayüzler de geliyor. Primitive tipleri generic olarak zaten kullanamıyoruz. Şu kod derlenmez.
Predicate<int> p; //does not compile
Ayrıca bu özelleşmiş arayüzler boxing yapmadıkları için daha iyi performans sunuyor.

Filter Tasarım Örüntüsü - Filter Design Pattern
Predicate'ın esas amacı Filter Tasarm Örüntüsünü gerçekleştirmek
Örnek
Male ve Engineering'te çalışan kişileri görmek için şöyle yaparız 
List<Employee> maleEngEmployeesUsingLambda = employees.stream()
  .filter(employee -> Gender.MALE.equals(employee.getGender()))
  .filter(employee -> Dept.ENG.equals(employee.getDepttName()))
  .collect(Collectors.toList());
constructor - lamda
Dikkat edilmesi gereken nokta biz artık predicate nesnesini yaratmıyoruz. Genellikle lambda kullanıyoruz. Predicate kendi içinde iki alt kümeye ayrılıyor. Bunlar Predicate ve BiPredicate.
Örnek
Şöyle yaparız.
Predicate<? super List> predicate = Collection::isEmpty;
Örnek
Şöyle yaparız.
Predicate<URL> p = u -> u.getFile().isEmpty();
Örnek
Şöyle yaparız.
Predicate<Node> isElement = node -> node.getNodeType() == Node.ELEMENT_NODE;
BiPredicate<Node,String> hasName = (node,name) -> node.getNodeName().equals(name);
constructor - method reference
Şöyle yaparız.
class Case {
  public boolean isEmpty() {
    return true;
  }

}

Predicate<Case> forNonStaticMethod = Case::isEmpty;
and metodu
and metodu aslında şuna benzer. Sanki elimizde bir Stream<Predicate> varmış ve allMatch() metodu çağrılmış gibidir. Şöyle yaparız.
boolean filter(Employee employee) {
  Stream<Predicate<String>> filters = Stream.of(
    this::filterBasedOnConsistAge,
    this::filterBasedOnConsistGender,
    this::filterBasedOnConsistNationality,
    this::filterBasedOnConsistHandicap
  );

  String employeeJSONString = employeeToString(employee);
  return filters.allMatch(f -> f.test(employeeJSONString));
}
Stream yerine Predicate nesnelerini and() metodu ile birbirine bağlamak mümkün. Şöyle yaparız.
Predicate<String> employeePredicate =
    new FilterBasedOnConsistAge()
    .and(new FilterBasedOnConsistGender())
    .and(new FilterBasedOnConsistNationality())
    .and(new FilterBasedOnConsistHandicap())
Örnek
İki tane Predicate nesnesini birleştirmek için şöyle yaparız.
Predicate<String> both = ((Predicate<String>) Utilities::isNumeric).negate()
                             .and(((Predicate<String>) String::isEmpty).negate());
isEqual metodu
Metodun içi şöyle. Sonuç olarak bir Predicate döner.
static <T> Predicate<T> isEqual(Object targetRef) {
    return (null == targetRef)
            ? Objects::isNull
            : object -> targetRef.equals(object);
}
Şöyle yaparız.
Predicate<Integer> predicate = Predicate.<Integer>isEqual(0).or(Predicate.isEqual(1));
negate metodu
Diğer bazı örnekler burada
Örnek - Method Reference
Şöyle yaparız.
Predicate<String> p = ((Predicate<String>) String::isEmpty).negate());
not metodu - Java 11
Örnek
Şöyle yaparız
Predicate<String> p = startWithZ = s -> s.charAt(0) == 'z';
Predicate<String> doesNotStartWithZ = Predicate.not(startWithZ);
or metodu
Örnek
3 şehirden herhangi birisinde yaşayanları bulmak için şöyle yaparız.
Predicate<Student> livesInDelhi = ...
Predicate<Student> livesInAmsterdam = ...
Predicate<Student> livesInNewYork = ...

Predicate<Student> livesInAnyOfTheseThreeCities = livesInDelhi.or(livesInAmsterdam)
  .or(livesInNewYork);
Örnek
Şöyle yaparız.
Predicate<Integer> pred21 = Predicate.isEqual(0);
Predicate<Integer> pred22 = pred21.or(Predicate.isEqual(1));
test metodu
Elimizde şöyle bir predicate olsun
Predicate<Integer> isGreaterThanZero = num -> num.intValue() > 0;
Şöyle yaparız.
isGreaterThanZero.test( new Integer( 2 ));
Diğer

Predicate ve lambda İlişkisi
Predicate yaratmak için en kolay yöntem lambda kullanmak. Lambda özellikle bir seferlik kodlarda - throw away - kullanılır. Aynı kod farklı yerlerde de kullanılacaksa ortak bir yere taşımak gerekir.
Ortak bir yere taşınan yani method refence olarak kullanılan bir sınıfın metodunu çağırmak için SınıfAdı::MetodAdı şeklinde kodlarız. 
Örnek
Şöyle yaparız
myFinalList = myListToParse.stream()
    .filter(Objects::nonNull)
    .map(this::doSomething)
    .collect(toList()); 
Bir başka örnek.
public class Foo {
    public static B bar(A a) {...}
}
...
useFunction(Foo::bar);
Lambda derleyici tarafından koda dönüştürülüyor.Örneğin filter metodunu kullanıyor olalım. Şöyle yazıyoruz.
double average = list
  .stream()
  .filter(p -> p.getGender() == Person.Sex.MALE)
  .mapToInt(Person::getAge)
  .average()
  .getAsDouble();
Derleyici bu kodu bizim için şu hale getiriyor.
double average = list
.stream()
.filter(new Predicate<Person>(){
          public boolean test(Person p){
              return p.getGender() == Person.Sex.MALE;
           }
      })
.mapToInt(Person::getAge)
.average()
.getAsDouble();
Predicate Dönmek

1. Predicate kullandığı nesneyi capture eder
Predicate kullandığı nesnesiyi capture eder. Şöyle yaparız. Böylece seen map'i bir kere yaratılır.
public static <T> Predicate<T> distinctByKey(Function<? super T,Object> keyExtractor) {
    Map<Object,Boolean> seen = new ConcurrentHashMap<>();
    return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

BigDecimal totalShare = orders.stream()
  .filter(distinctByKey(o -> o.getCompany().getId()))
  .map(Order::getShare)
  .reduce(BigDecimal.ZERO, BigDecimal::add);
2. Tekrar Kullanılabilir Predicate
Bu hep kafamı karıştıyor.

Örnek
Elimizde şöyle bir kod olsun
List<Foo> list = ...;
Foo nesnesinin getName() metodu olsun. Normalde şöyle yaparz
Foo findByName(String name){
  list.stream()
   .filter((foo) -> foo.getName().equals(name)
   ...
}
Burada predicate lambda olarak yazıldı. Tekrar kullanılabilir predicate için şöyle yaparız
class Foo {
  Predicate<Foo> findByName(String name){
    return foo -> foo.getName().equals(name)
  }
  ...
}
Şöyle yaparız.
Foo findByName(String name){
  list.stream()
   .filter(Foo.findByName(name))
  ...

}

Hiç yorum yok:

Yorum Gönder