面向對象的核心—類和對象
- 類的語法定義
- 類的修飾符
- static可以修飾變量和方法,稱為類變量、類方法,它們屬於類本身。不被static修飾的變量和方法稱為實例變量、實例方法,屬於類的實例。
- 在類准備階段,系統就為類變量分配內存空間,類初始化階段完成類變量的初始化。類變量可以通過類來訪問,也可以通過類的對象來訪問(其實系統在底層轉換成通過該類訪問類變量,並不會為類變量分配內存),類方法也是。
- 類成員的作用域比實例成員的作用域更大,可能類成員初始化完成而實例成員未初始化完成,為避免錯誤,類成員不能訪問實例成員。
- 類的修飾符
[public/final/abstract] class Name{
//構造器
[public/private/protected] Name(struct val){
}
//成員變量
[public/protected/private/static/final] type tname [= defualt];
//方法
[public/protected/private/static/final/abstract] type set/get(struct val){
}
}
- 類名
- 成員變量
類的成員變量分為類變量和實例變量,類變量與類共存亡,實例變量與對象共存亡。
- 局部變量
局部變量分為形參、方法局部變量和代碼塊局部變量,除了形參之外的局部變量聲明后必須顯式初始化才能使用(系統不會默認初始化局部變量)。局部變量保存基本類型的值或者對象的引用,存放在棧內存中,隨着方法或代碼塊的結束而消亡。
- 構造器
- 構造器名與類名相同,不能顯式指定返回值(隱式返回當前類)。沒有顯式提供構造器時調用系統默認構造器,即Java至少有一個構造器。
- 創建對象的根本途徑就是使用new關鍵字調用類的構造器。
`Person p = new Person();`
p是一個引用變量,只存儲一個地址值,Java程序不允許直接訪問堆內存中的對象,只能通過對象的引用來訪問。`創建對象`的過程如下:
- 調用構造器
- 類准備階段--系統為對象分配內存空間
- 類初始化階段--執行初始化塊,執行默認初始化
- 通過this引用執行構造器內的程序體,進行顯式初始化
- 返回該對象
- 構造器重載
如果一個構造器包含另一個構造器,則可以使用this來調用另一個構造器,方便以后的修改,降低維護成本。
- 初始化塊
如果多個構造器中有相同的初始化代碼,則可以把它們放到普通初始化塊里完成。初始化塊總在構造器執行之前執行。static{}靜態初始化塊初始化類。
- 方法
- Java的方法只能屬於類本身或者類的對象,不能獨立存在。
- Java方法只能使用值傳遞。如果參數是基本類型的值,則會開辟新的棧內存存放臨時變量;如果參數是引用類型的值,也會開辟新的棧內存存放引用變量,但是變量都是指向堆內存的同一對象。
- 參數為數組時可以采用形參個數可變的方法,進而使用foreach循環。
`public void exmple(int a, String... strings){}`
- 方法重載
同一個類相同方法名,參數列表不同即為重載,和其他項無關。
- 類的作用
- 定義變量
- 創建類的對象
- 調用類的方法或訪問類的變量
- this引用
this關鍵字總是指向調用該方法的對象。- 構造器中引用正在初始化的對象
public Person(int age){
this.age = age;
}
- 普通方法中引用調用該方法的對象(可省略this關鍵字)
誰調用set()方法,this代表誰。
public void get(){
}
pubilc void set(){
this.get();
}
- static修飾的方法不能使用this引用,因為如果使用this,則this無法指向合適的對象
抽象類
抽象類作為一種模板,提供通用的方法,是一種更高層次的抽象。
- 抽象方法只有方法簽名,沒有方法實現。有抽象方法的類只能被定義為抽象類,而抽象類里可以沒有抽象方法。抽象類和抽象方法都用abstract修飾。抽象方法和空方法不同。
public abstract void test();
- 抽象類的構造器不能創建實例,僅用於子類調用,子類必須重寫抽象方法。所以final、private和abstract不能同時使用,static和abstract不能同時修飾方法,可同時修飾內部類。
接口
接口作為特殊的抽象類,是一種規范,其不提供任何方法實現。所有方法都是抽象方法,Java8允許定義默認方法(default修飾,可以提供實現)。
- 接口使用interface關鍵字定義。
- 接口只能繼承接口,可以有多個父接口。
- 接口只能包含靜態常量(pubilc static final修飾+指定默認值),只能有抽象方法(默認public abstract,普通方法不能有方法實現,類方法和默認方法必須實現),只能定義內部類/接口/枚舉(默認public static)。
- 訪問控制符只能使用public或者省略。
- 一個類可以實現多個接口,關鍵字用implements,放在extends后面。
- 實現類必須重寫接口所有的抽象方法,必須用public修飾。
比較抽象類和接口
Java8增強的包裝類
- 為了將八種基本數據類型變成引用類型,繼承Object類,Java提供了包裝類,對應關系如下:
- byte - Byte
- short - Short
- int - Integer
- long - Long
- char - Character
- float - Float
- double - Double
- boolean - Boolean
- 自動裝箱和自動拆箱
可以直接將一個基本類型的變量賦值給對應的包裝類變量,或者賦值給Object變量,自動拆箱則相反。 - 字符串轉換成基本類型的值
- 利用除Character類的包裝類提供的靜態方法
int it1 = Integer.parseInt("12345");
- 利用包裝類提供的構造器
float ft1 = new Float("12345");
- 利用除Character類的包裝類提供的靜態方法
- 基本類型的值轉換成字符串
- 利用連接符+
String str1 = 10 + "";
- 利用String類重載的valueOf()方法
String str2 = String.valueOf(true);
- 利用連接符+
- 包裝類的實例可以與基本數據類型的值進行比較
- 自動裝箱緩存問題(見java.lang.Integer類的源代碼)
將-128~127之間的整數自動裝箱成Integer實例時,永遠都是引用cache數組中的同一個數組元素,而在范圍之外的值自動裝箱時,總是會創建新的Integer實例,不相同。
Object類
Java所有的類默認繼承Object父類,因此所有的類都可調用父類的方法。
- 重寫toSpring()方法輸出類的對象的狀態信息
public String toString(){ return "success"; }
- ==和equals方法
- 運算符
如果兩個數值類型(不一定數據類型嚴格對應)相等則返回true;如果兩個引用變量指向同一對象則返回true,不能比較非父子關系的兩個對象。 - 關於常量池
在編譯時被確定並保存在.class文件中的字符串常量和類、接口、方法中的常量保存在常量池中。常量池中的常量是唯一的。new Integer(12)
實際上定義了兩個對象。
- 運算符
class EqualTest{
public void compare() {
String str1 = "123";
String str2 = "123";
String str3 = new String("123");
String str4 = new String("123");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str3 == str4);
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str3.equals(str4));
}
}
輸出true、false、false;true、true、true。
- equals方法
- 如果兩個對象的值相等則返回true。
- String已經重寫了equals方法,對於其他的Object類equals方法等同於==(都是比較對象的地址),如果想自定義可以重寫equals方法。
- equals方法的前一個變量不能為null,如"123".equals(str)
public class Test {
String id = "213";
public String getId() {
return id;
}
public boolean equals(Object object) {
if(this == object)
return true;
if(object != null && object.getClass() == Test.class) {
Test test = (Test)object;
if(this.getId().equals(test.getId())) {
return true;
}
}
return false;
}
}
equals要求兩個對象是同一個類的實例,所以用object.getClass()==Test.class代替instanceof,這里用到了反射基礎。
3. getClass()方法
返回該對象的運行時類。
4. 控制線程的暫停和運行的方法
wait()、notify()、notifyAll()
5. protected修飾的clone()方法
自定義類實現克隆如下:
//自定義類實現Cloneable接口
public class User implements Cloneable{
//自定義實現clone()方法
public User cloneUser() throws CloneNotSupportedException {
//返回對象的副本
return (User)super.clone();
}
}
使用克隆數組比copy方法快。
Objects工具類
提供空指針安全的方法來操作對象。
String、StringBuffer和StringBuilder類
- String類是不可變類,所以會額外產生很多臨時變量
- int length()方法返回字符串長度
- charAt(int index)方法返回指定位置的字符
- int compareTo(String str)方法比較兩個字符串的大小,返回長度差或第一個不相同字符的差
- boolean contentEquals()方法比較兩個字符串是否相同
- byte[] getBytes()方法將字符串轉換成數組
- 。。。
- StringBuffer類和StringBuilder類相似,其對象字符串序列可變,前者線程安全,后者性能略高
- append(String string)方法追加字符串
- insert(int index, String string)方法插入字符串
- reverse()方法反轉字符串
- setCharAt()方法
- setlength()方法設置長度
- replace(left, right, string)替換字符串
- toString()方法將StringBuffer對象轉換為String對象
- 幾個面試題
- 請問String s = new String("hello");創建了幾個對象。
兩個。一個"hello"字符串對象,在方法區的常量池;一個s對象,在棧內存。 - 請寫出下面的結果
String s1 = new String("abc"); String s2 = new String("abc"); String s3 = "abc"; String s4 = "abc"; syso(s1==s2); //false syso(s1==s3); //false syso(s3==s4); //true
- 字符串對象一旦被創建就不能被改變。
指的是字符串常量值不改變。
- 請問String s = new String("hello");創建了幾個對象。
- 正則表達式
Math類
其構造器為private修飾,提供大量類變量(如PI、E)和類方法。
- pow()方法計算乘方
- random()方法返回一個偽隨機數(0.0-1.0)
Random類和ThreadLocalRandom類
- Random類生成的是一種偽隨機數,相同的種子相同的方法會產生相同的隨機數,因此使用當前時間作種子
Random rand = new Random(System.currentTimemillis());
- 使用ThreadLocalRandom在並發環境下具有更好的線程安全性,它通過current()方法生成對象
ThreadLocalRandom rand = ThreadLocalRandom.current();
- 生成指定范圍的隨機數
int tmp = rand.nextInt(5, 10);
BigDecimal類
Java的float和double類型會引起精度丟失,BigDecimal類提供大量構造器創建對象。
- 優先推薦使用參數為String的構造器。
- 如果必須使用浮點數為參數的構造器,需要使用valueOf(double val)靜態方法轉換為BigDecimal對象
BigDecimal f = BigDecimal.valueOf(12.45);
- 基本運算有add()方法、subtract()方法、multiply()方法、divide()方法、pow()方法等
Calendar類
- 抽象類,調用getInstance()靜態方法獲取對象。
Calendar cal = Calendar.getInstance();
- 獲取日期
cal.getTime()
- 修改日歷指定字段的值
cal.set(2013, 12, 20, 06, 30, 42)
- add()
- roll()
- 獲取日期
- java8新增了java.time包,含如下類:
- Clock類可取代System類的currentTimemillis()
- LocalDate類不帶時區的日期,提供靜態now()方法獲取當前時刻
- LocalTime類不帶時區的時間
- DayOfWeek枚舉類定義了周六到周日
- Month枚舉類定義了十二個月
單例類
不允許自由創建該類的實例,只允許該類創建一個實例。
public class Singleton {
//需要被static方法訪問的緩存變量
private static Singleton singleton = null;
//隱藏構造器
private Singleton() {
}
//暴露的方法,因為調用該方法之前還沒有對象,所以要用static修飾
public static Singleton getSingleton() {
if(singleton == null)
singleton = new Singleton();
return singleton;
}
}
final
- final修飾的變量不可被改變,但是final修飾的引用變量所引用的對象的內容可以被改變。final修飾的成員變量必須顯式的指定初始值(否則沒意義,默認初始化)。
- final定義並指定初始值的變量在編譯階段就確定下來,保存在常量池中,程序執行時直接進行宏替換。
- final修飾的方法不能被重寫,比如Object類中的getClass()方法,
- final修飾的類不能被繼承。
不可變類
- Java提供的八個包裝類和java.lang.String類是不可變類,它們的實例變量不可改變。
- 自定義不可變類需要滿足的規則:
- 使用private和final修飾成員變量
- 使用帶參數的構造器來初始化成員變量
- 提供getter方法,不能提供setter方法
- 有必要的話重寫Object類的hashCode()和equals方法
- 保證成員變量引用的對象不可變
- 如果某個不可變實例經常被使用,需考慮對不可變實例進行緩存,減少系統開銷。
System類和Runtime類
系統提供System類和Runtime類與程序的運行平台進行交互。
- System類代表當前Java程序的運行平台,它提供了一些代表標准輸入、標准輸出、錯誤輸出的類變量和用於訪問環境變量、系統屬性的類方法。
比如用long Tnow = System.currentTimeMillis()
返回系統當前時間。 - Runtime類代表Java程序的運行時環境,可以訪問JVM的相關信息。
內部類
Java8新增的lambda表達式
參見Lambda入門
枚舉類
枚舉類是一種特殊的類,使用enum關鍵字, 定義如下:
public enum SeasonEnum {
SPRING, SUMMER, FALL, WINTER;
}
使用如下:
public class EnumTest {
public void getEnum() {
//枚舉類默認的values()方法返回所有實例
for(SeasonEnum s : SeasonEnum.values()) {
System.out.println(s);
}
}
public void select(SeasonEnum s) {
//swich的控制表達式可以是枚舉類型
switch (s) {
//無需添加枚舉類限定
case SPRING:
System.out.println("spring");
break;
case SUMMER:
System.out.println("summer");
break;
case FALL:
System.out.println("fall");
break;
case WINTER:
System.out.println("winter");
break;
}
}
}