一、繼承
當兩個事物之間存在一定的所屬關系,即就像孩子從父母那里得到遺傳基因一樣,當然,java要遺傳的更完美,這兩個類中,一個繼承的類(為子類或者基礎類)可以從被繼承的類(為父類或者超類)中獲得一些屬性和方法,而不必再自己創建新方法(在不需要復寫的情況等)。
1、特點:
1、提高代碼復用性,定義在父類中的成員(變量和方法),可以被子類重復使用;
2、讓類與類之間產生關系,這樣就會有多態的特性。使得應用起來更方便。
需要注意的是:
A.不可以只是為了獲取其他類的功能,簡化代碼就一味的使用繼承;必須是類與類之間有所屬關系,才可以用繼承,這種關系稱為“is-a”。
B.java語言中,只支持單繼承,不支持多繼承。究其原因,是因為多繼承容易帶來安全隱患:
a.當多個父類中定義了相同的功能,且功能內容不同時,子類對象并不確定要運行哪一個,這就造成了程序的混亂而導致異常出現。
b但是java保留了這種機制,并改良了多繼承為多實現,而且接口就可以多繼承。
2、使用繼承體系中的功能
1、如果使用體系,要先對體系中父類的描述進行參閱,了解父類中的共性功能,即可以使用該體系了。這樣既可以減少查閱時間,也能更系統的了解體系結構,以及父類的功能。
2、具體調用父類中的功能的時候,需要創建子類對象,通過子類對象對父類方法調用,實現繼承。
為何要通過創建子類對象調用父類中的方法呢?原因如下:
a.父類很可能不可以創建對象,如父類是一個抽象類或者接口,這就需要子類創建對象,調用方法。
b.創建子類對象,可以使用更多的功能,如父類公有的,和子類中自定義的特有功能。
3、子父類出現后,類成員的特點:
類成員:變量、函數、構造函數
1、變量:
子父類中出現非私有的同名成員變量是,子類訪問本類的同名時,要用this關鍵字(如果省略了this,仍是代表本類的變量);子類訪問父類同名變量時,用super關鍵字。
補充:this和super兩者都存在與方法區中。
this:本類對象引用,那個對象調用this所在的函數,this就代表那個對象。
super:父類對象引用,用于子類初始化父類構造函數時等。
2、.函數:(覆蓋(重寫))
當子類同父類出現相同函數時,子類對象調用該函數時,會運行子類內容,如同覆蓋谷類函數一樣。實際上并沒有覆蓋父類的函數,如果還需要調用父類的這個方法,需要用super.方法名即可。
舉例:
class Father
{
int num = 3;
void show()
{
System.out.println(num);
}
}
class Son extends Father
{
int num = 5;
//復寫父類函數
void show()
{
System.out.println(num);//this引用,此處省略this,結果是5
System.out.println(super.num);//super引用,結果是3.
}
}
class Demo
{
public static void main(String [] args)
{
Son s = new Son();
s.show();
}
}
在內存中的加載方式:
Son中的對象加載之后,才會出現this和super。
應用:
a.當子類繼承父類時,就沿襲了父類的功能,在子類中,雖然具備該功能,,但是其內容卻與父類不同,此時,可覆蓋父類的方法(即保留),使用新特性。
注意:
第一、子類權限必須大于或等于父類的才能覆蓋;
第二、靜態只能覆蓋靜態。
b.用于擴展(新功能或內容等)
示例如下:
class Father
{
void show()
{
System.out.println("Wathing TV");
}
}
class Son extends Father
{
//復寫父類函數
void show()
{
System.out.println("Wathing TV");//父類中的功能
System.out.println("Playing PC");//擴展的功能
}
}
class Demo
{
public static void main(String [] args)
{
Son s = new Son();
s.show();
}
}
3、子父類構造函數:子類的構造函數中有一個隱式的構造函數
特點:在對子類對象進行初始化時,父類構造函數也在運行,而且是在子類之前運行。因為子類構造函數默認第一行有一條隱式語句:super();(沒有顯式的繼承父類的類中,是默認繼承Object的,所以構造函數第一條語句仍是super();),因此會首先訪問父類中空參數的構造函數(在不手動加入含有參數的super的語句的情況下),這是系統默認的。所有的構造函數均是如此。
需要注意的是:
a.如果父類中無空參數構造函數(即父類中顯式的構造了含有參數的構造函數),必須手動加入父類中該構造函數。
b.構造函數不存在覆蓋,子類中的構造函數必定至少有一個會訪問父類中的構造函數。
舉例:
為什么子類一定要訪問父類的構造函數呢?
第一、由于父類中的數據可直接獲取,所以子類對象建立時,需要先查看父類是如何對這些數據進行初始化的。因此,子類在初始化的時候須先訪問父類構造函數,如果父類對這些數據初始化后,可以使用,子類就無需在進行多余的操作了。
第二、如果要訪問指定的構造函數,可手動定義super語句的方式來指定特定的構造函數。
注意:
super語句一定要定義在子類構造函數的第一條語句,即第一行,因為要先初始化父類構造函數,然后再初始化子類構造函數,子類構造函數判斷是否父類的可用,不可用則子類將其覆蓋掉,這樣子類在可以方便選擇。
構造函數中,this和super語句都必須在第一語句出現,因為兩者都是用來初始化的,既然是初始化,就要首先存在,就只能進行一次,因此不能同時作為第一條語句出現。
總結:子類實例化過程
1、子類所有的構造函數默認均訪問父類中的空參數構造函數,因為子類每個構造函數內的第一行均有一句隱式的super();語句。
2、當父類中悟空參數構造函數時,子類必須手動通過super語句的形式指定要訪問的父類中的構造函數
3、子類構造函數也可手動指定this語句,來訪問本類中的構造函數,但子類中至少要有一個構造函數訪問父類的構造函數。
還有一個問題,就是繼承是對封裝性的一個挑戰,這個問題是用final解決的。具體關于final在此不再贅述了。
二、內部類
先來看一段簡單的代碼:
//情況一:創建兩個常規類A和B
class A{
int a = 5;
void fun(){
C c = new C();
c.method();
System.out.println("A:" + a);
}
//情況二:將C類放入A類中
class C{
void method(){
System.out.println("C訪問A:" + a);//可直接訪問
}
}
}
class B{
void fu(){
int a = 6;
A me = new A();
System.out.println("訪問A中a:" + me.a);//必須建立對象后才能訪問
}
}
class Demo
{
public static void main(String[] args)
{
A x = new A();
B y = new B();
x.fun();
y.fu();
}
}
從代碼中就可以看出,內部類的好處。
內部類:將一個類定義在另一個類之中。
1、訪問規則(即使用內部類的好處):
1、內部類方法可以訪問該類所在的類中作用域的成員,包括私有成員變量。
2、內部類可以對同一個包中的其他類隱藏起來,從而不被訪問
3、使用匿名內部類是比較便捷簡單的,這種情況較多的用于AWT設計中。
注:
為何內部類可直接訪問外部類:
由于成員可被對象訪問,內部類中持有了一個外部類的引用,,因此就可以直接用:外部類名.this.成員
2、訪問格式:
1當內部類定義在外部類的成員位置上,而且非私有,可在外部其他類中直接建立內部類對象
格式:外部類名.內部類名 變量名 = 外部類對象.內部類對象
2、當內部類在成員變量的位置上,可被成員修飾符修飾
·private:將內部類在外部類中進行封裝
·static:內部類具備了static的特性,當內部類被static修飾(下面說到的靜態內部類),只可訪問外部類中的static的成員,訪問則受限。
注:(Outer為外部類名,Inner為內部類名,function為非靜態成員)
在其他類中,如何訪問靜態內部類中非靜態成員:new Outer.Inner.function();(建立對象訪問)
在其他類中,如何訪問靜態內部類中的靜態成員:Outer.Inner.function();(可直接訪問)
注意:
1、當內部類中定義了靜態成員,該內部類必須為靜態的
2、當外部類中靜態方法訪問內部類時,內部類必須為靜態的
3、內部類一般定義為private的,而很少定義為public的。
3、內部類定義原則:------->多在程序設計中使用
1、當描述事物時,事物的內部還有事物,則該事物使用內部類來描述,因為內部事物要使用外部事物內容
舉例來說:
樓房內部還有家庭住戶,家庭住戶中還有房間,這就可以用內部類描述。(總不能訪問一個住戶,就建立一個樓房對象吧,那就可真是有錢人的風范了。)
2、何時定義內部類:
當一個類需要直接訪問另一個類中的成員時,則當這個類放入另一個類中,并將內部類封裝。
3、內部類生成文件:
我們在編譯一個源文件是,如果代碼中有內部類,你會發現,生成的class文件中含有例如這樣的文件:A(美元符號)分隔外部類名和內部類名的常規類文件,而虛擬機對此卻一無所知。
4、局部內部類(也稱局部類,方法中)
看下面一段代碼:
class A{
private int a = 5;
void fun(){
class B{//在方法中創建一個內部類
void method(){
System.out.println("C訪問A:" + a);//可直接訪問
}
}
B c = new B();//建立一個對象
c.method();
}
}
class Demo
{
public static void main(String[] args)
{
A x = new A();
x.fun();
}
}
局部內部類:當內部類只在外部類中的某個方法中,創建了這個類型的對象時,且僅使用了一次,那么可在這個方法中定義局部類。
注:
1、局部內部類不可用public或者private訪問修飾符聲明,它的作用域被限定在了聲明這個局部類的代碼塊中
2、局部類的優勢:
a.對外界完全隱藏,即使此方法所在的類也不可訪問,也就是說,除此方法外,無任何方法知道它的存在。
b.可訪問包含他們的外部類,因還持有外部類的引用;還可訪問局部變量,但是局部變量必須被聲明為final。
需要注意:局部內部類不可被成員修飾符修飾,如static
代碼如下:
/*
局部內部類測試:
*/
class Outer//類不能用private,但是內部類是可以的
{
int x = 3;
private static class InnerOuter//可在外部類的任意有效的地方,可以用private和static
{ //......
}
//含局部變量時
void method(final int a)//需訪問加final
{
final int y = 4;//y需要被聲明為最終類型,即final才可被局部內部類中的成員訪問
class Inner//在方法中,相當于局部成員,不能為static或private
{
void fun()//不能定義靜態成員(變量和方法)
{
System.out.println("訪問外部類成員變量:+ Outer.this :" + Outer.this.x);
System.out.println("局部內部類訪問本地變量定義為final:" + y);
System.out.println("方法中的參數需定義為final:" + a);
}
}
new Inner().fun();
}
}
class InnerClassDemo0
{
public static void main(String[] args)
{
Outer ou = new Outer();
ou.method(7);//打印3、4、7
ou.method(8);//打印3、4、8
}
}
- 說明:
在method
方法中聲明了Inner
的內部類,同時也產生了Inner
的內部類實例化對象,并調用其內部的方法。
method
方法參數a
必須定義為final
,在傳入7后,運行method
方法結束后,在棧內存中局部變量a
就會隨即消亡,然后就可以再傳入8,運行下一個method方法。
5、匿名內部類:
1、匿名內部類:就是內部類的一種簡寫格式。
當只創建該類的一個對象,可不用再為其命名。所以稱之為匿名內部類。
代碼示例:
class Outer2
{
int x = 3;
public void fun()
{
/*
其中Inner()被改寫為AbsDemo(){} (其實就是對父類的復寫)
*/
new AbsDemo()//看這里
{
void show()
{
System.out.println("匿名show:" + x);
}
}.show();
}
}
2、定義前提:
內部類必須繼承一個類或實現接口。
但是有一種很特殊,可以不直接繼承一個父類,仍可定義一個匿名內部類。因為任何類都是Object的子類。
如:
new Object()
{
類中的內容
fun()方法
}.fun();
3、格式:
new 父類或接口(參數){定義子類的內容};
4、要點說明:
A.其實匿名內部類就是一個匿名子類對象,可以理解為帶有內容的對象。
B.匿名內部類中的方法最好少于3個,方法少,比較方便簡單,匿名內部類一定要簡化,否則就違背了初衷。
6、靜態內部類
1、概述:
上面提到當內部類在成員變量的位置上,可被成員修飾符static修飾,這就是靜態內部類
2、使用前提:
某些情況下,會把內部類作為一個隱藏的類,不需要使用內部類引用外部類的對象。因此,可以將外部類聲明為static,就可以消除產生的引用。在內部類不需要訪問外部類對象的時候,應該使用靜態內部類。
下面是示例:
/*
靜態內部類測試:最大值最小值
*/
class ArrayAlg
{
public static class Pair
{
private double first;
private double second;
//構造函數獲得兩個值
public Pair(double first,double second)
{
this.first = first;
this.second = second;
}
//訪問器:訪問私有變量first和second
public double getFirst()
{
return first;
}
public double getSecond()
{
return second;
}
}
//定義一個獲得最大值和最小值的功能
public static Pair minmax(double[] arr)
{
//min和max要分別設置最大和最小,然后才能和數組中的數比較
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
for (int i=0;i<arr.length;i++)
{
if (min > arr[i])
min = arr[i];
if (max < arr[i])
max = arr[i];
}
return new Pair(min,max);
}
}
class ArrayZDemo
{
public static void main(String[] args)
{
double[] d = new double[20];
for (int i=0;i<d.length;i++)
d[i] = 100*Math.random();
ArrayAlg.Pair p = ArrayAlg.minmax(d);
System.out.println("min = " + p.getFirst());
System.out.println("max = " + p.getSecond());
}
}
練習:
interface Inter
{
void method();
}
class Test
{
//補足代碼,用匿名內部類
}
class NInnerClassTest
{
public static void main(String[] args)
{
Test.function().method();
}
}
對于Test.function().method();的理解:
Test.function():類中有一個靜態的方法function。
method():function這個方法運算后的結果是一個對象,而且是一個Inter類型的對象。
--->因為只有是Inter類型的對象,才可以調用method方法。
結果:
interface Inter
{
void method();
}
class Test
{
public static Inter function()
{
return new Inter()
{
public void method()
{
System.out.println("使用靜態");
}
};
}
}
//測試結果
class NInnerClassTest
{
public static void main(String[] args)
{
Test.function().method();
}
}
匿名內部類的應用:
class NInnerClassTest
{
public static void main(String[] args)
{
show(new Inter()
{
public void method()
{
System.out.println("method show run");
}
}
public static void show(Inter a)
{
a.method();
}
}
三、多態
多態:可理解為事物存在的多種體現形態。又稱為動態綁定,是java的核心機制之一。
理解:多態是在運行期間,判斷引用實際類型,根據實際類型調用相應的方法。
比如說人又男女之分,動物有貓、狗等之分
1、多態的體現形式:
1、父類的引用指向了自己子類的對象。
2、父類的引用可接收子類的對象。
比如說Person p = new Student();
中p指向了Student中的一個對象。
如圖:
2、多態的前提:
1、必須是類與類之間的關系,如繼承和實現關系
2、要存在覆蓋的操作,父類中必須由方法被子類覆蓋,即重寫
3、有父類引用指向子類對象
3、多態的利弊:
1、好處:大大提高了程序的擴展性
2、弊端:雖然提高擴展性,但是只能使用父類的引用訪問父類中的成員,不可預先使用子類。這是由于,子類在父類之后加載,此時還沒有子類被加載。
在下面的代碼中,Animal a = new Cat();是一種“類型提升,向上轉型”的過程。如果要實現Cat中的其他Animal沒有的功能,那么就需要強制將父類的引用轉換成子類類型,然后再調用子類的方法:
Cat c = (Cat) a;
c.catchM();
注意:
1、一定不能將父類的對象轉換成子類類型。
2、可轉換的:父類引用指向自己子類的對象時,該引用可被提升,也可被強制轉化。
3、多態自始至終均為子類對象在做變化。
例如:
/**
分析:
1、定義一個動物類這個超類類型(抽象),含有一個吃飯的抽象方法
2、定義子類(如貓,狗等),含有自己的方法(貓:抓老鼠;狗:看家)
3、用多態實現子類方法
*/
abstract class Animal //抽象類
{
abstract void eat();
}
class Cat extends Animal //Cat繼承父類Animal
{
//復寫父類的抽象方法
void eat(){
System.out.println("吃魚");
}
//定義自身功能
void catchM(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal
{
//復寫父類的抽象方法
void eat(){
System.out.println("啃骨頭");
}
//定義自身功能
void kanJ(){
System.out.println("看家");
}
}
class DuoTaiTest
{
public static void main(String[] args)
{
Animal a1 = new Cat();//多態運用a2.eat();
Dosome(a1);
Cat c = (Cat)a1;
Dosome(c);
c.catchM();
Animal a2 = new Dog();
a2.eat();
Dosome(a2);
Dog d = (Dog)a2;
Dosome(d);
d.kanJ();
}
public static void Dosome(Animal a)
{
a.eat();
}
}
4、多態的特點:
1、在多態中成員函數的特點:
·編譯時期:參閱引用型變量所屬的類中,是否有調用的方法,如果有,則編譯通過,若沒有,則編譯失敗。如上面的代碼中,引用型變量a是父類Animal的類型,其中有eat 的方法,所以沒問題,但是如果調用a.catchM()
就會編譯失敗。
·運行時期:參與對象所屬的類中,是否有調用的方法。如引用型變量c是所屬于Cat類型的,可以調用c.catchM()
總結:成員函數在多態調用時,編譯看左邊,運行看右邊。子類中局部有變量就訪問局部的,沒有就訪問成員的變量,成員中沒有的就在父類中找;如果父類中沒有,編譯失敗。
2、在多態中成員變量(和靜態成員)的特點:
·無論編譯和運行,都參考左邊的,即引用型變量所屬的類型。也就是說父類中有自己的變量,則先找父類自己的成員。
·非靜態有重寫的特點,靜態一般不被重寫。
原因:因為當調用靜態方法時,只要建立子類對象,父類與子類中的靜態方法都會隨之加載入內存,是不需要調用就可直接用類名.方法名的,而不需要對象。只要引用還存在,就看引用變量的類型。
class Father
{
int num = 3;
void method1()
{
System.out.println("father-1");
}
void method2()
{
System.out.println("father-2");
}
static void method4()
{
System.out.println("father-4");
}
}
class Son extends Father
{
int num = 5;
void method1()
{
System.out.println("son-1");
}
void method3()
{
System.out.println("son-3");
}
static void method4()
{
System.out.println("son-4");
}
}
class DuotaiYYDemo
{
public static void main(String[] args)
{
Father f = new Son();
Son s = new Son();
f.method1();//son-1
s.method1();//son-1
f.method2();//father-2
s.method3();//son-3
//f.method3();報錯
f.method4();//father-4
s.method4();//son-4
System.out.println(f.num);//3
System.out.println(s.num);//5
}
}
注:
如果在每次調用方法時都要進行搜索的話,效率是非常低的。因此,JVM虛擬機會預先為每個類在方法區創建一個方法表,列出了所有方法的參數和實際調用的方法。因此,在真正調用的時候,虛擬機只查找這個方法表就可行了。而且,方法區中會有靜態方法區和非靜態方法區之分(如圖)。其中像this和super這兩個關鍵字存在于非靜態方法區中,而被static修飾的,存在于靜態方法區中。
5、多態的應用
1、定義好工具類,即將共同行為封裝在一個類中。
2、對類型進行抽取,---->多態的產生。
3、操作同一個大類型,對其中的小類型均可操作
四、包
1、包概述:
java中的包就相當于系統中的文件夾。
當我們需要將各個類分門別類的存放的時候,或者含有不同功能的相同類名的時候,需要用到包(package)。包可以將相關的類放在一起,并可以將兩個或多個相同文件名的文件放在不同包中。
2、包的作用:
1、為避免多個類重名的情況,如果出現兩個相同名字的類,可通過包將兩者區分,從而避免沖突
2、對類文件進行分類管理,可以將相關的一些類放在同一個包中。
3、給類提供多層命名空間,如a包中的Demo.class文件即packagea.Demo.class
3、規則:
1、寫在程序的第一行,因為是包下的類,先有包了,才能將類放進去。
2、類名的全稱:包名.類名
3、包是一種封裝形式,是java封裝性的體現。
4、包與包之間的訪問:
1、要訪問其他包中的類,需要定義類名的全名:包名.類名
2、如果包不再當前目錄下,需要設置classpath,告知虛擬機包的執行路徑
3、有了包,范圍變大,一個包中的類要被訪問,必須有足夠大的權限。
注:
a.包與包間進行訪問,被訪問的包中的類以及類中的成員,需要public修飾。
b.不同包中的子類還可直接訪問父類中被protected權限修飾的成員。包與包間可使用的權限有兩種:public和protected。
c.其中protected只用于覆蓋。
示例:
package cn.conpany.test;
public class FatherDemo {
//父類受保護方法
protected void show(){
System.out.println("protected father");
}
//父類公有方法
public void fuc(){
System.out.println("public father");
}
}
package cn.conpany.test.array;
public class SonDemo extends cn.conpany.test.FatherDemo{
public void showSon(){
//自定義方法
System.out.println("son show");
//調用父類方法
show();
}
}
package cn.conpany.test.object;
import cn.conpany.test.FatherDemo;
import cn.conpany.test.array.SonDemo;
public class PackageDemo {
public static void main(String[] args) {
//創建子父類對象
FatherDemo f = new FatherDemo();
SonDemo s = new SonDemo();
//類PackageDemo可訪問其他類公有方法
f.fuc();//public father
//訪問其他類受保護方法,不可
//f.show();//報錯
s.fuc();//public father
//子類可訪問父類受保護方法
s.showSon();/*son show
protected father*/
}
}
5、包的導入:
1、為了簡化類名的書寫,使用import導入
如:import packa.packb.packc.*; 是將包packa下的包packb下的包packc下的所有類導入進來。
注:如果需要packb下還有類需要導入,則還需在導入,
如: import packa.packb.*;
2、注意事項:
a.兩個包中有相同類名文件,當再統一類中創建對象時,需要加上包名.類名創建。
b.建議定義包名不要重復,可以使用URL來定義,因為URL是唯一的。
如:
www.itcast.com---->package cn.itcast.Demo以及cn.itcast.Test
Java中各個主要包的作用(javax開頭的都是擴展包)
java.util是JAVA的utility工具包,包含一些使用工具類,如定義系統特性、使用與日期日歷相關的函數等的類
java.lang是JAVA的language核心語言包;如String、Math、Integer、System、Thread,提供常用的功能。特殊之處是不需要導入,是作為默認導入的包。
java.awt是JAVA的abstractwindow toolkit,抽象窗口工具包;包含了構成抽象窗口共具體的多個類,這些類用于構建和管理應用程序的圖形用戶(GUI)。
java.applet是創建APPLET的必須包;包含applet運行時所需要的一些類。
java.net是JAVA有關網絡操作的包。
java.io是JAVA的輸入輸出流的包。
java.sql是JAVA的數據庫操作包。
javax.swing是新的界面包。