一、类装载器
1.Java虚拟机使用每一个类的第一件事就是将该类的字节码装载进来,装载字节码的功能是由类装载器完成的。类装载器负责根据一个类的名称来定位和生成类的字节码数据,然后将这个字节码数据转换成一个java.lang.Class类的实例,每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()
方法就可以创建出该类的一个对象。
2.基本上所有的类加载器都是 java.lang.ClassLoader
类的一个实例,Java虚拟机也允许开发人员编写自己的类装载器,以便通过其他各种特殊的方式来生成类的字节码。ClassLoader还负责加载java应用所需的资源如图像文件配置文件等。
3.Java核心包中的类是由一个内嵌在Java虚拟机中名为Bootstrap的类装载器装载的,它属于虚拟机的内核,不用类装载器装载。由Bootstrap类装载的类的Class.getClassLoader方法返回的值为null,表示是Bootstrap类装载 器。Java\jre6\lib\ext下面的jar包中的类是由ExtClassLoader类装载器装载的,我们自己编写的类是由AppClassLoader进行装载的。
4.java类加载器的树形结构
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()
来获取它
5.类加载器的代理模式
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。也就是说java虚拟机判断两个类是否为同一个类不仅要看这两个类的名称是否一致,还要看看这两个类的类加载器是否一样。
二 、反射机制
1.首先了解Class类:java程序中的各个java类属于同一类事物,描述这类事物的java类就是Class,通过Class类可以获得关于这个类的各种信息,一个Class对象代表内存中的一份字节码,每个类在内存中都有一份字节码用于创建该类的对象。
2.获得Class对象(一份字节码)的三种方法:由类调用Class cl = Data.class;,对象调用.getCalss()方法获得,有Class类的静态方法Class.forName("java.util.Date");获得。第三种方法在反射中用的比较多,一般事先并不知道类的名字,类名以字符串的形式传进来,接着获得该类的字节码,进而用字节码创建对象实现功能。
3.9个预定义Class对象,包括8中基本数据类型对象和void类型的对象,源程序中出现的任意一种类型,都有对应的Class对象,例如int[ ]、void,反射就是将java中的各种成分映射成相应的java类。
4.表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应的类的实例对象表示,他们是Field、Method、Contructor、Package等等。也就是得到各种成分所对应的对象。
5.得到一个构造方法:先获得Class类型对象(字节码)在根据相应的方法获得构造方法(根据参数的不同得到不同的构造方法)
Constructor constructor = String.class.getConstructor(StringBuffer.class); String abc = (String)constructor.newInstance(new StringBuffer("abc"));用构造方法得到对象,构造方法调用newInstance方法可用来创建对象
6.成员变量:若类Point有三个变量x、y、z,可以根据类所对应的字节码获得代表变量y的Field类型对象,再根据代表变量的Field类型对象的方法fieldY.get(p)(或getInt()方法)获得特定Point对象中变量y的值。
Point p = new Point(2,3,4); //获得public的变量 Field fx = Point.class.getField("x"); int x = (Integer)fx.get(p); //获得友好类型的变量 Field fy = Point.class.getDeclaredField("y"); int y = fy.getInt(p); //获得私有类型的变量 Field fz = Point.class.getDeclaredField("z"); fz.setAccessible(true);//设置为可访问的 int z = (Integer)fz.get(p); System.out.println("x="+x+",y="+y+",z="+z);
通过反射的方式获得对象的成员变量
//扫描一个对象的成员变量,把里面成员变量中的“b”改成“a” public static void changeStringValue(Object obj) throws Exception { //获得对象中所有变量 Field[] fields = obj.getClass().getFields(); for (Field field : fields) { //class的比较都是字节码的比较用 “==” if(field.getType() == String.class) { String oldStr = (String)field.get(obj); String newStr = oldStr.replace("b","a"); field.set(obj, newStr); } } }7.成员方法:用反射的方式从字节码中得到方法,用方法再作用到某个对象,获得方法要提供两个参数,方法名和方法的参数类型的字节码。
//getMethod方法的参数为方法名和方法参数的字节码 Method mathodCharAt = String.class.getMethod("CharAt", int.class); //methodCharAt对象通过invoke方法可以为某个对象调用此方法(前一个参数为要作用的对象,后一个参数为该方法的参数) mathodCharAt.invoke(arc, 1);当我们要执行当我们要执行某一类中的方法时,我们并不知道类的名字但我们知道类中有某一方法,可用反射实现,jdk1.4和jdk1.5之间.invoke(Object obj, Objets... args);当第二个参数为数组类型时jdk1.5下会把数组拆成单个元素,在让类中的方法执行。
Jdk1.5中对于可变参数的方法一般会对数组拆包,我们若不想被拆可将数组强制转换为Object类型或在数组外面再包一层(Object类型)数组。一个数组也是Object类型的对象。
8.通过配置文件实例化类,Properties和HashMap类似里面存储的内容都是键值对的形式,但不同的是Properties类通过流对象可以读取文件的内容和把内容写入到文件,存储到文件的方法是store(OutputStream out,String comment),从文件中获取内容的方法load(InputStream inStream)
如下示例从config.properties文件读取数据,获得类名用类名构造一个该类的对象,文件内容"classname = java.util.Arraylist"。
InputStream is = new FileInputStream("config.properties"); Properties p = new Properties(); p.load(is); is.close(); String className = p.getProperty("className"); Class cl = Class.forName(className); Collection collection = (Collection)cl.newInstance();
9.当我们运行一个类时,类加载器会自动把类加载到内存中所以类加载器也可以把其他文件加载到内存中,获得某个类的类加载器的方法KuangJia.class.getClassLoader(); 通过类加载器加载文件返回一个输入流类型注意类加载器是从 项目的根目录开始搜索的
KuangJia.class.getClassLoader().getResourceAsStream("cn/itcust/day2/config.properties")。
还有另一个方法其实内部还是调用类的加载器只是这时路径相对于 该类所在的目录 。
KuangJia.class.getResourceAsStream("config.properties")。
这种方法我们也可以用绝对路径这时是从项目的根目录开始搜索的
KuangJia.class.getResourceAsStream("/cn/itcust/day2/config.properties")。
10.javaBean的内省操作:对一个javaBean里面的的属性进行操作,首先要获得描述该属性的一个PropertyDescriptor类再根据该类获得对应与属性的get和set方法。
获得属性值;
Point p = new Point(2,3,4); //属性名称 String propertyName = "x"; //根据属性名和实例化对象的字节码获得描述该属性x的一个对象。 PropertyDescriptor pd = new PropertyDescriptor(propertyName, p.getClass()); //获得属性x的get方法 Method methodGetx = pd.getReadMethod(); //调用对象p的属性x的get方法 methodGetx.invoke(p, null);封装成方法:
//内省方式获得对象的属性值 public Object getProperty(Object obj,String pName) throws Exception { //根据属性名和实例化对象的字节码获得描述该属性x的一个对象。 PropertyDescriptor pd = new PropertyDescriptor(pName, obj.getClass()); //获得属性x的get方法 Method methodGetx = pd.getReadMethod(); //调用对象p的属性x的get方法 return methodGetx.invoke(obj, null); } //内省的方式给对象的属性赋值 public void setProperty(Object obj,String pName,Object pValue) throws Exception { PropertyDescriptor pd = new PropertyDescriptor(pName, obj.getClass()); Method methodSet = pd.getWriteMethod(); methodSet.invoke(obj, pValue); }
三、JDK1.5新增的一些特性
1.静态导入
静态导入及导入一个类中的某个静态方法。
Math类是一final类里面定义了两个静态变量和大量提供运算的静态方法。
当一个类用final来修饰时,及这个类不能被改变,也就是不能被继承。
当一个方法被final修饰时,这个方法不能被重写。
当一个成员变量被final修饰时,这个变量为常量,必须赋予初值不能改变。
当一个参数被final修饰时,这个参数不能被改变。
在方法的局部变量前加final可以在该方法中的内部类访问。
当java程序运行时,类的字节码文件将被加载到内存,为类中的static方法分配了入口地址,当类创建第一个对象时为类中的实例方法分配入口地址,再创建对象时不再分配入口地址,也就是说方法的入口地址被所有对象共享。
2.可变参数
一个方法接收的参数不定可用可变参数来实现。可变参数只能出现在参数列表的最后,调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组形式访问该可变参数。例如:
public static int add(int n,int... is){
int sum=n;
for(int i=0;i<is.length;i++){
sum+=is[i];
}
return sum;
}
方法的重写,在子类中重写父类中的方法时不能降低方法的访问权限,如父类中的protected方法,子类重写时要为protected或public方法,父类的private方法子类不能重写。
3.增强for循环
for(type 变量名:集合变量名){...}
迭代变量必须在()中定义!
集合变量可以是数组或实现了iterable接口的集合类。
该集合可以使用Iterator<T>iterator()方法获得Interface Iterator<E>迭代器,从迭代器中取出集合元素。
4.自动拆箱和装箱
自动拆箱:Integer类型的对象可以自动转换成int型与int型数据运算。
自动装箱:Integer i3=4;
5.享元模式 flyweight
在java中有很多使用频繁的对象,我们只创建一次,把不同的属性用方法的参数形式传入(外部状态),相同的属性作为成员变量(内部状态)。在以后每次使用时该对象时不需创建只需调用就可以了。
-128到127的Integer类型的对象java用享元模式设计。
比较两个对象是否为同一个对象用==
Integer i1=7;
Integer i2=7;
System.out.println(i1==i2);结果true,享元模式设计引用同一个对象。
Integer i1=new Integer(7);
Integer i2=new Integer(7);
System.out.println(i1==i2);结果fault,分别创建了两个对象
String s3="abc";
String s4="abc";
System.out.println(s1==s2); 结果为true,s3和s4引用同一个对象。
System.out.println(sum(5,4));
String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1==s2);结果为fault,分别创建了两个对象。
5.类型安全的枚举
用普通类实现枚举类型
定义枚举类型变量时,首先创建一个类,再类中定义该类型的常量,构造方法设为私有,及不能在类的外部实例化新的对象。
public abstract class WeekDay {
//构造方法声明为私有,在外面不能创建新的对象
private WeekDay() {
}
//定义枚举类型对象元素为final,static
public final static WeekDay SUN=new WeekDay(){
@Override
public WeekDay nextDay() {
return MON;
}
};
public final static WeekDay MON=new WeekDay(){
@Override
public WeekDay nextDay() {
return SUN;
}
};
public abstract WeekDay nextDay();
public String toString(){
return this==SUN?"SUN":"MON";
}
}
Abstract修饰的类为抽象类,抽象类不能直接实例化对象,要有其子类实例化对象。
抽象类中可以有抽象方法,抽象方法用abstract修饰,不能有方法体,且抽象方法只能在抽象类中出现。
用专门的枚举类实现:
枚举相当于一个类,枚举元素相当于该类实例化的对象。
public enum WeekDay{
SUN,MON,TUE,WED,THI,FRI,STA;
}
该枚举类有几个静态方法如values()返回一个装有所有枚举元素的数组。
WeekDay.valueOf("SUN");方法可以把String类型的name转换成该类型的对象
带有构造方法的枚举:
public enum WeekDay{
SUN(0),MON,TUE,WED,THI,FRI,STA;
private WeekDay(){System.out.println("first");}
private WeekDay(int n){System.out.println("second");}
}
创建枚举时带上参数可以指定使用哪个构造方法创建枚举元素。
带有抽象方法的枚举类型交通灯实现
当类中定义抽象方法时,枚举元素便不能有该类产生,要有该枚举类的子类产生,用内部类的方法实现。
//带有抽象方法的枚举类型实现
public enum TrafficLamp{
RED(30){
public TrafficLamp nextLamp() {
return GREEN;
}},
GREEN(40){
public TrafficLamp nextLamp() {
return YELLOW;
}},
YELLOW(5){
public TrafficLamp nextLamp() {
return RED;
}};
private int time;
private TrafficLamp(int time){this.time=time;}
//枚举中的抽象方法下一个元素
public abstract TrafficLamp nextLamp();
}
当枚举类型只有一个元素时,可以作为单例模式的一种实现方式。
6.泛型:泛型就是变量类型的参数化。