黑马程序员——黑马学习日志之二十一 Java高新技术(三)

时间:2022-01-04 00:31:39

------- android培训java培训、期待与您交流! ----------

黑马学习日志之二十一 Java高新技术(三)

1 Class

Class类:Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class

Class代表字节码文件,首先要将class加载到内存,才能产生Class的实例对象,它没有构造函数。

Class类代表Java类,它的各个实例对象对应各个类在内存中的字节码,例如Person类的字节码。一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class

得到各个字节码对应的实例对象:

(1)类名.class,例如,System.class 

     已经知道该类是什么类型了,直接得到该类字节码

(2)对象.getClass(),例如,new Date().getClass()

 内存中已经存在该类字节码文件了,并且已经通过字节码建立对象了,所以能通过建立的对象的getClass方法得到 内存中的字节码文件

(3)Class.forName("类名"),例如,Class.forName("java.util.Date");

 静态方法,返回字节码对象,反射一般用这种 因为写源程序不知道该类。
 如果该类字节码已经加载在内存中,直接用这个静态方法得到
 该类还没有加载到内存,该静态方法用类加载器加载到内存中缓存,才得到字节码

九个预定义Class实例对象8个基本类型 以及void都有字节码对象

方法:isPrimitives() :是否是基本类型

  Integer.TYPE:代表包装类型的基本类型  int.class == Integer.TYPE;

数组类型的Class实例对象  Class.isArray()

总之,只要在源程序中出现的类型,都有各自的Class实例对象,例如int[],void

反射

反射:就是把Java类中的各种成分映射成相应的Java类。

例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示。就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,
它们是FieldMethodContructorPackage等等

一个类中每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用,如何使用是学习和应用反射的要点。

3 构造方法的反射

Constructor类:代表某个类中的一个构造方法。

一个类有多个构造方法,根据参数的个数和类型,可以区分清楚想得到其中的哪个方法

构造方法中有得到某个类的所有的构造方法:

Constructor[] constructors =Class.forName(“java.lang.String”).getConstructors() ;

得到某一个构造方法:

Constructor  constructor =Class.forName(“java.lang.String”).getConstructor(StringBuffer.class)  

创建实例对象

通常方式:String str = new String(new StringBuffer(“abc”));

反射方式:String str = (String)constructor.newInstance(new StringBuffer(“abc”));

调用获得的方法时要用到上面相同类型的实例对象

Class.newInstance()方法:使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。

例子:String obj = (String)Class.forName(“java.lang.String”).newInstance();

该方法的返回类型是object,因为编译时 只知道你是构造方法 但是对应哪个类的构造方法是不知道。只有运行时 才知道你是哪个类的构造方法

该方法内部先得到默认的构造函数,然后用该构造方法创建实例对象。

该方法内部的具体代码是怎样写的呢,用到了缓冲机制来保存默认构造方法的实例对象。反射比较消耗性能,耗时。

4 成员变量的反射

FieldField类代表某个类中的一个成员变量

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?
所以字段fieldX 代表的是x的定义,而不是具体的x变量。

例子:

FieldDemo field = new FieldDemo("Hello", "World");

Field fieldX = field.getClass().getField("y");

String str1 = (String)fieldX.get(field);

System.out.println(str1);

Field fieldY = field.getClass().getDeclaredField("x");

fieldY.setAccessible(true);

String str2 = (String)fieldY.get(field);

System.out.println(str2);

练习:将任意一个对象中的所有String类型的成员变量所对应字符串内容中的”b”该成”a”

class  Demo
{
  Stringname="abc";
  Stringemail="abd";
  intx =5;
}
func(Objectobj)
{
Field[]fields =obj.getClass().getDeclaredFields();
for(Fieldfield :fields)
{
 if(field.getType()==java.lang.String.class)
 {
   field.setAccesible(true);
   String original =(String)field.get(obj);
   field.set(obj,original.replaceAll("b","a");
 }
}
}

5 成员方法的反射

MethodMethod类代表某个类中的一个成员方法

得到类中的某一个方法:

Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);

调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));

如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!

JDK1.4JDK1.5invoke的区别:

JDK1.5public Object invoke(Object obj, Object …args);

JDK1.4public Object invoke(Object obj, Object[] argd);

需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可用JDK1.4改写为:charAt.invoke(“str”,new Object[]()); 

6 对接收数组参数的成员方法进行反射

写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。为什么要用反射的方式调用该类中的main方法String[] 数组中的参数表示的是执行程序的类名。

问题:

启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过放射方式来调用这个main方法时,如何为invoke方法传递参数呢?在给main方法传递参数时,不能使用代码mainMethod.invoke(null, new String[]{“xxx”});Javac只能把它当作JDK1.4的语法进行理解,因此会出现参数类型不对的问题。

解决办法:

mainMethod.invoke(null,new Object[]{new String[]{“xxx”}})

mainMethod invoke(null,(Object)new String[]{“xxxx”});编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。

7 数组与Object的关系及其反射类型

具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。代表数组的Class实例对象的getSuperClass()方法返回父类为Object类对应的Class.

基本数据类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,非基本类型的一维数组,既可以被当作Object类型使用,也可以当作Object[]类型使用。

Arrays.asList()方法处理int[] String[] 时的差异

8 数组的反射应用

Array工具类用于完成对数组的反射操作。该类是个工具类,其方法为静态方法,在java.lang.reflect。Arrays是个操作数组的工具类 在java.util。两个是不一样的

public final class Array extends ObjectArray 类提供了动态创建和访问 Java 数组的方法。 

public static Object get(Object array, int index) throws IllegalArgumentException, 

ArrayIndexOutOfBoundsException返回指定数组对象中索引组件的值。
Array 允许在执行 get 或 set 操作期间进行扩展转换,但如果发生收缩转换,则抛出 IllegalArgumentException

例子:

private static void printObject(Object obj) {

Class cls = obj.getClass();

if (cls.isArray()) {

int len = Array.getLength(obj);

for (int i = 0 ; i < len; i++) {

System.out.println(Array.get(obj, i));

}

}else {

System.out.println(obj);

}

}

反射得不到数组最外层的类型 但能得到元素类型

9 ArrayList  HashSet的比较及Hashcode分析

ArrayList类似于数组,带有下标,所以存入不同对象是可以指向同一个值的(可以存入相同对象)假如同一个对象 可以放在脚标1位置 还可以放在脚标同时放在1 4位置可以的。HashSet底层是hash表的结构,不能存入相同对象,存入时先判断hashcode,再判读equals

不复写存入对象的hashcode,hashset集合默认存入对象的hashcode值是内存地址值经过运算得来
如果两对象的equals相同,那么hashcode必须复写为相同
有可能两对象复写的hashcode值相同,但是equals方法不同,这时候两对象不同
如果将对象存入集合中了,不能再对对象的值进行修改 
修改之后集合对该对象操作修改删除,操作无效,有可能导致内存的溢出

内存泄露:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。

10 框架的概念及用反射技术开发框架的原理

反射的作用:实现框架功能

框架:我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,
用户需要使用我的框架,把门窗插入进我提供的框架中。

框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。框架是人家用你的类,工具类是你用人家的工具。

框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。

综合案例
先直接用new  语句创建ArrayListHashSet的实例对象。用eclipse自动生成 ReflectPoint类的equalshashcode方法,比较两个集合的运行结果差异。然后改为采用配置文件加反射的方式创建ArrayListHashSet的实例对象,比较观察运行结果差异。引入了elipse对资源文件的管理方式。

建立一个config.properties。当程序运行的时候,通过修改该文件,就可以运行想要运行的类。尽量面向接口编程。

把程序要调用的类放在配置文件中,而在源程序中不要出现

例子:

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

InputStream fis = new FileInputStream("config.properties");

Properties prop = new Properties();

prop.load(fis);

System.out.println(prop);

fis.close();

String className = prop.getProperty("className");

System.out.println(className);

Collection coll= (Collection)Class.forName(className).newInstance();

//Collection coll = new ArrayList();

coll.add(new Demo_Field("zhangsan","hehe"));

coll.add(new Demo_Field("lisi","hehe"));

coll.add(new Demo_Field("wangwu","hehe"));

coll.add(new Demo_Field("zhangsan","hehe"));

coll.add(new Demo_Field("zhangsan","hehe"));

coll.add(new Demo_Field("zhangsan","hehe"));

int size = coll.size();

System.out.println(size);

}

11 用类加载器的方式管理资源和配置文件

配置文件所放的位置,是将字节码文件打成jar包,配置文件所放的位置不应该是用相对路径,而是用绝对路径, 这个方法是唯一解决方法

一般是用户先将配置文件配好,并且写好配置文件放在哪儿,这样我们的程序就可以将路径运算出来在javaweb中有个getRealPath();方法 可以得到项目的磁盘位置,然后配置文件放在项目内部,我们就可以拼出内部文件中配置文件的位置,这样就可以拼出绝对路径。

一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。

同时此方法还可以得到OutputStream,输出配置文件所在位置,来存配置文件。可以在window 首选项中的Open Mode-->Double click或者single click

类加载器:常用的管理配置文件方法 就是用类加载器来加载配置文件。既然类加载器能够加载类,那么同样也能加载配置文件 。

加载时是在classpath路径下一个一个文件去找

类加载器,把.class文件加载到内存中,类加载器提供的方法getClassLoader()

public InputStream getResourceAsStream(String name)返回读取指定资源的输入流。

类加载器加载,是只读的,不能修改

例子:

Input Stream ips = ReflectTest2.class.getClassLoader().getResourceAsStream

("cn/itcast/day1/config.properties");

//使用类加载器加载

InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties");

//使用相对路径

InputStream ips = ReflectTest2.class.getResourceAsStream

("/cn/itcast/day1/resources/config.properties");

使用根目录


 

 

 


 

 

 


------- android培训java培训、期待与您交流! ----------