Wednesday, July 17, 2013

Java'da Soyut Sınıf ve Arayüz Kullanımı

Sınıf, nesne, kalıtım ve çok şekillilik nesneye dayalı programlamanın temel bileşenleridir. Modellemeyi ve gerçeklemeyi bu yapıları kullanarak sağlıyoruz. Java programlama dili de nesneye dayalı bir programlama dilidir. Söz dizimsel özellikleri C++ programlama dilininkine benzer. İşleyişte ise bazı temel farklar bulunur. Bu farklardan biri C++ ve Java'nın çoklu kalıtımı destekleme biçimleriyle ilgilidir. C++, bir sınıfın, birden fazla sınıftan, doğrudan türetilmesine izin verir. Bu çoklu kalıtım olarak adlandırılır. Ancak bu özelliğin sorunlu bazı yönleri bulunmaktadır. Bu sorunlardan birini, çoklu kalıtım uygulamasının yer aldığı aşağıda verilen kod üzerinde görmeye çalışalım. Aşağıdaki kod derlenmek istenirse, derleyici D sınıfının getX() metodu için hata iletisi verir ve derleme başarısız olur.

class A {

  protected:

     int x;

  public:

     virtual int getX(){

        return x;
      }
};
class B : public A {
  public:
      int getX(){
        return 2 * x;
      }
};
class C : public A {
  public:
      int getX(){
        return 3 *x;
      }
};
class D : public B , public C {
  public:
      int getX(){
        return 4 * x;
      }
};
Yukarıdaki çoklu kalıtım uygulamasında, türetme yapılan sınıfların ortak bir sınıftan türetilmiş olmaları durumunda, bu ortak sınıflardan gelen veriler, kalıtım yoluyla birden fazla kere tekrarlı olarak türetilmiş sınıfa aktarılır. Bu nedenle türetilmiş sınıfta, tekrarlı elemana erişimin olduğu yerlerde, derleyici belirsizlik hatası verecektir. Derleyici B üzerinden gelen x'e mi yoksa C sınıfı üzerinden gelen x'e mi erişilmek istenildiğine karar veremez. Yukarıdaki tasarımda A sınıfında tanımlanan x hem B hem de C üzerinde tekrarlı olarak D sınıfına aktarılır. C++'da bu problemin sezgisel olmayan sanal kalıtım olarak adlandırılan bir çözümü bulunmaktadır. Burada B ve C sınıfını kodlayan programcılar A sınıfından türetme yaparken sanal kalıtım uygulaması yaparlar:
class B : virtual public A {
  public:
      int getX(){
         return 2 * x;
      }
};
class C : virtual public A {
  public:
      int getX(){
        return 3 *x;
      }
};
Şimdi D sınıfına, A sınıfından kalıtımla sadece tek bir x aktarılır. Bu çözümün doğal olmayan bir yönü bulunmaktadır. Eğer B ve C sınıfları farklı programcılar tarafından kodlanıyorsa bu çözümün öngörülemez olduğu açıktır. Üstelik programcılardan birinin sanal kalıtımı uygulaması ancak diğer programcının uygulamaması durumunda önerilen çözüm çalışmaz. Java'da bir sınıfın sadece tek bir sınıfı türetebiliyor olmasının temel nedeni bu türden durumlar yaratmamaktır. Böylelikle Java'da yukarıda C++ için verilen örnekteki gibi bir durum tekli kalıtım kullanılması nedeni ile asla oluşmaz.
Fiziksel uzayda (problem uzayında) bazen doğal olarak çoklu kalıtım durumları ile karşılaşıyoruz. Bu gibi durumlarda Java'nın sağladığı çözüm arayüz (=interface) kullanmaktır. Az sonra Java ara yüzlerinin özelliklerini detaylı olarak çalışacağız. Ancak kısaca söylemek gerekirse, Java'da bir sınıf sadece tek bir sınıfından türetilebilirken (extends), birden fazla arayüzü gerçekleyebilir (=implements). Üstelik Java arayüzleri arasında çoklu kalıtım vardır: bir arayüz birden fazla arayüzden türetilebilir. Arayüz kullanıldığında, çoklu kalıtımın C++'daki gibi bir yan etkisi ile karşılaşılmamasının nedeni, arayüzlerde öznitelik tanımlanmasına izin verilmemesidir. Soyut sınıflar ile arayüz arasında önemli bir ilişki vardır. Az sonra bu ilişkinin özeliklerini çalışacağız. Ancak önce soyut sınıfları çalışalım.
Çok şekillilik için aşağıdaki üç özelliğin sağlanması gerekir:
  1. Aralarında kalıtım ilişkisi olan sınıflar,
  2. Bu sınıflarda yer alan çok şekilli metotlar,
  3. Bu sınıflar arasında en temel sınıfından tanımlanmış bir referans.
Bu referansı kullanarak yapılan tüm çağrılar, yürütme zamanında bağlanır ve dinamik bağlama olarak adlandırılır. Çok şekillilik için gerekli olan birinci koşul çoğu zaman doğal olarak oluşur ancak bazen nadir de olsa oluşmaz. Örneğin Bird, Airplane, Ufo (Unidentified Funny Objects) ve Superman sınıfları arasında kalıtım ilişkisi yoktur. Bird, Airplane değildir, Superman, Ufo değildir. Ancak bu dört sınıfta da ortak bir davranış vardır: fly(). fly() metodu çok şekilli bir metottur. Ancak çok şekilli bir metodun varlığı, sınıflar arasında kalıtım ilişkisi kurulamadığı için çok şekillilik için yeterli olmaz. Sadece çok şekilliliği kurabilmek için gerçek dünyada olmayan bir sınıf uydurulur. Bu sınıf Flyable olsun. Şimdi problem uzayımızdaki tüm sınıflar (Bird, Airplane, Ufo ve Superman) bu uydurulan sınıftan türetilir. Bu sınıfın gerçek dünyada olmayan, sırf çok şekillilik için kafamızdan "uydurduğumuz" bir sınıf olduğunu betimlemek üzere sınıfı soyut sınıf olarak adlandırıyoruz. Java'da soyut sınıf tanımlaması abstract anahtar kelimesi kullanılarak yapılır. Soyut bir sınıfın içinde en az bir tane soyut bir metot olması beklenir. Soyut metot abstract anahtar kelimesi ile tanımlanır. Örneğimizdeki Flyable soyut sınıfı aşağıdaki gibi tanımlanır:
public abstract class Flyable {
   public abstract void fly();
}
Sanal sınıflar gerçek dünyada var olmadıkları için çözüm uzayında da var olmamaları gerekir. Bu nedenle soyut sınıftan nesne yaratılmasına izin verilmez. Soyut sınıftan türetilen sınıflarda, soyut metotlara mutlaka işlev yüklemek gerekir:
public class Bird extends Flyable {
     @Override
     public void fly() {
       System.out.println("Bird is flying now...");
     }
}
public class Airplane extends Flyable {
     @Override
     public void fly() {
       System.out.println("Airplane is flying now...");
     }
}
public class Ufo extends Flyable {
     @Override
     public void fly() {
       System.out.println("Ufo is flying now...");
     }
}
public class Superman extends Flyable {
     @Override
     public void fly() {
       System.out.println("Superman is flying now...");
     }
}
Soyut sınıftan nesne yaratamıyor olsak da soyut bir sınıfın verisi ve kurucu metodu bulunabilir. Soyut bir sınıf başka bir soyut sınıftan türetilebilir. Bu durumda soyut sınıfın soyut metotlarına işlev yüklemesi gerekmez. Soyut bir sınıf bir arayüzü gerçekleyecek ise onun metotlarına işlev yüklemek zorunda değildir. Bu durumda arayüzün metotları soyut sınıfın soyut metotlarına dönüşür. Soyut sınıfın somut metotları olabilir. Somut metot gövdesini kıvırcık parantez kullanarak verdiğimiz, gerçek dünyada örneği ile karşılaşabildiğimiz ve bunun bir sonucu olarak da modelleyebildiğimiz metotlardır.
Java arayüzleri özel bir tür soyut sınıftır. Arayüzlerin verisi, kurucusu, somut metodu olamaz:
           Soyut Sınıf = Arayüz + Kurucu + Veri + Somut Metod
    Arayüz = Soyut Sınıf  - Kurucu - Veri - Somut Metod
Tüm metotları soyut olmak zorundadır. Üstelik bu soyut metotlar public tanımlı olmak zorundadır. Arayüz metotları hem public hem de abstract tanımlı olmak zorunda oldukları için tanımlanırken bunun açık olarak belirtilmesi gerekmez. Örtük olarak public ve abstract olduğu kabul edilir. Yukarıdaki Flyable soyut sınıfının verisi ve somut metodu olmadığı için arayüz olarak da gerçeklenebilir. Arayüz tanımını interface anahtar kelimesini kullanarak veriyoruz:
public abstract interface Flyable {
   public abstract void fly();
}
Bu durumda, Bird, Airplane, Ufo ve Superman sınıflarını Flyable arayüzünü kullanarak implements anahtar kelimesi ile tanımlıyoruz:
public class Bird implements Flyable {
     @Override
     public void fly(){
       System.out.println("Bird is flying now...");
     }
}
public class Airplane implements Flyable {
     @Override
     public void fly(){
       System.out.println("Airplane is flying now...");
     }
}
public class Ufo implements Flyable {
     @Override
     public void fly(){
       System.out.println("Ufo is flying now...");
     }
}
public class Superman implements Flyable {
     @Override
     public void fly(){
       System.out.println("Superman is flying now...");
     }
}
Daha gelişmiş bir örneği NetBeans projesi olarak bu bağlantıdan indirebilirsiniz. Örnek modellemeyi daha rahat takip edebilmeniz için oluşturduğum UML sınıf diyagramını aşağıda bulabilirsiniz:


Monday, July 8, 2013

Java 8 ile Sayısal Loto Oynamak

Sayısal Loto için 1-49 arasında biri birinden farklı altı sayı çeken ve ekrana sıralı olarak yazdıran programı Java programlama dilinde farklı yaklaşımlarla yazmaya çalışalım.
İlk olarak çözümü Java dizilerini kullanarak oluşturalım:
package com.example.lottery;

import java.util.Arrays;
import java.util.Random;

public class LotteryArrays {
 private static final int SIZE = 6;
 private static final int MIN_VALUE = 1;
 private static final int MAX_VALUE = 49;
 private static Random random= new Random();

 public static void main(String[] args) {
  int[] numbers = new int[SIZE];
  Arrays.fill(numbers, MIN_VALUE + MAX_VALUE);
  for (int i = 0; i < SIZE; ++i) {
   int candidate = 0;
   do {
    candidate = random.nextInt(MAX_VALUE) + 1;
   } while (Arrays.binarySearch(numbers, candidate) > 0);
   numbers[i] = candidate;
   Arrays.parallelSort(numbers);
  }
  System.out.println(Arrays.toString(numbers));
 }
}
Çözümde Arrays sınıfının binarySearch() ve parallelSort() metodlarını kullanmış olmak işimizi epey kolaylaştırdı. 

Şimdi de aynı problemi Java'daki Collections API'yi kullanarak çözelim. Eğer Set ve TreeSet sınıflarını kullanmayı akıl edersek çözüme hızlı bir şekilde ulaşabiliriz:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.console;

import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class LotteryCollections {
    private static final int SIZE = 6;
    private static Random random;
    public static void main(String[] args) {
        random = new Random();
        Set<Byte> numbers= new TreeSet<>();
        while (numbers.size()<SIZE){
           numbers.add((byte) (random.nextInt(49) + 1));
        }
        System.out.println(numbers);
    }
}

Son olarak aynı problemi Java 8 ile birlikte gelen Lambda ifadeleri ve Map/Reduce çatısını kullanarak çözelim:
package com.example.lottery;

import java.util.Random;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class LotteryLambda {
    public static void main(String[] args) {
         new Random()
             .ints(1,50)
             .parallel()              
             .distinct()
             .limit(6)
             .sorted()
             .sequential()                
             .forEach(System.out::println);                             
    }
}
Koda bakınca Java 8'in soyutlama gücünün arttığını hissetmişsinizdir. Çünkü artık lambda ifadeleri yazabiliyoruz, fonksiyonel programlama yapabiliyoruz. Fonksiyona parametre  olarak fonksiyon geçebiliyoruz. Fonksiyonel programlama ile bildirimsel programlamaya (=declarative programming) geçiş yapıyoruz. Buna karşın yukarıdaki ilk iki örnekte ise buyruksal programlama (=imperative programming) uygulaması var. Nesneye dayalı programlama ile hem bildirimsel hem de buyruksal programlama yapılabilir. Java 8 ile birlikte Java'da fonksiyonel programlama yapabiliyor olsak da Java saf bir fonksiyonel programlama dili değildir.

Wednesday, July 3, 2013

Java 7'de Sınıf Yükleyici Yazmak

Java 7, programlama dili seviyesinde, geliştiricinin daha verimli kod yazabilmesini sağlayan  basit bir kaç yenilik getirdi: 
  • Otomatik Kaynak Yönetimi 
  • catch içinde | sembolü ile ayırarak birden fazla hata sınıfı tanımlayabiliyoruz
  • switch parametresi olarak tam sayı tipler (byte, short, int, long), char, enum tipler dışında [nihayet] String sınıfını kullanabiliyoruz
Java sanal makinası (JSM) seviyesinde uzun bir süreden sonra yeni bir bytecode komutu geldi: invokedynamic. Henüz Java 7'de derlendiğinde invokedynamic kodu ürettirecek herhangi bir Java ifadesi bulunmuyor. Java 8'de ise fonksiyonel programlama ve arayüz metot eklentileri (=Method Extensions) içeren kodlar derlendiklerinde invokedynamic kodu içerebilecek. JSM seviyesinde, Java 7 ile gelen yeniliklerin bir kısmı, çok çekirdekli sistemlerde başarımı iyileştirmeyi amaçlıyor. Örneğin, sınıf yükleyicileri artık paralel çalışabiliyorlar. Java'da kendi sınıf yükleyicimizi yazabiliriz. Bunun için ClassLoader sınıfından yeni bir sınıf türetmemiz gerekiyor. Java 7'de yükleyicinin paralel çalışmaya uygun olduğunu belirtmek üzere registerAsParallelCapable() metodunu çağırıyoruz. Aşağıdaki örnekte class dosyalarını veritabanında arayan bir sınıf yükleyicisi yer alıyor:    
1:  public class JdbcClassLoader extends ClassLoader {  
2:    
3:    public JdbcClassLoader() {  
4:      boolean isOk = registerAsParallelCapable();  
5:      if (isOk) {  
6:        System.err.println("JdbcClassLoader is registered as parallel capable!");  
7:      } else {  
8:        System.err.println("JdbcClassLoader can not be registered as parallel capable!");  
9:      }  
10:    }  
11:    
12:    @Override  
13:    protected Class<?> findClass(String name) throws ClassNotFoundException {  
14:      Class cls;  
15:      EntityManagerFactory emf= Persistence.createEntityManagerFactory("classdbPU");  
16:      EntityManager em= emf.createEntityManager();  
17:      ClassEntity ce= em.createNamedQuery("ClassEntity.findByClassname", ClassEntity.class)  
18:          .setParameter("classname", name)  
19:          .getSingleResult();  
20:      byte[] bytes= ce.getClassBytes();  
21:      cls= defineClass(name, bytes, 0, bytes.length);  
22:      em.close();  
23:      emf.close();  
24:      return cls;  
25:    }  
26:  }  
27:    
Bunun için MySQL veritabanında önce bir veritabanı ve tablo yaratıyoruz:
mysql> create database classdb;
Query OK, 1 row affected (0.05 sec)
mysql> create table classes (
    -> id int(11) not null auto_increment,
    -> classname varchar(256) not null unique,
    -> class longblob not null,
    -> primary key(id)) engine=innodb;
Query OK, 0 rows affected (0.11 sec)
Aşağıdaki Circle sınıfını derleyip, class dosyasını yukarıdaki classes tablosuna ekliyoruz. Bu veriye test kodunu yazarken ihtiyacımız olacak.
1:  public class Circle {  
2:    private double x,y;  
3:    private double radius;  
4:    
5:    public Circle() {  
6:      radius= 1.0;  
7:    }  
8:    
9:    public Circle(double x, double y, double radius) {  
10:      this.x = x;  
11:      this.y = y;  
12:      this.radius = radius;  
13:    }  
14:    
15:    public double getX() {  
16:      return x;  
17:    }  
18:    
19:    public double getY() {  
20:      return y;  
21:    }  
22:    
23:    public double getRadius() {  
24:      return radius;  
25:    }  
26:    public double area(){  
27:      return Math.PI*radius*radius;  
28:    }    
29:  }  
Circle.class dosyasını ise aşağıdaki şekilde classes tablosuna atıyoruz: 
mysql> insert into classes values(NULL,'com.example.shapes.Circle','');
Query OK, 1 row affected (0.08 sec)

mysql> update classes set class=load_file('c:/temp/Circle.class');
Query OK, 1 row affected (0.11 sec)
Rows matched: 1  Changed: 1  Warnings: 0
Veritabanına erişim için JPA 2 kullanacağız. Bunun için projeye önce persistence unit ve daha sonra ise Entity sınıfını ekliyoruz:
1:  package com.example.entity;  
2:    
3:  import java.io.Serializable;  
4:  import javax.persistence.Basic;  
5:  import javax.persistence.Column;  
6:  import javax.persistence.Entity;  
7:  import javax.persistence.GeneratedValue;  
8:  import javax.persistence.GenerationType;  
9:  import javax.persistence.Id;  
10:  import javax.persistence.Lob;  
11:  import javax.persistence.NamedQueries;  
12:  import javax.persistence.NamedQuery;  
13:  import javax.persistence.Table;  
14:    
15:  /**  
16:   *  
17:   * @author Binnur Kurt  
18:   */  
19:  @Entity  
20:  @Table(name = "classes", catalog = "classdb", schema = "")  
21:  @NamedQueries({  
22:    @NamedQuery(name = "ClassEntity.findAll", query = "SELECT c FROM ClassEntity c"),  
23:    @NamedQuery(name = "ClassEntity.findById", query = "SELECT c FROM ClassEntity c WHERE c.id = :id"),  
24:    @NamedQuery(name = "ClassEntity.findByClassname", query = "SELECT c FROM ClassEntity c WHERE c.classname = :classname")})  
25:  public class ClassEntity implements Serializable {  
26:    private static final long serialVersionUID = 1L;  
27:    @Id  
28:    @GeneratedValue(strategy = GenerationType.IDENTITY)  
29:    @Basic(optional = false)  
30:    @Column(name = "id")  
31:    private Integer id;  
32:    @Basic(optional = false)  
33:    @Column(name = "classname")  
34:    private String classname;  
35:    @Basic(optional = false)  
36:    @Lob  
37:    @Column(name = "class")  
38:    private byte[] classBytes;  
39:    
40:    public ClassEntity() {  
41:    }  
42:    
43:    public ClassEntity(Integer id) {  
44:      this.id = id;  
45:    }  
46:    
47:    public ClassEntity(Integer id, String classname, byte[] classBytes) {  
48:      this.id = id;  
49:      this.classname = classname;  
50:      this.classBytes = classBytes;  
51:    }  
52:    
53:    public Integer getId() {  
54:      return id;  
55:    }  
56:    
57:    public void setId(Integer id) {  
58:      this.id = id;  
59:    }  
60:    
61:    public String getClassname() {  
62:      return classname;  
63:    }  
64:    
65:    public void setClassname(String classname) {  
66:      this.classname = classname;  
67:    }  
68:    
69:    public byte[] getClassBytes() {  
70:      return classBytes;  
71:    }  
72:    
73:    public void setClassBytes(byte[] classBytes) {  
74:      this.classBytes = classBytes;  
75:    }  
76:    
77:    @Override  
78:    public int hashCode() {  
79:      int hash = 0;  
80:      hash += (id != null ? id.hashCode() : 0);  
81:      return hash;  
82:    }  
83:    
84:    @Override  
85:    public boolean equals(Object object) {  
86:      // TODO: Warning - this method won't work in the case the id fields are not set  
87:      if (!(object instanceof ClassEntity)) {  
88:        return false;  
89:      }  
90:      ClassEntity other = (ClassEntity) object;  
91:      if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {  
92:        return false;  
93:      }  
94:      return true;  
95:    }  
96:    
97:    @Override  
98:    public String toString() {  
99:      return "com.example.entity.ClassEntity[ id=" + id + " ]";  
100:    }    
101:  }  
Test kodunda ise Java 7 ile gelen Reflection API'ye göre daha yüksek başarımlı olarak çalışan MethodHandle sınıfını kullanacağız:
1:  package org.godel.test;  
2:    
3:  import java.lang.invoke.MethodHandle;  
4:  import java.lang.invoke.MethodHandles;  
5:  import java.lang.invoke.MethodType;  
6:  import org.godel.loader.JdbcClassLoader;  
7:    
8:  /**  
9:   *  
10:   * @author Binnur Kurt  
11:   */  
12:  public class TestJdbcLoader {  
13:    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, Throwable {  
14:      JdbcClassLoader jcl= new JdbcClassLoader();  
15:      Class cls= jcl.loadClass("com.example.shapes.Circle");  
16:      MethodHandles.Lookup lk= MethodHandles.lookup();  
17:      MethodType mt= MethodType.methodType(double.class);   
18:      MethodHandle mh= lk.findVirtual(cls, "area", mt);  
19:      MethodType mtCons= MethodType.methodType(void.class,double.class,double.class,double.class);  
20:      MethodHandle mhCons= lk.findConstructor(cls, mtCons);  
21:      Object circle= mhCons.invoke(1.0,1.0,100.0);  
22:      double area= (double) mh.invoke(circle);  
23:      System.err.println("Area: "+area);  
24:      System.err.println(cls.getName());  
25:    }  
26:  }  
27:    
Uygulama çalıştırıldığında aşağıdaki gibi bir ekran görüntüsü oluşmaktadır: 
JdbcClassLoader is registered as parallel capable!
Area: 31415.926535897932
com.example.shapes.Circle