Java面向對象之final、abstract抽象、和變量生命周期

2020-12-17 老夫編程說
Java面向對象之final、abstract抽象、

final修飾符

final是最終、不可修改的意思, 在Java中它可以修飾非抽象類,非抽象方法和變量。但是需要注意的是:構造方法不能使用final修飾,因為構造方法不能夠被繼承。下面,咱們就來一一看看吧!

使用final關鍵字修飾類

先考慮下圖的代碼例子:

final class

代碼顯示錯誤,無法從SuperClass繼承,編譯器提示刪除final關鍵字;刪除final關鍵字後,代碼正確無誤。

代碼正確無誤

由此可得出:final修飾的類:,表示最終的類,,即該類不能再有子類,不能再被繼承。只要滿足以下條件就可以考慮把一個類設計成final類:

在設計之初就考慮不進入繼承體系的類。出於安全考慮,類的實現細節不允許被拓展和修改。比如:基本數據類型的包裝類就是一個典型的例子。該類不會再被拓展。java裡final修飾的類有很多,比如八大基本數據類型的包裝類(Byte,Character、Short、Integer、Long、Float、Double、Boolean)和String等。

java裡部分final修飾的類

使用final關鍵字修飾方法

如果用final關鍵字修飾方法呢?先考慮以下的代碼:

final 方法

若是用final修飾方法,繼承該方法時會報編譯錯誤;刪除該關鍵字後,doWork()可被繼承,代碼編譯通過;final修飾的方法為最終的方法,該方法不能被子類覆蓋,故也不能使用方法重寫。那麼什麼樣的情況下方法需要使用final修飾呢?

在父類中提供了統一的算法骨架,不允許子類通過方法覆蓋來修改其實現細節, 此時使用final修飾。比如在模板方法設計模式中。在構造器中調用的方法(初始化方法),此時一般使用final修飾。這也是構造器不能被繼承的原因。注意: final修飾的方法了,子類可以調用,但是不能覆蓋(重寫)。

類常量:使用final關鍵字修飾的欄位

常量分類:

字面值常量(直接給出的數據值/直接量);比如:整數常量1,2,3,小數常量3.14,布爾常量false,true等。final關鍵字修飾的常量。

final關鍵字修飾的常量

通過上述代碼,不難看出,final關鍵字修飾的欄位無法被修改。通常開發中,我們建議final修飾的常量名用大寫字母表示,多個單詞之間使用下劃線(_)連接:如:

public static final String USER_NAME = "用戶名";且在Java中多個修飾符之間是沒有先後關係的,以下的三種修飾符排列順序都是ok的:

public static final 或者 public final static 亦或者 final static publicfinal修飾的變量是最終的變量,常量;該變量只能賦值一次,也只能在聲明時被初始化一次,不能被修改。在使用時需注意:

final變量必須顯式地指定初始值,系統不會為final欄位初始化。final變量一旦賦予初始值,就不能再被重新賦值。常量名規範:常量名符合標識符,單詞全部使用大寫字母,如果是多個單詞組成,多個單詞之間使用下劃線(_)連接。全局靜態常量: public static final 修飾的變量,直接使用類名調用即可。

final修飾的引用類型變量到底表示引用的地址不能改變,還是其存儲的數據不能改變

修飾基本類型變量:表示該變量的值不能改變,即不能用「=」號重新賦值。修飾引用類型變量:表示該變量的引用的地址不能變,而不是其存儲的數據內容不能變,其存儲的數據內容是可以被修改的。

什麼時候使用常量

當在程序中,多個地方使用到共同的數據,而且該數據不會改變,此時可以將其定義全局的常量;一般的,在開發中我們會專門定義一個常量類,專門用來存儲常量數據。

為何要使用final修飾符呢?在繼承關係中最大弊端就是會破壞封裝,子類能訪問父類的實現細節,,而且可以通過方法重寫(方法覆蓋)的方式修改方法的實現細節。且 final還是是唯一可以修飾局部變量的修飾符。

抽象方法和抽象類

考慮一下的代碼:

案例:求圓(Circle)、矩形(rectangle)的面積

求圓(Circle)、矩形(rectangle)的面積

上述代碼設計是存在問題的:

每一個圖形都有面積。但是不同圖形求面積的算法是不一樣的,也就是說,每一個圖形的子類都必須去重寫getArea方法,如果不覆蓋,應該編譯報錯,無法計算其面積。在圖形類(Graph)中定義了getArea方法,該方法不應該存在方法體,因為不同圖形子類求面積算法不一樣,父類是不存在計算面積的方法的,故無法提供方法體。

案例:求圓(Circle)、矩形(rectangle)

抽象方法

使用abstract關鍵字修飾且沒有方法體的方法,稱為抽象方法。其特點是:

使用抽象abstract關鍵字修飾,方法沒有方法體,留給子類去實現/覆蓋其實現細節。抽象方法修飾符不能是private 和 final以及static,因為抽象方法是要被重寫的;抽象方法必須定義在抽象類或接口中。接口中的方法魔人都是使用public abstract 修飾的;一般會把abstract寫在方法修飾符最前面,一看就知道是抽象方法;當然如果不這樣寫也沒錯。

抽象類

使用abstract關鍵字修飾的類,稱為抽象類。其特點是:

抽象類不能創建實例,也就是不能使用new創建一個抽象類對象,即使創建出抽象類對象,調用了抽象方法,也無法實現功能,因為抽象方法沒有方法體。抽象類可以不包含抽象方法,倘若包含,哪怕是一個,該類也必須作為抽象類,抽象類可以包含普通方法,可以給子類調用;抽象類是有構造器的,且其子類構造器必須先調用父類構造器。若子類沒有實現/覆蓋父類所有的抽象方法,那么子類也得作為抽象類(抽象派生類)。構造方法不能都定義成私有的,否則不能有子類,因為子類構造器無法調用其構造器(創建子類對象前先調用父類構造方法)。抽象類不能使用final修飾,因為其必須有子類重寫其抽象方法,抽象方法才能得以實現。抽象類是不完整的類,需作為父類,由子類實現其功能細節,功能才能得以實現。抽象類在命名時,一般使用Abstract作為前綴,讓調用者見名知義,看類名就知道其是抽象類。抽象類中可以不存在抽象方法,這樣做雖然沒有太大的意義,但是可以防止外界創建其對象,所以我們會發現有些工具類沒有抽象方法,但卻是使用abstract來修飾類的。普通類有的成員(方法、欄位、構造器),抽象類本質上也是一個類,故其都有。抽象類不能創建對象,但抽象類中是可以包含普通方法的。

變量生命周期

程序中的變量是用來存儲數據的,其又分為常量和變量兩種,關於變量的詳情可以查看我的另一篇文章:Java 變量、表達式和數據類型詳解。定義變量的語法:

數據類型 變量名 = 值;

變量根據在類中定義位置的不同,分成兩大類

成員變量: 全局變量/欄位(Field),是定義在類中,方法作用域外的變量;可以先使用後定義(使用在前,定義在後)。

類成員變量:使用static修飾的欄位。實例成員變量:也稱為對象變量,即沒有使用static修飾的欄位。局部變量: 變量除了成員變量,其他都是局部變量,主要體現在方法內,方法參數,代碼塊內;局部變量必須先定義而後才能使用。

方法內部的變量。方法的形參。代碼塊中的變量,一對{}中的變量。

變量的初始值:變量只有在初始化後才會在內存中開闢空間。

成員變量: 默認是有初始值的。

成員變量的初始值

局部變量: 沒有初始值。所以必須先初始化才能使用,而且其初始化是在方法執行開始時才進行的。

變量的作用域:變量根據定義的位置不同,也決定了各自的作用域是不同的,最直觀的就是看變量所在的那對花括號{},也就是離得最近的那對{}。成員變量的作用域在整個類中都有效。局部變量的作用域在開始定義的位置開始,到緊跟著結束的花括號為止。

變量的生命周期

變量的作用域指的是變量的可使用的範圍,只有在這個範圍內,程序代碼才能訪問它。當一個變量被定義時,它的作用域就確定了。變量的作用域決定了變量的生命周期,作用域不同,生命周期就不一樣。

變量的生命周期指的是一個變量被創建並分配內存空間開始,到該變量被銷毀並清除其所佔內存空間的過程。

package 關鍵字

在開發中,一個項目會有成百上千個Java文件,如果所有的Java文件都在一個目錄中,那麼管理起來就會很痛苦,很難想像這樣的項目會是什麼樣子。在Java中,引入了稱之為包(package)的概念。即:關鍵字:package ,專門用來給當前Java文件設置包名(也就是命名空間)。其語法格式如下:

package 包名.子包名.子包名;必須把package語句作為Java文件中的第一行代碼,在所有代碼之前。

package 語句和java編譯

在編譯java文件時的編譯命令為:

javac -d . Hello.java如果此時Hello.java文件中沒有使用package語句,表示在當前目錄中生成字節碼文件。運行時也不需要考慮包名。

如果此時Hello.java文件中使用了package語句,此時表示在當前目錄中先生成包名目錄,再在包名目錄中生成字節碼文件。運行命令如下:

java 包名.類名;

package命名

1.包名的定義:自定義的包名不能以java開頭,會和java語言基礎類庫衝突。

a.包名必須遵循標識符規範/全部小寫。

b.企業開發中,包名由公司域名倒寫來決定。

c.如果域名是以數字開頭的,不符合規範,可以考慮使用下劃線_開頭;但是在Android中,如果package中使用了_,則不能部署到模擬器上。此時,我們也可以使用一個字母來代替_。

package命名格式

package 域名倒寫.模塊名.組件名;

2.package下的類名:

類的簡單名稱: PackageDemo.java類的全限定名稱: com._520.hello.PackageDemo.java3.建議:先定義package名稱,再在定義的package內定義類。

import 關鍵字

當A類和B類不在同一個包中,若A類需要使用到B類中的功能,此時就得讓A類中去引入B類。使用import語句,把某個包下的類導入到當前類中。

語法格式: import 需要導入類的全限定名;引入後在當前類中,只需要使用類的簡單名稱即可訪問。

如果我們需要引入包中的多個類,我們還得使用多個import語句,要寫很多次;此時可以使用通配符(*)

import 類的全限定名; 只能導入某一個類。import 包名.子包名.*; 表示會引入該包下的所有的在當前文件中使用到的類。import java.util.*; 表示導入java.util包下的所有類。注意:編譯器會默認導入java.lang包下的類,但是並不會導入java.lang的子包下的類。比如:java.lang.reflect.Method類,此時我們也得使用import java.lang.reflect.Method;來導入Method類。

靜態import

靜態import,靜態導入,是指將通過import static導入其他類的靜態成員。以下代碼實例:

靜態import 代碼示例

然後我們對StaticImportDemo反編譯,觀察JVM是如何處理靜態導入的:

靜態import 代碼反編譯

通過上述的反編譯代碼,不難發現,其實所謂的靜態導入也是一個語法糖/編譯器級別的新特性,其實在底層也是類名.靜態成員去訪問的。所以在企業項目開始中不建議使用靜態導入,容易引起欄位名,方法名混淆,不利於項目維護。

欄位不存在多態

通過對象調用欄位,在編譯時期就已經決定了調用哪一塊內存空間的數據。所以欄位不存在覆蓋的概念,也就是欄位不會有多態特徵,在運行時期體現的也會是子類特徵。

欄位不存在多態

運行結果為:SubClass.name

通過運行上述代碼,不難發現,當子類和父類存在相同的欄位的時候,無論修飾符是什麼(即使是private),都會在各自的內存空間中存儲數據,欄位並沒有體現出多態;其實通過方法重寫字面意思也能發現其是針對方法的。所以只有方法才有覆蓋的概念,而欄位並不會被覆蓋。

代碼塊

什麼是代碼塊:在類或者在方法中,直接使用"{}"括起來的一段代碼,表示一塊代碼區域,我們將其稱為代碼塊。代碼塊裡變量屬於局部變量,只在自己所在的作用域(所在的{})內有效。根據代碼塊定義的位置的不同,我們又分成三種形式:

1.局部代碼塊:直接定義在方法內部的代碼塊;一般不會直接使用局部代碼塊,而是會結合if,while,for,try等關鍵字配合使用,還有匿名內部類,表示一塊代碼區域。示例如下:

if (true) { ...... }2.初始化代碼塊(構造代碼塊):定義在類中,每次創建對象的時候都會執行,並且是在構造器調用之前先執行本類中的初始化代碼塊。但其實JVM在處理初始化代碼塊時是將其移動到構造器中的最前面,從而達到先執行初始化代碼塊,再執行構造器的功能。

在實際開發中,很少使用初始化代碼塊;初始化操作會在構造器中進行,如果做初始化操作的代碼比較複雜,可以另外定義一個方法做初始化操作,然後再在構造器中調用。

3.靜態代碼塊:使用static修飾的初始化代碼塊。格式如下:

class StaticDemo {

static { ...... }

}

靜態代碼塊會在主方法(main方法)執行之前執行,而且只執行一次。在Java中,main方法是程序的入口,靜態代碼塊優先於main方法執行;是因為靜態成員是隨著字節碼的加載而進入JVM中的,但此時此時main方法還沒執行,因為main方法需要JVM調用方能執行。

以下是一個代碼塊的示例:

代碼塊示例

其運行結果為:

執行靜態代碼塊執行初始化代碼塊執行無參構造器執行初始化代碼塊執行無參構造器執行初始化代碼塊執行無參構造器

不難發現,調用順序依次為:靜態代碼塊--》初始化代碼塊--》構造器,且靜態代碼塊只執行一次。然後再對上述示例代碼做反編譯:

代碼塊反編譯

通過反編譯結果,發現JVM在處理初始化代碼塊時是將初始化代碼塊的代碼移動到構造器中的最前面,從而達到先執行初始化代碼塊,再執行構造器的功能。

完結。

相關焦點

  • 論Java中的抽象類與接口
    抽象方法和抽象類都必須被abstract關鍵字修飾。——瘋狂的Java講義abstract不能用於修飾Field,不能用於修飾局部變量,即沒有抽象變量、沒有抽象Field等說法;abstract也不能用於修飾構造器,沒有抽象構造器,抽象類裡定義的構造器只能是普通構造器。
  • Java之final關鍵字的簡單介紹上
    其次,小編簡單介紹一下final關鍵字的用法:1.可以用來修飾一個類2可以用來修飾一個方法3.可以用來修飾一個成員變量4.可以用來修飾一個局部變量這次小編要介紹的是,final關鍵字用來修飾一個類和方法。
  • Java基礎面試題簡單總結
    不能創建abstract 類的實例。然而可以創建一個變量,其類型是一個抽象類,並讓它指向具體子類的一個實例。不能有抽象構造函數或抽象靜態方法。Abstract 類的子類為它們父類中的所有抽象方法提供實現,否則它們也是抽象類為。取而代之,在子類中實現該方法。
  • 「原創」Java並發編程系列06|你不知道的final
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫final在Java中是一個保留的關鍵字,可以修飾變量、方法和類。那麼fianl在並發編程中有什麼作用呢?本文就在對final常見應用總結基礎上,講解final並發編程中的應用。
  • Java之final關鍵字詳解
    前言針對Java語言中的final關鍵字,想必都不陌生了。本來主要是來對final做關鍵字做一個總結。final關鍵字用法修飾類當用final去修飾一個類的時候,表示這個類不能被繼承。注意:a.被final修飾的類,final類中的成員變量可以根據自己的實際需要設計為fianl。b. final類中的成員方法都會被隱式的指定為final方法。說明:在自己設計一個類的時候,要想好這個類將來是否會被繼承,如果可以被繼承,則該類不能使用fianl修飾,在這裡呢,一般來說工具類我們往往都會設計成為一個fianl類。在JDK中,被設計為final類的有String、System等。
  • Java 中的繼承和多態(深入版)
    面向對象的三大特性:封裝、繼承、多態。在這三個特性中,如果沒有封裝和繼承,也不會有多態。那麼多態實現的途徑和必要條件是什麼呢?以及多態中的重寫和重載在JVM中的表現是怎麼樣?在Java中是如何展現繼承的特性呢?對於子類繼承於父類時,又有什麼限制呢?
  • 圖說Java中的OOPs(面向對象編程系統)基本概念
    面向對象編程是一種編程概念,其核心思想是允許用戶創建所需要的對象,然後提供處理這些對象的方法,使用者通過操作對象而獲得運算數據。本文將以簡潔的方式對面向對象編程中的概念進行梳理。1.Encapsulation(封裝)簡單來說,封裝是一種將代碼和數據包裝到一個單元中的OOP技術。在OOPs概念中,一個類的變量始終對其他類隱藏,想要獲取類中的變量,只能使用當前類提供的方法對其進行訪問。我們以膠囊藥物為例,基本上,膠囊封裝了藥物的幾種組合,如果藥物的組合看成是變量和方法的,則膠囊將充當一個類,使用膠囊包裹藥物的過程則稱為封裝。
  • JAVA8 新特性詳解
    ,則局部變量必須是final的,若是局部變量沒有加final關鍵字,系統會自動添加,此後在修改該局部變量,會報錯;示例代碼:public interface LambdaTest {abstract void print();
  • Java基礎學習:java中整數類型變量
    主要有以下特點, 在類中聲明,但在方法、構造方法和語句塊之外,用static修飾; 類只擁有類變量的一份拷貝; 儲存在靜態存儲區; 在第一次被訪問時創建,在程序結束時銷毀; 可以通過:ClassName.VariableName
  • 「Java」基礎19:修飾符有哪些?
    ④用final修飾引用數據類型局部變量。只能創建對象一次,不能再次創建對象,編譯會報錯。引用數據類型不能更改的是對象的地址。該對象的值是可以通過對應的setXXX()方法更改的。3.修飾成員變量成員變量和局部變量有一個區別在於:成員變量是有默認值的。所以用final修飾成員變量,它就不再有默認值了,必須一步直接賦值 。
  • Java面向對象之:封裝、繼承、多態
    Java面向對象之:封裝、繼承、多態高內聚和低耦合面向對象的最終目的是要構建強健、安全、高效的項目,也就是要實現項目的高內聚和低耦合>繼承從面向對象的角度上說,繼承是一種從一般到特殊的關係,是一種「is a」的關係,即子類是對父類的拓展,是一種特殊的父類,比如:狗是動物的一種特殊情況,狗屬於動物;在這個例子中,動物是父類,狗是子類,狗繼承了動物的特徵和行為,並在動物的特徵和行為的基礎之上拓展自己的特徵和行為,構成了狗這種特殊的動物。
  • 你知道final,finally,finalize 三者的區別嗎?
    final:     final是一個修飾符    當final修飾一個變量的時候,變量變成一個常量,它不能被二次賦值
  • 面試刷題:Spring Bean的生命周期?
    今天的問題是:springBean的生命周期是怎樣的?答:spring最基礎的能力是IOC(依賴注入),AOP(面向切面編程),ioc改善了模塊之間的耦合問題,依賴注入的方式:set方法,構造方法,成員變量+ @Autowire ;Bean的管理是IOC的主要功能。
  • 你必須掌握的 21 個 Java 核心技術!
    對象和實例在這方面,開發者需要了解class和instance的概念以及之間的差別, 這是java面向對象特性的一個基礎。面向對象編程的概念這是一個java的核心概念,對於任何java開發者都需要熟練掌握。Java中很多特性或者說知識點都是和java面向對象編程概念相關的。在我的理解,一個好的開發者不僅僅需要了解這些特性(知識點)本身。
  • 100天python計劃-Day9面向對象進階
    面向對象進階在前面的章節我們已經了解了面向對象的入門知識,知道了如何定義類,如何創建對象以及如何給對象發消息。為了能夠更好的使用面向對象編程思想進行程序開發,我們還需要對Python中的面向對象編程進行更為深入的了解。
  • Java新玩法,Java8新特性終極解析
    函數式接口是只定義了一個抽象方法的接口。Java 8引入了FunctionalInterface註解來表明一個接口打算成為一個函數式接口。例如,java.lang.Runnable就是一個函數式接口。String::valueOf x ->String.valueOf(x)Object::toString x ->x.toString()x::toString () ->x.toString()ArrayList::new () -> new ArrayList<>()如果使用Lambda表達式訪問一個在Lambda語句體外定義的非靜態變量或者對象
  • Java 代碼精簡之道
    作者根據多年來的實踐探索,總結了大量的 Java 代碼精簡之「術」,試圖闡述出心中的 Java 代碼精簡之「道」。精簡:importstatic java.lang.Math.PI;importstatic java.lang.Math.pow;importstatic java.util.stream.Collectors.toList;List
  • Java之final關鍵字的簡單介紹下
    各位小夥伴大家好,在之前的文章中,小編有介紹過,final關鍵字修飾局部變量Java之final關鍵字的簡單介紹中,支持小編要介紹的是final關鍵字修飾成員變量Java之成員變量和局部變量的區別。final關鍵字修飾成員變量:對於成員變量來說,如果使用final關鍵字,那麼這個變量是不可變。1.由於成員變量具有默認值,所以用了final之後必須手動賦值,不會給默認值了。2.對於final修飾的成員變量,要麼使用直接賦值,要麼通過構造方法Java之構造方法與this關鍵字的簡單介紹賦值,二者選其一3.必須保證類當中所有重載的構造方法,都最終會對final的成員變量進行賦值。
  • C風格的面向對象編程
    面向對象編程(OOP),最早是C++、java等面向對象語言的一個核心特點,之後發展成了一種編程思想。面向對象編程的主要特點是,把屬性(數據)與方法(函數)按照類型綁定,並按照類型之間的關係分層分模塊設計,通過基類指針和虛函數實現多態。
  • Java之final關鍵字的簡單介紹中
    各位小夥伴,大家好,在之前的文章小編有介紹過關鍵字fina修飾類和成員方法Java之final關鍵字的簡單介紹上。這次,小編要介紹的是關鍵字final修飾局部變量。代碼如下:public class Demo01Final {public static void main(String[] args) {int num1=11;System.out.println(num1);num1=12;//變量可以改變Java之成員變量和局部變量的區別System.out.println(num1);//一旦使用final來修飾變量,這個變量就不能進行改變