分享Java 中如何運行字符串表達式?

在平常的開發中,偶爾會遇到運行字符串表達式的狀況,一般這樣的需求會對需求進行進一步分析,而後進行進一步 「特殊化」,最後直接寫到硬代碼中,這樣作的話,就不太好擴展了;也有另外的處理方式是採用 Java 內置的 JavaScript 引擎等運行字符串表達式,可是內置引擎也有弊端,好比頻繁運行片斷式的字符串的效率很是低,而且與 Java 之間的數據交互比較麻煩,因而,便產生了寫一個「字符串表達式計算引擎」的想法...git

寫的過程其實沒想象中那麼麻煩,最第一版大概在今年 5 月底寫好,可是結構比較混亂,寫的時候基本上是一邊寫一邊修,最後 if...else...這樣的條件以及嵌套太多,以致於本身也沒法徹底理解,好在邏輯基本完善,運行也沒出現意料以外的狀況(也許出現了,只是沒發現),而且是本身用,因此就沒太在乎。github

前兩個星期,又抽空從新整理了一遍,從新梳理了一下結構,擴展了一些功能,從新定義了一下各類符號的 「語義邊界」,儘量保證運算符與 Java 自己運算符一致,邏輯結構也更清晰,不會產生意外狀況等。web

RunnerUtil 在語法上很大程度參考了 JavaScript 的語法,好比用花括號表示一個鍵值對「對象」(實際上會被解析成 HashMap),鍵名沒必要用單引號或雙引號包裹,單引號雙引號均表示普通字符串,經過點號(.)和方括號鏈式取值等。這對於從事 JavaWeb 開發的同窗來講,書寫起來也比較方便。如今已經實現了絕大部分功能,已實現的功能也通過必定測試,確保能「符合指望」的運行,若是有想法和建議也但願多多的提一下哈。express

基本用法介紹

字符串表達式經過一個叫 RunnerUtil 的靜態類運行,能夠直接運行獲得表達式結果,也能夠解析一個表達式後在須要的時候運行,RunnerUtil 主要有如下幾個方法:數組

  • RunnerUtil.run(/* expression */); 直接運行表達式並獲得結果;
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run("1 + 1"); // 2
RunnerUtil.run(" 'Hello' + ' ' + 'World!' "); // "Hello World!"
複製代碼</pre>
  • RunnerUtil.run(/* expression */, / * data */); 運行含有變量的表達式,後面的 data 是變量將要指向的「值」;
  • RunnerUtil.parseRun(/* expression */); 直接運行「另外一種」表達式,並獲得結果,如:
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.parseRun("Hello {{   'World!'   }}"); // "Hello World!"
複製代碼</pre>

可見 #parseRun 是運行包含「插值語法」的表達式,被包裹的內容被做爲一個表達式單獨運行;架構

字符串中能夠包含多個插值語法表達式,但不能嵌套和交叉,也能夠運行含有變量的表達式。less

  • Runner runner = RunnerUtil.parse(/* expression */);

解析一個字符串表達式,獲得一個「字符串表達式運行器」 —— Runner,而後調用其 run(/ * data */) 方法運行並獲得結果。工具

語法及運算詳細介紹

做爲一個具備必定「語言特色」的東西,它定義了一些本身的語法、數據類型、運算類型等,但大部分都與 Java 和 JavaScript 兼容,相同符號具備相同或類似的語言意義。學習

數據類型:

  1. null:這是一個關鍵字,但由於它符合和變量的定義規則,因此須要注意一下,一樣被定義爲關鍵字的還有 true 和 false。
  2. boolean:true 和 false
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" null   "); // null
RunnerUtil.run("   true "); // true
RunnerUtil.run("false"); // false
// 表達式中多餘的空格自動忽略
複製代碼</pre>
  1. 數字:這裏面的數字統一採用 Java 裏的 int 和 double 型數據,直接參與運算的也只有是這兩種類型,區別就是有沒有小數點。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" 12  "); // 12
RunnerUtil.run(" 12.5 "); // 12.5
// 表示數字必須是連續,中間不能有空格的
// 不然將拋出異常,如
RunnerUtil.run(" 12\. 5"); // 異常
RunnerUtil.run(" 1 2 "); // 異常
複製代碼</pre>

表示數字的字符之間應該是連續的,如:2五、36.9 等;若是是不連續的會拋出異常,如:2 五、36 .9 等;測試

  1. 字符串:Java 裏的字符串用雙引號包裹,在這裏還將表示字符的單引號「徵用」,雙引號單引號包裹的都表示普通字符串的直接值,這樣作也是爲了書寫方便(與 JavaScript 類似),同時也就沒有了 char 類型數據啦啦啦……
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" 'abcdef'  "); // "abcdef"
RunnerUtil.run(" \"abcdef\"  "); // "abcdef"
RunnerUtil.run(" 'abc   def'  "); // "abc   def"
複製代碼</pre>
  1. List:其實是 ArrayList,對應 JavaScript 裏面的數組。Java 的數組也對應 JavaScript 數組。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" { } "); 
// 老是返回一個空ArrayList

RunnerUtil.run(" {1,2,,4, } ");
// 老是返回一個包含:一、二、null、4 這幾項的 ArrayList

// 能夠看出最後一個逗號以後若是是結束符號會自動忽略
// 中間的逗號與逗號之間若沒有其餘非空白符號會插入一個 null 值
複製代碼</pre>

  1. Map:其實是 HashMap,對應 JavaScript 裏的對象。一樣對應 JavaScript 對象的還有普通 POJO。

Map 對應的是 JavaScript 裏的對象,可是在這裏 Map 的鍵能夠是這些數據類型:

null、true / false、數字(int / double)、字符串,不能再是其餘 Java 對象了

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" {:} "); // 老是返回一個空 HashMap,
// 注意與空 List 的異同,都是用花括號表示
// 但空 Map 裏面須要有一個冒號,不然就是 List

RunnerUtil.run(" {key: 'value'}");
// 老是返回包含一個鍵值對的 HashMap
// 能夠看出,對象的鍵名是字符串的話能夠不用引號包裹
// 可是值必須被包裹
RunnerUtil.run(" {true: 'value'}"); // 鍵是 true
/*
 * 這裏的 true 不是字符串,而是 boolean。
 * 一樣,未被引號包裹的 null、false、數字都是對應類型的數據,而不是字符串
 * 其餘符合變量命名規則的鍵都是普通字符串,被單引號或雙引號包裹的也是
 */
RunnerUtil.run(" {'true': 'value', 25: false, 'name': \"張三\"}");
複製代碼</pre>

運算支持的類型:

  1. 普通四則混合運算:+、-、*、/、%、()
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" 1 + 1 "); // 2
RunnerUtil.run(" 1 + (3 * 4)) "); // 13
RunnerUtil.run(" 'Hello ' + \"World!\" ");  // "Hello World!"
RunnerUtil.run(" true + false "); // "truefalse"
/*
 * true+false 在 Java 中是不容許的
 * 但若是是「+」運算的話,這裏均做爲普通字符串;
 * 至關於調用了 toString 方法
 */
複製代碼</pre>
  1. 位運算:&、|、^、<<、>>
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" 1 ^ 1 "); 
RunnerUtil.run(" 1 & 1 "); 
RunnerUtil.run(" 1 | 1 "); 
RunnerUtil.run(" 1 << 1 "); 
RunnerUtil.run(" 1 >> 1 ");
複製代碼</pre>
  1. 比較運算:>、>=、==、<=、<
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" 1 + 1 == 2 "); // true
RunnerUtil.run(" 1 + 1 < 2 "); // false
複製代碼</pre>
  1. 邏輯運算:&&、||、!
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run("1+1==2 && 5 > 4"); // true
複製代碼</pre>
  1. 變量:命名規則與 Java 變量命名規則相同,同時 null、true、false 不能做爲變量

表達式中包含變量就表明這個表達式在運行獲得結果時須要從外部獲取數據,若是不能正確的從數據源讀取到數據,運行就會拋出異常;在此我向你們推薦一個架構學習交流裙。交流學習裙號:687810532,裏面會分享一些資深架構師錄製的視頻錄像

<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run(" 'Hello, ' + name "); // 拋出異常

Map data = new HashMap();
data.put("name", "Li Lei!");

RunnerUtil.run(" 'Hello, ' + name ", data); // "Hello, Li Lei!"
複製代碼</pre>
  1. 鏈式取值:鏈式語法與 JavaScript 很類似
<pre class="prettyprint hljs thrift" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">HashMap data = new HashMap(); 

ArrayList list = new ArrayList(); 
list.add(true); 
list.add(false); 
list.add(25); 
list.add('隔壁老王'); 

HashMap map = new HashMap(); 
map.put("name", "小四"); 
map.put("index", 2); 
map.put(true, "true 是 Boolean 類型做爲鍵"); 

data.put("list", list); 
data.put("map", map); 

RunnerUtil.run("map.name", data); // "小四"

RunnerUtil.run("map['name']", data); 
// "小四" (也能夠這樣取值)

RunnerUtil.run("list[ 2 ]", data);
// 25 (索引取值須要用方括號包裹) 

RunnerUtil.run("list[3]", data);
// "隔壁老王" (索引取值須要用方括號包裹) 

RunnerUtil.run("list[map.index]", data); // 25
// (這是高級點的用法,方括號包含另外一個表達式
// 返回值是一個索引,而後返回索引指向的值)

RunnerUtil.run("[true]", data); // "true 是 Boolean 類型做爲鍵"
// 若是不用方括號包括,true 就是一個直接值,返回 true
// 那麼問題來了:
// 若是傳入的數據不是 Map 或 POJO,而是 List 或數組怎麼辦呢?
RunnerUtil.run(" [1] ", list); // false
// 啊……唐宗宋祖,略顯風騷!

// 這種鏈式語法與 JavaScript 很類似
複製代碼</pre>
  1. 運行方法:目前只能運行無參和一個參數的方法,變長參數的方法支持不完善,慎用。
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">這裏的數據 data 繼續用上一條的 data,具體數據不寫了

RunnerUtil.run("map.size()", data); // 3
RunnerUtil.run("map.get('name')", data); // "小四" 
RunnerUtil.run("map.get('name').length()", data); // 2
RunnerUtil.run("map.name.length()", data); // 2
RunnerUtil.run(" [3].length() ", list); // 4
// 唐宗宋祖,又顯風騷!
複製代碼</pre>
  1. 運行靜態方法: @ ;運行靜態方法須要用到「@」符號做爲標記。目前也不支持多參數方法調用。

當你打開源碼會發現這是一整個獨立的工具庫,不少方法和 commons-lang 包內容類似(我的認爲不是重複造輪子,也有不少不一樣的和不如的)...,運行靜態方法也能夠運行這個工具庫內的全部工具方法,暫時未將 RunnerUtil 剝離出來,也還不支持自定義的靜態方法調用,不過這個工具庫所提供的功能

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">RunnerUtil.run("@System.currentTimeMillis() ");
// 15.....(一個毫秒數)
RunnerUtil.run("@Objects.toString(25) "); // "25"
複製代碼</pre>

綜上,就是這個工具庫所支持的字符串表達式運算了,以上所列舉的運算能夠嵌套、鏈接、可是不能交叉的進行運算。接下來要作的是加入的功能是多參數方法調用,但願對你們的平常開發有所幫助,也但願你們給點意見,如出現 BUG 必定在最快的時間內修改,謝謝你們啦啦啦!!

相關文章
相關標籤/搜索