摘要:寫在前面由于我對(duì)寫解析器只有閱讀了幾篇文章的知識(shí)量,因此水平并不是很高,此文權(quán)當(dāng)一次個(gè)人總結(jié),無法保證所涉及的知識(shí)點(diǎn)思路完全無誤,如有錯(cuò)誤,還請(qǐng)各位大佬指正。除此之外的狀態(tài)都是不合法的,這也就是有時(shí)候解析類的包比如會(huì)看到的錯(cuò)誤的情況。
寫在前面
由于我對(duì)寫解析器只有 閱讀了幾篇文章 的知識(shí)量,因此水平并不是很高,此文權(quán)當(dāng)一次個(gè)人總結(jié),無法保證所涉及的知識(shí)點(diǎn)、思路完全無誤,如有錯(cuò)誤,還請(qǐng)各位大佬指正。
從一個(gè)正整數(shù)表達(dá)式開始這篇文章圍繞的僅僅是一個(gè) 正整數(shù)表達(dá)式,而且它很簡(jiǎn)單,不會(huì)出現(xiàn)括號(hào)嵌套等情況,我們的目標(biāo)只是把
10 * 5 + 1
解析為一個(gè) Token 序列,如下:
[ { type: NUMBER, value: `10` }, { type: OPERATOR, value: `*` }, { type: NUMBER, value: `5` }, { type: OPERATOR, value: `+` }, { type: NUMBER, value: `1` } ]
我習(xí)慣從簡(jiǎn)單的開始,那么我們先從一個(gè)最簡(jiǎn)單的、只有個(gè)位數(shù)、沒有空格的式子開始:
1+1最簡(jiǎn)單的思路
其實(shí)詞法分析器要做的事本質(zhì)上很簡(jiǎn)單:對(duì)輸入的字符串進(jìn)行遍歷,分割成有意義的 Token。
因此,最簡(jiǎn)單的思路就是一個(gè) for 循環(huán):
String expression = "1+1"; for (char ch : expression.toCharArray()) { // 在這里進(jìn)行處理 }
所以我們定義一個(gè) Scanner,為了后續(xù)方便,順手實(shí)現(xiàn)個(gè)簡(jiǎn)單的單例吧:
public class Scanner { private static volatile Scanner instance; public static Scanner getInstance() { if (Scanner.instance == null) { synchronized ( Scanner.class ) { if (Scanner.instance == null) { Scanner.instance = new Scanner(); } } } return Scanner.instance; } private String expression; public Scanner from(String expression) { this.expression = expression; return this; } public void process() { for (char ch : expression.toCharArray()) { // 在這里進(jìn)行處理 } } public static void main(String ... args) { Scanner scanner = Scanner.getInstance().from("1+1"); scanner.process(); } }定義 Token 類型
在當(dāng)前的 1+1 表達(dá)式中,涉及到的 Token 不多,只有數(shù)字、操作符,因此用一個(gè)枚舉類即可表述:
public enum Type { INIT, NUMBER, OPERATOR, UNKNOWN; public static Type of(char ch) { if ("0" <= ch && ch <= "9") { return NUMBER; } if ("+-*/".indexOf(ch) != -1) { return OPERATOR; } return UNKNOWN; } }
同時(shí)該枚舉類承擔(dān)辨識(shí)字符類型的工作:
Type.of("1") // NUMBER Type.of("+") // OPERATOR Type.of("a") // UNKNOWN定義 Token
public class Token { // 一個(gè) Token 的類型一旦確定,就不可能再改變。 private final Type type; // 用以存儲(chǔ) Token 的值。 private final StringBuffer value; public Token(Type type) { this.type = type; this.value = new StringBuffer(); } public void appendValue(char ch) { this.value.append(ch); } public String getValue() { return this.value.toString(); } public Type getType() { return this.type; } @Override public String toString() { return String.format("{type: %s, value: `%s`}", this.getType().name(), this.getValue()); } }處理 1+1
public class Scanner { // 省略... public void process() { for (char ch : expression.toCharArray()) { Type type = Type.of(ch); if (Type.UNKNOWN.equals(type)) { throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch)); } Token token = new Token(type); token.appendValue(ch); System.out.println(token); } } public static void main(String ... args) { Scanner scanner = new Scanner("1+1"); scanner.process(); } } /** 輸出 * {type: NUMBER, value: `1`} * {type: OPERATOR, value: `+`} * {type: NUMBER, value: `1`} */現(xiàn)在來加點(diǎn)難度: 10+1
現(xiàn)在一個(gè)數(shù)字可能不止一位了,那么我們?cè)撛趺崔k呢?
使用狀態(tài)圖:
┌-[ 0-9 ]-┐ ┌-[ +|-|*|/ ]-┐ ┌-[ 0-9 ]-┐ ---( NUMBER )--- ( OPERATOR )---( NUMBER )---
具體的理論這里就不贅述了,有興趣可以自行查閱相關(guān)資料,這里簡(jiǎn)單說一下怎么用:
現(xiàn)在我們來列個(gè)表,看一下對(duì)于 10+1,在狀態(tài)上有什么變化:
字符 | 狀態(tài) | Token |
---|---|---|
NULL | INIT | NULL |
1 | NUMBER | {id: 0, type: NUMBER, value: 1} |
0 | NUMBER | {id: 0, type: NUMBER, value: 10} |
+ | OPERATOR | {id: 1, type: OPERATOR, value: +} |
1 | NUMBER | {id: 2, type: NUMBER, value: 1} |
可以看到,在讀到字符 1 和 0 時(shí),狀態(tài)沒有發(fā)生變化,也就是說它們是一個(gè)整體(或是一個(gè)整體的一部分)。
如果在 0 后面還有其他數(shù)字,那么直到引起狀態(tài)改變的字符出現(xiàn)之前,這些字符就組成了整個(gè) Token。
同時(shí),我們還發(fā)現(xiàn)引入狀態(tài)圖后,有個(gè)有意思的事:
從 初始狀態(tài) INIT 開始,我們只允許后邊是 NUMBER 類型;
NUMBER 后邊允許 NUMBER、OPERATOR 類型;
OPERATOR 后邊允許 NUMBER 類型。
除此之外的狀態(tài)都是不合法的,這也就是有時(shí)候解析類的包(比如 fast-json)會(huì)看到的 Invalid Character 錯(cuò)誤的情況。
所以我們需要改改代碼,同時(shí)為 Scanner 添加一個(gè)更新狀態(tài)的方法:
public class Scanner { // 省略 private Token token; public void setToken(Token token) { this.token = token; } public void process() { // 初始化 this.setToken(new Token(Type.INIT)); for (char ch : expression.toCharArray()) { Type type = Type.of(ch); if (Type.UNKNOWN.equals(type)) { throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch)); } // 根據(jù)當(dāng)前 Token 的類型,選擇不同的判斷分支 switch (token.getType()) { case INIT: switch (type) { // 當(dāng)前是初始狀態(tài),遇到了數(shù)字, 切換狀態(tài)。 case NUMBER: this.setToken(new Token(Type.NUMBER)); this.token.appendValue(ch); break; default: throw new RuntimeException(String .format("Invalid Character: `%c`", ch)); } break; case NUMBER: switch (type) { // 當(dāng)前是數(shù)字狀態(tài),遇到了數(shù)字,追加字符。 case NUMBER: this.token.appendValue(ch); break; // 當(dāng)前是數(shù)字狀態(tài),遇到了操作符,切換狀態(tài)。 case OPERATOR: this.setToken(new Token(Type.OPERATOR)); this.token.appendValue(ch); break; default: throw new RuntimeException(String .format("Invalid Character: `%c`", ch)); } break; case OPERATOR: switch (type) { // 當(dāng)前是操作符狀態(tài),遇到了數(shù)字,切換狀態(tài)。 case NUMBER: this.setToken(new Token(Type.NUMBER)); this.token.appendValue(ch); break; default: throw new RuntimeException(String .format("Invalid Character: `%c`", ch)); } break; } System.out.println(token); } } }
/** 輸出 * {type: NUMBER, value: `1`} * {type: NUMBER, value: `10`} * {type: OPERATOR, value: `+`} * {type: NUMBER, value: `1`} */試著簡(jiǎn)化一下
我們剛才用了一個(gè)巨大無比的 switch 結(jié)構(gòu)來描述狀態(tài)圖,現(xiàn)在我們由內(nèi)而外試著簡(jiǎn)化這個(gè)巨無霸。
先從內(nèi)部開始:switch (type) { // 當(dāng)前是 ** 狀態(tài),遇到了 ** , 執(zhí)行 ** 操作。 case NUMBER: // ... break; case ... default: throw new RuntimeException(String.format("Invalid Character: `%c`", ch)); }
其實(shí)稍微歸納總結(jié)一下就能發(fā)現(xiàn), 執(zhí)行 ** 操作 這部分,總的來說只有兩種:
NewToken 對(duì)應(yīng)著 { token = new Token(Type.NUMBER); token.appendValue(ch); } AppendValue 對(duì)應(yīng)著 { token.appendValue(ch); }
現(xiàn)在我們?cè)僖胍粋€(gè)工具來幫助我們簡(jiǎn)化:表驅(qū)動(dòng)。
其實(shí)從上面的對(duì)應(yīng)關(guān)系不難發(fā)現(xiàn),我們可以用 HashMap 來簡(jiǎn)單模擬一個(gè)表,幫助我們減少工作。
在此之前,我們需要把上述關(guān)系中的 {操作} 部分用一個(gè)接口來解耦:
public interface Behavior { void apply(Token token, Type type, char ch); }
然后我們來定義一個(gè)枚舉類 Behaviors 來表示操作類型:
public enum Behaviors { NewToken, AppendValue; private static final Scanner scanner; private static final HashMapbehaviorMap; static { scanner = Scanner.getInstance(); behaviorMap = new HashMap<>(); behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> { token = new Token(type); token.appendValue(ch); scanner.setToken(token); }); behaviorMap.put(Behaviors.AppendValue, (token, type, ch) -> { token.appendValue(ch); }); } public void apply(Token token, Type type, char ch) { behaviorMap.get(this) .apply(token, type, ch); } }
那么現(xiàn)在 執(zhí)行 操作 這部分,現(xiàn)在可以用 HashMap 來表述了:
// 根據(jù)當(dāng)前 Token 的類型,選擇不同的判斷分支 switch (token.getType()) { case INIT: HashMapbehaviorsMap = new HashMap<>(); // 當(dāng)前是初始狀態(tài),遇到了數(shù)字, 切換狀態(tài)。 behaviorsMap.put(Type.NUMBER, Behaviors.NewToken); break; case NUMBER: HashMap behaviorsMap = new HashMap<>(); // 當(dāng)前是數(shù)字狀態(tài),遇到了數(shù)字,追加字符。 behaviorsMap.put(Type.NUMBER, Behaviors.AppendValue); // 當(dāng)前是數(shù)字狀態(tài),遇到了操作符,切換狀態(tài)。 behaviorsMap.put(Type.Operator, Behaviors.NewToken); break; case OPERATOR: HashMap behaviorsMap = new HashMap<>(); // 當(dāng)前是操作符狀態(tài),遇到了數(shù)字,切換狀態(tài)。 behaviorsMap.put(Type.NUMBER, Behaviors.NewToken); break; }
既然是 Java ,那么讓我們來讓這部分看起來 OO 一些:
public class BehaviorMap { private final HashMapmap; public BehaviorMap() { this.map = new HashMap(); } public BehaviorMap at(Type type, Behaviors behaviors) { this.map.put(type, behaviors); return this; } public BehaviorsTable done() { return BehaviorsTable.getInstance(); } }
現(xiàn)在再來看看:
// 根據(jù)當(dāng)前 Token 的類型,選擇不同的判斷分支 switch (token.getType()) { case INIT: BehaviorMap map = new BehaviorMap(); map // 當(dāng)前是初始狀態(tài),遇到了數(shù)字, 切換狀態(tài)。 .at(Type.NUMBER, Behaviors.NewToken); break; case NUMBER: BehaviorMap map = new BehaviorMap(); map // 當(dāng)前是數(shù)字狀態(tài),遇到了數(shù)字,追加字符。 .at(Type.NUMBER, Behaviors.AppendValue); // 當(dāng)前是數(shù)字狀態(tài),遇到了操作符,切換狀態(tài)。 .at(Type.Operator, Behaviors.NewToken); break; case OPERATOR: BehaviorMap map = new BehaviorMap(); map // 當(dāng)前是操作符狀態(tài),遇到了數(shù)字,切換狀態(tài)。 .at(Type.NUMBER, Behaviors.NewToken); break; }簡(jiǎn)化外部
現(xiàn)在我們可以看到表驅(qū)動(dòng)對(duì)于消除判斷分支的威力了,那么我們可以用同樣的方法將外部 switch 也消除掉:
public class BehaviorsTable { private static volatile BehaviorsTable instance; public static BehaviorsTable getInstance() { if (BehaviorsTable.instance == null) { synchronized ( BehaviorsTable.class ) { if (BehaviorsTable.instance == null) { BehaviorsTable.instance = new BehaviorsTable(); } } } return BehaviorsTable.instance; } private final HashMapmap; public BehaviorsTable () { this.map = new HashMap<>(); } public BehaviorMap register(Type type) { BehaviorMap behaviorMap = new BehaviorMap(); this.map.put(type, behaviorMap); return behaviorMap; } }
現(xiàn)在整個(gè)巨大的 switch 結(jié)構(gòu)我們就可以簡(jiǎn)化為:
BehaviorsTable .getInstance() .register(Type.INIT) .at(Type.NUMBER, Behaviors.NewToken) .done() .register(Type.NUMBER) .at(Type.NUMBER, Behaviors.AppendValue) .at(Type.OPERATOR, Behaviors.NewToken) .done() .register(Type.OPERATOR) .at(Type.NUMBER, Behaviors.NewToken) .done();
現(xiàn)在 process 方法我們就可以簡(jiǎn)化為:
public void process() { this.setToken(new Token(Type.INIT)); for (char ch : expression.toCharArray()) { Type type = Type.of(ch); if (Type.UNKNOWN.equals(type)) { throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch)); } BehaviorsTable .getInstance() // 根據(jù)當(dāng)前 Token 類型獲取對(duì)應(yīng)的處理對(duì)策 .get(this.token.getType()) // 獲取當(dāng)前字符所屬的處理行為 .is(type) .apply(type, ch); System.out.println(token); } }
我們?cè)谠创a里做了一些改動(dòng),請(qǐng)參考文章底部全部代碼。讓我們測(cè)試一下
public static void main(String ... args) { Scanner scanner = Scanner.getInstance(); scanner.from("10+1").process(); /** * {type: NUMBER, value: `1`} * {type: NUMBER, value: `10`} * {type: NUMBER, value: `+`} * {type: NUMBER, value: `1`} */ scanner.from("10 +1").process(); /** * {type: NUMBER, value: `1`} * {type: NUMBER, value: `10`} * Exception in thread "main" java.lang.RuntimeException: ` ` 并不屬于期望的字符類型 */ scanner.from("10++1").process(); /** * {type: NUMBER, value: `1`} * {type: NUMBER, value: `10`} * {type: OPERATOR, value: `+`} * Exception in thread "main" java.lang.RuntimeException: Invalid Character: `+` for Token `OPERATOR` */ }
現(xiàn)在看起來一切正常,但是別忘了 Scanner 的工作是將輸入的字符串分割為 Token 序列,因此我們需要讓 process 方法返回處理后的 LinkedList
為此我們需要將每次新生成的 Token 保存下來:
public class Scanner { private LinkedListtokens; private Token token; public void addToken(Token token) { this.token = token; this.tokens.add(token); } public LinkedList process() { this.setToken(new Token(Type.INIT)); for (char ch : expression.toCharArray()) { Type type = Type.of(ch); if (Type.UNKNOWN.equals(type)) { throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch)); } BehaviorsTable .getInstance() .get(this.token.getType()) .is(type) .apply(type, ch); } return this.tokens; } public static void main(String ... args) { Scanner scanner = Scanner.getInstance().from("10*5+1"); LinkedList tokens = scanner.process(); for (Token token : tokens) { System.out.println(token); } } }
記得將 Behaviors 初始化部分中 NewToken 里的行為修改一下:
behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> { token = new Token(type); token.appendValue(ch); //scanner.setToken(token); scanner.addToken(token); });
現(xiàn)在再看看結(jié)果:
{type: NUMBER, value: `10`} {type: OPERATOR, value: `*`} {type: NUMBER, value: `5`} {type: OPERATOR, value: `+`} {type: NUMBER, value: `1`}
看起來一切都如我們所愿!現(xiàn)在離最初的目標(biāo)只剩下空格的處理了,得益于我們抽象了行為 Behaviors,我們只需要在 Type 中注冊(cè)空格,然后為 BehaviorsTable 注冊(cè)各種類型下對(duì)空格的處理就行了:
public enum Type { SPACE; public Tpye of(char ch) { if (" " == ch) return SPACE; } } public enum Behaviors { Continue; static { behaviorMap .put(Behaviors.Continue, (token, type, ch) -> { // 留空就行了 }) } } public class Scanner { static { BehaviorsTable .getInstance() .register(Type.INIT) .at(Type.NUMBER, Behaviors.NewToken) .at(Type.SPACE, Behaviors.Continue) .done() .register(Type.NUMBER) .at(Type.NUMBER, Behaviors.AppendValue) .at(Type.OPERATOR, Behaviors.NewToken) .at(Type.SPACE, Behaviors.Continue) .done() .register(Type.OPERATOR) .at(Type.NUMBER, Behaviors.NewToken) .at(Type.SPACE, Behaviors.Continue) .done(); } }還缺什么呢
我們現(xiàn)在完成的掃描器實(shí)際上無法識(shí)別出 1 1 + 0 是個(gè)錯(cuò)誤的表達(dá)式,它會(huì)解析出如下序列:
{type: NUMBER, value: 11} {type: OPERATOR, value: +} {type: NUMBER, value: 0}
我個(gè)人希望這部分工作往上層放,由消化 Token 序列的調(diào)用者通過模式匹配的方式去驗(yàn)證,不過這樣的話,Type.SPACE 的處理就不能隨意 Continue 了,有興趣的話看官可以自行嘗試一下 : P
全部代碼一個(gè)小嘗試,就不傳 Github 了,直接放這兒吧 (其實(shí)就是懶...
Scanner.javapublic class Scanner { private static volatile Scanner instance; public static Scanner getInstance() { if (Scanner.instance == null) { synchronized ( Scanner.class ) { if (Scanner.instance == null) { Scanner.instance = new Scanner(); } } } return Scanner.instance; } static { // 注冊(cè)行為表 BehaviorsTable .getInstance() // 注冊(cè) INIT 狀態(tài)的行為表 .register(Type.INIT) .at(Type.NUMBER, Behaviors.NewToken) .at(Type.SPACE, Behaviors.Continue) .done() .register(Type.NUMBER) .at(Type.NUMBER, Behaviors.AppendValue) .at(Type.OPERATOR, Behaviors.NewToken) .at(Type.SPACE, Behaviors.Continue) .done() .register(Type.OPERATOR) .at(Type.NUMBER, Behaviors.NewToken) .at(Type.SPACE, Behaviors.Continue) .done(); } private String expression; private LinkedListType.javatokens; private Token token; public Scanner from(String expression) { this.expression = expression; this.tokens = new LinkedList<>(); return this; } public void setToken(Token token) { this.token = token; } public Token getToken ( ) { return token; } public LinkedList process() { this.setToken(new Token(Type.INIT)); for (char ch : expression.toCharArray()) { Type type = Type.of(ch); if (Type.UNKNOWN.equals(type)) { throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch)); } BehaviorsTable .getInstance() // 獲取當(dāng)前 Token 類型所適用的行為表 .get(this.token.getType()) // 獲取當(dāng)前字符所適用的行為 .is(type) .apply(type, ch); } return this.tokens; } public void addToken(Token token) { // 更新一下當(dāng)前 Token this.token = token; this.tokens.add(token); } public static void main(String ... args) { Scanner scanner = Scanner.getInstance().from("10 * 5+1"); LinkedList tokens = scanner.process(); for (Token token : tokens) { System.out.println(token); } } }
// Token 類型枚舉 public enum Type { INIT, // 初始化時(shí)使用 SPACE, // 空格 NUMBER, // 數(shù)字 OPERATOR, // 操作符 UNKNOWN; // 未知類型 public static Type of(char ch) { if (" " == ch) { return SPACE; } if ("0" <= ch && ch <= "9") { return NUMBER; } if ("+-*/".indexOf(ch) != -1) { return OPERATOR; } return UNKNOWN; } }Token.java
public class Token { // 一個(gè) Token 的類型一旦確定,就不可能再改變。 private final Type type; // 用以存儲(chǔ) Token 的值。 private final StringBuffer value; public Token(Type type) { this.type = type; this.value = new StringBuffer(); } // 向 value 中追加字符 public void appendValue(char ch) { this.value.append(ch); } public String getValue() { return this.value.toString(); } public Type getType() { return this.type; } @Override public String toString() { return String.format("{type: %s, value: `%s`}", this.getType().name(), this.getValue()); } }Behavior.java
public interface Behavior { /** * 將行為抽象出來 * @param token 當(dāng)前的 token * @param type 讀入字符的類型 * @param ch 讀入的字符 */ void apply(Token token, Type type, char ch); }Behaviors.java
// 預(yù)設(shè)行為 public enum Behaviors { NewToken, // 新建一個(gè)指定類型的 Token, 將當(dāng)前字符保存到新 Token AppendValue, // 將當(dāng)前字符追加到當(dāng)前 Token 的值中 Continue, // 跳過當(dāng)前字符 InvalidCharacter; // 當(dāng)前 Token 類型所不期望的字符類型,會(huì)拋出一個(gè)異常 // 持有一個(gè)引用就不用老是調(diào)用 getInstance()。 private static final Scanner scanner; // 為預(yù)設(shè)行為指定行為內(nèi)容 private static final HashMapBehaviorsMap.javabehaviorMap; static { scanner = Scanner.getInstance(); behaviorMap = new HashMap<>(); // 指定 NewToken,行為邏輯參見枚舉值說明 behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> { token = new Token(type); token.appendValue(ch); scanner.addToken(token); }); // 指定 AppendValue,行為邏輯參見枚舉值說明 behaviorMap.put(Behaviors.AppendValue, (token, type, ch) -> { token.appendValue(ch); }); // 指定 Continue,行為邏輯參見枚舉值說明 behaviorMap.put(Behaviors.Continue, (token, type, ch) -> {}); // 指定 InvalidCharacter,行為邏輯參見枚舉值說明 behaviorMap.put(Behaviors.InvalidCharacter, (token, type, ch) -> { throw new RuntimeException(String .format("Invalid Character: `%c` for Token `%s`", ch, token.getType().name())); }); } public void apply(Type type, char ch) { // 獲取預(yù)設(shè)行為 behaviorMap.get(this) // 向行為中傳遞當(dāng)前 Token, 當(dāng)前字符類型,當(dāng)前字符 .apply(scanner.getToken(), type, ch); } }
// 保存某一字符類需要執(zhí)行何種預(yù)設(shè)行為的映射關(guān)系 public class BehaviorsMap { private final HashMapBehaviorsTable.javamap; public BehaviorsMap() { this.map = new HashMap(); } /** * 注冊(cè)指定類型所需的預(yù)設(shè)行為 * @param type 指定類型 * @param behaviors 指定所需的預(yù)設(shè)行為 */ public BehaviorsMap at(Type type, Behaviors behaviors) { this.map.put(type, behaviors); return this; } // 注冊(cè)完后回退操作域到 BehaviorsTable public BehaviorsTable done() { return BehaviorsTable.getInstance(); } // 獲取指定類型的預(yù)設(shè)行為 public Behaviors is (Type type) { Behaviors behaviors = this.map.get(type); if (behaviors == null) { // 如果沒有注冊(cè),那么使用 InvalidCharacter 預(yù)設(shè)行為,因?yàn)槌霈F(xiàn)了非預(yù)期的字符類型 behaviors = Behaviors.InvalidCharacter; } return behaviors; } }
// 行為表 public class BehaviorsTable { private static volatile BehaviorsTable instance; public static BehaviorsTable getInstance() { if (BehaviorsTable.instance == null) { synchronized ( BehaviorsTable.class ) { if (BehaviorsTable.instance == null) { BehaviorsTable.instance = new BehaviorsTable(); } } } return BehaviorsTable.instance; } private final HashMapmap; public BehaviorsTable () { this.map = new HashMap<>(); } // 注冊(cè)指定當(dāng)前類型,返回一個(gè)空的 BehaviorsMap 來注冊(cè)預(yù)設(shè)行為 public BehaviorsMap register(Type type) { BehaviorsMap behaviorsMap = new BehaviorsMap(); this.map.put(type, behaviorsMap); return behaviorsMap; } // 獲取指定當(dāng)前類型的 BehaviorsMap public BehaviorsMap get(Type type) { return this.map.get(type); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70961.html
摘要:驗(yàn)證過程驗(yàn)證過程的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。二虛擬機(jī)字節(jié)碼執(zhí)行引擎虛擬機(jī)的執(zhí)行引擎自行實(shí)現(xiàn),可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系。 本篇博客主要針對(duì)Java虛擬機(jī)的類加載機(jī)制,虛擬機(jī)字節(jié)碼執(zhí)行引擎,早期編譯優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請(qǐng)點(diǎn)擊Java虛擬總結(jié)上篇 。 一.虛擬機(jī)類加載機(jī)制 概述 虛擬機(jī)把描述類的數(shù)據(jù)從Clas...
摘要:深入之繼承的多種方式和優(yōu)缺點(diǎn)深入系列第十五篇,講解各種繼承方式和優(yōu)缺點(diǎn)。對(duì)于解釋型語言例如來說,通過詞法分析語法分析語法樹,就可以開始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn) JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 寫在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 但是注意: 這篇文章更像是筆記,哎,再讓我...
摘要:作用域分類作用域共有兩種主要的工作模型。換句話說,作用域鏈?zhǔn)腔谡{(diào)用棧的,而不是代碼中的作用域嵌套。詞法作用域詞法作用域中,又可分為全局作用域,函數(shù)作用域和塊級(jí)作用域。 一篇鞏固基礎(chǔ)的文章,也可能是一系列的文章,梳理知識(shí)的遺漏點(diǎn),同時(shí)也探究很多理所當(dāng)然的事情背后的原理。 為什么探究基礎(chǔ)?因?yàn)槟悴蝗ッ嬖嚹憔筒恢阑A(chǔ)有多重要,或者是說當(dāng)你的工作經(jīng)歷沒有亮點(diǎn)的時(shí)候,基礎(chǔ)就是檢驗(yàn)?zāi)愫脡牡囊豁?xiàng)...
摘要:一言以蔽之,閉包,你就得掌握。當(dāng)函數(shù)記住并訪問所在的詞法作用域,閉包就產(chǎn)生了。所以閉包才會(huì)得以實(shí)現(xiàn)。從技術(shù)上講,這就是閉包。執(zhí)行后,他的內(nèi)部作用域并不會(huì)消失,函數(shù)依然保持有作用域的閉包。 網(wǎng)上總結(jié)閉包的文章已經(jīng)爛大街了,不敢說筆者這篇文章多么多么xxx,只是個(gè)人理解總結(jié)。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結(jié)與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:詞法分析對(duì)構(gòu)成源程序的字符流進(jìn)行掃描然后根據(jù)構(gòu)詞規(guī)則識(shí)別單詞也稱單詞符號(hào)或符號(hào)。語義分析是編譯過程的一個(gè)邏輯階段語義分析的任務(wù)是對(duì)結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查進(jìn)行類型審查,審查抽象語法樹是否符合該編程語言的規(guī)則。 1. 文章的內(nèi)容和主題 我對(duì)編譯器的深入了解起源于一條推特中的問題:Angular是如何用Angular預(yù)先編譯器(AOT)對(duì)靜態(tài)代碼進(jìn)行解析工作的。在進(jìn)行一些...
閱讀 652·2021-11-23 09:51
閱讀 3599·2021-11-15 11:38
閱讀 926·2021-10-14 09:42
閱讀 3162·2021-09-29 09:35
閱讀 2104·2021-09-03 10:33
閱讀 769·2021-07-30 16:33
閱讀 1558·2019-08-30 15:55
閱讀 1840·2019-08-30 14:04