九、多態(2)

本章概要

  • 構造器和多態
    • 構造器調用順序
    • 繼承和清理
    • 構造器內部多態方法的行為
  • 協變返回類型
  • 使用繼承設計
    • 替代 vs 擴展
    • 向下轉型與運行時類型信息

構造器和多態

通常,構造器不同于其他類型的方法。在涉及多態時也是如此。盡管構造器不具有多態性(事實上人們會把它看作是隱式聲明的靜態方法),但是理解構造器在復雜層次結構中運作多態還是非常重要的。理解這個可以幫助你避免一些不愉快的困擾。

構造器調用順序

在“初始化和清理”和“復用”兩章中已經簡單地介紹過構造器的調用順序,但那時還沒有介紹多態。

在派生類的構造過程中總會調用基類的構造器。初始化會自動按繼承層次結構上移,因此每個基類的構造器都會被調用到。這么做是有意義的,因為構造器有著特殊的任務:檢查對象是否被正確地構造。由于屬性通常聲明為 private,你必須假定派生類只能訪問自己的成員而不能訪問基類的成員。只有基類的構造器擁有恰當的知識和權限來初始化自身的元素。因此,必須得調用所有構造器;否則就不能構造完整的對象。這就是為什么編譯器會強制調用每個派生類中的構造器的原因。如果在派生類的構造器主體中沒有顯式地調用基類構造器,編譯器就會默默地調用無參構造器。如果沒有無參構造器,編譯器就會報錯(當類中不含構造器時,編譯器會自動合成一個無參構造器)。

下面的例子展示了組合、繼承和多態在構建順序上的作用:

// polymorphism/Sandwich.java
// Order of constructor calls
// {java polymorphism.Sandwich}
package polymorphism;class Meal {Meal() {System.out.println("Meal()");}
}class Bread {Bread() {System.out.println("Bread()");}
}class Cheese {Cheese() {System.out.println("Cheese()");}
}class Lettuce {Lettuce() {System.out.println("Lettuce()");}
}class Lunch extends Meal {Lunch() {System.out.println("Lunch()");}
}class PortableLunch extends Lunch {PortableLunch() {System.out.println("PortableLunch()");}
}public class Sandwich extends PortableLunch {private Bread b = new Bread();private Cheese c = new Cheese();private Lettuce l = new Lettuce();public Sandwich() {System.out.println("Sandwich()");}public static void main(String[] args) {new Sandwich();}
}

輸出:

Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

這個例子用其他類創建了一個復雜的類。每個類都在構造器中聲明自己。重要的類是 Sandwich,它反映了三層繼承(如果算上 Object 的話,就是四層),包含了三個成員對象。

從創建 Sandwich 對象的輸出中可以看出對象的構造器調用順序如下:

  1. 基類構造器被調用。這個步驟被遞歸地重復,這樣一來類層次的頂級父類會被最先構造,然后是它的派生類,以此類推,直到最底層的派生類。
  2. 按聲明順序初始化成員。
  3. 調用派生類構造器的方法體。

構造器的調用順序很重要。當使用繼承時,就已經知道了基類的一切,并可以訪問基類中任意 publicprotected 的成員。這意味著在派生類中可以假定所有的基類成員都是有效的。在一個標準方法中,構造動作已經發生過,對象其他部分的所有成員都已經創建好。

在構造器中必須確保所有的成員都已經構建完。唯一能保證這點的方法就是首先調用基類的構造器。接著,在派生類的構造器中,所有你可以訪問的基類成員都已經初始化。另一個在構造器中能知道所有成員都是有效的理由是:無論何時有可能的話,你應該在所有成員對象(通過組合將對象置于類中)定義處初始化它們(例如,例子中的 bcl)。如果遵循這條實踐,就可以幫助確保所有的基類成員和當前對象的成員對象都已經初始化。

不幸的是,這不能處理所有情況,在下一節會看到。

繼承和清理

在使用組合和繼承創建新類時,大部分時候你無需關心清理。子對象通常會留給垃圾收集器處理。如果你存在清理問題,那么必須用心地為新類創建一個 dispose() 方法(這里用的是我選擇的名稱,你可以使用更好的名稱)。由于繼承,如果有其他特殊的清理工作的話,就必須在派生類中重寫 dispose() 方法。當重寫 dispose() 方法時,記得調用基類的 dispose() 方法,否則基類的清理工作不會發生:

// polymorphism/Frog.java
// Cleanup and inheritance
// {java polymorphism.Frog}
package polymorphism;class Characteristic {private String s;Characteristic(String s) {this.s = s;System.out.println("Creating Characteristic " + s);}protected void dispose() {System.out.println("disposing Characteristic " + s);}
}class Description {private String s;Description(String s) {this.s = s;System.out.println("Creating Description " + s);}protected void dispose() {System.out.println("disposing Description " + s);}
}class LivingCreature {private Characteristic p = new Characteristic("is alive");private Description t = new Description("Basic Living Creature");LivingCreature() {System.out.println("LivingCreature()");}protected void dispose() {System.out.println("LivingCreature dispose");t.dispose();p.dispose();}
}class Animal extends LivingCreature {private Characteristic p = new Characteristic("has heart");private Description t = new Description("Animal not Vegetable");Animal() {System.out.println("Animal()");}@Overrideprotected void dispose() {System.out.println("Animal dispose");t.dispose();p.dispose();super.dispose();}
}class Amphibian extends Animal {private Characteristic p = new Characteristic("can live in water");private Description t = new Description("Both water and land");Amphibian() {System.out.println("Amphibian()");}@Overrideprotected void dispose() {System.out.println("Amphibian dispose");t.dispose();p.dispose();super.dispose();}
}public class Frog extends Amphibian {private Characteristic p = new Characteristic("Croaks");private Description t = new Description("Eats Bugs");public Frog() {System.out.println("Frog()");}@Overrideprotected void dispose() {System.out.println("Frog dispose");t.dispose();p.dispose();super.dispose();}public static void main(String[] args) {Frog frog = new Frog();System.out.println("Bye!");frog.dispose();}
}

輸出:

在這里插入圖片描述

層級結構中的每個類都有 CharacteristicDescription 兩個類型的成員對象,它們必須得被銷毀。銷毀的順序應該與初始化的順序相反,以防一個對象依賴另一個對象。對于屬性來說,就意味著與聲明的順序相反(因為屬性是按照聲明順序初始化的)。對于基類(遵循 C++ 析構函數的形式),首先進行派生類的清理工作,然后才是基類的清理。這是因為派生類的清理可能調用基類的一些方法,所以基類組件這時得存活,不能過早地被銷毀。輸出顯示了,Frog 對象的所有部分都是按照創建的逆序銷毀的。

盡管通常不必進行清理工作,但萬一需要時,就得謹慎小心地執行。

Frog 對象擁有自己的成員對象,它創建了這些成員對象,并且知道它們能存活多久,所以它知道何時調用 dispose() 方法。然而,一旦某個成員對象被其它一個或多個對象共享時,問題就變得復雜了,不能只是簡單地調用 dispose()。這里,也許就必須使用_引用計數_來跟蹤仍然訪問著共享對象的對象數量,如下:

// polymorphism/ReferenceCounting.java
// Cleaning up shared member objects
class Shared {private int refcount = 0;private static long counter = 0;private final long id = counter++;Shared() {System.out.println("Creating " + this);}public void addRef() {refcount++;}protected void dispose() {if (--refcount == 0) {System.out.println("Disposing " + this);}}@Overridepublic String toString() {return "Shared " + id;}
}class Composing {private Shared shared;private static long counter = 0;private final long id = counter++;Composing(Shared shared) {System.out.println("Creating " + this);this.shared = shared;this.shared.addRef();}protected void dispose() {System.out.println("disposing " + this);shared.dispose();}@Overridepublic String toString() {return "Composing " + id;}
}public class ReferenceCounting {public static void main(String[] args) {Shared shared = new Shared();Composing[] composing = {new Composing(shared),new Composing(shared),new Composing(shared),new Composing(shared),new Composing(shared),};for (Composing c: composing) {c.dispose();}}
}

輸出:

在這里插入圖片描述

static long counter 跟蹤所創建的 Shared 實例數量,還提供了 id 的值。counter 的類型是 long 而不是 int,以防溢出(這只是個良好實踐,對于本書的所有示例,counter 不會溢出)。idfinal 的,因為它的值在初始化時確定后不應該變化。

在將一個 shared 對象附著在類上時,必須記住調用 addRef(),而 dispose() 方法會跟蹤引用數,以確定在何時真正地執行清理工作。使用這種技巧需要加倍細心,但是如果需要清理正在共享的對象,你沒有太多選擇。

構造器內部多態方法的行為

構造器調用的層次結構帶來了一個困境。如果在構造器中調用了正在構造的對象的動態綁定方法,會發生什么呢?

在普通的方法中,動態綁定的調用是在運行時解析的,因為對象不知道它屬于方法所在的類還是類的派生類。

如果在構造器中調用了動態綁定方法,就會用到那個方法的重寫定義。然而,調用的結果難以預料因為被重寫的方法在對象被完全構造出來之前已經被調用,這使得一些 bug 很隱蔽,難以發現。

從概念上講,構造器的工作就是創建對象(這并非是平常的工作)。在構造器內部,整個對象可能只是部分形成——只知道基類對象已經初始化。如果構造器只是構造對象過程中的一個步驟,且構造的對象所屬的類是從構造器所屬的類派生出的,那么派生部分在當前構造器被調用時還沒有初始化。然而,一個動態綁定的方法調用向外深入到繼承層次結構中,它可以調用派生類的方法。如果你在構造器中這么做,就可能調用一個方法,該方法操縱的成員可能還沒有初始化——這肯定會帶來災難。

下面例子展示了這個問題:

// polymorphism/PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect
class Glyph {void draw() {System.out.println("Glyph.draw()");}Glyph() {System.out.println("Glyph() before draw()");draw();System.out.println("Glyph() after draw()");}
}class RoundGlyph extends Glyph {private int radius = 1;RoundGlyph(int r) {radius = r;System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);}@Overridevoid draw() {System.out.println("RoundGlyph.draw(), radius = " + radius);}
}public class PolyConstructors {public static void main(String[] args) {new RoundGlyph(5);}
}

輸出:

Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5

Glyphdraw() 被設計為可重寫,在 RoundGlyph 這個方法被重寫。但是 Glyph 的構造器里調用了這個方法,結果調用了 RoundGlyphdraw() 方法,這看起來正是我們的目的。輸出結果表明,當 Glyph 構造器調用了 draw() 時,radius 的值不是默認初始值 1 而是 0。這可能會導致在屏幕上只畫了一個點或干脆什么都不畫,于是我們只能干瞪眼,試圖找到程序不工作的原因。

前一小節描述的初始化順序并不十分完整,而這正是解決謎團的關鍵所在。初始化的實際過程是:

  1. 在所有事發生前,分配給對象的存儲空間會被初始化為二進制 0。
  2. 如前所述調用基類構造器。此時調用重寫后的 draw() 方法(是的,在調用 RoundGraph 構造器之前調用),由步驟 1 可知,radius 的值為 0。
  3. 按聲明順序初始化成員。
  4. 最終調用派生類的構造器。

這么做有個優點:所有事物至少初始化為 0(或某些特殊數據類型與 0 等價的值),而不是僅僅留作垃圾。這包括了通過組合嵌入類中的對象引用,被賦予 null。如果忘記初始化該引用,就會在運行時出現異常。觀察輸出結果,就會發現所有事物都是 0。

另一方面,應該震驚于輸出結果。邏輯方面我們已經做得非常完美,然而行為仍不可思議的錯了,編譯器也沒有報錯(C++ 在這種情況下會產生更加合理的行為)。像這樣的 bug 很容易被忽略,需要花很長時間才能發現。

因此,編寫構造器有一條良好規范:做盡量少的事讓對象進入良好狀態。如果有可能的話,盡量不要調用類中的任何方法。在基類的構造器中能安全調用的只有基類的 final 方法(這也適用于可被看作是 finalprivate 方法)。這些方法不能被重寫,因此不會產生意想不到的結果。你可能無法永遠遵循這條規范,但應該朝著它努力。

協變返回類型

Java 5 中引入了協變返回類型,這表示派生類的被重寫方法可以返回基類方法返回類型的派生類型:

// polymorphism/CovariantReturn.java
class Grain {@Overridepublic String toString() {return "Grain";}
}class Wheat extends Grain {@Overridepublic String toString() {return "Wheat";}
}class Mill {Grain process() {return new Grain();}
}class WheatMill extends Mill {@OverrideWheat process() {return new Wheat();}
}public class CovariantReturn {public static void main(String[] args) {Mill m = new Mill();Grain g = m.process();System.out.println(g);m = new WheatMill();g = m.process();System.out.println(g);}
}

輸出:

Grain
Wheat

關鍵區別在于 Java 5 之前的版本強制要求被重寫的 process() 方法必須返回 Grain 而不是 Wheat,即使 Wheat 派生自 Grain,因而也應該是一種合法的返回類型。協變返回類型允許返回更具體的 Wheat 類型。

使用繼承設計

學習過多態之后,一切看似都可以被繼承,因為多態是如此巧妙的工具。這會給設計帶來負擔。事實上,如果利用已有類創建新類首先選擇繼承的話,事情會變得莫名的復雜。

更好的方法是首先選擇組合,特別是不知道該使用哪種方法時。組合不會強制設計是繼承層次結構,而且組合更加靈活,因為可以動態地選擇類型(因而選擇相應的行為),而繼承要求必須在編譯時知道確切類型。下面例子說明了這點:

// polymorphism/Transmogrify.java
// Dynamically changing the behavior of an object
// via composition (the "State" design pattern)
class Actor {public void act() {}
}class HappyActor extends Actor {@Overridepublic void act() {System.out.println("HappyActor");}
}class SadActor extends Actor {@Overridepublic void act() {System.out.println("SadActor");}
}class Stage {private Actor actor = new HappyActor();public void change() {actor = new SadActor();}public void performPlay() {actor.act();}
}public class Transmogrify {public static void main(String[] args) {Stage stage = new Stage();stage.performPlay();stage.change();stage.performPlay();}
}

輸出:

HappyActor
SadActor

Stage 對象中包含了 Actor 引用,該引用被初始化為指向一個 HappyActor 對象,這意味著 performPlay() 會產生一個特殊行為。但是既然引用可以在運行時與其他不同的對象綁定,那么它就可以被替換成對 SadActor 的引用,performPlay() 的行為隨之改變。這樣你就獲得了運行時的動態靈活性(這被稱為狀態模式)。與之相反,我們無法在運行時才決定繼承不同的對象;那在編譯時就完全決定好了。

有一條通用準則:使用繼承表達行為的差異,使用屬性表達狀態的變化。在上個例子中,兩者都用到了。通過繼承得到的兩個不同類在 act() 方法中表達了不同的行為,Stage 通過組合使自己的狀態發生變化。這里狀態的改變產生了行為的改變。

替代 vs 擴展

采用“純粹”的方式創建繼承層次結構看上去是最清晰的方法。即只有基類的方法才能在派生類中被重寫,就像下圖這樣:

在這里插入圖片描述

這被稱作純粹的“is - a"關系,因為類的接口已經確定了它是什么。繼承可以確保任何派生類都擁有基類的接口,絕對不會少。如果按圖上這么做,派生類將只擁有基類的接口。

純粹的替代意味著派生類可以完美地替代基類,當使用它們時,完全不需要知道這些子類的信息。也就是說,基類可以接收任意發送給派生類的消息,因為它們具有完全相同的接口。只需將派生類向上轉型,不要關注對象的具體類型。所有一切都可以通過多態處理。

在這里插入圖片描述

按這種方式思考,似乎只有純粹的“is - a”關系才是唯一明智的做法,其他任何設計只會導致混亂且注定失敗。這其實也是個陷阱。一旦按這種方式開始思考,就會轉而發現繼承擴展接口(遺憾的是,extends 關鍵字似乎慫恿我們這么做)才是解決特定問題的完美方案。這可以稱為“is - like - a” 關系,因為派生類就像是基類——它有著相同的基本接口,但還具有需要額外方法實現的其他特性:

在這里插入圖片描述

雖然這是一種有用且明智的方法(依賴具體情況),但是也存在缺點。派生類中接口的擴展部分在基類中不存在(不能通過基類訪問到這些擴展接口),因此一旦向上轉型,就不能通過基類調用這些新方法:

在這里插入圖片描述

如果不向上轉型,就不會遇到這個問題。但是通常情況下,我們需要重新查明對象的確切類型,從而能夠訪問該類型中的擴展方法。下一節說明如何做到這點。

向下轉型與運行時類型信息

由于向上轉型(在繼承層次中向上移動)會丟失具體的類型信息,那么為了重新獲取類型信息,就需要在繼承層次中向下移動,使用_向下轉型_。

向上轉型永遠是安全的,因為基類不會具有比派生類更多的接口。因此,每條發送給基類接口的消息都能被接收。但是對于向下轉型,你無法知道一個形狀是圓,它有可能是三角形、正方形或其他一些類型。

為了解決這個問題,必須得有某種方法確保向下轉型是正確的,防止意外轉型到一個錯誤類型,進而發送對象無法接收的消息。這么做是不安全的。

在某些語言中(如 C++),必須執行一個特殊的操作來獲得安全的向下轉型,但是在 Java 中,每次轉型都會被檢查!所以即使只是進行一次普通的加括號形式的類型轉換,在運行時這個轉換仍會被檢查,以確保它的確是希望的那種類型。如果不是,就會得到 ClassCastException (類轉型異常)。這種在運行時檢查類型的行為稱作運行時類型信息。下面例子展示了 RTTI 的行為:

// polymorphism/RTTI.java
// Downcasting & Runtime type information (RTTI)
// {ThrowsException}
class Useful {public void f() {}public void g() {}
}class MoreUseful extends Useful {@Overridepublic void f() {}@Overridepublic void g() {}public void u() {}public void v() {}public void w() {}
}public class RTTI {public static void main(String[] args) {Useful[] x = {new Useful(),new MoreUseful()};x[0].f();x[1].g();// Compile time: method not found in Useful://- x[1].u();((MoreUseful) x[1]).u(); // Downcast/RTTI((MoreUseful) x[0]).u(); // Exception thrown}
}

輸出:

Exception in thread "main"
java.lang.ClassCastException: Useful cannot be cast to
MoreUseful
at RTTI.main

正如前面類圖所示,MoreUseful 擴展了 Useful 的接口。而 MoreUseful 也繼承了 Useful,所以它可以向上轉型為 Useful。在 main() 方法中可以看到這種情況的發生。因為兩個對象都是 Useful 類型,所以對它們都可以調用 f()g() 方法。如果試圖調用 u() 方法(只存在于 MoreUseful 中),就會得到編譯時錯誤信息。

為了訪問 MoreUseful 對象的擴展接口,就得嘗試向下轉型。如果轉型為正確的類型,就轉型成功。否則,就會得到 ClassCastException 異常。你不必為這個異常編寫任何特殊代碼,因為它指出了程序員在程序的任何地方都可能犯的錯誤。{ThrowsException} 注釋標簽告知本書的構建系統:在運行程序時,預期拋出一個異常。

RTTI 不僅僅包括簡單的轉型。例如,它還提供了一種方法,使你可以在試圖向下轉型前檢查所要處理的類型。“類型信息”一章中會詳細闡述運行時類型信息的方方面面。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/36958.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/36958.shtml
英文地址,請注明出處:http://en.pswp.cn/news/36958.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【JavaScript】new 的原理以及實現

網道 - new 命令的原理 使用new命令時,它后面的函數依次執行下面的步驟。 創建一個空對象,作為將要返回的對象實例。將這個空對象的原型,指向構造函數的prototype屬性。將這個空對象賦值給函數內部的this關鍵字。如果構造函數返回了一個對象…

版本動態 | SolidUI 0.2.0 版本發布

SolidUI 一句話生成任何圖形 背景 隨著文本生成圖像的語言模型興起,SolidUI想幫人們快速構建可視化工具,可視化內容包括2D,3D,3D場景,從而快速構三維數據演示場景。SolidUI 是一個創新的項目,旨在將自然語言處理(NLP&…

[SpringCloud] 組件性能優化技巧

Feign 配置優化hystrix配置 優化ribbon 優化Servlet 容器 優化Zuul配置 優化 文章目錄 1.Servlet 容器 優化2.Feign 配置優化3.Zuul配置 優化4.hystrix配置 優化5.ribbon 優化 1.Servlet 容器 優化 默認情況下, Spring Boot 使用 Tomcat 來作為內嵌的 Servlet 容器, 可以將 We…

在Visual Studio上,使用OpenCV實現人臉識別

1. 環境與說明 本文介紹了如何在Visual Studio上,使用OpenCV來實現人臉識別的功能 環境說明 : 操作系統 : windows 10 64位Visual Studio版本 : Visual Studio Community 2022 (社區版)OpenCV版本 : OpenCV-4.8.0 (2023年7月最新版) 實現效果如圖所示&#xff0…

Linux命令200例:adduser用于創建新用戶

🏆作者簡介,黑夜開發者,全棧領域新星創作者?。CSDN專家博主,阿里云社區專家博主,2023年6月csdn上海賽道top4。 🏆數年電商行業從業經驗,歷任核心研發工程師,項目技術負責人。 &…

代理模式【Proxy Pattern】

什么是代理模式呢?我很忙,忙的沒空理你,那你要找我呢就先找我的代理人吧,那代理人總要知道 被代理人能做哪些事情不能做哪些事情吧,那就是兩個人具備同一個接口,代理人雖然不能干活,但是被 代…

解決 Mac 上使用 Electron Updater 更新 App 不成功的問題!!!

文章目錄 1. 現象2. 分析并如何解決3. 后續 1. 現象 在Mac電腦上,使用Electron Updater對程序進行更新,但是一直不成功,也不報錯。具體表現是這樣的:當前我的程序版本是3.11版本,點擊更新之后,也下載了&am…

11 迭代器|生成器|協程

文章目錄 迭代器可迭代對象可迭代對象的本質iter()函數與 next()函數迭代器 Iterator樣例 for...in...循環的本質使用的場景--斐波那契數列list和tuple也可以接收可迭代對象 生成器簡介創建生成器方法一方法二總結 使用 send 喚醒 協程協程和線程差異簡單實現協程greenletgeven…

微PE工具箱實現U盤重裝Windows系統

教程來源 U盤重裝Windows系統(微PE工具箱)_嗶哩嗶哩_bilibili 加上自己的一丟丟理解,如果你覺得長視頻看了犯困,不如看看我的理解文章說不定能夠幫助到你 準備工作 到這個網站使用迅雷下載免費無插件的官方鏡像MSDN, 我告訴你…

JVM筆記 —— 出現內存溢出錯誤時時如何排查

一、出現內存溢出的幾種情況 內存溢出錯誤分為StackOverflowError和OutOfMemoryError,前者是棧中出現溢出,后者一般是堆或方法區出現溢出,簡稱OOM 1. 棧溢出 StackOverflowError 棧溢出一般都是因為沒有正確的結束遞歸導致的,無…

Linux中安裝MySQL8版本,安裝MySQL步驟,MySQL8離線安裝

Linux中安裝MySQL8版本的步驟如下: 1.檢查下libaio.so.1的位置 [roottdx ]# whereis libaio.so.1 libaio.so: /usr/lib64/libaio.so.1 如果沒有找到該文件 (1).在線安裝 [roottdx ]# yum install -y libaio (2).離線安裝: 上傳之后執行命令安裝&#…

pymysql 庫 - python 操作 mysql

環境: Win10 x64 Python 3.7 PyMySQL 1.0.2 MySQL 8.0.27 1 安裝 pip install pymysql 2 地址 https://pypi.org/project/pymysql/ 3.1 數據庫版本查詢 (search_version.py) import pymysql# 打開數據庫連接 try:db pymysql.connect(hostlocalhost, userr…

python安裝第三方包時報錯:...\lib\site-packages\pip\_vendor\urllib3\response.py...

安裝redis第三方包: pip install redis報錯現象: 解決方法:使用以下命令可成功安裝 pip install redis -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

關于網絡入侵檢測領域使用Spark/Flink等計算框架做分布式

關于網絡入侵檢測領域使用Spark/Flink等計算框架做分布式 0、引言1 基于LightGBM的網絡入侵檢測研究2 基于互信息法的智能化運維系統入侵檢測Spark實現3 基于Spark的車聯網分布式組合深度學習入侵檢測方法4 基于Flink的分布式在線集成學習框架研究5 基于Flink的分布式并行邏輯回…

mongodb基礎

mongodb語法 參考文檔:https://docs.mongodb.com/manual/reference/ BSON Types BSON Type有2種標識符,整形和字符串 類型數值字符串說明Double1“double”String2“string”Object3“object”Array4“array”Binary data5“binData”Undefined6“un…

8.9黃金最新行情走勢分析及短線交易策略

近期有哪些消息面影響黃金走勢?黃金多空該如何研判? ?黃金消息面解析:周三(8月9日)現貨黃金維持震蕩,目前交投于1930美元附近,隔日現貨黃金盤中震蕩下行,失守1930關口并在美盤時段…

【Spring】-Spring的IoC和DI

作者:學Java的冬瓜 博客主頁:?冬瓜的主頁🌙 專欄:【Framework】 主要內容:什么是spring?IoC容器是什么?如何使代碼解耦合?IoC的核心原理,IoC的優點。依賴注入/對象裝配/…

【ARM 嵌入式 編譯系列 10 -- GCC 編譯縮減可執行文件 elf 文件大小】

文章目錄 GCC 如何縮減可執行文件size測試代碼 上篇文章:ARM 嵌入式 編譯系列 9-- GCC 編譯符號表(Symbol Table)的詳細介紹 下篇文章:ARM 嵌入式 編譯系列 10.1 – GCC 編譯縮減可執行文件 elf 文件大小 GCC 如何縮減可執行文件s…

Linux下在qtcreator中創建qt程序

目錄 1、新建項目 2、單工程項目創建 3、多工程項目創建 4、添加子工程(基于多工程目錄結構) 5、 .pro文件 1、新建項目 切換到“編輯”界面,點擊菜單欄中的“文件”-“新建文件或項目” 2、單工程項目創建 只有一個工程的項目&#…

Axure RP移動端高保真CRM辦公客戶管理系統原型模板及元件庫

Axure RP移動端高保真CRM辦公客戶管理系統原型模板及元件庫,一套典型的移動端辦公工具型APP Axure RP原型模板,可根據實際的產品需求進行擴展,也可以作為移動端原型設計的參考案例。為提升本作品參考價值,在模板設計過程中盡量追求…