寫在最前
此系列文章是作者在最近參加了一些筆試面試之后進行的一波小總結(jié),本文為第一篇,將持續(xù)定期更新。
無論你認為自己的編程技術(shù)在同齡人中多么出類拔萃,自己寫過多少高技術(shù)含量paper,跟過多少學校的項目。一個你可能從未仔細探索的小問題很可能讓你的能力不受信任,相信不少人和我一樣筆試時后悔沒提前鞏固一下基礎(chǔ)知識。說多都是淚...
文中的部分例子和定義來自作者十星推薦,每個學java人都看過的《Thinking in Java》,推薦大家看英文原版,會很大程度加深對Java的理解。另外,問題提綱的總結(jié)來自作者最近研讀的機械工業(yè)出版社的《Java程序員面試筆試寶典》。文章主干,分析解釋部分全部由作者手打,標明網(wǎng)上資料忘記出處的,希望細心人指點。
1.static的作用
static,顧名思義,靜態(tài)。當類中的成員變量或方法聲明為static時,無需為此類創(chuàng)建參照,便可對其進行操作。即,不依賴該類的實例,同時也被該類的實例所共享。下面通過兩種static的使用情況進行分析:
- static變量
同為成員變量,與實例變量每創(chuàng)建一次便為其分配一次內(nèi)存不同,JVM只為static變量分配一次內(nèi)存,并在加載類的過程中完成該操作。可用類名直接訪問此變量或通過實例調(diào)用,前者是被推薦的,因為這樣不僅強調(diào)了該變量的static屬性,而且也在某種程度上使編譯器更容易去進行優(yōu)化。所以在對象之間有共享值或為了方便訪問某種變量時一般需要使用static變量。
- static方法
對于static方法,也同時可以通過類名調(diào)用或?qū)嵗{(diào)用。因此需要注意的是,static方法中不能用this或super關(guān)鍵字,不能直接訪問此方法所在類的實例變量或?qū)嵗椒?/strong>,只能訪問該類的靜態(tài)成員變量和方法。因為實例變量和方法有特定的對象,而靜態(tài)方法占據(jù)一個特定的數(shù)據(jù)區(qū)域。
舉例:
Class StaticTest{
static int i = 47;
int j = 10;
}
Class Incrementable{
static void increment(){
//通過類名直接對i進行操作
StaticTest.i++;
//此處無法對j進行訪問,因為其為實例變量
}
}
2.final的作用
final,在Java中通常解釋為不可變的,也就是說,final關(guān)鍵字是用來防止變化。一般我們在兩種情況下使用:設計(design)或效率(efficiency)。下面分情況來分析final關(guān)鍵字的作用:
- final數(shù)據(jù)——聲明類中屬性或變量
每種編程語言都有一種聲明常量的方法,java中便是final?;绢愋?Primitive Type)和引用類型(Object Reference Type)的屬性在聲明final后,里面存放的值都不可再改變。但有所不同的是,在基本類型中,這個值是實在的,比如100,"java";在引用類型中存放的是地址,所以final只是使其地址不可改變,這個地址所指的對象、數(shù)組皆是可以改變的。需要注意的是,static final的基本變量命名法,全大寫字母,單詞之間用"_"(underscore)連接。舉例:
public static final int VALUE_ONE = 9;
- final方法
使用final聲明方法主要是為了給方法加鎖以防止繼承的類對其內(nèi)容進行修改,也就是使其不可重寫(override)。因此final方法可以被繼承,但不能被重寫。
- final類
在前面加上final關(guān)鍵字的類是因為你不希望此類被繼承,換句話說,在某種設計情況下你的類永遠不需要去作出改變,或者為了安全原因你不希望它有子類。簡單舉例:
class SmallBrain{}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f(){}
}
//class Further extends Dinosaur{}此句無法執(zhí)行,因為Dinosaur類為final
public class Jurassic {
Public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++
}
}
還有一個網(wǎng)上看到的例子,忘記出處了:
final byte bt1 = 1;
final byte bt2 = 2;
byte bt3 = bt1 + bt2;
此例中若沒有final便會報錯,因為如果去掉final,bt1和bt2在運算時JVM將其自動轉(zhuǎn)換為了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多態(tài)性(Polymorphism)的體現(xiàn)。其中Overriding(重寫)是指父類與子類中,同一方法,相同名稱和參數(shù),重新寫其內(nèi)容,相當于“推翻”了父類中的定義。而Overloading(重載)是指在同一類中,定義多個同名方法,但其中的參數(shù)類型或次序不同。例子如下:
- 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最常用的兩種類的復用方法。要了解他們的區(qū)別,我們先來看一下英文定義。
- 組合
Achieved by using instance variables that refers to other objects.
- 繼承
A mechanism wherein a new class is derived from an existing class.
從字面意思來看,組合是在一個類中使用一個類的引用,通常說明一個類具有某種屬性,有"has a"的關(guān)系。比如下面的例子,灑水機"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類中創(chuàng)建了WaterSource類的參照,因此此段代碼中最先輸出的為"WaterSource",這就是最簡單的組合。
而繼承是一個新類派生于舊類的關(guān)系,那么也就是說具有舊類的屬性,有"is a"的關(guān)系,大家應該都對此非常熟悉,因此在此處不再舉例。
不難看出,當某物具有多項屬性時使用組合,比如汽車有引擎,車門,輪胎。當某物屬于一種某物時使用繼承,比如哈士奇是一種狗。
5.clone的作用
clone()是Java中用來復制對象的方法,由于Java取消了指針的概念,很多人對引用和對象的區(qū)別有所忽視,全面理解clone()將對此有很大幫助。
需要注意的是,當寫出如下代碼時
Student st1 = new Student("Daniel");
Student st2 = st1;
System.out.println(st1);
System.out.println(st2);
打印結(jié)果(地址)是完全相同的,因為st1與st2為一個對象的兩個引用。因此我們在使用"="操作符賦值是,只是進行了引用的復制。而clone()方法,才是真正實現(xiàn)對象復制的途徑,與上面的做對比:
Student st1 = new Student("Daniel");
Student st2 = (Student)st1.clone();
System.out.println(st1);
System.out.println(st2);
在上面的代碼中,由于clone()返回的是Object對象,所以需要進行向下強制轉(zhuǎn)換為Student。此時打印的結(jié)果就是兩個不同的地址,真正地完成了對象的復制。
- 深拷貝(Deep Copy)和淺拷貝(Shallow Copy)
這里我們又要再次提到基本數(shù)據(jù)類型(Primitive Type)和非基礎(chǔ)類型(Non-Primitive Type)的區(qū)別了,看下面一段代碼:
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是基本數(shù)據(jù)類型,在拷貝后沒有什么問題,然而name是String類型,因此前面提到的方法拷貝過來的只是一個引用值,便是淺拷貝。相對而言,深復制就是再創(chuàng)建一個相同的String對象,將這個對象的飲用賦值給拷貝出的新對象。
要實現(xiàn)深拷貝,我們需要先實現(xiàn)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();
}
}
需要注意的是,在套用多層對象的組合方法中,徹底的深拷貝需要在每一層對象中都實現(xiàn)cloneable接口并重寫clone()方法。非常復雜,在實際開發(fā)中用處不多,但如前文所題,有助于讀者對內(nèi)存結(jié)構(gòu)有深一步的了解。
6.內(nèi)部類(Inner Classes)
顧名思義,內(nèi)部類是指講一個類定義內(nèi)置于另一個類定義之中。關(guān)于它的作用,《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.
將邏輯相通的類聲明為內(nèi)部類使代碼更易控制或處理,在安卓開發(fā)中我們會經(jīng)常見到此類用法。因為每個內(nèi)部類都能獨立地繼承一個(接口的)實現(xiàn),所以無論外圍類是否已經(jīng)繼承了某個(接口的)實現(xiàn),對于內(nèi)部類都沒有影響?!陨虾隗w摘自《Thinking in Java》,如翻譯拗口請見諒。
下面我們來看一個包裹的例子,來簡單分析內(nèi)部類的使用方法。
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便是內(nèi)部類我們可以在非靜態(tài)(non-static)方法中對其進行調(diào)用,當再靜態(tài)方法如main()中對其進行調(diào)用時,需使用“外部類.內(nèi)部類”的方式對其進行引用。如,將上述代碼進行修改。
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")
}
}
在此例子中調(diào)用Contents以及Destination對象時,使用了“外部類.內(nèi)部類”的調(diào)用方法。
7.接口與抽象類
在國外論壇上對于接口(Interface)和抽象類(Abstract class)的討論就一直不斷。我們還是先來看一下它們的英文定義。
- 抽象類
Classes that contain one or more abstract methods.
- 接口
It is a collection of abstract methods.
乍一看貌似差不多,都是有關(guān)抽象方法的集合,關(guān)于它們的區(qū)別,僅從定義上來看,有兩點不同:
1.接口暗示著全部方法為抽象,且不能有任何的implementation。需要注意的是,這里有一個??嫉狞c就是,既然不能implement,那么接口可以繼承嗎?答案是可以的。相對于接口,抽象類中可以有執(zhí)行默認行為的實例方法,也就是可以有implementation。
2.在接口中聲明的變量默認為final類型,然而在抽象類中可以有非final類型的變量。
那么在應用上該如何選用這兩者呢,首先我們需要知道他們的本質(zhì)。接口是對動作的抽象,而抽象類則是對根源的抽象。比如人要吃東西,狗也要吃東西,那么我們就可以把吃東西作為一個接口。其中人和狗都屬于生物,那么我們就可以將生物定為一個抽象類。需要注意的是,一個類只可以繼承一個類(抽象類),但卻可以實現(xiàn)多個接口。理解這一點不難,上例中人和狗是生物,但不可能同時是非生物體。
我們來看一個經(jīng)典的報警門的例子,相信大部分人都見過:
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() { }
void close() { }
void alarm() { }
}
此例中,只要是門,固有屬性都會有開和關(guān),所以將門設置為抽象類,報警門只是門的一種。然而報警門的特殊功能是報警,因此我們將報警設置為一個接口。在報警門的這個類中,便繼承了門,實現(xiàn)了報警接口。
8.stack和heap
關(guān)于堆(heap)和棧(stack)的理解不只局限于java,無論是任何語言,對此概念區(qū)分理解都是非常重要的。stackoverflow是這么說的:
Stack is used for static memory allocation and Heap for dynamic memory allocation
stack是靜態(tài)內(nèi)存,而heap是動態(tài)分配。通俗來說,棧就是用來放引用的,當在一段代碼塊定義一個變量時,Java就在棧中為這個變量分配內(nèi)存空間,當超過變量的作用域后,Java會自動釋放掉為該變量所分配的內(nèi)存空間,該內(nèi)存空間可以立即被另作他用。而堆是用來放對象和數(shù)組的,在Java中,這些對象和數(shù)組是由new來創(chuàng)建的,而在堆中分配的內(nèi)存,將由Java虛擬機的自動垃圾回收器來管理。
關(guān)于實例中的關(guān)系,只要你能理解上文中提到過的clone()方法,理解起來非常輕松。
9.== 和 equals
作為經(jīng)常出現(xiàn)在condition里的判斷方法,區(qū)分==和equals是非常重要的,尤其是在安卓的開發(fā)中。關(guān)于兩者的比較,我們要聯(lián)合前文提到的堆和棧來理解。" == "是比較兩個變量棧中的內(nèi)容是否相等,而與之相對應的"equals"則是比較堆中的內(nèi)容是否相等。讓我們來舉例說明:
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地址不同,但內(nèi)容相同。
10. 反射機制
反射機制是我們在開發(fā)過程中用的最多的,但可能你對他的文字定義并不了解:
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
也就是說,對于一個類,通過反射機制,我能知道他的屬性和方法;對于我創(chuàng)建的一個對象,我能夠調(diào)用他的屬性與方法。這種獲得信息以及調(diào)用對象是動態(tài)(Dynamic)的。我們通過stackoverflow上一個簡單的例子來理解:
public class TestReflect {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "Java reflection");
System.out.println(list.get(0));
}
}
Java中所有的對象都有g(shù)etClass()方法,用以獲取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;
/**
* <a class='replace_word' title="Java 知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>Java </a>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;
}
/**
* 得到某類的靜態(tài)公共屬性
*
* @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;
}
/**
* 執(zhí)行某對象方法
*
* @param owner
* 對象
* @param methodName
* 方法名
* @param args
* 參數(shù)
* @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);
}
/**
* 執(zhí)行某類的靜態(tài)方法
*
* @param className
* 類名
* @param methodName
* 方法名
* @param args
* 參數(shù)數(shù)組
* @return 執(zhí)行方法返回的結(jié)果
* @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
* 構(gòu)造函數(shù)的參數(shù)
* @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);
}
/**
* 得到數(shù)組中的某個元素
* @param array 數(shù)組
* @param index 索引
* @return 返回指定數(shù)組對象中索引組件的值
*/
public Object getByArray(Object array, int index) {
return Array.get(array,index);
}
}
關(guān)于反射機制更多的信息在作者的另一篇文章中有詳細說明:http://www.itdecent.cn/p/381ec446a318
結(jié)語
本文到此處告一段落,感謝大家的閱讀。我也曾拜讀很多“高科技”的文章去學習實現(xiàn)很多拉風的安卓特效,然而基礎(chǔ)知識無時無刻都在敲響警鐘,我堅信基礎(chǔ)知識是解決問題的最強后勤保障。
最后,文章還有很多不盡人意指出,如果有錯誤和建議還請大家指出,作為新人只希望通過總結(jié)能與大家一起提高。本文將持續(xù)連載,希望大家多多支持。有疑問的歡迎加我的weibo:LightningDC進行交流。