JAVA面試 基礎增強與鞏固:反射、註解、泛型等

做者-煥然一璐,支持原創,轉載請註明出處,謝謝合做。 原文連接:http://www.jianshu.com/p/aaf8594e02ebjava

企業重視的是學習能力:基礎很重要android

###JDK1.5新特性程序員

  1. 泛型
  2. foreach
  3. 自動拆箱裝箱
  4. 枚舉
  5. 靜態導入(Static import)
  6. 元數據(Metadata)
  7. 線程池
  8. 註解

###JDK1.6新特性算法

  1. Desktop類和SystemTray類
  2. 使用JAXB2來實現對象與XML之間的映射
  3. StAX
  4. 使用Compiler API
  5. 輕量級Http Server API
  6. 插入式註解處理API(Pluggable Annotation Processing API)
  7. 用Console開發控制檯程序
  8. 對腳本語言的支持
  9. Common Annotations

###JDK1.7新特性編程

1 對集合類的語言支持; 2 自動資源管理; 3 改進的通用實例建立類型推斷; 4 數字字面量下劃線支持; 5 switch中使用string; 6 二進制字面量; 7 簡化可變參數方法調用。設計模式

###JDK1.8新特性數組

  1. 接口的默認方法,也就是接口中能夠有實現方法
  2. 函數式接口與靜態導入
  3. Lambda 表達式
  4. 訪問局部變量

靜態導入:JDK1.5新特性緩存

//靜態導入某一個方法
import static java.lang.Math.abs;
//靜態導入一個類的全部靜態方法
import static java.lang.Math.*;

public static void main(String[] args) {
	//方法能夠直接使用
    System.out.println(abs(-100));
}


//若是類裏面本來就有同名的方法的話,就會覆蓋掉靜態導入的方法了
public static void abs() {

}
複製代碼

###可變參數安全

沒有可變參數以前,實現不定長參數只能經過方法的重載實現,可是這樣作工做量很大。微信

沒有可變參數以前,實現可變參數的方法能夠經過Obiect[]來實現。

  1. 可變參數只能放在參數列表的最後一個

  2. 編譯器隱含爲可變參數建立數組,所以能夠經過數組的方式去使用可變參數

    public static int add(int x, int... args) {
         int sum = x;
    
         for (int i = 0; i < args.length; i++) {
             sum += args[i];
         }
    
         return sum;
     }
    複製代碼

###加強for循環

//數組或者實現了Iterable接口的對象
    for 修飾符 Type arg : 要迭代的對象) {
        
    }
複製代碼

###基本數據類型的自動裝箱與拆箱

//自動裝箱示例,自動將基本數據類型包裝成對象
    Integer i1 = 1000;
    Integer i2 = 1000;

    //自動拆箱示例,自動將對象解包成基本數據類型
    System.out.println(i1 + 10);


    //若是數值在-128到127以前,對象會複用(享元設計模式)
    System.out.println(i1 == i2);
複製代碼

-128到127會緩衝起來,節省內存。這是享元設計模式的應用,內部狀態/外部狀態(可變)

###枚舉

####爲何要使用枚舉? 好比,咱們要使用1-7表明星期一到星期天,那麼一般咱們會想到的作法作,定義一個類,而且提供一些公有的靜態常量,例如:

public class WeekDay {
    public static final int SUN = 1;
    public static final int MON = 2;
}
複製代碼

可是當咱們使用的時候,有些人可能不想去理會這個細節,好比會直接傳入1(可能他本身以爲1是表明星期一的),所以運行的時候就會出現一些意想不到的問題。

爲了解決這個問題,java 5從新引入枚舉,保證變量的值只能取指定值中的某一個,經過在代碼編譯的時候就能夠知道傳的值是否合法。

####枚舉的模擬:

/**
 * 本身手動實現枚舉類
 * 1. 構造方法必須是私有的
 * 2. 提供公有的靜態成員變量表明枚舉,而且經過匿名內部子類去生成對象
 * 3. 對外提供的方法必須是抽象的
 */
public abstract class WeekDay {

    public static WeekDay MON = new WeekDay(0) {
        @Override
        public WeekDay next() {
            return SUN;
        }

        @Override
        public String toString() {
            return "SUN";
        }

    };

    public static WeekDay SUN = new WeekDay(1) {
        @Override
        public WeekDay next() {
            return MON;
        }

        @Override
        public String toString() {
            return "MON";
        }
    };

    private int num;

    //私有構造方法
    private WeekDay() {

    }

    //在生成匿名內部類的時候,能夠傳參給父類的構造函數
    private WeekDay(int num) {
        this.num = num;
    }

    //對外提供的抽象方法,由子類去實現
    public abstract WeekDay next();

}
複製代碼

一些關鍵的點已經在上面的註釋中給出,在使用的時候,咱們只能經過這樣去生成WeekDay對象。(實際上枚舉內部也是生成對象嘛)

WeekDay weekDay = WeekDay.MON;
weekDay.next();
複製代碼

枚舉的實現

public enum WeekDay {

    //枚舉對象必須放在最前面,匿名內部類的建立能夠帶參數,必須實現父類的抽象方法
    MON(1) {
        public WeekDay next() {
            return SUN;
        }
    },

    SUN(2) {
        public WeekDay next() {
            return MON;
        }
    };

    private int num;

    //枚舉的構造函數是默認爲private的,能夠帶參數
    WeekDay(int num) {
        this.num = num;
    }

    public abstract WeekDay next();

}
複製代碼

枚舉使用,以及一些枚舉特有的方法:

//使用方法,跟通常的對象是如出一轍的
WeekDay w = WeekDay.MON;
//直接打印枚舉對象其實是調用了toString
System.out.println(w);
//打印枚舉的名字,實際上打印類的簡短的名字w.getClass().getSimpleName()
System.out.println(w.name());
//打印枚舉對象在枚舉中的位置,0開始算
System.out.println(w.ordinal());
//經過字符串去或者獲取(構造)枚舉對象
System.out.println(WeekDay.valueOf("MON"));

//獲取枚舉類的全部對象,經過數組的方式去遍歷
for (WeekDay value : WeekDay.values()) {
    System.out.println(value);
}
複製代碼

####枚舉的特殊用法---利用枚舉能夠簡簡單單就實現單例模式

###反射 -- JDK1.2就有了

瞭解反射的基礎--Class類

用來描述Java類的類就是Class這個類。

每一個類在java虛擬機中佔用一片內存空間,裏面的內容就是對應這個類的字節碼(Class)

####Class字節碼的獲取:

類名.class
對象.getClass
Class.forName("類的全名");
複製代碼

其中,跟反射密切相關的就是forName這個方法,經過類名去獲取字節碼。前面兩種都是虛擬機中已經加載過了。forName方法在當虛擬機中沒有加載過對應字節碼的時候,就會去動態地加載進來;當已經加載過的時候,直接複用加載過的。

####九種預約義好的基本Class字節碼

八種數據類型,外加void也有對應的字節碼。下面給出一些例子:

Class<Integer> type = Integer.TYPE;
Class<Integer> integerClass = int.class;
Class<Void> voidClass = Void.class;
複製代碼

例子:

public static void main(String[] args) throws Exception {
    
    Class<?> c1 = Class.forName("java.lang.String");
    Class<String> c2 = String.class;
    Class<? extends String> c3 = new String("123").getClass();

	//返回的都是同一份字節碼,所以==
    System.out.println(c1 == c2);
    System.out.println(c2 == c3);

	//判斷是否是基本類型(九種)
    System.out.println(int.class.isPrimitive());
    System.out.println(int[].class.isPrimitive());
	
	//判斷是否是數組(數組也是一種類型,全部類型均可以反射)
    System.out.println(int[].class.isArray());
    
}
複製代碼

####反射的概念

一句話總結:反射就是把Java類中的各類成分經過java的反射API映射成相應的Java類,獲得這些類之後就能夠對其進行使用。好比方法,構造方法,成員變量,類型,包等。下面分別取講解。

Constructor類

獲得全部的構造方法

Constructor<?>[] constructors = Class.forName("java.lang.String").getConstructors();
複製代碼

獲得指定參數的某一個構造方法:

Constructor<?> constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
複製代碼

建立對象

String s = (String) constructor.newInstance(new StringBuffer("abc"));
System.out.println(s.charAt(2));

也能夠直接調用無參數的構造,實際上也是先找到Constructor再調用Constructor的newInstance
String s = (String) Class.forName("java.lang.String").newInstance();
複製代碼

查看源碼能夠發現,Class的newInstance方法中有把Constructor緩存起來的。由於反射的使用會大大下降系統的性能,對於計算機來講比較耗時,並且也容易發生運行時異常,所以須要慎重使用。

Field對象

Field表明類中的一個成員變量

例如咱們有一個測試的類Point

public class Point {

    public int x;
    private int y;
    protected int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
複製代碼

那麼,咱們能夠利用發射機制去獲得虛擬機中某個對象的成員變量,而且獲取他們的值

Point point = new Point(1, 2, 3);

//經過反射拿到成員變量的值
Field x = point.getClass().getField("x");
System.out.println(x.get(point));

//若是是私有或者保護變量的話,不能拿到Field,會報找不到Field的錯誤
//Field y = point.getClass().getField("y");

//私有變量須要經過使用getDeclaredField去獲取,若是要獲取值的話,須要設置setAccessible爲true
//這樣的反射比較暴力
Field y = point.getClass().getDeclaredField("y");
y.setAccessible(true);
System.out.println(y.get(point));
複製代碼

例子,把對象中全部String類型的值中的a修改成*:

public class Reflecttest1 {

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        changeString(test);
        System.out.println(test);
    }

    private static void changeString(Object obj) throws Exception {
        for (Field field : obj.getClass().getFields()) {
            
            //判斷是否是String類型,注意這裏最好使用==
            if (field.getType() == String.class) {
                String oldV = (String) field.get(obj);
                String newV = oldV.replace('a', '*');
                field.set(obj, newV);
            }
        }
    }
}
複製代碼

####Method類

String a = "0123";
//獲取方法,而且調用
Method charAt = a.getClass().getMethod("charAt", int.class);
//經過反射調用方法,第一個參數是對象,若是爲靜態方法的話,應該傳null
//第二個參數是可變長參數,傳入的是實參
System.out.println(charAt.invoke(a, 1));
複製代碼

調用指定對象的main方法,注意其中傳遞字符串數組的問題

因爲可變長參數的問題,jdk爲了兼容1.4如下的版本,會把傳進去的數組進行拆包。所以註釋代碼會報參數不匹配的錯。

解決方案是

一、再利用Object數組包裝一層,告訴編譯器,能夠拆包,可是拆開以後就是一個String數組。

二、相強制轉換爲Object對象,告訴編譯器,不要拆包。

public class ReflectTest3 {

    public static void main(String[] args) throws Exception {

        Method mainMethod = Class.forName("com.nan.test.T").getMethod("main", String[].class);
//        mainMethod.invoke(null, new String[]{"123"});
        mainMethod.invoke(null, new Object[]{new String[]{"123"}});
        mainMethod.invoke(null, (Object) new String[]{"123"});

    }

}

class T {
    public static void main(String[] args) {
        for (String s : args) {
            System.out.println(s);
        }
    }
}
複製代碼

####數組類型

具備相同的維度以及元素類型的數組,屬於同一種Class類型。

例子:

int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] a4 = new String[3];

System.out.println(a1.getClass());
System.out.println(a2.getClass());
System.out.println(a3.getClass());
System.out.println(a4.getClass());
複製代碼

輸出結果:

class [I
class [I
class [[I
class [Ljava.lang.String;
複製代碼

其中[表明是數組類型,I表明元素類型(int)

八種基本類型的數組不能轉換成Object數組。所以下面的語句不合法:

Object[] objects = a1;

//由於基本類型的一維數組不能當成Object數組,所以只能當作是一整個數組對象
//所以打印出來的結果是數組的對象的值,而不是裏面的內容
//那麼,按照JDK1.5的可變長參數語法,只能解析成一個數組對象
System.out.println(Arrays.asList(a1));
//String類型能夠轉換成Object數組打印的是內容
System.out.println(Arrays.asList(a4));
複製代碼

####數組

能夠經過反射來對數組進行操做,由於是Object數組,所以不能肯定整個數組是同一個類型,所以只能肯定的是每一項的類型。

private static void printObj(Object o) {
    //判斷是否是數組類型
    if (o.getClass().isArray()) {
        //經過反射APIArray去遍歷數組
        int length = Array.getLength(o);
        for (int i = 0; i < length; i++) {
            Object item = Array.get(o, i);
            System.out.println(item);
        }
    } else {
        System.out.println(o);
    }
}
複製代碼

equals與hashCode的聯繫與區別。

  1. 一個類的兩個實例對象equals的時候,hashCode也必須相等,反之不成立。
  2. hashCode只有在hash集合中才有意義,好比hashMap、hashSet等。當對象被存入hash集合或者從hash集合中移除、contains方法調用等的時候,先會經過計算hash值,算出對象應該在的存儲區域,而後再經過在這塊存儲區域,裏面經過equals方法去判斷對象是否重複。(hash相等,equals能夠不相等)

通常來講,兩個都須要重寫,並且在對象插入了hash集合之後,不要再修改這個對象與hash計算有關的數值了,由於這樣會致使hash集合根據變化以後的hash值找不到這個對象了,對象不能被清理,從而形成內存泄漏。

HashSet<Point> set = new HashSet<>();
Point p0 = new Point(1,2,3);
Point p1 = new Point(1,2,3);

set.add(p0);
//若是重寫了hashCode方法,這個時候不重寫equals方法,那麼這個對象能夠被插入
//若是重寫了hashCode以及equals方法,那麼這個對象不能夠被插入
set.add(p1);

//數值改變了,對象不能被移除(找到),從而形成內存泄漏
p0.x = 10000;
set.remove(p0);

System.out.println(set.size());
複製代碼

而通常的非hash集合,例如ArrayList,只保存數據的引用,數據是能夠重複的。

Java反射的做用 -- 實現框架功能

框架與工具的區別:

相同:都是其餘人提供的

不一樣點:

  1. 工具類是被用戶調用
  2. 框架是調用用戶提供的類

例如:

  1. 框架提供配置文件,用戶能夠配置,例如類名
  2. 讀取用戶的配置文件
  3. 經過反射加載對應的類,而且動態去使用

配置文件的讀取

//必定要用完整的路徑,不是硬編碼,而是運算出來的
InputStream is = new FileInputStream("文件目錄");
Properties properties = new Properties();
properties.load(is);

String value = properties.getProperty("key");
複製代碼

通常配置文件的加載基本都是利用類加載器來加載。

//經過類加載器能夠把與類放在一塊兒的配置文件讀取出來,這裏是與類相對路徑。若是寫上/表明是絕對路徑,須要寫完整/包名。。。。。
InputStream res = ReflectTest3.class.getResourceAsStream("文件目錄");
//InputStream res = ReflectTest3.class.getClassLoader().getResourceAsStream("文件目錄");
properties.load(res);
複製代碼

內省,與java Bean

java bean的概念:符合必定get、set規則的java類

java bean的屬性名爲get、set方法去掉get、set前綴,剩下的-- 若是第二個字母也是小寫的話,那麼 -- 首字母須要變成小寫

例如:

getAge-->age

setCPU-->CPU
複製代碼

內省:操做Java Bean對象的API

Bean b = new Bean(1);

String propertyName = "x";

//普通方法x-->X-->getX-->調用
//下面使用內省的方法去獲取get方法,set方法也是同一個道理
PropertyDescriptor pd = new PropertyDescriptor(propertyName, b.getClass());
Method getXMethod = pd.getReadMethod();
System.out.println(getXMethod.invoke(b, null));
複製代碼

比較麻煩的寫法,經過BeanInfo去作

BeanInfo beanInfo = Introspector.getBeanInfo(b.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor p : pds) {
    if (p.getName().equals("x")) {
        Method readMethod = p.getReadMethod();
        readMethod.invoke(b, null);
    }
}
複製代碼

註解 Annotation

小知識:

  1. 類型名:形容詞+名詞
  2. 方法名:動詞+名詞

####註解的概念 註解其實是一個類,寫註解其實是建立註解的對象。註解至關於爲程序打一種標記。javac編譯工具、開發工具以及其餘程序能夠利用反射來了解你的類以及各類元素上有沒有何種標記,就能夠去作相應的事。

標記能夠加在包、類型(Type,包括類,枚舉,接口)、字段、方法、方法的參數以及局部變量上面。

下面是java的一些比較常見的註解:

Deprecated				//標記方法過期
Override 				//標記該方法是子類覆蓋父類的方法,告訴編譯器去檢查
SuppressWarnings 		//去掉過期的刪除線
複製代碼

####註解的應用結構圖

一共須要三個類:

例子:

這是一個註解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by Administrator on 2016/9/16.
 */

@Retention(RetentionPolicy.RUNTIME)//註解保留在哪一個聲明週期
@Target({ElementType.METHOD, ElementType.TYPE})//做用於什麼元素身上
public @interface A {

}
複製代碼

註解做用的類:

@A
public class AnnotationTest {

}
複製代碼

獲取註解(經過對AnnotationTest進行反射):

Class<AnnotationTest> c = AnnotationTest.class;
//判斷是否有對應的註解
if (c.isAnnotationPresent(A.class)) {
	//獲取註解
    A a = c.getAnnotation(A.class);
}
複製代碼

####元註解,用來給註解添加註解的註解

@Target(ElementType.METHOD)			//聲明註解能夠做用在什麼地方,特別注意TYPE是類、枚舉、接口身上
@Retention(RetentionPolicy.SOURCE)	//聲明註解是在哪一個聲明週期的,分別是源代碼,class文件,運行時
複製代碼

####注意,多個屬性的話須要用大括號括起來:

@Target({ElementType.METHOD, ElementType.TYPE})
複製代碼

####註解的生命週期

  1. SOURCE:註解被保留到源文件階段。當javac把源文件編譯成.class文件的時候,就將相應的註解去掉。例如常見的Override、SuppressWarnings都屬於SOURCE類型的生命週期,由於一旦代碼編譯以後該註解就沒用了。
  2. CLASS:java虛擬機經過類加載器向內存中加載字節碼的時候,就將相應的註解去掉,所以沒法經過反射獲取相應的註解。
  3. RUNTIME:註解保留在內存中的字節碼上面了,虛擬機在運行字節碼的時候,仍然可使用的註解。例如Deprecated,類被別人使用的時候,加載到內存,掃描,從二進制代碼去看是否過期,而不是檢查源代碼。

####爲註解增長屬性

註解參數的可支持數據類型:

  1. 全部基本數據類型(int,float,boolean,byte,double,char,long,short)
  2. String類型
  3. Class類型
  4. enum類型
  5. Annotation類型
  6. 以上全部類型的數組

例子:

@Retention(RetentionPolicy.RUNTIME)//註解保留在哪一個聲明週期
@Target({ElementType.METHOD, ElementType.TYPE})//做用於什麼元素身上
public @interface A {

    String stringAttr();

    Class value() default Object.class;

    int[] arrAttr();

    Deprecated annoAttr() default @Deprecated;
}
複製代碼

在使用註解的時候注意點:

  1. 當只有value須要設置值的時候(即只有value屬性或者其餘屬性已經制定了default的時候),能夠直接不寫value,直接在括號裏面寫值,例如:

    @SuppressWarnings("deprecation")

  2. 當類型是數組的時候,若是元素只有一個,那麼能夠省略大括號,直接寫一個元素便可。

經過反射得到註解以後,就能夠隨心去使用了:

Class<AnnotationTest> c = AnnotationTest.class;
if (c.isAnnotationPresent(A.class)) {
    A a = c.getAnnotation(A.class);
	//得到stringAttr屬性的值
    System.out.println(a.stringAttr());
}
複製代碼

泛型

####概念

集合,反射等等地方都使用到了泛型,免去了強制類型轉換的不安全性問題,包括code階段以及運行階段。泛型是給編譯器看的,讓編譯器攔截源程序中的非法輸入,編譯完之後就會去掉類型信息,保證程序的運行效率。對於參數化的泛型類型,getClass方法的返回值和原始類型徹底同樣。

因此編譯完之後,跳過編譯器,經過反射就能夠向集合添加其餘類型的數據,例子以下:

List<Integer> list = new ArrayList<>();
//經過反射的方式取添加「非法類型」到集合當中
list.getClass().getMethod("add", Object.class).invoke(list, "abc");
System.out.println(list.get(0));
複製代碼

####關於泛型的一些術語:

ArrayList<E>						泛型類型
ArrayList<E>中的E					類型變量或者類型參數
ArrayList<String>					參數化的類型
ArrayList<String>中的String			實際類型參數
ArrayList<String>中的<>讀做type of
複製代碼
  1. 用不用泛型,程序最終的運行結果都同樣,用了有好處而已
  2. 參數化類型,不考慮類型參數的繼承關係

例如,下面的這行代碼是錯誤的,由於不考慮父子關係:

List<Object> list1 = new ArrayList<String>();
複製代碼

泛型中的通配符 ?

不用Object,而是使用?表示任意類型。?通配符能夠引用各類其餘參數化的類型,?通配符定義的變量主要用做引用。類型參數沒有賦值的時候,不能調用與類型參數有關的方法(方法中有泛型參數的方法)。

例子:

public static void printSise(List<?> l) {

    l.add(new Object());//這一局編譯就報錯
    Object o = l.get(1);//返回值有泛型,可是咱們能夠轉換爲Object

    for (Object obj : l) {
        System.out.println(obj);
    }
}
複製代碼

####泛型的上下邊界,能夠用&實現多個接口的限定

//上邊界
List<? extends Number> l1 = new ArrayList<Integer>();

//下邊界
List<? super Integer> l2 = new ArrayList<Object>();
複製代碼

泛型的案例,以及各類集合(主要是MAP)的迭代方法:

####1.加強for循環遍歷MAP,這是最多見的而且在大多數狀況下也是最可取的遍歷方式。注意:for-each循環在java 5中被引入因此該方法只能應用於java 5或更高的版本中。若是你遍歷的是一個空的map對象,for-each循環將拋出NullPointerException,所以在遍歷前你老是應該檢查空引用。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Map.Entry<Integer, Integer> entry : map.entrySet()) {

    entry.getKey();
	entry.getValue();

}
複製代碼

####2.在for-each循環中遍歷keys或values.若是隻須要map中的鍵或者值,你能夠經過keySet或values來實現遍歷,而不是用entrySet。該方法比entrySet遍歷在性能上稍好(快了10%),並且代碼更加乾淨。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

//遍歷map中的鍵

for (Integer key : map.keySet()) {

}

//遍歷map中的值

for (Integer value : map.values()) {

}
複製代碼

####3.使用迭代器,這種方法能夠在迭代之中刪除元素。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();

while (entries.hasNext()) {

    Map.Entry<Integer, Integer> entry = entries.next();

    entry.getKey();
	entry.getValue());

}
複製代碼

####4.經過鍵值去遍歷,效率低下,通常不採用。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Integer key : map.keySet()) {

    Integer value = map.get(key);

    System.out.println("Key = " + key + ", Value = " + value);

}
複製代碼

####總結:

若是僅須要鍵(keys)或值(values)使用方法二。若是你使用的語言版本低於java 5,或是打算在遍歷時刪除entries,必須使用方法三。不然使用方法一(鍵值都要)。

####由C++的模板函數,引入自定義泛型

####java中的泛型不能徹底作到C++中的模板功能的緣由:

java中的泛型相似於C++中的模板,可是這種類似性僅限於表面,java中的泛型基本上是在編譯器中實現,用於編譯器執行類型檢查和推斷,而後生成普通的非泛型字節碼,這種技術成爲擦除。由於擴展虛擬機指令集來支持泛型被認爲是沒法接受的,這會爲java廠商升級JVM形成困難。所以,泛型參數不一樣不能構成重載。

  1. 泛型的實際類型不能是基本類型,只能是引用類型。
  2. 修飾符以後,返回值類型以前,用聲明一種新的類型。
  3. 能夠有多個類型變量,用逗號隔開。
  4. 類型參數的名字通常用一個大寫字母來命名。
  5. 編譯器不容許直接new T的數組或者對象。
  6. 能夠用類型變量表示異常,稱爲參數化異常,用得很少。

例子:

private static <T> T[] swap(T[] arr, int i, int j) {
    T tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
    return arr;
}
複製代碼

####參數的類型推斷(比較複雜)

類型參數的類型推斷:編譯器判斷泛型方法的實際類型參數的過程稱爲類型推斷,類型推斷是相對於知覺推斷的,其實現方法是一種很是複雜的過程。根據調用泛型方法時實際傳遞參數類型或返回值的類型來推斷,具體規則以下:

  1. 當某個類型變量值在整個參數列表中的全部參數和返回值中的一處被應用類,那麼根據調用方法時該處的實際應用類型來肯定,這很容易憑着感受推斷出來,即直接根據調用方法時傳遞的參數類型或返回值來決定泛型參數的類型,例如: swap(new String[3],3,4) --> static void swap(E[] a,int i,int j)
  2. 當某個類型變量在整個參數列表中的全部參數和返回值中的多處被應用了,若是調用方法時這多處的實際應用類型都對應同一種類型來肯定,這很容易憑着感受推斷出來,例如: add(2,5) -->static T add (T a, T b)
  3. 當某個類型變量在整個參數列表中的全部參數和返回值中的多處被應用了,若是調用方法時這多處的實際應用類型對應到類不一樣的類型,且沒有使用返回值,這時候取多個參數中的最大交集類型,例如,下面語句實際對應的類型就是Number了,編譯沒問題,只是運行時出問題: fill(new Integer[3],3.5f)-->static void fill(T[], T v)//Integer∩Float = Number ,它們都是Number的子類
  4. 當某個類型變量在整個參數列表中的全部參數和返回值中的多處被應用了,若是調用方法時這多處的實際應用類型對應到了不一樣的類型,而且使用返回值,這時候優先考慮返回值的類型,例如,下面語句實際對應的類型就是Integer了,編譯將報告錯誤,將變量x的類型改成float,對比eclipse報告的錯誤提示,接着再將變量x類型改成Number,則沒有了錯誤: int x = add(3,3.5f) -->static T add(T a,T b)

####定義泛型的類型

  1. 方法級別(上面已經講過)

  2. 泛型的類型(類):多個方法使用的是同一個類型

    class A {

    }

注意,類裏面的靜態方法不能含有對象的泛型。可是能夠有通常的泛型靜態方法。例子:

public class A<T> {
    //編譯器報錯,由於靜態方法能夠避開對象的建立嘛
    public static void add(T t) {

    }

    //編譯器不報錯,單獨分開
    public static <E> void set(E e) {

    }
}
複製代碼

####難點:經過反射的方法把方法中的參數的類型提取出來

public static void main(String[] args) throws Exception {

    //經過反射的方法把方法中的參數的類型提取出來
    Method method = A.class.getMethod("test", List.class);
    //其返回的是參數的參數化的類型,裏面的帶有實際的參數類型
    Type[] types = method.getGenericParameterTypes();
    //將類型向參數化類型轉換
    ParameterizedType type = (ParameterizedType) types[0];
    //獲得原始類型(interface java.util.List)
    System.out.println(type.getRawType());
    //獲得實際參數類型的類型(class java.lang.String)
    System.out.println(type.getActualTypeArguments()[0]);

}


public static void test(List<String> list) {

}
複製代碼

類加載器的介紹

####類加載器的父子關係以及管轄範圍

例子:獲取而且打印類加載器:

public static void main(String[] args) {

    //打印出當前線程的類加載器
    System.out.println(Thread.currentThread().getContextClassLoader().getClass().getName());

    //第一個類默認由當前線程的類加載器去進行加載
    System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
    //System是由BootStrap類加載器加載的,是C/C++寫的,BootStrap不須要其餘加載器去加載
    //在java層面不能獲取該類的引用
    System.out.println(System.class.getClassLoader() == null);

}
複製代碼

類加載器的委託機制,至關於android中的事件傳遞,防止字節碼的重複加載。

####自定義加載器

原理:ClassLoader有loadClass和findClass兩個方法,loadClass會首先去找父加載器,若是找不到就會回傳,若是傳到本身的話,就會回調findClass方法來加載class。爲了保證這一個流程不被破壞(模板方法設計模式),所以咱們須要覆蓋的是findClass方法。

####自定義加載器能夠實現字節碼的加密解密

下面僅僅寫出一些關鍵的步驟:

  1. 寫一個須要被加密的類,而且編譯生成.class文件

  2. 利用加密算法(好比與0xff異或)對.class文件文件進行加密,用輸入流讀進來,再用輸出流輸出到文件中。

  3. 自定義類加載器,繼承ClassLoader。複寫findClass方法,把加密事後的.class加載進來,轉換成字節數組而且解密,利用ClassLoader的下面這個方法把class文件轉換成字節碼。

  4. 獲得字節碼就能夠經過反射的方式進行newInstance等使用了。

    //獲得class文件轉換成字節碼
     protected final Class<?> defineClass(byte[] b, int off, int len)
    複製代碼

###代理

要爲已存在的多個具備相同接口的目標類(已經開發好,或者沒有源碼)的各個方法增長一些系統功能,例如異常處理、日誌、計算方法的運行時間、事務管理等。可使用代理,代理就有這樣的好處。

JVM能夠在運行期間動態生成出類的字節碼,這種動態生成的類每每用做代理,成爲動態代理。JVM生成的類必須實現一個或者多個接口,因此JVM生成的類智能用做具備相同家口的目標類的代理。

CGLIB庫能夠動態生成一個類的子類,一個類的子類也能夠用做該類的代理類,因此若是要爲一個沒有實現接口的類生成動態代理類的話,可使用這個庫。

###AOP面向切面編程的概念

####面向切面編程(AOP是Aspect Oriented Program的首字母縮寫),在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。 通常而言,咱們管切入到指定類指定方法的代碼片斷稱爲切面,而切入到哪些類、哪些方法則叫切入點。有了AOP,咱們就能夠把幾個類共有的代碼,抽取到一個切片中,等到須要時再切入對象中去,從而改變其原有的行爲。 這樣看來,AOP其實只是OOP的補充而已。OOP從橫向上區分出一個個的類來,而AOP則從縱向上向對象中加入特定的代碼。有了AOP,OOP變得立體了。若是加上時間維度,AOP使OOP由原來的二維變爲三維了,由平面變成立體了。從技術上來講,AOP基本上是經過代理機制實現的。 AOP在編程歷史上能夠說是里程碑式的,對OOP編程是一種十分有益的補充。

####例子

使用反射API,JVM動態生成Collection接口的代理類

//使用反射API,JVM動態生成Collection接口的代理類
Class<?> clazzProxy0 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

//輸入這個動態生成的類的名字
System.out.println(clazzProxy0.getName());

//輸出全部構造方法
printConstructor(clazzProxy0);

//輸出全部方法
printMethod(clazzProxy0);

//使用這個類去構建對象
//不能使用無參數的構造函數,由於代理類只有一個有參數的構造函數
//clazzProxy0.newInstance();

Constructor<?> constructor = clazzProxy0.getConstructor(InvocationHandler.class);
Collection collection = (Collection) constructor.newInstance(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
});
複製代碼

生成代理類對象須要傳入InvocationHandler對象,代理類的方法調用會觸發InvocationHandler的分發,InvocationHandler內部會對被代理類的對象的方法進行調用,而且插入一些指定的功能。

下面是打印全部構造方法與方法的函數

private static void printMethod(Class<?> clazz) {
    System.out.println("全部方法");
    for (Method m : clazz.getMethods()) {
        StringBuilder builder = new StringBuilder(m.getName());
        builder.append("(");

        Class[] types = m.getParameterTypes();
        for (Class<?> t : types) {
            builder.append(t.getName()).append(",");
        }
        if (types.length != 0) {
            builder.deleteCharAt(builder.length() - 1);
        }

        builder.append(")");

        System.out.println(builder.toString());
    }
}

private static void printConstructor(Class<?> clazz) {
    System.out.println("全部構造方法");
    for (Constructor c : clazz.getConstructors()) {
        StringBuilder builder = new StringBuilder(c.getName());
        builder.append("(");

        Class[] types = c.getParameterTypes();
        for (Class<?> t : types) {
            builder.append(t.getName()).append(",");
        }
        if (types.length != 0) {
            builder.deleteCharAt(builder.length() - 1);
        }

        builder.append(")");

        System.out.println(builder.toString());
    }
}
複製代碼

一步到位:

//一步到位,獲取代理類而且生成對象
Collection collection1 = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
});
複製代碼

下面給出實用的案例,在每一個方法的調用的時候插入廣告:

/**
 * Created by Administrator on 2016/9/18.
 */
public class ProxyTest1 {

    //被代理的對象
    private static ArrayList<String> target = new ArrayList<>();

    public static void main(String[] args) {

        //一步到位,獲取代理類而且生成對象
        Collection collection = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("----廣告開始----");

                Object returnVal = method.invoke(target, args);

                System.out.println("----廣告結束----");

                return returnVal;


            }
        });

        collection.add("aad");
        collection.add("aad");
        collection.add("aad");
        collection.add("aad");
        System.out.println(collection.size());
    }

}
複製代碼

固然,咱們但願的是調用的東西是框架完成之後用戶(配置)輸入的,所以,咱們須要再提供接口:

public interface Advice {

    void before(Object proxy, Method method, Object[] args);

    void after(Object proxy, Method method, Object[] args);
}
複製代碼

如上所示,爲了方便,接口只提供兩個簡單的方法,分別在方法執行先後執行。

而後,咱們也把獲取代理對象的方法封裝一下,用戶只須要傳入接口的實現類便可。

public class ProxyTest1 {

    //被代理的對象
    private static ArrayList<String> target = new ArrayList<>();

    public static void main(String[] args) {

        //一步到位,獲取代理類而且生成對象
        Collection collection = (Collection) getProxyInstance(Collection.class, new Advice() {
            @Override
            public void before(Object proxy, Method method, Object[] args) {
                System.out.println(method.getName() + "開始執行");
            }

            @Override
            public void after(Object proxy, Method method, Object[] args) {
                System.out.println(method.getName() + "結束執行");
            }
        });

        collection.add("aad");
        collection.size();
    }

    private static Object getProxyInstance(Class<?> clazz, Advice advice) {
        return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                advice.before(proxy, method, args);
                Object returnVal = method.invoke(target, args);
                advice.after(proxy, method, args);

                return returnVal;	
            }
        });
    }

}
複製代碼

注意:接口的方法都會交給InvocationHandler分發處理,可是由Object繼承過來的方法只有toString、equals、hashCode纔會交給InvocationHandler處理。

###String、StringBuilder與StringBuffer的區別

  1. 在執行速度方面的比較:StringBuilder > StringBuffer

  2. StringBuffer與StringBuilder,他們是字符串變量,是可改變的對象,每當咱們用它們對字符串作操做時,其實是在一個對象上操做的,不像String同樣建立一些對象進行操做,因此速度就快了。

  3. StringBuilder:線程非安全的   StringBuffer:線程安全的

    當咱們在字符串緩衝去被多個線程使用是,JVM不能保證StringBuilder的操做是安全的,雖然他的速度最快,可是能夠保證StringBuffer是能夠正確操做的。固然大多數狀況下就是咱們是在單線程下進行的操做,因此大多數狀況下是建議用StringBuilder而不用StringBuffer的,就是速度的緣由。

對於三者使用的總結:

  1. 若是要操做少許的數據用 = String
  2. 單線程操做字符串緩衝區 下操做大量數據 = StringBuilder
  3. 多線程操做字符串緩衝區 下操做大量數據 = StringBuffer

###Overload(重載)與Override(重寫)的區別

  1. 重載是指不一樣的函數使用相同的函數名,可是函數的參數個數或類型不一樣。調用的時候根據函數的參數來區別不一樣的函數。

  2. 覆蓋(也叫重寫)是指在派生類中從新對基類中的虛函數(注意是虛函數)從新實現。即函數名和參數都同樣,只是函數的實現體不同。

  3. 隱藏是指派生類中的函數把基類中相同名字的函數屏蔽掉了。

  4. 方法的重寫(Overriding)和重載(Overloading)是Java多態性的不一樣表現。 重寫(Overriding)是父類與子類之間多態性的一種表現,而重載(Overloading)是一個類中多態性的一種表現。

    override(重寫)

    1. 方法名、參數、返回值相同。
     2. 子類方法不能縮小父類方法的訪問權限。	
     3. 子類方法不能拋出比父類方法更多的異常(但子類方法能夠不拋出異常)。	
     4. 存在於父類和子類之間。
     5. 方法被定義爲final不能被重寫。
    複製代碼

    overload(重載)

    1. 參數類型、個數、順序至少有一個不相同。 
     2. 不能重載只有返回值不一樣的方法名。
     3. 存在於父類和子類、同類中。
    複製代碼

==、hashCode與equals的區別:

  1. 基本數據類型,也稱原始數據類型。byte,short,char,int,long,float,double,boolean他們之間的比較,應用雙等號(==),比較的是他們的值。
  2. 複合數據類型(類)複合數據類型之間進行equals比較,在沒有覆寫equals方法的狀況下,他們之間的比較仍是基於他們在內存中的存放位置的地址值的,由於Object的equals方法也是用雙等號(==)進行比較的,因此比較後的結果跟雙等號(==)的結果相同。
  3. 將對象放入到集合中時,首先判斷要放入對象的hashcode值與集合中的任意一個元素的hashcode值是否相等,若是不相等直接將該對象放入集合中。若是hashcode值相等,而後再經過equals方法判斷要放入對象與集合中的任意一個對象是否相等,若是equals判斷不相等,直接將該元素放入到集合中,不然不放入。

規則:

  1. 若是兩個對象根據equals()方法比較是相等的,那麼調用這兩個對象中任意一個對象的hashCode方法都必須產生一樣的整數結果。
  2. 若是兩個對象根據equals()方法比較是不相等的,那麼調用這兩個對象中任意一個對象的hashCode方法,則不必定要產生相同的整數結果。可是程序員應該知道,給不相等的對象產生大相徑庭的整數結果,有可能提升散列表的性能。

HashMap、HashTable的區別

  1. HashMap幾乎能夠等價於Hashtable,除了HashMap是非synchronized的,並能夠接受null(HashMap能夠接受爲null的鍵值(key)和值(value),而Hashtable則不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,這意味着Hashtable是線程安全的,多個線程能夠共享一個Hashtable;而若是沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
  3. 二者使用的迭代方式不同。

sychronized意味着在一次僅有一個線程可以更改Hashtable。就是說任何線程要更新Hashtable時要首先得到同步鎖,其它線程要等到同步鎖被釋放以後才能再次得到同步鎖更新Hashtable。

HashMap能夠經過下面的語句進行同步: Map m = Collections.synchronizeMap(hashMap);

總結:

Hashtable和HashMap有幾個主要的不一樣:線程安全以及速度。僅在你須要徹底的線程安全的時候使用Hashtable,而若是你使用Java 5或以上的話,請使用ConcurrentHashMap吧。

ArrayList、Vector的區別

  1. Vector是線程安全的,也就是說是它的方法之間是線程同步的,而ArrayList是線程序不安全的。
  2. 數據增加:ArrayList與Vector都有一個初始的容量大小,當存儲進它們裏面的元素的個數超過了容量時,就須要增長ArrayList與Vector的存儲空間,每次要增長存儲空間時,不是隻增長一個存儲單元,而是增長多個存儲單元,每次增長的存儲單元的個數在內存空間利用與程序效率之間要取得必定的平衡。Vector默認增加爲原來兩倍,而ArrayList的增加策略在文檔中沒有明確規定(從源代碼看到的是增加爲原來的1.5倍)。ArrayList與Vector均可以設置初始的空間大小,Vector還能夠設置增加的空間大小,而ArrayList沒有提供設置增加空間的方法。

###Java中建立對象的四種方法 收藏Java中建立對象的四種方式

  1. 用new語句建立對象,這是最多見的建立對象的方法。
  2. 運用反射手段,調用java.lang.Class或者java.lang.reflect.Constructor類的newInstance()實例方法。
  3. 調用對象的clone()方法。
  4. 運用反序列化手段,搜索調用java.io.ObjectInputStream對象的 readObject()方法。

###說說靜態變量、靜態代碼塊加載的過程和時機? 回答:當類加載器將類加載到JVM中的時候就會建立靜態變量,靜態變量加載的時候就會分配內存空間。靜態代碼塊的代碼只會在類第一次初始化,也就是第一次被使用的時候執行一次。

##MVC、MVVP、MVP設計模式

  1. MVC:MVC的耦合性仍是比較高,View能夠直接訪問Model
  2. MVP:Model、View、Presenter,View不能直接訪問Model
  3. MVVM:View和Model的雙向綁定,相似於Data-Binding

###JAVA容器的架構圖

若是以爲個人文字對你有所幫助的話,歡迎關注個人公衆號:

公衆號:Android開發進階

個人羣歡迎你們進來探討各類技術與非技術的話題,有興趣的朋友們加我私人微信huannan88,我拉你進羣交(♂)流(♀)

相關文章
相關標籤/搜索