Czy są obecnie (Java 6) rzeczy, które można wykonać w kodzie bajtowym Java, a których nie można wykonać w języku Java?
Wiem, że oba są kompletne w Turingu, więc przeczytaj „może”, ponieważ „może działać znacznie szybciej / lepiej lub po prostu w inny sposób”.
Myślę o dodatkowych kodach bajtowych, takich jak invokedynamic
, których nie można wygenerować za pomocą Javy, z wyjątkiem tego, że konkretny dotyczy przyszłej wersji.
rol
w asemblerze, którego nie można pisać w C ++.(x<<n)|(x>>(32-n))
dorol
instrukcji.Odpowiedzi:
O ile wiem, w kodach bajtowych obsługiwanych przez Javę 6 nie ma żadnych głównych funkcji, które nie są również dostępne z kodu źródłowego Javy. Głównym tego powodem jest oczywiście to, że kod bajtowy Java został zaprojektowany z myślą o języku Java.
Istnieją jednak pewne funkcje, które nie są tworzone przez współczesne kompilatory Java:
ACC_SUPER
Flag :Jest to flaga, którą można ustawić dla klasy i określa, w jaki sposób
invokespecial
jest obsługiwany konkretny narożnik kodu bajtowego dla tej klasy. Jest ustawiana przez wszystkie współczesne kompilatory Javy (gdzie „modern” to> = Java 1.1, jeśli dobrze pamiętam) i tylko starożytne kompilatory Javy tworzyły pliki klas, w których ta opcja nie była ustawiona. Ta flaga istnieje tylko ze względu na kompatybilność wsteczną. Należy zauważyć, że począwszy od języka Java 7u51, ACC_SUPER jest całkowicie ignorowany ze względów bezpieczeństwa.Plik
jsr
/ret
bytecodes.Te kody bajtowe zostały użyte do implementacji podprogramów (głównie do implementacji
finally
bloków). Nie są już produkowane od wersji Java 6 . Powodem ich dezaktualizacji jest to, że bardzo komplikują statyczną weryfikację bez większego zysku (tj. Kod, który używa, można prawie zawsze ponownie zaimplementować z normalnymi skokami z bardzo niewielkim narzutem).Posiadanie dwóch metod w klasie, które różnią się tylko zwracanym typem.
Specyfikacja języka Java nie zezwala na stosowanie dwóch metod w tej samej klasie, jeśli różnią się one tylko typem zwracanym (tj. Ta sama nazwa, ta sama lista argumentów, ...). Jednak specyfikacja JVM nie ma takiego ograniczenia, więc plik klasy może zawierać dwie takie metody, po prostu nie ma możliwości utworzenia takiego pliku klasy przy użyciu zwykłego kompilatora Java. W tej odpowiedzi jest ładny przykład / wyjaśnienie .
źródło
a
a inną naA
wewnątrz pliku JAR. Zajęło mi około pół godziny rozpakowywania na komputerze z systemem Windows, zanim zdałem sobie sprawę, gdzie są brakujące klasy. :)'.'
,';'
,'['
, lub'/'
. Nazwy metod są takie same, ale nie mogą też zawierać'<'
lub'>'
. (Z godnymi uwagi wyjątkami<init>
i<clinit>
na przykład konstruktorami statycznymi.) Powinienem zwrócić uwagę, że jeśli ściśle przestrzegasz specyfikacji, nazwy klas są w rzeczywistości znacznie bardziej ograniczone, ale ograniczenia nie są wymuszane."throws ex1, ex2, ..., exn"
jako część sygnatur metod; nie możesz dodawać klauzul wyrzucających wyjątki do przesłoniętych metod. ALE, JVM nie może się mniej przejmować. Tak więc tylkofinal
metody są naprawdę gwarantowane przez JVM jako wolne od wyjątków - oczywiście opróczRuntimeException
s iError
s. To tyle, jeśli chodzi o sprawdzoną obsługę wyjątków: DPo dłuższej pracy z kodem bajtowym Javy i przeprowadzeniu dodatkowych badań w tej sprawie, oto podsumowanie moich ustaleń:
Wykonaj kod w konstruktorze przed wywołaniem super konstruktora lub konstruktora pomocniczego
W języku programowania Java (JPL) pierwsza instrukcja konstruktora musi być wywołaniem super konstruktora lub innego konstruktora tej samej klasy. Nie dotyczy to kodu bajtowego języka Java (JBC). W kodzie bajtowym wykonanie dowolnego kodu przed konstruktorem jest absolutnie uzasadnione, o ile:
Ustaw pola instancji przed wywołaniem super konstruktora lub konstruktora pomocniczego
Jak wspomniano wcześniej, ustawienie wartości pola instancji przed wywołaniem innego konstruktora jest całkowicie legalne. Istnieje nawet stary hack, który umożliwia wykorzystanie tej „funkcji” w wersjach Java przed 6:
W ten sposób pole można ustawić przed wywołaniem super konstruktora, co jednak nie jest już możliwe. W JBC to zachowanie nadal można zaimplementować.
Rozgałęzienie wywołania super konstruktora
W Javie nie jest możliwe zdefiniowanie wywołania konstruktora, takiego jak
Aż do wersji Java 7u23 weryfikator HotSpot VM pomijał tę kontrolę, dlatego było to możliwe. Było to wykorzystywane przez kilka narzędzi do generowania kodu jako rodzaj włamania, ale implementacja takiej klasy nie jest już legalna.Ten ostatni był tylko błędem w tej wersji kompilatora. W nowszych wersjach kompilatora jest to znowu możliwe.
Zdefiniuj klasę bez konstruktora
Kompilator Java zawsze implementuje co najmniej jeden konstruktor dla dowolnej klasy. W kodzie bajtowym Java nie jest to wymagane. Pozwala to na tworzenie klas, których nie można zbudować nawet przy użyciu odbicia. Jednak używanie
sun.misc.Unsafe
nadal pozwala na tworzenie takich instancji.Zdefiniuj metody z identycznym podpisem, ale z innym typem zwracania
W JPL metoda jest identyfikowana jako unikalna na podstawie jej nazwy i surowych typów parametrów. W JBC dodatkowo brany jest pod uwagę surowy typ zwrotu.
Zdefiniuj pola, które nie różnią się nazwą, ale tylko typem
Plik klasy może zawierać kilka pól o tej samej nazwie, o ile deklarują one inny typ pola. JVM zawsze odwołuje się do pola jako do krotki zawierającej nazwę i typ.
Rzucaj niezadeklarowane, zaznaczone wyjątki bez przechwytywania ich
Środowisko wykonawcze Java i kod bajtowy Java nie są świadome koncepcji sprawdzanych wyjątków. Tylko kompilator języka Java sprawdza, czy sprawdzane wyjątki są zawsze albo przechwytywane, albo deklarowane, jeśli są zgłaszane.
Użyj dynamicznego wywołania metody poza wyrażeniami lambda
Tak zwane dynamiczne wywołanie metody może być używane do wszystkiego, nie tylko do wyrażeń lambda Javy. Korzystanie z tej funkcji umożliwia na przykład przełączanie logiki wykonywania w czasie wykonywania. Wiele dynamicznych języków programowania, które sprowadzają się do JBC, poprawiło swoją wydajność , korzystając z tej instrukcji. W kodzie bajtowym Javy można również emulować wyrażenia lambda w Javie 7, gdzie kompilator nie zezwalał jeszcze na jakiekolwiek użycie dynamicznego wywołania metody, podczas gdy maszyna JVM już zrozumiała instrukcję.
Użyj identyfikatorów, które zwykle nie są uważane za legalne
Czy kiedykolwiek chciałeś użyć spacji i podziału wiersza w nazwie swojej metody? Stwórz własny JBC i powodzenia w przeglądaniu kodu. Jedynymi nielegalnych znaków dla identyfikatorów są
.
,;
,[
i/
. Ponadto metody, które nie są nazwane<init>
lub<clinit>
nie mogą zawierać<
i>
.Ponownie przypisz
final
parametry lubthis
odniesieniefinal
parametry nie istnieją w JBC i mogą zostać ponownie przypisane. Każdy parametr, w tymthis
odwołanie, jest przechowywany tylko w prostej tablicy w JVM, co pozwala na ponowne przypisaniethis
odwołania w indeksie0
w ramach pojedynczej ramki metody.Ponownie przypisz
final
polaDopóki w konstruktorze jest przypisane ostatnie pole, dozwolone jest ponowne przypisanie tej wartości lub nawet jej całkowity brak. Dlatego następujący dwaj konstruktorzy są legalni:
W przypadku
static final
pól dozwolone jest nawet ponowne przypisanie pól poza inicjatorem klasy.Traktuj konstruktory i inicjator klasy tak, jakby były metodami
Jest to raczej cecha koncepcyjna, ale konstruktorzy nie są traktowani inaczej w JBC niż zwykłe metody. Tylko weryfikator JVM zapewnia, że konstruktorzy wywołują innego legalnego konstruktora. Poza tym jest tylko konwencją nazewnictwa języka Java, która wymaga wywołania konstruktorów
<init>
i wywołania inicjatora klasy<clinit>
. Poza tą różnicą reprezentacja metod i konstruktorów jest identyczna. Jak zauważył Holger w komentarzu, można nawet zdefiniować konstruktory z typami zwracanymi innymi niżvoid
lub inicjatorem klasy z argumentami, nawet jeśli nie jest możliwe wywołanie tych metod.Twórz asymetryczne rekordy * .
Podczas tworzenia rekordu
javac wygeneruje plik klasy z pojedynczym polem o nazwie
bar
, metodą dostępu o nazwiebar()
i konstruktorem z pojedynczą nazwąObject
. Dodatkowobar
dodawany jest atrybut rekordu dla . Poprzez ręczne generowanie rekordu można utworzyć inny kształt konstruktora, pominąć pole i inaczej zaimplementować akcesor. Jednocześnie nadal można przekonać interfejs API odbicia, że klasa reprezentuje rzeczywisty rekord.Wywołaj dowolną super metodę (aż do Java 1.1)
Jest to jednak możliwe tylko dla wersji Java 1 i 1.1. W JBC metody są zawsze wysyłane w jawnym typie docelowym. Oznacza to, że dla
można było zaimplementować
Qux#baz
wywołanieFoo#baz
podczas przeskakiwaniaBar#baz
. Chociaż nadal jest możliwe zdefiniowanie jawnego wywołania w celu wywołania innej implementacji super metody niż bezpośrednia superklasa, nie ma to już żadnego wpływu na wersje Java po 1.1. W Javie 1.1 to zachowanie było kontrolowane przez ustawienieACC_SUPER
flagi, która umożliwiłaby to samo zachowanie, które wywołuje tylko bezpośrednią implementację superklasy.Zdefiniuj niewirtualne wywołanie metody zadeklarowanej w tej samej klasie
W Javie nie ma możliwości zdefiniowania klasy
Powyższy kod zawsze spowoduje wywołanie
RuntimeException
whenfoo
na wystąpieniuBar
. Nie jest możliwe zdefiniowanieFoo::foo
metody wywoływania własnejbar
metody zdefiniowanej wFoo
. Ponieważbar
jest to nieprywatna metoda wystąpienia, wywołanie jest zawsze wirtualne. Z kodu bajtowego, można jednak określić inwokację używaćINVOKESPECIAL
kod operacji, która łączy się bezpośrednio zebar
wywołanie metody wFoo::foo
celuFoo
„s wersji. Ten kod operacji jest zwykle używany do implementacji wywołań super metod, ale możesz ponownie użyć kodu operacji, aby zaimplementować opisane zachowanie.Adnotacje drobnoziarniste
W Javie adnotacje są stosowane zgodnie z ich
@Target
deklaracją. Dzięki manipulacji kodem bajtowym możliwe jest definiowanie adnotacji niezależnie od tej kontrolki. Możliwe jest również na przykład opisanie typu parametru bez opisywania parametru, nawet jeśli@Target
adnotacja dotyczy obu elementów.Zdefiniuj dowolny atrybut dla typu lub jego elementów członkowskich
W języku Java możliwe jest tylko definiowanie adnotacji dla pól, metod lub klas. W JBC można w zasadzie osadzić dowolne informacje w klasach Java. Aby skorzystać z tych informacji, nie możesz już polegać na mechanizmie ładowania klas Java, ale musisz samodzielnie wyodrębnić metainformacje.
Przelewowe i niejawnie Przypisywanie
byte
,short
,char
iboolean
wartościTe ostatnie typy pierwotne nie są zwykle znane w JBC, ale są definiowane tylko dla typów tablic lub deskryptorów pól i metod. W instrukcjach kodu bajtowego wszystkie wymienione typy zajmują 32-bitową przestrzeń, co pozwala na ich przedstawienie jako
int
. Oficjalnie tylkoint
,float
,long
idouble
typy kodu bajtowego istnieją w których wszyscy potrzebujemy wyraźnej konwersji przez rządy weryfikatora JVM za.Nie zwalniaj monitora
synchronized
Blok jest w rzeczywistości składa się z dwóch sprawozdań, jeden do zdobycia i do uwolnienia jednego monitora. W JBC możesz go zdobyć bez zwalniania go.Uwaga : w ostatnich implementacjach HotSpot prowadzi to zamiast tego do an
IllegalMonitorStateException
na końcu metody lub do niejawnego wydania, jeśli metoda jest zakończona przez sam wyjątek.Dodaj więcej niż jedną
return
instrukcję do inicjatora typuW Javie nawet trywialny inicjator typu, taki jak
jest nielegalne. W kodzie bajtowym inicjator typu jest traktowany jak każda inna metoda, tj. Instrukcje powrotu można zdefiniować w dowolnym miejscu.
Twórz nieredukowalne pętle
Kompilator Java konwertuje pętle na instrukcje goto w kodzie bajtowym Java. Takie instrukcje mogą służyć do tworzenia nieredukowalnych pętli, czego kompilator języka Java nigdy nie robi.
Zdefiniuj rekurencyjny blok catch
W kodzie bajtów Java można zdefiniować blok:
Podobna instrukcja jest tworzona niejawnie w przypadku użycia
synchronized
bloku w języku Java, w którym każdy wyjątek podczas zwalniania monitora powraca do instrukcji zwalniania monitora. Zwykle żaden wyjątek nie powinien wystąpić w takiej instrukcji, ale gdyby tak się stało (np. PrzestarzałeThreadDeath
), monitor nadal zostałby zwolniony.Wywołaj dowolną metodę domyślną
Kompilator Java wymaga spełnienia kilku warunków, aby umożliwić wywołanie metody domyślnej:
B
rozszerza interfejs,A
ale nie przesłania metody w programieA
, nadal można wywołać metodę.W przypadku kodu bajtowego Java liczy się tylko drugi warunek. Pierwsza jest jednak nieistotna.
Wywołaj super metodę na instancji, która nie jest
this
Kompilator Java pozwala tylko na wywołanie metody super (lub domyślnej interfejsu) w instancjach
this
. W kodzie bajtowym możliwe jest jednak również wywołanie metody super na instancji tego samego typu, podobnie jak poniżej:Uzyskaj dostęp do członków syntetycznych
W kodzie bajtowym Javy możliwy jest bezpośredni dostęp do składowych syntetycznych. Na przykład zastanów się, jak w poniższym przykładzie
Bar
uzyskuje się dostęp do zewnętrznej instancji innej instancji:Zwykle dotyczy to każdego pola, klasy lub metody syntetycznej.
Zdefiniuj niezsynchronizowane informacje o typie ogólnym
Chociaż środowisko wykonawcze Java nie przetwarza typów ogólnych (po tym, jak kompilator języka Java zastosuje wymazywanie typów), te informacje są nadal dołączane do skompilowanej klasy jako informacje meta i udostępniane za pośrednictwem interfejsu API odbicia.
Weryfikator nie sprawdza spójności tych
String
wartości zakodowanych w metadanych. W związku z tym można zdefiniować informacje o typach ogólnych, które nie pasują do usunięcia. W zasadzie prawdziwe mogą być następujące stwierdzenia:Ponadto podpis można zdefiniować jako nieprawidłowy, tak aby zgłaszany był wyjątek czasu wykonywania. Ten wyjątek jest generowany, gdy informacje są uzyskiwane po raz pierwszy, ponieważ są oceniane leniwie. (Podobnie do wartości adnotacji z błędem).
Dołącz meta informacje o parametrach tylko dla niektórych metod
Kompilator Java umożliwia osadzanie nazwy parametru i informacji o modyfikatorze podczas kompilowania klasy z
parameter
włączoną flagą. W formacie pliku klasy Java informacje te są jednak przechowywane według metody, co umożliwia osadzenie takich informacji o metodzie tylko dla niektórych metod.Zepsuć rzeczy i mocno zepsuć JVM
Na przykład w kodzie bajtowym Java można zdefiniować wywołanie dowolnej metody dowolnego typu. Zwykle weryfikator narzeka, jeśli typ nie jest znany takiej metody. Jeśli jednak wywołasz nieznaną metodę na tablicy, znalazłem błąd w niektórych wersjach JVM, w którym weryfikator to przegapi, a Twoja JVM zakończy pracę po wywołaniu instrukcji. Nie jest to jednak żadna funkcja, ale technicznie jest to coś, co nie jest możliwe w skompilowanej javac Javie. Java ma swego rodzaju podwójną walidację. Pierwsza weryfikacja jest stosowana przez kompilator języka Java, a druga przez maszynę JVM podczas ładowania klasy. Pomijając kompilator, możesz znaleźć słaby punkt w walidacji weryfikatora. Jest to jednak raczej ogólne stwierdzenie niż funkcja.
Dodaj adnotacje do typu odbiornika konstruktora, gdy nie ma klasy zewnętrznej
Od wersji Java 8 niestatyczne metody i konstruktory klas wewnętrznych mogą deklarować typ odbiornika i dodawać do nich adnotacje. Konstruktorzy klas najwyższego poziomu nie mogą dodawać adnotacji do swojego typu odbiorcy, ponieważ większość z nich nie deklaruje.
Ponieważ
Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()
jednak zwracaAnnotatedType
reprezentacjęFoo
, możliwe jest dołączenie adnotacji typu dlaFoo
konstruktora bezpośrednio w pliku klasy, gdzie te adnotacje są później odczytywane przez interfejs API odbicia.Użyj nieużywanych / starszych instrukcji kodu bajtowego
Ponieważ inni go nazwali, dołączę to również. Wcześniej Java korzystała z podprogramów w instrukcjach
JSR
iRET
. JBC znało nawet własny typ adresu zwrotnego do tego celu. Jednak użycie podprogramów spowodowało nadmierną komplikację statycznej analizy kodu, dlatego te instrukcje nie są już używane. Zamiast tego kompilator Java powieli kod, który kompiluje. Jednak w zasadzie tworzy to identyczną logikę, dlatego tak naprawdę nie uważam, aby osiągnąć coś innego. Podobnie możesz na przykład dodać rozszerzenieNOOP
instrukcja kodu bajtowego, która nie jest używana przez kompilator Javy, ale tak naprawdę nie pozwoliłaby na osiągnięcie czegoś nowego. Jak wskazano w kontekście, wspomniane „instrukcje dotyczące funkcji” zostały teraz usunięte z zestawu prawnych rozkazów, co czyni je jeszcze mniej ważnymi.źródło
<clinit>
metodę, definiując metody z nazwą,<clinit>
ale akceptując parametry lub mającevoid
typ niezwracający. Ale te metody nie są zbyt przydatne, JVM je zignoruje, a kod bajtowy nie może ich wywołać. Jedynym zastosowaniem byłoby zmylenie czytelników.IllegalMonitorStateException
jeśli pominąłeśmonitorexit
instrukcję. A w przypadku wyjątkowego wyjścia z metody, które nie powiodło sięmonitorexit
, po cichu resetuje monitor.Oto kilka funkcji, które można wykonać w kodzie bajtowym Java, ale nie w kodzie źródłowym Java:
Zgłaszanie sprawdzonego wyjątku z metody bez deklarowania, że metoda go zgłasza. Wyjątki zaznaczone i niezaznaczone są sprawdzane tylko przez kompilator Java, a nie przez JVM. Z tego powodu Scala może na przykład rzucać sprawdzone wyjątki od metod bez ich deklarowania. Chociaż w przypadku generycznych języków Java istnieje obejście zwane podstępnym rzutem .
Posiadanie dwóch metod w klasie, które różnią się tylko typem zwracania, jak już wspomniano w odpowiedzi Joachima : Specyfikacja języka Java nie zezwala na dwie metody w tej samej klasie, jeśli różnią się one tylko typem zwracania (tj. Ta sama nazwa, ta sama lista argumentów, ...). Jednak specyfikacja JVM nie ma takiego ograniczenia, więc plik klasy może zawierać dwie takie metody, po prostu nie ma możliwości utworzenia takiego pliku klasy przy użyciu zwykłego kompilatora Java. W tej odpowiedzi jest ładny przykład / wyjaśnienie .
źródło
Thread.stop(Throwable)
do podstępnego rzutu. Zakładam jednak, że ten już połączony jest szybszy.GOTO
może być używany z etykietami do tworzenia własnych struktur kontrolnych (innych niżfor
while
itp.)this
zmienną lokalną wewnątrz metodyJako punkt pokrewny możesz pobrać nazwęparametru dla metod skompilowanych z debugowaniem ( Paranamer robi to czytając kod bajtowy
źródło
override
tę lokalną zmienną?this
Zmienna posiada wskaźnik zera, ale oprócz tego, że wstępnie inicjalizowanethis
odniesienia podczas wprowadzania sposobu przykład, tylko lokalna zmienna. Możesz więc zapisać do niego inną wartość, która może działać jak zakończeniethis
zakresu lub zmianathis
zmiennej, w zależności od tego, jak jej używasz.this
zmienić? Myślę, że to tylko słowo „override” sprawiło, że zacząłem się zastanawiać, co dokładnie oznacza.Może sekcja 7A w tym dokumencie jest interesująca, chociaż dotyczy raczej pułapek związanych z kodem bajtowym, a nie funkcjami kodu bajtowego .
źródło
W języku Java pierwsza instrukcja w konstruktorze musi być wywołaniem konstruktora superklasy. Kod bajtowy nie ma tego ograniczenia, zamiast tego obowiązuje zasada, że konstruktor superklasy lub inny konstruktor w tej samej klasie musi zostać wywołany dla obiektu przed uzyskaniem dostępu do elementów członkowskich. Powinno to pozwolić na większą swobodę, na przykład:
Nie testowałem ich, więc proszę, popraw mnie, jeśli się mylę.
źródło
Coś, co można zrobić z kodem bajtowym, a nie zwykłym kodem Java, to wygenerowanie kodu, który można załadować i uruchomić bez kompilatora. Wiele systemów ma JRE zamiast JDK i jeśli chcesz generować kod dynamicznie, może być lepiej, jeśli nie łatwiej, wygenerować kod bajtowy zamiast kodu Javy, zanim będzie można go użyć.
źródło
Napisałem optymalizator kodu bajtowego, gdy byłem I-Play (został zaprojektowany w celu zmniejszenia rozmiaru kodu dla aplikacji J2ME). Jedną z dodanych przeze mnie funkcji była możliwość użycia wbudowanego kodu bajtowego (podobnie do wbudowanego języka asemblera w C ++). Udało mi się zmniejszyć rozmiar funkcji, która była częścią metody bibliotecznej, używając instrukcji DUP, ponieważ potrzebuję wartości dwukrotnie. Miałem też instrukcje zerobajtowe (jeśli wywołujesz metodę, która pobiera char i chcesz przekazać int, o której wiesz, że nie musi być rzutowana, dodałem int2char (var), aby zastąpić char (var) i usunąłby instrukcja i2c, aby zmniejszyć rozmiar kodu. Zrobiłem również, że float a = 2,3; float b = 3,4; float c = a + b; i to zostanie przekonwertowane na stały punkt (szybciej, a niektóre J2ME nie obsługuje zmiennoprzecinkowe).
źródło
W Javie, jeśli spróbujesz zastąpić metodę publiczną metodą chronioną (lub jakimkolwiek innym ograniczeniem dostępu), pojawi się błąd: „próbuję przypisać słabsze uprawnienia dostępu”. Jeśli zrobisz to z kodem bajtowym JVM, weryfikator nie będzie z tym problemem i możesz wywołać te metody za pośrednictwem klasy nadrzędnej, tak jakby były publiczne.
źródło