Fonksiyonel Java

0 3.101

Uzun bir aradan sonra yeni makale ile merhaba. Bu yazı Java hakkında yazdığım ilk makale olacak. Bu yazıdan sonraki hedefim JavaScript hakkında daha sık makale yazmak olacak. Hadi başlayalım…

Image for post

İçerik:


 

Tanım 1: Fonksiyonel programlama, yalnızca fonksiyonların kullanılmasıyla yazılmış programlardır.

Tanım 2: Bir uygulamanın durumunu (state) ve verilerini doğrudan değiştirmeden, matematiksel fonksiyonlar yardımıyla sonuç üretilmesini sağlayan bir programlama yaklaşımıdır.

FP’de değişkene değer atama işlemi genellikle yapılmaz. Yapılacaksa değişkenin değeri kopyalanır ve yeni bir değişken yaratılır. Bunun sebebi Tanım 2 de belirttiği gibi uygulamanın durumunu değiştirmemek ve çok kanallı (multi-thread) yani bir programda aynı anda birden çok fazla işin yapılması durumunda, aynı kaynağa erişmekten dolayı kilitlenmelerin oluşması engellemektir.

FP’de; çıktı değeri, fonksiyona girdi olarak verilen parametre değerlerine bağlıdır. Yani fonksiyon aynı parametreler ile defalarca çağrılsa ,işlemin sonunda aynı sonuç üretilir.

Church Turing tezi ile literatüre kazandırılmıştır. Doğal sayılar üzerinde tanımlı olan bir fonksiyon, kaynak sınırı göz ardı edilirse, bir insan tarafından hesaplanabilir ve aynı zamanda Turing makinesi(Yazdığımız bütün programlar) tarafından hesaplanan fonksiyonlar üzerine kurulu tezdir.

Diğer bir deyişle; sadece parametreleri ve işleyişi ile tanımlanan isimsiz fonksiyonlara Lambda ifadeleri denir. Bu ifadeler ile yapılan hesaplama modeline Lambda Calculus denir. Aşağıdaki resimde verilen sayının küübünü hesaplayan lamda fonksiyonu gösterilmiştir.

Image for post
Bir lambda ifadesine geçirilen parametre, ifadede değerlendirilip sonuç üretebilir.

Ama nesne yönelimli programlama dilleri yeni versiyonlarına FP’yi dahil etmektedir. Hatta bir çok uygulamada bu iki programlama paradigması karışık olarak kullanılmaktadır. Nesne yönelimli programlama da bu fonksiyonlara metot ismi verilmektedir.


 

FP’de kullanılan temel kavramlar şu şekildedir.

Image for post

Birinci Sınıf Fonksiyon(First Class Function): Programlama dillerinde, bir değişkene atanma, metoda parametre olarak geçirilme ya da onda değer olarak döndürülme işlemlerine tabi tutulan yapılara birinci sınıf vatandaş denir. Fonksiyonların birinci sınıf vatandaş gibi davranması durumudur. Yazılım dilinde bu fonksiyonların uygulandığı fonksiyonlara Yüksek Merteden fonksiyonlar(Higher Order Funcitons) denir.

Saf Fonksiyon(Pure Function): Sadece girdi değerini alıp, global bir değişkene etki etmeden çıktı değer üreten fonksiyonlardır. Bu duruma ise Başvuruşsal şeffaflık (Referential transparency) denir.

Yan Etkilerden Arındırılma(No Side Effects)Bir fonksiyonun dışardan gelen durumlara göre farklı çıktı üretmesi durumuna Yan etki denir. Fonksiyonel progralamada fonksiyonlar yan etkilerden arındırılmış olmalıdır.

Değiştirilmez Veri (Immutable Data): Verilen değerinin değişmemesi durumudur

Durumsuzlık(Statelessness): Fonksiyon uygulamaya ait herhangi bir durum bilgisini tutmaz ve değiştirmez

Lazy Evaluation: Bir değişkenin değerinin, mecbur kalınana kadar hesaplanmaması durumudur. Değişkenin değerine ihtiyaç olduğu ana kadar hesaplama yapılmaz

 

Java’da Fonksiyonel Programlama

Bir yazılım dilinin fonksiyonel programlama özelliğine sahip olabilmesi için aşağıdaki 3 temel konsepti sağlaması gerekmektedir. Java’nın bu konseptleri nasıl sağladığına yakından bakalım.

 

Programlama dillerinde, bir değişkene atanma, metoda parametre olarak geçirilme ya da metottan değer olarak döndürülme işlemlerine tabi tutulan nesnelere birinci sınıf vatandaş denir. FP ise fonksiyonların birinci sınıf nesne gibi davrandığı programlama yaklaşımıdır. Bu yaklaşımda fonksiyonların parametre olarak bir fonksiyona geçirilebilir, bir fonksiyonun örneği (instance) yaratılabilir, bir fonksiyon örneği, referans değişkende tutulabilir.

Java’da fonksiyonlar(metotlar) birinci sınıf nesneler değildir. Java 8 ile Lambda İfadeleri kullanılarak Birinci Sınıf Fonksiyonlar kavramı getirilmiştir.

Çalıştırılma anında herhangi bir yan etkiye mâruz kalmayan ve parametre olarak verilen değerlere göre çıktı üreten fonksiyonlara denir.

Saf fonksiyonların tanımlanmasında dikkat edilecek kurallar:


 

topla fonksiyonu 2 tane parametre almakta ve verilen parametreleri toplayıp sonucu dönmektedir. Uygulama içerisinde bulunan ve fonksiyonun dışındaki herhangi bir değişkenin durumunu değiştirmediği için herhangi bir yan etkisi bulunmamaktadır. Aşağıdaki örnekte Saf Olmayan fonksiyon tanımı yapılmmıştır.

Durumsuzluk: FP’nin önemli kurallarından biridir. Bir fonksiyon, geçici olarak durumu tutan local değişkenlere sahip olabilir. Ama bu fonksiyon kesinlikle sınıf veya ait olduğu fonksiyonda tanımlı olan değişkenlere ve nesnelere referans etmemeli.

Yan etkilerden arındırılma: Bir fonksiyon kesinlikle dışarıdaki bir durumu değiştirmemeli.

Değişmez/Sabit/Durağan değişkenler: Değiştirilemez değişkenler FP’nin üçüncü önemli kuralıdır. Bu değişkenler yan etkilerden kurtulmak için önemlidir. Java 8 ile gelen map ve filter API’leri bir diziye uygulandığı dizinin orijinal yapısında herhangi bir işlem yapmayıp dizinin bir kopyası ile gerekli işlemleri yapmaktadır.

Parametre olarak bir veya birden çok fonksiyon alan ve sonuç olarak başka bir fonksiyon dönen fonksiyonlara denir. Java’da yüksek mertebe fonksiyonlar; Lambda ifadelerinin parametre olarak geçirilmesi ve başka bir lambda ifadesinin dönmesi ile sağlanmaktadır.

Yüksek Mertebe Fonksiyonlar

Yukarıdaki örnekte createFactory metodu, parametre olarak iki tane fonksiyonel arayüz örneğini almakta ve lambda ifadesini sonuç olarak dönmektedir. createFactory metodu yüksek mertebe fonksiyonudur.

Java lambda ifadeleri Fonksiyonel Arayüzden(Functional Interface) uygulanmış(implement) edilmelidir.

Java 8 ile yukarıda belirtilen tüm konsept Java’ya dahil edilmiş ve Java’nın fonksiyonel programlamayı destekleyen bir dil olması sağlanmıştır.

Lambda ifadeleri kendi başlarına tanımlanabilen fonksiyonlara denir. Söz dizilimi basitçe şu şekildedir.

 

Java’da Lamda ifadeleri

Lambda komutlarında tipsiz veya parametresiz fonksiyon tanımlamak mümkündür. Eğer tek parametreli bir fonksiyon tanımlanacaksa parantezlere ihtiyaç yoktur. Parametresiz bir fonksiyon tanımlanacaksa () şeklinde tanımlama yapılır. Fonksiyon gövdesinde eğer tek satırlık bir işlem yapılıyorsa {} parantezlerine ve return ifadesine gerek yoktur. Eğer istenilirse parametrelerin tipi belirtilebilir.

Lambda ifadelerin kullanım şekilleri:

  • Lambda ifadeleri nesne olarak kullanılabilir. Lambda ifadelerini bir değişkene atayıp, herhangi bir nesneye geçirilebilir.
  • Lambda ifadeleri :: kullanılarak metot referansı olarak kullanılabilir.
  • Static metotlarda lambda ifadelerinde kullanılabilir
  • Parametreli metotlarda lambda ifadeleri ile kullanılabilir.

  • Override edilmiş Static metot’uda lambda referans olarak kullanılabilir.
  • Lambda fonksiyonları constructor metot referansı olarak kullanılabilir

İçerisinde sadece bir tane soyut metot (implement edilmemiş) bulunan arayüzlerdir. Java’da fonksiyonların birinci sınıf fonksiyonlar olarak kullanılması için ve lambda ifadesi ile tanımlanan fonksiyonların başka bir fonksiyona parametre olarak geçirilmesi veya fonksiyonların bir değişkene tanımlanabilmesi için getirilen bir özelliktir.

Zorunlu olmamakla birlikte @FunctionalInterface anotasyonu ile tanımlanır. Bu anotasyon interface tanımına eklenirse ve yanlışlıkla arayüzün içine birden fazla soyut metot tanımlanması durumunda compile error verecektir

Normalde arayüzlerde metotların içeriği olmaz, sadece açıklanır. Ama default ve static anahtar kelimeleri ile metot gövdeleri yazılabilir. Bu Arayüzün Fonksiyonel Arayüz sayılabilmesi için bir tane implement edilmemiş soyut metot bulunması yeterlidir.

Gömülü(Built-in) Arayüzler:

Geliştirme esnasında karşılaşılan bir sorunların çözümleri Java 8 gömülü olarak fonksiyonel fonksiyonlarla geldi. Bu fonksiyonlar birçok durumda kullanılabileceği için, çoğu zaman yeni bir Fonksiyonel Arayüz yazmaya gerek duyulmamaktadır. Aşağıda en yaygın olarak kullanılan arayüzler örneklerle açıklanmıştır.

Supplier<T>: Herhangi bir parametre almayan belirtilen tip nesne veya sonuç döner. “get()” soyut sınıfı ile istenilen sonuç alınır.



 

Predicate<T> : Verilen parametreye ve tanımlanan koşul fonksiyonuna göre true yada false değerini dönen Fonksiyonel Arayüzdür.

BiPredicate<T,T>: Predicate ile benzer işlemi yapar. BiPredicate ile verilen 2 parametreye göre koşul işlemleri oluşturabilir.

Consumer<T>: Kendisine verilen parametreyle işlem yapan ve geriye birşey dönmez


Function<T,R>: Kendisine verilen parametre ile belirtilen işlemi yapan ve işlemin sonunda belirtilen tipde değer döner


Parametre olarak fonksiyon alabilen sonuc olarak fonksiyon dönebilen fonksiyonlara denir.

Yukarıdaki örnekte dizide sıralama işlemi yapılıyor. Örnekte Array.sort yüksek mertebe fonksiyonu iki parametre almaktadır. ilk parametre işlem yapılacak liste ikinci parametre ise sıralama fonksiyonu. Parametre olarak bir fonksiyon aldığında bu fonksiyon YMF’dur.

Yukarıdaki örnekte parametre olarak verilen listeye tersten sıralama işlemi yapılıyor. Örnek Liste oluşturulduktan sonra sıralama lambda fonksiyonu Comparator fonksiyonel arayüzüne implement ediliyor. Comprator arayüzünde bulunan reversed() metodu çağrılıyor. reversed() metodu ters sıralama işlemi yapacak olan yeni bir Comprator tipinde lambda fonksiyonu dönüyor(fonksiyon döndüğü için comprator bir YMF’dur) . yani -1 * comparator.compare(a,b) fonksiyonu dönüyor.

Birden fazla parametreleri tek parametreli fonksiyon şekline dönüştürme işlemidir. Bu işlem ile Yüksek mertebe fonksiyonlar üzerinden yürütülür. Parametreler, teker teker başka fonksiyonlar döndüren fonksiyonlara paslama şeklinde iletilir. Söz dizimi şu şekildedir:

Küçük parçalı fonksiyonları birleştirerek yeni bir fonksiyon oluşturma tekniğidir. Kendi fonksiyonlarınızı yaratabileceğiniz gibi Java 8 de bu işlemi yapabilmeniz için fonksiyonlar gömülü olarak gelmiştir. Bu fonksiyonlar genellikle lambda ifadeleri ile kullanılır.

Yukarıdaki örnekte kullanıcı tarafından tanımlı fonksiyonel kompozisyon oluşturuluyor. Örnekte ilk önce verilen metnin A harfi ile başlayıp başlamadığını kontrol eden lambda ifadesi Predicate fonksiyonel arayüzüne implement ediliyor. Daha sonra verilen metnin son karakterinin K ile bitip bitmediğini kontrol eden Predicate fonksiyonel arayüzüne implement ediliyor. Daha sonra bu iki fonksiyonlar üçüncü bir fonksiyonda birleştiriliyor. ve en sonra olarak fonksiyon çalıştırılıyor. iki kontrolde true dönerse sonuç true olarak dönüyor.

Fonksiyonel Kompozisyon işlemleri Fonksiyonel arayüzler kullanılarak gerçekleştirilir.


 

Java Akış(Stream)

Image for post

Java Stream, öğelerinin iç yinelemesini(iteration) yapabilen bir bileşendir. Bunun anlamı, öğeler kendisini yineleyebilir. Stream kelimesinin Türkçesi “akış, nehir, akarsu”dur. Aslında bu şekilde düşündüğümüzde yapısını kolayca anlayabiliriz. Bir nehir var ve devamlı akmakta, akıp gittiği için stream içindeki elemanlar sadece bir kere ziyaret edilir. Aynı elemanı tekrar işleme dahil edebilmek için yeni bir stream/nehir oluşturmamız gerekmektedir. Bu akış genel itibariyle kullanmış olduğumuz işlemlerin türüne bağlı olacak şekilde bir boru hattı gibi düşünülebilir.

Java’da bir çok yolla akış oluşturulabilir. Yaygın olarak kullanılan Java Collections’lardır.

Tamamlanmayan (Ara İşlemler — Non-Terminal) Operaötler:

Akış üzerinden işlem yaparlar ve bu işlem sonucunda yine işlenebilecek bir akış dönerler. Bu yüzden birden fazla kez kullanılabilirler.

Aşağıda en yaygın kullanılan operatörler mevcut. Şimdi bunlara daha yakından bakalım.


Örneklerde kullanmak için bakalım Kişi sınıfını yakından inceleyelim. Comparable Arayüzünden implement ederek Kişi sınıfını oluşturuyoruz. Bu sınıfta kişiyi tanımlayacak özellikleri ve burada gösterilmeyen getter ve setter’ları oluşturuyoruz. 2 tane constructor yaratıyoruz. Ve toString() metodunu override ediyoruz. Yaşa göre sıralama yapabilmek için compareTo() metodunu override ediyoruz. Cinsiyet bilgisini ise enum şeklinde tanımlıyoruz. Oluşturduğumuz sınıf şu şekilde:

Sınıfımızda uygun kişileri yaratıp. Kişiler listesini oluşturuyoruz.


 

allMatch(): “Verilen koşula göre tüm elemanlar akışta mevcut mu?” sorusuna cevap veren operatördür. Eğer sorunun cevabı evet ise true, değil ise false değerini döner. Eğer akışın içinde uygun olmayan bir kayıt bulunursa akış anında sona erer.

anyMatch(): “Verilen koşula göre en az bir tane eleman mevcut mu?” sorusunun cevabıdır. Eğer sorunun cevabı evet ise true, değil ise false değerini döner. Eğer akışın içinde bir tane uygun olan bir kayıt bulunursa akış anında sona erer.

noneMatch(): ”Verilen koşul listede sağlanmıyor mu?” sorunusun cevabıdır. allMatch()operaötürünün tersidir.

collect(): Akış türündeki nesneleri, başka biçimdeki nesneye veya veri yapısına dönüştürmek için kullanılır. Akış sonlandırmada en yaygın kullanılan operatördür.

count() : “Verilen koşula göre kaç tane elaman var?” sorusunun cevabını veren operatördür.

forEach() : Verilen koşulu listenin tüm elemanlarına uygulayan operatördür. Yan etki oluşturabilir.

min(comparator): Verilen koşula göre listede en küçük elemanı akış olarak dönen operatördür. Dönüş değeri Optinal tipindedir.

max(): min() operatörün tersini yapan operatördür.

reduce(): Verilen koşula göre listenin elemanlarında biriktirme işlemi yapan operatördür.Duruma göre Optional tipinde değer döner.

filter(): Lamda ifadesiyle verilen koşulu sağlayan elemanları yeni bir akış şeklinde dönen operasyondur.

map(): Mevcut akıştaki elaman verilen fonksiyonu uygulanmasından ortaya çıkan yeni akışı dönen fonksiyondur. Örneğin akıştaki elamanda bulunan nesneyi int tipinde yeni bir akışa döndürmek için bu operasyon kullanılır. 4 tane tipi mevcuttur.

  • map(): uygulanan işlemin sonunda R tipinde yeni bir akış döner.
  • mapToInt(): akışı int(primitif) tipinde yeni bir akışa dönüştürür.
  • mapToLong(): akışı long(primitif) tipinde yeni bir akışa dönüştürür.
  • mapToDouble(): takışı double(primitif) tipinde yeni bir akışa dönüştürür.

sorted() / sorted(comparator): Akışa verilen kurala göre sıralayıp yeni akış olarak dönen operatördür.

distinct(): Akıştaki elemanları equals metoduna göre çokluyan kayıtları teke indiren operasyondur.

skip(): Akışta belirtilen elemana kadar akıştaki elemanları atlayıp daha sonraki akışları işleme alan operatördür.

Örnekler:


 

Bir sonraki Javascript yazılarında görüşmek üzere.

Email adresiniz yayınlanmayacaktır.