码迷,mamicode.com
首页 > 编程语言 > 详细

Java基础:基础加强

时间:2016-10-11 12:01:49      阅读:308      评论:0      收藏:0      [点我收藏+]

标签:


1.泛型

1.1 概述
(1).泛型是JDK1.5版本以后出现的新特性,用于解决安全文帝的一个类型安全机制;
(2).泛型在集合类中的应用:JDK1.5以后的集合类希望在定义集合时,明确表明要向集合中存储的是哪一类型的数据,无法处理指定类型以外的数据;
(3).泛型是提供给Javac编译器使用的,可以限定集合中的输入类型,在编译完成后的字节码文件中会去掉类型信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型一致;
(4).由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据;
使用反射的方式绕过编译器,添加不同类型的数据
//绕过编译器向泛型集合中添加不同元素;
package com.zr.day28;
import java.lang.reflect.*;
import java.util.*;
class GenericDemo
{
	public static void main(String[] args) throws Exception
	{
		ArrayList<String> al = new ArrayList<String>();
		al.add("generic");
		//编译失败,不能添加Integer类型;
		//al.add(Integer.valueOf(5));
		System.out.println(al);

		
		//通过反射来添加不同类型的数据;
		Method addMethod = al.getClass().getMethod("add", Object.class);
		addMethod.invoke(al, Integer.valueOf(8));
		
		System.out.println(al);
	}
}


(5).当仍然按照JDK1.5之前的方式处理,即不定义泛型类型,编译器会出现Uncheck警告;
1.2 泛型的好处
(1).将运行时期出现的问题ClassCastException,转移到了编译时期,方便程序员解决问题,让运行时期问题减少安全;
(2).避免了强制转换的麻烦;比如在反射的应用中;
1.3 泛型中的术语
以ArrayList<E>和ArrayList<Integer>为例:
整个ArrayList<E>称为泛型类型;
ArrayList<E>中的E称为类型变量或类型参数;
整个ArrayList<Integer>称为参数化的类型;
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数;
ArrayList<Integer>中的<>称作typeof;
ArrayList称为原始类型;
参数化类型和原始类型的兼容性:
(1).参数化类型可以引用一个原始类型的对象,编译报告警告
eg:Collection<String> c = new Vector();
(2).原始类型可以引用一个参数化类型的对象,编译报告警告
eg:Collection c = new Vector<String>();
参数化类型不考虑类型参数的继承关系;
错误示例:
Vector<String> v = new Vector<Object>();
Vector<Object> v = new Vector<String>();
编译器不允许创建泛型变量的数组;
1.4 泛型的通配符
通配符:<?>,又称占位符;
当传入的类型不确定时,可以使用通配符,好处是可以不用明确具体传入的类型,这样在使用泛型类或者泛型方法时,提高了扩展性;
使用<?>通配符可以引用其他各种参数化的类型,<?>通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法;
泛型限定:
对于一个范围的一类事物,可以通过泛型限定的方式定义;
(1).? extends E:可以接收E类型或者是E类型的子类类型,称为上限限定;
(2).? super E:可以接收E类型或者E类型的父类类型,称为下限限定;
示例:定义一个上限限定的方法:
package com.zr.day28;
import java.lang.reflect.*;
import java.util.*;
class Person
{
	protected String name;
	Person(String name)
	{
		this.name = name;
	}
	public String toString()
	{
		return "person:"+this.name;
	}
}
class Student extends Person
{
	Student(String name)
	{
		super(name);
	}
	public String toString()
	{
		return "student:"+this.name;
	}
}
class GenericDemo
{
	public static void main(String[] args)
	{
		ArrayList<Person> al_p = new ArrayList<Person>();
		al_p.add(new Person("zxx"));
		al_p.add(new Person("flx"));
		al_p.add(new Person("lhm"));
		ArrayList<Student> al_s = new ArrayList<Student>();
		al_s.add(new Student("zr"));
		al_s.add(new Student("fyk"));
		al_s.add(new Student("ggx"));
		
		outArraylist(al_p);
		System.out.println("***********");
		outArraylist(al_s);
	}
	//定义上限限定方法;
	public static void outArraylist(ArrayList<? extends Person> al)
	{
		Iterator<? extends Person> it = al.iterator();
		while(it.hasNext())
		{
			System.out.println(it.next());
		}
	}
}


1.5 泛型类
当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来实现扩展;
如果类的实例对象中的多出都要用到同一泛型参数,即这些地方引用的泛型类型要保持同一个世纪类型时,可以采用泛型类型的方式进行定义,也就是类级别的泛型;
泛型类是根据引用该类名时指定的类型信息来参数化类型变量的;
泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所要操作的类型就已经固定了;
注意:
(1).在对泛型类型进行参数化时,类型参数的实力必须是引用类型,不能是基本数据类型;
(2).当一个变量被声明为泛型时,只能被实例变量和方法调用,而不能被静态变量和静态方法调用:因为静态成员是被所有参数化的类所共享,无哦一静态成员不应该有类级别的类型参数;
1.6 泛型方法
为了让不同的方法可以操作不同类型的数据,而且类型不确定,可以将泛型定义在方法上;
泛型方法的使用:
(1).用于放置泛型类型的尖括号定义在方法的修饰符之后和方法的返回类型之前;
(2).只有引用类型才能作为泛型方法的实际参数,eg:swap(new int[5],3,4)编译失败;
(3).可以使用&符号来定义多个泛型边界;
(4).普通方法,构造方法,静态方法中都可以使用泛型,编译器不允许创建类型变量的数组;
(5).在泛型中可以同时有多个类型参数,参数之间用逗号隔开;
(6).在类和方法上都定义了泛型的时候,方法所操作的类型不受类上定义的泛型局限;
(7).静态方法泛型:静态方法不能访问类上定义的泛型,因为只有在类的对象建立时,泛型才会确定;如果静态方法操作的类型不确定的话,可以经泛型定义在方法上;
1.7 类型的推断
编译器判断泛型方法的实际类型参数的过程称为类型推断;
(1).当某个类型变量只在真个参数列表中的所有参数和返回值中的一处被应用,那么根据调用方法时该处的实际应用类型来确定;
(2).当某个类型变量在整个参数列表中的所有参数和返回值中的多出被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型;
(3).当某个类型变量在整个参数列表中的所有参数和返回值中的多出被应用了,如果调用方法时这多处的实际应用类型对应不同类型,且没有使用返回值,这时候取多个参数中的最大交集类型;
(4).当某个类型变量在整个参数列表中的所有参数和返回值中的多出被应用了,如果调用方法时这多处的实际应用类型对应不同类型,并且使用返回值,这时优先考虑返回值的类型;
(5).参数类型的类型推断具有传递性;
1.8 通过反射获得泛型的实际类型参数
//演示泛型的反射,获取泛型的实际类型参数;
package com.zr.day28;
import java.lang.reflect.*;
import java.util.*;
class GenericDemo
{
	public static void main(String[] args) throws Exception
	{
		Method applyMethod = GenericDemo.class.getMethod("applyVector", Vector.class);
		//获取方法的参数列表带泛型;
		Type[] types = applyMethod.getGenericParameterTypes();
		System.out.println(types[0]);
		//ParameterizedType:表示参数化类型;其超类是Type类型;
		ParameterizedType pt = (ParameterizedType)types[0];
		//返回表示此类型实际类型参数的 Type对象的数组;
		System.out.println(pt.getActualTypeArguments()[0]);
		//表示声明此类型的类或接口;
		System.out.println(pt.getRawType());
	}
	
	//在编译后,字节码文件中没有泛型信息,就不能通过class文件来获取泛型;
	//当方法中定义的参数有使用泛型的时候,可以通过对方法的反射获取泛型信息;
	public static void applyVector(Vector<Date> v)
	{
		
	}
}


2.枚举

2.1 枚举概述
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错;
枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标;
使用普通类模拟枚举:
/*
 * 使用普通类模拟枚举;
 * 1.将构造方法私有化,不允许本类以外的其他地方创建对象;
 * 2.每个元素分别用一个公有的静态成员变量表示;
 * 3.可以有若干共有方法;采用抽象方法定义特性方法,让对象在创建时去具体实现,优化大量的if/else语句;
 * */
package com.zr.day25;
//模拟一个星期类,类中的元素固定
//因为包含抽象方法,所以类定义为抽象类
abstract class WeekDay
{
	//构造方法私有化
	private WeekDay(){}
	//将每个元素需要实现的特性方法定义抽象方法
	public abstract WeekDay nextDay();
	//类中的成员变量在创建时要实现nextDay()方法
	public static final WeekDay MON = new WeekDay(){
		public WeekDay nextDay()
		{
			return SUN;
		}
	};
	public static final WeekDay SUN = new WeekDay(){
		public WeekDay nextDay()
		{
			return MON;
		}
	};
	public String toString()//自定义打印输出的结果;
	{
		return this==SUN?"SUN":"MON";
	}
}


2.2 枚举的基本应用
(1).通过关键字定义枚举类,枚举类是一个特殊的类,每个元素都是该类的一个实例对象;
(2).用枚举约定值,以后用此类型定义的值只能是这个类中规定好的那些值,若不是这些值,编译器会报错;
(3).在编译时期会发现错误,表明值不符合条件,减少了运行时期的错误;
(4).枚举类是一个类,而且是一个不可被继承的final类,其中的元素都是类静态常量;
(5).在类中定义的枚举类似枚举内部类,可以被成员修饰符修饰;
(6).只要是使用到了枚举类,就会创建枚举所包含的实例对象;
(7).用枚举可以实现单例;
(8).代码格式上,枚举元素必须位于枚举体中的最开始部分,枚举元素列表后面要有分号和其他成员分隔,当没有其他成员时可以省略分号;
(9).枚举中的常用方法:
构造方法:
枚举的构造方法只是在构造枚举元素的时候被调用;
构造方法只能被private修饰,这样可以保证外部代码不会在重新创建枚举类型实例;
构造方法有多个,重载形式存在,具体的实例对象可以由相应的构造方法创建,不需要构造方法一致;
非静态方法:所有的枚举类都继承了Enum类,因此枚举对象不能在继承其他类;
String toString():返回枚举常量的名称;
String name():返回枚举常量的名称;
int ordinal():返回枚举常量的序数,在枚举声明中的位置,初始序数为0;
Class getClass():获取对应的类名;
静态方法:
values():获取所有的枚举对象元素;
valueOf(String e):转换为对应的枚举对象,将字符串转换为枚举对象;
package com.zr.day25;
class EnumDemo
{
	//定义一个表示星期信息的枚举类;
	//枚举内部类
	public enum  Week
	{
		//枚举的元素必须要放在枚举体的开头
		MON {
			@Override
			public Week nextDay() {
				return THU;
			}
		},THU {
			@Override
			public Week nextDay() {
				return WED;
			}
		},WED {
			@Override
			public Week nextDay() {
				return THR;
			}
		},THR {
			@Override
			public Week nextDay() {
				return FRI;
			}
		},FRI {
			@Override
			public Week nextDay() {
				return STA;
			}
		},STA {
			@Override
			public Week nextDay() {
				return SUN;
			}
		},SUN {
			@Override
			public Week nextDay() {
				return MON;
			}
		};
		//如果枚举中还有成员:变量,方法,构造方法,用分号";"将枚举元素和其他成员分隔;
		//枚举中定义了抽象方法,枚举元素在实例化的时候就需要实现抽象方法;
		//同时构造方法可以有多个,根据需要让具体的元素实例调用相应的构造方法;
		public abstract Week nextDay();
	}
	
	public static void main(String[] args)
	{
		//可以声明操作枚举的语句;
	}
}



3.反射

3.1 反射技术:
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态的获取信息以及动态调用对象的成员方式称为Java语言的反射机制;
反射就是对Java类进行剖析,将Java类中的各种成分映射成相应的Java类;
反射大大的提高了程序的扩展性;
3.2 反射的使用:
一个已经可以使用的程序,不建议在对代码进行修改,当后期需要功能完善的时候该如何处理:
常用的做法是,提供一个配置文件,来供以后实现此程序的类来扩展个功能,让后期出现的子类直接将类名配置到配置文件中,然后处理:
(1).加载这个类;
(2).创建该类的对象;
(3).使用该类中的内容;
应用程序使用的类不确定时,可以通过提供配置文件,让使用者将具体的子类存储到配置文件中,然后该程序通过反射对指定的类进行内容的获取;
3.3 反射的基石:Class类
(1).Class概述:
所有的类文件都有共同的属性,可以进行向上抽取,把这些共性内容封装成一个类,这个类就叫Class:专门用于描述字节码文件的对象;
Class类描述的信息有:类的名字,类的访问属性,类所属的包,字段名称列表,方法名称列表,构造器列表;
字节码文件(二进制):当源程序中使用到某个类时,首先要将这个类的源文件编译成二进制代码,然后将这些二进制代码文件加载进内存;
(2).Class和class的区别:
Class指的是Java程序中的各个Java类是属于同一类事物,每一个Java类就是Class的一个实例;
class是类的实例对象;
(3).获取Class对象的方式:
方式一:
	public static void main(String[] args)
	{
		//获取Class对象的方式
		Person p = new Person("zr",23);
		//方式一:通过实例对象的getClass()方法获取;
		//在获取时,需要具体的类和该类的对象,毫无扩展性而言;
		Class clazz = p.getClass();
	}


方式二:
	public static void main(String[] args)
	{
		//获取Class对象的方式
		//方式二:类类型都具备一个静态的属性class,这个属性可以直接获取该类型对应的Class对象;
		//相较方式一,不需要创建对象,不用调用getClass()方法
		//但是还是要使用具体的类,以及该类中的一个具体的class属性;
		Class clazz = Person.class;
	}


方式三:
	public static void main(String[] args) throws ClassNotFoundException
	{
		//获取Class对象的方式
		//方式三:只需要知道类的名称即可,不需要使用该类,也不需要调用具体的方法或属性;
		//即使是该类不存在也没有关系,依旧可以根据名称获取,所以使用这种方式会出现异常ClassNotFoundException
		//另外使用这种方式最好是将类名的全称包括包名填写完整;
		//这种方式仅需要知道类的名称,更有利于扩展;
		Class clazz = Class.forName("Person");
	}


注意:
1).九个预定义的Class:byte,short,int,long,float,double,char,boolean以及void.class;
2).Integer.TYPE是Integer类的一个常量,它代表的是此包装类型包装的基本类型字节码,即Integer.TYPE=int.class;
3).只要是在源程序中出现的哦类型都有各自的Class实例对象,数组类型的实例对象,eg:int[].class,可以使用Class.isArray()方法来判断是否为数组类型;
(4).Class类中的常用方法
static class forName(String className):返回与带有给定字符串名的类或接口相关联的Class对象;
Constructor getConstructor(Class... parameterType):
Constructor getDeclaredConstructor(Class... parameterType):
Constructor[] getConstructors():
Constructor[] getDeclaredConstructors():
Method getMethod(String methodName, Class... parameterType)
Method getDeclaredMethod(String methodName, Class... parameterType)
Method[] getMethods()
Method[] getDeclaredMethods()
Field getField(String fieldName)
Field getDeclaredField(String fieldName)
Field[] getFields()
Field[] getDeclaredFields()
String getName():以字符串形式返回此Class对象所表示的实体名称;
package getPackage()
Class getSuperClass():返回表示此Class所表示的实体的超类的Class;
boolean isAnnotation():判断是否为注解类型;
boolean isArray():判断是否是数组类;
boolean isPrimitive():判断是否为一个基本数据类型;
Object newInstance():创建此Class对象所表示的类的一个实例,无参构造方法;
(5).通过Class对象创建具体类的实例;
之前的通用做法:
1).查找并加载class文件进内存,并将该文件封装成Class对象;
2).再依据Class对象创建该类具体的实例;
3).调用构造方法对对象进行初始化;
现在使用Class对象实例来实现:
1).查找并加载class文件进内存,并将该文件封装成Class对象;
2).获取该Class对象实例后,调用newInstance()方法,使用空参的构造方法进行初始化;
package com.zr.day28;
class Person
{
	private String name;
	private int age;
	Person()
	{
		System.out.println("person() run!");
	}
	Person(String name, int age)
	{
		System.out.println("person(string,int) run!");
		this.name = name;
		this.age = age;
	}
	public String toString()
	{
		return "name = " + this.name +"***age = "+this.age;
	}
}
class ClassDemo
{
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException
	{
		//加载class文件,获取Class实例对象;
		Class clazz = Class.forName("com.zr.day28.Person");
		//调用该实例对象的newInstance()方法,使用无参的构造方法;
		//需要进行强转,而且会抛出异常;
		Person p = (Person)clazz.newInstance();
		System.out.println(p);
	}
}


3.4 构造方法的反射:Constructor
(1).Constructor概述
如果指定的类中没有空参的构造方法,或者要创建的类对象需要通过指定的构造方法进行初始化,这时直接使用Class实例对象的newInstance()方法就不适用;
既然要通过指定的构造方法进行对象的初始化,就必须先获得这个类的构造方法;
class类中的构造方法用Constructor类来说明;
(2).获取构造方法的方式:
Constructor[] getConstructors():获取类中的所有构造方法;公有的;
Constructor getConstructor(Class...  parameterType):获取某个具体的构造方法
(3).使用指定的构造方法创建实例对象
object newInstance(Object...  initargs)
使用反射方式创建实例时,newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致;
newInstance()方法没调用一次就构造一个对象;
使用Constructor类来创建实例的好处是可以指定构造函数,而Class类只能使用无参的构造函数创建类的实例对象;
示例:
package com.zr.day28;

import java.lang.reflect.*;

class Person
{
	private String name;
	private int age;
	public Person()
	{
		System.out.println("person() run!");
	}
	public Person(String name, int age)
	{
		System.out.println("person(string,int) run!");
		this.name = name;
		this.age = age;
	}
	public String toString()
	{
		return "name = " + this.name +"***age = "+this.age;
	}
}
class ClassDemo
{
	public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
	{
		//对构造方法的反射;
		Constructor personCon = Class.forName("com.zr.day28.Person").getConstructor(String.class,Integer.TYPE);
		//调用指定的构造方法,强制转换;
		Person p = (Person)personCon.newInstance("zr",23);
		System.out.println(p);
	}
}


3.5 方法的反射:Method
(1).Method概述
Method类代表某个类中的成员方法,调用某个对象上的方法,要先得到方法,在针对某个对象调用;
使用反射的方式调用某个方法时,要指定具体的方法同时还要关联具体执行该方法的对象;
(2).获取类中的方法
Method[] getMethods():获取公共的方法其中包括父类的方法;
Method[] getDeclaredMethod();获取本类中的方法,包括私有的;
Method getMethod(String methodName, Class... parameterType):当方法的参数为空时,可以定义为null;
(3).执行某个具体的方法
Object  invoke(Object obj, Object... args)
其中obj就是调用这个方法的具体对象,如果第一个参数为null,说明Method对象对应的是一个静态方法;
示例:
//对String类中的方法进行反射,调用其中的方法;
package com.zr.day28;
import java.lang.reflect.*;
class StringReflectDemo
{
	public static void main(String[] args) throws Exception
	{
		//利用反射获取指定字符串指定位置的字符;
		String str = "badRomance";
		//根据方法名和参数获取具体的指定方法;
		Method charAt = Class.forName("java.lang.String").getMethod("charAt", Integer.TYPE);
		//在调用时要指定调用该方法的具体对象;
		char c = (char)charAt.invoke(str,3);
		System.out.println(c);
	}
}


(4).使用反射的方式调用某个类的main()方法;
使用反射:在设计源程序时,并不知道使用者传入的类型是什么,但是虽然传入的类名不知道,但是知道这个类中有main方法,所以可以通过反射的方式,通过使用者传入类名,内部通过传入的类名获取main方法,然后执行相应的内容;
在调用main方法时,如何为invoke传递参数,main方法的参数类型应该是一个字符串类型的数组;
但是按照JDK1.4的语法会将数组中的每个字符串作为参数传递给invoke方法,按照JDK1.5的语法会将这整个数组作为参数传递,我们需要的是这种处理方法,可是虚拟机为了兼容之前的语法规则,不幸的是使用JDK1.4的解析方式,而使用这种方式会出现参数类型不正确的情况;
解决方式:编译器不会数组中的元素解析为实际参数;
1).invoke(null, new Object[]{new String[]{"xxxx"}})
2).invoke(null, (Object) new String[]{"xxx"})
示例:
package com.zr.day28;
import java.lang.reflect.*;
class MainReflectDemo
{
	public static void main(String[] args)throws Exception 
	{
		//使用普通的方式调用
		Test.main(new String[]{"123","456","789"});
		
		//使用反射的方式,将需要执行的类通过字符串参数指定;
		String className = args[0];
		Class clazz = Class.forName(className);
		
		//获取指定的main()方法;
		Method mainMethod = clazz.getMethod("main", String[].class);
		//方式一:将数组打包,编译器拆包后就是一个String类型的数组;
		mainMethod.invoke(null,new Object[]{new String[]{"789","456","123"}});
		//方式二:强制转换为Object,不用拆包
		mainMethod.invoke(null,(Object)new String[]{"789a","456b","123c"});
	}
}

//定义一个测试类,使用反射的方式调用这个类的main方法
class Test
{
	public static void main(String[] args)
	{
		for(String str : args)
		{
			System.out.println(str);
		}
	}
}


3.6 变量的反射:Field
Field类用来代表某个类中的成员变量;
(1).获取Field对象:
Field getField(String s):获取公有的和父类中公有的;
Field getDeclared(String s):获取该类中的任意成员变量,包括私有的;
(2).Field类中常用的方法;
Object get(Object obj):获取指定对象上此Field表示的字段的值;
String  getName():获取此Field对象表示的字段的名称;
void set(Object obj, Object value):将指定对象变量上此Field对象所表示的字段设置为指定的value值;
void setAccessible(boolean flag):如果是私有字段,要先将该私有字段进行取消权限检查的能力,也称之为暴力反射;
示例:
package com.zr.day28;
import java.lang.reflect.*;
//定义一个用来测试字段反射的类
class ReflectPoint
{
	private int x;
	private int y;
	ReflectPoint(int x, int y)
	{
		this.x = x;
		this.y = y;
	}
	public String toString(){
		return "x="+this.x+"**y="+this.y;
	}
}
class FieldReflectDemo
{
	public static void main(String[] args)throws Exception
	{
		//新建对象
		ReflectPoint rp = new ReflectPoint(4,5);
		//获取Class对象实例
		Class clazz = rp.getClass();
		//获取该测试类中的所有字段,包括私有
		Field[] fields = clazz.getDeclaredFields();
		//遍历所有字段
		for(Field field : fields)
		{
			System.out.println(field.getName());
		}
		//获取某个对象的指定字段值
		//私有获取
		Field xField = clazz.getDeclaredField("x");
		//设置访问权限
		xField.setAccessible(true);
		//获取字段值
		System.out.println(xField.get(rp));
		
		//设置某个对象的字段值
		Field yField = clazz.getDeclaredField("y");
		yField.setAccessible(true);
		//设置字段值
		yField.set(rp, 99);
		System.out.println(rp);
	}
}


3.7 数组的反射
(1).具有相同维数的元素类型的数组属于同一类型,即具有相同的Class实例对象;
(2).Object[]和String[]没有继承关系,Object和String具有继承关系,所以new Object[]{"a","b"}不能强制转换为String[]{"a","b"};
(3).无法得到某个数组的具体类型,只能得到其中某个元素的类型;
(4).基本数据类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
(5).非基本数据类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用;
(6).Array工具类用于完成对数组的反射操作;Array中提供了大量的用来操作数组的方法;
示例:
//使用Arrays工具类实现数组的反射,具体实现打印任意数据;
//如果是数组就打印数组中的元素,否则打印数据本身;

package com.zr.day28;

import java.lang.reflect.Array;
import java.util.Date;

class ArrayReflectDemo
{
	public static void main(String[] args)
	{
		//打印数组
		printObj(new String[]{"dfds","sf","12515","3135","sdrer45d54f"});
		//打印对象
		printObj(new Date());
	}
	public static void printObj(Object obj)
	{
		//首先获取Class实例对象
		Class clazz = obj.getClass();
		//判断是否是数组类型
		if(clazz.isArray())
		{
			//使用的是java.lang.reflect.Array中的方法来实现数组的反射;
			//打印数组中的元素
			int len = Array.getLength(obj);
			for(int i=0; i<len; i++)
			{
				System.out.println(Array.get(obj, i));
			}
		}
		//打印对象
		else
		{
			System.out.println(obj);
		}
	}
}


3.8 内存泄漏
当一个对象存储进HashSet集合中后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了;
在这种情况下,调用Contains方法或者remove方法来查找或者删除这个对象的引用,就会找不到这个对象,从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄漏;
内存泄漏:某些对象不再使用,占用内存空间,并未释放;

4.内省

内省:IntroSpector,主要针对JavaBean进行操作;
4.1 JavaBean概述:
(1).Javabean 是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法符合某种特殊的命名规则;
(2).只要某个类中的方法有时get或者set开头,就可以当作JavaBean来处理;
(3).一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到Java类内部的成员变量;
4.2 JavaBean的好处:
一个符合JavaBean特点的类可以当作普通类来使用,也可以当作JavaBean来使用,这样会有一些额外的好处;
(1).在JavaEE开发中,经常要使用javaBean,很多环境就要求按JavaBean的方式进行操作;
(2).JDK中提供了对javaBean进行操作的API,这套API称为内省,操作JavaBean比使用普通的方式更方便;
java.beans下定义用于内省的类,其中主要的有PropertyDescriptor类,IntroSpector类和BeanInfo接口;
4.3 简单的内省操作:
根据提供的属性名称,使用PropertyDescriptor来操作:
(1),构造方法:
PropertyDescriptor(String propertyName, class beanClass):参数是属性名称,和满足JavaBean的Class实例;
(2).常用方法:
Method getReadMethod():获得应用于读取属性值的方法;相当于get操作;
Method getWriteMethod():获得应用于写入属性值的方法;相当于set操作;
(3).示例:
//简单的内省操作
package com.zr.day28;
import java.beans.*;
import java.lang.reflect.*;
class BeanTest
{
	private int x;
	private int y;
	BeanTest(int x, int y)
	{
		this.x = x;
		this.y = y;
	}
	public String toString()
	{
		return "x = "+x+"*** y = "+y;
	}
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
}
class JavaBeanDemo
{
	public static void main(String[] args)throws Exception
	{
		String propertyName = "x";
		BeanTest bt = new BeanTest(8,9);
		//使用Eclipse的重构功能,抽取方法,将功能封装成方法;
		//使用内省的方式
		//PropertyDescriptor类来操作;
		PropertyDescriptor pd = new PropertyDescriptor(propertyName,bt.getClass());
		//首先是调用get方法,即读方法;
		Method getXMethod = pd.getReadMethod();
		System.out.println(getXMethod.invoke(bt));
		
		//接下来是调用set方法,即写方法;
		int value = 123;
		Method setXMethod = pd.getWriteMethod();
		setXMethod.invoke(bt,value);
		System.out.println(bt);
	}
}


4.4 复杂的内省操作:
(1).java.beans.IntroSpector:提供的 static BeanInfo getBeanInfo(Class beanClass),对JavaBean进行内省;
(2).java.beans.BeanInfo:提供的PropertyDescriptor[] getPropertyDescriptors(),获取属性;
(3).示例:
//复杂的内省操作
package com.zr.day28;
import java.beans.*;
import java.lang.reflect.*;
class BeanTest
{
	private int x;
	private int y;
	BeanTest(int x, int y)
	{
		this.x = x;
		this.y = y;
	}
	public String toString()
	{
		return "x = "+x+"*** y = "+y;
	}
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
}
class JavaBeanDemo
{
	public static void main(String[] args) throws Exception 
	{
		String propertyName = "x";
		BeanTest bt = new BeanTest(8,9);
		//复杂的内省操作;
		//调用IntroSpector类中的静态方法,返回BeanInfo信息;
		BeanInfo bi = Introspector.getBeanInfo(bt.getClass());
		//获取属性描述
		PropertyDescriptor[] pds = bi.getPropertyDescriptors();
		//遍历属性描述信息
		for(PropertyDescriptor pd : pds )
		{
			//判断属性名陈是否是指定的属性
			if(pd.getName().equals(propertyName))
			{
				//获取需要的方法
				Method getXMethod = pd.getReadMethod();
				System.out.println(getXMethod.invoke(bt));
				break;
			}
		}
	}
}


4.5 BeanUtils工具包
(1).该工具包中的set和get方法中,传入的是字符串,返回的也是字符串,因为在浏览器中,用户输入到文本框的都是以字符串的形式发送到服务器上的,所以操作的都是字符串,也就是说这个工具包的内部有自动将其他类型数据转换成字符串的操作;
(2).支持属性的级联操作;

5.注解

5.1 注解概述
注解Annotation,相当于一种标记,在程序中加入注解就等于为程序打上某种标记;
以后Java编译器,开发工具,和其他程序就可以通过反射获取该类的各个成分上有无标记,或者是有什么标记,然后做出相应动作;
注解可以加在包,类,字段,方法,方法的参数以及局部变量上;
在java.lang包中提供了最基本的注解;
注解的使用格式:@注解名
(1).如果只有一个value名称的属性或其他属性缺省,则@注解名(“属性值”);
(2).如果有多个或不缺省的或者是需要重新赋值的属性,则使用@注解名(属性名=“属性值”,...);
Class类中和注解有关的方法:
Annotation[] getAnnotations():返回此元素上存在的所有注释;
Object getAnnotation(class annotationClass):如果存在指定的注解,则返回这些注解,否则返回null;
boolean isAnnotation():如果此Class对象表示一个注释类型则返回true;
boolean isAnnotationPresent(Class annotationClass):判断该Class对象上是否有指定类型的注解;
5.2 基本注解
(1).public @interface SuppressWarnings :告知编译器或开发工具等不再提示指定的警告;
@Retention(value = SOURCE)
(2).public @interface Deprecated :告知调用者,该方法或者字段已经过时,不推荐使用;
@Retention(value = RUNTIME)
(3).public @interface Override :表示接下来的方式是覆盖的父类中的方法,如果不存在覆盖关系,就会报错;
@Target(value = METHOD)
@Retention(value = SOURCE)
5.3 注解的应用
注解就相当于一个源程序中要使用的一个类,要在源程序中应用某个注解,就必须先定义好这个注解类;
注解的应用结构图:
技术分享
自定义注解:
(1).格式:@interface 注解名称{ statement }
(2).元注解:注解的注解
两个常用的元注解:
注解一:Retention
用于说明注解保留在那个阶段,定义了注解的生命周期;
一个注解的生命周期:Java源程序--(javac)-->class文件--(类加载器)-->内存中的字节码;
class文件中不是字节码,只有class文件中的内容加载进内存,用类加载器加载处理后(进行完整的检查处理后),最后得到的二进制内容才是字节码;
分别对应Retention这个枚举类的值:
1).RetentionPolicy.SOURCE:Java源文件时期,如@Override和@SuppressWarnings
2).RetentionPolicy.CLASS:class文件时期,默认阶段
3).RetentionPolicy.RUNTIME:运行时期,如@Deprected
注解二:Target
用于说明注解类的使用范围,默认值是任何地方;
Target的取值可以是枚举类ElementType中的任意一个:
PACKAGE(包声明)
FIELD(字段声明)
ANNOTATION_TYPE(注释类型声明)
CONSTRUCTOR(构造器声明)
METHOD(方法声明)
PARAMETER(参数声明)
TYPE:class,enum,interface,@interface
LOCAL_VARIABLE(局部变量声明)
(3).自定义注解示例
第一:定义注解类,
@interface A{  }
第二:定义应用了”注释类“的类,
@A
class B{  }
第三:对应用来注解类的类进行反射操作的类,
class C 
{
B.class.isAnnotationPresent(A.class);//判断是否该类应用了指定的注解类;
A a = B.class.getAnnotation(A.class);//获取这个注释类的对象;
}
5.4 为注解添加基本属性
(1).定义格式,相当于接口中的方法;
eg:String color();
eg:String value() default "zr";//定义缺省值;
(2).基本属性的应用:直接在注解的括号中添加自身的属性;
eg:@MyAnnotation(color="red")
说明:
1).如果注解中有一个名称为value的属性,其他属性值都采用缺省方式定义,或者只有value属性,那么可以省略value=部分;
2).可以为属性值指定缺省值,在应用时可以重新设置属性值;
3).用反射的方式获得注解对应的实例对象后,可以通过该对象调用属性对应的方法来获取属性值;
5.5 为注解添加高级属性
可以对注解增加的高级属性有:
(1).八种基本数据类型;
(2).String类型;
(3).Class类型;
(4).枚举类型;
(5).注解类型;
(6).前五种类型的数组;
注解示例:
package com.zr.day28;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Retention(RetentionPolicy.RUNTIME)//元注释  
@Target({ElementType.METHOD,ElementType.TYPE})//元注解,指定使用范围  
//注解类  
public @interface MyAnnotation {  
    String color() default "red" ;  
    String value();  
    //数组  
    int[] arr() default {1,2,3};  
    //枚举  
    EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.GREEN;  
    //注解类  
    MetaAnnotation annotation() default @MetaAnnotation("heima");  
    //Class类  
    Class clazz() default System.class;  
}  
  
import java.lang.reflect.Method;  
  
//注解类的应用,给属性赋值或者重新赋值  
@MyAnnotation(lamp=EnumTest.TrafficLamp.YELLOW,value="heima",  
            clazz=AnnotationDemo.class,annotation=@MetaAnnotation("itheima"))  
//应用类  
public class AnnotationDemo {  
    @SuppressWarnings("deprecation")//此注解用于抑制过时信息的提示  
    @MyAnnotation("Method")//自定义注解应用在方法上  
    public static void main(String[] args) throws NoSuchMethodException, SecurityException {  
          
        System.runFinalizersOnExit(true); //这是一个过时了的方法 ,如果没有注解就会有警告提示  
        //判断此类是否有MyAnnotation注解  
        if (AnnotationDemo.class.isAnnotationPresent(MyAnnotation.class)) {  
            //如果有,则获取该注解  
            MyAnnotation annotation =AnnotationDemo.class.getAnnotation(MyAnnotation.class);  
            System.out.println(annotation);//@cn.itheima.Demo.MyAnnotation()  
            System.out.println(annotation.color());//red  
            System.out.println(annotation.value());//heima  
            System.out.println(annotation.arr().length);//3  
            System.out.println(annotation.lamp());//YEllOW  
            System.out.println(annotation.annotation().value());//itheima  
            System.out.println(annotation.clazz());//class cn.itheima.demo.AnnotationDemo  
        }  
          
        //获取方法上的注解  
        Method mainMethod=AnnotationDemo.class.getMethod("main",String[].class);  
        MyAnnotation annotationMethod=(MyAnnotation) mainMethod.getAnnotation(MetaAnnotation.class);  
        SuppressWarnings sw=mainMethod.getAnnotation(SuppressWarnings.class);  
        System.out.println(sw);//null  
        System.out.println(annotationMethod);//null   
    }  
} 



6.类加载器

6.1 类加载器概述
(1).类加载器就是加载类的工具;
在Java程序中使用到了一个类,出现了这个类的名字,Java虚拟机要将这个类的class文件加载到内存生成字节码文件,这一操作主要有类加载器来完成;
(2).类加载器也是Java类,因为其他的Java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是Java类,这个类加载器就是BootStrap;
(3).java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载器;
(4).Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类负责加载特定位置的类;
具体的类加载器之间的关系图:
技术分享

6.2 java.lang.ClassLoader
(1).构造方法
protected ClassLoader():会通过getSystemClassLoader()返回ClassLoader作为该加载器的默认父类加载器;
protected ClassLoader(ClassLoader parent):自定义指定父类加载器;
(2).常用方法
protected Class defineClass(String name, byte[] b, int off , int len):将一个byte数组转换为Class类的实例;
Class findClass(Stirng name):查找类
Class loadClass(String name):加载类
(3).类加载器中的模块方法设计模式
不要覆盖loadClass()方法,覆盖findClass()方法:
每一个继承ClassLoader的类中的loadClass方法定义的是查找流程,会先将查找任务提交父类加载器,父类加载器会向上继续提交给它的父类加载器,当向上至顶后,每一层的类加载器会调用自己类中定义的findClass方法加载指定的类,当父类的findClass查找失败,就将查找任务返回由子类加载器去查找,当回到自己这个类加载器后仍然没有查找加载就返回查找失败;
6.3 类加载器的委托机制
每个ClassLoader本身只能分别加载特定位置和目录中的类,但他们可以委托其他的类加载器去加载类,这就是类加载器的委托机制;
加载类的方式:到底用哪个类加载器加载;
(1).首先,当前线程的类加载器去加载线程中的第一个类;
(2).若A引用了B,Java虚拟机将使用加载A类的加载器去加载B;
(3).还可以直接调用ClassLoader的loadClass()方法,来指定某个类加载器去加载某个类;
每个类加载器在加载类时,会先委托给上级类加载器;
类加载器从下到上一级级的委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子类加载器去进行加载,当回退到最初的发起者类加载器时,如果它自己也不能完成类的加载,那么就会抛出ClassNotFoundException异常,不会在让自己的子类加载器去加载;
委托机制的优点:可以进行集中管理,不会产生多字节码重复的现象;

7.代理

7.1 程序中的代理
(1).要为即存在的多个具有相同接口的目标类的各个方法增加一些系统功能,比如异常处理,日志,计算程序运行时间,事务管理等的;
(2).编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码;
(3).如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类,这样以后很容易切换;
(4).代理架构图:
技术分享
7.2 AOP:面向方面的编程
(1).系统存在交叉业务,一个交叉业务就是要切入到系统的一个方面;
(2). 交叉业务的编程问题即为面向方面编程,AOP的目标就是使交叉业务模块化,可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的;
(3).使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术;
7.3 动态代理技术
(1).要为系统中的各种接口的类增加代理功能,需要太多的代理类,全部采用静态的代理方式,会非常繁琐;
(2).JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类;
(3).JVM生成的动态类必须实现一个或多个接口,所以JVM生成的动态代理类只能用作具有相同接口的目标类的代理;
(4).CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,可以使用CGLIB库;
(5).代理类的各个方法通常除了要调用目标的相应方法和对外返回目标返回接口外,可以在代理方法中的指定位置添加系统功能代码;
1).在调用目标方法之前;
2).在调用目标方法之后;
3).在调用目标方法前后;
4).在处理目标方法异常的Catch块中;
7.4 java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler接口;
Proxy类:
(1).构造方法:protected Proxy(InvocationHandler h):必须要传入一个调用处理器;
(2).字段:protected InvocationHandler  h;
(3).常用方法:都是静态方法;
InvocationHandler getInvocationHandler(Object proxy):返回指定代理实例的调用处理器;
Class getProxyClass(ClassLoader loader, Class... interfacers):返回代理类的java.lang.Class对象,需要指明其类加载器和实现的接口;
Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回一个指定接口的代理类实例;
InvocationHandler接口:
Object invoke(Object proxy,Method method,Object[] args):在代理实例上处理方法调用并返回结果;
7.5 创建动态代理类
(1).创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass()方法的各个参数;
(2).编码列出动态类中所有构造方法和参数;
(3).编码列出动态类中的所有方法和参数;
(4)创建动态类的实例对象;
1).利用反射获取代理类实例对象的构造方法;
2).直接使用静态方法newProxyInstance();
代码实现:
package com.zr.day28;
import java.lang.reflect.*;
import java.util.Collection;
class ProxyDemo
{
	public static void main(String[] args) throws Exception 
	{
		//创建实现了Collection接口的动态类Class对象,并获取动态类的名称;
		//调用getProxyClass获取代理类Class对象实例;
		//需要指定实现的接口,以及类加载器;
		Class proxyClass = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
		System.out.println(proxyClass.getName());
		//输出结果:com.sun.proxy.$Proxy0
		
		//获取该动态类Class对象实例中的构造器;
		//打印的形式为:$Proxy0(InvocationHandler)
		System.out.println("***constructor listing***");
		Constructor[] constructors = proxyClass.getConstructors();
		for(Constructor con : constructors)
		{
			StringBuilder sb = new StringBuilder();
			sb.append(con.getName());
			sb.append("(");
			Class[] parameters = con.getParameterTypes();
			for(Class para:parameters)
			{
				sb.append(para.getName()+",");
			}
			if(parameters!=null && parameters.length!=0)
				sb.deleteCharAt(sb.length()-1);
			sb.append(")");
			System.out.println(sb.toString());
		}
		
		//获取动态代理类中的所有方法;
		System.out.println("***Method listing***");
		Method[] methods = proxyClass.getMethods();
		for(Method method : methods)
		{
			StringBuilder sb = new StringBuilder();
			sb.append(method.getName());
			sb.append("(");
			Class[] parameters = method.getParameterTypes();
			for(Class para:parameters)
			{
				sb.append(para.getName()+",");
			}
			if(parameters!=null && parameters.length!=0)
				sb.deleteCharAt(sb.length()-1);
			sb.append(")");
			System.out.println(sb.toString());
		}
		
		//方式一:利用反射创建动态类的实例对象;
		Constructor constructor = proxyClass.getConstructor(java.lang.reflect.InvocationHandler.class);
		Collection col = (Collection)constructor.newInstance(new InvocationHandler()
		{

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// TODO Auto-generated method stub
				return null;
			}
			
		});
		
		//方式二:直接使用Proxy中的静态方法;
		Collection coll = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), 
				new Class[]{Collection.class},
				new InvocationHandler()
				{
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						// TODO Auto-generated method stub
						return null;
					}
				});
	}
}


(5).使用创建好的代理类对象去调用功能;
以add方法为例来说明:
用户程序调用add方法时,涉及三个要素:proxy对象,add方法,“abc”参数;
class $proxy0
{
add(Object obj)
{
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
调用调用代理对象从Object类继承的hashCode()方法,equals()方法,和toString()方法时,代理对象将调用请求转发为InvocationHandler对象,对于其他方法,不转发调用请求;
7.6 让动态生成的类称为目标类的代理
(1).传递目标类:
直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但是没有实际意义;
为InvocationHandler实现类注入目标类的实例对象,不能才有匿名内部类的形式;
让匿名的InvocationHandler实现类访问外部的目标类实例对象的final类型的引用变量;
(2).将创建代理的过程抽取为方法绑定目标的同时返回代理对象;
(3).将系统功能代码模块化,即将切面代码也通过参数形式提供,把要执行的代码装到一个对象的某个方法中,然后把这个对象作为参数传递,接收者调用这个对象的方法,就相当于执行外界提供的代码;

8.JDK1.5新特性

8.1 静态导入
格式:带有关键字static的import语句;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.lang.System.*;
区别于:import java.util.*;
静态导入:导入的是指定类中的所有静态成员;
import导入:导入的是指定包中的类;
静态导入是为了方便在调用静态成员时不需要频繁的在成员前面使用“类.”来调用,直接写成员名即可;
注意:当导入两个类中有同名的成员时,需要在成员前面加上具体的所属的类;
示例:
package com.zr.day25;
import java.util.*;  
import static java.util.Arrays.*;  
import static java.lang.System.*;  
  
class  StaticImport //extends Object  
{  
    public static void main(String[] args)   
    {  
		out.println("haha");//打印输出时就可以直接省略书写System.  
        int[] arr = {3,1,5};  
        //使用Arrays工具类的方法sort时就可以省略书写Array.
        sort(arr);  
        //半分查找也是一样可以省略  
        int index = binarySearch(arr,1);
		out.println("Index="+index);  
		//当没有指定继承时,所以类默认继承了Object,  
		//因为toString方法都具备,所以为了区分,必须写上具体调用者  
		out.println(Arrays.toString(arr));  
    }  
}  


8.2 增强for循环
(1).高级for循环的格式
for(数据类型   变量名 :被遍历的集合或者数组) {执行的语句;}
(2).传统的for循环和高级for循环:
高级for循环在书写上更加简洁,最为重要的是其存在局限性,必须要有遍历的目标;
传统的for循环在遍历的过程可以不用遍历目标,而且对于数组而言,可以利用角标进行更加丰富的操作;
8.3 可变参数
如果一个方法在参数列表中传入多个参数,个数不确定,那么每次都要复写该方法;可以用数组的形式作为参数,但是在传入时每次都要定义一个数组对象作为实际参数,为了解决这类问题的复杂性操作,引入了可变参数;
格式:使用“。。。”三个点来表示,这三个点位于变量类型和变量名之间,前后有无空格均可;
使用时,可变参数定义在参数列表的最后面,先匹配前面的类型;
class  ParamMethodDemo  
{  
    public static void main(String[] args)   
    {  
        show("testparameter",2,3,4,5,6);  
    }  
    //...就表示可变参数
    public static void show(String str,int... arr)  
    {  
        System.out.println(arr.length);  
    }  
} 


8.4 基本数据类型的自动装箱和拆箱
自动装箱:Integer i = 3;比较于:Integer i = new Integer(3);
自动拆箱:定义好的Integer对象i ,同整形的数值进行运算;
Integer i = i + 3;//右边先进行拆箱,然后运算,赋值给左边的时候再进行装箱;
值得注意的是:对于范围在byte范围内的整数,即-128~127,在常量池中仅保留有一份,多个对象共享使用;

Java基础:基础加强

标签:

原文地址:http://blog.csdn.net/zr523725410/article/details/39563041

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!