Kompilacja, ładowanie, wykonanie aplikacji Java - potrzebny opis

0

Cześć,

Zgłębiam tajemnicę podstaw działania aplikacji od momentu kompilacji do działania i potrzebuje kilku wyjaśnień w celu zrozumienia tematu, ale po kolei :

Definicje :

  1. JDK - środowisko odpowiedzialne za kompilację kodu

  2. JRE - środowisko uruchomieniowe

  3. JVM - środowisko działania

  4. Na początku działa JDK kompilując nasze klasy Java do kodu maszynowego dla środowiska JVM. Tworzony jest również Manifest zawierający listę klas oraz klasę uruchomieniową (nie jestem pewien). Docelowo podczas kompilacji generyki mają typ Object (tego też nie jestem pewien).

  5. Podczas ładowania działamy w środowisku JRE gdzie ładowane są wszystkie klasy poprzez ClassLoader i tutaj również działa linker, który wykonuje wiązanie wczesne - lokalnych zmiennych, metod, klas statycznych itd. itp. oraz wiązanie późne podczas wykonywania programu dla wszystkich obiektów (tworzonych dynamicznie przez new), metod i klas parametryzowanych, interfejsów i dziedziczonych klas jest to wykonywane w czasie działania programu (JUST IN TIME). Następnie przygotowywany jest główny wątek (Main ) oraz obiekty w uruchamiane w metodzie, które wrzucane są do pamięci (referencje oraz primitywy do stack , obiekty do cheap).

  6. Podczas działania programu zarządzamy pamięcią i uruchamiane są nowe wątki (jeśli istnieją) oraz obiekty które wrzucane są do pamięci i w tym czasie wiemy jakiego typu są klasy, metody generyczne oraz obiekty dziedziczące interfejsy lub klasy.

Przykład :

Mając tablicę int[] tablica = new tab[3];

  1. Klasa z tą tablicą jest kompilowana do kodu maszynowego.
  2. Podczas ładowania klasa jest ładowana do pamięci i następuje wiązanie typów. W sytuacji gdy nie można stwierdzić typu mamy exception - ArrayStoreException stąd nie moża używać typów parametryzowanych bo są one wiązane podczas wykonania

Przykład drugi :

//pseudo kod

Mając klasy :

interface Parent {
public void parentVoice();
}

class Child implements Parent{
public void childVoice();
}

Potem w main tworzymy Parent p = new Child();
p.childVoice();

  1. Klasy są kompilowane do kodu maszynowego
  2. Podczas ładowania wykonywane jest wiązanie klas - z racji tego ze nie znamy typu tworzonego obiekty wiązanie następuje podczas fazy wykonania, a referencja p wiąże metody klasy Parent. Błąd następuje podczas wykonania programu przy odwołaniu p.childVoice()

Proszę o sugestie i podpowiedzi. Ze względu na rozległość tematu nie potrzebuje szczegółów dotyczących kompilatora, ładowania i wykonania (chyba, że uznacie to za stosowne). Chciałbym zrozumieć ogół sytuacji, by wiedzieć jak działają generyki, interejsy, dziedziczenie oraz środowisko od drugiej strony - kod pisać potrafię, ale to jak lizanie cukierka przez papierek.

Pytanie urodziło się w momencie czytania tematu tworzenia tablic - można stworzyć listę tablic, ale tablicę list już niekoniecznie, właśnie ze względu na różnicę w momencie sprawdzania typu obu rodzajów.

//dzięki Shalom

1
  1. Manifest to mozesz stworzyć sobie sam a klasa uruchomieniowa wcale nie musi istnieć ;) Generyki mają taki typ jaki powinny, po prostu parametr generyczny znika. Tzn List<T> ma typ List i tyle. Na pewno nie Object.
  2. Ja tam zawsze wiem...

Te przykłady z d**y bo nie ma słowa kluczowego Class, nie można implementować klasy. Zresztą ten drugi jest niepoprawny. To:

Parent p = new Child();
p.childVoice();

się nie skompiluje bo klasa Parent nie ma metody childVoice. Java to nie python. Późne wiązanie określa która implementacja się uruchomi w sytuacji kiedy w hierarchii dziedziczenia jest ich kilka.

0

Przykład pierwszy dotyczy tworzenia obiektów dynamicznie :


public class Przyklad1 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
			A a = new Bb();//czy tutaj też jest późne wiązanie? czemu tutaj jest błąd kompilatora? tzn wiem, że nie można rzutować na tę klasę, ale skąd się to bierze i kiedy wiadomo, że nowy obiekt jest typu Bb?
		
	}

	

}
class Aa implements A{

	@Override
	public void methodA() {
		// TODO Auto-generated method stub
		
	}
	
}
class Bb implements B{

	@Override
	public void methodB() {
		// TODO Auto-generated method stub
		
	}
	
}
interface A{
	void methodA();
}
interface B{
	void methodB();
}

Przykład drugi dotyczy późnego wiązania i błędu podczas wykonania (gdy program nie wie jakiego typu jest rzutowany obiekt)


public class Przyklad2 {

	
		
	
	public static void main(String[] args) {
		A2 a2 = new A2(); //poźne wiązanie - a2 jest tworzona w momencie wykonania
		B2 b2 = (B2)a2; // błąd czasu wykonania, dlaczego?
	}

}
class A2{
	void methodA(){
		
	}
}
class B2 extends A2{
	void methodB(){
		
	}
}

Przykład trzeci dotyczy generics :


public class Przykład3 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Generics<Integer>();

	}

}
class Generics<T>{
	
	T value; //jakiego typu jest zmienna value w pliku Generics.class przed utworzeniem obiektu?
}
0

Jak to od kiedy? Od razu wiadomo, bo przecież widać że próbujesz przypisać obiekty nieskorelowanych typów.
W ogóle ty chyba nie bardzo rozumiesz czym jest wczesne i późne wiązanie. Wczesne wiązanie oznacza że w miejscu wywołania jakiejś metody kompilator może od razu wstawić sobie adres odpowiedniej funkcji, bo wiadomo jaką funkcję chcesz wołać. Nie miałoby sensu "późne wiązanie" konstruktora na przykład, bo wiadomo co chcesz wywołać.
Późne wiązanie oznacza że na etapie kompilacji nie wiadomo która funkcja będzie wykonana. Przykład?

interface A{
   void metoda();
}

class B implements A{
   void metoda(){System.out.println("B");}
}
class C implements A{
   void metoda(){System.out.println("C");}
}

// main
A a;
if(new Random().nextBoolean()){
    a = new B();
}else{
    a = new C();
}

a.metoda(); //co się wypisze?

Widzisz w czym rzecz? Na etapie kompilacji nie wiadomo czy wywołana będzie metoda z klasy B czy C więc kompilator nie może podmienić tego wywołania na konkretny adres.

0

Wiem co znaczy terminologia - chciałbym wiedzieć gdzie się wykonuje i w jakich okolicznościach stąd pytanie :

Wczesne wiązanie - od razu wiadomo gdzie skierowany jest kierunek działania programu
Późne wiązanie - gdy nie wiadomo.

Do tej pory myślałem, że operator new powoduje późne wiązanie zawsze i w momencie ładowania skompilowanych klas nie wiadomo jaki obiekt będzie przypisany do referencji. Wychodzi na to, że to bzdura, bo kompilator już to sprawdza i zostawia tylko referencje, w których nie wiadomo, czyli wiązanie wykonuje się na etapie kompilacji?

Jakie sytuacje można z doświadczenia opisać jako późne wiązanie?

EDIT: A może jest jeszcze inaczej A a = new A() - kompilator w momencie kompilacji sprawdza tylko typy, potem podczas wykonania ładowana jest do pamięci referencja a (stack) i tworzony obiekt za pośrednictwem new A() - na cheap (stąd adres), dlatego w przykładzie 2 pojawia się błąd czasu wykonania bo kompilator jeszcze nie wie?

1 użytkowników online, w tym zalogowanych: 0, gości: 1