Java概述
何為編程
編程就是讓計算機為解決某個問題而使用某種程序設計語言編寫程序代碼七咧,并最終得到結果的過程厕宗。
為了使計算機能夠理解人的意圖麸恍,人類就必須要將需解決的問題的思路滤钱、方法、和手段通過計算機能夠理解的形式告訴計算機髓需,使得計算機能夠根據(jù)人的指令一步一步去工作,完成某種特定的任務房蝉。這種人和計算機之間交流的過程就是編程僚匆。
什么是Java
Java是一門面向對象編程語言,不僅吸收了C++語言的各種優(yōu)點搭幻,還摒棄了C++里難以理解的多繼承咧擂、指針等概念,因此Java語言具有功能強大和簡單易用兩個特征檀蹋。Java語言作為靜態(tài)面向對象編程語言的代表松申,極好地實現(xiàn)了面向對象理論,允許程序員以優(yōu)雅的思維方式進行復雜的編程 。
jdk1.5之后的三大版本
Java SE(J2SE攻臀,Java 2 Platform Standard Edition焕数,標準版)
Java SE 以前稱為 J2SE。它允許開發(fā)和部署在桌面刨啸、服務器堡赔、嵌入式環(huán)境和實時環(huán)境中使用的 Java 應用程序。Java SE 包含了支持 Java Web 服務開發(fā)的類设联,并為Java EE和Java ME提供基礎善已。
Java EE(J2EE,Java 2 Platform Enterprise Edition离例,企業(yè)版)
Java EE 以前稱為 J2EE换团。企業(yè)版本幫助開發(fā)和部署可移植、健壯宫蛆、可伸縮且安全的服務器端Java 應用程序艘包。Java EE 是在 Java SE 的基礎上構建的,它提供 Web 服務耀盗、組件模型想虎、管理和通信 API,可以用來實現(xiàn)企業(yè)級的面向服務體系結構(service-oriented architecture叛拷,SOA)和 Web2.0應用程序舌厨。2018年2月,Eclipse 宣布正式將 JavaEE 更名為 JakartaEE
Java ME(J2ME忿薇,Java 2 Platform Micro Edition裙椭,微型版)
Java ME 以前稱為 J2ME。Java ME 為在移動設備和嵌入式設備(比如手機署浩、PDA揉燃、電視機頂盒和打印機)上運行的應用程序提供一個健壯且靈活的環(huán)境。Java ME 包括靈活的用戶界面筋栋、健壯的安全模型你雌、許多內置的網絡協(xié)議以及對可以動態(tài)下載的連網和離線應用程序的豐富支持《矗基于 Java ME 規(guī)范的應用程序只需編寫一次婿崭,就可以用于許多設備,而且可以利用每個設備的本機功能肴颊。
JVM氓栈、JRE和JDK的關系
JVM
Java Virtual Machine是Java虛擬機,Java程序需要運行在虛擬機上婿着,不同的平臺有自己的虛擬機授瘦,因此Java語言可以實現(xiàn)跨平臺醋界。
JRE
Java Runtime Environment包括Java虛擬機和Java程序所需的核心類庫等。核心類庫主要是java.lang包:包含了運行Java程序必不可少的系統(tǒng)類提完,如基本數(shù)據(jù)類型形纺、基本數(shù)學函數(shù)、字符串處理徒欣、線程逐样、異常處理類等,系統(tǒng)缺省加載這個包
如果想要運行一個開發(fā)好的Java程序打肝,計算機中只需要安裝JRE即可脂新。
JDK
Java Development Kit是提供給Java開發(fā)人員使用的,其中包含了Java的開發(fā)工具粗梭,也包括了JRE争便。所以安裝了JDK,就無需再單獨安裝JRE了断医。其中的開發(fā)工具:編譯工具(javac.exe)滞乙,打包工具(jar.exe)等
JVM&JRE&JDK關系圖
什么是跨平臺性?原理是什么
所謂跨平臺性鉴嗤,是指java語言編寫的程序斩启,一次編譯后,可以在多個系統(tǒng)平臺上運行躬窜。
實現(xiàn)原理:Java程序是通過java虛擬機在系統(tǒng)平臺上運行的浇垦,只要該系統(tǒng)可以安裝相應的java虛擬機炕置,該系統(tǒng)就可以運行java程序荣挨。
Java語言有哪些特點
簡單易學(Java語言的語法與C語言和C++語言很接近)
面向對象(封裝,繼承朴摊,多態(tài))
平臺無關性(Java虛擬機實現(xiàn)平臺無關性)
支持網絡編程并且很方便(Java語言誕生本身就是為簡化網絡編程設計的)
支持多線程(多線程機制使應用程序在同一時間并行執(zhí)行多項任)
健壯性(Java語言的強類型機制默垄、異常處理、垃圾的自動收集等)
安全性
什么是字節(jié)碼甚纲?采用字節(jié)碼的最大好處是什么
字節(jié)碼:Java源代碼經過虛擬機編譯器編譯后產生的文件(即擴展為.class的文件)口锭,它不面向任何特定的處理器,只面向虛擬機介杆。
采用字節(jié)碼的好處:
Java語言通過字節(jié)碼的方式鹃操,在一定程度上解決了傳統(tǒng)解釋型語言執(zhí)行效率低的問題,同時又保留了解釋型語言可移植的特點春哨。所以Java程序運行時比較高效荆隘,而且,由于字節(jié)碼并不專對一種特定的機器赴背,因此椰拒,Java程序無須重新編譯便可在多種不同的計算機上運行晶渠。
先看下java中的編譯器和解釋器:
Java中引入了虛擬機的概念,即在機器和編譯程序之間加入了一層抽象的虛擬機器燃观。這臺虛擬的機器在任何平臺上都提供給編譯程序一個的共同的接口褒脯。編譯程序只需要面向虛擬機,生成虛擬機能夠理解的代碼缆毁,然后由解釋器來將虛擬機代碼轉換為特定系統(tǒng)的機器碼執(zhí)行番川。在Java中,這種供虛擬機理解的代碼叫做字節(jié)碼(即擴展為.class的文件)积锅,它不面向任何特定的處理器爽彤,只面向虛擬機。每一種平臺的解釋器是不同的缚陷,但是實現(xiàn)的虛擬機是相同的适篙。Java源程序經過編譯器編譯后變成字節(jié)碼,字節(jié)碼由虛擬機解釋執(zhí)行箫爷,虛擬機將每一條要執(zhí)行的字節(jié)碼送給解釋器嚷节,解釋器將其翻譯成特定機器上的機器碼,然后在特定的機器上運行虎锚,這就是上面提到的Java的特點的編譯與解釋并存的解釋硫痰。
Java源代碼---->編譯器---->jvm可執(zhí)行的Java字節(jié)碼(即虛擬指令)---->jvm---->jvm中解釋器----->機器可執(zhí)行的二進制機器碼---->程序運行。
什么是Java程序的主類窜护?應用程序和小程序的主類有何不同效斑?
一個程序中可以有多個類,但只能有一個類是主類柱徙。在Java應用程序中缓屠,這個主類是指包含main()方法的類。而在Java小程序中护侮,這個主類是一個繼承自系統(tǒng)類JApplet或Applet的子類敌完。應用程序的主類不一定要求是public類,但小程序的主類要求必須是public類羊初。主類是Java程序執(zhí)行的入口點滨溉。
Java應用程序與小程序之間有那些差別?
簡單說應用程序是從主線程啟動(也就是main()方法)长赞。applet小程序沒有main方法晦攒,主要是嵌在瀏覽器頁面上運行(調用init()線程或者run()來啟動),嵌入瀏覽器這點跟flash的小游戲類似得哆。
Java和C++的區(qū)別
我知道很多人沒學過C++脯颜,但是面試官就是沒事喜歡拿咱們Java和C++比呀!沒辦法A帧7ゲ薄热幔!就算沒學過C++,也要記下來讼庇!
都是面向對象的語言绎巨,都支持封裝、繼承和多態(tài)
Java不提供指針來直接訪問內存蠕啄,程序內存更加安全
Java的類是單繼承的场勤,C++支持多重繼承;雖然Java的類不可以多繼承歼跟,但是接口可以多繼承和媳。
Java有自動內存管理機制,不需要程序員手動釋放無用內存
Oracle JDK 和 OpenJDK 的對比
Oracle JDK版本將每三年發(fā)布一次哈街,而OpenJDK版本每三個月發(fā)布一次留瞳;
OpenJDK 是一個參考模型并且是完全開源的,而Oracle JDK是OpenJDK的一個實現(xiàn)骚秦,并不是完全開源的她倘;
Oracle JDK 比 OpenJDK 更穩(wěn)定。OpenJDK和Oracle JDK的代碼幾乎相同作箍,但Oracle JDK有更多的類和一些錯誤修復硬梁。因此,如果您想開發(fā)企業(yè)/商業(yè)軟件胞得,我建議您選擇Oracle JDK荧止,因為它經過了徹底的測試和穩(wěn)定。某些情況下阶剑,有些人提到在使用OpenJDK 可能會遇到了許多應用程序崩潰的問題跃巡,但是,只需切換到Oracle JDK就可以解決問題个扰;
在響應性和JVM性能方面魏铅,Oracle JDK與OpenJDK相比提供了更好的性能阱扬;
Oracle JDK不會為即將發(fā)布的版本提供長期支持迹炼,用戶每次都必須通過更新到最新版本獲得支持來獲取最新版本刀闷;
Oracle JDK根據(jù)二進制代碼許可協(xié)議獲得許可情连,而OpenJDK根據(jù)GPL v2許可獲得許可沸呐。
基礎語法
數(shù)據(jù)類型
Java有哪些數(shù)據(jù)類型
定義:Java語言是強類型語言呐伞,對于每一種數(shù)據(jù)都定義了明確的具體的數(shù)據(jù)類型颈渊,在內存中分配了不同大小的內存空間淋昭。
分類
基本數(shù)據(jù)類型
- 數(shù)值型
- 整數(shù)類型(byte,short,int,long)
- 浮點類型(float,double)
- 字符型(char)
- 布爾型(boolean)
引用數(shù)據(jù)類型 - 類(class)
- 接口(interface)
- 數(shù)組([])
Java基本數(shù)據(jù)類型圖
switch 是否能作用在 byte 上俐填,是否能作用在 long 上,是否能作用在 String 上
在 Java 5 以前翔忽,switch(expr)中英融,expr 只能是 byte盏檐、short、char驶悟、int胡野。從 Java5 開始,Java 中引入了枚舉類型痕鳍,expr 也可以是 enum 類型硫豆,從 Java 7 開始,expr 還可以是字符串(String)笼呆,但是長整型(long)在目前所有的版本中都是不可以的熊响。
用最有效率的方法計算 2 乘以 8
2 << 3(左移 3 位相當于乘以 2 的 3 次方,右移 3 位相當于除以 2 的 3 次方)诗赌。
Math.round(11.5) 等于多少汗茄?Math.round(-11.5)等于多少
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11铭若。四舍五入的原理是在參數(shù)上加 0.5 然后進行下取整剔难。
float f=3.4;是否正確
不正確。3.4 是雙精度數(shù)奥喻,將雙精度型(double)賦值給浮點型(float)屬于下轉型(down-casting偶宫,也稱為窄化)會造成精度損失,因此需要強制類型轉換float f =(float)3.4; 或者寫成 float f =3.4F;环鲤。
short s1 = 1; s1 = s1 + 1;有錯嗎?short s1 = 1; s1 += 1;有錯嗎
對于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 類型纯趋,因此 s1+1 運算結果也是 int型,需要強制轉換類型才能賦值給 short 型冷离。
而 short s1 = 1; s1 += 1;可以正確編譯吵冒,因為 s1+= 1;相當于 s1 = (short(s1 + 1);其中有隱含的強制類型轉換。
編碼
Java語言采用何種編碼方案西剥?有何特點痹栖?
Java語言采用Unicode編碼標準,Unicode(標準碼)瞭空,它為每個字符制訂了一個唯一的數(shù)值揪阿,因此在任何的語言,平臺咆畏,程序都可以放心的使用南捂。
注釋
什么Java注釋
定義:用于解釋說明程序的文字
分類
單行注釋
格式: // 注釋文字
多行注釋
格式: /* 注釋文字 /
文檔注釋
格式:/* 注釋文字 */
作用
在程序中,尤其是復雜的程序中旧找,適當?shù)丶尤胱⑨尶梢栽黾映绦虻目勺x性溺健,有利于程序的修改、調試和交流钮蛛。注釋的內容在程序編譯的時候會被忽視鞭缭,不會產生目標代碼剖膳,注釋的部分不會對程序的執(zhí)行結果產生任何影響。
注意事項:多行和文檔注釋都不能嵌套使用岭辣。
訪問修飾符
訪問修飾符 public,private,protected,以及不寫(默認)時的區(qū)別
定義:Java中潮秘,可以使用訪問修飾符來保護對類、變量易结、方法和構造方法的訪問枕荞。Java 支持 4 種不同的訪問權限。
分類
private : 在同一類內可見搞动。使用對象:變量躏精、方法。 注意:不能修飾類(外部類)
default (即缺省鹦肿,什么也不寫矗烛,不使用任何關鍵字): 在同一包內可見,不使用任何修飾符箩溃。使用對象:類瞭吃、接口、變量涣旨、方法歪架。
protected : 對同一包內的類和所有子類可見。使用對象:變量霹陡、方法和蚪。 注意:不能修飾類(外部類)。
public : 對所有類可見烹棉。使用對象:類攒霹、接口、變量浆洗、方法
訪問修飾符圖
運算符
&和&&的區(qū)別
&運算符有兩種用法:(1)按位與催束;(2)邏輯與。
&&運算符是短路與運算伏社。邏輯與跟短路與的差別是非常巨大的抠刺,雖然二者都要求運算符左右兩端的布爾值都是true 整個表達式的值才是 true。&&之所以稱為短路運算洛口,是因為如果&&左邊的表達式的值是 false矫付,右邊的表達式會被直接短路掉凯沪,不會進行運算第焰。
注意:邏輯或運算符(|)和短路或運算符(||)的差別也是如此。
關鍵字
Java 有沒有 goto
goto 是 Java 中的保留字妨马,在目前版本的 Java 中沒有使用挺举。
final 有什么用杀赢?
用于修飾類、屬性和方法湘纵;
被final修飾的類不可以被繼承
被final修飾的方法不可以被重寫
被final修飾的變量不可以被改變脂崔,被final修飾不可變的是變量的引用,而不是引用指向的內容梧喷,引用指向的內容是可以改變的
final finally finalize區(qū)別
final可以修飾類砌左、變量、方法铺敌,修飾類表示該類不能被繼承汇歹、修飾方法表示該方法不能被重寫、修飾變量表
示該變量是一個常量不能被重新賦值偿凭。
finally一般作用在try-catch代碼塊中产弹,在處理異常的時候,通常我們將一定要執(zhí)行的代碼方法finally代碼塊
中弯囊,表示不管是否出現(xiàn)異常痰哨,該代碼塊都會執(zhí)行,一般用來存放一些關閉資源的代碼匾嘱。
finalize是一個方法斤斧,屬于Object類的一個方法,而Object類是所有類的父類霎烙,該方法一般由垃圾回收器來調
用折欠,當我們調用System.gc() 方法的時候,由垃圾回收器調用finalize()吼过,回收垃圾锐秦,一個對象是否可回收的
最后判斷。
this關鍵字的用法
this是自身的一個對象盗忱,代表對象本身酱床,可以理解為:指向對象本身的一個指針。
this的用法在java中大體可以分為3種:
1.普通的直接引用趟佃,this相當于是指向當前對象本身扇谣。
2.形參與成員名字重名,用this來區(qū)分:
public Person(String name, int age) {
this.name = name;
this.age = age;
}
3.引用本類的構造函數(shù)
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this(name);
this.age = age;
}
}
super關鍵字的用法
super可以理解為是指向自己超(父)類對象的一個指針闲昭,而這個超類指的是離自己最近的一個父類罐寨。
super也有三種用法:
1.普通的直接引用
與this類似,super相當于是指向當前對象的父類的引用序矩,這樣就可以用super.xxx來引用父類的成員鸯绿。
2.子類中的成員變量或方法與父類中的成員變量或方法同名時,用super進行區(qū)分
class Person{
protected String name;
public Person(String name) {
this.name = name;
}
}
class Student extends Person{
private String name;
public Student(String name, String name1) {
super(name);
this.name = name1;
}
public void getInfo(){
System.out.println(this.name); //Child
System.out.println(super.name); //Father
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student("Father","Child");
s1.getInfo();
}
}
3.引用父類構造函數(shù)
- super(參數(shù)):調用父類中的某一個構造函數(shù)(應該為構造函數(shù)中的第一條語句)。
- this(參數(shù)):調用本類中另一種形式的構造函數(shù)(應該為構造函數(shù)中的第一條語句)瓶蝴。
this與super的區(qū)別
super: 它引用當前對象的直接父類中的成員(用來訪問直接父類中被隱藏的父類中成員數(shù)據(jù)或函數(shù)毒返,基類與派生類中有相同成員定義時如:super.變量名 super.成員函數(shù)據(jù)名(實參)
this:它代表當前對象名(在程序中易產生二義性之處,應使用this來指明當前對象舷手;如果函數(shù)的形參與類中的成員數(shù)據(jù)同名拧簸,這時需用this來指明成員變量名)
super()和this()類似,區(qū)別是,super()在子類中調用父類的構造方法男窟,this()在本類內調用本類的其它構造方法盆赤。
super()和this()均需放在構造方法內第一行。
盡管可以用this調用一個構造器歉眷,但卻不能調用兩個弟劲。
this和super不能同時出現(xiàn)在一個構造函數(shù)里面,因為this必然會調用其它的構造函數(shù)姥芥,其它的構造函數(shù)必然也會有super語句的存在兔乞,所以在同一個構造函數(shù)里面有相同的語句,就失去了語句的意義凉唐,編譯器也不會通過庸追。
this()和super()都指的是對象,所以台囱,均不可以在static環(huán)境中使用淡溯。包括:static變量,static方法,static語句塊簿训。
從本質上講咱娶,this是一個指向本對象的指針, 然而super是一個Java關鍵字。
static存在的主要意義
static的主要意義是在于創(chuàng)建獨立于具體對象的域變量或者方法强品。以致于即使沒有創(chuàng)建對象膘侮,也能使用屬性和調用方法!
static關鍵字還有一個比較關鍵的作用就是 用來形成靜態(tài)代碼塊以優(yōu)化程序性能的榛。static塊可以置于類中的任何地方琼了,類中可以有多個static塊。在類初次被加載的時候夫晌,會按照static塊的順序來執(zhí)行每個static塊雕薪,并且只會執(zhí)行一次。
為什么說static塊可以用來優(yōu)化程序性能晓淀,是因為它的特性:只會在類加載的時候執(zhí)行一次所袁。因此,很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行凶掰。
static的獨特之處
1燥爷、被static修飾的變量或者方法是獨立于該類的任何對象蜈亩,也就是說,這些變量和方法不屬于任何一個實例對象局劲,而是被類的實例對象所共享勺拣。
怎么理解 “被類的實例對象所共享” 這句話呢奶赠?就是說鱼填,一個類的靜態(tài)成員,它是屬于大伙的【大伙指的是這個類的多個對象實例毅戈,我們都知道一個類可以創(chuàng)建多個實例苹丸!】,所有的類對象共享的苇经,不像成員變量是自個的【自個指的是這個類的單個實例對象】…我覺得我已經講的很通俗了赘理,你明白了咩?
2扇单、在該類被第一次加載的時候商模,就會去加載被static修飾的部分,而且只在類第一次使用時加載并進行初始化蜘澜,注意這是第一次用就要初始化施流,后面根據(jù)需要是可以再次賦值的。
3鄙信、static變量值在類加載的時候分配空間瞪醋,以后創(chuàng)建類對象的時候不會重新分配。賦值的話装诡,是可以任意賦值的银受!
4、被static修飾的變量或者方法是優(yōu)先于對象存在的鸦采,也就是說當一個類加載完畢之后宾巍,即便沒有創(chuàng)建對象,也可以去訪問渔伯。
static應用場景
因為static是被類的實例對象所共享蜀漆,因此如果某個成員變量是被所有對象所共享的,那么這個成員變量就應該定義為靜態(tài)變量咱旱。
因此比較常見的static應用場景有:
1确丢、修飾成員變量 2、修飾成員方法 3吐限、靜態(tài)代碼塊 4鲜侥、修飾類【只能修飾內部類也就是靜態(tài)內部類】 5、靜態(tài)導包
static注意事項
1诸典、靜態(tài)只能訪問靜態(tài)描函。 2、非靜態(tài)既可以訪問非靜態(tài)的,也可以訪問靜態(tài)的舀寓。
流程控制語句
break ,continue ,return 的區(qū)別及作用
break 跳出總上一層循環(huán)胆数,不再執(zhí)行循環(huán)(結束當前的循環(huán)體)
continue 跳出本次循環(huán),繼續(xù)執(zhí)行下次循環(huán)(結束正在執(zhí)行的循環(huán) 進入下一個循環(huán)條件)
return 程序返回互墓,不再執(zhí)行下面的代碼(結束當前的方法 直接返回)
在 Java 中必尼,如何跳出當前的多重嵌套循環(huán)
在Java中,要想跳出多重循環(huán)篡撵,可以在外面的循環(huán)語句前定義一個標號判莉,然后在里層循環(huán)體的代碼中使用帶有標號的break 語句,即可跳出外層循環(huán)育谬。例如:
public static void main(String[] args) {
ok:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
System.out.println("i=" + i + ",j=" + j);
if (j == 5) {
break ok;
}
}
}
}
面向對象
面向對象概述
面向對象和面向過程的區(qū)別
面向過程:
優(yōu)點:性能比面向對象高券盅,因為類調用時需要實例化,開銷比較大膛檀,比較消耗資源;比如單片機锰镀、嵌入式開發(fā)、Linux/Unix等一般采用面向過程開發(fā)咖刃,性能是最重要的因素泳炉。
缺點:沒有面向對象易維護、易復用僵缺、易擴展
面向對象:
優(yōu)點:易維護胡桃、易復用、易擴展磕潮,由于面向對象有封裝翠胰、繼承、多態(tài)性的特性自脯,可以設計出低耦合的系統(tǒng)之景,使系統(tǒng)更加靈活、更加易于維護
缺點:性能比面向過程低
面向過程是具體化的膏潮,流程化的锻狗,解決一個問題,你需要一步一步的分析焕参,一步一步的實現(xiàn)轻纪。
面向對象是模型化的,你只需抽象出一個類叠纷,這是一個封閉的盒子刻帚,在這里你擁有數(shù)據(jù)也擁有解決問題的方法。需要什么功能直接使用就可以了涩嚣,不必去一步一步的實現(xiàn)崇众,至于這個功能是如何實現(xiàn)的掂僵,管我們什么事?我們會用就可以了顷歌。
面向對象的底層其實還是面向過程锰蓬,把面向過程抽象成類,然后封裝眯漩,方便我們使用的就是面向對象了芹扭。
面向對象三大特性
面向對象的特征有哪些方面
面向對象的特征主要有以下幾個方面:
抽象:抽象是將一類對象的共同特征總結出來構造類的過程,包括數(shù)據(jù)抽象和行為抽象兩方面坤塞。抽象只關注對象有哪些屬性和行為冯勉,并不關注這些行為的細節(jié)是什么澈蚌。
封裝
封裝把一個對象的屬性私有化摹芙,同時提供一些可以被外界訪問的屬性的方法捆交,如果屬性不想被外界訪問之宿,我們大可不必提供方法給外界訪問。但是如果一個類沒有提供給外界訪問的方法尺借,那么這個類也沒有什么意義了份汗。
繼承
繼承是使用已存在的類的定義作為基礎建立新類的技術盈电,新類的定義可以增加新的數(shù)據(jù)或新的功能,也可以用父類的功能杯活,但不能選擇性地繼承父類匆帚。通過使用繼承我們能夠非常方便地復用以前的代碼。
關于繼承如下 3 點請記着跃:
子類擁有父類非 private 的屬性和方法吸重。
子類可以擁有自己屬性和方法,即子類可以對父類進行擴展歪今。
子類可以用自己的方式實現(xiàn)父類的方法嚎幸。(以后介紹)。
多態(tài)
所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調用在編程時并不確定寄猩,而是在程序運行期間才確定嫉晶,即一個引用變量到底會指向哪個類的實例對象,該引用變量發(fā)出的方法調用到底是哪個類中實現(xiàn)的方法田篇,必須在由程序運行期間才能決定替废。
在Java中有兩種形式可以實現(xiàn)多態(tài):繼承(多個子類對同一方法的重寫)和接口(實現(xiàn)接口并覆蓋接口中同一方法)。
其中Java 面向對象編程三大特性:封裝 繼承 多態(tài)
封裝:隱藏對象的屬性和實現(xiàn)細節(jié)泊柬,僅對外提供公共訪問方式椎镣,將變化隔離,便于使用彬呻,提高復用性和安全性衣陶。
繼承:繼承是使用已存在的類的定義作為基礎建立新類的技術柄瑰,新類的定義可以增加新的數(shù)據(jù)或新的功能,也可以用父類的功能剪况,但不能選擇性地繼承父類教沾。通過使用繼承可以提高代碼復用性。繼承是多態(tài)的前提译断。
關于繼承如下 3 點請記资诜:
子類擁有父類非 private 的屬性和方法。
子類可以擁有自己屬性和方法孙咪,即子類可以對父類進行擴展堪唐。
子類可以用自己的方式實現(xiàn)父類的方法。
多態(tài)性:父類或接口定義的引用變量可以指向子類或具體實現(xiàn)類的實例對象翎蹈。提高了程序的拓展性淮菠。
在Java中有兩種形式可以實現(xiàn)多態(tài):繼承(多個子類對同一方法的重寫)和接口(實現(xiàn)接口并覆蓋接口中同一方法)。
方法重載(overload)實現(xiàn)的是編譯時的多態(tài)性(也稱為前綁定)荤堪,而方法重寫(override)實現(xiàn)的是運行時的多態(tài)性(也稱為后綁定)合陵。
一個引用變量到底會指向哪個類的實例對象,該引用變量發(fā)出的方法調用到底是哪個類中實現(xiàn)的方法澄阳,必須在由程序運行期間才能決定拥知。運行時的多態(tài)是面向對象最精髓的東西,要實現(xiàn)多態(tài)需要做兩件事:
方法重寫(子類繼承父類并重寫父類中已有的或抽象的方法)碎赢;
對象造型(用父類型引用子類型對象低剔,這樣同樣的引用調用同樣的方法就會根據(jù)子類對象的不同而表現(xiàn)出不同的行為)。
什么是多態(tài)機制肮塞?Java語言是如何實現(xiàn)多態(tài)的襟齿?
所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調用在編程時并不確定,而是在程序運行期間才確定峦嗤,即一個引用變量倒底會指向哪個類的實例對象蕊唐,該引用變量發(fā)出的方法調用到底是哪個類中實現(xiàn)的方法,必須在由程序運行期間才能決定烁设。因為在程序運行時才確定具體的類替梨,這樣,不用修改源程序代碼装黑,就可以讓引用變量綁定到各種不同的類實現(xiàn)上副瀑,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼恋谭,讓程序可以選擇多個運行狀態(tài)糠睡,這就是多態(tài)性。
多態(tài)分為編譯時多態(tài)和運行時多態(tài)疚颊。其中編輯時多態(tài)是靜態(tài)的狈孔,主要是指方法的重載信认,它是根據(jù)參數(shù)列表的不同來區(qū)分不同的函數(shù),通過編輯之后會變成兩個不同的函數(shù)均抽,在運行時談不上多態(tài)嫁赏。而運行時多態(tài)是動態(tài)的,它是通過動態(tài)綁定來實現(xiàn)的油挥,也就是我們所說的多態(tài)性潦蝇。
多態(tài)的實現(xiàn)
Java實現(xiàn)多態(tài)有三個必要條件:繼承、重寫深寥、向上轉型攘乒。
繼承:在多態(tài)中必須存在有繼承關系的子類和父類。
重寫:子類對父類中某些方法進行重新定義惋鹅,在調用這些方法時就會調用子類的方法则酝。
向上轉型:在多態(tài)中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調用父類的方法和子類的方法负饲。
只有滿足了上述三個條件堤魁,我們才能夠在同一個繼承結構中使用統(tǒng)一的邏輯實現(xiàn)代碼處理不同的對象喂链,從而達到執(zhí)行不同的行為返十。
對于Java而言,它多態(tài)的實現(xiàn)機制遵循一個原則:當超類對象引用變量引用子類對象時椭微,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法洞坑,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法蝇率。
面向對象五大基本原則是什么(可選)
單一職責原則SRP(Single Responsibility Principle)
類的功能要單一迟杂,不能包羅萬象,跟雜貨鋪似的本慕。
開放封閉原則OCP(Open-Close Principle)
一個模塊對于拓展是開放的排拷,對于修改是封閉的,想要增加功能熱烈歡迎锅尘,想要修改监氢,哼,一萬個不樂意藤违。
里式替換原則LSP(the Liskov Substitution Principle LSP)
子類可以替換父類出現(xiàn)在父類能夠出現(xiàn)的任何地方浪腐。比如你能代表你爸去你姥姥家干活。哈哈~~
依賴倒置原則DIP(the Dependency Inversion Principle DIP)
高層次的模塊不應該依賴于低層次的模塊顿乒,他們都應該依賴于抽象议街。抽象不應該依賴于具體實現(xiàn),具體實現(xiàn)應該依賴于抽象璧榄。就是你出國要說你是中國人特漩,而不能說你是哪個村子的吧雹。比如說中國人是抽象的,下面有具體的xx省涂身,xx市吮炕,xx縣。你要依賴的抽象是中國人访得,而不是你是xx村的龙亲。
接口分離原則ISP(the Interface Segregation Principle ISP)
設計時采用多個與特定客戶類有關的接口比采用一個通用的接口要好。就比如一個手機擁有打電話悍抑,看視頻鳄炉,玩游戲等功能,把這幾個功能拆分成不同的接口搜骡,比在一個接口里要好的多拂盯。
類與接口
抽象類和接口的對比
抽象類是用來捕捉子類的通用特性的。接口是抽象方法的集合记靡。
從設計層面來說谈竿,抽象類是對類的抽象,是一種模板設計摸吠,接口是行為的抽象空凸,是一種行為的規(guī)范。
相同點
接口和抽象類都不能實例化
都位于繼承的頂端寸痢,用于被其他實現(xiàn)或繼承
都包含抽象方法呀洲,其子類都必須覆寫這些抽象方法
不同點
參數(shù) 抽象類 接口
聲明 抽象類使用abstract關鍵字聲明 接口使用interface關鍵字聲明
實現(xiàn) 子類使用extends關鍵字來繼承抽象類。如果子類不是抽象類的話啼止,它需要提供抽象類中所有聲明的方法的實現(xiàn) 子類使用implements關鍵字來實現(xiàn)接口道逗。它需要提供接口中所有聲明的方法的實現(xiàn)
構造器 抽象類可以有構造器 接口不能有構造器
訪問修飾符 抽象類中的方法可以是任意訪問修飾符 接口方法默認修飾符是public。并且不允許定義為 private 或者 protected
多繼承 一個類最多只能繼承一個抽象類 一個類可以實現(xiàn)多個接口
字段聲明 抽象類的字段聲明可以是任意的 接口的字段默認都是 static 和 final 的
備注:Java8中接口中引入默認方法和靜態(tài)方法献烦,以此來減少抽象類和接口之間的差異滓窍。
現(xiàn)在,我們可以為接口提供默認實現(xiàn)的方法了巩那,并且不用強制子類來實現(xiàn)它吏夯。
接口和抽象類各有優(yōu)缺點,在接口和抽象類的選擇上拢操,必須遵守這樣一個原則:
行為模型應該總是通過接口而不是抽象類定義锦亦,所以通常是優(yōu)先選用接口,盡量少用抽象類令境。
選擇抽象類的時候通常是如下情況:需要定義子類的行為杠园,又要為子類提供通用的功能。
普通類和抽象類有哪些區(qū)別舔庶?
普通類不能包含抽象方法抛蚁,抽象類可以包含抽象方法陈醒。
抽象類不能直接實例化,普通類可以直接實例化瞧甩。
抽象類能使用 final 修飾嗎钉跷?
不能,定義抽象類就是讓其他類繼承的肚逸,如果定義為 final 該類就不能被繼承爷辙,這樣彼此就會產生矛盾,所以 final 不能修飾抽象類
創(chuàng)建一個對象用什么關鍵字朦促?對象實例與對象引用有何不同膝晾?
new關鍵字,new創(chuàng)建對象實例(對象實例在堆內存中)务冕,對象引用指向對象實例(對象引用存放在棧內存中)血当。一個對象引用可以指向0個或1個對象(一根繩子可以不系氣球,也可以系一個氣球);一個對象可以有n個引用指向它(可以用n條繩子系住一個氣球)
變量與方法
成員變量與局部變量的區(qū)別有哪些
變量:在程序執(zhí)行的過程中禀忆,在某個范圍內其值可以發(fā)生改變的量臊旭。從本質上講,變量其實是內存中的一小塊區(qū)域
成員變量:方法外部箩退,類內部定義的變量
局部變量:類的方法中的變量离熏。
成員變量和局部變量的區(qū)別
作用域
成員變量:針對整個類有效。
局部變量:只在某個范圍內有效乏德。(一般指的就是方法,語句體內)
存儲位置
成員變量:隨著對象的創(chuàng)建而存在撤奸,隨著對象的消失而消失,存儲在堆內存中喊括。
局部變量:在方法被調用,或者語句被執(zhí)行的時候存在矢棚,存儲在棧內存中郑什。當方法調用完,或者語句結束后蒲肋,就自動釋放蘑拯。
生命周期
成員變量:隨著對象的創(chuàng)建而存在,隨著對象的消失而消失
局部變量:當方法調用完兜粘,或者語句結束后申窘,就自動釋放。
初始值
成員變量:有默認初始值孔轴。
局部變量:沒有默認初始值剃法,使用前必須賦值。
使用原則
在使用變量時需要遵循的原則為:就近原則
首先在局部范圍找路鹰,有就使用贷洲;接著在成員位置找收厨。
在Java中定義一個不做事且沒有參數(shù)的構造方法的作用
Java程序在執(zhí)行子類的構造方法之前,如果沒有用super()來調用父類特定的構造方法优构,則會調用父類中“沒有參數(shù)的構造方法”诵叁。因此,如果父類中只定義了有參數(shù)的構造方法钦椭,而在子類的構造方法中又沒有用super()來調用父類中特定的構造方法拧额,則編譯時將發(fā)生錯誤,因為Java程序在父類中找不到沒有參數(shù)的構造方法可供執(zhí)行彪腔。解決辦法是在父類里加上一個不做事且沒有參數(shù)的構造方法势腮。
在調用子類構造方法之前會先調用父類沒有參數(shù)的構造方法,其目的是漫仆?
幫助子類做初始化工作捎拯。
一個類的構造方法的作用是什么?若一個類沒有聲明構造方法盲厌,改程序能正確執(zhí)行嗎署照?為什么?
主要作用是完成對類對象的初始化工作吗浩〗ㄜ剑可以執(zhí)行。因為一個類即使沒有聲明構造方法也會有默認的不帶參數(shù)的構造方法懂扼。
構造方法有哪些特性禁荸?
名字與類名相同;
沒有返回值阀湿,但不能用void聲明構造函數(shù)赶熟;
生成類的對象時自動執(zhí)行,無需調用陷嘴。
靜態(tài)變量和實例變量區(qū)別
靜態(tài)變量: 靜態(tài)變量由于不屬于任何實例對象映砖,屬于類的,所以在內存中只會有一份灾挨,在類的加載過程中邑退,JVM只為靜態(tài)變量分配一次內存空間。
實例變量: 每次創(chuàng)建對象劳澄,都會為每個對象分配成員變量內存空間地技,實例變量是屬于實例對象的,在內存中秒拔,創(chuàng)建幾次對象莫矗,就有幾份成員變量。
靜態(tài)變量與普通變量區(qū)別
static變量也稱作靜態(tài)變量,靜態(tài)變量和非靜態(tài)變量的區(qū)別是:靜態(tài)變量被所有的對象所共享趣苏,在內存中只有一個副本狡相,它當且僅當在類初次加載時會被初始化。而非靜態(tài)變量是對象所擁有的食磕,在創(chuàng)建對象的時候被初始化尽棕,存在多個副本,各個對象擁有的副本互不影響彬伦。
還有一點就是static成員變量的初始化順序按照定義的順序進行初始化滔悉。
靜態(tài)方法和實例方法有何不同?
靜態(tài)方法和實例方法的區(qū)別主要體現(xiàn)在兩個方面:
在外部調用靜態(tài)方法時单绑,可以使用"類名.方法名"的方式回官,也可以使用"對象名.方法名"的方式。而實例方法只有后面這種方式搂橙。也就是說歉提,調用靜態(tài)方法可以無需創(chuàng)建對象。
靜態(tài)方法在訪問本類的成員時区转,只允許訪問靜態(tài)成員(即靜態(tài)成員變量和靜態(tài)方法)苔巨,而不允許訪問實例成員變量和實例方法;實例方法則無此限制
在一個靜態(tài)方法內調用一個非靜態(tài)成員為什么是非法的废离?
由于靜態(tài)方法可以不通過對象進行調用侄泽,因此在靜態(tài)方法里,不能調用其他非靜態(tài)變量蜻韭,也不可以訪問非靜態(tài)變量成員悼尾。
什么是方法的返回值?返回值的作用是什么肖方?
方法的返回值是指我們獲取到的某個方法體中的代碼執(zhí)行后產生的結果9胛骸(前提是該方法可能產生結果)。返回值的作用:接收出結果窥妇,使得它可以用于其他的操作舷胜!
內部類
什么是內部類?
在Java中活翩,可以將一個類的定義放在另外一個類的定義內部,這就是內部類翻伺。內部類本身就是類的一個屬性材泄,與其他屬性定義方式一致。
內部類的分類有哪些
內部類可以分為四種:成員內部類吨岭、局部內部類拉宗、匿名內部類和靜態(tài)內部類。
靜態(tài)內部類
定義在類內部的靜態(tài)類,就是靜態(tài)內部類旦事。
public class Outer {
private static int radius = 1;
static class StaticInner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
}
}
}
靜態(tài)內部類可以訪問外部類所有的靜態(tài)變量魁巩,而不可訪問外部類的非靜態(tài)變量;靜態(tài)內部類的創(chuàng)建方式姐浮,new 外部類.靜態(tài)內部類()谷遂,如下:
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
成員內部類
定義在類內部,成員位置上的非靜態(tài)類卖鲤,就是成員內部類肾扰。
public class Outer {
private static int radius = 1;
private int count =2;
class Inner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
System.out.println("visit outer variable:" + count);
}
}
}
成員內部類可以訪問外部類所有的變量和方法,包括靜態(tài)和非靜態(tài)蛋逾,私有和公有集晚。成員內部類依賴于外部類的實例,它的創(chuàng)建方式外部類實例.new 內部類()区匣,如下:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
局部內部類
定義在方法中的內部類偷拔,就是局部內部類。
public class Outer {
private int out_a = 1;
private static int STATIC_b = 2;
public void testFunctionClass(){
int inner_c =3;
class Inner {
private void fun(){
System.out.println(out_a);
System.out.println(STATIC_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void testStaticFunctionClass(){
int d =3;
class Inner {
private void fun(){
// System.out.println(out_a); 編譯錯誤亏钩,定義在靜態(tài)方法中的局部類不可以訪問外部類的實例變量
System.out.println(STATIC_b);
System.out.println(d);
}
}
Inner inner = new Inner();
inner.fun();
}
}
定義在實例方法中的局部類可以訪問外部類的所有變量和方法莲绰,定義在靜態(tài)方法中的局部類只能訪問外部類的靜態(tài)變量和方法。局部內部類的創(chuàng)建方式铸屉,在對應方法內钉蒲,new 內部類()弟头,如下:
public static void testStaticFunctionClass(){
class Inner {
}
Inner inner = new Inner();
}
匿名內部類
匿名內部類就是沒有名字的內部類迹栓,日常開發(fā)中使用的比較多。
public class Outer {
private void test(final int i) {
new Service() {
public void method() {
for (int j = 0; j < i; j++) {
System.out.println("匿名內部類" );
}
}
}.method();
}
}
//匿名內部類必須繼承或實現(xiàn)一個已有的接口
interface Service{
void method();
}
除了沒有名字七兜,匿名內部類還有以下特點:
匿名內部類必須繼承一個抽象類或者實現(xiàn)一個接口昌屉。
匿名內部類不能定義任何靜態(tài)成員和靜態(tài)方法钙蒙。
當所在的方法的形參需要被匿名內部類使用時,必須聲明為 final间驮。
匿名內部類不能是抽象的躬厌,它必須要實現(xiàn)繼承的類或者實現(xiàn)的接口的所有抽象方法。
匿名內部類創(chuàng)建方式:
new 類/接口{
//匿名內部類實現(xiàn)部分
}
內部類的優(yōu)點
我們?yōu)槭裁匆褂脙炔款惸鼐好保恳驗樗幸韵聝?yōu)點:
一個內部類對象可以訪問創(chuàng)建它的外部類對象的內容扛施,包括私有數(shù)據(jù)!
內部類不為同一包的其他類所見屹篓,具有很好的封裝性疙渣;
內部類有效實現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷堆巧。
匿名內部類可以很方便的定義回調妄荔。
內部類有哪些應用場景
一些多算法場合
解決一些非面向對象的語句塊泼菌。
適當使用內部類,使得代碼更加靈活和富有擴展性啦租。
當某個類除了它的外部類哗伯,不再被其他的類使用時。
局部內部類和匿名內部類訪問局部變量的時候篷角,為什么變量必須要加上final焊刹?
局部內部類和匿名內部類訪問局部變量的時候,為什么變量必須要加上final呢内地?它內部原理是什么呢伴澄?
先看這段代碼:
public class Outer {
void outMethod(){
final int a =10;
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
以上例子,為什么要加final呢阱缓?是因為生命周期不一致非凌, 局部變量直接存儲在棧中,當方法執(zhí)行結束后荆针,非final的局部變量就被銷毀敞嗡。而局部內部類對局部變量的引用依然存在,如果局部內部類要調用局部變量時航背,就會出錯喉悴。加了final,可以確保局部內部類使用的變量與外層的局部變量區(qū)分開玖媚,解決了這個問題箕肃。
內部類相關,看程序說出運行結果
public class Outer {
private int age = 12;
class Inner {
private int age = 13;
public void print() {
int age = 14;
System.out.println("局部變量:" + age);
System.out.println("內部類變量:" + this.age);
System.out.println("外部類變量:" + Outer.this.age);
}
}
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.print();
}
}
運行結果:
局部變量:14
內部類變量:13
外部類變量:12
重寫與重載
構造器(constructor)是否可被重寫(override)
構造器不能被繼承今魔,因此不能被重寫勺像,但可以被重載。
重載(Overload)和重寫(Override)的區(qū)別错森。重載的方法能否根據(jù)返回類型進行區(qū)分吟宦?
方法的重載和重寫都是實現(xiàn)多態(tài)的方式,區(qū)別在于前者實現(xiàn)的是編譯時的多態(tài)性涩维,而后者實現(xiàn)的是運行時的多態(tài)性殃姓。
重載:發(fā)生在同一個類中,方法名相同參數(shù)列表不同(參數(shù)類型不同瓦阐、個數(shù)不同蜗侈、順序不同),與方法返回值和訪問修飾符無關睡蟋,即重載的方法不能根據(jù)返回類型進行區(qū)分
重寫:發(fā)生在父子類中宛篇,方法名、參數(shù)列表必須相同薄湿,返回值小于等于父類,拋出的異常小于等于父類,訪問修飾符大于等于父類(里氏代換原則)豺瘤;如果父類方法訪問修飾符為private則子類中就不是重寫吆倦。
對象相等判斷
== 和 equals 的區(qū)別是什么
== : 它的作用是判斷兩個對象的地址是不是相等。即坐求,判斷兩個對象是不是同一個對象蚕泽。(基本數(shù)據(jù)類型 == 比較的是值,引用數(shù)據(jù)類型 == 比較的是內存地址)
equals() : 它的作用也是判斷兩個對象是否相等桥嗤。但它一般有兩種使用情況:
情況1:類沒有覆蓋 equals() 方法须妻。則通過 equals() 比較該類的兩個對象時,等價于通過“==”比較這兩個對象泛领。
情況2:類覆蓋了 equals() 方法荒吏。一般,我們都覆蓋 equals() 方法來兩個對象的內容相等渊鞋;若它們的內容相等绰更,則返回 true (即,認為這兩個對象相等)锡宋。
舉個例子:
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 為一個引用
String b = new String("ab"); // b為另一個引用,對象的內容一樣
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 從常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false儡湾,非同一對象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
說明:
String中的equals方法是被重寫過的,因為object的equals方法是比較的對象的內存地址执俩,而String的equals方法比較的是對象的值徐钠。
當創(chuàng)建String類型的對象時,虛擬機會在常量池中查找有沒有已經存在的值和要創(chuàng)建的值相同的對象役首,如果有就把它賦給當前引用尝丐。如果沒有就在常量池中重新創(chuàng)建一個String對象。
hashCode 與 equals (重要)
HashSet如何檢查重復
兩個對象的 hashCode() 相同宋税,則 equals() 也一定為 true摊崭,對嗎?
hashCode和equals方法的關系
面試官可能會問你:“你重寫過 hashcode 和 equals 么杰赛,為什么重寫equals時必須重寫hashCode方法呢簸?”
hashCode()介紹
hashCode() 的作用是獲取哈希碼,也稱為散列碼乏屯;它實際上是返回一個int整數(shù)根时。這個哈希碼的作用是確定該對象在哈希表中的索引位置。hashCode() 定義在JDK的Object.java中辰晕,這就意味著Java中的任何類都包含有hashCode()函數(shù)蛤迎。
散列表存儲的是鍵值對(key-value),它的特點是:能根據(jù)“鍵”快速的檢索出對應的“值”含友。這其中就利用到了散列碼L骜伞(可以快速找到所需要的對象)
為什么要有 hashCode
我們以“HashSet 如何檢查重復”為例子來說明為什么要有 hashCode:
當你把對象加入 HashSet 時校辩,HashSet 會先計算對象的 hashcode 值來判斷對象加入的位置,同時也會與其他已經加入的對象的 hashcode 值作比較辆童,如果沒有相符的hashcode宜咒,HashSet會假設對象沒有重復出現(xiàn)。但是如果發(fā)現(xiàn)有相同 hashcode 值的對象把鉴,這時會調用 equals()方法來檢查 hashcode 相等的對象是否真的相同故黑。如果兩者相同,HashSet 就不會讓其加入操作成功庭砍。如果不同的話场晶,就會重新散列到其他位置。(摘自我的Java啟蒙書《Head first java》第二版)怠缸。這樣我們就大大減少了 equals 的次數(shù)诗轻,相應就大大提高了執(zhí)行速度。
hashCode()與equals()的相關規(guī)定
如果兩個對象相等凯旭,則hashcode一定也是相同的
兩個對象相等概耻,對兩個對象分別調用equals方法都返回true
兩個對象有相同的hashcode值,它們也不一定是相等的
因此罐呼,equals 方法被覆蓋過鞠柄,則 hashCode 方法也必須被覆蓋
hashCode() 的默認行為是對堆上的對象產生獨特值。如果沒有重寫 hashCode()嫉柴,則該 class 的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數(shù)據(jù))
對象的相等與指向他們的引用相等厌杜,兩者有什么不同?
對象的相等 比的是內存中存放的內容是否相等而 引用相等 比較的是他們指向的內存地址是否相等计螺。
值傳遞
當一個對象被當作參數(shù)傳遞到一個方法后夯尽,此方法可改變這個對象的屬性,并可返回變化后的結果登馒,那么這里到底是值傳遞還是引用傳遞
是值傳遞匙握。Java 語言的方法調用只支持參數(shù)的值傳遞。當一個對象實例作為一個參數(shù)被傳遞到方法中時陈轿,參數(shù)的值就是對該對象的引用圈纺。對象的屬性可以在被調用過程中被改變,但對對象引用的改變是不會影響到調用者的
為什么 Java 中只有值傳遞
首先回顧一下在程序設計語言中有關將參數(shù)傳遞給方法(或函數(shù))的一些專業(yè)術語麦射。按值調用(call by value)表示方法接收的是調用者提供的值蛾娶,而按引用調用(call by reference)表示方法接收的是調用者提供的變量地址。一個方法可以修改傳遞引用所對應的變量值潜秋,而不能修改傳遞值調用所對應的變量值蛔琅。 它用來描述各種程序設計語言(不只是Java)中方法參數(shù)傳遞方式。
Java程序設計語言總是采用按值調用峻呛。也就是說罗售,方法得到的是所有參數(shù)值的一個拷貝辜窑,也就是說,方法不能修改傳遞給它的任何參數(shù)變量的內容莽囤。
下面通過 3 個例子來給大家說明
example 1
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
結果:
a = 20
b = 10
num1 = 10
num2 = 20
解析:
在swap方法中谬擦,a、b的值進行交換朽缎,并不會影響到 num1、num2谜悟。因為话肖,a、b中的值葡幸,只是從 num1最筒、num2 的復制過來的。也就是說蔚叨,a床蜘、b相當于num1、num2 的副本蔑水,副本的內容無論怎么修改邢锯,都不會影響到原件本身。
通過上面例子搀别,我們已經知道了一個方法不能修改一個基本數(shù)據(jù)類型的參數(shù)丹擎,而對象引用作為參數(shù)就不一樣,請看 example2.
example 2
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 將數(shù)組的第一個元素變?yōu)?
array[0] = 0;
}
結果:
1
0
1
2
解析:
array 被初始化 arr 的拷貝也就是一個對象的引用歇父,也就是說 array 和 arr 指向的時同一個數(shù)組對象蒂培。 因此,外部對引用對象的改變會反映到所對應的對象上榜苫。
通過 example2 我們已經看到护戳,實現(xiàn)一個改變對象參數(shù)狀態(tài)的方法并不是一件難事。理由很簡單垂睬,方法得到的是對象引用的拷貝媳荒,對象引用及其他的拷貝同時引用同一個對象。
很多程序設計語言(特別是羔飞,C++和Pascal)提供了兩種參數(shù)傳遞的方式:值調用和引用調用肺樟。有些程序員(甚至本書的作者)認為Java程序設計語言對對象采用的是引用調用,實際上逻淌,這種理解是不對的么伯。由于這種誤解具有一定的普遍性,所以下面給出一個反例來詳細地闡述一下這個問題卡儒。
example 3
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小張");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
結果:
x:小李
y:小張
s1:小張
s2:小李
解析:
交換之前:
交換之后:
通過上面兩張圖可以很清晰的看出: 方法并沒有改變存儲在變量 s1 和 s2 中的對象引用田柔。swap方法的參數(shù)x和y被初始化為兩個對象引用的拷貝俐巴,這個方法交換的是這兩個拷貝
總結
Java程序設計語言對對象采用的不是引用調用,實際上硬爆,對象引用是按值傳遞的欣舵。
下面再總結一下Java中方法參數(shù)的使用情況:
- 一個方法不能修改一個基本數(shù)據(jù)類型的參數(shù)(即數(shù)值型或布爾型》
- 一個方法可以改變一個對象參數(shù)的狀態(tài)。
- 一個方法不能讓對象參數(shù)引用一個新的對象缀磕。
值傳遞和引用傳遞有什么區(qū)別
值傳遞:指的是在方法調用時缘圈,傳遞的參數(shù)是按值的拷貝傳遞,傳遞的是值的拷貝袜蚕,也就是說傳遞后就互不相關了糟把。
引用傳遞:指的是在方法調用時,傳遞的參數(shù)是按引用進行傳遞牲剃,其實傳遞的引用的地址遣疯,也就是變量所對應的內存空間的地址。傳遞的是值的引用凿傅,也就是說傳遞前和傳遞后都指向同一個引用(也就是同一個內存空間)缠犀。
Java包
JDK 中常用的包有哪些
java.lang:這個是系統(tǒng)的基礎類;
java.io:這里面是所有輸入輸出有關的類聪舒,比如文件操作等辨液;
java.nio:為了完善 io 包中的功能,提高 io 包中性能而寫的一個新包过椎;
java.net:這里面是與網絡有關的類室梅;
java.util:這個是系統(tǒng)輔助類,特別是集合類疚宇;
java.sql:這個是數(shù)據(jù)庫操作的類亡鼠。
import java和javax有什么區(qū)別
剛開始的時候 JavaAPI 所必需的包是 java 開頭的包,javax 當時只是擴展 API 包來說使用敷待。然而隨著時間的推移间涵,javax 逐漸的擴展成為 Java API 的組成部分。但是榜揖,將擴展從 javax 包移動到 java 包將是太麻煩了勾哩,最終會破壞一堆現(xiàn)有的代碼。因此举哟,最終決定 javax 包將成為標準API的一部分思劳。
所以,實際上java和javax沒有區(qū)別妨猩。這都是一個名字潜叛。
IO流
java 中 IO 流分為幾種?
按照流的流向分,可以分為輸入流和輸出流;
按照操作單元劃分威兜,可以劃分為字節(jié)流和字符流销斟;
按照流的角色劃分為節(jié)點流和處理流。
Java Io流共涉及40多個類椒舵,這些類看上去很雜亂蚂踊,但實際上很有規(guī)則,而且彼此之間存在非常緊密的聯(lián)系笔宿, Java I0流的40多個類都是從如下4個抽象類基類中派生出來的犁钟。
InputStream/Reader: 所有的輸入流的基類,前者是字節(jié)輸入流措伐,后者是字符輸入流特纤。
OutputStream/Writer: 所有輸出流的基類,前者是字節(jié)輸出流侥加,后者是字符輸出流。
按操作方式分類結構圖:
按操作對象分類結構圖:
BIO,NIO,AIO 有什么區(qū)別?
簡答
BIO:Block IO 同步阻塞式 IO粪躬,就是我們平常使用的傳統(tǒng) IO担败,它的特點是模式簡單使用方便,并發(fā)處理能力低镰官。
NIO:Non IO 同步非阻塞 IO提前,是傳統(tǒng) IO 的升級,客戶端和服務器端通過 Channel(通道)通訊泳唠,實現(xiàn)了多路復用狈网。
AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2笨腥,實現(xiàn)了異步非堵塞 IO 拓哺,異步 IO 的操作基于事件和回調機制。
詳細回答
BIO (Blocking I/O): 同步阻塞I/O模式脖母,數(shù)據(jù)的讀取寫入必須阻塞在一個線程內等待其完成士鸥。在活動連接數(shù)不是特別高(小于單機1000)的情況下,這種模型是比較不錯的谆级,可以讓每一個連接專注于自己的 I/O 并且編程模型簡單烤礁,也不用過多考慮系統(tǒng)的過載、限流等問題肥照。線程池本身就是一個天然的漏斗脚仔,可以緩沖一些系統(tǒng)處理不了的連接或請求。但是舆绎,當面對十萬甚至百萬級連接的時候鲤脏,傳統(tǒng)的 BIO 模型是無能為力的。因此亿蒸,我們需要一種更高效的 I/O 處理模型來應對更高的并發(fā)量凑兰。
NIO (New I/O): NIO是一種同步非阻塞的I/O模型掌桩,在Java 1.4 中引入了NIO框架,對應 java.nio 包姑食,提供了 Channel , Selector波岛,Buffer等抽象。NIO中的N可以理解為Non-blocking音半,不單純是New则拷。它支持面向緩沖的,基于通道的I/O操作方法曹鸠。 NIO提供了與傳統(tǒng)BIO模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現(xiàn),兩種通道都支持阻塞和非阻塞兩種模式煌茬。阻塞模式使用就像傳統(tǒng)中的支持一樣,比較簡單彻桃,但是性能和可靠性都不好坛善;非阻塞模式正好與之相反。對于低負載邻眷、低并發(fā)的應用程序眠屎,可以使用同步阻塞I/O來提升開發(fā)速率和更好的維護性;對于高負載肆饶、高并發(fā)的(網絡)應用改衩,應使用 NIO 的非阻塞模式來開發(fā)
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型驯镊。異步 IO 是基于事件和回調機制實現(xiàn)的葫督,也就是應用操作之后會直接返回,不會堵塞在那里板惑,當后臺處理完成橄镜,操作系統(tǒng)會通知相應的線程進行后續(xù)的操作。AIO 是異步IO的縮寫洒放,雖然 NIO 在網絡操作中蛉鹿,提供了非阻塞的方法,但是 NIO 的 IO 行為還是同步的往湿。對于 NIO 來說妖异,我們的業(yè)務線程是在 IO 操作準備好時,得到通知领追,接著就由這個線程自行進行 IO 操作他膳,IO操作本身是同步的。查閱網上相關資料绒窑,我發(fā)現(xiàn)就目前來說 AIO 的應用還不是很廣泛棕孙,Netty 之前也嘗試使用過 AIO,不過又放棄了。
Files的常用方法都有哪些蟀俊?
Files. exists():檢測文件路徑是否存在钦铺。
Files. createFile():創(chuàng)建文件。
Files. createDirectory():創(chuàng)建文件夾肢预。
Files. delete():刪除一個文件或目錄矛洞。
Files. copy():復制文件。
Files. move():移動文件烫映。
Files. size():查看文件個數(shù)沼本。
Files. read():讀取文件。
Files. write():寫入文件锭沟。
反射
什么是反射機制抽兆?
JAVA反射機制是在運行狀態(tài)中,對于任意一個類族淮,都能夠知道這個類的所有屬性和方法辫红;對于任意一個對象,都能夠調用它的任意一個方法和屬性祝辣;這種動態(tài)獲取的信息以及動態(tài)調用對象的方法的功能稱為java語言的反射機制厉熟。
靜態(tài)編譯和動態(tài)編譯
靜態(tài)編譯:在編譯時確定類型,綁定對象
動態(tài)編譯:運行時確定類型较幌,綁定對象
反射機制優(yōu)缺點
優(yōu)點: 運行期類型的判斷,動態(tài)加載類白翻,提高代碼靈活度乍炉。
缺點: 性能瓶頸:反射相當于一系列解釋操作,通知 JVM 要做的事情滤馍,性能比直接的java代碼要慢很多岛琼。
反射機制的應用場景有哪些?
反射是框架設計的靈魂巢株。
在我們平時的項目開發(fā)過程中槐瑞,基本上很少會直接使用到反射機制,但這不能說明反射機制沒有用阁苞,實際上有很多設計困檩、開發(fā)都與反射機制有關,例如模塊化的開發(fā)那槽,通過反射去調用對應的字節(jié)碼悼沿;動態(tài)代理設計模式也采用了反射機制,還有我們日常使用的 Spring/Hibernate 等框架也大量使用到了反射機制骚灸。
舉例:①我們在使用JDBC連接數(shù)據(jù)庫時使用Class.forName()通過反射加載數(shù)據(jù)庫的驅動程序糟趾;②Spring框架也用到很多反射機制,最經典的就是xml的配置模式。Spring 通過 XML 配置模式裝載 Bean 的過程:1) 將程序內所有 XML 或 Properties 配置文件加載入內存中; 2)Java類里面解析xml或properties里面的內容义郑,得到對應實體類的字節(jié)碼字符串以及相關的屬性信息; 3)使用反射機制蝶柿,根據(jù)這個字符串獲得某個類的Class實例; 4)動態(tài)配置實例的屬性
Java獲取反射的三種方法
1.通過new對象實現(xiàn)反射機制 2.通過路徑實現(xiàn)反射機制 3.通過類名實現(xiàn)反射機制
public class Student {
private int id;
String name;
protected boolean sex;
public float score;
}
public class Get {
//獲取反射機制三種方式
public static void main(String[] args) throws ClassNotFoundException {
//方式一(通過建立對象)
Student stu = new Student();
Class classobj1 = stu.getClass();
System.out.println(classobj1.getName());
//方式二(所在通過路徑-相對路徑)
Class classobj2 = Class.forName("fanshe.Student");
System.out.println(classobj2.getName());
//方式三(通過類名)
Class classobj3 = Student.class;
System.out.println(classobj3.getName());
}
}
常用API
String相關
字符型常量和字符串常量的區(qū)別
形式上: 字符常量是單引號引起的一個字符 字符串常量是雙引號引起的若干個字符
含義上: 字符常量相當于一個整形值(ASCII值),可以參加表達式運算 字符串常量代表一個地址值(該字符串在內存中存放位置)
占內存大小 字符常量只占一個字節(jié) 字符串常量占若干個字節(jié)(至少一個字符結束標志)
什么是字符串常量池?
字符串常量池位于堆內存中非驮,專門用來存儲字符串常量交汤,可以提高內存的使用率,避免開辟多塊空間存儲相同的字符串院尔,在創(chuàng)建字符串時 JVM 會首先檢查字符串常量池县匠,如果該字符串已經存在池中槽畔,則返回它的引用,如果不存在,則實例化一個字符串放到池中堪簿,并返回其引用。
String 是最基本的數(shù)據(jù)類型嗎
不是笙各。Java 中的基本數(shù)據(jù)類型只有 8 個 :byte利术、short、int例获、long汉额、float、double榨汤、char蠕搜、boolean;除了基本類型(primitive type)收壕,剩下的都是引用類型(referencetype)妓灌,Java 5 以后引入的枚舉類型也算是一種比較特殊的引用類型。
這是很基礎的東西蜜宪,但是很多初學者卻容易忽視虫埂,Java 的 8 種基本數(shù)據(jù)類型中不包括 String,基本數(shù)據(jù)類型中用來描述文本數(shù)據(jù)的是 char圃验,但是它只能表示單個字符掉伏,比如 ‘a’,‘好’ 之類的,如果要描述一段文本澳窑,就需要用多個 char 類型的變量斧散,也就是一個 char 類型數(shù)組,比如“你好” 就是長度為2的數(shù)組 char[] chars = {‘你’,‘好’};
但是使用數(shù)組過于麻煩照捡,所以就有了 String颅湘,String 底層就是一個 char 類型的數(shù)組,只是使用的時候開發(fā)者不需要直接操作底層數(shù)組栗精,用更加簡便的方式即可完成對字符串的使用闯参。
String有哪些特性
不變性:String 是只讀字符串瞻鹏,是一個典型的 immutable 對象,對它進行任何操作鹿寨,其實都是創(chuàng)建一個新的對象新博,再把引用指向該對象。不變模式的主要作用在于當一個對象需要被多線程共享并頻繁訪問時脚草,可以保證數(shù)據(jù)的一致性赫悄。
常量池優(yōu)化:String 對象創(chuàng)建之后,會在字符串常量池中進行緩存馏慨,如果下次創(chuàng)建同樣的對象時埂淮,會直接返回緩存的引用。
final:使用 final 來定義 String 類写隶,表示 String 類不能被繼承倔撞,提高了系統(tǒng)的安全性。
String為什么是不可變的嗎慕趴?
簡單來說就是String類利用了final修飾的char類型數(shù)組存儲字符痪蝇,源碼如下圖所以:
/** The value is used for character storage. */
private final char value[];
String真的是不可變的嗎?
我覺得如果別人問這個問題的話冕房,回答不可變就可以了躏啰。 下面只是給大家看兩個有代表性的例子:
- String不可變但不代表引用不可以變
String str = "Hello";
str = str + " World";
System.out.println("str=" + str);
結果:
str=Hello World
解析:
實際上,原來String的內容是不變的耙册,只是str由原來指向"Hello"的內存地址轉為指向"Hello World"的內存地址而已给僵,也就是說多開辟了一塊內存區(qū)域給"Hello World"字符串。
- 通過反射是可以修改所謂的“不可變”對象
// 創(chuàng)建字符串"Hello World"详拙, 并賦給引用s
String s = "Hello World";
System.out.println("s = " + s); // Hello World
// 獲取String類中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
// 改變value屬性的訪問權限
valueFieldOfString.setAccessible(true);
// 獲取s對象上的value屬性的值
char[] value = (char[]) valueFieldOfString.get(s);
// 改變value所引用的數(shù)組中的第5個字符
value[5] = '_';
System.out.println("s = " + s); // Hello_World
結果:
s = Hello World
s = Hello_World
解析:
用反射可以訪問私有成員想际, 然后反射出String對象中的value屬性, 進而改變通過獲得的value引用改變數(shù)組的結構溪厘。但是一般我們不會這么做,這里只是簡單提一下有這個東西牌柄。
是否可以繼承 String 類
String 類是 final 類畸悬,不可以被繼承。
String str="i"與 String str=new String(“i”)一樣嗎珊佣?
不一樣蹋宦,因為內存的分配方式不一樣。String str="i"的方式咒锻,java 虛擬機會將其分配到常量池中冷冗;而 String str=new String(“i”) 則會被分到堆內存中。
String s = new String(“xyz”);創(chuàng)建了幾個字符串對象
兩個對象惑艇,一個是靜態(tài)區(qū)的"xyz"蒿辙,一個是用new創(chuàng)建在堆上的對象拇泛。
String str1 = "hello"; //str1指向靜態(tài)區(qū)
String str2 = new String("hello"); //str2指向堆上的對象
String str3 = "hello";
String str4 = new String("hello");
System.out.println(str1.equals(str2)); //true
System.out.println(str2.equals(str4)); //true
System.out.println(str1 == str3); //true
System.out.println(str1 == str2); //false
System.out.println(str2 == str4); //false
System.out.println(str2 == "hello"); //false
str2 = str1;
System.out.println(str2 == "hello"); //true
如何將字符串反轉?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法思灌。
示例代碼:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba
數(shù)組有沒有 length()方法俺叭?String 有沒有 length()方法
數(shù)組沒有 length()方法 ,有 length 的屬性泰偿。String 有 length()方法熄守。JavaScript中,獲得字符串的長度是通過 length 屬性得到的耗跛,這一點容易和 Java 混淆裕照。
String 類的常用方法都有那些?
indexOf():返回指定字符的索引调塌。
charAt():返回指定索引處的字符晋南。
replace():字符串替換。
trim():去除字符串兩端空白烟阐。
split():分割字符串搬俊,返回一個分割后的字符串數(shù)組。
getBytes():返回字符串的 byte 類型數(shù)組蜒茄。
length():返回字符串長度唉擂。
toLowerCase():將字符串轉成小寫字母。
toUpperCase():將字符串轉成大寫字符檀葛。
substring():截取字符串玩祟。
equals():字符串比較。
在使用 HashMap 的時候屿聋,用 String 做 key 有什么好處空扎?
HashMap 內部實現(xiàn)是通過 key 的 hashcode 來確定 value 的存儲位置,因為字符串是不可變的润讥,所以當創(chuàng)建字符串時转锈,它的 hashcode 被緩存下來,不需要再次計算楚殿,所以相比于其他對象更快撮慨。
String和StringBuffer、StringBuilder的區(qū)別是什么脆粥?String為什么是不可變的
可變性
String類中使用字符數(shù)組保存字符串砌溺,private final char value[],所以string對象是不可變的变隔。StringBuilder與StringBuffer都繼承自AbstractStringBuilder類规伐,在AbstractStringBuilder中也是使用字符數(shù)組保存字符串,char[] value匣缘,這兩種對象都是可變的猖闪。
線程安全性
String中的對象是不可變的鲜棠,也就可以理解為常量,線程安全萧朝。AbstractStringBuilder是StringBuilder與StringBuffer的公共父類岔留,定義了一些字符串的基本操作,如expandCapacity检柬、append献联、insert、indexOf等公共方法何址。StringBuffer對方法加了同步鎖或者對調用的方法加了同步鎖里逆,所以是線程安全的。StringBuilder并沒有對方法進行加同步鎖用爪,所以是非線程安全的原押。
性能
每次對String 類型進行改變的時候,都會生成一個新的String對象偎血,然后將指針指向新的String 對象诸衔。StringBuffer每次都會對StringBuffer對象本身進行操作,而不是生成新的對象并改變對象引用颇玷。相同情況下使用StirngBuilder 相比使用StringBuffer 僅能獲得10%~15% 左右的性能提升笨农,但卻要冒多線程不安全的風險。
對于三者使用的總結
如果要操作少量的數(shù)據(jù)用 = String
單線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuilder
多線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuffer
Date相關
包裝類相關
自動裝箱與拆箱
裝箱:將基本類型用它們對應的引用類型包裝起來帖渠;
拆箱:將包裝類型轉換為基本數(shù)據(jù)類型谒亦;
int 和 Integer 有什么區(qū)別
Java 是一個近乎純潔的面向對象編程語言,但是為了編程的方便還是引入了基本數(shù)據(jù)類型空郊,但是為了能夠將這些基本數(shù)據(jù)類型當成對象操作份招,Java 為每一個基本數(shù)據(jù)類型都引入了對應的包裝類型(wrapper class),int 的包裝類就是 Integer狞甚,從 Java 5 開始引入了自動裝箱/拆箱機制锁摔,使得二者可以相互轉換。
Java 為每個原始類型提供了包裝類型:
原始類型: boolean哼审,char鄙漏,byte,short棺蛛,int,long巩步,float旁赊,double
包裝類型:Boolean,Character椅野,Byte终畅,Short籍胯,Integer,Long离福,F(xiàn)loat杖狼,Double
Integer a= 127 與 Integer b = 127相等嗎
對于對象引用類型:==比較的是對象的內存地址。
對于基本數(shù)據(jù)類型:==比較的是值妖爷。
如果整型字面量的值在-128到127之間蝶涩,那么自動裝箱時不會new新的Integer對象,而是直接引用常量池中的Integer對象絮识,超過范圍 a1==b1的結果是false
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 將3自動裝箱成Integer類型
int c = 3;
System.out.println(a == b); // false 兩個引用沒有引用同一對象
System.out.println(a == c); // true a自動拆箱成int類型再和c比較
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true
}