JAVA反射机制作用是什么

转自这里

Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对大家以后学习框架技术有很大的帮助。

那么什么是Java的反射呢?

       大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。

Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。

那么Java反射有什么作用呢?

假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。

Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。

Class类

       要正确使用Java反射机制就得使用java.lang.Class这个类。它是Java反射机制的起源。当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员以及构造方法的声明和定义等信息。

反射API

       u反射API用于反应在当前Java虚拟机中的类、接口或者对象信息

u功能
—获取一个对象的类信息.

       —获取一个类的访问修饰符、成员、方法、构造方法以及超类的信息.

       —检获属于一个接口的常量和方法声明.

       —创建一个直到程序运行期间才知道名字的类的实例.

       —获取并设置一个对象的成员,甚至这个成员的名字是
   在程序运行期间才知道.

       —检测一个在运行期间才知道名字的对象的方法

       利用Java反射机制我们可以很灵活的对已经加载到Java虚拟机当中的类信息进行检测。当然这种检测在对运行的性能上会有些减弱,所以什么时候使用反射,就要靠业务的需求、大小,以及经验的积累来决定。

       那么如何利用反射API在运行的时候知道一个类的信息呢?

代码示例:

[java]  view plain copy
  1. "font-size:16px;">import java.lang.reflect.Field;  
  2. import java.lang.reflect.Method;  
  3. import javax.swing.JOptionPane;  
  4. /** 
  5.   *本类用于测试反射API,利用用户输入类的全路径, 
  6. *找到该类所有的成员方法和成员属性 
  7.   */  
  8. public class MyTest {  
  9.      /** 
  10.      *构造方法 
  11.      */  
  12.     public MyTest(){  
  13.        String classInfo=JOptionPane.showInputDialog(null,"输入类全路径");//要求用户输入类的全路径  
  14.        try {  
  15.            Class cla=Class.forName(classInfo);//根据类的全路径进行类加载,返回该类的Class对象  
  16.             
  17.            Method[] method=cla.getDeclaredMethods();//利用得到的Class对象的自审,返回方法对象集合  
  18.             
  19.            for(Method me:method){//遍历该类方法的集合  
  20.               System.out.println(me.toString());//打印方法信息  
  21.            }  
  22.             
  23.            System.out.println("********");  
  24.             
  25.            Field[] field=cla.getDeclaredFields();//利用得到的Class对象的自审,返回属性对象集合  
  26.            for(Field me:field){ //遍历该类属性的集合  
  27.               System.out.println(me.toString());//打印属性信息  
  28.            }  
  29.        } catch (ClassNotFoundException e) {  
  30.            e.printStackTrace();  
  31.        }  
  32.     }  
  33.     public static void main(String[] args) {  
  34.        new MyTest();  
  35.     }  
  36. }  

运行的时候,我们输入javax.swing.JFrame,那么运行结果如下:

public void javax.swing.JFrame.remove(java.awt.Component)

public void javax.swing.JFrame.update(java.awt.Graphics)

…………

********

public static final int javax.swing.JFrame.EXIT_ON_CLOSE

private int javax.swing.JFrame.defaultCloseOperation

…………

    大家可以发现,类的全路径是在程序运行的时候,由用户输入的。所以虚拟机事先并不知道所要加载类的信息,这就是利用反射机制来对用户输入的类全路径来对类自身的一个自审。从而探知该类所拥有的方法和属性。

通过上面代码,大家可以知道编译工具为什么能够一按点就能列出用户当前对象的属性和方法了。它是先获得用户输入对象的字符串,然后利用反射原理来对这样的类进行自审,从而列出该类的方法和属性。

使用反射机制的步骤:

u导入java.lang.relfect 包

u遵循三个步骤
第一步是获得你想操作的类的 java.lang.Class 对象
第二步是调用诸如 getDeclaredMethods 的方法
第三步使用 反射API 来操作这些信息

获得Class对象的方法

u如果一个类的实例已经得到,你可以使用

       【Class c = 对象名.getClass(); 

      例: TextField t = new TextField();

              Class c = t.getClass();

              Class s = c.getSuperclass();

u如果你在编译期知道类的名字,你可以使用如下的方法

Class c = java.awt.Button.class; 
或者

         Class c = Integer.TYPE;

u如果类名在编译期不知道, 但是在运行期可以获得, 你可以使用下面的方法

          Class c = Class.forName(strg);

   这样获得Class类对象的方法,其实是利用反射API把指定字符串的类加载到内存中,所以也叫类加载器加载方法。这样的话,它会把该类的静态方法和静态属性,以及静态代码全部加载到内存中。但这时候,对象还没有产生。所以为什么静态方法不能访问非静态属性和方法。因为静态方法和属性产生的时机在非静态属性和方法之前。

代码示例:

[java]  view plain copy
  1. "font-size:16px;">package  com;  
  2.    
  3. public class MyTest {  
  4.     public static void main(String[] args) {  
  5.        TestOne  one=null;  
  6.        try{  
  7.        Class  cla=Class.forName("com.TestOne");//进行com.TestOne类加载,返回一个Class对象  
  8.        System.out.println("********");  
  9.        one=(TestOne)cla.newInstance();//产生这个Class类对象的一个实例,调用该类无参的构造方法,作用等同于new TestOne()  
  10.        }catch(Exception e){  
  11.            e.printStackTrace();  
  12.        }  
  13.        TestOne two=new TestOne();  
  14.   System.out.println(one.getClass() == two.getClass());//比较两个TestOne对象的Class对象是否是同一个对象,在这里结果是true。说明如果两个对象的类型相同,那么它们会有相同的Class对象  
  15.     }  
  16. }  
  17.    
  18. class TestOne{  
  19.     static{  
  20.        System.out.println("静态代码块运行");  
  21.     }  
  22.     TestOne(){  
  23.        System.out.println("构造方法");  
  24.     }  
  25. }  

  以上代码过行的结果是:

静态代码块运行

***********

构造方法

构造方法


代码分析:

在进行Class.forName("com.TestOne")的时候,实际上是对com.TestOne进行类加载,这时候,会把静态属性、方法以及静态代码块都加载到内存中。所以这时候会打印出"静态代码块运行"。但这时候,对象却还没有产生。所以"构造方法"这几个字不会打印。当执行cla.newInstance()的时候,就是利用反射机制将Class对象生成一个该类的一个实例。这时候对象就产生了。所以打印"构造方法"。当执行到TestOne two=new TestOne()语句时,又生成了一个对象。但这时候类已经加载完毕,静态的东西已经加载到内存中,而静态代码块只执行一次,所以不用再去加载类,所以只会打印"构造方法",而"静态代码块运行"不会打印。

反射机制不但可以例出该类对象所拥有的方法和属性,还可以获得该类的构造方法及通过构造方法获得实例。也可以动态的调用这个实例的成员方法。

代码示例:

[java]  view plain copy
  1. "font-size:16px;">package reflect;  
  2.    
  3. import java.lang.reflect.Constructor;  
  4.    
  5.    
  6. /** 
  7.  * 
  8.  * 本类测试反射获得类的构造器对象, 
  9.  * 并通过类构造器对象生成该类的实例 
  10.  * 
  11.  */  
  12. public class ConstructorTest {  
  13.    
  14.     public static void main(String[] args) {  
  15.        try {  
  16.            //获得指定字符串类对象  
  17.            Class cla=Class.forName("reflect.Tests");  
  18.            //设置Class对象数组,用于指定构造方法类型  
  19.            Class[] cl=new Class[]{int.class,int.class};  
  20.             
  21.            //获得Constructor构造器对象。并指定构造方法类型  
  22.            Constructor con=cla.getConstructor(cl);  
  23.             
  24.            //给传入参数赋初值  
  25.            Object[] x={new Integer(33),new Integer(67)};  
  26.             
  27.            //得到实例  
  28.            Object obj=con.newInstance(x);  
  29.        } catch (Exception e) {  
  30.            e.printStackTrace();  
  31.        }  
  32.     }  
  33.    
  34. }  
  35.    
  36. class Tests{  
  37.     public Tests(int x,int y){  
  38.        System.out.println(x+"    "+y);  
  39.     }  
  40. }  

运行的结果是” 33    67。说明我们已经生成了Tests这个类的一个对象。 


三、Java类反射中所必须的类:
Java的类反射所需要的类并不多,它们分别是:Field、Constructor、Method、Class、Object,下面我将对这些类做一个简单的说明。
Field类:提供有关类或接口的属性的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)属性或实例属性,简单的理解可以把它看成一个封装反射类的属性的类。
Constructor类:提供关于类的单个构造方法的信息以及对它的访问权限。这个类和Field类不同,Field类封装了反射类的属性,而Constructor类则封装了反射类的构造方法。
Method类:提供关于类或接口上单独某个方法的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。 这个类不难理解,它是用来封装反射类方法的一个类。
Class类:类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
Object类:每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。

四、Java的反射类能做什么:
看完上面的这么多我想你已经不耐烦了,你以为我在浪费你的时间,那么好吧!下面我们就用一些简单的小例子来说明它。
首先我们来看一下通过Java的反射机制我们能得到些什么。
首先我们来写一个类:

java 代码
 
  1. import  java.awt.event.ActionListener;  
  2. import  java.awt.event.ActionEvent;  
  3. class  A  extends  Object  implements  ActionListener{  
  4. private   int  a =  3 ;  
  5. public  Integer b =  new  Integer( 4 );  
  6. public  A(){}  
  7. public  A( int  id,String name){}  
  8. public   int  abc( int  id,String name){ return   0 ;}  
  9. public   void  actionPerformed(ActionEvent e){}  
  10. }  


你可能被我这个类弄糊涂了,你看不出我要做什么,那就不要看这个类了,这个类是用来测试的,你知道知道它继承了Object类,有一个接口是ActionListener,两个属性int和Integer,两个构造方法和两个方法,这就足够了。
下面我们把A这个类作为一个反射类,来过去A类中的一些信息,首先我们先来过去一下反射类中的属性和属性值。

java 代码
 
  1. import  java.lang.reflect.*;  
  2. class  B{  
  3. public   static   void  main(String args[]){  
  4. A r = new  A();  
  5. Class temp = r.getClass();  
  6. try {  
  7. System.out.println("反射类中所有公有的属性" );  
  8. Field[] fb =temp.getFields();  
  9. for ( int  j= 0 ;j
  10. Class cl = fb[j].getType();  
  11. System.out.println("fb:" +cl);  
  12. }  
  13.   
  14. System.out.println("反射类中所有的属性" );  
  15. Field[] fa = temp.getDeclaredFields();  
  16. for ( int  j= 0 ;j
  17. Class cl = fa[j].getType();  
  18. System.out.println("fa:" +cl);  
  19. }  
  20. System.out.println("反射类中私有属性的值" );  
  21. Field f = temp.getDeclaredField("a" );  
  22. f.setAccessible(true );  
  23. Integer i = (Integer)f.get(r);  
  24. System.out.println(i);  
  25. }catch (Exception e){  
  26. e.printStackTrace();  
  27. }  
  28. }  
  29.   
  30. }   


这里用到了两个方法,getFields()、getDeclaredFields(),它们分别是用来获取反射类中所有公有属性和反射类中所有的属性的方法。另外还有getField(String)和getDeclaredField(String)方法都是用来过去反射类中指定的属性的方法,要注意的是getField方法只能取到反射类中公有的属性,而getDeclaredField方法都能取到。
这里还用到了Field 类的setAccessible方法,它是用来设置是否有权限访问反射类中的私有属性的,只有设置为true时才可以访问,默认为false。另外 Field类还有set(Object AttributeName,Object value)方法,可以改变指定属性的值。

下面我们来看一下如何获取反射类中的构造方法

java 代码
 
  1. import  java.lang.reflect.*;  
  2. public   class  SampleConstructor {  
  3. public   static   void  main(String[] args) {  
  4. A r = new  A();  
  5. printConstructors(r);  
  6. }  
  7.   
  8. public   static   void  printConstructors(A r) {  
  9. Class c = r.getClass();  
  10. //获取指定类的类名   
  11. String className = c.getName();  
  12. try  {  
  13. //获取指定类的构造方法   
  14. Constructor[] theConstructors = c.getConstructors();  
  15. for ( int  i= 0 ; i
  16. //获取指定构造方法的参数的集合   
  17. Class[] parameterTypes = theConstructors[i].getParameterTypes();  
  18.   
  19. System.out.print(className + "(" );  
  20.   
  21. for ( int  j= 0 ; j
  22. System.out.print(parameterTypes[j].getName() + " " );  
  23.   
  24. System.out.println(")" );  
  25.   
  26. }  
  27. }catch (Exception e) {  
  28. e.printStackTrace();  
  29. }  
  30. }  
  31. }  

这个例子很简单,只是用getConstructors()方法获取了反射类的构造方法的集合,并用Constructor类的getParameterTypes()获取该构造方法的参数。

下面我们再来获取一下反射类的父类(超类)和接口

java 代码
 
  1. import  java.io.*;  
  2. import  java.lang.reflect.*;  
  3.   
  4. public   class  SampleInterface {  
  5. public   static   void  main(String[] args)  throws  Exception {  
  6. A raf = new  A();  
  7. printInterfaceNames(raf);  
  8. }  
  9.   
  10. public   static   void  printInterfaceNames(Object o) {  
  11. Class c = o.getClass();  
  12. //获取反射类的接口   
  13. Class[] theInterfaces = c.getInterfaces();  
  14. for ( int  i= 0 ; i
  15. System.out.println(theInterfaces[i].getName());  
  16. //获取反射类的父类(超类)   
  17. Class theSuperclass = c.getSuperclass();  
  18. System.out.println(theSuperclass.getName());  
  19. }  
  20. }  


这个例子也很简单,只是用Class类的getInterfaces()方法获取反射类的所有接口,由于接口可以有多个,所以它返回一个 Class数组。用getSuperclass()方法来获取反射类的父类(超类),由于一个类只能继承自一个类,所以它返回一个Class对象。

下面我们来获取一下反射类的方法

java 代码
 
  1. import  java.lang.reflect.*;  
  2. public   class  SampleMethod {  
  3.   
  4. public   static   void  main(String[] args) {  
  5. A p = new  A();  
  6. printMethods(p);  
  7. }  
  8.   
  9. public   static   void  printMethods(Object o) {  
  10. Class c = o.getClass();  
  11. String className = c.getName();  
  12. Method[] m = c.getMethods();  
  13. for ( int  i= 0 ; i
  14. //输出方法的返回类型   
  15. System.out.print(m[i].getReturnType().getName());  
  16. //输出方法名   
  17. System.out.print(" " +m[i].getName()+ "(" );  
  18. //获取方法的参数   
  19. Class[] parameterTypes = m[i].getParameterTypes();  
  20. for ( int  j= 0 ; j
  21. System.out.print(parameterTypes[j].getName());  
  22. if (parameterTypes.length>j+ 1 ){  
  23. System.out.print("," );  
  24. }  
  25. }  
  26.   
  27. System.out.println(")" );  
  28. }  
  29.   
  30. }  
  31.   
  32. }  


这个例子并不难,它只是获得了反射类的所有方法,包括继承自它父类的方法。然后获取方法的返回类型、方法名和方法参数。

接下来让我们回过头来想一想,我们获取了反射类的属性、构造方法、父类、接口和方法,可这些东西能帮我们做些什么呢!!
下面我写一个比较完整的小例子,来说明Java的反射类能做些什么吧!!

java 代码
 
  1. import  java.lang.reflect.Constructor;  
  2. import  java.lang.reflect.Method;  
  3.   
  4. public   class  LoadMethod {  
  5. public  Object Load(String cName,String MethodName,String[] type,String[] param){  
  6. Object retobj = null ;  
  7. try  {  
  8. //加载指定的Java类   
  9. Class cls = Class.forName(cName);  
  10.   
  11. //获取指定对象的实例   
  12. Constructor ct = cls.getConstructor(null );  
  13. Object obj = ct.newInstance(null );  
  14.   
  15. //构建方法参数的数据类型   
  16. Class partypes[] = this .getMethodClass(type);  
  17.   
  18. //在指定类中获取指定的方法   
  19. Method meth = cls.getMethod(MethodName, partypes);  
  20.   
  21. //构建方法的参数值   
  22. Object arglist[] = this .getMethodObject(type,param);  
  23.   
  24. //调用指定的方法并获取返回值为Object类型   
  25. retobj= meth.invoke(obj, arglist);  
  26.   
  27. }  
  28. catch  (Throwable e) {  
  29. System.err.println(e);  
  30. }  
  31. return  retobj;  
  32. }  
  33.   
  34. //获取参数类型Class[]的方法   
  35. public  Class[] getMethodClass(String[] type){  
  36. Class[] cs = new  Class[type.length];  
  37. for  ( int  i =  0 ; i < cs.length; i++) {  
  38. if (!type[i].trim().equals( "" )||type[i]!= null ){  
  39. if (type[i].equals( "int" )||type[i].equals( "Integer" )){  
  40. cs[i]=Integer.TYPE;  
  41. }else   if (type[i].equals( "float" )||type[i].equals( "Float" )){  
  42. cs[i]=Float.TYPE;  
  43. }else   if (type[i].equals( "double" )||type[i].equals( "Double" )){  
  44. cs[i]=Double.TYPE;  
  45. }else   if (type[i].equals( "boolean" )||type[i].equals( "Boolean" )){  
  46. cs[i]=Boolean.TYPE;  
  47. }else {  
  48. cs[i]=String.class ;  
  49. }  
  50. }  
  51. }  
  52. return  cs;  
  53. }  
  54.   
  55. //获取参数Object[]的方法   
  56. public  Object[] getMethodObject(String[] type,String[] param){  
  57. Object[] obj = new  Object[param.length];  
  58. for  ( int  i =  0 ; i < obj.length; i++) {  
  59. if (!param[i].trim().equals( "" )||param[i]!= null ){  
  60. if (type[i].equals( "int" )||type[i].equals( "Integer" )){  
  61. obj[i]= new  Integer(param[i]);  
  62. }else   if (type[i].equals( "float" )||type[i].equals( "Float" )){  
  63. obj[i]= new  Float(param[i]);  
  64. }else   if (type[i].equals( "double" )||type[i].equals( "Double" )){  
  65. obj[i]= new  Double(param[i]);  
  66. }else   if (type[i].equals( "boolean" )||type[i].equals( "Boolean" )){  
  67. obj[i]=new  Boolean(param[i]);  
  68. }else {  
  69. obj[i] = param[i];  
  70. }  
  71. }  
  72. }  
  73. return  obj;  
  74. }  
  75. }  


这是我在工作中写的一个实现Java在运行时加载指定的类,并调用指定方法的一个小例子。这里没有main方法,你可以自己写一个。
Load方法接收的五个参数分别是,Java的类名,方法名,参数的类型和参数的值。


      Java中反射机制使用的还是比较广泛的,系统的灵活性、可扩展性大都都是通过反射等方式来加载外部插件,使得系统与插件解耦的同时,增加了功能。但是很多人都只是会用,却是不知道它的实现机制,今天就由我来带大家揭开反射机制的神秘面纱。

       Java中是用Class.forName(classname)来反射类。


  1. package com.java.reflecttest;  
  2.   
  3. import com.java.dbtest.DBTest;  
  4.   
  5.   
  6. /** 
  7.  * Java反射机制测试 
  8.  * @author Longxuan 
  9.  * 
  10.  */  
  11. public class ReflectTest {  
  12.   
  13.     /** 
  14.      * 测试反射类 
  15.      */  
  16.     public static void refTest(){  
  17.         String className = "com.java.dbtest.TestConnection";  
  18.         DBTest dbTest = null;  
  19.         try {  
  20.               
  21.             //通过反射机制,使用类装载器,装载该类  
  22.             Class tc = Class.forName(className);  
  23.               
  24.             //输出反射得到的类  
  25.             System.out.println(tc);  
  26.               
  27.             //创建该类的实例,转化为接口  
  28.             dbTest =(DBTest)tc.newInstance();  
  29.               
  30.             //通过接口,调用该类的方法  
  31.             dbTest.SelectUser();  
  32.               
  33.         } catch (ClassNotFoundException e) {  
  34.             e.printStackTrace();  
  35.         } catch (InstantiationException e) {  
  36.             e.printStackTrace();  
  37.         } catch (IllegalAccessException e) {  
  38.             e.printStackTrace();  
  39.         }  
  40.           
  41.     }  
  42.       
  43.     public static void main(String[] args){  
  44.         refTest();  
  45.     }  
  46. }  


       通过main函数的调试,已经通过,结果如图:

       

       经过调试,查资料,结合自己的推测和理解,似乎是明白了一些。现与大家分享讨论。

       先说执行过程:

       Class.forName(classname)方法,实际上是调用了Class类中的 Class.forName(classname, true, currentLoader)方法。参数:name - 所需类的完全限定名;initialize - 是否必须初始化类;loader - 用于加载类的类加载器。currentLoader则是通过调用ClassLoader.getCallerClassLoader()获取当前类加载器的。类要想使用,必须用类加载器加载,所以需要加载器。反射机制,不是每次都去重新反射,而是提供了cache,每次都会需要类加载器去自己的cache中查找,如果可以查到,则直接返回该类。


       有意思的是java的类加载器也有些门道。它分为BootStrap Class Loader(引导类加载器),Extensions Class Loader (扩展类加载器),App ClassLoader(或System Class Loader),当然少不了Custom ClassLoader(用户自定义类加载器)。其加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。类加载器的详细介绍会在接下来的博文中较深入的分析,欢迎期待。


       forName方法中,调用了ClassLoader.loadClass方法来完成类的反射。根据类加载器的特殊性,结合我的调试过程,画了一个简单的流程图,



       我的这幅图简单的说明了类加载器的类加载过程。先检查自己是否已经加载过该类,如果加载过,则直接返回该类,若没有则调用父类的loadClass方法,如果父类中没有,则执行findClass方法去尝试加载此类,也就是我们通常所理解的片面的"反射"了。这个过程主要通过ClassLoader.defineClass方法来完成。defineClass 方法将一个字节数组转换为 Class 类的实例(任何类的对象都是Class类的对象)。这种新定义的类的实例需要使用 Class.newInstance 来创建,而不能使用new来实例化。

 

      为什么说“任何类的对象都是Class类的对象”呢?在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类(.java文件),编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。


       其实说的简单通俗一点,就是在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。


       以上内容是我经过调试、查java Api和网上资料,结合自己的理解,与大家分享讨论的,如果有错,欢迎大家指正,我们共同进步。


你可能感兴趣的:(Java)