黑马程序员_java高新技术(1)

 ------- 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");

 


你可能感兴趣的:(黑马程序员_java高新技术(1))