十個不可忽視的Java基礎知識
1.static的作用
static,顧名思義,靜態。當類中的成員變數或方法宣告為static時,無需為此類建立參照,便可對其進行操作。即,不依賴該類的例項,同時也被該類的例項所共享。下面通過兩種static的使用情況進行分析:
static變數
同為成員變數,與例項變數每建立一次便為其分配一次記憶體不同,JVM只為static變數分配一次記憶體,並在載入類的過程中完成該操作。可用類名直接訪問此變數或通過例項呼叫,前者是被推薦的,因為這樣不僅強調了該變數的static屬性,而且也在某種程度上使編譯器更容易去進行優化。所以在物件之間有共享值或為了方便訪問某種變數時一般需要使用static變數。
static方法
對於static方法,也同時可以通過類名呼叫或例項呼叫。因此需要注意的是,static方法中不能用this或super關鍵字,不能直接訪問此方法所在類的例項變數或例項方法,只能訪問該類的靜態成員變數和方法。因為例項變數和方法有特定的物件,而靜態方法佔據一個特定的資料區域。
舉例:
Class StaticTest{
static int i = 47;
int j = 10;
}
Class Incrementable{
static void increment(){
//通過類名直接對i進行操作
StaticTest.i++;
//此處無法對j進行訪問,因為其為例項變數
}
}
2.final的作用
final,在Java中通常解釋為不可變的,也就是說,final關鍵字是用來防止變化。一般我們在兩種情況下使用:設計(design)或效率(efficiency)。下面分情況來分析final關鍵字的作用:
final資料——宣告類中屬性或變數
每種程式語言都有一種宣告常量的方法,java中便是final。基本型別(Primitive Type)和引用型別(Object Reference Type)的屬性在宣告final後,裡面存放的值都不可再改變。但有所不同的是,在基本型別中,這個值是實在的,比如100,”java”;在引用型別中存放的是地址,所以final只是使其地址不可改變,這個地址所指的物件、陣列皆是可以改變的。需要注意的是,static final的基本變數命名法,全大寫字母,單詞之間用”_”(underscore)連線。舉例:
public static final int VALUE_ONE = 9;
final方法
使用final宣告方法主要是為了給方法加鎖以防止繼承的類對其內容進行修改,也就是使其不可重寫(override)。因此final方法可以被繼承,但不能被重寫。
final類
在前面加上final關鍵字的類是因為你不希望此類被繼承,換句話說,在某種設計情況下你的類永遠不需要去作出改變,或者為了安全原因你不希望它有子類。簡單舉例:
class SmallBrain{}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f(){}
}
//class Further extends Dinosaur{}此句無法執行,因為Dinosaur類為final
public class Jurassic {
Public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++
}
}
還有一個網上看到的例子,忘記出處了:
final byte bt1 = 1;
final byte bt2 = 2;
byte bt3 = bt1 + bt2;
此例中若沒有final便會報錯,因為如果去掉final,bt1和bt2在運算時JVM將其自動轉換為了int型別變數,最後相當於將一個int型別賦值給了一個byte型別。
3.Overload與Override
為了對比,我們先來看一下兩者的英文定義:
Overriding
Having two methods with the same arguments, but different implementations.
Overloading
A feature that allows a class to have two or more methods having same name, if their argument lists are different.
不難看出,Overload和Override都是Java多型性(Polymorphism)的體現。其中Overriding(重寫)是指父類與子類中,同一方法,相同名稱和引數,重新寫其內容,相當於“推翻”了父類中的定義。而Overloading(過載)是指在同一類中,定義多個同名方法,但其中的引數型別或次序不同。例子如下:
Overriding
class Dog {
public void bark(){
System.out.println(“woof “);
}
}
class Hound extends Dog{
public void sniff(){
System.out.println(“sniff”);
}
public void bark(){
System.out.println(“bowl “);
}
}
Overloading
class Dog {
public void bark(){
System.out.println(“woof “);
}
//overloading method
public void bark(int num){
for (int i = 0; i < num; i++ ) {
System.out.println(“woof “);
}
}
}
4.組合與繼承
組合(Composition)與繼承(Inheritance)是Java最常用的兩種類的複用方法。要了解他們的區別,我們先來看一下英文定義。
組合
Achieved by using instance variables that refers to other objects.
繼承
A mechanism wherein a new class is derived from an existing class.
從字面意思來看,組合是在一個類中使用一個類的引用,通常說明一個類具有某種屬性,有”has a”的關係。比如下面的例子,灑水機”has a”水源:
class WaterSource{
private String s;
WaterSource(){
System.out.println(“WaterSource()”);
s = “Constructed”;
}
public String toString() {
return s;
}
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
private WaterSource source = new WaterSource();
private int i;
private float f;
public String toString() {
return
“valve1 = ” + valve1 + ” ” +
“valve2 = ” + valve2 + ” ” +
“valve3 = ” + valve3 + ” ” +
“valve4 = ” + valve4 + “\n” +
“i = ” + i + ” ” + “f = ” + f + ” ” +
“source = ” + source;
}
public static void main(String[] args) {
SprinklerSystem sprinklers = new SprinklerSystem();
System.out.println(sprinklers);
}
}
例子中的SprinklerSystem類中建立了WaterSource類的參照,因此此段程式碼中最先輸出的為”WaterSource”,這就是最簡單的組合。
而繼承是一個新類派生於舊類的關係,那麼也就是說具有舊類的屬性,有”is a”的關係,大家應該都對此非常熟悉,因此在此處不再舉例。
不難看出,當某物具有多項屬性時使用組合,比如汽車有引擎,車門,輪胎。當某物屬於一種某物時使用繼承,比如哈士奇是一種狗。
5.clone的作用
clone()是Java中用來複制物件的方法,由於Java取消了指標的概念,很多人對引用和物件的區別有所忽視,全面理解clone()將對此有很大幫助。
需要注意的是,當寫出如下程式碼時
Student st1 = new Student(“Daniel”);
Student st2 = st1;
System.out.println(st1);
System.out.println(st2);
列印結果(地址)是完全相同的,因為st1與st2為一個物件的兩個引用。因此我們在使用”=”操作符賦值是,只是進行了引用的複製。而clone()方法,才是真正實現物件複製的途徑,與上面的做對比:
Student st1 = new Student(“Daniel”);
Student st2 = (Student)st1.clone();
System.out.println(st1);
System.out.println(st2);
在上面的程式碼中,由於clone()返回的是Object物件,所以需要進行向下強制轉換為Student。此時列印的結果就是兩個不同的地址,真正地完成了物件的複製。
深拷貝(Deep Copy)和淺拷貝(Shallow Copy)
這裡我們又要再次提到基本資料型別(Primitive Type)和非基礎型別(Non-Primitive Type)的區別了,看下面一段程式碼:
Public class Student {
private int id;
private String name;
Public Student(int id, String name){
this.age = age;
this.name = name;
}
public Student() {}
public int getId() {
return age;
}
public String getName(){
return name;
}
}
其中id是基本資料型別,在拷貝後沒有什麼問題,然而name是String型別,因此前面提到的方法拷貝過來的只是一個引用值,便是淺拷貝。相對而言,深複製就是再建立一個相同的String物件,將這個物件的飲用賦值給拷貝出的新物件。
要實現深拷貝,我們需要先實現Cloneable介面並重寫clone()方法,將上述程式碼略作修改:
Public class Student implements Cloneable{
private int id;
private String name;
Public Student(int id, String name){
this.age = age;
this.name = name;
}
public Student() {}
public int getId() {
return age;
}
public String getName(){
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Student)super.clone();
}
}
需要注意的是,在套用多層物件的組合方法中,徹底的深拷貝需要在每一層物件中都實現cloneable介面並重寫clone()方法。非常複雜,在實際開發中用處不多,但如前文所題,有助於讀者對記憶體結構有深一步的瞭解。
6.內部類(Inner Classes)
顧名思義,內部類是指講一個類定義內置於另一個類定義之中。關於它的作用,《Thinking in Java》裡是這樣說明的:
The inner class is a valuable feature because it allows you to group classes that logically belong together and to control the visibility of one within the other.
將邏輯相通的類宣告為內部類使程式碼更易控制或處理,在安卓開發中我們會經常見到此類用法。因為每個內部類都能獨立地繼承一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。——以上黑體摘自《Thinking in Java》,如翻譯拗口請見諒。
下面我們來看一個包裹的例子,來簡單分析內部類的使用方法。
public class Parcel1 {
class Contents {
private int i = 11;
public value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship(“Tasmania”);
}
}
其中Destination和Contents便是內部類我們可以在非靜態(non-static)方法中對其進行呼叫,當再靜態方法如main()中對其進行呼叫時,需使用“外部類.內部類”的方式對其進行引用。如,將上述程式碼進行修改。
public class Parcel2 {
class Contents {
private int i = 11;
public value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents contents() {
return new Contents();
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship(“Tasmania”);
Parcel2 q = new Parcel2();
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to(“Borneo”)
}
}
在此例子中呼叫Contents以及Destination物件時,使用了“外部類.內部類”的呼叫方法。
7.介面與抽象類
在國外論壇上對於介面(Interface)和抽象類(Abstract class)的討論就一直不斷。我們還是先來看一下它們的英文定義。
抽象類
Classes that contain one or more abstract methods.
介面
It is a collection of abstract methods.
乍一看貌似差不多,都是有關抽象方法的集合,關於它們的區別,僅從定義上來看,有兩點不同:
1.介面暗示著全部方法為抽象,且不能有任何的implementation。需要注意的是,這裡有一個常考的點就是,既然不能implement,那麼介面可以繼承嗎?答案是可以的。相對於介面,抽象類中可以有執行預設行為的例項方法,也就是可以有implementation。
2.在介面中宣告的變數預設為final型別,然而在抽象類中可以有非final型別的變數。
那麼在應用上該如何選用這兩者呢,首先我們需要知道他們的本質。介面是對動作的抽象,而抽象類則是對根源的抽象。比如人要吃東西,狗也要吃東西,那麼我們就可以把吃東西作為一個介面。其中人和狗都屬於生物,那麼我們就可以將生物定為一個抽象類。需要注意的是,一個類只可以繼承一個類(抽象類),但卻可以實現多個介面。理解這一點不難,上例中人和狗是生物,但不可能同時是非生物體。
我們來看一個經典的報警門的例子,相信大部分人都見過:
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() { }
void close() { }
void alarm() { }
}
此例中,只要是門,固有屬性都會有開和關,所以將門設定為抽象類,報警門只是門的一種。然而報警門的特殊功能是報警,因此我們將報警設定為一個介面。在報警門的這個類中,便繼承了門,實現了報警介面。
8.stack和heap
關於堆(heap)和棧(stack)的理解不只侷限於java,無論是任何語言,對此概念區分理解都是非常重要的。stackoverflow是這麼說的:
Stack is used for static memory allocation and Heap for dynamic memory allocation
stack是靜態記憶體,而heap是動態分配。通俗來說,棧就是用來放引用的,當在一段程式碼塊定義一個變數時,Java就在棧中為這個變數分配記憶體空間,當超過變數的作用域後,Java會自動釋放掉為該變數所分配的記憶體空間,該記憶體空間可以立即被另作他用。而堆是用來放物件和陣列的,在Java中,這些物件和陣列是由new來建立的,而在堆中分配的記憶體,將由Java虛擬機器的自動垃圾回收器來管理。
關於例項中的關係,只要你能理解上文中提到過的clone()方法,理解起來非常輕鬆。
9.== 和 equals
作為經常出現在condition裡的判斷方法,區分==和equals是非常重要的,尤其是在安卓的開發中。關於兩者的比較,我們要聯合前文提到的堆和棧來理解。” == “是比較兩個變數棧中的內容是否相等,而與之相對應的”equals”則是比較堆中的內容是否相等。讓我們來舉例說明:
public class Test {
public static void main(String[] args) {
String s1 = “Daniel”;
String s2 = new String(“Daniel”);
if (s1 == s2){
System.out.println("s1 == s2");
}else{
System.out.println("s1 != s2");
}
if (s1 .equals(s2)){
System.out.println("s1 equals s2");
}else{
System.out.println("s1 not equals s2");
}
}
}
本例中輸出的為s1 != s2以及s1 equals s2。由此不難理解,s1與s2地址不同,但內容相同。
- 反射機制
反射機制是我們在開發過程中用的最多的,但可能你對他的文字定義並不瞭解:
Java Reflection makes it possible to inspect classes, interfaces, fields and methods at runtime, without knowing the names of the classes, methods etc. at compile time. It is also possible to instantiate new objects, invoke methods and get/set field values
using reflection
也就是說,對於一個類,通過反射機制,我能知道他的屬性和方法;對於我建立的一個物件,我能夠呼叫他的屬性與方法。這種獲得資訊以及呼叫物件是動態(Dynamic)的。我們通過stackoverflow上一個簡單的例子來理解:
public class TestReflect {
public static void main(String[] args) throws Exception {
ArrayList list = new ArrayList();
Method method = list.getClass().getMethod(“add”, Object.class);
method.invoke(list, “Java reflection”);
System.out.println(list.get(0));
}
}
Java中所有的物件都有getClass()方法,用以獲取Class物件。不難看出,上述程式碼在泛型為Integer的ArrayList中存放一個String型別的物件。
需要注意的是,除了getClass()還有兩種方式可以獲取class物件,一是forName(),另一個則是使用名稱.class來代表。最後我將放上一個CSDN的Winiex’s Blog中給出的一個例子給讀者去進行分析,相信在你成功分析了本例之後,對反射機制的運用將會感覺”Easy as pie”了。
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
*
Java Reflection Cookbook
*
* @author Michael Lee
* @since 2006-8-23
* @version 0.1a
*/
public class Reflection {
/**
* 得到某個物件的公共屬性
*
* @param owner, fieldName
* @return 該屬性物件
* @throws Exception
*
*/
public Object getProperty(Object owner, String fieldName) throws Exception {
Class ownerClass = owner.getClass();
Field field = ownerClass.getField(fieldName);
Object property = field.get(owner);
return property;
}
/**
* 得到某類的靜態公共屬性
*
* @param className 類名
* @param fieldName 屬性名
* @return 該屬性物件
* @throws Exception
*/
public Object getStaticProperty(String className, String fieldName)
throws Exception {
Class ownerClass = Class.forName(className);
Field field = ownerClass.getField(fieldName);
Object property = field.get(ownerClass);
return property;
}
/**
* 執行某物件方法
*
* @param owner
* 物件
* @param methodName
* 方法名
* @param args
* 引數
* @return 方法返回值
* @throws Exception
*/
public Object invokeMethod(Object owner, String methodName, Object[] args)
throws Exception {
Class ownerClass = owner.getClass();
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(owner, args);
}
/**
* 執行某類的靜態方法
*
* @param className
* 類名
* @param methodName
* 方法名
* @param args
* 引數陣列
* @return 執行方法返回的結果
* @throws Exception
*/
public Object invokeStaticMethod(String className, String methodName,
Object[] args) throws Exception {
Class ownerClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(null, args);
}
/**
* 新建例項
*
* @param className
* 類名
* @param args
* 建構函式的引數
* @return 新建的例項
* @throws Exception
*/
public Object newInstance(String className, Object[] args) throws Exception {
Class newoneClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Constructor cons = newoneClass.getConstructor(argsClass);
return cons.newInstance(args);
}
/**
* 是不是某個類的例項
* @param obj 例項
* @param cls 類
* @return 如果 obj 是此類的例項,則返回 true
*/
public boolean isInstance(Object obj, Class cls) {
return cls.isInstance(obj);
}
/**
* 得到陣列中的某個元素
* @param array 陣列
* @param index 索引
* @return 返回指定陣列物件中索引元件的值
*/
public Object getByArray(Object array, int index) {
return Array.get(array,index);