20 Aralık 2016 Salı

Küçük Detayların Büyük Etkileri

0 yorum
English version is at DZone

Soru:
 Aşağıdaki kod parçası ile kaç satır yazı yazılır?
public static void main(String[] args){ for (int i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i++) { System.out.println(i); } }

a) Sonsuz sayıda
b) (2147483647 *2) +1
c) 0
d) Derleme Hatası
Birçoğumuz, dikkat etmezsek, cevabın “b” şıkkı olduğu yanılgısına kapılabiliriz. Ama aşağıdaki diğer bir kod parçası cevabın “a” şıkkı olduğuna bizi ikna edecektir.
public static void main(String[] args){ System.out.println(Integer.MIN_VALUE); System.out.println(Integer.MAX_VALUE); System.out.println(Integer.MAX_VALUE + 1); }


Çıktı:
-2147483648
2147483647
-2147483648

Yukarıdaki kod parçası, i değişkeninin değeri Integer.MAX_VALUE değerine eşit olana kadar beklendiği şekilde çalışacaktır. Bu noktada i değişkenin değeri yani Integer.MAX_VALUE ekrana yazılır. Mevcut iterasyonun kontrolü geçildikten sonra, i değişkeninin değeri 1 arttırılır. Burada i değişkeninin yeni değerinin Integer.MAX_VALUE+1 olduğunu düşünebiliriz, ama değil, yeni değer Integer.MIN_VALUE ’dur. Integer.MIN_VALUE değeri Integer.MAX_VALUE değerinden küçük olduğundan iterasyon sonsuza kadar devam eder. 
Bu durumu anlamak için byte’ların ve bit’lerin dünyasına dalmamız gerekecektir. Java’da int veri tipi 32 bit’e kadar olan değerleri tutabilmektedir. Tutabildiği en küçük değer de Integer.MIN_VALUE, yani -2147483648 değeridir. İkili sayı sistemindeki ifadesi ile 1000 0000 0000 0000 0000 0000 0000 0000 ’dır. Burada en soldaki bit, işaret bitidir. 0 değeri sayının pozitif, 1 değeri ise negatif olduğunu belirtir. Yani yukarıdaki ifadede işaret biti 1’dir. Burada da görüldüğü gibi, Java sayıları tutmak için sadece 31 biti kullanmaktadır.






Buradan edindiğimiz bilgi ile, Integer.MAX_VALUE, yani 2147483647, ikili sistemdeki ifadesi ile 0111 1111 1111 1111 1111 1111 1111 1111 değerine 1 değerini ekleyelim:
Görüdüğü gibi sonuç, Integer.MIN_VALUE değeridir.
Yukarıdaki durumu ele alacak olursak, örneğin, oluşturduğumuz bir JPA entity class’ında java.lang.Integer tipindeki bir identifier ile kodumuz bir süre(belki yıllarca) beklendiği gibi çalışsa da, veritabanında oluşturacağımız kayıtların miktarı arttıkça yukarıda bahsettiğimiz problemle karşılaşabiliriz. Hiç ara vermeden, her milisaniyede yeni bir identifier ürettiğimiz bir uygulamada, Integer tipi bizi neredeyse 2 ay idare edebilecekken, Long tipi ile 300 milyon yıl gidebiliriz.
Sonuç olarak, doğru veri tiplerinin seçimi küçük bir detay olarak düşünülebilse de, başımızı ağırtacak büyük etkilere neden olabilirler. Hocamızın, “Mühendislik becerisi uç noktalarda belli olur” sözünü de bu vesileyle  paylaşmak isterim.
ali kemal taşçı

22 Mart 2016 Salı

Java Notları – Parasal Problemler

2 yorum
English version is at DZone

İlkokulda çözmeye alıştığımız basit bir problemi bir de Java’da yazacağımız kodla çözmeye çalışalım.

Klasik problem şöyle:

Ali’nin cebinde 1 TL’si vardır, Ali cebindeki para ile bakkaldaki, 10 Kr’luk şekerlerden kaç tane alabilir?

Java Kodumuz:
public static void main(String[] args) { float alininParasi = 1.00f; float sekerinFiyati = 0.10f; int aldigiSekerlerinSayisi = 0; System.out.println("Ali'nin cebinde, " + alininParasi + " TL'si vardir."); System.out.println("Bakkaldaki sekerin tanesi " + sekerinFiyati + " TL'dir."); while (alininParasi >= sekerinFiyati) { aldigiSekerlerinSayisi++; alininParasi -= sekerinFiyati; } System.out.println("Hesaplamamiza gore, Ali bakkaldan " + aldigiSekerlerinSayisi + " seker alabilir ve cebinde de " + alininParasi + " TL'si kalir."); }
Program Çıktısı:

Ali'nin cebinde, 1.0 TL'si vardir.
Bakkaldaki sekerin tanesi 0.1 TL'dir.
Hesaplamamiza gore, Ali bakkaldan 9 seker alabilir ve cebinde de 0.09999993 TL'si kalir.

Nasıl olur?

Burada, beklentimizden farklı bir sonuç görmemizin nedeni, kodumuzda parasal değerleri float(veya abisi double) veri tiplerinde tutmamızdır. Java’da float ve double veri tipleri, bilimsel ve mühendislik hesaplamalarında, hızlı ve doğru sonuçlar elde etmek üzere tasarlanmışlardır. Bu sebepten, tam(exact) sonuçlar beklediğimiz parasal hesaplamalarda bu veri tiplerini kullanmamamız gerekir. Bunun yerine BigDecimal, int veya long veri tiplerini kullanmamız gerekir.

Yukarıdaki Java kodumuzu float yerine BigDecimal kullanarak tekrar yazalım:
public static void main(String[] args) { BigDecimal alininParasi = new BigDecimal("1.00"); BigDecimal sekerinFiyati = new BigDecimal("0.10"); int aldigiSekerlerinSayisi = 0; System.out.println("Ali'nin cebinde, " + alininParasi + " TL'si vardir."); System.out.println("Bakkaldaki sekerin tanesi " + sekerinFiyati + " TL'dir."); while (alininParasi.compareTo(sekerinFiyati) >= 0) { aldigiSekerlerinSayisi++; alininParasi = alininParasi.subtract(sekerinFiyati); } System.out.println("Hesaplamamiza gore, Ali bakkaldan " + aldigiSekerlerinSayisi + " seker alabilir ve cebinde de " + alininParasi + " TL'si kalir."); }
Program Çıktısı:

Ali'nin cebinde, 1.0 TL'si vardir.
Bakkaldaki sekerin tanesi 0.1 TL'dir.
Hesaplamamiza gore, Ali bakkaldan 10 seker alabilir ve cebinde de 0.0 TL'si kalir.

Evet şimdi oldu.

Burada BigDecimal kullanımı ile ilgili iki dezavantajdan bahsedebiliriz:
  1.  Primitive tipler kadar kullanışlı değil
  2.  Yavaş

Basit hesaplamalar için ikincisi pek canımızı sıkmasa da, ilki bazen sinir bozucu olabilir. 

BigDecimal’e alternatif olarak, int veya long veri tiplerini kullanmayı seçebiliriz. Bu durumda ondalık kısımları bizim yönetmemiz gerekiyor. Yani yukarıdaki örneğimizde hesaplamalarımızı lira olarak değil de kuruş olarak yapmamız gerekir:
public static void main(String[] args) { int alininParasi = 100; int sekerinFiyati = 10; int aldigiSekerlerinSayisi = 0; System.out.println("Ali'nin cebinde, " + (alininParasi*0.01) + " TL'si vardir."); System.out.println("Bakkaldaki sekerin tanesi " + (sekerinFiyati*0.01) + " TL'dir."); while (alininParasi >= sekerinFiyati) { aldigiSekerlerinSayisi++; alininParasi -= sekerinFiyati; } System.out.println("Hesaplamamiza gore, Ali bakkaldan " + aldigiSekerlerinSayisi + " seker alabilir ve cebinde de " + (alininParasi *0.01) + " TL'si kalir."); }
Program Çıktısı:

Ali'nin cebinde, 1.0 TL'si vardir.
Bakkaldaki sekerin tanesi 0.1 TL'dir.
Hesaplamamiza gore, Ali bakkaldan 10 seker alabilir ve cebinde de 0.0 TL'si kalir.

BigDecimal ve int veri tipleri arasındaki performans farkını, Ali’nin parasını 1.000.000 TL’ye çıkardığımız aşağıdaki program çıktılarında görebiliriz.

Program Çıktısı(BigDecimal):

Ali'nin cebinde, 1000000.0 TL'si vardir.
Bakkaldaki sekerin tanesi 0.1 TL'dir.
Hesaplamamiza gore, Ali bakkaldan 10000000 seker alabilir ve cebinde de 0.0 TL'si kalir.
islem suresi: 460 ms

Program Çıktısı(int):

Ali'nin cebinde, 1000000.0 TL'si vardir.
Bakkaldaki sekerin tanesi 0.1 TL'dir.
Hesaplamamiza gore, Ali bakkaldan 10000000 seker alabilir ve cebinde de 0.0 TL'si kalir.

ali kemal taşçı

4 Aralık 2015 Cuma

Java Notları – Overloading

0 yorum
English version is at DZone

Java 1.5  öncesinde primitive tipler, reference tiplerinden tamamen farklıydı. Ama artık autoboxing kavramıyla, bu farkın ortadan kalkması, birçok sorunu da beraberinde getirmiştir.
Bu sorunlardan birini aşağıdaki kod parçası ile açıklayalım:


import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; public class SetAndList{ public static void main(String[] args) { Set<Integer> set = new TreeSet<Integer>(); List<Integer> list = new ArrayList<Integer>(); for (int i = -2; i < 3; i++) { set.add(i); list.add(i); } for (int i = 0; i < 3; i++) { set.remove(i); list.remove(i); } System.out.println(set + " " + list); } }

Birçoğumuz yukarıdaki kod parçasının çıktısının aşağıdaki gibi olacağını düşünebiliriz:
[-2, -1] [-2, -1]
Ama maalesef kod düşündüğümüzden farklı bir şekilde çalışarak, aşağıdaki çıktıyı üretecektir:
[-2, -1] [-1, 1]
Yukarıdaki durumun, Java 1.5 ile hayatımıza giren “autoboxing” özelliği ile “overloading” kavramlarının dikkatsiz kullanımından kaynaklandığını söyleyebiliriz.
Yukarıdaki koddaki “set.remove(i)” satırı ile, “remove(E)” methodu çağrılmaktadır. Buradaki “E”, set’in element tipi olan “Integer”’dır ve int değeri auotoboxing ile Integer değere çevrilir. Bu da beklediğimiz bir durumdur, yani kod set’in içindeki pozitif değerleri set’ten çıkarır.
Diğer taraftan, koddaki “list.remove(i)” satırı, overload edilen “remove(int i)” methodunu çağırır. Bu method ise, listedeki belirtilen posizyondaki değeri siler. Yani yukarıdaki kod, "list” ’ in içeriğinin [ -2, -1, 0, 1, 2 ] olduğu durumda, önce “0” pozisyonundaki, sonra “1” ve sonra da “2” poziyonundaki değeri listeden çıkarır. Sonunda listenin içeriği [-1, 1] olarak kalır. Buradaki karışıklığı önlemek için, “list.remove(i)” satırındaki i’yi Integer’a cast ederek doğru overload edilmiş methodun çağrılmasını sağlayabiliriz.

for (int i = 0; i < 3; i++) { set.remove(i); list.remove((Integer)i); // veya list.remove(Integer.valueOf(i)); }


Bu düzeltmeden sonra, kodumuz beklediğimiz gibi aşağıdaki çıktıyı verebilir:
[-2, -1] [-2, -1]
Yukarıdaki karışıklığın nedeni, compiler’ın List interface’inin overload edilen iki adet remove methodunu(remove(E) ve remove(int)) autoboxing özelliği nedeniyle karıştırmış olmasından kaynaklanmaktadır. Java 1.5 sonrası, generic ve autoboxing kavramları, List interface’ini bozdu diyebiliriz.
Buradan çıkaracağımız dersleri aşağıdaki gibi özetleyebiliriz:

  • Java class’larımızdaki method’larımızı overload edebiliyor olmamız, onları overload etmemiz gerektiği anlamına gelmemektedir.

  • Genel olarak, aynı sayıda parametre içeren methodlarımızı overload etmekten kaçınmamız, yani methodlarımız için anlamlı ve farklı isimler kullanmamız gerekmektedir. İlla bu şekilde yapmamız gerekiyorsa da, methodların aynı parametrelerde, aynı davranışı göstermelerine dikkat etmemiz gerekmektedir.

Yukarıdaki maddelere dikkat etmediğimiz durumda, methodları kullanacak kişilere çok dikkatli olmaları gerektiğini anlatmamız gerekecektir. Aksi halde methodların neden çalışmadıklarını anlamakta zorlanacaklardır.

Yararlandığım Kaynaklar
Efective Java 2nd Edition, J. Bloch

ali kemal taşçı

7 Ekim 2015 Çarşamba

Java Performans Notları: Autoboxing / Unboxing

0 yorum
English version is at DZone

Aşağıdaki kod parçasında sadece 1 karakteri değiştirerek, işlem süresini yaklaşık 5’te 1’ine indirebileceğimizi söylesem ne düşünürsünüz?

long t = System.currentTimeMillis(); Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println("toplam:" + sum); System.out.println("işlem süresi: " + (System.currentTimeMillis() - t) + " ms") ;

Çıktı:
toplam:2305843005992468481
işlem süresi:  6756 ms



Yukarıdaki kod parçası hakkında biraz düşündükten sonra, aşağıdaki daha hızlı çalışan kod parçasını inceleyebilirsiniz.

long t = System.currentTimeMillis(); //Long sum = 0L; long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println("toplam:" + sum); System.out.println("işlem süresi: " + (System.currentTimeMillis() - t) + " ms") ;

Çıktı:
toplam:2305843005992468481
işlem süresi: 1248 ms

Yukarıdaki hız farkının, Java 1.5 ile hayatımıza giren “Autoboxing” özelliğinin dikkatsiz kullanımından kaynaklandığını söyleyebiliriz.

Farkın nedenine devam etmeden, öncelikle Java’da “Autoboxing” ve “Unboxing” kavramını açıklamaya çalışalım. 

Java’da tipler primitive ve referans olmak üzere ikiye ayrılmaktadır. Java’da 8 adet primitive tip bulunmaktadır. Her primitive tipe karşılık gelen de bir referans tipi(wrapper class) vardır. 

Primitive tipler
Referans Tipler(Wrapper Class)
boolean
Boolean
byte
Byte
char
Character
float
Float
int
Integer
long
Long
short
Short
double
Double

Aşağıdaki örnek kod parçasında autoboxing ve unboxing kavramlarına birer örnek görebilirsiniz. Burada Long bekleyen bir ArrayList’e long bir değer eklenmektedir. Java 1.4’te bu işlemin yapılabilmesi için primitive değerin uygun bir referans tip içine eklenerek(boxing) yapılması gerekirdi. Java 1.5 itibariyle bu işlemi compiler bizim için yapmaktadır. Böylece fazladan kod yazmak zorunda kalmıyoruz.

Yani aşağıdaki kod;

List<Long> longList = new ArrayList<>(); long i = 4; longList.add( i ); //autoboxing long j = longList.get( 0 ); //unboxing

compiler tarafından otomatik olarak aşağıdaki koda çevrilmektedir.

List<Long> longList = new ArrayList<>(); long i = 4; longList.add(Long.valueOf( i ) ); long j = longList.get( 0 ).longValue();

Buradan ilk kodumuzun da aslında aşağıdaki gibi bir koda çevrildiğini söyleyebiliriz. Bu işlemin uzun sürmesinin nedenini de kod çalışırken, 2147483647 adet gereksiz Long instance’ı oluşturmaya çalışması olarak açıklayabiliriz.

long t = System.currentTimeMillis(); Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += new Long(i); } System.out.println("toplam:" + sum); System.out.println("işlem süresi: " + (System.currentTimeMillis() - t) + " ms") ;

Çıktı:
toplam:2305843005992468481
işlem süresi: 6764 ms

Sonuç olarak, daha hızlı çalışacak Java kodları yazmak için, autoboxing ve unboxing durumlarını dikkate almalıyız. Gereksiz instance oluşturma işlemlerinden kaçınmalı, hesap gerektiren çoklu işlemlerde primitive tipleri tercih etmeliyiz diyebiliriz.

Yararlandığım Kaynaklar
Autoboxingand Unboxing
Autoboxing


ali kemal taşçı

26 Mayıs 2015 Salı

SQL Notları: İsimlendirme Kuralları/Standartları (Naming Convention)

0 yorum
Öncelikle, yaşanmış ve gereksiz zaman almış aşağıdaki gibi bir problemi inceleyerek başlayalım:

Problemimize konu olan db tablomuz aşağıdaki gibi:

CREATE TABLE T_CHAT_UPDATE_LOOKUP ( ID NUMBER, OPERATOR_NAME VARCHAR2(100 BYTE) NOT NULL, SCENARIO_LIST VARCHAR2(1000 BYTE) NOT NULL, OG_ROUTE_LIST VARCHAR2(1000 BYTE) NOT NULL, START_DATE DATE NOT NULL, END_DATE DATE DEFAULT to_date('31.12.9999','dd.mm.yyyy'), CREATED_DATE DATE DEFAULT SYSDATE, CREATED_BY VARCHAR2(50 CHAR) DEFAULT USER NOT NULL, MODIFIED_DATE DATE, MODIFIED_BY VARCHAR2(50 CHAR) );
İçindeki örnek datalar da aşağıdaki gibi:



Aşağıdaki fonksiyonumuzla, verilen tarih aralığında geçerli olan, parametre sayısı bulunmak istenmekte:
create or replace function get_count(start_date varchar2, end_date varchar2) return number is l_count number; begin select count(*) into l_count from T_CHAT_UPDATE_LOOKUP c where c.start_date <= TO_DATE(start_date, 'yymmdd') and nvl(c.end_date, to_date('991231', 'yymmdd')) >= TO_DATE(end_date, 'yymmdd'); return l_count; end;

Bu fonksiyon aşağıdaki gibi çalıştırılmak istendiğinde:
declare l_result number; begin l_result := get_count('150501','150501'); dbms_output.put_line(l_result); end;

Şu hata alınmakta:

Fonsiyon aşağıdaki iki şekildeki gibi değiştirildiğinde ise hata alınmamaktadır.
Buradaki sorunun to_date çevrimlerinden kaynaklandığı anlaşılmaktadır.

Peki start_date ve end_date parametreleri to_date fonksiyonunun beklediği varchar2 tipinde sayısal karakterlerden oluşuyor iken ve sorgudan önce hiçbir şekilde değişikliğe uğratılmadığına göre, yukarıdaki hatayı neden alırız? 

1.
create or replace function get_count(start_date varchar2, end_date varchar2) return number is l_count number; begin select count(*) into l_count from T_CHAT_UPDATE_LOOKUP c; --where c.start_date <= TO_DATE(start_date, 'yymmdd') -- and nvl(c.end_date, to_date('991231', 'yymmdd')) >= TO_DATE(end_date, 'yymmdd'); return l_count; end;

2.
create or replace function get_count(start_date varchar2, end_date varchar2) return number is l_count number; begin select count(*) into l_count from T_CHAT_UPDATE_LOOKUP c where c.start_date <= TO_DATE('150501', 'yymmdd') and nvl(c.end_date, to_date('991231', 'yymmdd')) >= TO_DATE('150501', 'yymmdd'); return l_count; end;

Bu hata ile ilgil biraz düşündükten sonra, aşağıdaki çözümü inceleyebilirsiniz.

Çözüm:


Aslında basit olan bu problemin nedenini şöyle açıklayabiliriz:

Aşağıdaki “get_count” fonksiyonumuzun içindeki sorguda kullanılan varchar2 “start_date” ve “end_date” parametrelerinin, algısal olarak, date’e çevrilip sorguda kullanılması gerektiğini düşünürüz.
Ama gerçekte, sorgu bu şekilde gerçekleşmemektedir.

Gerçekte olan ise, sorguda önce date tipindeki “start_date” ve “end_date” kolonları yine date’e çevrilmeye çalışılmaktadır.
Çözüm olarak ise, “get_count” fonksiyonunda aşağıdaki değişiklikleri yapmamız gerekir.
Aslında baştan bu fonksiyonu yazarken, belki de pek önemsenmeyen, yazılım standartlarındaki isimlendirme kurallarına/standartlarına(naming conventions) göre kod yazmaya çalışsak, bu problemlerle karşılaşmaz ve çözümünde de gereksiz zaman harcamazdık.
create or replace function get_count(p_start_date varchar2, p_end_date varchar2) return number is l_count number; begin select count(*) into l_count from T_CHAT_UPDATE_LOOKUP c where c.start_date <= TO_DATE(p_start_date, 'yymmdd') and nvl(c.end_date, to_date('991231', 'yymmdd')) >= TO_DATE(p_end_date, 'yymmdd'); return l_count; end;


Bu vesileyle değişken isimlendirme ile ilgili aşağıdaki bağlantıları da paylaşmak isterim:

Java Kod İsimlendirme ve Şekil (Format) Standardı - http://www.javaturk.org/?p=3180  
Kaliteli Java Kodu Nasıl Yazılır? ya da Kaliteli Kodun Temel İlkeleri - http://www.javaturk.org/?p=3231


ali kemal taşçı

28 Ocak 2015 Çarşamba

SQL Notları: SQL - LIKE Sorgularında Yüzde( percent - '%') ve Alt Çizgi ( underscore - '_' ) Kullanımı

0 yorum
SQL sorgularımızda kullandığımız like ifadesinde “_” , “%” karakterlerini wildcard olarak değil de sorgu karakteri olarak kullanmak istediğimizde bunları escape karakteri ile belirtmeliyiz.

Örneğin, aşağıdaki sorguda “TYPES_” ile başlayan tabloları listelemek istediğimizde, aşağıdaki sorgudaki “_” karakteri wildcard karakteri olarak görüleceğinden istediğimiz sonuç döndürülmeyecektir.

select * from all_tables where table_name like 'TYPES_%';



Bunun için sorgumuzu aşağıdaki şekilde değiştirmeliyiz:

select * from all_tables where table_name like 'TYPES\_%' ESCAPE '\';
 Benzer şekilde içinde “intercon_cdr%rowtype” geçen kodları aramak için de aşağıdaki sorgu istediğimiz sonucu döndürmeyecektir:

select * from dba_SOURCE where lower(text) like '%intercon_cdr%rowtype%'; 


Bunun için sorgumuzu aşağıdaki şekilde değiştirmeliyiz:

select * from dba_SOURCE where lower(text) like '%intercon_cdr\%rowtype%' ESCAPE '\';



Kaynaklar:

ali kemal taşçı