一、單例設計模式
1、什麼是單例設計模式?
對於單例模式(Singleton Pattern)是一個比較簡單的模式,他的定義如下:
Ensure a class has only one instance,and provide a global point of access to it.
意思是確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。
好處:保證對象的唯一性
使用場景:比如多個程序都要使用一個配置文件中的數據,而且要實現數據共享和交換。必須要將多個數據封裝到一個對象中。而且多個程序操作的是同一個對象。那也就是說必須保證這個配置文件對象的唯一性。
如何能保證對象的唯一性?
一個類只要提供了構造方法,就可以產生多個對象。完全無法保證唯一。既然數量不可 控,乾脆不讓其他程序建立對象。
不讓其他程序創建,對象何在?
自己在本類中創建一個對象,好處,對象可控。
創建完成後,是不是要給其他程序提供訪問的方式?
怎麼實現這個步驟?
怎麼就能不讓其他程序創建對象呢?
直接私有化構造方法,不讓其他程序創建的對象存在。
直接在本類中new一個本類對象
定義一個功能,其他程序可以通過這個功能獲取到本類對象。
2、單例模式分成兩種:懶漢式、餓漢式
3、下面就通過代碼來看看這三種單例模式的區別
餓漢式:
//餓漢式
class Singleton_1{
private static Singleton_1 s = new Singleton_1();
private Singleton_1(){}
public static Singleton_1 getInstance(){
return s;
}
}
懶漢式:(容易引發線程安全問題)
//懶漢式(單例模式的延遲加載方式),面試最多的是懶漢式
class Singleton_2{
private static Singleton_2 s = null;
private Singleton_2(){}
public static Singleton_2 getInstance(){
if(s == null){
s = new Singleton_2();
}
return s;
}
}
二、繼承
1、假設有兩個類,一個類是Student類,另外一類是Worker類,兩個類中都有屬性name和age,那麼既然有共同的屬性,我們能不能對他們進行抽取呢?
答案:可以,可以將兩個類中的name屬性和age屬性都抽取取來,放到另外一個類,這個類就是Person,因爲學生是人,工人也是人,所以就抽取出來再建立Person類。
繼承的關鍵字:extends
代碼體現:
package day08.itcast01;
public class ExtendsDemo {
public static void main(String[] args) {
Student s = new Student();
s.name = "林青霞";
s.age = 16;
s.show();
Worker w = new Worker();
w.name = "小二";
w.age = 28;
w.show();
}
}
class Student extends Person{//定義一個Student類,並提供一個成員方法
public void study(){
System.out.println("我在學習");
}
}
class Worker extends Person{ //定義一個Worker類,並提供一個成員方法
public void work(){
System.out.println("我在工作");
}
}
class Person{ //定義一個Person類,由Student類和Worker類抽取而來,是Worker類和Student類的父類(基類、超類)
String name;
int age;
public void show(){
System.out.println(name+"---"+age);
}
}
繼承的好處:提高了代碼的複用性,爲面向對象另一個特徵多態提供了前提條件
什麼時候定義繼承?
必須保證類與類之間的所屬(is a)關係,XX是XXX的一種
比如:狗是動物的一種,學生是人的一種
Java當中允許單繼承,不允許多繼承
單繼承:一個子類只會有一個父類
多繼承:一個子類會有多個父類
2、繼承中子父類成員的特點:
1、成員變量
特殊情況:當子類和父類定義了同名的成員變量的時候,如何在子類中訪問父類中的變量?
通過關鍵字super來完成
super的用法與this的用法類似,
this代表的是本類對象的引用。
super代表的是父類的內存空間
2、成員方法
當子類和父類定義了一模一樣的的方法時,當子類調用該方法時,運行的是子類中的方法
這種情況在子父類中被稱之爲方法的重寫。
何時需要重寫方法?
當子類的方法有自己的特有的功能的時候就需要重寫。
子類重寫父類的方法必須保證權限要大於或者等於父類的權限
靜態只能覆蓋靜態的
寫法上需要注意的:必須一模一樣,方法的返回值類型 方法名 參數列表都要一樣。
代碼體現:
package day09.itcast01;
public class ExtendsDemo1 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.age = 15;
zi.des();
}
}
class Fu{
int age;
public void des(){
System.out.println("父類的des方法"+"---父類的成員變量---"+age);
}
}
class Zi extends Fu{
int age;
public void des(){
super.age = 10;
super.des();
System.out.println("子類的des方法"+"---子類的成員變量---"+age);
}
}
3、子父類中構造方法的的特點
package day09.itcast02;
public class ExtendsDemo2 {
public static void main(String[] args) {
Son son = new Son();
}
}
class Father{
Father(){
System.out.println("Father is running");
}
}
class Son extends Father{
Son(){
System.out.println("Son is running");
}
}
分析運行結果:
因爲在子類的所有構造方法中的第一行都默認有一個super();它會調用父類的構造方法
爲什麼會有super();呢?
因爲在子類進行初始化的時候,先要對父類進行初始化,只有對父類進行初始化完成後,才能使用父類的一些方法
當父類沒有空參的構造方法時,需要使用super關鍵字去調用相應的構造方法
如果在子類的第一行使用了this調用本類的其他構造方法,還會有super();嗎?
沒有,因爲this()或者super()只能定義在構造方法的第一行
父類的構造方法中是否有super();
有,因爲所有類的構造方法的第一行都有一個super();此時父類調用的是所有類的父類Object類。
如果默認的隱式super語句沒有對應的構造函數,必須在構造函數中通過this或者super的形式明確調用的構造函數。
三、final關鍵字
繼承的缺點:打破封裝性,如何能保證繼有繼承又不會打破封裝性呢?
就不讓其他類繼承該類,就不會重寫方法。這時就需要用到final關鍵字
final的意思是最終,它用於修飾類,方法或者變量(成員變量、局部變量、靜態變量)
final的特點:
1、final修飾的類是一個最終類,不能再派生子類
如果一個類中的方法部分需要重寫,部分不需要,就對不需要被重寫的方法使用final修飾
2、final修飾的方法是最終方法,該方法不能被重寫
3、final修飾的變量是一個常量,只能被賦值一次
什麼時候需要在程序中定義final常量呢?
當程序中一個數據使用時是固定不變的,這時爲了增加閱讀性,可以給該數據起個名字。
這就是變量,爲了保證這個變量的值不被修改,加上final修飾,這就是一個閱讀性很強的常量。
書寫規範:被final修飾的常量名所有的字母都是大寫,如果該變量名是由多個單詞組成的,每個字母都大寫,並且單詞之間使用"_"連接。
四、抽象類
對與狗和狼這兩種動物他們都有一個吼叫的行爲,而且他們還屬於動物,對他們的共性進行向上抽取,可以使用繼承,但是狗和狼吼叫的行爲又不同。這時使用繼承就顯得不合適了,這時就需要使用另外一個關鍵字abstract(抽象的)對父類進行修飾。
抽象類的特點:抽象類和抽象方法都需要使用abstract修飾,抽象方法一定要定義抽象類中,抽象類中的方法不一定都是抽象方法。
只有覆蓋了抽象類中的所有抽象方法後,其子類纔可以實例化。否則該子類還是一個抽象類。
抽象類要實例化的話需要通過子類對父類進行實例化。(多態)
細節:
抽象類一定是一個父類
是的,因爲抽象類就是子類的功能不斷抽取出來的
抽象類中是否有構造方法?
有,不能給自己的對象實例化,可以給子類的對象進行初始化。
抽象類和普通類的異同點?
相同:它們都是用來描述事物的,它們之間都可以定義屬性和行爲
不同:一般類可以具體的描述事物,抽象類描述的事物信息不具體
抽象類可以多定義一個成員:抽象方法。
一般類可以創建對象,而抽象類不能創建對象。
抽象類中是否可以定義普通方法?
可以,如果抽象類中定義了普通方法,那麼其抽象類的作用就是不能對該類進行實例化。
抽象關鍵字abstract不能與哪些關鍵字共存?
final
private
static
代碼體現:
package day09;
public class AbstractDemo {
public static void main(String[] args) {
Dog d = new Dog();
d.show();
d.speak();
}
}
abstract class Animal{
public void show(){
System.out.println("Animal");
}
public abstract void speak();
}
class Dog extends Animal{
public void speak(){
System.out.println("小狗叫");
}
}
案例:
需求:公司中程序員有姓名,工號,薪水,工作內容。
項目經理除了有姓名,工號,薪水,還有獎金,工作內容。
分析:程序員和項目經歷都屬於公司裏的員工,而且他們都有共性:姓名、工號、薪水以及工作內容
package day09.itcast02;
public class AbstractDemo {
public static void main(String[] args) {
Programer p = new Programer("張三","448",5000);
Manager m = new Manager("李四","500",5000,2500);
p.content();
p.show();
m.content();
m.show();
}
}
abstract class Employee{
String name;
String number;
int salary;
public Employee(String name,String number,int salary){
this.name = name;
this.number = number;
this.salary = salary;
}
public abstract void content();
public abstract void show();
}
class Programer extends Employee{
public Programer(String name,String number,int salary){
super(name,number,salary);
}
public void content(){
System.out.println("敲代碼");
}
public void show(){
System.out.println(name+"---"+number+"---"+salary);
}
}
class Manager extends Employee{
int pay;//獎金
public Manager(String name,String number,int salary,int pay){
super(name,number,salary);
this.pay = pay;
}
public void content(){
System.out.println("寫規劃");
}
public void show(){
System.out.println(name+"---"+number+"---"+salary+"---"+pay);
}
}
五、接口
當一個抽象類中的方法都是抽象方法的時候,這個抽象類就有另外一個表現方式,叫做接口(interface)
定義接口使用關鍵字interface,格式:interface 接口名{}
接口中的成員已經被限定爲固定的幾種。
接口中的定義格式(兩種)
1、定義變量,但是變量必須有固定的修飾,public static final,所以接口中的變量也被稱之爲常量。
2、定義方法,方法也有固定的修飾符,public abstract
接口中的成員都是公共的。
特點:
接口不可以創建對象
子類必須覆蓋掉接口中的所有抽象方法後,子類纔可以被實例化。否則子類是一個抽象類。
定義接口的子類,類與類之間的關係是繼承,而子類與接口之間的關係是實現(implements)。
格式:
interface Animal{ // 定義一個動物接口
}
class Dog implements Animal{ //定義犬類實現了動物的接口
}
接口解決了多繼承中調用不明確的弊端,講多繼承機制在java中通過多實現完成了
接口的出現避免了單繼承的侷限性
父類中定義事物的基本功能。
接口中定義事物的擴張功能。
類與類之間是繼承(is a)關係,類與接口之間是實現(like a)關係.
接口與接口之間是繼承關係,而且可以多繼承。
抽象類和接口的區別:
接口中定義的方法都是抽象方法,不允許定義普通方法。
抽象類中可以允許有普通方法,但是抽象方法一定定義在抽象類中。
接口的思想:
1、對功能實現了擴展。
2、定義了規則。
3、降低了耦合性。
三、多態
什麼是多態?
舉例子:學生和工人,但是他們都是人中的某一類,這就是多態。多態就是一種事物的不同體現。
多態的體現:
父類的引用或者接口的引用指向了自己的子類對象。
好處:提高了程序的擴展性。
弊端:通過父類的引用操作子類對象時,只能使用父類中已有的方法,不能操作子類特有的方法
多態的前提:繼承或者實現。
多態通常都有重寫操作。
代碼體現:
package day10.itcast01;
public class DuotaiDemo1 {
public static void main(String[] args) {
Person p = new Student();
p.eat(p);
Person p1 = new Worker();
p1.eat(p1);
}
}
abstract class Person{
public void eat(Person p){
if(p instanceof Student ){
System.out.println("喫飯");
p.method();
}else if( p instanceof Worker){
System.out.println("喫飯");
p.method();
}
}
public abstract void method();
}
class Student extends Person{
public void method(){
System.out.println("學習");
}
}
class Worker extends Person{
public void method(){
System.out.println("工作");
}
}
2、多態調用子類的特有方法
Person p = new Studnet();
或者Person p = new Worker();
父類引用指向了子類的對象,這是讓子類對象進行類型的提升(向上轉型)
好處:提高了擴展性,隱藏了子類,弊端:不能使用子類的特有方法。
如果想要使用子類的特有方法,只有子類的對象才能使用,這時就需要使用向下轉型。
向下轉型屬於強制類型轉換,向上轉型是自動類型轉換。
package day10.itcast01;
public class DuotaiDemo1 {
public static void main(String[] args) {
Person p = new Student();
p.eat(p);
// p.write(); 報錯
((Student)p).writer();
Person p1 = new Worker();
p1.eat(p1);
((Worker)p1).work();
}
}
abstract class Person{
public void eat(Person p){
if(p instanceof Student ){
System.out.println("喫飯");
p.method();
}else if( p instanceof Worker){
System.out.println("喫飯");
p.method();
}
}
public abstract void method();
}
class Student extends Person{
public void method(){
System.out.println("學習");
}
public void writer(){
System.out.println("學生的特有方法:寫作業");
}
}
class Worker extends Person{
public void method(){
System.out.println("工作");
}
public void work(){
System.out.println("工人的特有方法:幹活");
}
}
注意:無論是向上轉型還是向下轉型,最終都是子類對象做着類型的變化。
【向下轉型的注意事項】:
Person p = new Studnet();
Worker w = (Worker)p;
以上代碼是不允許出現的,會導致ClassCastException異常
爲了避免出現類轉換異常,在進行類轉換時常使用instanceof關鍵字對對象進行類型判斷
格式:對象名 instanceof 類名
3、子父類成員的調用問題
1、成員變量:當子父類中出現了同名的成員變量時
編譯時期:參考的是引用型變量所屬的類是否有被調用的成員變量,沒有,編譯失敗。
運行時期:參考的是引用型變量所屬類中的成員變量。
口訣:編譯運行看左邊。
2、成員方法:當子父類中出現了同名的成員方法時
編譯時期:參考左邊,如果沒有,編譯失敗
運行時期:參考右邊
口訣:編譯看左邊,運行看右邊。
對於成員方法是動態綁定到對象上。
3、靜態方法
編譯和運行都參考左邊
靜態方法是靜態綁定到類上。
4、題目
看一下代碼分析打印結果(通過畫圖分析)
package day10.itcast02;
public class DuoTaiTest3
{
public static void main(String[] args)
{
Fu f = new Zi();
System.out.println("main :" +f.getNum());
}
}
class Fu
{
int x = 4;
Fu()
{
System.out.println("A fu() : "+getNum());
}
int getNum()
{
System.out.println("B fu getnum run...."+x);
return 100;
}
}
class Zi extends Fu
{
int x = 5;
Zi()
{ //super();默認存在並調用父類的無參構造
System.out.println("C zi() : "+getNum());
}
int getNum()
{
System.out.println("D zi getnum run...."+x);
return 200;
}
}
結果分析:
分析這道題首先從main方法開始,Fu f = new Zi(); 程序首先會調用子類的構造方法,進入到子類的構造方法時,因爲在子類的構造方法中第一行都會有默認的super();調用父類的構造方法,所以程序進入到 父類的構造方法之中,在父類的構造方法中,有一條輸出語句,輸出語句中調用了getNum()方法,那麼這時候調用的是哪個呢?因爲在main方法中,使 用了多態,使父類的對象引用指向了子類,所以調用的是子類的getNum方法,在getNum方法中輸出了一個x,因爲從程序開始一直到現在都還未對x進 行顯示的初始化,所以x的值爲0,所以最想打印的是D zi getnum run....0,接着getNum方法返回了200到父類的構造方法中,所以接着打印了A fu() : 200,當打印了兩條語句之後,又回到了子類的構造方法中,這時子類的構造方法中同樣有一條輸出語句,在語句中調用了getNum()方法,這個 getNum方法同樣是子類中的方法,因爲此時對子類中的x已經完成了顯式初始化,所以打印了D zi getnum run....5,打印完成後又回到了子類的構造方法中執行了輸出語句,此時打印了C zi() : 200,至此Fu f = new Zi()完成了所有的操作,最後mian方法中的輸出語句中同樣調用的是子類中的getNum()方法,所以打印的是main :200
綜合以上的分析,可以得出最後的結果爲:
D zi getnum run....0
A fu() : 200
D zi getnum run....5
C zi() : 200
main :200
六、object類
object是所有的類的父類或者間接父類
在object類中有兩個常用的方法
equlas和toString方法
equals:通常用來比較兩象是個對否相等。
toString:返回對象的字符串表現形式,Object 類的 toString 方法返回一個字符串,該字符串由類名(對象是該類的一個實例)、at 標記符“@”和此對象哈希碼的無符號十六進制表示組成。換句話說,該方法返回一個字符串,它的值等於:
getClass().getName() + '@' + Integer.toHexString(hashCode())
一般來說equals和toString方法都需要被重寫
案例:
package cn.test;
public class EqualsDemo {
public static void main(String[] args) {
Student s1 = new Student("謝羣",18);
Student s2 = new Student("謝羣",22);
boolean result = s1.equals(s2);
System.out.println("result:"+result);
System.out.println(s2.toString());
s2.setAge(18);
result = s1.equals(s2);
System.out.println("result:"+result);
System.out.println(s2.toString());
}
}
class Student{
private String name;
private int age;
public Student(String name, int age){
this.name = name;
this.age = age;
}
public Student(){}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public boolean equals(Object s){
if(s == null){
return false;
}
if(!(s instanceof Student)){
return false;
}
if(s == this){
return true;
}
Student s1 = (Student)s;
return this.name.equals(s1.name) && this.age == s1.age;
}
public String toString(){
return this.name +"----"+this.age;
}
}