java8--類加載機制與反射(java瘋狂講義3復習筆記)


  

本章重點介紹java.lang.reflect包下的接口和類

當程序使用某個類時,如果該類還沒有被加載到內存中,那么系統會通過加載,連接,初始化三個步驟來對該類進行初始化.

類的加載時指將類的class文件讀入內存,並為之創建一個java.lang.class對象,也就是說,當程序中使用任何類時,系統都會為之建立一個java.lang.Class對象.(幾乎所有的類都是java.lang.Class的實例);

所以JVM最先初始化的總是java.long.Object類.

在java中,一個類用其全限定類名(包括包名和類名)作為標識;但在JVM中,一個類用其全限定類名和類加載器作為其唯一標識.

打印根類加載器:

public class BootstrapTest
{
    public static void main(String[] args)
    {
        // 獲取根類加載器所加載的全部URL數組
        for (URL url : sun.misc.Launcher.getBootstrapClassPath().getURLs()) {
            // 遍歷、輸出根類加載器加載的全部URL
            System.out.println(url.toExternalForm());
        }
    }
}

----------------------------------------------------------------------
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfr.jar

擴展類加載器,這個可以加入自己的jar包,挺好玩的.

系統類加載器:這個就是我們平常自定義類的父加載器了

開發者實現自定義的類加載器需要通過繼承ClassLoader來實現.

public static void main(String[] args)
        throws IOException
    {
        // 獲取系統類加載器
        ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系統類加載器:" + systemLoader);
        /*
        獲取系統類加載器的加載路徑——通常由CLASSPATH環境變量指定
        如果操作系統沒有指定CLASSPATH環境變量,默認以當前路徑作為
        系統類加載器的加載路徑
        */
        Enumeration<URL> em1 = systemLoader.getResources("");
        while(em1.hasMoreElements())
        {
            System.out.println(em1.nextElement());
        }
        // 獲取系統類加載器的父類加載器:得到擴展類加載器
        ClassLoader extensionLader = systemLoader.getParent();
        System.out.println("擴展類加載器:" + extensionLader);
        System.out.println("擴展類加載器的加載路徑:"
            + System.getProperty("java.ext.dirs"));
        System.out.println("擴展類加載器的parent: "
            + extensionLader.getParent());
    }

 自定義類加載器的例子:

由於java8.0.51的URLClassLoader里重寫了ClassLoader這個類里的下面這個方法,

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

所以,現在還沒弄明白原因,如果我們自己重寫findClass(String name),結果就是程序會先調用URLClassLoader里的findClass方法,如果這個方法找不到類,才會調用我們自己寫的findClass(String name).

比如我們要動態加載某個類(如果內存存在這個類,那么閑從內存取;如果內存中不存在,那么加載那個類的java文件並編譯(我不確定是不是要先檢查是否有class文件,看源碼和自己的測試結果是沒有檢查)).

例子

public class Hello
{
    public static void main(String[] args)
    {
        System.out.println("tes22t2");
        for (String arg : args)
        {
            System.out.println("運行Hello的參數:" + arg);
        }
    }
}
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.jar.Manifest;

import sun.misc.Resource;
import sun.misc.URLClassPath;

import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf;
/**
 * Description:
 * <br/>網站: <a href="http://www.crazyit.org">瘋狂Java聯盟</a>
 * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
 * <br/>This program is protected by copyright laws.
 * <br/>Program Name:
 * <br/>Date:
 * @author Yeeku.H.Lee kongyeeku@163.com
 * @version 1.0
 */
public class CompileClassLoader extends ClassLoader
{
    // 讀取一個文件的內容
    private byte[] getBytes(String filename)
        throws IOException
    {
        File file = new File(filename);
        long len = file.length();
        byte[] raw = new byte[(int)len];
        try(
            FileInputStream fin = new FileInputStream(file))
        {
            // 一次讀取class文件的全部二進制數據
            int r = fin.read(raw);
            if(r != len)
            throw new IOException("無法讀取全部文件:"
                + r + " != " + len);
            return raw;
        }
    }
    // 定義編譯指定Java文件的方法
    private boolean compile(String javaFile)
        throws IOException
    {
        System.out.println("CompileClassLoader:正在編譯 "
            + javaFile + "...");
        // 調用系統的javac命令
        Process p = Runtime.getRuntime().exec("javac " + javaFile);
        try
        {
            // 其他線程都等待這個線程完成
            p.waitFor();
        }
        catch(InterruptedException ie)
        {
            System.out.println(ie);
        }
        // 獲取javac線程的退出值
        int ret = p.exitValue();
        // 返回編譯是否成功
        return ret == 0;
    }
    // 重寫ClassLoader的findClass方法
 @Override protected Class<?> findClass(String tmpName) throws ClassNotFoundException
    {
        System.out.println(tmpName);
        Class clazz = null;
        // 將包路徑中的點(.)替換成斜線(/);
        String className = tmpName.replace("." , "/").replace("1" , "");
     // 這里因為我是在eclipse里編輯的,而java文件統一放到src里,class文件統一放到了bin里, 所以我需要加前綴, String javaFilename
= "src/"+className + ".java"; String classFilename = "bin/"+className + ".class"; File javaFile = new File(javaFilename); System.out.println(javaFile.getAbsolutePath()); File classFile = new File(classFilename); // 當指定Java源文件存在,且class文件不存在、或者Java源文件 // 的修改時間比class文件修改時間更晚,重新編譯 if(javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // 如果編譯失敗,或者該Class文件不存在 if(!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException( "ClassNotFoundExcetpion:" + javaFilename); } } catch (IOException ex) { ex.printStackTrace(); } } // 如果class文件存在,系統負責將該文件轉換成Class對象 if (classFile.exists()) { try { // 將class文件的二進制數據讀入數組 byte[] raw = getBytes(classFilename); // 調用ClassLoader的defineClass方法將二進制數據轉換成Class對象 clazz = defineClass(className,raw,0,raw.length); } catch(IOException ie) { ie.printStackTrace(); } } // 如果clazz為null,表明加載失敗,則拋出異常 if(clazz == null) { throw new ClassNotFoundException(className); } return clazz; } // 定義一個主方法 public static void main(String[] args) throws Exception { // 如果運行該程序時沒有參數,即沒有目標類 args = new String[]{"Hello","java瘋狂講義w"}; // 第一個參數是需要運行的類 String progClass = args[0]; // 剩下的參數將作為運行目標類時的參數, // 將這些參數復制到一個新數組中 String[] progArgs = new String[args.length-1]; System.arraycopy(args , 1 , progArgs , 0 , progArgs.length); CompileClassLoader ccl = new CompileClassLoader(); // 加載需要運行的類 Class<?> clazz = ccl.loadClass(progClass); // 獲取需要運行的類的主方法 Method main = clazz.getMethod("main" , (new String[0]).getClass()); Object[] argsArray = {progArgs}; main.invoke(null,argsArray); } }

輸出結果是

tes22t2
運行Hello的參數:java瘋狂講義w

打斷點可見,並沒有調用我們自定義的findClass(String tmpName)方法.

當我們把
args = new String[]{"Hello","java瘋狂講義w"};

改為
args = new String[]{"Hello1","java瘋狂講義w"};

 時,由於URLClassLoader並沒有找到被加載的類Hello1,所以最后會調用我們自定義的findClass方法.

當然,更規范的是把 Class<?> clazz = ccl.loadClass(progClass); 改為 Class<?> clazz = ccl.findClass(progClass);

輸出結果:

Hello1
/Users/liuxin/work/workspace2/learnJava/src/Hello.java
tes22t2
運行Hello的參數:java瘋狂講義w

這里我們可以看到,如果要動態加載某個類,不用自己覆寫findClass方法,只要如下代碼就好:

import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.jar.Manifest;

import sun.misc.Resource;
import sun.misc.URLClassPath;

import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf;
public class CompileClassLoader extends ClassLoader
{
    public static void main(String[] args) throws Exception
    {
        // 如果運行該程序時沒有參數,即沒有目標類
        args = new String[]{"Hello","java瘋狂講義w"};
        // 第一個參數是需要運行的類
        String progClass = args[0];
        // 剩下的參數將作為運行目標類時的參數,
        // 將這些參數復制到一個新數組中
        String[] progArgs = new String[args.length-1];
        System.arraycopy(args , 1 , progArgs
            , 0 , progArgs.length);
        CompileClassLoader ccl = new CompileClassLoader();
        // 加載需要運行的類
        Class<?> clazz = ccl.loadClass(progClass);
        // 獲取需要運行的類的主方法
        Method main = clazz.getMethod("main" , (new String[0]).getClass());
        Object[] argsArray = {progArgs};
        main.invoke(null,argsArray);
    }
}

----------------輸出結果--------------------
tes22t2
運行Hello的參數:java瘋狂講義w

 

18.2.4 URLClassLoader類

java為ClassLoader提供了一個URLClassLoader實現類,該類也是系統類加載器和擴展類加載器的父類(此處的父類,就是指類與類之間的繼承關系).URLClassLoader功能強大,可以從本地或遠程主機獲取二進制文件來加載類.

下面是一個例子

import java.sql.*;
import java.util.*;
import java.net.*;
/**
 * Description:
 * <br/>網站: <a href="http://www.crazyit.org">瘋狂Java聯盟</a>
 * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
 * <br/>This program is protected by copyright laws.
 * <br/>Program Name:
 * <br/>Date:
 * @author Yeeku.H.Lee kongyeeku@163.com
 * @version 1.0
 */
public class URLClassLoaderTest
{
    private static Connection conn;
    // 定義一個獲取數據庫連接方法
    public static Connection getConn(String url ,
        String user , String pass) throws Exception
    {
        if (conn == null)
        {
            // 創建一個URL數組
            URL[] urls = {new URL(
                "file:mysql-connector-java-5.1.30-bin.jar")};
            // 以默認的ClassLoader作為父ClassLoader,創建URLClassLoader
            URLClassLoader myClassLoader = new URLClassLoader(urls);
            // 加載MySQL的JDBC驅動,並創建默認實例
            Driver driver = (Driver)myClassLoader.
                loadClass("com.mysql.jdbc.Driver").newInstance();
            // 創建一個設置JDBC連接屬性的Properties對象
            Properties props = new Properties();
            // 至少需要為該對象傳入user和password兩個屬性
            props.setProperty("user" , user);
            props.setProperty("password" , pass);
            // 調用Driver對象的connect方法來取得數據庫連接
            conn = driver.connect(url , props);
        }
        return conn;
    }
    public static void main(String[] args)throws Exception
    {
        Connection temconn = getConn("jdbc:mysql://localhost:3306/mybatis"
                , "root" , "password");
            try{
                String sql = "INSERT INTO `mybatis`.`users` (`NAME`, `age`) VALUES ('java8', '2')";
                java.sql.PreparedStatement stmt = temconn.prepareStatement(sql);
                stmt.execute();
                stmt.close();
                String sql2 = "select * from `mybatis`.`users`";
                java.sql.PreparedStatement stmt2 = temconn.prepareStatement(sql2);
                ResultSet rs = stmt2.executeQuery();
                ResultSetMetaData m=rs.getMetaData();
                //顯示列,表格的表頭
                int columns=m.getColumnCount();
                for(int i=1;i<=columns;i++)
                   {
                    System.out.print(m.getColumnName(i));
                    System.out.print("\t\t");
                   }
                System.out.println();
                   //顯示表格內容
                   while(rs.next())
                   {
                    for(int i=1;i<=columns;i++)
                    {
                     System.out.print(rs.getString(i));
                     System.out.print("\t\t"); } System.out.println(); }
                stmt2.close();
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                temconn.close();
            }
    }
}

所以前面的動態加載類,可以用URLClassLoader改寫如下:(為了弄明白路徑,把Hello.java放到了learnJava包里去了)

public static void main(String[] args) throws Exception
    {
        args = new String[]{"learnJava.Hello","java瘋狂講義w"};
        String progClass = args[0];
        String[] progArgs = new String[args.length-1];
        System.arraycopy(args , 1 , progArgs, 0 , progArgs.length);
        URL[] urls = {new URL("file:")};
        Class<?> clazz = (new URLClassLoader(urls)).loadClass(progClass);
        // 獲取需要運行的類的主方法
        Method main = clazz.getMethod("main" , (new String[0]).getClass());
        Object[] argsArray = {progArgs};
        main.invoke(null,argsArray);
    }

18.3 通過反射來查看類的信息

什么時候會用到反射?

從Class中獲取信息,方法分以下幾類:

1.獲取構造器

2.獲取方法

3.獲取屬性

上面這三類方法,每一類都分4個方法,例如:(單個,多個) * (按權限,不顧權限)

4.獲取注解,這個太多:

5.獲取內部類

Class<?>[] getDeclaredClasses():返回該Class對象對應類包含的全部內部類

6.獲取外部類

Class<?>[] getDeclaringClasse():返回該Class對象對應類所在的外部類

7.獲取接口

Class<?>[] getInterfaces():返回該Class對象對應類所實現的全部接口

8.其他的如下:

 例子:

// 定義可重復注解
@Repeatable(Annos.class)
@interface Anno {}
@Retention(value=RetentionPolicy.RUNTIME)
@interface Annos {
    Anno[] value();
}
// 使用4個注解修飾該類
@SuppressWarnings(value="unchecked")
@Deprecated
// 使用重復注解修飾該類
@Anno
@Anno
public class ClassTest
{
    // 為該類定義一個私有的構造器
    private ClassTest()
    {
    }
    // 定義一個有參數的構造器
    public ClassTest(String name)
    {
        System.out.println("執行有參數的構造器");
    }
    // 定義一個無參數的info方法
    public void info()
    {
        System.out.println("執行無參數的info方法");
    }
    // 定義一個有參數的info方法
    public void info(String str)
    {
        System.out.println("執行有參數的info方法"
            + ",其str參數值:" + str);
    }
    // 定義一個測試用的內部類
    class Inner
    {
    }
    public static void main(String[] args)
        throws Exception
    {
        // 下面代碼可以獲取ClassTest對應的Class
        Class<?> clazz = ClassTest.class;
        // 獲取該Class對象所對應類的全部構造器
        Constructor[] ctors = clazz.getDeclaredConstructors();
        System.out.println("ClassTest的全部構造器如下:");
        for (Constructor c : ctors)
        {
            System.out.println(c);
        }
        // 獲取該Class對象所對應類的全部public構造器
        Constructor[] publicCtors = clazz.getConstructors();
        System.out.println("ClassTest的全部public構造器如下:");
        for (Constructor c : publicCtors)
        {
            System.out.println(c);
        }
        // 獲取該Class對象所對應類的全部public方法
        Method[] mtds = clazz.getMethods();
        System.out.println("ClassTest的全部public方法如下:");
        for (Method md : mtds)
        {
            System.out.println(md);
        }
        // 獲取該Class對象所對應類的指定方法
        System.out.println("ClassTest里帶一個字符串參數的info()方法為:"
            + clazz.getMethod("info" , String.class));
        // 獲取該Class對象所對應類的上的全部注解
        Annotation[] anns = clazz.getAnnotations();
        System.out.println("ClassTest的全部Annotation如下:");
        for (Annotation an : anns)
        {
            System.out.println(an);
        }
        System.out.println("該Class元素上的@SuppressWarnings注解為:"
            + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
        System.out.println("該Class元素上的@Anno注解為:"
            + Arrays.toString(clazz.getAnnotationsByType(Anno.class)));
        // 獲取該Class對象所對應類的全部內部類
        Class<?>[] inners = clazz.getDeclaredClasses();
        System.out.println("ClassTest的全部內部類如下:");
        for (Class c : inners)
        {
            System.out.println(c);
        }
        // 使用Class.forName方法加載ClassTest的Inner內部類
        Class inClazz = Class.forName("ClassTest$Inner");
        // 通過getDeclaringClass()訪問該類所在的外部類
        System.out.println("inClazz對應類的外部類為:" +
            inClazz.getDeclaringClass());
        System.out.println("ClassTest的包為:" + clazz.getPackage());
        System.out.println("ClassTest的父類為:" + clazz.getSuperclass());
    }
}

 

18.3.3 java8 新增的方法參數反射

關於反射方法的參數的名字,這個比較麻煩,如下的例子

class Test
{
    public void replace(String str, List<String> list){}
}
public class MethodParameterTest
{
    public static void main(String[] args)throws Exception
    {
        // 獲取Tesy的類
        Class<Test> clazz = Test.class;
        // 獲取Test類的帶兩個參數的replace()方法
        Method replace = clazz.getMethod("replace"
            , String.class, List.class);
        // 獲取指定方法的參數個數
        System.out.println("replace方法參數個數:" + replace.getParameterCount());
        // 獲取replace的所有參數信息
        Parameter[] parameters = replace.getParameters();
        System.out.println((new File("")).getAbsolutePath());
        int index = 1;
        // 遍歷所有參數
        for (Parameter p : parameters)
        {
            if (p.isNamePresent())
            {
                System.out.println("---第" + index++ + "個參數信息---");
                System.out.println("參數名:" + p.getName());
                System.out.println("形參類型:" + p.getType());
                System.out.println("泛型類型:" + p.getParameterizedType());
            }
        }
    }
}

 所以,上面這個例子在用eclipse編譯時,總是找不到參數名.沒找到設置的地方,只能用javac編譯

編譯命令如下:
javac -parameters -encoding GBK -d . MethodParameterTest.java 

因為瘋狂java講義里的源碼都是GBK編碼,而一般的電腦默認utf-8,所以需要指定編碼方式

運行:
java MethodParameterTest ----------------------結果--------------------------
replace方法參數個數:2
/Users/liuxin/work/workspace2/learnJava/src
---第1個參數信息---
參數名:str
形參類型:class java.lang.String
泛型類型:class java.lang.String
---第2個參數信息---
參數名:list
形參類型:interface java.util.List
泛型類型:java.util.List<java.lang.String>

 

18.4 使用反射生成並操作對象

 

例子:這個個別地方沒理解,但是這是一個反射的典型應用,需要好好理解

import java.util.*;
import java.io.*;
import java.lang.reflect.*;
/**
 * Description:
 * <br/>網站: <a href="http://www.crazyit.org">瘋狂Java聯盟</a>
 * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
 * <br/>This program is protected by copyright laws.
 * <br/>Program Name:
 * <br/>Date:
 * @author Yeeku.H.Lee kongyeeku@163.com
 * @version 1.0
 */
public class ExtendedObjectPoolFactory
{
    // 定義一個對象池,前面是對象名,后面是實際對象
    private Map<String ,Object> objectPool = new HashMap<>();
    private Properties config = new Properties();
    // 從指定屬性文件中初始化Properties對象
    public void init(String fileName)
    {
//        File tmpFi = new File(fileName);
//        System.out.println(tmpFi.getAbsolutePath());
        try(FileInputStream fis = new FileInputStream(fileName))
        {
            config.load(fis);
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
            System.out.println("讀取" + fileName + "異常");
        }
    }
    // 定義一個創建對象的方法,
    // 該方法只要傳入一個字符串類名,程序可以根據該類名生成Java對象
    private Object createObject(String clazzName)
        throws InstantiationException
        , IllegalAccessException , ClassNotFoundException
    {
        // 根據字符串來獲取對應的Class對象
        Class<?> clazz =Class.forName(clazzName); // 使用clazz對應類的默認構造器創建實例
        return clazz.newInstance();
    }
    // 該方法根據指定文件來初始化對象池,
    // 它會根據配置文件來創建對象
    public void initPool()throws InstantiationException
        ,IllegalAccessException , ClassNotFoundException
    {
        for (String name : config.stringPropertyNames())
        {
            // 每取出一對key-value對,如果key中不包含百分號(%)
            // 這就標明是根據value來創建一個對象
            // 調用createObject創建對象,並將對象添加到對象池中
            if (!name.contains("%"))
            {
                objectPool.put(name ,
                    createObject(config.getProperty(name)));
            }
        }
    }
    // 該方法將會根據屬性文件來調用指定對象的setter方法
    public void initProperty()throws InvocationTargetException
        ,IllegalAccessException,NoSuchMethodException
    {
        for (String name : config.stringPropertyNames())
        {
            // 每取出一對key-value對,如果key中包含百分號(%)
            // 即可認為該key用於控制調用對象的setter方法設置值,
            // %前半為對象名字,后半控制setter方法名
            if (name.contains("%"))
            {
                // 將配置文件中key按%分割
                String[] objAndProp = name.split("%");
                // 取出調用setter方法的參數值
                Object target = getObject(objAndProp[0]);
                // 獲取setter方法名:set + "首字母大寫" + 剩下部分
                String mtdName = "set" +
                objAndProp[1].substring(0 , 1).toUpperCase()
                    + objAndProp[1].substring(1);
                // 通過target的getClass()獲取它實現類所對應的Class對象
                Class<?> targetClass = target.getClass(); // 獲取希望調用的setter方法
                Method mtd = targetClass.getMethod(mtdName , String.class); // 通過Method的invoke方法執行setter方法,
                // 將config.getProperty(name)的值作為調用setter的方法的參數
 mtd.invoke(target , config.getProperty(name));
            }
        }
    }
    public Object getObject(String name)
    {
        // 從objectPool中取出指定name對應的對象。
        return objectPool.get(name);
    }
    public static void main(String[] args)
        throws Exception
    {
        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
        epf.init("src/extObj.txt");
        epf.initPool();
        epf.initProperty();
        System.out.println(epf.getObject("a"));
    }
}

 

例子二:使用指定的構造器來構造對象.

public class CreateJFrame
{
    public static void main(String[] args)
        throws Exception
    {
        // 獲取JFrame對應的Class對象
        Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
        // 獲取JFrame中帶一個字符串參數的構造器
        Constructor ctor = jframeClazz
            .getConstructor(String.class);
        // 調用Constructor的newInstance方法創建對象
        Object obj = ctor.newInstance("測試窗口");
        // 輸出JFrame對象
        System.out.println(obj);
    }
}

18.4.3 訪問成員變量值

例子:

class Person
{
    private String name;
    private int age;
    public String toString()
    {
        return "Person[name:" + name +
        " , age:" + age + " ]";
    }
}
public class FieldTest
{
    public static void main(String[] args)
        throws Exception
    {
        // 創建一個Person對象
        Person p = new Person();
        // 獲取Person類對應的Class對象
        Class<Person> personClazz = Person.class;
        // 獲取Person的名為name的成員變量
        // 使用getDeclaredField()方法表明可獲取各種訪問控制符的成員變量
        Field nameField = personClazz.getDeclaredField("name");
        // 設置通過反射訪問該成員變量時取消訪問權限檢查
        nameField.setAccessible(true);
        // 調用set()方法為p對象的name成員變量設置值
        nameField.set(p , "Yeeku.H.Lee");
        // 獲取Person類名為age的成員變量
        Field ageField = personClazz.getDeclaredField("age");
        // 設置通過反射訪問該成員變量時取消訪問權限檢查
        ageField.setAccessible(true);
        // 調用setInt()方法為p對象的age成員變量設置值
        ageField.setInt(p , 30);
        System.out.println(p);
    }
}

用java.lang.reflect包下的Array類操作數組

例子:

public class ArrayTest1
{
    public static void main(String args[])
    {
        try
        {
            // 創建一個元素類型為String ,長度為10的數組
            Object arr = Array.newInstance(String.class, 10);
            // 依次為arr數組中index為5、6的元素賦值
            Array.set(arr, 5, "瘋狂Java講義");
            Array.set(arr, 6, "輕量級Java EE企業應用實戰");
            // 依次取出arr數組中index為5、6的元素的值
            Object book1 = Array.get(arr , 5);
            Object book2 = Array.get(arr , 6);
            // 輸出arr數組中index為5、6的元素
            System.out.println(book1);
            System.out.println(book2);
        }
        catch (Throwable e)
        {
            System.err.println(e);
        }
    }
}

操作多維數組的例子:

public class ArrayTest2
{
    public static void main(String args[])
    {
        /*
          創建一個三維數組。
          根據前面介紹數組時講的:三維數組也是一維數組,
          是數組元素是二維數組的一維數組,
          因此可以認為arr是長度為3的一維數組
        */
        Object arr = Array.newInstance(String.class, 3, 4, 10);
        // 獲取arr數組中index為2的元素,該元素應該是二維數組
        Object arrObj = Array.get(arr, 2);
        // 使用Array為二維數組的數組元素賦值。二維數組的數組元素是一維數組,
        // 所以傳入Array的set()方法的第三個參數是一維數組。
        Array.set(arrObj , 2 , new String[]
        {
            "瘋狂Java講義",
            "輕量級Java EE企業應用實戰"
        });
        // 獲取arrObj數組中index為3的元素,該元素應該是一維數組。
        Object anArr  = Array.get(arrObj, 3);
        Array.set(anArr , 8  , "瘋狂Android講義");
        // 將arr強制類型轉換為三維數組
        String[][][] cast = (String[][][])arr;
        // 獲取cast三維數組中指定元素的值
        System.out.println(cast[2][3][8]);
        System.out.println(cast[2][2][0]);
        System.out.println(cast[2][2][1]);
    }
}

18.5 使用反射生成JDK動態代理

在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過使用這個類和接口,可生成JDK動態代理或動態代理對象.

動態代理的例子:

interface Person
{
    void walk();
    void sayHello(String name);
}
class MyInvokationHandler implements InvocationHandler
{
    /*
    執行動態代理對象的所有方法時,都會被替換成執行如下的invoke方法
    其中:
    proxy:代表動態代理對象
    method:代表正在執行的方法
    args:代表調用目標方法時傳入的實參。
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    {
        System.out.println("----正在執行的方法:" + method);
        if (args != null)
        {
            System.out.println("下面是執行該方法時傳入的實參為:");
            for (Object val : args)
            {
                System.out.println(val);
            }
        }
        else
        {
            System.out.println("調用該方法沒有實參!");
        }
        return null;
    }
}
public class ProxyTest
{
    public static void main(String[] args)
        throws Exception
    {
        // 創建一個InvocationHandler對象
        InvocationHandler handler = new MyInvokationHandler();
        // 使用指定的InvocationHandler來生成一個動態代理對象
        Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader()
            , new Class[]{Person.class}, handler);
        // 調用動態代理對象的walk()和sayHello()方法
        p.walk();
        p.sayHello("孫悟空");
    }
}

18.5.2 動態代理和AOP

一個AOP的實現例子:

public interface Dog
{
    // info方法聲明
    void info();
    // run方法聲明
    void run();
}
public class GunDog implements Dog
{
    // 實現info()方法,僅僅打印一個字符串
    public void info()
    {
        System.out.println("我是一只獵狗");
    }
    // 實現run()方法,僅僅打印一個字符串
    public void run()
    {
        System.out.println("我奔跑迅速");
    }
}
public class DogUtil
{
    // 第一個攔截器方法
    public void method1()
    {
        System.out.println("=====模擬第一個通用方法=====");
    }
    // 第二個攔截器方法
    public void method2()
    {
        System.out.println("=====模擬通用方法二=====");
    }
}

 

 

public class MyInvokationHandler implements InvocationHandler
{
    // 需要被代理的對象
    private Object target;
    public void setTarget(Object target)
    {
        this.target = target;
    }
    // 執行動態代理對象的所有方法時,都會被替換成執行如下的invoke方法
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Exception
    {
        DogUtil du = new DogUtil();
        // 執行DogUtil對象中的method1。
        du.method1();
        // 以target作為主調來執行method方法
        Object result = method.invoke(target , args);
        // 執行DogUtil對象中的method2。
        du.method2();
        return result;
    }
}
public class MyProxyFactory
{
    // 為指定target生成動態代理對象
    public static Object getProxy(Object target)
        throws Exception
        {
        // 創建一個MyInvokationHandler對象
        MyInvokationHandler handler =
        new MyInvokationHandler();
        // 為MyInvokationHandler設置target對象
        handler.setTarget(target);
        // 創建、並返回一個動態代理
        return Proxy.newProxyInstance(target.getClass().getClassLoader()
            , target.getClass().getInterfaces() , handler);
    }
}
public class Test
{
    public static void main(String[] args)
        throws Exception
    {
        // 創建一個原始的GunDog對象,作為target
        Dog target = new GunDog();
        // 以指定的target來創建動態代理
        Dog dog = (Dog)MyProxyFactory.getProxy(target);
        dog.info();
        dog.run();
    }
}
--------------結果-----------------
=====模擬第一個通用方法=====
我是一只獵狗
=====模擬通用方法二=====
=====模擬第一個通用方法=====
我奔跑迅速
=====模擬通用方法二=====

18.6 反射和泛型

例子1:泛型工廠類

public class CrazyitObjectFactory2
{
    public static <T> T getInstance(Class<T> cls)
    {
        try
        {
            return cls.newInstance();
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }
    public static void main(String[] args)
    {
        // 獲取實例后無須類型轉換
        Date d = CrazyitObjectFactory2.getInstance(Date.class);
        JFrame f = CrazyitObjectFactory2.getInstance(JFrame.class);
    }
}

例子2:使用反射來獲取泛型信息

public class GenericTest
{
    private Map<String , Integer> score;
    public static void main(String[] args)
        throws Exception
    {
        Class<GenericTest> clazz = GenericTest.class;
        Field f = clazz.getDeclaredField("score");
        // 直接使用getType()取出的類型只對普通類型的成員變量有效
        Class<?> a = f.getType();
        // 下面將看到僅輸出java.util.Map
        System.out.println("score的類型是:" + a);
        // 獲得成員變量f的泛型類型
        Type gType = f.getGenericType();
        // 如果gType類型是ParameterizedType對象
        if(gType instanceof ParameterizedType)
        {
            // 強制類型轉換
            ParameterizedType pType = (ParameterizedType)gType;
            // 獲取原始類型
            Type rType = pType.getRawType();
            System.out.println("原始類型是:" + rType);
            // 取得泛型類型的泛型參數
            Type[] tArgs = pType.getActualTypeArguments();
            System.out.println("泛型信息是:");
            for (int i = 0; i < tArgs.length; i++)
            {
                System.out.println("第" + i + "個泛型類型是:" + tArgs[i]);
            }
        }
        else
        {
            System.out.println("獲取泛型類型出錯!");
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM