java基础知识一览
一、关键字:JDK1.5的新特性、Eclipse、MyEclipse、IDE、Workspace、Perspective、view、设置javac和java的版本、模块代码、快捷键、导入工程、静态导入、可变参数、增强for循环、自动装箱与拆箱
1、 Ecplise的基础设置
MyEclipse是Eclipse的插件,相当于补丁,增强了一些功能,用于开发JavaEE的程序(JavaEE,Java Platform Enterprise Edition,企业级版本),即WEB项目。现在是将2个软件和在一起安装,更加方便。Eclipse是用Java语言开发的。运行Eclipse,实际是在运行javaw.exe,即图形界面。Eclipse启动后会自动关闭DOS。可以直接通过windows任务管理器关闭javaw.exe来关闭Eclipse。
2、IDE
IDE:Integrated Development Environment,集成开发环境。使用工程化(project)方式管理一个项目的开发过程。一个独立的项目就是一个工程,项目中涉及许多java类、资源文件(配置文件)等,在一个工程中进行集中管理。工程框架内部包括,源文件、jar包(第三方类)、资源文件等。不使用工程的情况下,编程人员需要维护这些源文件、源文件与其他文件的目录关系。需要逐一编译这些源文件,并手动运行编译后的结果,非常复杂、非常麻烦。将一个程序的所有源文件用一个工程来组织,开发工具对所有的源文件进行集中管理,记住每个源文件的位置和相互关系。工程中的源文件、启动类、启动参数设置等配置信息都有记录。
3、Workspace(工作间)
Javac.exe和java.exe不是Eclipse内嵌的,是可以设置的。配置工作间:工具栏中的Window—》Prefrences(手选项)—》java,编译时使用Compiler(编译器),可以确定JDK的版本;运行时使用Installed JREs,确定jre的版本。不同人开发的不同工程运行在同一个Eclipse,可以有不同的风格,比如可以设置不同快捷键。可以使用工作间Workspace,将一些工程整合成一个整体,统一管理。一个工作间内可以包括多个工程,工作间的设置可以影响旗下的所有工程。
File—》Swtich Workspace,创建工作间在某目录下,没有该目录会自动创建。新工作间创建完成后重启,打开新工作间。也用于切换、导入工作间。
创建工程,File—》New—》Project—》JavaProject,取名、设置jre,完成创建。Perspective透视图,即处在java的界面之下。
导入工程:使用图表Import Project。
工程的内部层次:Project—》SourceFolder—》Package—》class。按照这个顺序,在相应的目录上建立源文件和类文件,会分类整齐。也可以利用工具栏上的图标创建各种文件。
包名需要加上公司域名(倒写),类名需要准确使用英语,创建类时可以直接加入main主函数。更改工程名:右键—》Refactor(重构)—》Rename。工作间下的类都会受到工作间的影响,会删除以前的配置,例如,快捷键。Refactor也可以更改类名,其他应用该类名的地方也会更改,非常高效。
4、快捷键设置
Window—》Prefrences—》General(整体配置)—》Eidtors(编辑)—》Keys(键盘),RemoveBinding,解除绑定;Apply,应用;Key Sequence,设置快捷键;Command,查看。
明确工作间和工程之间的关系。在工作间进行配置,影响旗下所有的工程。
5、 Perspective与view
透视图,若干个小窗口的集合,用于调试代码等功能。每一个小窗口称为视图view。一些视图的预定义的集合,称为透视图。切换到某一功能的透视图后,相应的视图就就会出现。例如,java perspective,写代码的透视图。
调试变量值,或查看变量值,右键—》DebugAs,进入Debug perspective,选择变量点右键,选择Watch,然后Step Into下一步,看到值;或者直接在Debug的透视图下,对该行代码,点右键再次Debug,并下一步,看结果。
调试完毕后,再回到java perspective,继续写代码。右键—》Run as,在Console运行。
Window—》Show View,显示视图,调出你需要的视图。Other里面还有更多的视图。
6、 设置工程的javac和java
对整个工程进行设置:工具栏中的Window—》Prefrences—》java,编译时使用Compiler(编译器),可以确定JDK的版本,Add安装新的JDK;运行时使用Installed JREs,确定jre的版本。需要保证jre版本的统一。
对单个工程进行配置:点右键到properties,Java Compiler,设置jre,即javac.exe版本。点右键—》Run as—》Run—》JRE,查看Runtime JRE版本,即java.exe版本。也可以通过BuildPath设置JDK,方法:右键—》Build Path—》Configure Build Path—》Libraries,添加新的JDK。
“编译—运行”原则:
1、 高版本的java能运行低版本的javac编译的程序。
2、 低版本的java不能运行高版本的javac编程才程序。
错误:bad version number in .class file,低运行版本。
所有工程都继承工作间的配置,其中单个工程也可以复写工作间的配置。这就像父类和子类的关系。这是一种思想,也可以用到其他地方。
设置模块代码
选择代码—》右键—》Surround With,里面有多个语句的模板可供选择,会将该代码放在语句中。
可以按照自己的需求设置语句,例如,try catch finally。Window—》Prefrences—》java—》Editor—》Templates(模板),通过New Template创建新模板,需要有Name、Description、Pattern,通过Insert Variable添加固定格式。
7、导入工程
File—》Swtich Workspace,选择需要的工作间导入。
将某一工程导入到工作间的方法:先将工程目录复制到工作间的目录下,然后
1、右键—》Import—》General—》Existing Projects into Workspace(工作间内已有的工程)—》选择需要的工程。
2、图标Import Project—》选择需要的工程。
如果发现新加入的工程无法运行,可能是JDK版本不对,需要重新设置。右键—》Build Path—》Configure Build Path—》Libraries,添加新的JDK。
在Java Build Path窗口下,可以添加jar包到预定义库,也可以直接添加jar包库。在JRE System Library[jre6]预定义库中添加额外的jar包:Add External Jars,添加需要的jar包。或者将需要的jar包添加入一个库中,以后再使用时,直接调用jar包库即可,方法:AddLibrary—》UserLibrary—》建立自己的jar包库,添加需要的jar包,例如处理图片的jar包。
8、静态导入
普通的import语句:导入一个类或某个包中所有类。不占用资源,只是不用再写包名而已。
静态导入:导入类中的所有静态方法。JDK1.5以后出现的新特性。格式:import static 包名.类名.*;。例如,import static java.lang.Math.*;
注意:新的特性不能在老版本的JDK上运行。
某些方法是Object和工具类共同拥有的,必须说明具体调用者才能正确使用。例如,System.out.println(Arrays.toString(arr));,不说明的话默认是Object的toString()方法。
当类名重名时,需要指定具体的包名;当方法重名时,需要指定具体的类或对象。不加static,导出的都是类;加了static后,导出的都是静态成员。
可变参数VariableParameter
JDK1.5后的新特性,通过一个可以变长度的数组来储存值并进行操作,本质上仍是数组,是数组的简写形式。例如
public static void show(int... arr)
{
for(int i : arr)
{
System.out.println(i);
}
}
不用每次都手动建立数组对象,只要将需要操作的元素作为参数传递即可,虚拟机隐式的将这些参数封装成了数组。…代表可变参数。不输入参数也可以。asList,输出的就是可变参数的集合。
方法的可变参数:可变参数一定要定义在参数列表的最后面。例如,public static void show(String str , int... arr),防止后面的整数自行组成数组。
overload:重载,函数名一样,参数列表不一样,返回值和函数内容可以一样可不一样。针对的是同一功能但不同参数的情况。JDK1.5以后,有了新特性,可变参数。
override:重写,子类覆盖父类的方法,函数名、返回值和参数列表都一样,函数内容一定不一样,只有这样才能完全覆盖父类的方法。
可变参数的特点:
1、 只能出现在参数列表的最后。
2、 …位于变量类型和变量名之间,前后有无空格都可以。
3、 调用可变参数方法时,编译器为该可变参数隐式创建一个数组,所以在方法体中需要以数组的形式运行可变参数。
JDK1.5以前,没有可变参数,使用数组代替。
增强for循环
语法:for(type 变量名 :集合变量名){….}
注意:1. 迭代变量必须在()中定义。2. 集合变量可以是数组或实现了Iterable接口的集合类。变量名可以是表达式。
type前面加修饰符,可以访问内部类。
/正斜杠爬坡,\反斜杠下坡。
寻找知识的源头,处理书中没有的知识。找到语言规范,使用Ctrl+F,寻找for循环。
基本数据类型的自动装箱与拆箱
将基本数据类型包装成引用数据类型再赋给引用变量,例如,Integer i = 4;
将引用数据类型拆箱变成基本数据类型,例如,System.out.println(i+10);
基本类型的整数,自动装箱成Integer对象,如果是1个字节,即-128到127之间,会放到缓存池中,使用时直接从缓存池中取出使用,节省了内存空间。因为数字很小且很少变化,使用很频繁,所以用一个数字对应多个对象。所以同一个数字装箱成的对象一样,==和equals都返回true。这是“享元”设计模式flyweight。
享元模式:很多很小的对象,他们有很多相同的东西,可以将他们变成一个对象,不同的东西变成外部的属性,作为函数的参数传入,称为外部状态。例如,String类、文件图标,对象只有一个,但名称不同。超过1个字节,使用频率低,一个数字对应一个对象,即使数字相同,但对象不同,所以==返回false,equals返回true。
二、关键字:枚举、反射、Class类、反射、Constructor类、Field类、Method类、数组的反射、框架、类加载器、配置文件
枚举enum 定义一个类型,让该类的变量的取值只能是若干个固定值中的一个,否则编译报错,这个过程称为枚举。枚举可以让编译器在编译时就控制源程序中的非法值,普通变量无法实现。本质是限制了对象的创建。 普通类实现枚举:本类中,私有化构造函数,并限制了对象和引用变量的创建,将引用变量变成常量(对象类型)。外部类在使用本类时,引用变量只能赋值这些常量,不能指定规定以外的值,例如一星期的七天,3色的交通灯。 普通类实现枚举的思路: 1. 私有构造方法。 2. 每个元素分别用一个共有的静态变量表示。 3. 可以有共有方法和抽象方法。例如,采用抽象方法、匿名内部类和多态定义nextDay()就将大量的if、else转成成一个个独立的子类。 通过点击左边框里的错误图标,可以直接创建新的类、接口等,非常便捷。 枚举相当于一个类,枚举里面的元素相当于常量,指向实例对象。可以调用一些方法对枚举的对象进行操作,就像普通类通过继承Object类获得了一些基本方法。例如,name()、toString()、ordinal()(自己的顺序)、getclass()。枚举也有一些静态方法,valueOf(),将字符串变成该枚举的对象;values(),将枚举里的元素装入一个数组中。 枚举的对象获得:类.元素,例如,TrafficLamp.RED。 枚举的构造方法特点: 1. 构造方法必须位于元素列表之后。成员变量和成员方法都要放在元素后面,否则报错。 2. 私有化构造方法。 3. 当枚举被调用,内部的元素(静态变量)会都被加载进内存,然后逐个调用空参数的构造函数创建相应的对象。 4. 调用带参数的构造函数创建对象:在元素后面加上括号,括号内传入构造函数的参数。例如,SUN(1)。由此可见,如果括号内没有参数,调用空参数构造函数。 访问修饰符:外部类:public和默认。内部类:public、private、protected和默认,和成员函数平级,访问修饰符也一样。 匿名内部类的另类用法:{}及其里面的内容放在引用的后面,而不是对象的后面,例子如下。再者就是,子类对象可以调用父类的带参数的构造方法,例如,new data(30){}; 当枚举只有一个成员是,可以当做一个单例的实现方式,比单例设计模式方便,只要列出一个元素即可,其他方法一样。 public enum TrafficLamp {//带有抽象的方法,但不用写出。 RED(30){//另类的匿名内部类使用方法。 public TrafficLamp nextLamp(){ return GREEN; } }, GREEN(5){ public TrafficLamp nextLamp(){ return YELLOW; } }, YELLOW(40){ public TrafficLamp nextLamp(){ return RED; } }; public abstract TrafficLamp nextLamp(); private int time; private TrafficLamp(int time){this.time = time;} } 反射(Reflect)与Class类 作用在于,在不知道类的具体内容(API或源代码)的情况下(只有.class文件)操作类的内容,也就是在类被编译后,再对类进行操作,操作的内存里的字节码。例如,给出参数,选择一个构造函数创造对象,这就需要调用反射来获得类的构造函数;获得对象中指定的变量的值;有对象和参数,调用某个方法。正因为此,需要抛出异常,因为这些成员可能没有。 JDK1.2出现,不是新特性。 Class类《——》java类 Constructor类《——》构造函数 Field类《——》成员变量 Method类《——》成员函数 Array《——》处理数组 Class类(java.lang包) java类用于描述事物的特性。java程序中的任何java类和接口也属于同一种事物,描述这类事物使用Class类。对象——》java类和接口——》Class类。枚举是类,注释是接口。 通过Class中的各种方法,可以获得java类的信息,例如,类名、包名、父类等。但是没有构造函数,Class的实例对象就是java类在内存里的字节码,具有唯一性。例如,Class ch = Data.class;,对象为Data类在内存的字节码。.class文件不是字节码,只有被类加载器加载入内存后,内存里的二进制数据才是字节码。 通过forName()获得java类的字节码,例如,Class.forName(“java.lang.String”);获得String在内存的字节码。forName()获得字节码的情况有两种: 1. 如果该类已经在虚拟机,无需加载。直接找到字节码返回即可。 2. 如果该类不在虚拟机,使用类加载器,将类加载入虚拟机,然后从虚拟机中获得字节码。 java类的对象调用getClass()方法,获得对应的java类。例如,stud.getClass();。 获得该字节码对应的Class类对象的3种方式: 1. 类名.class,例如,System.class 2. 对象.getClass(),例如,new Data.getClass()//获得该对象的类的Class类对象 3. Class.forName(“类名’),静态方法,例如,Class.forName(“java.lang.String”);反射中使用比较多,类名也可以是变量,不用提前知道java类的名字。需要处理异常,可能类没有加载,但没有使用类加载器ClassLoader 。 通过上面三种方法都可以获得字节码,一个类在内存中只有唯一的一份字节码。 8个基本类型也有对应的Class对象。void也有对应的Class对象,例如,Class ch = void.class;。所以共有9个预定义的Class对象。 isPrimitive(),是否是基本数据类型。基本类型获得字节码,例如,int.class()。注意,基本类型的字节码和包装类的字节码不一样,例如,int.class 和Integer.class对应的字节码不一样。但是,Integer.TYPE表示包装类内部基本数据类型的字节码,所以int.class 和Integer.TYPE是一致的。9个预定义类型都是如此。 基本数据类型组成的数组,也有对应的字节码,是Class的实例对象,但不是基本类型。是数组类型,可以用isArray()判断是否是数组类型。 将鼠标放在错误图标上,可以看到错误原因。 总之,只要是在源程序中出现的类型,都有各自对应的Class实例对象。例如,数组等。 反射 反射就是将java类中的各种成分都解析成对应的java类(解构主义),例如,函数用Method类描述、包用Package、成员变量用Field、构造函数用Constructor。java类,纵向形成对象,横向的各种成分形成对应的java类。 具体的一个java类中的成分,用反射类的对象表示,例如,System类中有许多函数,exits()、out.println(),用Method类下不同的对象obj1、obj2表示,一个Method对象对应一个System类的函数。 一个类中的所有成员都可以用相应的反射API类的一个实例对象表示,通过调用Class类的方法获得这些对象,并对其进行操作。 Constructor类 描述类中的构造方法。通过调用Class中的方法获得Constructor对象。与java类相关。 获得构造函数:Constructor[] getConstructors();返回的是构造方法的数组。 Constructor<T> getConstructor(Class… type),传入想要的构造函数中的参数类型的Class类对象,获得该参数对应的一个构造函数。使用了可变参数,满足不同的参数需求。使用泛型,可以指定构造函数对象构造的类型。 用getConstructor返回的构造函数对象可以创建对应类的实例对象,使用newInstance()方法,例如 //获得String类的构造函数,该StringBuffer表示选择哪个构造方法。 Constructor con = String.class.getConstructor(StringBuffer.class); //必须传入指定的参数(StringBuffer对象),而且需要转换类型。 String str =(String)con.newInstance(new StringBuffer("kkk")); 注意:newInstance()返回的是泛型,需要在Constructor处指定类型或者创建号对象后类型转换,例如,Constructor<String> con = String.class.getConstructor(StringBuffer.class); 编译时和运行时对程序的处理不同:编译只负责检查语法错误,并不执行等号右边的语句,也不明确变量的类型,所以需要类型转换或者指定泛型。树立编译和运行的两阶段思想。 需要注意,获得构造函数和用构造函数创建对象,内部使用的参数需要统一,不能上面用StringBuffer,下面用String,一种构造函数只能使用一种参数。 通过反射创建实例对象的步骤: 1、class字节码—》Constructor构造函数对象—》newInstance()创建对象。 2、class字节码—》(Class类的)newInstance()创建对象。 Class类的newInstance()方法,用于创建空参数的实例对象。底层仍然是调用Constructor类的newInstance()方法,使用缓冲机制保存实例对象,等到使用时提供出去,非常占用资源。 String str1 = String.class.newInstance();//已经知道类型,无需转换。 String str2 = (String)Class.forName("java.lang.String").newInstance();//需要转换类型。 Field类 代表java类中的成员变量。 右键—》Source—》使用构造函数目标创建构造函数。 ReflectPoint rf = new ReflectPoint(3,6); Field fy = ReflectPoint.class.getField("y");//fy是ReflectPoint类的共有的成员变量y System.out.println(fy.getInt(rf));//想要获得fy对应值,需要指定具体的ReflectPoint对象。 获得变量:Class类中的getField()只能获得共有的变量,getFields()返回变量的数组,getDeclaredField(),返回所有的变量,包括私有。 Field类中的get()方法获得变量对应的值,返回Object类型,需要转换。对于私有变量,还需要设置“暴力反射”才能获得对应值,例如,fx.setAccessible(true);。也有一些可以获得基本类型的值的方法,但是必须预先知道变量的类型才能使用,例如,getInt()。总之,值是和对象有关的。 字节码之间的比较应该使用==,能够确定是否是同一份字节码,equals会比较内容,不够严谨。 set(Object obj, Object value),将某个对象的某个成员变量的值换成新值。 getType(),获得某个变量的类型,注意返回Class对象。 getName(),获得某个变量的变量名。 变量的组成:类型、变量名和值,对应的获得方法:getType()、getName()和get()。这些方法都需要抛出异常。 Method类 代表类中成员方法。与java类相关,与对象无关。先获得方法,再用对象调用。 需要导包:import java.lang.reflect.*;。 获得java类中方法:使用Class类中的方法, 1、getMethod(String name, Class... parameterTypes),指定方法名和参数列表,获得方法。参数类型使用Class类。例如,int.class 2、getMethods(),获得方法组成的数组,Method[]。 对象调用方法:invoke(Object obj, Object... args),某个对象调用该方法,并传入参数,符合面向对象的思想。参数为可变参数,因为不同的方法对应不同的参数,可以使用基本类型。返回Object类型,需要转换。例如,Character ch = (Character)methodCharAt.invoke(s1, 2);相当于,s1.charAt(2)。如果s1是null,意味着这个Method对象对应的是静态方法。 JDK1.4时没有可变参数,传入数组,例如,Character ch = (Character)meCharAt.invoke(s1, new Object[]{2});。将数组打开,获得里面的元素作为参数,这个数组可以放入各种对象,例如,Integer、String等,使用多态。JDK1.5兼容了1.4的方法,会自动拆包数组。 专家模式:谁拥有数据,谁就可以调用方法。 用反射执行main主函数 可以直接用类调用主函数,例如,TestArguments.main(new String[]{});。写程序时,并不知道需要调用哪个类的main函数,等程序运行后在告诉调用哪个类的main,相当于主函数传入参数。先调用类,即使这个类没有被写好,只要运行时写好就可以。 需要给本类传入参数,右键—》Run As—》Arguments—》Program arguments,将外面类放入,即主函数传入参数。 String classname = args[0]; Method mainMethod = Class.forName(classname).getMethod("main", String[].class); mainMethod.invoke(null,(Object)new String[]{"kk","qq"});// JDK1.5为了兼容JDK1.4,如果传入一个数组,会默认为Object数组,自动拆包,将数组中的元素作为参数。将上例的数组拆包成2个字符串对象。为了防止这种情况,处理方法有二种: 1、将数组变成一个Object数组对象,例如,new Object[]{(new String[]{"kk","qq"})}。因为数组也属于Object类的子类。 2、转换数组的类型,例如,(Object)new String[]{"kk","qq"}。 数组的反射 相同的维度和数据类型的数组,其Class对象一样。 String getName(),返回类名,即该Class对象对应的类、接口、数组类、基本类型的名称。如果是数组,会按照数组给出简写字母。例如,int[]数组的Class对象的名字是[I,[表示数组,I表示int类型。 int[] a1 = new int[]{2,5,7}; int[] a2 = new int[4]; int[][] a3 = new int[3][5]; String[] a4 = new String[]{"d","j","f"}; Class getSuperclass(),获得该Class对象的父类的Class对象。 所有一维数组的父类是Object,二维数组可以看做Object[]。也就是说,整个一维数组、二维数组中的数组都可以看做Object类型。例如,a1、a3的元素、a4的元素都是Object。另外,引用数据的数组既可以当做Object,也可以当做Object[]。 Arrays的asList()方法在处理基本类型数组(int[])和引用类型数组(String[])时的差异: 可以将引用数据的数组变成集合,数组中的元素变成集合中的元素;会将整个基本数据的数组变成一个Object对象。因为JDK1.4中,asList()接受的是数组Object[];JDK1.5中,asList()接受的是可变参数T…a,T可以是基本型或引用型。例如,a4按照1.4处理,直接打印;a1按照1.5处理,打印出哈希值。想要打印基本数据的数组,可以逐个元素录入,或者 使用反射中的Array工具类对数组进行反射操作。Class中拥有Arrays没有的方法,例如判断对象所属的类是否是数组等,再用Array中的方法操作数组。这些方法基本是静态方法,需要将数组对象传入。例如,打印对象,类似拆包 public static void printObject(Object obj){ Class oss = obj.getClass();//变成Class对象进行判断。 if(oss.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); } } 目前还无法通过数组中的元素获得数组的类型,因为基本数据无法使用getClass()方法,引用数据可以使用该方法。因为getClass()方法属于Object类。通过getClass().getName()获得该元素的类型。考虑到多态,无法确定数组的类型。 几种集合的比较 ArrayList,内部存入的是对象的引用,不是对象本身,所以同一个对象可以放入多次。有索引,按照顺序排列。 HashSet,存入的也是对象的引用。先判断该对象是否存在,如果已经存在就不放入;不存在的话,再通过哈希值排序后存入。要替换已有的相同对象,必须先删除原有的对象,再存入新对象。 按照hashCode()和equals()方法进行比较。将HashSet中的元素按照哈希值分成若干区域,先将对象按照哈希值放入对应的区域,再在区域内使用哈希值和equals()排序。这种算法只在HashSet集合中才有效。只有哈希值和equals()都相等,才能是同一个元素。在其他集合中,不需要按照哈希值排序。 当一个对象被存入HashSet集合后,就不能修改对象中参与计算哈希值的变量了。对于元素的操作,都是在区域中完成,如果元素的哈希值变化,会进入其他区域,也就无法操作,例如remove()、contains()。 内存泄露:对于某些不用的数据,虽然代码上已经被释放,但实际内存里并没有释放,一直在运行。或者调用了系统底层的资源,但是没有关闭资源。日积月累,内存会被消耗殆尽。例如,在HashSet集合中,由于修改了元素的哈希值,导致无法对元素进行操作,尽管代码上的确进行了操作。 反射的作用——实现框架功能 先建框架,然后再写类。运行时,框架调用这些类。 使用别人的类的方式有两种: 1、直接调用别人的类。 2、通过反射,让别人的类调用我的类。 自己的类是工具,别人的类是框架。通过工具类,使得框架合乎自己的需求。例如,框架是房子,我的类是门、窗户等配件。 框架解决的核心问题:写框架时,还不知道需要调用什么类,需要使用反射Class.forName(“类名”),等到类写好后,框架获得类名即可使用类、操作类的对象。 类的名字一般放在配置文件中。创建一个File文件config.properties作为配置文件,设置className=类名,需要时可以通过更改配置文件,将框架改造成自己需要的样子。配置文件放在工程目录下。举例,写个小框架 InputStream ips = new FileInputStream("config.properties"); Properties props = new Properties(); props.load(ips); ips.close(); String classname = props.getProperty("className"); Collection collections = (Collection)Class.forName(classname).newInstance(); 通过配置文件创建想要的对象,更改classname对应的类,即可获得其他想要的对象,例如,HashSet对象。 类加载器 得到配置文件的方式: 1、一定要使用完整的路径,但完整的路径不是固定的,是利用getRealPath()方法运算获得。项目的路径不固定,但配置文件在项目内的路径是固定的,例如,金山词霸\\配置文件,金山词霸安装位置不固定,但配置文件的目录是固定的。 这种方式还可以保存对配置文件的更改,使用更广泛。 2、类加载器(ClassLoader)会将.class文件和配置文件都加载进内存,所以可以使用类加载器加载指定的配置文件。编写代码时,将配置文件放在classpath目录下,Eclipse放在src目录或子目录下;编译后,Eclipse会自动将所有源代码编译后的.class文件存放到classpath目录或bin目录下,并将非.class文件原样复制到这些目录下。例如, InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");//从根目录开始向下找配置文件。 但这种方式只读,不能改,有局限性。SSH使用的都是类加载器。 Class内部提供了直接获得配置文件的方法,跳过了类加载器。但底层仍然是在调用类加载器。默认在该.class文件的包中,不是的话,使用绝对路径:自己指定包,或者使用根目录。例如, InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
三、关键字:内省、JavaBean、PropertyDescriptor类、Introspector类、BeanUtils工具包、注解、Rentention、Target、注解的基本属性和高级属性
内省IntroSpector JavaBean主要用于传递数据信息,其方法用于访问私有变量,且方法名符合某种规则。 如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。方法比较少。这些信息储存在类的私有变量中,通过set()、get()获得。 内省主要是对JavaBean进行操作。JavaBean内部的方法要按照某种规则命名,例如void setAge(int age)、int getAge()。JavaBean可以作为普通类进行操作;普通类如果内部有set()、get()方法,也可以当做JavaBean使用。 JavaBean的属性是通过get()和set()方法推断出来的,即去掉get、set后的字母,例如,属性为age,而不是成员变量,因为成员变量看不见。 获得属性名的规则:如果属性名的第二个字母是小写,则把第一个字母小写。例如,gettime—>time,setTime—>time,getCPU—>CPU。 JavaBean处理的好处: 1、JavaEE中许多地方需要使用JavaBean。 2、JDK给JavaBean提供的API称为内省。 PropertyDescriptor类 PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。主要方法: 1、getPropertyType(),获得属性的Class对象。 2、getReadMethod(),获得用于读取属性值的方法;getWriteMethod(),获得用于写入属性值的方法。 3、hashCode(),获取对象的哈希值。 4、setReadMethod(Method readMethod),设置用于读取属性值的方法;setWriteMethod(Method writeMethod),设置用于写入属性值的方法; 导包java.bean.*; 通过属性名获取对应的值,利用反射方法,如下: ReflectPoint pt1 = new ReflectPoint(7,9); String propertyName = "x";//给一个属性,获取值 PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt1.getClass()); Method methodGetX = pd.getReadMethod();//Read对应get()方法 Object reValue = methodGetX.invoke(pt1); 给某个属性设置值,如下: String propertyName2 = "y";//给一个属性,设置值 PropertyDescriptor pd2 = new PropertyDescriptor(propertyName2,pt1.getClass()); Method methodSetY = pd2.getWriteMethod();//Write对应set()方法 methodSetY.invoke(pt1,3); 右键—》Source—》Generate Geters and Setters,创建get()和set()方法。 选择一些代码,右键—》Refactor—》Extract Method,创建一个方法,提高复用性。 Introspector类 将JavaBean中的属性封装起来进行操作。在程序把一个类当做JavaBean来看,就是调用Introspector.getBeanInfo()方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息,即属性的信息。需要导包java.beans.*。 getPropertyDescriptors(),获得属性的描述,可以采用遍历BeanInfo的方法,来查找、设置类的属性。 private static Object getProperty_2(Object pt1, String propertyName) throws Exception { BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass()); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); Object reValue = null; for(PropertyDescriptor pd : pds){ if(pd.getName().equals(propertyName)){ Method methodGetX = pd.getReadMethod(); reValue = methodGetX.invoke(pt1); break; } } return reValue; } 通过这两个类的比较可以看出,都是需要获得PropertyDescriptor,只是方式不一样:前者通过创建对象直接获得,后者需要遍历,所以使用PropertyDescriptor类更加方便。 BeanUtils工具包 为JavaBean提供更多、放方便的功能。 beanutils.jar = beanutils-core.jar + beanutils-bean-collections.jar,可以通过BuildPath,添加额外的jar包,或者工程下建立lib目录,将jar包复制进来,再加载这个jar包:右键—》add to BuildPath。使用时需要导包:org.apache.commons.beanutils.BeanUtils。 需要配合使用acpche提供的日志包:logging。 获得属性的值,例如,BeanUtils.getProperty(pt1,"x"),返回字符串 设置属性的值,例如,BeanUtils.setProperty(pt1,"y",22),参数是字符串或基本类型自动包装。设置属性的值是字符串,获得的值也是字符串,不是基本类型。 BeanUtils的特点: 1、对基本数据类型的属性的操作:在WEB开发、使用中,录入和显示时,值会被转换成字符串,但底层运算用的是基本类型,这些类型转到动作由BeanUtils自动完成。 2、对引用数据类型的属性的操作:首先在类中必须有对象,不能是null,例如,private Date birthday=new Date();。操作的是对象的属性而不是整个对象,例如,BeanUtils.setProperty(pt1,"birthday.time",121); Java7的新特性:Map和JavaBean之间可以进行相互转换,key是属性,value是值。 describe:JavaBean—>Map;populate:Map—>JavaBean。例如: Map map = (name:Kim,age:18); BeanUtils.setProperty(map,"name","Kim"); copyProperties(Object dest, Object orig) ,将一个对象的属性值复制到另一个对象的属性,需要保证属性一致。 PropertyUtils类 和BeanUtils不同在于,运行getProperty、setProperty操作时,没有类型转换,使用属性的原有类型或者包装类。 注解Annotation JDK1.5出现的新特性。在java.lang.annotation包中。 对于过时的语句,java会提示过时了,通过@SuppressWarnings("Deprecation")在DOS中取消提示,但Eclipse无法取消。这就是注解,相当于标记。编译器、开发工具、javac通过反射获得注解里的内容,进而明确应该做什么、不应该做什么。注解可以加在包、类、属性、方法、参数及局部变量之上。 一个注解就是一个类。@SuppressWarnings,取消警告。@Deprecated,已过时,老版可以用,新版无法用。 HashSet集合中,对象必须覆盖Object类的equals()方法,否则会继续使用Object类的equals()方法进行比较,错误的比较方法。覆盖equals()方法,参数必须一致,为了防止错误写入本类的对象,加入@Override,必须正确覆盖父类方法,不是创建新方法。 注解的应用 在源程序中,调用一个类,这个类会用到注解,需要先准备好注解类,类在调用注解类的对象。注解类的写法类似接口,@interface。先写好注解类A,将注解放在类B中,类C在调用类B时通过反射获得注解类A的内容,进而明确该做什么、不该做什么。可以加上多个注解,加上的实际是注解类的对象:@interfaceA。 main()方法必须放在一个类下,但与这个类不一定有所属关系。 在注解类A上加注解B,这个注解B只为这个注解类A服务,B称为“元注解”。类似的还有元信息、元数据。元注解有2个:Rentention和Target。对注解类的注解,可以理解为注解类的属性。 Rentention注解类 注解的生命周期:Java源文件—》class文件—》内存中的字节码。编译或者运行时,都有可能会取消注解。Rentention的3种取值意味让注解保留到哪个阶段,RententionPolicy.SOURCE、RententionPolicy.CLASS(默认值)、RententionPolicy.RUNTIME。 @Override、@SuppressWarnings是默认保留到SOURCE阶段;@Deprecated是保留到RUNTIME阶段。 Rentention相当于注解类的一个属性,因为Rentention的值不同,注解类保留到的阶段不同。注解类内部Rentention的值使用value表示,例如,@Deprecated中,value=Runtime。 Rentention的值是枚举RententionPolicy的值,只有3个:SOURCE、CLASS、RUNTIME。 Target注解类 性质和Rentention一样,都是注解类的属性,表示注解类应该在什么位置,对那一块的数据有效。例如,@Target(ElementType.METHOD) Target内部的值使用枚举ElementType表示,表示的主要位置有:注解、构造方法、属性、局部变量、函数、包、参数和类(默认值)。多个位置使用数组,例如,@Target({ElementType.METHOD,ElementType.TYPE})。 类、接口、枚举、注解这一类事物用TYPE表示,Class的父类,JDK1.5的新特性。 注解的基本属性 属性,给注解提供更加详细的信息。 注解相当于接口,属性相当于方法。例如,@ItcastAnnotation(color="black"),给属性赋值,取值时类似调用方法,例如,System.out.println(annotation.color());。所有的属性必须全部出现,除非有缺省值。 如果只有value属性,没有其他属性,可以不写=,只针对value,例如,@SuppressWarnings("Deprecation")。或者有其他属性而且有缺省值,例如,String color() default "blue";,此时value单独出现,不用=。 获得注解的属性的值,例如 if(AnnotationDemo.class.isAnnotationPresent(ItcastAnnotation.class)){ ItcastAnnotation annotation = (ItcastAnnotation)AnnotationDemo.class.getAnnotation(ItcastAnnotation.class); System.out.println(annotation.color()); } } 利用反射获得注解的对象,在让该对象调用属性对应的方法。注意类型转换。 Rentention和Target也是属性,都是value对应的值,值的类型分别是RententionPolicy和ElementType,例如,@Retention(value=RetentionPolicy.RUNTIME)。 注解的高级属性 给注解增加高级属性,数组、枚举、注解。 数组类型的属性 例如,int[] arr() default {3,7,5};,MyAnnotation(arr={3,7,6})。如果数组只有1个元素,可以不加{}。@Target({ElementType.METHOD,ElementType.TYPE})也是数组类型的属性。 枚举类型的属性 //注解类内部的内容 EnumerationDemo.TrafficLamp lamp() default EnumerationDemo.TrafficLamp.RED; //调用注解的类上 @ItcastAnnotation(lamp=EnumerationDemo.TrafficLamp.YELLOW) //对注解进行操作 System.out.println(annotation.lamp().nextLamp().name()); 注解类型的属性 将一个注解类作为属性加入到另一个注解类中。 MetaAnnotation annotationAtt() default @MetaAnnotation("Jobs") @ItcastAnnotation(annotationAtt=@MetaAnnotation("Kim")) annotation.annotationAtt().value() 注解的返回值可以是8个基本类型、String、Class、枚举以及前面类型的数组,内部还有属性。 需要详细学习注解,可