當(dāng)前位置:首頁(yè)>職場(chǎng)>java枚舉法的用法(從一道面試題開始說(shuō)起)
發(fā)布時(shí)間:2024-01-24閱讀(13)
前段時(shí)間在dota群,一哥們出去面試,回顧面試題的時(shí)候,說(shuō)問(wèn)到了枚舉。
作為一名Android選手,談到枚舉,那肯定是:
Android上不應(yīng)該使用枚舉,占內(nèi)存,應(yīng)該使用@XXXDef注解來(lái)替代,balabala…
這么一回答,心里美滋滋。
沒想到面試官問(wèn)了句:
聽到這就慌了,沒了解過(guò)呀。
下面說(shuō)第一個(gè)問(wèn)題(沒錯(cuò)還有第二個(gè)問(wèn)題)。
枚舉的本質(zhì)
有篇文章:
http://blog.csdn.net/mhmyqn/article/details/48087247
寫得挺好的。
下面還是要簡(jiǎn)述一下,我們先寫個(gè)枚舉類:
public Enum Animal {
DOG,CAT
}
看著這代碼,完全看不出來(lái)原理。不過(guò)大家應(yīng)該都知道java類編譯后會(huì)產(chǎn)生class文件。
越接近底層,本質(zhì)就越容易暴露出來(lái)了。
我們先javac搞到Animal.class,然后通過(guò)javap命令看哈:
javap Animal.class
輸出:
public final class Animal extends java.lang.Enum<Animal> {
public static final Animal DOG;
public static final Animal CAT;
public static Animal[] values();
public static Animal valueOf(java.lang.String);
static {};
}
其實(shí)到這里我們已經(jīng)大致知道枚舉的本質(zhì)了,實(shí)際上我們編寫的枚舉類Animal是繼承自Enum的,每個(gè)枚舉對(duì)象都是static final的類對(duì)象。
還想知道更多的細(xì)節(jié)怎么辦,比如我們的對(duì)象什么時(shí)候初始化的。
我們可以添加-c參數(shù),對(duì)代碼進(jìn)行反編譯。
你可以使用javap -help 查看所有參數(shù)的含義。
javap -c Animal.class
輸出:
public final class Animal extends java.lang.Enum<Animal> {
public static final Animal DOG;
public static final Animal CAT;
public static Animal[] values();
Code:
0: getstatic #1 // Field $VALUES:[LAnimal;
3: invokevirtual #2 // Method "[LAnimal;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LAnimal;"
9: areturn
public static Animal valueOf(java.lang.String);
Code:
0: ldc #4 // class Animal
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class Animal
9: areturn
static {};
Code:
0: new #4 // class Animal
3: dup
4: ldc #7 // String DOG
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field DOG:LAnimal;
13: new #4 // class Animal
16: dup
17: ldc #10 // String CAT
19: iconst_1
20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #11 // Field CAT:LAnimal;
26: iconst_2
27: anewarray #4 // class Animal
30: dup
31: iconst_0
32: getstatic #9 // Field DOG:LAnimal;
35: aastore
36: dup
37: iconst_1
38: getstatic #11 // Field CAT:LAnimal;
41: aastore
42: putstatic #1 // Field $VALUES:[LAnimal;
45: return
}
好了,現(xiàn)在可以分析代碼了。
但是,這代碼看起來(lái)也太頭疼了,我們先看一點(diǎn)點(diǎn):
static中部分代碼:
0: new #4 // class Animal
3: dup
4: ldc #7 // String DOG
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field DOG:LAnimal;
大致含義就是new Animal(String,int),然后給我們的靜態(tài)常量DOG賦值。
好了,不看了,好煩。我們轉(zhuǎn)念想一下,如果這個(gè)字節(jié)碼咱們能看懂,那就是有規(guī)則的,只要有規(guī)則,肯定有類似翻譯類的工具,直接轉(zhuǎn)成java代碼的。
確實(shí)有,比如jad:
http://www.javadecompilers.com/jad
我們先下載一份,很小:

命令也很簡(jiǎn)單,執(zhí)行:
./jad -sjava Animal.class
就會(huì)在當(dāng)前目錄生成java文件了。
輸出如下:
public final class Animal extends Enum
{
public static Animal[] values()
{
return (Animal[])$VALUES.clone();
}
public static Animal valueOf(String s)
{
return (Animal)Enum.valueOf(Animal, s);
}
private Animal(String s, int i)
{
super(s, i);
}
public static final Animal DOG;
public static final Animal CAT;
private static final Animal $VALUES[];
static
{
DOG = new Animal("DOG", 0);
CAT = new Animal("CAT", 1);
$VALUES = (new Animal[] {
DOG, CAT
});
}
}
到這,我相信你知道我們編寫的枚舉類:
public enum Animal {
DOG,CAT
}
最終生成是這樣的類,那么對(duì)應(yīng)的我們所使用的方法也就都明白了。此外,你如何拿這樣的類,跟兩個(gè)靜態(tài)INT常量比內(nèi)存,那肯定是多得多的。
其次,我們也能順便回答,枚舉對(duì)象為什么是單例了。
并且其Enum類中對(duì)readObject和clone方法都進(jìn)行了實(shí)現(xiàn),看一眼你就明白了。
本文并不是為了去討論枚舉的原理,而是想要給大家說(shuō)明的是很多“語(yǔ)法糖”類似的東西,都能按照這樣的思路去了解它的原理。
下面我們?cè)倏匆粋€(gè),聽起來(lái)稍微高端一點(diǎn)的:
動(dòng)態(tài)代理
這個(gè)比較出名的就是retrofit了。
問(wèn):retrofit的原理是?
答:基于動(dòng)態(tài)代理,然后balabal...
問(wèn):那么動(dòng)態(tài)代理的原理是?
答:...
我們依然從一個(gè)最簡(jiǎn)單的例子開始。
我們寫一個(gè)接口:
public interface IUserService{
void login(String username, String password);
}
然后,利用動(dòng)態(tài)代理去生成一個(gè)代理對(duì)象,去調(diào)用login方法:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class Test{
public static void main(String[] args){
IUserService userService = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(),
new Class[]{IUserService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method = " method.getName() " , args = " Arrays.toString(args));
return null;
}
});
System.out.println(userService.getClass());
userService.login("zhy","123");
}
}
好了,這應(yīng)該是最簡(jiǎn)單的動(dòng)態(tài)代理的例子了。
當(dāng)我們?nèi)フ{(diào)研userService.login方法,你會(huì)發(fā)現(xiàn)InvocationHandler的invoke方法調(diào)用了,并且輸出了相關(guān)信息。
怎么會(huì)這么神奇呢?
我們寫了一個(gè)接口,就能產(chǎn)生一個(gè)該接口的對(duì)象,然后我們還能攔截它的方法。
繼續(xù)看:
先javac Test.java,得到class文件。
然后調(diào)用:
java Test
輸出:
class com.sun.proxy.$Proxy0
method = login , args = [zhy, 123]
可以看到當(dāng)我們調(diào)用login方法的時(shí)候,invoke中攔截到了我們的方法,參數(shù)等信息。
retrofit的原理其實(shí)就是這樣,攔截到方法、參數(shù),再根據(jù)我們?cè)诜椒ㄉ系淖⒔猓テ唇訛橐粋€(gè)正常的Okhttp請(qǐng)求,然后執(zhí)行。
想知道原理,根據(jù)我們枚舉中的經(jīng)驗(yàn),肯定想看看這個(gè)
com.sun.proxy.$Proxy0 // userService對(duì)象輸出的全路徑
這個(gè)類的class文件如何獲取呢?
很簡(jiǎn)單,你在main方法的第一行,添加:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
然后重新編譯、執(zhí)行,就會(huì)在當(dāng)前目錄看到了。
MacBook-Pro:tmp zhanghongyang01$ tree
.
├── IUserService.class
├── IUserService.java
├── Test$1.class
├── Test.class
├── Test.java
└── com
└── sun
└── proxy
└── $Proxy0.class
3 directories, 6 files
然后,還想通過(guò)javap -c來(lái)看么~~
還是拿出我們剛才下載的jad吧。
執(zhí)行:
./jad -sjava com/sun/proxy/$Proxy0.class
在jad的同目錄,你就發(fā)現(xiàn)了Proxy0的java文件了:
package com.sun.proxy;
import IUserService;
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy
implements IUserService
{
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final void login(String s, String s1)
{
super.h.invoke(this, m3, new Object[] {
s, s1
});
}
private static Method m3;
static
{
m3 = Class.forName("IUserService").getMethod("login", new Class[] {
Class.forName("java.lang.String"), Class.forName("java.lang.String")
});
}
}
為了便于理解,刪除了一些equals,hashCode等方法。
你可以看到,實(shí)際上為我們生成一個(gè)實(shí)現(xiàn)了IUserSevice的類,我們調(diào)用其login方法,實(shí)際上就是調(diào)用了:
super.h.invoke(this, m3, new Object[] {
s, s1
});
m3即為我們的login方法,靜態(tài)塊中初始化的。剩下是我們傳入的參數(shù)。
那我們看super.h是什么:
package java.lang.reflect;
public class Proxy{
protected InvocationHandler h;
}
就是我們自己創(chuàng)建的InvocationHandler對(duì)象。
看著這個(gè)類,再想login方法,為什么會(huì)回調(diào)到InvocationHandler的invoke方法,你還覺得奇怪么~~
好了,實(shí)際上這個(gè)哥們面試距離現(xiàn)在挺久了,終于抽空寫完了,希望大家有一定的收獲~

歡迎分享轉(zhuǎn)載→http://www.avcorse.com/read-234030.html
下一篇:紅娘是哪一部作品中的人物
Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號(hào)-5 TXT地圖HTML地圖XML地圖