Java基礎知識
1、在Java語言中多態性體現在由方法重載實現的靜態多態性和方法重寫實現的動態多態性。
2、final 類不能被繼承,即不能有子類。
3、abstract 類不能創建對象,必須產生其子類,由子類創建對象。
4、String 類是不可變類,對象所包含的字符串內容永遠不會被改變。
5、如果一個方法被修飾爲 final 方法,則這個方法不能被重寫。
6、對於 abstract 方法,只允許聲明,而不允許實現。
7、如果一個方法被修飾爲final方法,則這個方法不能被重寫 。
8、如果一個類是一個abstract類的子類,它必須具體實現父類 的abstract方法。
9、局部變量的名字與成員變量的名字相同,若想在該方法內使用成員變量,必須使用關鍵字 this 。
10、在關鍵字中能代表當前類或對象本身的是 this 。
11、如果在子類中想使用被子類隱藏的父類的成員變量或方法可以使用關鍵字 super 。
Java中的類修飾符
之前每次寫小測試程序的時候,總是把一個類放在一個Java文件中,按理說這樣比較規範,可主要原因是我是在不知道怎麼在一個文件裏放多個類,可見java基礎有多差了。。。只要把類的屬性改成默認的就可以了,也就是前面什麼控制符都不加,採用默認的方式。
Java語言程序設計中,定義類時除了要使用class 關鍵字說明所定義的數據類型是類,還可以在 class 之前增加若干類的修飾符來限定所定義類的操作特性,說明類的屬性。
類的修飾符分爲:可訪問控制符和非訪問控制符兩種。
可訪問控制符是: 公共類修飾符 public
非訪問控制符有:抽象類修飾符 abstract
最終類修飾符:final
不加修飾符的時候 即直接聲明 class A{ }
在這種情況下,class前面沒有加任何的訪問修飾符,通常稱爲“默認訪問模式”,在該模式下,這個類只能被同一個包中的類訪問或引用,這一訪問特性又稱包訪問性。
公共類的修飾符 public
一個java的源文件中,規定最多只能有一個public 類,當然也可以一個都沒有。如果有public公共類,則規定文件名只能public 公共類的類名稱一致,若是沒有,則文件名可以任意。作爲一個java程序的入口(javaSE)的類必須爲public類。
所謂公共類是指這個類可以被所有的其它類或其它包中的類訪問和引用,也就是說這個類作爲一個整體,是可見的、可以使用的,程序的其它部分可以創建這個類的對象、訪問這個類內部公共的 (用可訪問控制符 public 定義的 )變量和方法。
哪些類應定義爲公共類呢?
(1)個Java程序的主類都必須定義爲公共類,用 public 修飾;
(2)作爲公共工具供其它類和程序使用的類應定義爲公共類,用 public 修飾。
抽象類修飾符 abstract
凡是用修飾符 abstract修飾的類,被稱爲抽象類。接口是一個100%的純抽象類。
最終類修飾符 final
當一個類不可能有子類時(final類是不可以被繼承的)可以用修飾符 final把它說明爲最終類。
被定義爲 final類通常是一些有固定作用、用來完成某種標準功能的類。如Java系統定義好的用來實現網絡功能的InetAddress、Socket等類都是 final類。
如果把有繼承關係的類用樹表示出來,不難看到樹的葉結點應該被定義爲final最終類。
將一個類定義爲 final 類。就可以把它的屬性和功能固定下來,與它的類名形成穩定的映射關係,從而保證引用這個類時所實現的功能正確無誤。
注意:修飾符abstract和修飾符final 不能同時修飾同一個類,因爲abstract類是沒有具體對象的類,它必須有子類,即就是是用來被繼承的;而 final類是不可能有子類的類,所以用abstract和final修飾同一個類是無意義的。
Java中5種創建對象的方式
下面給出它們的例子還有它們的字節碼
使用new關鍵字 | } → 調用了構造函數 |
使用Class類的newInstance方法 | } → 調用了構造函數 |
使用Constructor類的newInstance方法 | } → 調用了構造函數 |
使用clone方法 | } → 沒有調用構造函數 |
使用反序列化 | } → 沒有調用構造函數 |
如果你運行了末尾的的程序,你會發現方法1,2,3用構造函數創建對象,方法4,5沒有調用構造函數。
1.使用new關鍵字
這是最常見也是最簡單的創建對象的方式了。通過這種方式,我們可以調用任意的構造函數(無參的和帶參數的)。
Employee emp1 = new Employee();
0: new #19 // class org/programming/mitra/exercises/Employee
3: dup
4: invokespecial #21 // Method org/programming/mitra/exercises/Employee."":()V
2.使用Class類的newInstance方法
我們也可以使用Class類的newInstance方法創建對象。這個newInstance方法調用無參的構造函數創建對象。
我們可以通過下面方式調用newInstance方法創建對象:
Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee").newInstance();
或者
Employee emp2 = Employee.class.newInstance();
51: invokevirtual #70 // Method java/lang/Class.newInstance:()Ljava/lang/Object;
3.使用Constructor類的newInstance方法
和Class類的newInstance方法很像, java.lang.reflect.Constructor類裏也有一個newInstance方法可以創建對象。我們可以通過這個newInstance方法調用有參數的和私有的構造函數。
Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
111: invokevirtual #80 // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;
這兩種newInstance方法就是大家所說的反射。事實上Class的newInstance方法內部調用Constructor的newInstance方法。這也是衆多框架,如Spring、Hibernate、Struts等使用後者的原因。想了解這兩個newInstance方法的區別,請看這篇Creating objects through Reflection in Java with Example.
4.使用clone方法
無論何時我們調用一個對象的clone方法,jvm就會創建一個新的對象,將前面對象的內容全部拷貝進去。用clone方法創建對象並不會調用任何構造函數。
要使用clone方法,我們需要先實現Cloneable接口並實現其定義的clone方法。
Employee emp4 = (Employee) emp3.clone();
162: invokevirtual #87 // Method org/programming/mitra/exercises/Employee.clone ()Ljava/lang/Object;
5.使用反序列化
當我們序列化和反序列化一個對象,jvm會給我們創建一個單獨的對象。在反序列化時,jvm創建對象並不會調用任何構造函數。
爲了反序列化一個對象,我們需要讓我們的類實現Serializable接口
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
261: invokevirtual #118 // Method java/io/ObjectInputStream.readObject:()Ljava/lang/Object;
我們從上面的字節碼片段可以看到,除了第1個方法,其他4個方法全都轉變爲invokevirtual(創建對象的直接方法),第一個方法轉變爲兩個調用,new和invokespecial(構造函數調用)。
例子
讓我們看一看爲下面這個Employee類創建對象:
class Employee implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String name;
public Employee() {
System.out.println("Employee Constructor Called...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Employee [name=" + name + "]";
}
@Override
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
下面的Java程序中,我們將用5種方式創建Employee對象。你可以從GitHub找到這些代碼。
public class ObjectCreation {
public static void main(String... args) throws Exception {
// By using new keyword
Employee emp1 = new Employee();
emp1.setName("Naresh");
System.out.println(emp1 + ", hashcode : " + emp1.hashCode());
// By using Class class's newInstance() method
Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee")
.newInstance();
// Or we can simply do this
// Employee emp2 = Employee.class.newInstance();
emp2.setName("Rishi");
System.out.println(emp2 + ", hashcode : " + emp2.hashCode());
// By using Constructor class's newInstance() method
Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
emp3.setName("Yogesh");
System.out.println(emp3 + ", hashcode : " + emp3.hashCode());
// By using clone() method
Employee emp4 = (Employee) emp3.clone();
emp4.setName("Atul");
System.out.println(emp4 + ", hashcode : " + emp4.hashCode());
// By using Deserialization
// Serialization
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.obj"));
out.writeObject(emp4);
out.close();
//Deserialization
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
in.close();
emp5.setName("Akash");
System.out.println(emp5 + ", hashcode : " + emp5.hashCode());
}
}
程序會輸出:
Employee Constructor Called...
Employee [name=Naresh], hashcode : -1968815046
Employee Constructor Called...
Employee [name=Rishi], hashcode : 78970652
Employee Constructor Called...
Employee [name=Yogesh], hashcode : -1641292792
Employee [name=Atul], hashcode : 2051657
Employee [name=Akash], hashcode : 63313419
java實現三個線程A B C,A線程打印A之後才能B線程打印B,然後接着C線程執行打印C,繼而循環下去
在JAVA中,是沒有類似於PV操作、進程互斥等相關的方法的。JAVA的進程同步是通過synchronized()來實現的,需要說明的是,JAVA的synchronized()方法類似於操作系統概念中的互斥內存塊,在JAVA中的Object類型中,都是帶有一個內存鎖的,在有線程獲取該內存鎖後,其它線程無法訪問該內存,從而實現JAVA中簡單的同步、互斥操作。明白這個原理,就能理解爲什麼synchronized(this)與synchronized(static XXX)的區別了,synchronized就是針對內存區塊申請內存鎖,this關鍵字代表類的一個對象,所以其內存鎖是針對相同對象的互斥操作,而static成員屬於類專有,其內存空間爲該類所有成員共有,這就導致synchronized()對static成員加鎖,相當於對類加鎖,也就是在該類的所有成員間實現互斥,在同一時間只有一個線程可訪問該類的實例。如果只是簡單的想要實現在JAVA中的線程互斥,明白這些基本就已經夠了。但如果需要在線程間相互喚醒的話就需要藉助Object.wait(), Object.nofity()了。
Obj.wait(),與Obj.notify()必須要與synchronized(Obj)一起使用,也就是wait,與notify是針對已經獲取了Obj鎖進行操作,從語法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語句塊內。從功能上來說wait就是說線程在獲取對象鎖後,主動釋放對象鎖,同時本線程休眠。直到有其它線程調用對象的notify()喚醒該線程,才能繼續獲取對象鎖,並繼續執行。相應的notify()就是對對象鎖的喚醒操作。但有一點需要注意的是notify()調用後,並不是馬上就釋放對象鎖的,而是在相應的synchronized(){}語句塊執行結束,自動釋放鎖後,JVM會在wait()對象鎖的線程中隨機選取一線程,賦予其對象鎖,喚醒線程,繼續執行。這樣就提供了在線程間同步、喚醒的操作。Thread.sleep()與Object.wait()二者都可以暫停當前線程,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了對象鎖的控制。
單單在概念上理解清楚了還不夠,需要在實際的例子中進行測試才能更好的理解。對Object.wait(),Object.notify()的應用最經典的例子,應該是三線程打印ABC的問題了吧,這是一道比較經典的面試題,題目要求如下:
建立三個線程,A線程打印10次A,B線程打印10次B,C線程打印10次C,要求線程同時運行,交替打印10次ABC。這個問題用Object的wait(),notify()就可以很方便的解決。代碼如下:
[java] view plaincopy
- public class MyThreadPrinter2 implements Runnable {
- private String name;
- private Object prev;
- private Object self;
- private MyThreadPrinter2(String name, Object prev, Object self) {
- this.name = name;
- this.prev = prev;
- this.self = self;
- }
- @Override
- public void run() {
- int count = 10;
- while (count > 0) {
- synchronized (prev) {
- synchronized (self) {
- System.out.print(name);
- count--;
- self.notify();
- }
- try {
- prev.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args) throws Exception {
- Object a = new Object();
- Object b = new Object();
- Object c = new Object();
- MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
- MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
- MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
- new Thread(pa).start();
- new Thread(pb).start();
- new Thread(pc).start(); }
- }
先來解釋一下其整體思路,從大的方向上來講,該問題爲三線程間的同步喚醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循環執行三個線程。爲了控制線程執行的順序,那麼就必須要確定喚醒、等待的順序,所以每一個線程必須同時持有兩個對象鎖,才能繼續執行。一個對象鎖是prev,就是前一個線程所持有的對象鎖。還有一個就是自身對象鎖。主要的思想就是,爲了控制執行的順序,必須要先持有prev鎖,也就前一個線程要釋放自身對象鎖,再去申請自身對象鎖,兩者兼備時打印,之後首先調用self.notify()釋放自身對象鎖,喚醒下一個等待線程,再調用prev.wait()釋放prev對象鎖,終止當前線程,等待循環結束後再次被喚醒。運行上述代碼,可以發現三個線程循環打印ABC,共10次。程序運行的主要過程就是A線程最先運行,持有C,A對象鎖,後釋放A,C鎖,喚醒B。線程B等待A鎖,再申請B鎖,後打印B,再釋放B,A鎖,喚醒C,線程C等待B鎖,再申請C鎖,後打印C,再釋放C,B鎖,喚醒A。看起來似乎沒什麼問題,但如果你仔細想一下,就會發現有問題,就是初始條件,三個線程按照A,B,C的順序來啓動,按照前面的思考,A喚醒B,B喚醒C,C再喚醒A。但是這種假設依賴於JVM中線程調度、執行的順序。具體來說就是,在main主線程啓動ThreadA後,需要在ThreadA執行完,在prev.wait()等待時,再切回線程啓動ThreadB,ThreadB執行完,在prev.wait()等待時,再切回主線程,啓動ThreadC,只有JVM按照這個線程運行順序執行,才能保證輸出的結果是正確的。而這依賴於JVM的具體實現。考慮一種情況,如下:如果主線程在啓動A後,執行A,過程中又切回主線程,啓動了ThreadB,ThreadC,之後,由於A線程尚未釋放self.notify,也就是B需要在synchronized(prev)處等待,而這時C卻調用synchronized(prev)獲取了對b的對象鎖。這樣,在A調用完後,同時ThreadB獲取了prev也就是a的對象鎖,ThreadC的執行條件就已經滿足了,會打印C,之後釋放c,及b的對象鎖,這時ThreadB具備了運行條件,會打印B,也就是循環變成了ACBACB了。這種情況,可以通過在run中主動釋放CPU,來進行模擬。代碼如下:
[java] view plaincopy
- public void run() {
- int count = 10;
- while (count > 0) {
- synchronized (prev) {
- synchronized (self) {
- System.out.print(name);
- count--;
- try{
- Thread.sleep(1);
- }
- catch (InterruptedException e){
- e.printStackTrace();
- }
- self.notify();
- }
- try {
- prev.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
運行後的打印結果就變成了ACBACB了。爲了避免這種與JVM調度有關的不確定性。需要讓A,B,C三個線程以確定的順序啓動,最終代碼如下:
[java] view plaincopy
- public class MyThreadPrinter2 implements Runnable {
- private String name;
- private Object prev;
- private Object self;
- private MyThreadPrinter2(String name, Object prev, Object self) {
- this.name = name;
- this.prev = prev;
- this.self = self;
- }
- @Override
- public void run() {
- int count = 10;
- while (count > 0) {
- synchronized (prev) {
- synchronized (self) {
- System.out.print(name);
- count--;
- try{
- Thread.sleep(1);
- }
- catch (InterruptedException e){
- e.printStackTrace();
- }
- self.notify();
- }
- try {
- prev.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args) throws Exception {
- Object a = new Object();
- Object b = new Object();
- Object c = new Object();
- MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
- MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
- MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
- new Thread(pa).start();
- Thread.sleep(10);
- new Thread(pb).start();
- Thread.sleep(10);
- new Thread(pc).start();
- Thread.sleep(10);
- }
- }
文章來自:http://blog.csdn.net/u012110719/article/details/47161789
這樣纔可以完美的解決該問題。通過這個例子也是想說明一下,很多理論、概念如Obj.wait(),Obj.notify()等,理解起來,比較簡單,但是在實際的應用當中,這裏卻是往往出現問題的地方。需要更加深入的理解。並在解決問題的過程中不斷加深對概念的掌握。