java反編譯內部類
『壹』 用java反編譯器,把.class文件反編譯後會不會和原文件不同
如果是簡單的JAVA代碼或者沒有經過混淆編譯的代碼就會使一樣或者差不多的。
但是一下兩種情況就特別不一樣:
1.經過混淆編譯的
2.使用內部類的
另外,所有的注釋都是沒有的。
當然還有其他的一些情況下你會發現不一樣了,某些情況下循環的時候就會,而且變數申明的位置會改變等等。
『貳』 深入理解Java中為什麼內部類可以訪問外部類的成員
內部類簡介
雖然Java是一門相對比較簡單的編程語言,但是對於初學者, 還是有很多東西感覺雲里霧里,
理解的不是很清晰。內部類就是一個經常讓初學者感到迷惑的特性。 即使現在我自認為Java學的不錯了,
但是依然不是很清楚。其中一個疑惑就是為什麼內部類對象可以訪問外部類對象中的成員(包括成員變數和成員方法)?
早就想對內部類這個特性一探究竟了,今天終於抽出時間把它研究了一下。
內部類就是定義在一個類內部的類。定義在類內部的類有兩種情況:一種是被static關鍵字修飾的, 叫做靜態內部類,
另一種是不被static關鍵字修飾的, 就是普通內部類。 在下文中所提到的內部類都是指這種不被static關鍵字修飾的普通內部類。
靜態內部類雖然也定義在外部類的裡面, 但是它只是在形式上(寫法上)和外部類有關系,
其實在邏輯上和外部類並沒有直接的關系。而一般的內部類,不僅在形式上和外部類有關系(寫在外部類的裡面), 在邏輯上也和外部類有聯系。
這種邏輯上的關系可以總結為以下兩點:
1 內部類對象的創建依賴於外部類對象;
2 內部類對象持有指向外部類對象的引用。
上邊的第二條可以解釋為什麼在內部類中可以訪問外部類的成員。就是因為內部類對象持有外部類對象的引用。但是我們不禁要問, 為什麼會持有這個引用? 接著向下看, 答案在後面。
通過反編譯位元組碼獲得答案
在源代碼層面, 我們無法看到原因,因為Java為了語法的簡介, 省略了很多該寫的東西, 也就是說很多東西本來應該在源代碼中寫出, 但是為了簡介起見, 不必在源碼中寫出,編譯器在編譯時會加上一些代碼。 現在我們就看看Java的編譯器為我們加上了什麼?
首先建一個工程TestInnerClass用於測試。 在該工程中為了簡單起見, 沒有創建包, 所以源代碼直接在默認包中。在該工程中, 只有下面一個簡單的文件。
?
1
2
3
4
5
6
7
8
9
public class Outer {
int outerField = 0;
class Inner{
void InnerMethod(){
int i = outerField;
}
}
}
該文件很簡單, 就不用過多介紹了。 在外部類Outer中定義了內部類Inner, 並且在Inner的方法中訪問了Outer的成員變數outerField。
雖然這兩個類寫在同一個文件中, 但是編譯完成後, 還是生成各自的class文件:
這里我們的目的是探究內部類的行為, 所以只反編譯內部類的class文件Outer$Inner.class 。 在命令行中, 切換到工程的bin目錄, 輸入以下命令反編譯這個類文件:
?
1
javap -classpath . -v Outer$Inner
-classpath . 說明在當前目錄下尋找要反編譯的class文件
-v 加上這個參數輸出的信息比較全面。包括常量池和方法內的局部變數表, 行號, 訪問標志等等。
注意, 如果有包名的話, 要寫class文件的全限定名, 如:
?
1
javap -classpath . -v com..Outer$Inner
反編譯的輸出結果很多, 為了篇幅考慮, 在這里我們省略了常量池。 下面給出除了常量池之外的輸出信息。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
final Outer this$0;
flags: ACC_FINAL, ACC_SYNTHETIC
Outer$Inner(Outer);
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #10 // Field this$0:LOuter;
5: aload_0
6: invokespecial #12 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LOuter$Inner;
void InnerMethod();
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: getfield #10 // Field this$0:LOuter;
4: getfield #20 // Field Outer.outerField:I
7: istore_1
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LOuter$Inner;
8 1 1 i I
}</init>
首先我們會看到, 第一行的信息如下:
?
1
final Outer this$0;
這句話的意思是, 在內部類Outer$Inner中, 存在一個名字為this$0 , 類型為Outer的成員變數, 並且這個變數是final的。
其實這個就是所謂的「在內部類對象中存在的指向外部類對象的引用」。但是我們在定義這個內部類的時候, 並沒有聲明它,
所以這個成員變數是編譯器加上的。
雖然編譯器在創建內部類時為它加上了一個指向外部類的引用, 但是這個引用是怎樣賦值的呢?畢竟必須先給他賦值,它才能指向外部類對象。下面我們把注意力轉移到構造函數上。 下面這段輸出是關於構造函數的信息。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Outer$Inner(Outer);
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #10 // Field this$0:LOuter;
5: aload_0
6: invokespecial #12 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LOuter$Inner;</init>
我們知道, 如果在一個類中, 不聲明構造方法的話, 編譯器會默認添加一個無參數的構造方法。 但是這句話在這里就行不通了, 因為我們明明看到, 這個構造函數有一個構造方法, 並且類型為Outer。 所以說,
編譯器會為內部類的構造方法添加一個參數, 參數的類型就是外部類的類型。
下面我們看看在構造參數中如何使用這個默認添加的參數。 我們來分析一下構造方法的位元組碼。 下面是每行位元組碼的意義:
aload_0 :
將局部變數表中的第一個引用變數載入到操作數棧。 這里有幾點需要說明。
局部變數表中的變數在方法執行前就已經初始化完成;局部變數表中的變數包括方法的參數;成員方法的局部變數表中的第一個變數永遠是this;操作數棧就是
執行當前代碼的棧。所以這句話的意思是: 將this引用從局部變數表載入到操作數棧。
aload_1:
將局部變數表中的第二個引用變數載入到操作數棧。 這里載入的變數就是構造方法中的Outer類型的參數。
putfield #10 // Field this$0:LOuter;
使用操作數棧頂端的引用變數為指定的成員變數賦值。 這里的意思是將外面傳入的Outer類型的參數賦給成員變數this$0 。
這一句putfield位元組碼就揭示了, 指向外部類對象的這個引用變數是如何賦值的。
下面幾句位元組碼和本文討論的話題無關, 只做簡單的介紹。 下面幾句位元組碼的含義是: 使用this引用調用父類(Object)的構造方法然後返回。
用我們比較熟悉的形式翻譯過來, 這個內部類和它的構造函數有點像這樣: (注意, 這里不符合Java的語法, 只是為了說明問題)
?
1
2
3
4
5
6
7
8
class Outer$Inner{
final Outer this$0;
public Outer$Inner(Outer outer){
this.this$0 = outer;
super();
}
}
說到這里, 可以推想到, 在調用內部類的構造器初始化內部類對象的時候, 編譯器默認也傳入外部類的引用。 調用形式有點像這樣: (注意, 這里不符合java的語法, 只是為了說明問題)
vcq9ysfP4M2stcShoyDU2sTasr//wOC1xLPJ1LGx5MG/b3V0ZXJGaWVsZKOsIM/Cw++NDQtcSjugo8YnI+Cgo8cHJlIGNsYXNzPQ=="brush:java;">
void InnerMethod();
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: getfield #10 // Field this$0:LOuter;
4: getfield #20 // Field
Outer.outerField:I
7: istore_1
8: return
getfield #10 // Field this$0:LOuter;
將成員變數this$0載入到操作數棧上來
getfield #20 // Field Outer.outerField:I
使用上面載入的this$0引用, 將外部類的成員變數outerField載入到操作數棧
istore_1
將操作數棧頂端的int類型的值保存到局部變數表中的第二個變數上(注意, 第一個局部變數被this佔用,
第二個局部變數是i)。操作數棧頂端的int型變數就是上一步載入的outerField變數。 所以, 這句位元組碼的含義就是:
使用outerField為i賦值。
上面三步就是內部類中是如何通過指向外部類對象的引用, 來訪問外部類成員的。
文章寫到這里, 相信讀者對整個原理就會有一個清晰的認識了。 下面做一下總結:
本文通過反編譯內部類的位元組碼, 說明了內部類是如何訪問外部類對象的成員的,除此之外, 我們也對編譯器的行為有了一些了解, 編譯器在編譯時會自動加上一些邏輯, 這正是我們感覺困惑的原因。
關於內部類如何訪問外部類的成員, 分析之後其實也很簡單, 主要是通過以下幾步做到的:
1 編譯器自動為內部類添加一個成員變數, 這個成員變數的類型和外部類的類型相同, 這個成員變數就是指向外部類對象的引用;
2 編譯器自動為內部類的構造方法添加一個參數, 參數的類型是外部類的類型, 在構造方法內部使用這個參數為1中添加的成員變數賦值;
3 在調用內部類的構造函數初始化內部類對象時, 會默認傳入外部類的引用。
『叄』 如何把jar包里的class文件反編譯重命名
手機端反編譯的:
很容易,下載個py平台安裝在手機端(這個很重要,因為等下安裝的反編譯軟體需要py平台支持)。接著下載漢化風暴,安裝在手機端.可以了。
打開漢化風暴後,你應該就知道如何操作了。但是想找到class文件的前提是,你有把那個jar程序包解壓,然後你就可以用漢化風暴讀取那個解壓的路徑里,讀取目標class。
電腦端反編譯的:
下載個名為「jar游戲破解」(名字貌似叫手機頑童),安裝在電腦就可以了.接下去你就重點如何操作了
祝你玩得愉快
其他的手機破解,手機jar游戲破解,均可來找我.
『肆』 Java中,為什麼外部類可以訪問內部類的私有成員
本文通過反編譯內部類的位元組碼,說明了內部類是如何訪問外部類對象的成員的,除此之外,我們也對編譯器的行為有了一些了解,編譯器在編譯時會自動加上一些邏輯,這正是我們感覺困惑的原因。關於內部類如何訪問外部類的成員,分析之後其實
『伍』 java反編譯可以將位元組碼文件到java源文件嗎
可以啊,問題是目前的反編譯都無法100%還原源碼,尤其是注釋,在編譯階段是被javac直接忽略掉的,可是一個復雜的系統離開注釋是很難通過閱讀源碼去理解的,反編譯幫助不大
『陸』 java反編譯class文件能完整的編譯出原始代碼么
在一定程度上可以,但是注釋之類是是反編譯不過來的,如果使用的還有內部類,反編譯也會有問題。
『柒』 哪位給翻譯一下,反編譯的帶匿名內部類的java文件
本文通過反編譯內部類的位元組碼,
說明了內部類是如何訪問外部類對象的成員的,除此之外,
我們也對編譯器的行為有了一些了解,
編譯器在編譯時會自動加上一些邏輯,
這正是我們感覺困惑的原因。
關於內部類如何訪問外部類的成員,
分析之後其實也很簡單,
主要是通過以下幾步做到的:
1
編譯器自動為內部類添加一個成員變數,
這個成員變數的類型和外部類的類型相同,
這個成員變數就是指向外部類對象的引用;
2
編譯器自動為內部類的構造方法添加一個參數,
參數的類型是外部類的類型,
在構造方法內部使用這個參數為1中添加的成員變數賦值;
3
在調用內部類的構造函數初始化內部類對象時,
會默認傳入外部類的引用。
『捌』 java反編譯的時候出現import了一個並不存在的類
把內容貼出來吧,可能是你缺了這個類文件
『玖』 java 匿名內部類 的執行順序和文字表述, ,下面看代碼
內部類(理解)
(1)把類定義在另一個類的內部,該類就被稱為內部類。
舉例:把類B定義在類A中,類B就被稱為內部類。
(2)內部類的訪問規則
A:可以直接訪問外部類的成員,包括私有
B:外部類要想訪問內部類成員,必須創建對象
(3)內部類的分類
A:成員內部類
B:局部內部類
(4)成員內部類
A:private 為了數據的安全性
B:static 為了訪問的方便性
成員內部類不是靜態的:
外部類名.內部類名 對象名 = new 外部類名.new 內部類名();
成員內部類是靜態的:
外部類名.內部類名 對象名 = new 外部類名.內部類名();
局部內部類
A:局部內部類訪問局部變數必須加final修飾。
B:為什麼呢?
因為局部變數使用完畢就消失,而堆內存的數據並不會立即消失。
所以,堆內存還是用該變數,而改變數已經沒有了。
為了讓該值還存在,就加final修飾。
通過反編譯工具我們看到了,加入final後,堆內存直接存儲的是值,而不是變數名。
(7)匿名內部類(掌握)
A:是局部內部類的簡化形式
B:前提
存在一個類或者介面
C:格式:
new 類名或者介面名() {
重寫方法;
}
D:本質:
其實是繼承該類或者實現介面的子類匿名對象
(8)匿名內部類在開發中的使用
我們在開發的時候,會看到抽象類,或者介面作為參數。
而這個時候,我們知道實際需要的是一個子類對象。
如果該方法僅僅調用一次,我們就可以使用匿名內部類的格式簡化。
『拾』 j使用Font End plus 反編譯包含內部類的java類的問題
如果有內部類,就一定會出現上述代碼,上述代碼是java編輯器做的特殊處理,目的是讓內部類能夠訪問外部類的私有成員變數;在google上搜「java反編譯 內部類」
,能夠得到跟多的結果