31. 反射的作用與原理
- import java.lang.reflect.Field;
- import java.lang.reflect.Method;
- class A {
- private int varA;
- public void myPublicA() {
- System.out.println("I am public in A !");
- };
- private void myPrivateA() {
- System.out.println("I am private in A !");
- };
- }
- class B extends A {
- public int varB;
- public void myPublicB(){};
- }
- public class Main {
- public static void main(String[] args) throws Exception {
- B b = new B();
- // 子類方法
- Method methods[] = b.getClass().getMethods();
- for (Method method : methods)
- System.out.println(method);
- System.out.println("");
- // 子類變量
- Field fields[] = b.getClass().getFields();
- for (Field field : fields)
- System.out.println(field);
- // 基類
- System.out.println("\n" + b.getClass().getSuperclass() + "\n");
- // 基類private方法也不能避免
- Class superClass = b.getClass().getSuperclass();
- methods = superClass.getDeclaredMethods();
- for (Method method : methods) {
- System.out.println(method);
- method.setAccessible(true);
- // 實例化A來調用private方法!
- method.invoke(superClass.newInstance(), null);
- }
- }
- }
在上面的例子中,我們用一個子類B,通過反射找到了他的數據域、與方法,還找到了他的基類A。更甚者,我們實例化了基類A,還調用了A里面的所有方法,甚至是private方法。從以上的例子相信大家都感受到了反射的威力了,運用使用class對象和反射提供的方法我們可以輕易的獲取一個類的所有信息,包括被封裝隱藏起來的信息,不僅如此我們還可以調用獲取的信息來構造實例和調用方法。以上的展示只是反射的冰山一角,反射在動態代理和調用隱藏API等黑科技方面還發揮着重要的作用,博主在此不做深入探討。
32. 泛型常用特點,List<String>能否轉為List<Object>
- List<String> listString = new ArrayList<>();
- // error : Type mismatch: cannot convert from List<String> to List<Object>
- List<Object> listObject = listString;
- // it's ok !
- List<? extends Object> listExtendsObject = listString;
- // error : The method add(capture#1-of ? extends Object) in the type List<capture#1-of ? extends Object> is not applicable for the
- // arguments (String)
- listExtendsObject.add("string");
- // it's ok !
- listExtendsObject.add(null);
- // it's ok !
- Object object = listExtendsObject.get(0);
接下來講講泛型常用的特點,利用泛型我們可以實現以下內容:
1.帶參數類型的類,泛型類
- class A<T,S> {
- }
2.帶參數類型的方法,泛型方法
- public <T> void f(T x) {
- }
使用泛型方法時通常不必指明參數類型,編譯器會為我們找出具體類型,這稱為類型參數推斷。
3.關鍵字
- class A {}
- class B extends A {}
- class C {};
- //代表了T為A或A的子類
- class D <T extends A> {};
- public class Main {
- public static void main(String[] args) {
- D<A> a;
- D<B> b;
- // no work!
- D<C> c;
- }
- }
4.擦除
- import java.util.Arrays;
- class A {}
- class B extends A {}
- class C extends B {};
- class D <T> {
- T t;
- D(T t) {
- this.t = t;
- }
- public void f() {
- System.out.println(Arrays.toString(this.getClass().getTypeParameters()));
- }
- };
- public class Main {
- public static void main(String[] args) {
- D<A> a = new D<A>(new A());
- D<B> b = new D<B>(new B());
- D<C> c = new D<C>(new C());
- a.f();
- b.f();
- c.f();
- }
- }

- class A {
- public void fa() {};
- }
- class C <T> { // 擦除到Object
- T t;
- public void f(Object a) {
- if (a instanceof T) {} // error,不知道具體的類型信息
- T var = new T(); // error,不知道該類型是否有默認構造函數
- T[] array = new T[1]; // error
- t.fa(); // error
- }
- };
- class D <T extends A> { // 擦除到A
- T t;
- public void f(Object a) {
- t.fa(); // this works
- }
- };
33. 解析XML的幾種方式的原理與特點:DOM、SAX、PULL
1.DOM
- 使用DocumentBuilderFactory.newInstance方法獲取DOM工廠實例
- 使用工廠的newDocumentBuilder方法獲取builder
- 使用builder的parse方法解析xml獲取生成的Document對象
- 使用Document的getDocumentElement方法獲取根節點
- 調用根節點的getChildNodes方法遍歷子節點
2.SAX
使用SAX解析主要步驟如下:
- 調用SAXParserFactory.newInstance獲取SAX工廠實例
- 調用工廠的newSAXParser方法獲取解析器
- 調用解析器的getXMLReader獲取事件源reader
- 調用setContentHandler方法為事件源reader設置處理器
- 調用parse方法開始解析數據
3.PULL
- 讀取到xml的聲明返回 START_DOCUMENT
- 讀取到xml的結束返回 END_DOCUMENT
- 讀取到xml的開始標簽返回 START_TAG
- 讀取到xml的結束標簽返回 END_TAG
- 讀取到xml的文本返回 TEXT
- 使用XmlPullParserFactory.newInstance方法獲取Pull工廠
- 調用工廠newPullParser方法返回解析器
- 使用解析器setInput方法設置解析文件
- 調用next方法解析下一行,調用getEventType方法獲取當前的解析情況
- public static void main(String[] args) throws Exception {
- // 桌面的xml文件,文件內容如下
- // <all name="testData">
- // <item first="1" second="A" third="一"/>
- // <item first="2" second="B" third="二"/>
- // <item first="3" second="C" third="三"/>
- // <item first="4" second="D" third="四"/>
- // <item first="5" second="E" third="五"/>
- // </all>
- File file = new File("C:/Users/Administrator/Desktop/test.xml");
- // 文件流
- FileInputStream fis = new FileInputStream(file);
- {
- // DOM解析xml
- System.out.println("DOM:");
- // 獲取DOM工廠實例
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- // 生成builder
- DocumentBuilder builder = factory.newDocumentBuilder();
- // 解析文件
- Document document = builder.parse(file);
- // 獲取根節點
- Element element = document.getDocumentElement();
- System.out.println(element.getTagName() + " " + element.getAttribute("name"));
- // 獲取子節點列表
- NodeList nodeList = element.getChildNodes();
- for (int i = 0; i < nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- // 節點類型為元素節點
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element child = (Element) node;
- // 輸出標簽名和元素內容
- System.out.println(child.getTagName() + " " + child.getAttribute("first") + " "
- + child.getAttribute("second") + " " + child.getAttribute("third"));
- }
- }
- }
- System.out.println(""); // empty line
- {
- // SAX解析xml
- System.out.println("SAX:");
- // 獲取SAX工廠實例
- SAXParserFactory factory = SAXParserFactory.newInstance();
- // 獲取SAX解析器
- SAXParser parser = factory.newSAXParser();
- // 獲取reader
- XMLReader reader = parser.getXMLReader();
- // 設置解析源和處理器
- reader.setContentHandler(new MySAXHandler()); // 在parse之前設置
- reader.parse(new InputSource(fis));
- }
- }
- // 自定義SAX處理器
- static class MySAXHandler extends DefaultHandler {
- @Override
- public void startDocument() throws SAXException {
- // 解析文檔開始時調用
- super.startDocument();
- }
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
- // 解析元素開始時調用
- // 打印元素名
- System.out.print(qName);
- // 打印元素屬性
- for (int i = 0; i < attributes.getLength(); i++)
- System.out.print(" " + attributes.getValue(i));
- System.out.println("");
- super.startElement(uri, localName, qName, attributes);
- }
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- // 解析元素結束時調用
- super.endElement(uri, localName, qName);
- }
- @Override
- public void endDocument() throws SAXException {
- // 解析文檔結束時調用
- super.endDocument();
- }
- }
- XmlPullParserFactory factory = XmlPullParserFactory
- .newInstance();
- XmlPullParser xmlPullParser = factory.newPullParser();
- // use gb2312 encode
- ByteArrayInputStream is = new ByteArrayInputStream(
- response.getBytes("GB2312"));
- xmlPullParser.setInput(is, "GB2312");
- int eventType = xmlPullParser.getEventType();
- String provinceName = "";
- String provinceCode = "";
- while (eventType != XmlPullParser.END_DOCUMENT) {
- String nodeName = xmlPullParser.getName();
- switch (eventType) {
- // start parse node
- case XmlPullParser.START_TAG:
- if ("city".equals(nodeName)) {
- provinceName = xmlPullParser.getAttributeValue("",
- "quName");
- provinceCode = xmlPullParser.getAttributeValue("",
- "pyName");
- Province province = new Province();
- province.setProvinceName(provinceName);
- province.setProvinceCode(provinceCode);
- coolWeatherDB.saveProvince(province);
- }
- break;
- default:
- break;
- }
- eventType = xmlPullParser.next();
本人對三種解析方法的總結如下:
- 需要解析小的xml文件,需要重復解析xml文件或需要對xml文件中的節點進行刪除修改排序等操作:使用DOM
- 需要解析較大的xml文件,只需要解析一次的xml文件:使用SAX或Pull
- 只需要手動解析部分的xml文件:使用Pull
34. Java與C++對比
Java是由C++發展而來的,保留了C++的大部分內容,其編程方式類似於C++,但是摒棄了C++的諸多不合理之處,Java是純面向對象的編程語言。Java和C++的區別主要如下:
1.都是類與對象
在Java中,一切的組件都是類與對象,沒有單獨的函數、方法與全局變量。C++中由於可以使用C代碼,故C++中類對象與單獨的函數方法共存。
2.多重繼承
在C++中類可以多重繼承,但這有可能會引起菱形問題。而在Java中類不允許多重繼承,一個類只能繼承一個基類,但可以實現多個接口,避免了菱形問題的產生。
3.操作符重載
C++允許重載操作符,而Java不允許。
4.數據類型大小
在C++中,不同的平台上,編譯器對基本數據類型分別分配不同的字節數,導致了代碼數據的不可移植性。在Java中,采用基於IEEE標准的數據類型,無論任何硬件平台上對數據類型的位數分配總是固定的。(然而boolean基本類型要看JVM的實現)
5.內存管理
C++需要程序員顯式地聲明和釋放內存。Java中有垃圾回收器,會在程序內存不足或空閑之時在后台自行回收不再使用的內存,不需要程序員管理。
6.指針
指針是C++中最靈活也最容易出錯的數據類型。Java中為了簡單安全去掉了指針類型。
7.類型轉換
C++中,會出現數據類型的隱含轉換,涉及到自動強制類型轉換。Java中系統要對對象的處理進行嚴格的相容性檢查,防止不安全的轉換。如果需要,必須由程序顯式進行強制類型轉換。(如int類型不能直接轉換為boolean類型)
8.方法綁定
C++默認的方法綁定為靜態綁定,如果要使用動態綁定實現多態需要用到關鍵字virtual。Java默認的方法綁定為動態綁定,只有final方法和static方法為靜態綁定。
目前博主就想到這么多,還有的以后再補充。
35. Java1.5、1.7與1.8新特性
JDK1.5:
- switch允許傳入字符串
- 泛型實例化類型自動推斷:List<String> tempList = new ArrayList<>()
- 對集合的支持,創建List / Set / Map 時寫法更簡單了,如:List< String> list = ["item"],String item = list[0],Set< String > set = {"item"}等
- 允許在數字中使用下划線
- 二進制符號加入,可用作二進制字符前加上 0b 來創建一個二進制類型:int binary = 0b1001_1001
- 一個catch里捕捉多個異常類型,‘|’分隔
JDK1.8:
- public class Main {
- public static void main(String[] args) throws Exception {
- // 動態庫名字,windows平台自動拓展成makeStr_jni.dll
- System.loadLibrary("makeStr_jni");
- // 打印字符串
- printString("Java World!");
- }
- // native關鍵字表示為本地方法
- public static native void printString(String str);
- }
- 使用native關鍵字聲明某方法為本地方法
- 使用System.loadLibrary加載由C/C++編寫成的動態鏈接庫(我們只需要寫出庫名字即可,Java會根據平台補充庫的全名windows:dll,linux:so)



- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include "jni.h"
- /* Header for class Main */
- #ifndef _Included_Main
- #define _Included_Main
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: Main 方法所在的類
- * Method: printString 方法名
- * Signature: (Ljava/lang/String;)V 簽名
- */
- JNIEXPORT void JNICALL Java_Main_printString
- (JNIEnv *, jclass, jstring);
- #ifdef __cplusplus
- }
- #endif
- #endif
可以看到我們在Java層定義的printString方法對應成了Native層的Java_Main_printString方法,方法上面有javah給我們生成的注釋,它提供了以下信息:
- Class:方法所屬於的類
- Method:方法的名稱
- Signature:方法的簽名。簽名是由於Java中的方法允許重載,僅僅通過類與名稱並不能確定該Native方法所對應的Java方法,因此JNI技術中就將參數類型和返回值的組合作為了一個函數的簽名信息,有了簽名和函數名我們才能順利找到Javac層中對應的函數
- #include<iostream>
- #include"_Main.h"
- using namespace std;
- /*
- * JNIEnv : env JNI環境,一個提供JNI系統函數的結構體
- * jclass : clazz 代表Java層的Class對象,由於printString方法是一個靜態方法,故傳入Class對象
- * jstring : s 代表Java層的String對象,表示傳入的參數
- */
- JNIEXPORT void JNICALL Java_Main_printString
- (JNIEnv * env, jclass clazz, jstring s) {
- jboolean iscopy;
- // 通過jstring對象生成本地字符串
- const char *charData = env->GetStringUTFChars(s, &iscopy);
- // 打印字符串
- cout << "A message from Native World: " << charData << endl;
- // 釋放資源
- env->ReleaseStringUTFChars(s, charData);
- }
以上代碼相信注釋已解釋的非常清楚,故此處不再贅述。值得一提的是在Native層中有多種與Java層中相對應的數據結構,如:jclass代表Class對象,jstring代表String對象,jboolean代表boolean基本類型等。有興趣的童鞋可以自行去了解下。

