---
title: 'Paketleme ve Çalıştırılabilir Çıktı'
source: 'https://academia.sh/tr/kurslar/java-ekosistem/paketleme-ve-calistirilabilir-cikti'
course: 'Derleme, Test ve Ekosistem'
language: tr
updated: '2026-08-23T14:25:06+00:00'
license: 'CC BY-SA 4.0'
---

# Paketleme ve Çalıştırılabilir Çıktı

Aynı arşivleme çağrısı hem kendine yeten bir arşiv hem de dışarıdan bir sınıfa muhtaç bir arşiv üretir: birincisi koşum sınıf yolunda tek başına yeterlidir, ikincisi tek başına NoClassDefFoundError ile düşer ve yalnız eksik dizin eklendiğinde çalışır. Giriş noktası da kaynakta değil arşivin kendi bildiriminde durur: aynı sınıflardan üretilen bildirimsiz bir arşivde bu bilgi hiç yoktur.

Önceki ders bir modül sınırının derlemedeki karşılığının bir sınıf yolu ve yükleyici
kararı olduğunu ölçtü: sınır ne kaynağın dizin yapısına ne paket adına bağlıydı, yalnız
tüketen tarafa hangi sınıf dosyalarının verildiğine bağlıydı. Ama bu ölçümün tamamı hâlâ
dağınık dizinler üzerinden yürüdü — koşum sınıf yolu, birbirinden ayrı, birer birer
oluşturulan dizinlerin bir listesiydi. Bu ders o listeyi taşınabilir tek bir ürüne
çeviriyor: derlenmiş sınıflar bir **arşive** konuyor, ve arşivin kendisi artık koşum sınıf
yolunun tek bir öğesi olarak taşınabiliyor.

İlk derste bir bildirimden türeyen evre dizisi dört evreyle sınırlıydı: temizleme, kaynak
derleme, derleme sınıf yolunu kurma, koşum sınıf yolunu kurma. Paketleme, bu dizinin
doğal bir uzantısı — kaynak derlendikten, iki sınıf yolu kurulduktan sonra, koşum sınıf
yolunun bir bölümünü tek bir taşınabilir dosyaya sıkıştıran beşinci bir adım. Bu adımın
girdisi hep aynı: daha önce derlenmiş sınıf dosyalarının bulunduğu bir dizin. Çıktısı ise
şimdiye kadar hiç görülmemiş bir şey: kendi başına açılıp okunabilen, kendi içinde bir
**giriş noktası** taşıyabilen tek bir dosya — **çalıştırılabilir çıktı**.

## Arşivleme ve Bildirim

Bir arşiv, bir dizindeki sınıf dosyalarının tek bir dosyaya paketlenmiş hâlidir; **arşivleyici**
(standart kitaplığın bir parçası, program içinden çağrılan bir araç sağlayıcısı) bu paketlemeyi
gerçekten yapıyor. Arşivleyiciye verilen tek girdi, hangi dizinin paketleneceği ve
isteğe bağlı olarak bir **giriş noktası** adı; arşivleyici bu ikisinden bir arşiv dosyası
ve bir **bildirim** (manifest) üretiyor.

- **DR20.** Arşivleyici çağrısı, sınıf dosyalarının bulunduğu dizini olduğu gibi
  paketliyor; hiçbir bağımlılık dizini bu çağrıya eklenmiyor. Paketlenen şey yalnız
  verilen dizinin içeriği.

Aşağıdaki `arsivle` yöntemi bu tek derleme görevini üç parametreyle çalıştırıyor: hangi
arşiv dosyasının üretileceği, hangi dizinin paketleneceği, ve isteğe bağlı olarak hangi
sınıfın giriş noktası sayılacağı. Bu üçü dışında hiçbir bilgi arşivleme çağrısına
girmiyor — özellikle hiçbir bağımlılık listesi, hiçbir başka sınıf yolu. Bu kısıtlılık
kasıtlı: bu dersin ikinci yarısı tam olarak bu kısıtlılığın sonucunu ölçüyor.

```java
// PaketYardimci.java — kaynak yazma, derleme ve arsivleme icin ortak islemler
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class PaketYardimci {
    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, Path... kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessiz = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static void arsivle(Path jarYolu, Path siniflarDizini, String anaSinif) throws IOException {
        Files.createDirectories(jarYolu.getParent());
        List<String> arg = new ArrayList<>(List.of("--create", "--file", jarYolu.toString()));
        if (anaSinif != null) { arg.add("--main-class"); arg.add(anaSinif); }
        arg.add("-C"); arg.add(siniflarDizini.toString()); arg.add(".");
        ToolProvider arsivleyici = ToolProvider.findFirst("jar").orElseThrow();
        StringWriter sessiz = new StringWriter();
        arsivleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
    }

    static String kos(List<Path> sinifYolu, String anaSinif) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, PaketYardimci.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return "tamam";
        } catch (java.lang.reflect.InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }
}
```

`arsivle` yalnız iki şey yapıyor: verilen dizini paketliyor, ve verilirse bir giriş noktası
adını bildirime yazıyor. `kos` önceki derslerdeki aynı işlemi yapıyor — tek farkla, sınıf
yolundaki bir öğe artık bir dizin değil, bir arşiv dosyası olabiliyor; `URLClassLoader` bir
arşiv dosyasının konumunu da bir dizin gibi kabul ediyor.

Bildirim, arşivin içinde ayrı bir kayıt olarak duruyor — sınıf dosyalarının yanında, ama
onlardan biri değil. Bir bildirim birçok alan taşıyabilir; bu ders yalnız birini
kullanıyor: Ana-Sınıf. Bu alan bir sınıf adını (bir dizgiyi) taşıyor, hiçbir çalıştırılabilir
kod içermiyor. Bildirimi okuyan taraf — burada bizim kendi kodumuz, gerçek bir kurulumda
ise bir çalıştırıcı — bu dizgiyi alıyor, o adı taşıyan sınıfı arşivin içinden buluyor ve
onun `main` yöntemini çağırıyor. Giriş noktasının kendisi hâlâ sıradan bir Java sınıfı;
değişen tek şey, o sınıfın adının nereden okunduğu.

## Giriş Noktası Kaynakta Değil, Bildirimde

Bir arşiv onlarca sınıf taşıyabilir, ve bunların yalnız biri (ya da hiçbiri) bir `main`
yöntemi içerebilir. Arşivin kendisini çalıştırmak isteyen taraf — bir çalıştırıcı, ya da
bu derste bizim kendi kodumuz — hangi sınıftan başlayacağını bilmek zorunda, ama bu
bilgiyi arşivin içindeki sınıf dosyalarını tek tek tarayarak bulmuyor. Aradığı yer sabit:
arşivin bildirimindeki tek bir alan.

- **DR21.** Giriş noktası bilgisi yalnız bildirimin Ana-Sınıf (`Main-Class`) alanına
  bakılarak okunuyor; kaynak dosyalarının hiçbirinde bu bilgi aranmıyor.

```java
// CalistirilabilirCikti.java — arsivleyici gercekten cagirilir, giris noktasi bildirimde durur, arsiv bagimliliklari icermez
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.jar.*;

public class CalistirilabilirCikti {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("paket");
        Path kaynak = kok.resolve("kaynak");

        Path sBagimsiz = PaketYardimci.yaz(kaynak, "Bagimsiz",
                "public class Bagimsiz { public static void main(String[] a) { } }");
        Path bagimsizD = kok.resolve("bagimsizD");
        PaketYardimci.derle(bagimsizD, List.of(), sBagimsiz);

        Path bildirimliJar = kok.resolve("bildirimli.jar");
        PaketYardimci.arsivle(bildirimliJar, bagimsizD, "Bagimsiz");

        Path bildirimsizJar = kok.resolve("bildirimsiz.jar");
        PaketYardimci.arsivle(bildirimsizJar, bagimsizD, null);

        System.out.println("-- giris noktasi kaynakta degil, arsivin bildiriminde --");
        try (JarFile jf = new JarFile(bildirimliJar.toFile())) {
            String ana = jf.getManifest().getMainAttributes().getValue("Main-Class");
            System.out.println("bildirimli arsivin giris noktasi   : " + ana);
        }
        try (JarFile jf = new JarFile(bildirimsizJar.toFile())) {
            Manifest m = jf.getManifest();
            String ana = (m == null) ? null : m.getMainAttributes().getValue("Main-Class");
            System.out.println("bildirimsiz arsivin giris noktasi  : " + ana);
        }
        System.out.println("bildirimli arsiv, arsivin kendisinden kosuyor mu: "
                + PaketYardimci.kos(List.of(bildirimliJar), "Bagimsiz"));

        System.out.println();
        System.out.println("-- sinirlayici olcum: arsiv bagimliliklarini icermez --");

        Path sYardimci = PaketYardimci.yaz(kaynak, "Yardimci",
                "public class Yardimci { public static String yardim() { return \"yardimci\"; } }");
        Path sMuhtac = PaketYardimci.yaz(kaynak, "Muhtac",
                "public class Muhtac { public static void main(String[] a) { Yardimci.yardim(); } }");
        Path muhtacD = kok.resolve("muhtacD");
        PaketYardimci.derle(muhtacD, List.of(), sYardimci, sMuhtac);

        Path sadeceMuhtacDizini = kok.resolve("sadeceMuhtac");
        Files.createDirectories(sadeceMuhtacDizini);
        Files.copy(muhtacD.resolve("Muhtac.class"), sadeceMuhtacDizini.resolve("Muhtac.class"));
        Path muhtacJar = kok.resolve("muhtac.jar");
        PaketYardimci.arsivle(muhtacJar, sadeceMuhtacDizini, "Muhtac");

        Path yardimciD = kok.resolve("yardimciD");
        Files.createDirectories(yardimciD);
        Files.copy(muhtacD.resolve("Yardimci.class"), yardimciD.resolve("Yardimci.class"));

        String bagimsizSonuc = PaketYardimci.kos(List.of(bildirimliJar), "Bagimsiz");
        String muhtacTekBasina = PaketYardimci.kos(List.of(muhtacJar), "Muhtac");
        String muhtacArtiYardimci = PaketYardimci.kos(List.of(muhtacJar, yardimciD), "Muhtac");

        System.out.printf("%-48s %s%n", "Bagimsiz arsivi, koşum sınıf yolu yalniz arsiv:", bagimsizSonuc);
        System.out.printf("%-48s %s%n", "Muhtac arsivi, koşum sınıf yolu yalniz arsiv:", muhtacTekBasina);
        System.out.printf("%-48s %s%n", "Muhtac arsivi, Yardimci dizini eklenince:", muhtacArtiYardimci);
    }
}
```

```
-- giris noktasi kaynakta degil, arsivin bildiriminde --
bildirimli arsivin giris noktasi   : Bagimsiz
bildirimsiz arsivin giris noktasi  : null
bildirimli arsiv, arsivin kendisinden kosuyor mu: tamam

-- sinirlayici olcum: arsiv bagimliliklarini icermez --
Bagimsiz arsivi, koşum sınıf yolu yalniz arsiv:  tamam
Muhtac arsivi, koşum sınıf yolu yalniz arsiv:    NoClassDefFoundError
Muhtac arsivi, Yardimci dizini eklenince:        tamam
```

İlk üç satır giriş noktasının nerede durduğunu ölçüyor. Aynı `Bagimsiz` sınıfından iki
arşiv üretiliyor: biri bir giriş noktasıyla (`--main-class Bagimsiz`), öbürü hiç giriş
noktası verilmeden. Bildirimli arşivin bildirimi okunduğunda Ana-Sınıf alanı `Bagimsiz`
değerini taşıyor; bildirimsiz arşivin bildiriminde bu alan hiç yok, okunan değer `null`.
İki arşiv de aynı sınıf dosyasını taşıyor, aynı derlemeden geliyor — aralarındaki tek fark
bildirimdeki bir satır. Kaynak dosyasının kendisinde giriş noktası diye bir bilgi hiç yok;
`Bagimsiz.java` ne "ben giriş noktasıyım" der ne de böyle bir işaretleyici taşır. Bu bilgi
yalnız arşivleme anında, arşivin kendi bildirimine yazılıyor — ve üçüncü satırın gösterdiği
gibi, arşiv bu bildirimi kullanarak kendi başına koşabiliyor.

Bu, Java Temelleri kursunda kurulan giriş noktası kavramının bir katman ötesi. Orada
giriş noktasının imzası (bir `main` yönteminin biçimi) sabitti; burada sorulan soru farklı:
birden çok sınıfın bulunduğu bir arşivde, **hangi** sınıf giriş noktası? Bu sorunun yanıtı
artık kaynakta değil, üzerinde çalışılan arşivin kendi bildiriminde. Bildirimsiz arşiv de
geçerli bir arşiv — sınıf dosyalarını taşıyor, açılabiliyor, içeriği okunabiliyor — ama
"hangi sınıftan başlanacağı" sorusunun yanıtı o arşivde yok. Bu eksiklik yalnız bildirim
okunduğunda görünüyor; arşivin kendisi bunu hiçbir yerde açıkça söylemiyor, `null` dönen
bir alan olarak duruyor.

## Sınırlayıcı Ölçüm: Arşiv Bağımlılıklarını İçermez

- **DR22.** `Bagimsiz` arşivi hiçbir dış tipi anmıyor; `Muhtac` arşivi ise kendi
  dışındaki `Yardimci` tipine çağrı zamanında ihtiyaç duyuyor. İkisi de aynı arşivleme
  çağrısıyla üretiliyor.
- **DR23.** `Muhtac` arşivi yalnız kendi sınıf dosyasını içeriyor; `Yardimci`'nin sınıf
  dosyası bilinçli olarak dışarıda bırakılıyor ki koşum sınıf yolunun geri kalanına
  duyulan ihtiyaç ölçülebilsin.
- **DR24.** Koşum hatası, arşivin kendisinden değil arşivin tek başına konduğu sınıf
  yolundan doğuyor; aynı arşiv farklı bir sınıf yoluna konunca aynı hata düşmüyor.
- **DR25.** Bildirimsiz arşiv de geçerli bir arşivdir ve sınıf dosyalarını taşır;
  yalnızca giriş noktası bilgisi eksiktir ve bu eksiklik yalnız bildirim okunduğunda
  görünür.

Son üç satır iki arşivi yan yana koşturuyor. `Bagimsiz` arşivi tek başına koşum sınıf
yoluna konduğunda sorunsuz çalışıyor — çünkü `Bagimsiz.main` hiçbir dış tipi
anmıyor, ihtiyaç duyduğu her şey kendi sınıf dosyasında. `Muhtac` arşivi ise aynı
biçimde, tek başına koşum sınıf yoluna konduğunda `NoClassDefFoundError` ile düşüyor:
`Muhtac.main`, `Yardimci.yardim()`'i çağırıyor, ama `Yardimci`'nin sınıf dosyası bu
arşivin içinde yok — arşivleme sırasında bilinçli olarak yalnız `Muhtac.class`
paketlendi. Aynı `Muhtac` arşivi, koşum sınıf yoluna `Yardimci`'nin bulunduğu dizin de
eklendiğinde sorunsuz çalışıyor; arşivin kendisi değişmedi, değişen yalnız yanına
konan sınıf yolu.

Bu üç satır tek bir kuralı doğruluyor: **arşiv bağımlılıklarını içermez.** Arşivleyici
çağrısı yalnız verilen dizini paketler; o dizinin dışındaki hiçbir sınıf dosyası
otomatik olarak eklenmez. Bir arşivin "kendine yeter" olup olmadığı arşivin kendi
içeriğine değil, kaynak kodunun hangi tipleri andığına bağlı — ve bu, derleme anında bilinen
bir şey (`Muhtac`, `Yardimci`'yi derleme sınıf yolunda gerektirdi) ile paketleme anında
unutulabilecek bir şey (`Yardimci`'nin sınıf dosyası arşive hiç girmedi) arasındaki
boşluk. Derlemenin geçmiş olması, arşivin kendine yeteceğinin garantisi değil.

Bu boşluk, Bağımlılık Yönetimi dersinde ölçülen sessiz kusurla aynı ailedendir, ama
tersine dönmüş biçimde. Orada bir tipin koşum sınıf yolunda gerekmemesi onun
**kopyalanmış** olduğu anlamına geliyordu — sabitin değeri çağrı yerine gömülmüştü ve
kaynak dosyası ortadan kalkabilirdi. Burada ise bir tipin arşive girmemesi onun hâlâ
**gerekli** olduğu, yalnız paketlemenin onu unuttuğu anlamına geliyor. İki durumda da
hata anında değil, sonraki bir adımda ortaya çıkıyor: orada sabit değiştiğinde, burada
arşiv tek başına koşum sınıf yoluna konduğunda. Aradaki fark, kusurun sınıfı: `Muhtac`'ın
eksik bağımlılığı sessiz değil, `NoClassDefFoundError` ile açıkça düşüyor — çünkü
`Yardimci`'ye yapılan çağrı derleme zamanında gömülemeyecek kadar dinamik, bir yöntem
çağrısı, bir sabit değil.

Bu, arşivleme adımının sorumluluğunu da netleştiriyor: bir arşivin bağımlılıklarını
"otomatik olarak" toplaması beklenmemeli. Hangi sınıf dosyalarının bir arşive girdiği,
tıpkı hangi bağımlılığın hangi sınıf yoluna girdiği gibi, paketleme çağrısına verilen
girdiyle belirlenen bir karardır — ve bu kararın eksik verilmesi, derleme anında hiç
görünmeyen bir koşum hatasına dönüşebilir.

Dört ders boyunca ölçülen şeyin özeti burada tamamlanıyor. Bir bildirimden sabit bir evre
dizisi türedi; o evrelerden ikisi bildirimdeki her bağımlılığı bir kapsam etiketine göre
süzerek iki ayrı sınıf yolu kurdu; bir modülün genel ile iç yüzeyi arasındaki sınır bu iki
yoldan birine hangi dizinin girdiğiyle belirlendi; ve şimdi bu yollardan biri — koşum sınıf
yolu — tek bir taşınabilir dosyaya paketlendi. Dört adımın hiçbirinde kaynak metnin kendisi
değişmedi; değişen hep bildirimdi, hep sınıf yoluydu.

## Özet

- Bir arşiv, bir dizindeki sınıf dosyalarının arşivleyici tarafından tek bir dosyaya
  paketlenmiş hâlidir; koşum sınıf yolunun tek bir öğesi olarak taşınabilir.
- Giriş noktası bilgisi kaynak dosyasında değil, arşivin kendi bildiriminin Ana-Sınıf
  alanında durur; bildirimsiz bir arşiv geçerlidir ama giriş noktası bilgisi eksiktir.
- Arşiv bağımlılıklarını içermez: arşivleme çağrısı yalnız verilen dizini paketler,
  o dizinin dışındaki hiçbir sınıf dosyası otomatik eklenmez.
- Kendine yeten bir arşiv (dış tip anmayan) tek başına koşum sınıf yoluna
  konduğunda çalışır; dışa muhtaç bir arşiv aynı koşulda koşum hatasıyla düşer ve
  yalnız eksik sınıf yolu tamamlandığında çalışır.
- Derlemenin geçmiş olması bir arşivin kendine yeteceğinin garantisi değildir; derleme
  sınıf yolunda gerekli olan bir tip, paketleme sırasında arşive hiç girmemiş olabilir.

## Sonraki Adım

Bu payda dört ders boyunca tek bir soru izlendi: bir bildirimden, bir bağımlılıktan, bir
modül sınırından, bir arşivden — hep aynı ikili sınıf yolu ayrımı çıktı. Koşum sınıf yolu
artık taşınabilir bir ürün hâline geldi ve içine ne konduğu bir karardır. Sıradaki konu
aynı kararı üçüncü bir yol için veriyor — testin gördüğü yol, üretimin gördüğü yol
değildir.
