2、知識點
2.1、上次課程的主要知識點
1、 String類的特點:
· 兩種初始化方式:
|- 第一種:直接賦值,只開闢一個空間,所有的內容都放在字符串對象池之中,以後如果定義了相同內容,則不會重新開闢新的空間;
|- 第二種:通過關鍵字new,開闢兩個空間,使用intern()方法可以讓一個字符串歸入池中;
· 一個字符串就是String的匿名對象;
· 字符串比較有兩種方式:==(比較地址數值)、equals()(比較字符串內容)
· 字符串一旦聲明則不可改變,改變的是內存地址的指向。
2、 String類的方法:charAt()、indexOf()、contains()、compareTo()、replaceAll()、substring()、split()、toUpperCase()、toLowerCase()、toCharArray()、getBytes()、isEmpty()、startsWith()、endsWith()、trim()、length()
3、 面向對象的三大特徵:封裝、繼承、多態;
Java本身是面嚮對象語言,但是最早的面嚮對象語言是smalltalk(IBM),後來纔有了C++。
4、 類與對象的關係:
· 類:對現實世界的一種抽象方法,由屬性及方法所組成;
· 對象:是類的具體應用,類只有通過對象纔可以使用,對象的所有操作行爲由類決定;
· 對象的引用傳遞及內存分配;
5、 封裝性:通過private關鍵字完成,而且也強調了在Java的類中只要是屬性就要求進行封裝,所有封裝的屬性只能通過setter和getter設置和取得;
6、 構造方法:
· 構造方法的名稱要求與類名稱保持一致;
· 構造方法沒有返回值類型的聲明,也不可以使用void;
· 在通過關鍵字new實例化一個類對象的時候,會自動調用類中的構造方法;
· 每個類中至少存在一個構造方法,如果一個類沒有明確的聲明一個構造方法的話,則會自動生成一個無參的,什麼都不做的構造方法;
· 構造方法允許重載,但是在進行構造方法重載的時候建議一定要將參數少的構造方法寫在前面;
7、 this關鍵字:
· this可以調用本類中的屬性,this.屬性,而且也強調過,以後只要是調用屬性永遠加上this;
· this可以調用本類中的方法,包括普通方法及構造方法,但是使用this()調用構造方法的時候必須放在構造方法的首行,而且如果一個類中有多個構造方法的話,則至少有一個構造方法是不使用this()調用的情況;
· 以上的所有特點實際上都可以理解爲:this表示當前對象,當前調用類中方法的對象就是當前對象。
2.2、本次預計講解的知識點
1、 引用傳遞的深入研究;
2、 鏈表的基本實現(難點);
3、 對象數組的基本概念及使用;
4、 static關鍵字的使用。
3、具體內容
3.1、深入引用傳遞(重點)
引用傳遞爲整個JAVA的核心部分,在實際的開發之中肯定要多次使用,而且也作爲重點必須掌握,下面通過一些實際的範例強調一下引用傳遞的使用。
3.1.1、三道引用傳遞分析
爲了更好的說明問題,下面給出三道題目,要求可以獨立的分析出最終的運行結果。
範例:第一道範例
class Demo {
private int count = 10 ;
public void setCount(int count){
this.count = count ;
}
public int getCount(){
return this.count ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Demo d = new Demo() ;
d.setCount(100) ;
fun(d) ;
System.out.println(d.getCount()) ;
}
public static void fun(Demo temp){
temp.setCount(30) ;
}
};
下面通過內存關係圖來分析本程序的運行結果。
範例:第二道範例
public class OODemo { // 第二個類
public static void main(String args[]){
String str = "Hello" ;
fun(str) ;
System.out.println(str) ;
}
public static void fun(String s){
s = "World" ;
}
};
分析的關鍵點:字符串的內容一旦聲明則不可改變,改變的是其內存地址的指向。
因爲字符串的內容不可改變,所以在方法之中修改的“World”根本就不會被保留下來,而原本的“Hello”並沒有任何的變化。
範例:第三道範例
class Demo {
private String info = "Hello" ;
public void setInfo(String info){
this.info = info ;
}
public String getInfo(){
return this.info ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Demo d = new Demo() ;
d.setInfo("World") ;
fun(d) ;
System.out.println(d.getInfo()) ;
}
public static void fun(Demo temp){
temp.setInfo("!!!") ;
}
};
本程序實際上與第一道程序沒有區別,下面同樣通過內存關係圖來劃分。
這三道程序實際上只是分爲兩組:
· 第一道和第三道程序:就好比一個揹包一樣
· 第二道程序:就好比一個直接的饅頭
這三道程序的主要目的是爲了理解字符串及引用傳遞的一個簡單的問題分析,在程序中記住了,如果現在傳的是一個類,則類中的屬性的修改是可以被保留下來的,但是String雖然是一個類,可是在進行引用傳遞的時候最好將其理解爲一個普通的數字。
3.1.2、實際問題
一個人有一輛小汽車,那麼此種關係應該怎樣表示呢?
人應該屬於一個類,車也應該屬於一個類。
class Person {
private String name ;
private int age ;
private Car car ; // 一個人有一輛車
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
public void setCar(Car car){ // 爲一個人配一輛車
this.car = car ;
}
public Car getCar(){ // 通過人可以找到他的車
return this.car ;
}
};
class Car {
private String name ;
private Person person ;
public Car(String name){
this.name = name ;
}
public String getInfo(){
return "車的名字:" + this.name ;
}
public void setPerson(Person person){
this.person = person ;
}
public Person getPerson(){
return this.person ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per = new Person("張三",30) ;
Car car = new Car("BMW 5X") ;
per.setCar(car) ; // 一個人有一輛車
car.setPerson(per) ; // 一輛車屬於一個人
System.out.println(per.getInfo() + per.getCar().getInfo()) ;
}
};
問題:一個人長大之後肯定要有孩子,每個孩子依然還要輛車。
class Person {
private String name ;
private int age ;
private Car car ; // 一個人有一輛車
private Person child ; // 表示一個孩子
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
public void setCar(Car car){ // 爲一個人配一輛車
this.car = car ;
}
public void setChild(Person child){
this.child = child ;
}
public Person getChild(){
return this.child ;
}
public Car getCar(){ // 通過人可以找到他的車
return this.car ;
}
};
class Car {
private String name ;
private Person person ;
public Car(String name){
this.name = name ;
}
public String getInfo(){
return "車的名字:" + this.name ;
}
public void setPerson(Person person){
this.person = person ;
}
public Person getPerson(){
return this.person ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per = new Person("張三",30) ;
Person chd = new Person("張四",18) ;
Car car = new Car("BMW 5X") ;
Car c = new Car("自行車") ;
per.setCar(car) ; // 一個人有一輛車
car.setPerson(per) ; // 一輛車屬於一個人
chd.setCar(c) ; // 一個孩子有一輛車
c.setPerson(chd) ; // 這輛車屬於一個孩子
per.setChild(chd) ; // 一個人有一個孩子
System.out.println(per.getInfo() + per.getCar().getInfo()) ;
// 代碼鏈對象.方法().方法().方法().....
System.out.println(per.getChild().getInfo() + "," + per.getChild().getCar().getInfo()) ;
}
};
掌握了此方法之後,現實世界之中的很多關係都可以表示了:
· 一個人有一間房子;
· 一個人有一臺電腦;
· 一個人有一個原配妻子;
3.2、對象比較(重點)
在JAVA之中,有一種程序是比較難用語言描述出來的概念,在正常情況下,一個在類外部的對象不能操作類本身的私有屬性,但是如果現在將一個類的對象放回到類本身之中,則可以直接操作,但是這種做法只有在一個地方上使用。
範例:接收本類的引用
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
public void set(Person p){
p.name = "李四" ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per = new Person("張三",20) ;
per.set(per) ;
System.out.println(per.getInfo()) ;
}
};
之所以要講本類用法,主要是爲以下的一個要求服務。
範例:現在有兩個Person對象,如何可以判斷這兩個對象是否相等呢?
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
public String getName(){
return this.name ;
}
public int getAge(){
return this.age ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per1 = new Person("張三",20) ;
Person per2 = new Person("張三",20) ;
if(per1.getName().equals(per2.getName()) && per1.getAge()==per2.getAge()){
System.out.println("是同一個人!") ;
} else {
System.out.println("不是同一個人!") ;
}
}
};
現在已經實現了對象的比較操作,但是這種代碼存在問題?
1、 每次的比較都要寫此代碼;
2、 要是屬性多了肯定還得這麼比;
3、 main()方法是一個客戶端代碼,客戶端的操作越少越好;
4、 對於這種比較的操作,應該是類本身所具備的功能,每個對象都應該直接提供此操作,而不應該交給第三方完成功能。
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public boolean compare(Person p){
if(this == p){ // 兩個對象的地址相等
return true ; // 肯定相等
}
if(p == null){
return false ;
}
if(this.name.equals(p.name) && this.age==p.age){
return true ;
} else {
return false ;
}
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
public String getName(){
return this.name ;
}
public int getAge(){
return this.age ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per1 = new Person("張三",20) ;
Person per2 = new Person("張三",20) ;
if(per1.compare(per2)){
System.out.println("是同一個人!") ;
} else {
System.out.println("不是同一個人!") ;
}
}
};
本程序是在開發中唯一接收本類對象有意義的程序,那麼說到這裏,忽然又想到另外一個根本就沒有意義的程序,來分析一下本程序的操作。
class A {
public A(){
new B(this).fun() ;
}
public void print(){
System.out.println("Hello World !!!") ;
}
};
class B {
private A a ;
public B(A a){
this.a = a ;
}
public void fun(){
this.a.print() ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new A() ;
}
};
程序中構造方法的this表示的就是new A() ;這個在主方法中實例化的對象,
3.3、鏈表(理解)
鏈表操作是數據結構的一種體現,也是一種最簡單的數據結構。
鏈表實際上是一種數據的載體體現
如果現在將每一個人當作一個節點來看的話,則每一個節點要保存當前的數據,以及下一個節點。
class Node {
private String data ;
private Node next ;
};
以後如果有數據則直接向data中保存,而且每一個新增加的節點都要放在當前節點之後。
在鏈表操作之中,第一個節點肯定就是根節點。
class Node {
private String data ;
private Node next ;
public Node(String data){
this.data = data ;
}
public void setNext(Node next){
this.next = next ;
}
public String getData(){
return this.data ;
}
public Node getNext(){
return this.next ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Node root = new Node("根節點") ;
Node n1 = new Node("第一個節點") ;
Node n2 = new Node("第二個節點") ;
Node n3 = new Node("第三個節點") ;
root.setNext(n1) ; // 根節點和第一個節點的關係
n1.setNext(n2) ; // 第二個節和第一個節點的關係
n2.setNext(n3) ;
print(root) ; // 從根節點開始輸出
}
public static void print(Node node){
System.out.println(node.getData()) ;
if(node.getNext() != null){ // 還有下一個節點
print(node.getNext()) ; // 繼續輸出下一個節點
}
}
};
以上是一個鏈表實現的基本模型,但是在這個操作模型之中,可以發現所有的內容都是手工設置的,這樣肯定不合適,就好比小孩排隊一樣,當挑選出來一個孩子之後,跟他說,孩子去排隊。
在這種情況下,這種手工完成的鏈表設置實在是太麻煩了,所以應該使用一種自動的方式完成,即:每一個新的數據如果要想保存進來之後,應該有一個操作類,幫助用戶將此新的數據排放到整個鏈表的最後一個位置。
現在增加一個新節點的根本原因在於:當前節點沒有下一個節點的時候,就將新節點保存。
class Node {
private String data ;
private Node next ;
public Node(String data){
this.data = data ;
}
public void setNext(Node next){
this.next = next ;
}
public String getData(){
return this.data ;
}
public Node getNext(){
return this.next ;
}
public void addNode(Node newNode){ // 增加新節點
if(this.next == null){ // 當前節點沒有下一個節點
this.next = newNode ; // 保存新節點
} else {
this.next.addNode(newNode) ;
}
}
public void printNode(){
System.out.println(this.data) ; // 輸出當前節點內容
if(this.next != null){ // 當前節點之後還有節點
this.next.printNode() ;
}
}
};
class Link { // 完成鏈表的操作類
private Node root ; // 表示根節點
public void add(String data){ // 增加數據
Node newNode = new Node(data) ; // 新的節點
if(this.root == null){ // 現在還沒有根節點,則應該將新的節點作爲根節點
this.root = newNode ; // 第一個節點作爲根節點
} else { // 該放到那裏就放到那裏去
this.root.addNode(newNode) ;
}
}
public void print(){
if(this.root != null){ // 這個隊有人了
this.root.printNode() ;
}
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Link link = new Link() ;
link.add("A") ;
link.add("B") ;
link.add("C") ;
link.print() ;
}
};
如果現在需要查詢某個節點是否存在呢?
public boolean search(String data){ // 檢查指定的內容是否存在
if(this.root != null){
return this.root.searchNode(data) ; // 交給Node去做
}
return false ; // 沒有內容,肯定找不到
}
public boolean searchNode(String data){ // 查找數據是否存在
if(data.equals(this.data)){ // 與當前節點數據一樣,表示查找到了
return true ;
} else {
if(this.next != null){ // 當前節點存在下一個節點
return this.next.searchNode(data) ;
}
}
return false ; // 沒有任何一個節點符合要求
}
既然現在已經可以查找了,那麼如果要想刪除一個節點呢?
public void delete(String data){
if(this.search(data)){ // 可以找到,則可以刪除
if(this.root.getData().equals(data)){ // 根節點滿足要求
this.root = this.root.getNext() ; // 根節點的下一個爲新的根節點
} else {
this.root.getNext().deleteNode(this.root,data) ;
}
}
}
public void deleteNode(Node previous ,String data){
if(this.data.equals(data)){
previous.next = this.next ;
} else {
if(this.next != null){ // 還有下一個節點
this.next.deleteNode(this,data) ;
}
}
}
本代碼無論現在隔多少個節點都可以刪除掉,因爲是採用逐步判斷的操作,當然,如果有重複的,每次只能刪除一個。
這種鏈表程序屬於單向鏈表:
class Node {
private String data ;
private Node next ;
public Node(String data){
this.data = data ;
}
public void setNext(Node next){
this.next = next ;
}
public String getData(){
return this.data ;
}
public Node getNext(){
return this.next ;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode ;
} else {
this.next.addNode(newNode) ;
}
}
public void printNode(){
System.out.println(this.data) ;
if(this.next!=null){
this.next.printNode() ;
}
}
public boolean searchNode(String data){ // 查找數據是否存在
if(data.equals(this.data)){ // 與當前節點數據一樣,表示查找到了
return true ;
} else {
if(this.next != null){ // 當前節點存在下一個節點
return this.next.searchNode(data) ;
}
}
return false ; // 沒有任何一個節點符合要求
}
public void deleteNode(Node previous ,String data){
if(this.data.equals(data)){
previous.next = this.next ;
} else {
if(this.next != null){ // 還有下一個節點
this.next.deleteNode(this,data) ;
}
}
}
};
class Link { // 這是一個專門進行鏈表的操作類
private Node root ; // 所有的數據結構前提必須有一個根
public void add(String data){ // 將數據保存
Node newNode = new Node(data) ;
if(this.root == null){
this.root = newNode ;
} else {
this.root.addNode(newNode) ;
}
}
public void print(){
if(this.root != null){
this.root.printNode() ;
}
}
public boolean search(String data){ // 檢查指定的內容是否存在
if(this.root != null){
return this.root.searchNode(data) ; // 交給Node去做
}
return false ; // 沒有內容,肯定找不到
}
public void delete(String data){
if(this.search(data)){ // 可以找到,則可以刪除
if(this.root.getData().equals(data)){ // 根節點滿足要求
this.root = this.root.getNext() ; // 根節點的下一個爲新的根節點
} else {
this.root.getNext().deleteNode(this.root,data) ;
}
}
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Link link = new Link() ;
link.add("A") ;
link.add("B") ;
link.add("C") ;
System.out.println(link.search("B")) ;
System.out.println(link.search("X")) ;
System.out.println(link.search("A")) ;
link.delete("B") ;
link.delete("A") ;
link.delete("C") ;
link.print() ;
}
};
如果現在向完成循環鏈表,則意味着現在所有的數據都將形成一個圈。
class Node {
private String data ;
private Node next ;
private boolean rootFlag = false ;
public Node(String data){
this.data = data ;
}
public void setNext(Node next){
this.next = next ;
}
public void setRootFlag(boolean rootFlag){
this.rootFlag = rootFlag ;
}
public boolean getRootFlag(){
return this.rootFlag ;
}
public String getData(){
return this.data ;
}
public Node getNext(){
return this.next ;
}
public void addNode(Node root,Node newNode){
if(this.next!=null) {
if(this.next.rootFlag == true){ // 當前的下一個是根節點
this.next = newNode ;
newNode = root ;
} else {
this.next.addNode(root,newNode) ;
}
} else {
this.next = newNode ; // 如果下一個爲空,則表示是第一個節點
newNode.next = root ;
}
}
public void printNode(){
System.out.println(this.data) ;
if(this.next != null){
if(this.next.rootFlag == false){ // 不是根
this.next.printNode() ;
}
}
}
};
class Link { // 這是一個專門進行鏈表的操作類
private Node root ; // 所有的數據結構前提必須有一個根
public void add(String data){
Node newNode = new Node(data) ; // 新節點
if(this.root == null){
this.root = newNode ;
this.root.setRootFlag(true) ; // 設置根節點
} else {
this.root.addNode(this.root,newNode) ;
}
}
public void print(){
if(this.root != null){ // 有根節點
this.root.printNode() ;
}
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Link link = new Link() ;
link.add("A") ;
link.add("B") ;
link.add("C") ;
link.add("D") ;
link.print() ;
}
};
還可以實現雙向鏈表,每個節點知道自己的下一個節點,同時也知道自己的上一個節點。
3.4、對象數組(重點)
數組之前已經強調了,是一組相關變量的集合,那麼所謂的對象數組就是數組中的每個元素都是對象,在Java中對象數組的定義格式如下:
類名稱 對象數組名稱[] = new 類名稱[數組長度] ;
表示聲明併爲數組開闢空間,但是這種開闢的語法屬於動態初始化,則數組中的每個元素的內容都是null。
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per[] = new Person[3] ; // 定義數組,一共有三個元素
for(int x=0;x<per.length;x++){
System.out.println(per[x]) ;
}
}
};
現在由於每個元素都沒有進行各自的實例化操作,所以數組中的每個元素都是null,那麼所以對象數組中的每一個元素都必須分別進行實例化操作。
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per[] = new Person[3] ; // 定義數組,一共有三個元素
per[0] = new Person("張三",10) ;
per[1] = new Person("李四",12) ;
per[2] = new Person("王五",13) ;
for(int x=0;x<per.length;x++){
System.out.println(per[x].getInfo()) ;
}
}
};
數組本身是存在靜態和動態初始化的,所以現在也可以通過靜態初始化的方式完成對象數組的定義。
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Person per[] = {new Person("張三",10),
new Person("李四",12),new Person("王五",13)} ;
for(int x=0;x<per.length;x++){
System.out.println(per[x].getInfo()) ;
}
}
};
對象數組本身確實可以保存多個對象,但是其本身的缺點也是有的:就是數組本身永遠會受到長度的限制,所以如果現在希望將代碼的操作更加容易的話,往往可以通過鏈表完成操作。
3.5、JDK 1.5的新功能(理解)
JAVA除了JDK 1.0之外還有兩個重要的版本:JDK 1.2、JDK 1.5(JAVA SE 5.0 Tiger),在JDK 1.5之中增加了許多的新特性,但是這些新特性並沒有被廣泛的應用在現代的開發之中。
3.5.1、可變參數
在正常情況下,一個方法之中規定的參數格式就是在調用此方法時必須傳入的參數個數。
public class OODemo { // 第二個類
public static void main(String args[]){
fun(10,20) ;
}
public static void fun(int x,int y){
System.out.println(x) ;
System.out.println(y) ;
}
};
但是,在JDK 1.5之後,爲了方便用戶的操作,JAVA專門對參數的接收上提供了一種稱爲可變參數的定義格式,在調用方法的時候可以傳遞參數也可以傳遞數組或者是不傳遞參數,那麼這些參數接收的時候實際上都是按照數組的形式接收的。
public class OODemo { // 第二個類
public static void main(String args[]){
System.out.print("沒有參數:") ;
fun() ;
System.out.print("\n有一個參數:") ;
fun(10) ;
System.out.print("\n有多個參數:") ;
fun(10,20,30,40,50,60,70) ;
System.out.print("\n數組:") ;
fun(new int[]{10,20,30}) ;
}
public static void fun(int ... arg){
for(int x=0;x<arg.length;x++){
System.out.print(arg[x] + "、") ;
}
}
};
同樣,既然可以傳遞普通數據,也可以傳遞對象。
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
fun(new Person[]{new Person("張三",20),new Person("李四",20)}) ;
}
public static void fun(Person ... arg){
for(int x=0;x<arg.length;x++){
System.out.println(arg[x].getInfo()) ;
}
}
};
3.5.2、foreach輸出
for循環本身是JAVA所支持的一種循環的輸出,但是在JDK 1.5之後,JAVA向C#學習了foreach輸出的格式,改善了原本的for的輸出形式
public class OODemo { // 第二個類
public static void main(String args[]){
fun(new Person[]{new Person("張三",20),new Person("李四",20)}) ;
}
public static void fun(Person ... arg){
for(Person p : arg){
System.out.println(p.getInfo()) ;
}
}
};
但是,以上的新功能知道即可,別用。
3.6、static關鍵字(重點)
3.6.1、static關鍵字的作用
static關鍵字本身之前已經見過了,使用static可以聲明屬性也可以聲明方法,那麼爲什麼要有static屬性的概念呢?爲了解釋這個問題,下面先來觀察以下的一段程序:
class BeiJingRen {
private String name ;
private int age ;
private String city = "北京" ;
public BeiJingRen(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age + ",城市:" + city ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
BeiJingRen bjr1 = new BeiJingRen("張三",10) ;
BeiJingRen bjr2 = new BeiJingRen("李四",11) ;
BeiJingRen bjr3 = new BeiJingRen("王五",12) ;
System.out.println(bjr1.getInfo()) ;
System.out.println(bjr2.getInfo()) ;
System.out.println(bjr3.getInfo()) ;
}
};
本程序,如果畫成內存關係圖:
如果現在假設北京這座城市的名字改成了最早的北平,而且現在已經有了500000個對象產生了,則此時要修改500000個對象的city屬性,而且如果按照這種定義格式的話也會存在問題,city屬性現在是重複的,那麼這個時候就發現,city應該定義成一個公共屬性最合適,所以就可以通過static定義完成。
class BeiJingRen {
private String name ;
private int age ;
static String city = "北京" ;
public BeiJingRen(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age + ",城市:" + city ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
BeiJingRen bjr1 = new BeiJingRen("張三",10) ;
BeiJingRen bjr2 = new BeiJingRen("李四",11) ;
BeiJingRen bjr3 = new BeiJingRen("王五",12) ;
System.out.println("======== city修改之前 ==========") ;
System.out.println(bjr1.getInfo()) ;
System.out.println(bjr2.getInfo()) ;
System.out.println(bjr3.getInfo()) ;
System.out.println("======== city修改之後 ==========") ;
bjr1.city = "北平" ;
System.out.println(bjr1.getInfo()) ;
System.out.println(bjr2.getInfo()) ;
System.out.println(bjr3.getInfo()) ;
}
};
現在發現,如果修改了一個對象中的city屬性(city屬性使用了static關鍵字聲明)則所有對象的city內容都變了。
現在每一個對象都佔有着各自的name和age屬性,但是對於公共的city屬性是所有對象所共同擁有的,很明顯,現在如果一個對象將city屬性修改了,其他的所有對象都將改變,但是這個時候又有一個問題了,既然static聲明的屬性是公共屬性的話,那麼你認爲一個對象就修改了,這樣合適嗎?
這些所有的對象最大的頭是類,所以一般調用static屬性都會通過類直接調用,使用“類.static屬性”的格式完成。
BeiJingRen.city = "北平" ;
既然static聲明的屬性可以被類名稱直接調用,所以在一些書中也稱其爲“類屬性”。
既然可以使用static聲明屬性,那麼也就可以使用static聲明方法,而且使用static聲明的方法可以直接由類名稱調用。
class BeiJingRen {
private String name ;
private int age ;
private static String city = "北京" ;
public BeiJingRen(String name,int age){
this.name = name ;
this.age = age ;
}
public static void setCity(String c){
city = c ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age + ",城市:" + city ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
BeiJingRen bjr1 = new BeiJingRen("張三",10) ;
BeiJingRen bjr2 = new BeiJingRen("李四",11) ;
BeiJingRen bjr3 = new BeiJingRen("王五",12) ;
System.out.println("======== city修改之前 ==========") ;
System.out.println(bjr1.getInfo()) ;
System.out.println(bjr2.getInfo()) ;
System.out.println(bjr3.getInfo()) ;
System.out.println("======== city修改之後 ==========") ;
BeiJingRen.setCity("北平") ;
System.out.println(bjr1.getInfo()) ;
System.out.println(bjr2.getInfo()) ;
System.out.println(bjr3.getInfo()) ;
}
};
但是必須提醒的是,對於static屬性或方法的調用上,不一定非要使用類,但是爲了保證代碼的編寫質量,往往都會通過類名稱調用。
而且現在的類中已經存在了static方法,那麼此時與非static操作之間就存在問題了:
· 非static的方法可以調用static的屬性或方法;
· static的方法只能調用static的屬性或方法。
爲什麼要存在這樣的限制?
由於static的所定義的操作可以直接由類名稱調用,那麼也就可以在沒有一個實例化對象產生的時候完成static屬性或方法的調用,而所有的非static操作要求必須有實例化對象的時候纔可以調用。
解釋:關於之前方法定義的問題
在之前強調過,如果一個方法在主類中定義(有main()方法的類)直接調用的話,則必須使用以下的格式:
public static 返回值類型 方法名稱(參數){
[return 返回值] ;
}
下面來觀察,如果現在在定義的時候沒有寫出static會如何:
public class OODemo { // 第二個類
public static void main(String args[]){
print() ;
}
public void print(){
System.out.println("Hello World!!!") ;
}
};
如果不使用static聲明,則現在的情況是static的方法調用非static的操作,按照之前所講,肯定是錯誤的,那麼如果現在非要調用print()方法的話呢,則只能實例化本類對象,並完成調用。
public class OODemo { // 第二個類
public static void main(String args[]){
new OODemo().print() ;
}
public void print(){
System.out.println("Hello World!!!") ;
}
};
3.6.2、static的使用
static屬性本身屬於公共的操作,所以在開發之中可以利用static統計出一個類產生了多少個新的實例化對象,每個對象實例化的時候都要調用類中的構造方法,所以這種統計的操作可以直接在構造方法之中完成。
class Person {
private static int count = 0 ;
public Person(){
System.out.println("第" + ++count + "個對象產生。") ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new Person() ;
new Person() ;
new Person() ;
}
};
如果現在將此操作簡單修改的話,也可以完成屬性的自動命名操作。
例如:一個類之中存在一個name的屬性,而且此類有兩個構造方法,一個構造方法可以專門傳入name屬性的內容,另外一個構造方法不接收任何參數,如果用戶現在調用的是不接收任何參數的構造方法的話,則按照“匿名-X”的形式保存,其中X就表示一個數字。
class Person {
private String name ;
private static int count = 0 ;
public Person(){
this("匿名 - " + count++) ; // 自動命名
}
public Person(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
System.out.println(new Person().getName()) ;
System.out.println(new Person("張三").getName()) ;
System.out.println(new Person().getName()) ;
System.out.println(new Person("李四").getName()) ;
System.out.println(new Person().getName()) ;
}
};
3.6.3、主方法組成
主方法一直在使用,那麼這裏面有很多個關鍵字所組成:
public static void main(String args[]),這些關鍵字的意義如下:
· public:表示一個訪問權限,是最大的訪問權限,所有用戶都可以訪問;
· static:表示此方法可以直接由類名稱完成調用;
· void:表示主方法是一切操作的起點,沒得後悔;
· main():系統規定好的方法名稱,只要是通過java命令運行,則肯定找到此方法;
· args:表示所有程序運行時接收的參數,以數組的形式返回。
所有的運行參數,都直接通過java 類名稱 參數1 參數2 參數3 …的形式傳遞進來。
public class OODemo { // 第二個類
public static void main(String args[]){
for(int x=0;x<args.length;x++){
System.out.println(args[x]) ;
}
}
};
但是這個時候又出現了一個新的問題了,如果現在輸入的參數之中本身就有空格怎麼辦,則此時就需要使用“"”將每個參數括起來:java OODemo "hello world" "hello mldn"
3.7、思考題
現在要求完成一個用戶的登陸程序,通過初始化參數,輸入登陸的用戶名和密碼
例如:java LoginDemo 用戶名 密碼
如果用戶名是hello,密碼是mldn的話,則表示登陸成功,顯示登陸歡迎的信息,如果登陸失敗,則顯示登陸失敗的信息。
自己思考如何做,先完成基本功能,之後再進行結構的修改。
本程序算是一個比較綜合性的程序,主要的功能是理解類的設計問題,但是在實際的項目開發中一定要給一個建議:一定要先完成一些基本功能,之後再做所謂的優化。
一定要先完成基本的開發模型,之後進行代碼的修改。
基本功能就是接收參數,並進行驗證。
public class OODemo { // 第二個類
public static void main(String args[]){
String name = args[0] ; // 第一個參數
String password = args[1] ; // 第二個參數
if("hello".equals(name) && "mldn".equals(password)){
System.out.println("用戶登陸成功!") ;
} else {
System.out.println("用戶登陸失敗!") ;
}
}
};
現在基本功能實現了,但是這種代碼不好,主方法是一個客戶端,主方法中的代碼越少越好。
思考:現實世界中的登陸流程
第一步:確定所要傳入的數據是否正確
第二步:將信息傳入到一個接收端進行數據的採集
第三步:驗證數據是否合法
首先應該有一個專門負責數據驗證的操作類。
class User {
private String name ;
private String password ;
public User(String name,String password){
this.name = name ;
this.password = password ;
}
public boolean validate(){
if("hello".equals(this.name) && "mldn".equals(this.password)){
return true ;
} else {
return false ;
}
}
};
class Login { // 一個專門的操作窗口
private String args[] ;
public Login(String args[]){
this.args = args ; // 接收所有的參數
this.isExit() ; // 判斷參數是否合法
}
public void isExit(){ // 如果接收的參數個數有錯誤,則意味着程序應該退出
if(this.args.length != 2){ // 參數的長度錯誤
System.out.println("錯誤:傳入了非法的參數。") ;
System.exit(1) ; // 退出系統
}
}
public String getMsg(){
if(new User(this.args[0],this.args[1]).validate()){
return "用戶登陸成功!" ;
} else {
return "用戶登陸失敗!" ;
}
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
System.out.println(new Login(args).getMsg()) ;
}
};
到底類該怎麼設計呢?
數據庫的表設計沒有標準的設計方法,只有相對合理的設計,那麼類設計本身也是一樣的,就記住一點:一個類完成一個具體的功能,例如:如果設計的是一個表示手的類,就應該完成手的功能,而不應該將嘴的功能寫在手的功能之中。
3.8、代碼塊(理解)
在Java程序之中,使用“{}”括起來的一段代碼稱爲代碼塊,但是根據其所在的位置不同或者是聲明的關鍵字的不同,代碼塊的作用也不同,代碼塊一共分爲四種:普通代碼塊、構造塊、靜態塊、同步代碼塊。
3.8.1、普通代碼塊
普通代碼塊是指定義在一個方法之中代碼塊。
public class OODemo { // 第二個類
public static void main(String args[]){
{ // 普通代碼塊
int x = 10 ; // 局部變量
System.out.println("x = " + x) ;
}
int x = 100 ;
System.out.println("x = " + x) ;
}
};
3.8.2、構造塊
當在一個類之中直接定義一個代碼塊的時候,這種代碼塊就稱爲構造塊。
class Demo {
{ // 構造塊
System.out.println("構造塊。") ;
}
public Demo(){
System.out.println("構造方法。") ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new Demo() ;
new Demo() ;
}
};
構造塊依然在對象實例化的時候進行調用,而且構造塊優先於構造方法執行,每次有新的對象產生了,都將重複調用構造塊中的內容。
3.8.3、靜態塊
在構造塊的基礎之上增加static關鍵字的聲明,就表示是一個靜態塊了。
class Demo {
static{ // 靜態塊
System.out.println("****** 靜態塊。。。") ;
}
{ // 構造塊
System.out.println("構造塊。") ;
}
public Demo(){
System.out.println("構造方法。") ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new Demo() ;
new Demo() ;
}
};
靜態塊是最優先執行的,而且不管實例化多少次對象,最終也只調用一次,靜態塊的主要功能是用於爲靜態屬性初始化的,但是,如果現在是在主類中定義了靜態塊的話,則會優先於主方法執行。
class Demo {
static{ // 靜態塊
System.out.println("****** 靜態塊。。。") ;
}
{ // 構造塊
System.out.println("構造塊。") ;
}
public Demo(){
System.out.println("構造方法。") ;
}
};
public class OODemo { // 第二個類
static {
System.out.println("主類中的靜態塊。") ;
}
public static void main(String args[]){
new Demo() ;
new Demo() ;
}
};
既然靜態塊本身優先於主方法執行,所以這個時候就可以通過代碼讓一個靜態塊來替代掉主方法的功能。
public class OODemo { // 第二個類
static {
System.out.println("Hello World!!!") ;
System.exit(1) ;
}
};
這種代碼只是娛樂,沒有任何的實際意義。
3.9、內部類(重點)
內部類是JAVA的一大重點,這個重點的興起是由於框架技術的興起。
3.9.1、內部類的基本概念
實際上所謂的內部類就是指一個類的內部還有另外的類存在,一個類可以定義多個內部類。
class 外部類{
class 內部類{}
}
範例:定義一個簡單的內部類
class Outer {
private String info = "HELLO WORLD" ; // 外部類的私有屬性
class Inner { // 內部類
public void print(){
System.out.println(info) ; // 外部類的私有屬性
}
};
public void fun(){
new Inner().print() ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new Outer().fun() ;
}
};
本程序非常的好理解,但是這道程序有那些缺點和優點呢?
1、 缺點:一直在強調一個類由屬性和方法所組成,但是這個時候又多了一個內部類,所以代碼的結構不好;
2、 優點:?
如果要想知道此操作的優點,可以將這個類拆分成兩個不同的類。
class Outer {
private String info = "HELLO WORLD" ; // 外部類的私有屬性
public void fun(){
new Inner(this).print() ;
}
public String getInfo(){
return this.info ;
}
};
class Inner { // 內部類
private Outer out ;
public Inner(Outer out){
this.out = out ;
}
public void print(){
System.out.println(this.out.getInfo()) ; // 外部類的私有屬性
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new Outer().fun() ;
}
};
這個時候就可以發現了,如果將其拆分成兩個類,則在內部類訪問外部類私有屬性的時候將變得及其的複雜,所以內部類的唯一有點就是可以方便的訪問外部類中的私有操作。
但是如果現在假設在外部想實例化內部類的對象該怎麼做呢,這個時候就必須按照如下的格式完成了:
外部類.內部類 內部類的對象 = new 外部類().new 內部類() ;
之所以採用這種形式訪問,主要原因還是在於內部類的生成的*.class格式:Outer$Inner.class
$是標識符的組成,但是一般而言這種操作都是在文件命名上使用的,而這種符號換到了程序之中就是“.”,所以內部類的名稱就是“外部類.內部類”,之所以在實例化內部類對象的時候還必須首先實例化外部類對象,主要原因是因爲內部類可以訪問外部類中的私有成員,那麼現在就必須保證外部類已經被實例化了纔可以進行。
class Outer {
private String info = "HELLO WORLD" ; // 外部類的私有屬性
class Inner { // 內部類
public void print(){
System.out.println(info) ; // 外部類的私有屬性
}
};
};
public class OODemo { // 第二個類
public static void main(String args[]){
Outer.Inner in = new Outer().new Inner() ;
in.print() ;
}
};
3.9.2、使用static定義內部類
在內部類的定義中也可以使用static關鍵字進行內部類的定義,使用static定義的內部類就表示一個外部類,可以直接被外部類所訪問,但是static定義的內部類只能訪問其外部類的static屬性或方法。
class Outer {
private static String info = "HELLO WORLD" ; // 外部類的私有屬性
static class Inner { // 內部類
public void print(){
System.out.println(info) ; // 外部類的私有屬性
}
};
};
public class OODemo { // 第二個類
public static void main(String args[]){
// Outer.Inner in = new Outer().new Inner() ;
Outer.Inner in = new Outer.Inner() ;
in.print() ;
}
};
3.9.3、在方法中定義內部類
內部類可以在任意的位置上定義,可以在if語句或while語句中,也可以在一個方法之中定義。
class Outer {
private String info = "HELLO WORLD" ; // 外部類的私有屬性
public void fun(){
class Inner { // 內部類
public void print(){
System.out.println(info) ; // 外部類的私有屬性
}
};
new Inner().print() ; // 實例化內部類對象,同時調用方法
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new Outer().fun() ;
}
};
但是如果一個內部類在方法之中定義的話,此時如果要想訪問方法中定義的參數或者是局部變量的話,這些參數或變量名稱之前必須加上一個final的關鍵字聲明。
class Outer {
private String info = "HELLO WORLD" ; // 外部類的私有屬性
public void fun(final int x){
final int temp = 10 ;
class Inner { // 內部類
public void print(){
System.out.println(info) ; // 外部類的私有屬性
System.out.println(x) ; // 外部類的私有屬性
System.out.println(temp) ; // 外部類的私有屬性
}
};
new Inner().print() ; // 實例化內部類對象,同時調用方法
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
new Outer().fun(199) ;
}
};
3.10、使用內部類實現鏈表(理解)
爲了更好的方便內部類的理解,下面使用內部類完成一個鏈表的操作程序,爲了簡便,本次只完成增加及輸出的操作,而且一定要使用內部類,將Node作爲內部類比較合適。
鞏固一個概念:內部類可以方便的訪問外部類中的私有操作,實際上外部類也可以方便的訪問內部類中的私有操作。
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
};
class Link {
class Node { // 節點類
private Person data ;
private Node next ; // 下一個節點
public Node(Person data){
this.data = data ;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode ;
} else {
this.next.addNode(newNode) ;
}
}
public void printNode(){
System.out.println(this.data.getInfo()) ;
if(this.next != null){
this.next.printNode() ;
}
}
};
private Node root ; // 一定要存在根節點
public void add(Person data){
Node newNode = new Node(data) ;
if(this.root == null){
this.root = newNode ; // 第一個節點爲根節點
} else {
this.root.addNode(newNode) ;
}
}
public void print(){
if(this.root != null){
this.root.printNode() ;
}
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Link link = new Link() ;
link.add(new Person("張三",10)) ;
link.add(new Person("李四",20)) ;
link.add(new Person("王五",30)) ;
link.print() ;
}
};
如果現在要想繼續完成查詢操作的話,則必須存在一個對象的比較操作,而這種對象的比較操作由每個類完成。
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public boolean compare(Person p){
if(this == p){
return true ;
}
if(p == null){
return false ;
}
if(this.name.equals(p.name) && this.age==p.age){
return true ;
} else {
return false ;
}
}
public String getInfo(){
return "姓名:" + this.name + ",年齡:" + this.age ;
}
};
class Link {
class Node { // 節點類
private Person data ;
private Node next ; // 下一個節點
public Node(Person data){
this.data = data ;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode ;
} else {
this.next.addNode(newNode) ;
}
}
public void printNode(){
System.out.println(this.data.getInfo()) ;
if(this.next != null){
this.next.printNode() ;
}
}
public boolean searchNode(Person data){
if(this.data.compare(data)){ // 調用對象比較操作
return true ;
} else {
if(this.next != null){
return this.next.searchNode(data) ;
}
}
return false ;
}
};
private Node root ; // 一定要存在根節點
public void add(Person data){
Node newNode = new Node(data) ;
if(this.root == null){
this.root = newNode ; // 第一個節點爲根節點
} else {
this.root.addNode(newNode) ;
}
}
public void print(){
if(this.root != null){
this.root.printNode() ;
}
}
public boolean search(Person data){
if(this.root != null){
return this.root.searchNode(data) ;
}
return false ;
}
};
public class OODemo { // 第二個類
public static void main(String args[]){
Link link = new Link() ;
link.add(new Person("張三",10)) ;
link.add(new Person("李四",20)) ;
link.add(new Person("王五",30)) ;
link.print() ;
System.out.println(link.search(new Person("李四",20))) ;
}
};
4、總結
1、 一定要理解引用傳遞的問題,理解棧和堆的對應關係;
2、 可以通過編程語言描述出現實世界的產物;
3、 一定要理解鏈表的操作,可以從頭到尾寫出鏈表的基本實現:增加、查詢、刪除、輸出;
4、 對象數組,可以保存一組對象,但是對象數組本身會受到長度的限制,那麼可以通過鏈表來解決此問題;
5、 JDK 1.5的新特性還有很多,以後慢慢消化,但是對於這些新特性,理解即可,儘可能不要使用;
6、 static關鍵字可以聲明屬性及方法,聲明的屬性和方法可以由類名稱直接調用;
7、 代碼塊只需要理解即可,一般用的較少;
8、 內部類的開發在實際的工作中使用較多,一定要明白內部類的優點和缺點,還有內部類可以在方法之中定義,但是訪問方法中的參數時,必須有final,使用static定義的內部類就是外部類,可以直接通過“外部類.內部類”的形式訪問。
5、預習任務
繼承的基本概念及實現、繼承的限制、super關鍵字、方法的覆寫、抽象類和接口、Object類、對象多態性。
6、作業
所有代碼反覆熟練。