本文轉自阿里云
摘要:編者按:在Java文章頻道里,我們大部分人應該對該語言都非常的了解,而且在該生態圈內至少已經呆了好幾年了。這讓我們有常規和專業的知識,但是也同時也讓我們一些井蛙之見。 在Outside-In Java系列文章中,一些非Java開發人員會給我們講講他們對于我們這個生態圈的看法。
編者按:在Java文章頻道里,我們大部分人應該對該語言都非常的了解,而且在該生態圈內至少已經呆了好幾年了。這讓我們有常規和專業的知識,但是也同時也讓我們一些井蛙之見。 在Outside-In Java系列文章中,一些非Java開發人員會給我們講講他們對于我們這個生態圈的看法。
從哲學的角度來講,Python幾乎是與Java截然相反。它拋棄了靜態類型和剛性結構,而是使用了一個松散的沙盒,在這里面你可以自由的做任何你想做的事情。也許Python是關于你能夠做什么,而Java則是關于你可以做什么。
然而,兩種語言都從C語言里吸取了大量的靈感。他們都是命令式語言,擁有塊、循環、方法、賦值以及中綴算術(infix math)。兩者都大量使用了類、對象、繼承以及多態性。兩者的功能異常都相當優秀。 兩者都能自動管理內存。它們都是編譯成可以運行在某種虛擬機上的字節碼,盡管Python是透明的進行編譯。 Python甚至從Java汲取了一些營養:比如基本庫的 logging 和 unittest 模塊分別是受到了log4j 和JUnit的啟發。
鑒于以上的技術重疊,我認為Java開發人員在使用Python時理應感到賓至如歸。 所以我來給你一些簡單的Python介紹。 我會可以告訴你什么使得Python與Java不同,以及為什么我覺得這些差異有吸引力。 至少,您可能會發現一些有趣的想法使您回到Java生態系統。
(如果你想要一個Python教程,Python文檔是一個很好的選擇,而且這是從Python 3的角度編寫的,Python 2的使用還是很常見的,它與Python 3有一些語法上的差異。
語法
我們先把語法弄明白。下面是 hello world入門程序:
print("Hello,?world!")
嗯, 并不是很有啟發性。 好吧,再來看一個函數,看看如何在一個文件中找到最常見的10個單詞。在這里我取巧使用了標準庫的 Counter 類型,但是它就是這么的好用。
fromcollections?import?Counter
def?count_words(path):
words?=?Counter()
withopen(path)asf:
forlineinf:
forwordinline.strip().split():
words[word]?+=?1
forword,countinwords.most_common(10):
print(f"{word}?x{count}")
Python由空格分隔。人們經常對此有強烈的意見。當我第一次看到它的時候,我 甚至認為這是異端邪說。現在,十多年過去了,這種寫法似乎自然到我很難再回到大括號式的寫法。如果你因此逃避,我甚至懷疑我可以說服你,不過我勸你至少暫時忽略一下它;實際上它并沒有造成任何嚴重的問題,反而消除了一大堆的干擾。此外,Python開發人員從來不必爭論{應該放在哪里。
除了審美上的差異之外,其他方面應該看起來很熟悉。我們有一些數字,一些賦值和一些方法調用。import 語句的工作方式有些不同,但它具有相同的“使這些內容可用”的一般含義。 Python的for循環與Java的for-each循環非常相似,只是少了點標點符號。函數本身使用def而不是類型進行分隔,但它正是按照你期望的方式工作:您可以使用參數調用它,然后返回一個值(盡管某些函數不返回值)。
只有兩件事情是很不尋常的。 一個是 with 塊,非常類似于Java 7的“try-with-resources” – 它保證文件在塊的結尾處關閉,即使會拋出一個異常。 另一個是f“…”語法,這是一個相當新的功能,允許將表達式直接插入到字符串中。
就是這樣! 你已經讀了一些Python的內容。 至少,它不是來自一個完全不同的星球的語言。
動態類型
看這個例子可能很明顯,但是Python代碼里沒有太多的類型聲明。 變量聲明上沒有,參數或返回類型上沒有,對象上也沒有。 任何值在任何時候都可以是任何類型的。 我還沒有顯示一個類定義,所以這里只是一個簡單的定義。
class?Point:
def?__init__(self,?x,?y):
self.x?=?x
self.y?=?y
def?magnitude(self):
return(self.x?**?2?+?self.y?**?2)?**?0.5
point?=?Point(3,?4)
print(point.x)??#?3
print(point.magnitude())??#?5.0
盡管x和y有并沒有定義為屬性,它們的存在是因為構造器中創建了它們。沒有誰強制我必須傳個整型參數,我也可以傳小數和分數。
如果你以前只用過靜態語言,這可能看起來一片混亂。類型是溫暖的懶惰的以及令人滿意的。他們保證···(好吧,或許不會)代碼實際能工作(雖然有人不同意)。但是當你都不知道什么是正確類型的時候,你又怎么能依靠代碼呢?
但是等等 – Java也沒有這樣的保證! 畢竟任何對象可能都是null,對吧? 實際上幾乎從來沒有一個正確類型的對象。
您可能會將動態類型視為對null問題的徹底放棄。 如果我們必須處理它,我們不妨擁抱它,并讓它為我們工作 – 通過將一切 延遲到運行時。 類型錯誤變成正常的邏輯錯誤,您可以以相同的方式處理它們。
(對于相反的方法,請參閱 Rust,它沒有空值 – 或者異常,我還是寧愿寫Python,但是我很欣賞Rust的類型系統并不總是對我說謊。)
在我的magnitude方法中,self.x是int或float或任何其他類型,這都不重要。 它只需要支持**運算符并返回支持+運算符的內容。 (Python支持操作符重載,所以這可能是任何內容。)同樣適用于普通方法調用:任何類型都可以接受,只要它實際上可以工作。
這意味著Python不需要泛型; 一切都是按照泛型工作的。 不需要接口; 一切都已經是多態的。 沒有向下轉型(downcasts),沒有向上轉型(upcasts),在類型系統中沒有逃生艙口(escape hatches)。 當它們可以和任意Iterable工作良好時,運行時API不需要List。
許多常見的模式變得更加容易。 您可以創建包裝器對象和代理,而無需更改消費代碼。 您可以使用組合而不是繼承來擴展第三方類型,而不需要為保留多態做任何特殊的操作。 靈活的API不需要將每個類作為接口復制; 一切都已經作為一個隱式接口了。
動態類型哲學
使用靜態類型,無論誰編寫 某些代碼來選擇這些類型,編譯器都會檢查它們是否正常工作。 使用動態類型,無論誰使用 某些代碼來選擇這些類型,運行時都會嘗試一下。 這就是對立的哲學在起作用:類型系統著重于你可以 做什么,而不是你可能 做什么。
這樣使用動態類型有時被稱為 “鴨子類型”(duck typing),這是基于這種思想: “如果它走起來像鴨子,而且叫起來也像鴨子,那么它就是一個鴨子。” 換言之,就是說,如果你想要的是能像鴨子一樣呱呱叫的東西的話,你不用強制你的代碼必須接收一個鴨子對象,相反的你可以接收任何給你的東西,并且讓它能夠呱呱叫即可。如果它可以達成你的目標的話,那么它就跟鴨子一樣好用。(否則如果它無法如你所愿,會拋出AttributeError的錯誤,但是這也沒什么大不了的。)
同時也要注意Python是強類型的。這個詞有點模糊,它通常意味著變量的值在運行時會一直保持其類型不變。一個典型的例子是,Python不會讓你把一個字符串賦值給一個數字類型的變量,而像弱類型語言,如JavaScript 則可以將一種類型靜默的轉換成另一種,這里使用了優先級的規則,跟你的預期會有所不同。
與大多數動態語言不同的是,Python 在運行之前就可以捕獲錯誤信息。例如讀取一個不存在的變量會產生一個異常,包括從一個字典對象(如 Map)中讀取一個不存在的鍵時也會報錯。在 JavaScript 、Lua 和類似語言中,上面兩種情況是返回 null 值。(Java 也是返回 null 值)。如果想在值不存在時返回默認值,字典類提供了更加明確的方法調用。
這里絕對是一個權衡的結果,是否值得取決于不同的項目和人。對我來說,至少非常適合用來設計一個更加可靠的系統,以為我可以看到它實際執行的情況。但是靜態類型的語言都期待有一個預先的設計。靜態類型很難嘗試各種不同的想法,更難發揮。
你確實擁有了更少的靜態檢查保證,但根據我的經驗,大多數的類型錯誤可以正確被捕獲……因為我寫完代碼的第一件事就是嘗試去運行它!其它任何錯誤應該可以被你的測試所捕獲—測試應該在所有語言中都有,并且python語言寫測試相對來說更容易。
一個混合的范例
Python 和 Java 都是命令式和對象導向的:它們的工作方式是執行指令,它們把每件事物塑造為對象。
在最近的發行版本中,Java增加了一些函數式語言特征,我認為這是一件好事。Python也有函數式語言特征,但是實現的方式不太一樣。它提供了一些內置的函數如map和reduce,但是它并不是基于串聯許多小函數的思想來設計的。
相反,Python混合了其它東西。我不知道Python采用的方法的通用名稱。我認為它把“函數鏈”的思想分為兩個:作用于序列上和使函數自身更加有用。
序列
序列和迭代在Python有重要的地位。序列是最基礎的數據結構,作為于之上的工具非常有價值。我認為Python對于函數式編程的實現如下:Python首先使得使用命令式代碼來操作序列非常容易,而不是使得結合許多小函數然后應用于序列非常容易。
回到本文開始的地方,我曾寫下這一行代碼:
forword,countinwords.most_common(10):
for循環對我們來說很熟悉,但是這行代碼一次迭代了兩個變量。 實際發生的是,列表most_common中的每個元素返回一個元組,它們是一組按順序區分的值。 真正發生的是元組可以通過將它們分配給元組變量名來解包。 元組通常用于在Python中返回多個值,但它們偶爾也可用于特殊結構。 在Java中,您需要一個完整的類和幾行分配值的代碼。
任何可以迭代的東西同樣可以解包。 解包支持任意嵌套,所以a,(b,c)= …按照它看起來的樣子解包。 對于未知長度的序列,一個*leftovers元素可以出現在任何地方,并且將根據需要獲取盡可能多的元素。 也許你真的會喜歡LISP?
values=?[5,?7,?9]
head,?*tail?=values
print(head)??#?5
print(tail)??#?(7,?9)
Python還具有通過簡單表達式創建列表的語法 – 所謂的“列表解析” – 這比函數方法如map更為常見。 存在類似的語法用于創建分組和集合。 整個循環可以減少到一個單一的表達式,只突出你真正感興趣的部分。
values=?[3,?4,?5]
values2?=?[val?*?2forvalinvaluesif?val?!=?4]
print(values2)?#?[6,?10]
標準庫還在itertools模塊中包含了許多有趣的迭代,組合器和用法。
最后,Python具有用于生成命令行代碼的延遲序列的生成器。 包含yield關鍵字的函數在被調用時不立即執行; 而是返回一個生成器對象。 當生成器迭代結束時,該函數運行直到它遇到一個yield,此時它暫停; 生成的值將成為下一個迭代值。
def?odd_numbers():
n?=?1
whileTrue:
yield?n
n?+=?2
forxinodd_numbers():
print(x)
if?x?>?4:
break
#?1
#?3
#?5
因為生成器延遲運行,它們可以產生無限序列或在中途中斷。 他們可以產生大量的大型對象,不會因為讓它們全部存活而消耗一大堆內存。 它們也可以作為“鏈式”函數編程風格的一般替代。 您可以編寫熟悉的命令行代碼,而不是組合map和filter。
#?Thisisthe?pathlib.Path?APIfromthe?standard?library
def?iter_child_filenames(dirpath):
forchildindirpath.iterdir():
if?child.is_file():
yield?child.name
要在Java中表示完全任意的惰性迭代器,您需要編寫一個手動跟蹤其狀態的Iterator。除了最簡單的情況之外,這一切都會變得相當棘手。 Python也有一個迭代接口,所以您仍然可以使用這種方法,但是生成器非常容易使用,以至于大多數自定義迭代都是用它們編寫的。
而且因為生成器可以自己暫停,所以它們在其他一些上下文中是有用的。 通過手動調用生成器(而不是僅用一個for循環來一次性全部迭代),就可以在一段時間內運行一個功能,讓它在某一點停止,并在恢復該函數之前運行其他代碼。 Python充分利用這一點來添加對異步I/O(不使用線程的非阻塞網絡)的支持,盡管現在它具有專用的async 和await 語法。
函數
乍一看,Python的函數看上去非常面熟。你可以使用參數來調用它們。傳遞風格與Java完全相同—Python既沒有引用也沒有隱式復制。 Python甚至有“docstrings”,類似于Javadoc注釋,但它是內置的語法并且在運行時可見。
def?foo(a,?b,?c):
"""Print?out?the?arguments.?Not?a?very?useful?function,?really."""
print("I?got",?a,?b,?c)
foo(1,?2,?3)?#?I?got?1?2?3
Java具有args …語法的可變函數; Python使用* args可以實現類似的功能。 (用于解包的*leftovers語法靈感來源于函數語法。)但是,Python還有一些技巧。任何參數都可以有一個默認值,使其成為可選項。任何參數也可以通過名稱 給出 – 我之前使用Point(x = 3,y = 4)演示了這點。在調用 任何函數時,可以使用* args語法,它將傳遞一個序列,就像它是單獨的參數一樣,并且有一個等價的** kwargs將命名參數作為一個dict接受或傳遞。一個參數可以作為“僅關鍵字(keyword-only)”,所以它必須 通過名稱傳遞,這對于可選的布爾值是非常好的。
Python當然沒有 函數重載,但是你使用它實現的功能大部分都可以被鴨子類型(duck typing)和可選參數替換。
這是現階段Python最強大的功能之一。 與動態類型一樣,您可以通過包裝器或代理透明地替換對象,* args和** kwargs允許任何函數 被透明地包裝。
def?log_calls(old_function):
def?new_function(*args,?**kwargs):
print("i'm?being?called!",?args,?kwargs)
returnold_function(*args,?**kwargs)
returnnew_function
@log_calls
def?foo(a,?b,?c=3):
print(f"a?=?{a},?b?=?{b},?c?=?{c}")
foo(1,?b=2)
#?i'm?being?called!?(1,)?{'b':?2}
#?a?=?1,?b?=?2,?c?=?3
這有點難以理解,對不起。 不用擔心它究竟是如何工作的; 要點是,foo被一個new_function替換,它將所有的參數轉發到foo。 foo和調用者都不需要知道哪些事情有哪些不同。
我不能低估它有多么強大。 它可用于記錄,調試,管理資源,緩存,訪問控制,驗證等。 它與其他元編程功能工作良好,同樣地,它可以讓您分解結構,而不僅僅是代碼。
對象和動態運行時
動態運行時是一種在背后驅動語言核心部分的東西 — 它可以在運行時被執行。像 C 或者 C++ 這類的語言絕不會具有動態運行時;它們源碼的結構被“烘焙”成編譯輸出,并且后續沒有明顯的方法來改變它的行為。從另一方面,Java的確具有動態運行時!它甚至帶有一整個專門用于反射的包。
Python 當然也有反射。很多簡單的函數通過反射被構建,用來聯機檢查或修改對象的屬性,這對調試以及偶爾的欺騙非常有用。
但 Python 對此更深入一點。 因為一切都在運行時完成,Python 暴露了很多擴展點來自定義它的語義。 你不能改變語法,代碼依然看起來像 Python,但你可以分解結構 — 這在一種更死板的語言是非常難以做到的。
舉個極端的例子,看下 pytest, 它聰明的處理了 Python 的 assert 聲明。 通常, assert x == 1 為 false 時只會簡單的拋出一個 AssertionError 異常,導致你不知道錯誤是什么或者哪里出錯了。這就是為什么 Python 內置的單元測試模塊 — 像 JUnit 和很多其他的測試工具 — 提供很多專門的工具函數,比如 assertEquals。不幸的是,這些工具函數使得測試初看到時,更加復雜更難以讀懂。但在 pytest 中, assert x == 1 是好用的。如果失敗,pytest 將告訴你 x 是… 或者兩個列表哪里有分歧,或者兩個集合哪些元素不同, 或者其他的。所有的這些都是基于比較完成和運算對象的類型自動發生的。
pytest是如何工作的呢? 你確實不想知道。你不需要知道如何用pytest寫測試 — 這使人很開心。
這就是動態運行時的真正優勢所在。 就自己而言,可能沒有使用這些功能。但是你能從這些庫中收獲巨大的好處,你使用這些庫并不需要關心它們如何運行。 甚至 Python 本身依靠使用自己的擴展點實現了很多額外的特性 — 這些不需要改變語法或者解釋器。
對象
屬性(在C#中一般翻譯成特性)存取是我喜歡舉的一個簡單例子。在 Java 中,一個 Point 類可能選擇用 getX() 和 setX() 方法而不是一個簡單直白的 x 屬性。原因是如果你需要改變 x 的讀或寫,你不需要破壞接口。在 Python 中,你不需要擔心前面的這些, 因為必要時你能解釋屬性存取。
class?Point:
def?__init__(self,?x,?y):
self._x?=?x
self._y?=?y
@property
def?x(self):
returnself._x
#?...?samefory?...
point?=?Point(3,?4)
print(point.x)??#?3
有趣的 @property 語法是一種修飾,看來就像 Java 的注解,但它可以更直接地修改函數或類。這是完全透明的調用代碼——和讀其它屬性沒什么區別——但是對象可以根據自己的需要干預或處理它。與 Java 不同,屬性訪問是類 API 的一部分,可以自由的定義。(注意這個示例讓 x 成為只讀的,因為我沒有指定寫方法!可寫屬性的語法有點滑稽,這里暫時不管它如何工作。但你可以具體規定只有奇數可以賦值給 point.x。)
這個特性也存在于其它靜態語言中,比如 C#,所以這并不是什么大不了的東西。關于 Python 真正有趣的部分是,屬性并沒什么特別。它是一個正常的內建類型,一段純粹而且不滿一屏的 Python 程序。它的工作原理是 Python 類可以自定義其屬性訪問,包括一般的和按屬性的。包裝、代理和組合很容易實現:你可以將所有訪問調用轉發到底層對象,而不必知道它有什么方法。
相同的鉤子屬性可用于懶加載屬性或者自動持有弱引用的屬性——這對調用代碼完全透明,通過純 Python 就能實現。
你可能已經注意到我的代碼沒有使用 public 或 private 修飾符。事實上,Python 中不存在這個概念。按照慣例,用一個下劃線開頭表示“私有”——或者更準備地說,“不打算成為穩定公開 API 的一部分”。但這并沒有語言上的意義,Phthon 本身不會阻止偵查或修改這樣的屬性(如果是方法的話,則是調用)。同樣,也沒有final 、static或const。
這是同樣的工作原理:核心 Python 通常不會妨礙你做任何事情。如果你需要,它會非常有用。我已經通過啟動時調用或重寫,甚至重新定義私有方法來修補第三方庫的 BUG。這樣我不需要重新做一個項目的本地分支,可以省不少事。而且一量官方修復了 BUG,可很容易就能刪掉自己的補丁代碼。
同樣,你可以輕松地編寫依賴外部狀態的測試代碼——比如當前時間。如果重構不可行,你可以在測試時把 time.time() 替換為模擬函數。庫函數只是模塊模塊的屬性(就像 Java 的包),而且 Python 模塊是種對象,和其它對象一樣,所以它們可以以同樣的方式偵查到或被修改。
類
Java 的類由 Class 對象支持,但二者并不有完全互換。比如 Foo 類的 Class 對象是 Foo.class。我不認為 Foo 可以被它自己使用,因為它命名了一個類型,Java 在處理類型和值的時候會有一些微妙的區別。
Python 中,類是對象,是類型的實例(它本身就是對象,是它自己的實例,想起來很有趣。)類可以當作其它值一樣使用:作為參數、保存一某個更大的數據結構中、檢查、操作。有時候把類作為字典的鍵特別有用。而且因為類是實例化的,可以簡單地調用它們——Python 沒有 new 關鍵字 —— 很多情況下 new 和簡單的函數調用可以互換。這樣一來,一些常見的模式,比如工廠模式,就太簡單了,幾乎不需要。
#?等等,Vehicle?是一個類還是一個工廠函數?誰管呢!
#?就算它在類或工廠函數之間互換,也不會破壞這段代碼。
car?=?Vehicle(wheels=4,?doors=4)
最近我好幾次把函數甚至常規代碼放在頂層,不在任何類里面。這樣做不會有問題,但是其含義有點微妙。Python 中甚至 class 和 def 都是常規代碼,在運行的時候執行。Python 文件從上往下執行,class 和 def 并不特別。它們只是有特殊的語法,用來創建特定類型的對象:類和函數。
以下是真正酷的部分。類是對象,它們的類型是type,因此你可以子類化并改變它的運行。然后,你可以生成你的子類的實例。
第一次接觸仔細想想會感覺有點奇怪。但是再想下,你獲益于不需要知道它是如何運行的。比如,Python沒有enum塊,但它確實有 enum module:
class?Animal(Enum):
cat?=?0
dog?=?1
mouse?=?2
snake?=?3
print(Animal.cat)???????????#?
print(Animal.cat.value)?????#?0
print(Animal(2))????????????#?
print(Animal['dog'])????????#?
class 語句創建了一個對象,這意味著它在某處調用了構造函數,而且可以重寫該構造函數來改變類的構造方式。這里Enum創建了一個固定的實例集,而不是類屬性。所有這些都是用普通的 Python 代碼的常規的 Python 語法實現的。
實體庫也是基于這個思路來構建的。你討厭在構造函數中單調的干 self.foo = foo 這種事情嗎?然后純手工定義相等性比較、哈希和克隆和開發可讀的列表?Java 需要編譯器支持,這個支持可能來自 Amber 項目。Python 非常靈活,所以社區中有 attrs 庫解決了這個問題。
import?attr
@attr.s
class?Point:
x?=?attr.ib()
y?=?attr.ib()
p?=?Point(3,?4)
q?=?Point(x=3,?y=4)
p?==?q??#True,?which?it?wouldn't?have?been?before!
print(p)??#?Point(x=3,?y=4)
或者采用 SQLAlchemy 這個功能強大的 Python 數據庫封裝庫。它包含一個靈感來自 Hibernate 的 ORM,但不需要在配置文件里定義表結構或者通過其他冗長的注解,你可直接在類里編寫數據庫映射代碼:
classOrder(Table):
id?=Column(Integer,?primary_key=True)
order_number?=Column(Integer,index=True)
status?=Column(Enum('pending','complete'),default='pending')
...
它的基本思想類同Enum,但SQLAlchemy也使用和property同樣的鉤子,自然而然地,你可以修改欄位值。
order.order_number?=?5
session.commit()
最后,類本身可以在運行中被創建。 這有點好處,可是 thriftpy 創建的整個 module, 里面全是基于 Thrift 定義文件的類。 在Java中,你得需要代碼生成,這就增加了全新的編譯步驟,從而導致不同步。
所有這些示例依賴于Python現存的語法,但也在里面吸收了新的含義。它能做的,沒有你在Java或者其他語言中不能做的。但它刪減了結構性的重復 — 正是這些使得編碼更易寫、易讀,以及產生更少的bug。
結語
Python 有很多和Java相同的基本概念,但處理方向非常不同,它加入了一些全新的思想。Java關注于穩定性和可靠性,而Python關注于可表達性和靈活性。它是一種完全不同的思想方式來思考命令式編程。
我有理由相信Python將讓你在Java所擅長的領域替換Java。Python可能不會贏在速度競賽上,比如(Pypy,一種即時編譯的Python)。Java擁有對線程的原生支持,而Python界大都回避了這些問題。規模龐大復雜又有很多死角的軟件更喜歡靜態類型提供的合理性檢查(比如mypy,一種Python靜態類型檢查器)。
也許某些 Java 并不擅長的領域剛好是 Python 的強項。例如大量的軟件對性能的要求并不高,也不需要并行執行,我們只需要考慮具體實現業務的問題。我發現使用 Python 開始一個項目非常簡單而且快速。沒有單獨的編譯過程,編碼然后運行的速度非常快。代碼很簡短,這也意味著代碼更容易被理解。嘗試不同的架構方法看起來更加容易。而且有時候嘗試一些愚蠢的想法是如此的有趣,就好像 使用庫實現 goto 功能。
我希望你能試試 Python ,自從我用了它之后,已經從中獲得很多樂趣,我相信你也一樣。至少不用刻意的去拒絕它。
最壞的情況,還有個 Pyjnius 可以讓你這樣做:
fromjnius?import?autoclass
System?=?autoclass('java.lang.System')
System.out.println('Hello,?world!')
好了同學們,我能介紹的也都全部介紹完給你們了,以上的部分就是我想說的內容,如果你也想在IT行業拿高薪,可以參加我們的JAVA全棧晉階訓練營課程,選擇最適合自己的課程學習,技術大牛親授,7個月后,進入名企拿高薪。我們的課程內容有:反射原理、枚舉原理與應用、注解原理、常用設計模式、正規表達式高級應用、JAVA操作Office原理詳解、JAVA圖像處理技術,等多個知識點的詳解和實戰。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿里面試但擔心面試不過的,你都可以來,群:240448376注:加群要求
1、具有1-3工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。
3、如果沒有工作經驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。
5.阿里Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!
6.小號或者小白之類加群一律不給過,謝謝。
最后,每一位讀到這里的網友,感謝你們能耐心地看完。覺得對你有幫助可以給個喜歡!希望在成為一名更優秀的Java程序員的道路上,我們可以一起學習、一起進步