------- android培训、java培训、期待与您交流! ----------
Java高级部分
增强for循环的时候仅仅对实现了Iterable并实现了其iterator方法的类或者数组才能使用,类如list.,但是对于map来说,就不能使用.
自动装箱和拆箱.
当一个基本数据类型和它们各自的对象相互使用时候,会相互转换。
十分要注意:
对于这些基本类型的整数(如int),要装箱成为Integer对象的时候,如果数字在一个字节范围中-128------127;也就会把这些数字缓冲起来。缓冲在一个池中。当下次又要当成一个对象的话。先看池中有没有这个整数,如果有,直接拿就可以了。
这样节省了内存。也就是大家都去用它,而且不去改变它。
这是一种设计模式:享元模式。
有很多个小的对象,它们有很多属性相同,把它们变成一个对象。
那些不同 的属性方法的参数,称为外部状态,做为方法的参数传入。相同的属性称为对象的内部状态。这样就使对象的个数大大减少。
枚举:(当枚举只有一个成员时候,就可以作为一种单例的实现方式)
也就是我定义了这么一个类型(有固定的一组值)。
那么以后你使用这个类型定义一个值,只能是我提前定义好的那些值,如果不是那些值。编译器就不让你编过。
优点:在编译源程序的时候就发现你填写的值不合法。
例子:
class WeekDay { private WeekDay() { } public final static WeekDay SUN = new WeekDay(); public final static WeekDay MON = new WeekDay(); } WeekDay w = WeekDay.SUN;
只能为这个类的引用去使用这个类中特定的值,而不是随便写的值。
在这个类中也可以定义方法。比如定义一个方法返回明天是星期几?
public WeekDay nextDay() { if(this==SUN) { return MON; } else { return SUN; } }
可以定义如上的方法。以获得明天。而对于我们想打印这个枚举对象的时候,应该定义toString方法,如下
public String toString() { if(this==SUN) return "SUN"; else return "MON"; }
这是我们用普通的java类来实现的枚举类。
将nextDay中的if..else语句转成一个个独立的类。
public abstract class WeekDay { private WeekDay() { } 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() { if(this==SUN) return "SUN"; else return "MON"; } }
现在使用java中的枚举类.
public enum WeekDay{ SUN,MON,TUE,WED,THI,FRI,SAT; }
而我给这个枚举类型的变量赋值的时候,只能用它里边定义的那些元素。
WeekDay weekDay2 = WeekDay.FRI;
枚举就相当于是一个类,枚举里边的元素就相当于是这个类定义的一个实例对象。
对于枚举这样的对象,看看有什么方法可以用。
System.out.println(weekDay2.FRI);
也就是java定义的枚举自动帮我们实现了其中的toString();
这比我们手工提供一个枚举类要省很多事情。
weekDay2.name()//得到这个对象的名字、 weekDay2.ordinal();//也就是这个枚举类中这个对象的值排行老几。
看看枚举这个类中有什么静态的方法
WeekDay.valueOf(“SUN”)
将一个普通的字符串,变成一个枚举类型的对象。
WeekDay.values()
得到一个枚举类中所有的枚举元素的数组。对于我们这个例子来说。数组的类型是
WeekDay[]
这个方法用来实现对枚举类的遍历,先得到所有元素数组,再去遍历这个数组。
实现带有构造方法的枚举。
1, 元素列表必须位于所有的成分之前。
2, 如果元素列表后有东西,元素列表后必须带;号
3, 对于枚举的构造方法。必须是private
由于枚举中的成员都是static类型,所以只要加载了类,它的静态代码就会执行,所以它的构造方法在加载的时候也会被调用。
要想使枚举中的元素在构造的时候调用它有参数的构造函数的时候(默认调用它无参的构造函数),使用下边的方法。
public enum WeekDay{ SUN(1),MON,TUE,WED,THI,FRI,SAT; private WeekDay() { System.out.println("first"); } private WeekDay(int day) { System.out.println("second"); } }
带有抽象方法的枚举,并含有成员变量以及自定义的构造方法。
public enum TrafficLamp { RED(30){ @Override public TrafficLamp nextLamp() { return GREEN; } }, GREEN(45){ @Override public TrafficLamp nextLamp() { return YELLOW; } }, YELLOW(5){ @Override public TrafficLamp nextLamp() { return RED; } }; private int time; public abstract TrafficLamp nextLamp(); private TrafficLamp(int time) { this.time = time; } }
Class类。
Java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class
Class c1 =Date.class;//存放的是字节码文件对象
也可以通过 对象.getClass()来的到一个类的字节码对象。
由ClassLoader和它的子类来实现的对其类的装载,它是一个重要的java运行时系统组件,它负责运行时候查找和装入类文件的一个类。
具体的过程是这样的:.java文件经过编译器编译之后就被转换成java字节码.class文件。类加载器负责读取java字节码代码,再利用这个字节码在内存中创建成为java.lang.Class类的一个实例。也就是一个字节码文件对象。
类加载的时候,通过动态装载技术在运行的时候动态的去加载。
那么什么时候去装载一个特定的类呢?
类加载的时候,通过动态装载技术在运行的时候动态的去加载,
1. 使用Class.forName(),JVM会加载一个类并创建一个类的字节码文件对象并返回。
2.new 一个对象的时候,会加载一个类。并用加载的这个字节码文件对象去创建类的对象,使用对象.getClass()可以的到这个字节码文件对象
3.使用 类名.class会加载一个类,并得到这个类的字节码文件对象。
使用:
Class.forName(“java.lang.String”);//可以指定一个类的完整的名称。
它得到这个类的字节码文件对象。得到的方式有两种。
1:这个类的字节码已经加载到内存中了,这时候就不用加载了。直接找到那份字节码返回即可。
2:这个类的字节码在虚拟机中 还没有这个字节码。于是,用类加载器去加载。
加载完成后,将字节码缓冲起来。同时上边这个方法返回刚刚加载进来的字节码文件对象。
得到字节码的方式有三种:
1.类名.class 例如,System.class;
2.对象.getClass() ,new Data().getClass()
3.使用静态的方法加载这个字符串所对应类的字节码。
Class.forName(“java.util.String”);
//第三种可以动态的去指定去加载哪个类所对应的字节码。
可以把这个字符串变成一个变量
String str1 = "abc"; Class cls1 = str1.getClass(); Class cls2 = Class.forName("java.lang.String"); Class cls3 = String.class; System.out.println(cls1 == cls2); System.out.println(cls2 == cls3);
也就证明了,不管使用什么方法去得到对应类的字节码对象,其实都是那一个。
字节码文件对象在内存中仅仅只有一份。
cls1.isPrimitive()//判断是一个基本类型的字节码还是类类型的字节码。
基本类型的字节码文件对象有
boolean、byte、char、short、int、long、float 和 double,void对应的字节码文件对象。
注意:
Int.class!=Integer.class;
但是
int.class ==Integer.TYPE
Integer.TYPE表示的是这个包装的基本类型的字节码文件对象。
使用
public boolean isArray()
判定此 Class 对象是否表示一个数组类。
如int[].class.isArray() 值为true;
总之,在源程序中出现的任何类型都有各自的Class实例对象,例如Int[],void.
反射:
反射就是利用类的字节码文件对象把java类中的各个成分映射成相应的java类对象。
如将包名映射成一个Package类的实例对象来表示。
如一个具体的方法被映射成一个Method类的实例对象。
Constructor类就代表这个字节码中的一个构造方法。
Constructor[] constructors= Class.forName("day9.Person").getConstructors(); Constructor c=Class.forName("day9.Person").getConstructor(int.class); // Person p = (Person)c.newInstance(); Person pp = (Person)c.newInstance(2); //由于编译器不知道这个构造方法对象究竟对应的那个构造方法。 //运行的时候才知道你究竟 调用了那个构造方法 // System.out.println(p); System.out.println(pp); Constructor c=Class.forName("day9.Person").getConstructor(int.class);
获得方法时候要使用类型。
调用获得的方法时候要用到上边类型相同的实例对象
Person pp = (Person)c.newInstance(2);
Class类中也有一个newInstance,也会去创建一个对象。
这里的和构造方法有什么关系。
这里是使用无参的构造方法去创建一个类的实例对象,
Person p =(Person)Class.forName("day9.Person").newInstance();
中间省略了去得到一个构造函数类。
反射的缺点:导致程序性能下降。
Field类表示类中的成员。
class Person { public int a = 20; public int b = 10; public Person() { } public Person(int a,int b) { this.a = a; this.b = b; } } Person p = new Person(3,4); Field[] fields = p.getClass().getFields(); // 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。 Field field = p.getClass().getField("a");
这个field仅仅代表这个类字节码身上的变量。它没有对应到对象身上。
要用它去取某个对象上的对应的值。
可以这样得到某一个具体对象上变量的值
Field field =p.getClass().getField("a");
field.get(p);
//得到这个p对象的变量a的值
而对于不可见的成员变量。
使用
Field[] fields1= p.getClass().getDeclaredFields();
但是这个Field对象不能使用
field.get(p);去得到一个对应对象的值。
但是可以使用
Field field =p.getClass().getDeclaredField("a");
field.setAccessible(true);//设置为可以访问。
System.out.println(field.get(p));
这叫做暴力反射机制。
在字节码文件对象的比较中,使用==而不用equals方法去比较,因为字节码文件对象只有一份。
下边,我们使用一个例子利用反射修改其成员对象的值
package day10; import java.lang.reflect.*; import java.util.Arrays; class Point { private int x; private int y; private String string1; private String string2; public Point(int x, int y,String s1,String s2) { super(); this.string1 = s1; this.string2 = s2; this.x = x; this.y = y; } } public class Test1 { public static void main(String[] args) throws Exception { Point pp = new Point(2,3,"awanga","abcabc"); Class c = pp.getClass(); Field[] fields = c.getDeclaredFields(); for(Field field:fields) { field.setAccessible(true); if(field.getType()==String.class) { String s = (String)field.get(pp); String ss = s.replace('a','b'); field.set(pp,ss); } System.out.println(field.get(pp)); } } }
作用:也就是你给我一个对象,我用特定的值给你改掉你其中的值。
这也是Spring框架的功能。换掉一个对象中的字段。
Method
代表字节码中的方法。不是一个对象上的方法。
由于方法的调用需要提供对象,(非静态)
所以先得到这个方法,再通过对象去调用这个方法。
调用非静态的方法
Method method = String.class.getMethod("charAt",int.class); System.out.println(method.invoke("dsfdsaf",1));
调用静态的方法
Method method1 = Math.class.getMethod("random",null); System.out.println(method1.invoke(null,null)); Method method1 = Math.class.getMethod("pow",double.class,double.class); System.out.println(method1.invoke(null,2.0,3.0));
用反射去调用一个类的Main方法
可以动态的执行任何类中main方法
Class c = Class.forName("day9.ReflectDemo1"); Method method = c.getDeclaredMethod("main",String[].class); method.invoke(null,new Object[]{new String[]{}}); method.invoke(null,(object)new String[]{});
为什么这里使用method.invoke(null,new String[]{});会报错呢?
这是因为java看到这个一个字符串类型的数组的时候,会将这个参数打开,每一个元素也就是String对象当做一个参数。所以会抛出wrongnumber of arguments
可以按照上边的这两种方式解决
数组的反射
具有相同维数和元素类型的数组具有相同的类的字节码文件对象。(也就是同一种类型)
int[] a1 = new int[3]; int[] a2 = new int[4]; int[][] a3 = new int[2][3]; String[] a4 = new String[3]; a1.getClass()==a2.getClass()为true;
下边我们来看看a1的字节码文件对象的类型名字是什么
System.out.println(al.getClass().getName());
输出 [I 也就是int类型的数组的类的名字。
接下来我们来看看这个字节码文件类的父类是什么
System.out.println(al.getClass().getSuperclass().getName());
得到
java.lang.Object,也就是这个类继承自Object
所以可以这样
Object o = a1;
Object o1 = a4;
但是
Object[] o2 =a1;编译错误
Object[] o3 =a4;编译正确
Object[] o4= a3;编译正确
分析一下,a4是一个String类型的数组,也就是数组中每一个元素是一个String
也就是一个Object。所以o4=a3; a3是一个二维数组,也就相当于一个数组中包含一个数组,由于int[]类型也是Object类型,所以o4 = a3;编译正确
但是a1中,a1是一个Object类型,但是a1中的元素为int类型,所以出现错误。
疑惑:
当我们使用Arrays中的asList()时候
int [] a1 = newint[]{1,2,3,4};
String []a4 =new String[]{"1","2"};
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
为什么a1输出为 [[I@b5dac4],而不是[1,2,3,4]。
因为a1是Object类型,不对应JDK1.4版本的Object[] a的参数
所以使用JDK1.5的去处理,参数为T…a
仅仅当做一个Object去处理也就是等效于是一个参数。[[I@b5dac4]
对数组进行反射有专门的反射类(专门用来反射数组这个类型的一个反射类)
比如得到这个数组的长度信息,得到它的第几个元素等等。
Array
而对一般的类进行反射,只有使用Class字节码文件对象去反射。
使用Array.getLength(Object o);
对一个指定的数组进行反射得到其长度。
Array.get(o,i)
得到一个指定数组o的第i个元素
但是没有办法得到指定数组的元素类型。
例子如下
private staticvoid printObject(Object o)
{
Class c = o.getClass();
if(c.isArray())
{
int len = Array.getLength(o);
for(int i=0;i<len;i++)
{
System.out.println(Array.get(o,i));
}
}
else
{
System.out.println(o);
}
}
Java反射的综合案例。
在今后使用别人的类有两种使用的方式
1. 你去调用别人的类。如String(工具)
2. 别人去调用你的类。如struts2框架中的核心控制类。(框架)
因为在写框架的时候,(比如说struts2)无法指定要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射的方式来做。
例子, 从一个配置文件中读取相应的信息,去创建相应的对象。
public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("src/day10/config.properties"); Properties p = new Properties(); p.load(is); is.close(); String className = (String)p.get("className"); Class cls = Class.forName(className); Collection collection = (Collection)cls.newInstance(); collection.add(new Point(1)); collection.add(new Point(1)); System.out.println(collection.size()); }
对于配置文件到底放在哪里比较合适?
在实际项目中,几乎没有人 用下边这种方式去做
InputStream is = newFileInputStream("config.properties");
这种相对的方式,是相对于当前工作目录来说的。所以这样不准确。
一般使用绝对路径,但是绝对路径不是硬编码出来的,而是用某种方式get动态得到的。一般在一个配置文件中去配置这个路径。
在java web中有一个方法用来得到我这个项目在硬盘上的具体目录(getRealPath);
用这个工程目录+文件在你工程下的目录,就得到了绝对位置。
另外一种得到资源文件的方式,很重要,在大多数框架中都是使用这种方式。
但是并不能替代上边的方式
每一个.class字节码文件使用的时候一定先将类加载到内存中然后再去创建一个
相应的字节码文件对象。加载它的是谁呢?叫类加载器
既然它可以加载.class文件,当然也可以加载其他文件。
Test1.class.getClassLoader().getResourceAsStream();
用getClassLoader()得到这个类字节码加载器,然后使用getResourceAsStream(String name)
这里的name路径名为其所带包名。
返回读取指定资源的输入流。
这个方法在指定的classpath目录下逐一的查找你要加载的文件,因为我们的类加载器在加载.c lass文件的时候要在classpath的位置找对应的.class文件,同样也会在这个位置找其他文件.
将对应的config文件放到于这个类同一个包下,这样会将非.java的文件放在classpath指定的目录下去。
设置classpath的作用是为了运行时候加载相应的.class时候能找到.class文件
对于上边这种读取数据的方式,它仅仅是只读的。
以后所学到的框架的配置文件都放在classpath指定的目录下。内部就是这种方式。
使用类的字节码文件对象,可以直接去加载一个特定的资源。
InputStream is =Test1.class.getResourceAsStream("config.properties");
这种方式只需写上配置文件的名字,而不用写目录名。也就是系统会默认在这个类所在的包目录下,去寻找这个文件。
如果这个文件和你的类不再同一个包下。那么
需要这样写(绝对路径)
Test1.class.getResourceAsStream("/你定义的包名/config.properties");