Friday, July 14, 2017

Java SE 7 Kodunu Java SE 9'a Taşımak


Bu yazıda Java SE 7 ve öncesinde yazılmış bir uygulamada yer alan dizi ve torbalar üzerinde gerçekleştirilmiş döngülerin Java SE 9'a taşınması ile ilgili bir örnek ele alınacaktır. Aşağıda UML sınıf çizgesi verilen alan modelini önce Java 7'de kodlayalım:




Animal.java:

package com.example.animals.domain;

/**
 * 
 * @author Binnur KURT (binnur.kurt@gmail.com)
 *
 */
public abstract class Animal {
 protected int legs;

 public Animal(int legs) {
  this.legs = legs;
 }

 public int getLegs() {
  return legs;
 }

 public void walk() {
  System.out.println("Animal is walking now...");
 }

 public abstract void eat();
}

Pet.java:

package com.example.animals.domain;

/**
 * 
 * @author Binnur KURT (binnur.kurt@gmail.com)
 *
 */
public abstract interface Pet {
 void setName(String name);

 String getName();

 public abstract void play();
}

Spider.java:

package com.example.animals.domain;

/**
 * 
 * @author Binnur KURT (binnur.kurt@gmail.com)
 *
 */
public class Spider extends Animal {

 public Spider() {
  super(8);
 }

 @Override
 public void eat() {
  System.out.println("Spider is eating now...");
 }

 @Override
 public String toString() {
  return "Spider [legs=" + legs + "]";
 }

}

Cat.java:

package com.example.animals.domain;

/**
 * 
 * @author Binnur KURT (binnur.kurt@gmail.com)
 *
 */
public class Cat extends Animal implements Pet {
 private String name;

 public Cat() {
  this("Garfield");
 }

 public Cat(String name) {
  super(4);
  this.name = name;
 }

 @Override
 public void setName(String name) {
  this.name = name;
 }

 @Override
 public String getName() {
  return name;
 }

 @Override
 public void play() {
  System.out.println(name + " is playing now...");
 }

 @Override
 public void eat() {
  System.out.println(name + " is eating now...");
 }

 @Override
 public String toString() {
  return "Cat [name=" + name + ", legs=" + legs + "]";
 }

}

Fish.java:

package com.example.animals.domain;

/**
 * 
 * @author Binnur KURT (binnur.kurt@gmail.com)
 *
 */
public class Fish extends Animal implements Pet {
 private String name;

 public Fish() {
  this("Nemo");
 }

 public Fish(String name) {
  super(0);
  this.name = name;
 }

 @Override
 public void setName(String name) {
  this.name = name;
 }

 @Override
 public String getName() {
  return name;
 }

 @Override
 public void play() {
  System.out.println(name + " is playing now...");
 }

 @Override
 public void eat() {
  System.out.println(name + " is eating now...");
 }

 @Override
 public void walk() {
  System.out.println(name + " is swimming now...");
 }

 @Override
 public String toString() {
  return "Fish [name=" + name + ", legs=" + legs + "]";
 }

}

Şimdi bu alan modelini kullanan bir uygulama kodunu Java 7'de kodlayalım:

package com.example.animals.application;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.example.animals.domain.Animal;
import com.example.animals.domain.Cat;
import com.example.animals.domain.Fish;
import com.example.animals.domain.Pet;
import com.example.animals.domain.Spider;

/**
 * 
 * @author Binnur KURT (binnur.kurt@gmail.com)
 *
 */
public class AnimalAppInJava7 {

    public static void main(String[] args) {
        List<Animal> animals = Arrays.asList(
            new Spider(), 
            new Cat(), 
            new Fish("Free Willy"), 
            new Cat("Tekir"),                        
            new Fish("Jaws"), 
            new Spider()
        );
        
        for (Animal animal : animals) {
            animal.walk();
            animal.eat();
            if (animal instanceof Pet) {
                ((Pet) animal).play();
            }
        }
        
        List<Animal> wildAnimals = new ArrayList<>();
        for (Animal animal : animals) {
            if (!(animal instanceof Pet))
                wildAnimals.add(animal);
        }
        System.out.println("Wild Animals: "+wildAnimals);        
        
        List<Pet> pets = new ArrayList<>();
        for (Animal animal : animals) {
            if (animal instanceof Pet)
                pets.add((Pet) animal);
        }
        System.out.println("Pets: "+pets);
        
        int maxLegs= -1;
        for (Animal animal: animals){
            final int legs = animal.getLegs();
            if (legs>maxLegs) maxLegs= legs;
        }
        System.out.println("max number of legs: "+maxLegs);
    }

}

Java SE 7'de yazdığımız uygulamada ağırlıklı olarak Java SE 5'de gelen for döngüsünü kullanarak torbalar üzerinde çalışan döngüler kurduk. Şimdi birebir aynı işi yapan uygulamayı Java SE 9'da kodlayalım:

package com.example.animals.application;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import com.example.animals.domain.Animal;
import com.example.animals.domain.Cat;
import com.example.animals.domain.Fish;
import com.example.animals.domain.Pet;
import com.example.animals.domain.Spider;

/**
 * 
 * @author Binnur KURT (binnur.kurt@gmail.com)
 *
 */
public class AnimalAppInJava9 {

    public static void main(String[] args) {
        List<Animal> animals= List.of(
            new Spider(),
            new Cat(),
            new Fish("Free Willy"),
            new Cat("Tekir"),
            new Fish("Jaws"),
            new Spider()
        );

        Class<Pet> clazz = Pet.class;
        Consumer<Animal> walk = Animal::walk;
        Consumer<Animal> eat = Animal::eat;
        Consumer<Animal> playIfPet = animal -> {
            if (clazz.isInstance(animal)) 
                clazz.cast(animal).play();
        };
        animals.forEach(walk.andThen(eat).andThen(playIfPet));
        
        Predicate<? super Animal> isPet = animal -> animal instanceof Pet;
        animals.stream().filter(isPet).map(Pet.class::cast).forEach(Pet::play);

        List<Animal> pets= animals.stream()
                                  .filter(isPet)
                                  .collect(Collectors.toList());
        System.out.println("Pets: "+pets);
        
        Predicate<? super Animal> isWild= isPet.negate();
        List<Animal> wildAnimals= animals.stream()
                                    .filter(isWild)
                                    .collect(Collectors.toList());
        System.out.println("Wild animals: "+wildAnimals);

        animals.stream().map(Animal::getLegs)
                        .max(Integer::compare)
                        .ifPresent(System.out::println);
    }

}

Java 7 ve öncesinde uzun uzun yazdığımız döngüleri Java 9'da tek satırlık ifadelerle karşılamak mümkün:
  • Önce hayvanları yürüyüşe çıkaralım, ardından karınlarını doyuralım ve evcil olanları ile oyun oynayalım:
animals.forEach(walk.andThen(eat).andThen(playIfPet));
  • Evcil hayvanlar ile oyun oynayalım:
animals.stream().filter(Pet.class::isInstance).map(Pet.class::cast).forEach(Pet::play);
  • Vahşi hayvanların listesini alalım:
animals.stream().filter(isWild).collect(Collectors.toList());
  • Evcil hayvanların listesini alalım:
animals.stream().filter(isPet).collect(Collectors.toList());
  • En çok ayağa sahip hayvanda kaç adet ayak bulunur:
animals.stream().map(Animal::getLegs).max(Integer::compare).ifPresent(System.out::println);
  • Hayvanların toplam ayak sayısı:
int numberOfLegs= animals.stream().mapToInt(Animal::getLegs).sum();

Tek Satırlık İfadeler

Java SE 8'de gelen Fonksiyonel Programlama, Stream API ve MapReduce çatıları sayesinde çözümü tek satırlık ifadelerle oluşturabiliyoruz. Bu tek satırlık ifadelere örnekler oluşturabilmek amacıyla yukardıki örnek alan modelini kullanarak, farklı problemler çözelim.  

1. Java 9'da rastgele olarak 100 Animal türünden nesne ile dolu bir liste elde etmeye çalışalım:

List<Supplier<Animal>> constructors= List.of(Spider::new,Cat::new,Fish::new);
List<Animal> randomAnimals= 
          new Random().ints(0,constructors.size())
                      .limit(100)
                      .mapToObj(constructors::get)
                      .map( Supplier::get )
                      .collect( Collectors.toList() );
randomAnimals.forEach(System.out::println);

2. Her bir Animal türünden kaç adet nesne var sınıflandırmaya çalışalım:

Function<Animal, String> groupByClassName = animal -> animal.getClass().getSimpleName();
Map<String, Long> animalCountsByClassName = 
        randomAnimals.stream()
               .collect(
                   Collectors.groupingBy( groupByClassName, Collectors.counting() )
               );
System.out.println(animalCountsByClassName);

Yukarıdaki kod parçasını çalıştırdığımızda ekranda aşağıdaki gibi bir ileti alıyoruz:

{Cat=36, Spider=35, Fish=29}

3. Aynı ayak sayısına sahip hayvanların listesini alalım:

Function<Animal, String> classNameMapper = animal -> animal.getClass().getSimpleName();
Map<Integer, Set<String>> animalCountsByLegs = 
    randomAnimals.stream().collect(
        Collectors.groupingBy(
            Animal::getLegs,
            Collectors.mapping(classNameMapper,Collectors.toSet())
        )
    );        
System.out.println(animalCountsByLegs);

Yukarıdaki kod parçasını çalıştırdığımızda ekranda aşağıdaki gibi bir ileti alıyoruz:

{0=[Fish], 4=[Cat], 8=[Spider]}

4. Hayvanları önce ayak sayısına göre, ayak sayıları eşit olanları ise sınıf adına göre sıralı olacak şekilde ekrana yazmak istiyoruz:

final Comparator<Animal> compareByLegNumbers = (left,right) -> left.getLegs() - right.getLegs();
final Comparator<Animal> compareByClassName = (left,right) -> left.getClass().getSimpleName().compareTo(right.getClass().getSimpleName());
randomAnimals.stream().sorted(compareByLegNumbers.thenComparing(compareByClassName)).forEach(System.out::println);

5. Dört ayaklı evcil hayvanlar içerisinde en kalabalık türün adını bulmaya çalışalım:

final Predicate<? super Animal> isPet = animal -> animal instanceof Pet;        
final Comparator<Animal> compareByClassName = (left,right) -> left.getClass().getSimpleName().compareTo(right.getClass().getSimpleName());
final Function<Animal, String> classNameMapper = animal -> animal.getClass().getSimpleName();
final Comparator<Animal> compareByLegNumbers = (left,right) -> left.getLegs() - right.getLegs();
final Collector<Animal, ?, Map<String, Long>> clusterByAnimalTypeAndCountClusterSize = Collectors.groupingBy(classNameMapper,Collectors.counting());
final Comparator<Map.Entry<String, Long>> clusterSizeComparator = (e1,e2) -> Long.valueOf(e1.getValue() - e2.getValue()).intValue();
randomAnimals.stream()
             .filter(isPet)
             .collect(clusterByAnimalTypeAndCountClusterSize)
             .entrySet()
             .stream()
             .max(clusterSizeComparator)
             .map(Map.Entry::getKey)
             .ifPresent(System.out::println);

Sunday, June 18, 2017

Java 9'da Görüntü Dosyaları ile Çalışmak


Java 9 ile birlikte görüntü dosyalarına erişim ile ilgili olarak iki önemli yenilik bizi bekliyor:
  • Image IO paketi ile PNG ve JPEG formatlarında görüntü dosyalarını okuyup, yazabiliyorduk. Artık buna çok sayfalı TIFF formatı da eklendi. Bu tür görüntü dosyalarını artık Java Advanced Imaging gibi üçüncü parti bir kütüphane kullanmadan erişebiliyoruz.
  • İkinci yenilik ise bir görüntünün farklı çözünürlükteki kopyaları ile çalışmayı kolaylaştırıyor. Uygulama içinden elimizdeki ekran ya da görüntü işleme algoritması için en uygun çözünürlükteki görüntüye basit bir şekilde ulaşabiliyoruz. 
Örnek uygulamada Roma'nın farklı çözünürlükte çekilmiş görüntüleri ile çalışacağız:
  • rome-1.jpg: 4098 x 4177
  • rome-2.jpg: 2049 x 2089
  • rome-3.jpg: 1025 x 1045
  • rome-4.jpg: 513 x 523

package com.example.java9.features;

import java.awt.Image;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.BufferedImage;
import java.awt.image.MultiResolutionImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class MultiResolutiomImageExample {
    public static void main(String[] args) {
        List<String> imageFileNames = List.of("rome-4.jpg","rome-3.jpg","rome-2.jpg","rome-1.jpg");
        final Function<String, BufferedImage> mapToBufferedImage = file -> {
            try {
                return ImageIO.read(new File("build/classes",file));
            } catch (IOException e) {
                return null;
            }
        };
        List<BufferedImage> images = imageFileNames.stream()
                                                   .map(mapToBufferedImage)
                                                   .filter(Objects::nonNull)
                                                   .collect(Collectors.toList());
        System.err.println("images.size():"+images.size());
        MultiResolutionImage multiResolutionImage = new BaseMultiResolutionImage(images.toArray(new Image[0]));
        List<Image> imageVariants = multiResolutionImage.getResolutionVariants();
        System.out.println("number of images: " + imageVariants.size());        
        imageVariants.forEach(System.out::println);
        final Image image1 = multiResolutionImage.getResolutionVariant(1024, 768);
        System.out.println(image1);
        final Image image2 = multiResolutionImage.getResolutionVariant(2049, 2089);
        System.out.println(image2);
       
    }
}

Yukarıda örnek uygulamada Roma'nın dört farklı çözünürlükteki görüntüsünü dosyalardan okuduktan sonra MultiResolutionImage sınıfının yönetimine bırakıyoruz. MultiResolutionImage kurucu fonksiyonu bu görüntüleri BufferedImage tipinden bir dizide en düşük çözünürlükten en yüksek çözünürlüğe doğru sıralı olacak şekilde bizden bekliyor. En uygun çözünürlükte görüntüye ulaşmak için ise MultiResolutionImage sınıfının getResolutionVariant() metodunu kullanıyoruz:
  • multiResolutionImage.getResolutionVariant(1024, 768) çağrısı 1025 x 1045 çözünürlüğe sahip görüntüyü dönerken 
  • multiResolutionImage.getResolutionVariant(2049,2089) çağrısı 2049 x 2089 çözünürlüğe sahip görüntüyü döndü.
Şimdi ise çok sayfalı TIFF dokümanı okuyan basit bir uygulama geliştireceğiz. Bu uygulamada kullanacağımız çok sayfalı TIFF, bir önceki örnekte kullandığımız yukarıda değerleri verilen dört farklı çözünürlükteki görüntüyü içeriyor.

package com.example.java9.features;

import java.awt.Image;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.BufferedImage;
import java.awt.image.MultiResolutionImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.*;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class MultiPageTiffExample {

    public static ImageReader tiffReader;

    public static Optional<BufferedImage> read(int index) {
        try {
            return Optional.of(tiffReader.read(index));
        } catch (IOException ex) {
            return Optional.empty();
        }
    }

    public static void main(String[] args) throws FileNotFoundException, IOException {
        InputStream stream = new FileInputStream(new File("build/classes", "/image.tif"));
        tiffReader = ImageIO.getImageReadersByFormatName("tiff").next();

        ImageInputStream input = ImageIO.createImageInputStream(stream);

        tiffReader.setInput(input);

        final int numOfImages = tiffReader.getNumImages(true);
        System.out.println("# of pages: " + numOfImages);

        List<BufferedImage> images = IntStream.range(0, numOfImages)
                .mapToObj(MultiPageTiffExample::read)
                .filter(Optional::isPresent)
                .flatMap(Optional::stream)
                .collect(Collectors.toList());
        MultiResolutionImage multiResolutionImage = new BaseMultiResolutionImage(images.toArray(new Image[0]));
        List<Image> imageVariants = multiResolutionImage.getResolutionVariants();
        System.out.println("number of images: " + imageVariants.size());
        imageVariants.forEach(System.out::println);
        final Image image1 = multiResolutionImage.getResolutionVariant(1024, 768);
        System.out.println(image1);
        final Image image2 = multiResolutionImage.getResolutionVariant(2049, 2089);
        System.out.println(image2);
    }
}

TIFF formatında görüntü dosyalarını okumak için kullanacağımız nesneyi ImageIO.getImageReadersByFormatName("tiff").next() ile alıyoruz.  Uygulamaların kodlarına bu bağlantıdan ulaşabilirsiniz.