第3章:抽象數據類型(ADT)和麪向對象編程(OOP) 3.1數據類型和類型檢查

大綱

1.編程語言中的數據類型
2.靜態與動態數據類型
3.類型檢查
4.易變性和不變性
5.快照圖
6.複雜的數據類型:數組和集合
7.有用的不可變類型
8.空引用
9.總結java

編程語言中的數據類型

類型和變量程序員

類型是一組值,以及能夠對這些值執行的操做。
變量:存儲一個特定類型值的命名位置編程

Java中的類型數組

Java有幾種基本類型,例如:安全

  • int(對於像5和-200這樣的整數,但限於±2 ^ 31或大約±20億的範圍)
  • 長(對於大於±2 ^ 63的整數)
  • 布爾值(對於true或false)
  • 雙精度浮點數(表明實數的一個子集)
  • char(單個字符,如'A'和'$')

Java也有對象類型,例如:數據結構

  • 字符串表示一系列字符。
  • BigInteger表示一個任意大小的整數。

按照Java約定,基本類型是小寫字母,而對象類型以大寫字母開頭。app

對象類型的層次結構編程語言

根是Object(全部非基元都是對象)ide

  • 除Object之外的全部類都有一個父類,使用extends子句指定

class Guitar extends Instrument { ... }
若是省略了子句,則默認爲Object
一個類是其全部超類的一個實例函數

  • 從其超類繼承可見的字段和方法
  • 能夠覆蓋(Override)方法來改變他們的行爲

包裝的基本類型

用於原始類型的不變容器
– Boolean, Integer, Short, Long, Character, Float, Double
典型用例是集合
除非你必須使用包裝的基本類型!
語言自動包裝和自動解包裝

運算符

運算符:執行簡單計算的符號

  • 賦值:=
  • 加法:+
  • 減法: -
  • 乘法:*
  • 除法:/

操做順序:遵循標準的數學規則

  • 1.括號
  • 2.乘法和除法
  • 3.加法和減法

字符串鏈接(+)

操做

操做是獲取輸入和生成輸出的函數(有時會自行更改值)。

  • 做爲中綴,前綴或後綴運算符。 例如,a + b調用操做+:int×int→int。
  • 做爲一個對象的方法。 例如,bigint1.add(bigint2)調用操做add:BigInteger×BigInteger→BigInteger。
  • 做爲一項功能。 例如,Math.sin(theta)調用sin:double→double操做。 在這裏,數學不是一個對象。 這是包含sin函數的類。

重載操做符/操做

一些操做被重載,由於相同的操做名稱用於不一樣的類型
對於Java中的數字基本數據類型,算術運算符+, - ,*,/會嚴重重載。
方法也能夠重載。 大多數編程語言都有必定程度的重載。
(將在3.3節OOP中討論)

靜態輸入與動態輸入

Java是一種靜態類型的語言。

  • 全部變量的類型在編譯時已知(在程序運行以前),所以編譯器也能夠推導出全部表達式的類型。
  • 若是將a和b聲明爲int,則編譯器得出結論a + b也是int。
  • 在編寫代碼時,Eclipse環境會執行此操做,事實上,您仍然能夠在輸入時瞭解許多錯誤。
  • 在編譯階段進行類型檢查

在像Python這樣的動態類型語言中,這種檢查被推遲到運行時(程序運行時)。

3.類型檢查

靜態檢查和動態檢查

一種語言能夠提供的三種自動檢查:

  • 靜態檢查:在程序運行以前會自動發現錯誤。
  • 動態檢查:執行代碼時會自動發現錯誤。
  • 不檢查:語言根本無助於您找到錯誤。 你必須親自觀察,不然最終會獲得錯誤的答案。

不用說,靜態捕獲一個bug比動態捕獲它要好,動態捕獲它比根本沒有捕獲它要好。

靜態檢查

靜態檢查意味着在編譯時檢查錯誤。
錯誤是編程的禍根。
靜態類型能夠防止大量的錯誤感染程序:準確地說,經過對錯誤類型的參數應用操做而致使的錯誤。
若是你寫了一行代碼,如:「5」*「6」,它試圖乘以兩個字符串,那麼靜態類型在編程時會捕獲這個錯誤,而不是等到執行過程當中到達該行。

  • 語法錯誤,如額外的標點符號或假詞。 即便是像Python這樣的動態類型語言也能夠進行這種靜態檢查。
  • 錯誤的名字,如Math.sine(2)。 (正確的名字是sin)
  • 錯誤的參數數量,如Math.sin(30,20)。
  • 錯誤的參數類型,如Math.sin(「30」)。
  • 錯誤的返回類型,如返回「30」; 從聲明的函數返回一個int。

動態檢查

非法的參數值。 例如,當y實際上爲零時,整數表達式x / y只是錯誤的; 不然它將運行。 因此在這個表達式中,除零不是一個靜態錯誤,而是一個動態錯誤。

  • 不具備表明性的返回值,即特定返回值沒法在類型中表示時。
  • 超出範圍的索引,例如,在字符串上使用負值或太大的索引。
  • 在空對象引用上調用方法。

靜態與動態檢查

靜態檢查每每是關於類型的,與變量具備的特定值無關的錯誤。

  • 靜態類型保證了一個變量會從該集合中得到一些值,可是咱們直到運行時才知道它具備哪一個值。
  • 因此若是錯誤只會被某些值引發,好比被零除或索引超出範圍,那麼編譯器不會引起關於它的靜態錯誤。

相比之下,動態檢查每每是由特定值引發的錯誤。

原始數據類型不是真正的數字

Java中的一個陷阱 - 以及其餘許多編程語言 - 就是它的原始數字類型具備不像咱們習慣的整數和實數那樣的特例。
結果,真正應該動態檢查的一些錯誤根本不被檢查。

  • 整數除法:5/2不返回分數,它返回一個截斷的整數。
  • 整數溢出。 若是計算結果過於積極或過於消極而沒法適應該有限範圍,則會悄然溢出並返回錯誤答案。 (沒有靜態/動態檢查!)例如,int big = 200000 * 200000;
  • 浮點類型中的特殊值。 NaN(「不是數字」),POSITIVE_INFINITY和NEGATIVE_INFINITY。 例如,double a = 7/0;

4可變性和不變性

賦值

使用「=」給變量賦值
賦值能夠和變量聲明結合使用

更改變量或其值

改變變量和改變數值有什麼區別?

  • 當你分配給變量時,你正在改變變量的箭頭指向的地方。 您能夠將其指向不一樣的值。
  • 當分配給可變值的內容時(例如數組或列表),您將在該值內改變引用。

變化是必要的罪惡。
好的程序員能夠避免變化的事情,由於它們可能會意外地改變。

不變性

不變性是一個主要的設計原則。
不可變類型是一種類型,它們的值一旦建立就永遠不會改變。
Java也爲咱們提供了不可變的引用:一次賦值且永不從新賦值的變量。

  • 爲了使引用不可變,用關鍵字final聲明它。

若是Java編譯器不肯定最終變量只會在運行時分配一次,那麼它將產生編譯器錯誤。 因此最終給你靜態檢查不可變引用。

最好使用final來聲明方法的參數和儘量多的局部變量。
像變量的類型同樣,這些聲明是重要的文檔,對代碼讀者頗有用,並由編譯器進行靜態檢查。
注意:

  • fianl的類聲明意味着它不能被繼承。
  • final變量意味着它始終包含相同的值/參考,但不能最終更改
  • final方法意味着它不能被子類覆蓋

可變性和不變性

對象是不可變的:一旦建立,它們老是表示相同的值。
對象是可變的:它們具備改變對象值的方法。

字符串做爲不可變類型

字符串是不可變類型的一個例子。
一個String對象老是表示相同的字符串。
因爲String是不可變的,一旦建立,String對象始終具備相同的值。
要將某些內容添加到字符串的末尾,您必須建立一個新的String對象:

StringBuilder做爲一個可變類型

StringBuilder是一個可變類型的例子。
它具備刪除字符串部分,插入或替換字符等的方法
該類具備更改對象值的方法,而不只僅是返回新值:

可變類型的優勢

  • 使用不可變的字符串,這會產生大量的臨時副本
  • 得到良好的性能是咱們使用可變對象的一個緣由。
  • 另外一個是方便共享:經過共享一個常見的可變數據結構,您的程序的兩個部分能夠更方便地進行通訊。
    「全局變量」
    可是你必須知道全局變量的缺點......

5快照圖做爲Code-level,Run-time,Moment視圖

快照圖

爲了理解微妙的問題,咱們能夠繪製運行時發生的事情的圖片。
快照圖在運行時表示程序的內部狀態 - 其堆棧(正在進行的方法及其局部變量)及其堆(當前存在的對象)。
爲何咱們使用快照圖表?

  • 經過圖片相互交流。
  • 爲了說明基本類型與對象類型,不可變值與不可變引用,指針別名,堆棧與堆,抽象與具體表示等概念。
  • 幫助解釋您的團隊項目設計(彼此之間以及與您的技術援助相關)。
  • 爲後續課程中豐富的設計符號鋪平道路。

可變值與從新分配變量

快照圖給咱們提供了一種可視化更改變量和更改值之間區別的方法:

  • 當您分配給變量或字段時,您將更改變量的箭頭指向的位置。 您能夠將其指向不一樣的值。
  • 當分配給可變值的內容時(例如數組或列表),您將在該值內改變引用。

快照圖中的基本類型和對象類型

基本類型的值

  • 基本類型的值由裸常量表示。 傳入的箭頭是對來自變量或對象字段的值的引用。

對象類型的值

  • 對象類型的值是由其類型標記的圓。
  • 當咱們想要顯示更多細節時,咱們在其中寫入字段名稱,箭頭指向它們的值。 有關更詳細的信息,這些字段能夠包含它們的聲明類型。

從新分配和不可變的值

例如,若是咱們有一個字符串變量s,咱們能夠將其從「a」值從新分配給「ab」
String s =「a」;
s = s +「b」;
字符串是不可變類型的一個例子,一種類型的值一旦建立就永遠不會改變。
不變對象(它們的設計者打算始終表示相同的值)在快照圖中用雙邊框表示,就像咱們圖中的String對象同樣。

可變的值

相比之下,StringBuilder(一個內置的Java類)是一個可變對象,表示一串字符,而且它具備更改對象值的方法:
StringBuilder sb = new StringBuilder(「a」);
sb.append( 「B」);
這兩個快照圖看起來很是不一樣,這是很好的:可變性和不可變性之間的差別將在使代碼免受bug影響方面發揮重要做用。

不變的引用

Java也爲咱們提供了不可變的引用:一次賦值且永不從新賦值的變量。 爲了使引用不可變,用關鍵字final聲明它:
final int n = 5;
若是Java編譯器不肯定最終變量只會在運行時分配一次,那麼它將產生編譯器錯誤。 因此最終給出了對不可變引用的靜態檢查。
在快照圖中,不可變引用(final)由雙箭頭表示。

6複雜的數據類型:數組和集合

數組

數組是另外一個類型T的固定長度序列。例如,下面是如何聲明一個數組變量並構造一個數組值以分配給它:
int [] a = new int [100];
int []數組類型包含全部可能的數組值,可是一旦建立了特定的數組值,永遠不會改變其長度。
數組類型的操做包括:

  • 索引:a [2]
  • 賦值:a [2] = 0
  • 長度:a.length

列表

咱們使用List類型來代替固定長度的數組。
列表是另外一個類型T的可變長度序列。 List <Integer> list = new ArrayList <Integer>();
其部分操做:

  • 索引:list.get(2)
  • 賦值:list.set(2,0)
  • 長度:list.size()

注1:列表是一個接口
注2:列表中的成員必須是一個對象。

集合

Set是零個或多個惟一對象的無序集合。
一個對象不能屢次出如今一個集合中。 要麼它在或它不在。

  • s1.contains(e)測試集合是否包含元素
  • s1.containsAll(s2)測試是否s1⊇s2
  • s1.removeAll(s2)從s1移除s2

Set是一個抽象接口

地圖

地圖相似於字典(key)

  • map.put(key,val)添加映射key→val
  • map.get(key)獲取一個key的值
  • map.containsKey(key)測試地圖是否有key
  • map.remove(key)刪除映射

地圖是一個抽象界面

聲明List,Set和Map變量

使用Java集合,咱們能夠限制集合中包含的對象的類型。
當咱們添加一個項目時,編譯器能夠執行靜態檢查,以確保咱們只添加適當類型的項目。
而後,當咱們取出一個對象時,咱們保證它的類型將是咱們所指望的。

建立List,Set和Map變量

Java有助於區分

  • 一種類型的規範 - 它有什麼做用? 抽象接口 - 實現
  • 代碼是什麼? 具體類

List,Set和Map都是接口:

  • 他們定義了這些相應的類型是如何工做的,但他們不提供實現代碼。
  • 優勢:用戶有權在不一樣狀況下選擇不一樣的實施方式。

List,Set和Map的實現:

  • 列表:ArrayList和LinkedList
  • 集合:HashSet
  • 地圖:HashMap

迭代器是一個可變類型的迭代器

迭代器是一個對象,它遍歷一組元素並逐個返回元素。
當你使用for(...:...)循環遍歷一個List或數組時,迭代器在Java中被使用。
迭代器有兩種方法:

  • next()返回集合中的下一個元素---這是一個可變的方法!
  • hasNext()測試迭代器是否已到達集合的末尾。

7有用的不可變類型

原始類型和原始包裝都是不可變的。

  • 若是你須要用大數來計算,BigInteger和BigDecimal是不可變的。

不要使用可變日期,根據您須要的計時粒度,使用java.time或java.time.ZonedDateTime中適當的不可變類型。
Java的集合類型(List,Set,Map)的一般實現都是可變的:ArrayList,HashMap等
Collections實用程序類具備獲取這些可變集合的不可修改視圖的方法:

  • Collections.unmodifiableList
  • Collections.unmodifiableSet
  • Collections.unmodifiableMap

可變數據類型的不可變包裝
Java集合類提供了一個有趣的折衷:不可變的包裝器。

  • Collections.unmodifiableList()須要一個(可變的)List,而且用一個看起來像List的對象封裝它,可是其禁用的mutator - set(),add(),remove()等會拋出異常。 因此你能夠構造一個使用mutator的列表,而後用一個不可修改的包裝器來封裝它(而且拋棄你對原始可變列表的引用,而且獲得一個不變的列表。

缺點是你在運行時得到了不變性,但不是在編譯時。

  • 若是你嘗試排序()這個不可修改的列表,Java在編譯時不會發出警告。
  • 你會在運行時獲得一個異常。
  • 但這仍是比沒有好,因此使用不可修改的列表,地圖和集合能夠是下降錯誤風險的一種很是好的方法。

不可修改的包裝

不可修改的包裝器經過攔截全部修改集合並拋出UnsupportedOperationException的操做來取消修改集合的能力。
不可修改的包裝有兩個主要用途,以下所示:

  • 一旦創建一個集合就不可變。 在這種狀況下,最好不要保留對後備集合的引用。 這絕對保證了不變性。
  • 容許某些客戶端只讀訪問您的數據結構。 您保留對後備集合的引用,但請分發引用。 這樣,客戶能夠看,但不能修改,而你保持徹底訪問。

8空引用

空引用
在Java中,對對象和數組的引用也能夠採用特殊值Null,這意味着引用不指向對象。 空值是Java類型系統中的一個不幸的漏洞。
基元不能爲null,編譯器會拒絕這種帶有靜態錯誤的嘗試:
int size = null; //非法
能夠將null分配給任何非原始變量,而且編譯器在編譯時高興地接受這些代碼。 可是你會在運行時遇到錯誤,由於你不能調用任何方法或者使用帶有這些引用之一的任何字段(拋出NullPointerExceptions):
String name = null;
name.length();
int [] points = null;
points.length;
null與空字符串「」或空數組不一樣。

空引用

非基元和像List這樣的集合的數組多是非空的,但包含null做爲值
只要有人試圖使用集合的內容,這些空值就可能致使錯誤。
空值是麻煩和不安全的,因此你最好建議將它們從你的設計詞彙表中刪除。
空值在參數和返回值中被隱式禁止。

來自Guava(Google)

不當心使用null會致使各類各樣的錯誤。
研究谷歌代碼庫,咱們發現像95%的集合不該該有任何空值,而且讓這些快速失敗而不是默默接受空值會對開發人員有所幫助。
另外,null很不明確。
不多有人明白空返回值應該是什麼意思 - 例如,Map.get(key)能夠返回null,由於map中的值爲null,或者該值不在map中。 空可能意味着失敗,可能意味着成功,幾乎意味着任何事情。
使用除null以外的其餘內容能夠明確您的意思。

總結

靜態類型檢查:

  • 減小錯誤保證安全。 靜態檢查經過在運行時捕獲類型錯誤和其餘錯誤來幫助安全。
  • 容易明白。 它有助於理解,由於類型在代碼中已明確說明。
  • 準備改變。 靜態檢查經過識別須要一塊兒更改的其餘位置來更容易地更改代碼。 例如,當您更改變量的名稱或類型時,編譯器會當即在使用該變量的全部位置顯示錯誤,並提醒您更新它們。

可變性對於性能和便利性頗有用,但它也會經過要求使用對象的代碼在全範圍內表現良好而形成bug的風險,這極大地增長了咱們必須作的推理和測試,以確保其正確性。
確保你瞭解不可變對象(如String)和不可變引用(如final變量)之間的區別。
快照圖能夠幫助理解。

  • 對象是由快照圖中的圓圈表示的值,而不可變的對象是具備雙邊框的值,表示它永遠不會更改其值。
  • 引用是一個指向對象的指針,用快照圖中的箭頭表示,不可變引用是帶有雙線的箭頭,表示不能將箭頭移動到指向不一樣的對象。

關鍵的設計原則是不變性:儘量使用不可變對象和不可變引用。

  • 減小錯誤保證安全。 不可變對象不容易出現鋸齒引發的錯誤。 不可變引用老是指向同一個對象。
  • 容易明白。 由於不可變的對象或引用老是意味着相同的事物,因此代碼讀者推理起來更簡單 - 他們沒必要追蹤全部代碼以找到可能更改對象或引用的全部位置,由於 它不能改變。
  • 準備好改變。 若是在運行時不能更改對象或引用,那麼當程序更改時,不須要修改依賴於該對象或引用的代碼。
相關文章
相關標籤/搜索