黑马程序员--Java基础加强篇

时间:2021-12-28 00:41:44

开发工具使用

----------------------------------------------------------------------------------------------------------------------

Eclipse以及IDE开发工具介绍:

IDE:他是itegrity  development  environment的简称,译为集成开发环境;

常见的开发工具有:

       Eclipse:被广大的开发人员所使用,此外还有MyEclipse,他是在Eclipse的基础上安装

插件,从而提供更强的功能;

       NetBeans:由于被Eclipse抢夺的市场,没有普及起来;

       JBuilder:  由于收费,所以已经没落;

Eclipse工程管理和快捷键的配置:

Workspcace的概念:不同的人在使用eclipse编程时有不同的风格,可以借助工作间来使用个人独有的设置;

                                      打个比喻:eclipse是一家公司,来了一个新人,他就会分给一个新的工作间, 用自己

                                     独有的方式在工作间里工作;

       所以我们在打开eclipse时,他会提示我们建立一个工作间;这样我们编写的所有的工程都会保存在这个

工作间指定的目录中,同时在编程的过程中,所有的工程(project)都会共享我们在工作间配置的一些东西,

比如:模板,快捷键等;

如何新建一个工作间workspace

       File--->Swith Workspace ---> Other --->在文本框中新建  ---> ok;

如何新建一个工程project

       File---> New --->Java Project ---> 在文本框中输入名字 ---> Finish;

如何在工程中新建一个包:

       选中工程文件---> (右击)New ---> Package ---> 在Name文本框中输入包名 ---> Finish;

       (输入报名有讲究:通常是将公司的域名反过来写,全部小写;比如:cn.itcast.day1)

如何在包中新建一个类:

       选中包---> (右击)New --->Class ---> 在Name文本框中输入包名  --->Finish;

       (在输入类名时也是有讲究的:如果类名由多个单词组成,那么每个单词首字母要大写)

如何对工程、包或者类改名字:

       选中要改的对象---> (右击)Refactor ---> Rename ---> 输入新的名字 ---> ok;

如何配置编译环境和运行环境:

       window---> preferences ---> java |---> compiler:进行编译器配置;

                                                        |--->Installed JREs:进行运行环境的配置;

       这样一来在整个工作间就使用配置后结果!

       如果我们想在某个工程中单独使用编译器和运行环境的配置,而不是使用整个工作间的配置,我们可以这样做:

选中工程--->(右击)Run  As---> RunConfigurations --->选中JRE进行单独的配置!

配置的原则是:不能用行动Java版本来编译,然后用旧的Java版本来运行!

快捷键的设置:

       window---> keys ---> 在文本框中输入:Content Assist --->

如果他已经有了快捷键设置,但是我们想要设置成为自己配置的,选中后:

可以Remove Binding (在MyEclipse中);或UnBinding Commend(在Eclipse中);

       --->然后在下面的Binding文本框中直接按按键“alt”+“/”,配置自己的快捷键--->Apply;         

有可能我们配置的快捷键此时还不能使用,因为有可能我们自己配置的快捷键和其他的已有的快捷键冲突,

此时我们要:

              window---> keys ---> 在文本框中输入:alt + / --->  看看是什么原有的快捷键和

             我自定义的快捷键冲突,然后将他删除,这样我们配置的快捷键才可以正常使用!

eclipse中配置Java的模板代码:

       window--->preferences ---> java ---> Editor ---> Templats ---> new --->

       然后在Name文本框中输入模板的名称,在pattern中输入模板代码,在模板代码中确定原来选中代码的

        位置:line_selection,以及cursor(光标)的位置;然后Apply;

       在使用时的用法是:选中一段代码---> (右击)Surround With ---> 通过名称选择模板;这样选中的语句将

出现在line_selection处,而光标出现在Cursor处,等待我们输入。

Eclipse中导入已有的工程:

       我们从他人那里得到一个工程的代码,要导入到Eclipse中,应该如何操作呢?

       首先,将工程复制到已有的工作台的指定目录中去;

       然后按照下面的流程进行:File ---> import---> Genneral --->

Existing Project To Workspace ---> Next---> 选择目录---> Finish ;

最后调节导入的工程的一些配置:因为导入的工程的配置可能和我们自己的工作间的配置有些不同,这样

我们需要做一些调整:

        选中工程---> (右击)Build Path ---> configure---> Libararies ---> 选中它原有的库---> Remove --->

         Add Library ---> JRE  System Library(我们也可以添加自己的库:User  Library);

----------------------------------------------------------------------------------------------------------------------

枚举:

       引子:对于一周七天,有的人把星期天作为一周的第一天,记为0;而有的人将星期天作为一周的最后

一天,记为7;这样一来就会导致程序员之间由于没有达成共识,从而使得编写出来的程序有安全隐患;

       为了避免上述的情况发生,Java提供了枚举这种方式来限定某个类型变量的取值只能为若干个固定值

中的一个,这样就不会发生程序代码因人而异,从而在编译时期就控制了源程序填写的非法值,提供了

程序的安全性;而普通变量的开发方式则无法在开发阶段实现这一目标;

下面我们通过普通类来模拟枚举的原理

为了代码的简单我们假设一周只有周一和周日两天,这两天交替到来!

public  class EnumTest{
public static void main(String[] args){
WeekDay weekDay = WeekDay.Mon;
System.out.println(weekDay.nextDay());
}
}

方式一:

public  class  WeekDay{
privete WeekDay(); //将构造方法私有化,这样就不能乱new对象,而是只能
//通过静态成员来获取我们规定的对象;
public static final WeekDay SUN = new WeekDay();
public static final WeekDay MON = new WeekDay();
//通过这种方式限定了对象,这样调用者就无法乱用了!提高了安全性
public WeekDay nextDay(){
if(this == SUN){
return MON;
}else{
return SUN; //如果一周有七天的话就需要写大量的if…else语句;
}
}
public String toString(){ //将WeekDay以字符串的形式打印出来;
return this == SUN?“SUN”:“MON”;
//如果是一周七天的话,就不能用三元运算符了,而是通过大量的
if…else语句来输出对应的字符串数据*/
}
}

方式二:

       看了上面的方式一,我们发现如果需要通过枚举限制的对象太多的话,就需要大量的if…else语句,

这样一来代码就不好看了!于是产生了下面这种定义枚举的方式:

public  abstract  class  WeekDay{   //被abstract修饰,所以不能直接new对象;
private WeekDay();

public static final WeekDay SUN = new WeekDay(){ //多态以及内部类;
public WeekDay nextDay(){ //复写父类的方法
return MON;
}
public String toString(){ //复写父类的方法
return SUN;
}
};

public static final WeekDay MON = new WeekDay(){ //多态以及内部类;
public WeekDay nextDay(){ //复写父类的方法
return SUN;
}
public String toString() //复写父类的方法
return MON;
}

}

public abstract WeekDay nextDay();
//定义了抽象方法,功能是获取下一天是星期几;他将被内部类实现!
public abstract String toString();
//定义了抽象方法,功能是输出对象的字符串表达形式他将被内部类实现!

}

           方式二中采用抽象类的形式将大量的if…else语句转移成了一个个独立的内部类;

枚举的基本应用:

public  class  EnumTest{
public static void main(String[] args ){
WeekDay weekDay = WeekDay.FRI;
System.out.println(weekDay); //调用toString()方法,输出的结果为FRI;
System.out.println(weekDay.name()); //输出自己的名字:FRI;
System.out.println(weekDay.ordinal()); //输出自己的角标位:5;
System.out.println(WeekDay.ValueOf(“SUN”));
//将传入的字符串形式的SUN变成一个对应的WeekDay的实例对象;
System.out.println(weekDay.values()。Length);
//将枚举类中所有的元素取出装到一个数组中

}
public enum Weekday{ //枚举就相当于是一个类;
SUN,MON,TES,WED,THI,FRI,SAT;
//里面的每一个元素就是一个枚举对象,最后的分号可有可无;
}
}

实现带有构造方法的枚举:

public  enum  WeekDay{
SUN,MON,TES,WED(2),THI,FRI,SAT;
//枚举元素列表需要放在代码部分的最前面,同时和后面的内容通过分号隔开;
//枚举的元素列表中所有的元素都是静态的;
private WeekDay(){ //列表中所有无参的元素在初始化时调用该构造函数
System.out.ptintln(“first”);
}
private WeekDay(int day){ //WED(2) 在初始化时会使用该构造函数
System.out.println(“sceond”);
}
}

实现带有抽象方法的枚举:

      定义一个交通灯,他有红绿黄三种颜色,而且每个灯都有自己的时间,以及获取下一个灯是什么的方法:

class  abstract  TrafficLamp{
RED(30){ //他是TrafficLamp实现了的实例对象;
public TrafficLamp nextLamp(){ //复写了TrafficLamp的抽象方法;
return GREEN;
}
}, //在实例化过程中自动调用父类有参的构造函数;
GREEN(35){
public TrafficLamp nextLamp(){
return YELLOW;
}
},
YELLOW(5){
public TrafficLamp nextLamp(){
return RED;
}
};

public abstract TrafficLamp nextLamp(); //定义获取下一个灯的功能;
int time;
public TrafficLamp(int time){ //定义有参的构造函数;
this.time = time;
}
}

我们可以通过枚举来达到单例设计模式的要求,即只产生一个对象:那就是在枚举类中只有一个元素;

----------------------------------------------------------------------------------------------------------------------

反射:

      概念:反射就是将Java中的各种成分映射成为相应的Java类

      我们知道在一个Java类中有成员变量,成员函数,构造函数,包以及修饰符等信息,对于这些成分,

我们可以通过反射将这些成分映射为对应的类,如:Filed , Method , Constructor, Package等;这样一来,

类中的这些成分就可以被我们不依赖于类而拿来单独使用,因为他被我们取了出来,存放在对应的类中,

不依赖于类名去调用,增加了使用的灵活性,为使用框架打下了基础;

      数学是研究数量、结构、变化以及空间模型等概念的一门学科;物理是研究物质结构、物质相互作用

和运动规律的自然科学;化学是研究物质性质、组成、结构与变化规律,并创造新物质的科学……这每一

门学科都研究一个领域,但是他们都是同一类事物---科学!

       程序中的各个Java类他们也各自描述某一类事物,但是这些Java类也可以被描述为同一类事物---Class!

也就是说Class这个类是用来描述类的!

       正如:卞之琳在《断章》所写的:“我在楼上看风景,看风景的人在楼下看我”。Java类在描述别的事物,

同时他们也被Class这个类在描述;通过查看API文档我们发现Class这个类没有构造函数,那么我们如何

得到Class这个类的实例对象?

通常获取各个字节码(类)的对应的实例对象有以下三种途径

(1)、类名.class,例如:System.class  这样就获取了System的字节码;

(2)、对象.getClass,例如:new  Data().getClass();这样就获取了Data这个类的字节码;

(3)、Class.forName(“类名”),例如:Class.forName(“java.util.Data”) ;这样也可以获取了

                Data这个类的字节码;-----这种形式也是我们使用频率最好的获取字节码的方式

---------------------------------------

按照万物皆对象的思想;类中的成员变量也可以有类来描述他们,那就是Filed类,类中的成员方法也可以有

类来描述,那就是Method类,类中的构造函数也可以有类来描述,那就是Constructor类……

---------------------------------------

构造方法的反射:

      Constructor标示一个类的构造方法,我们可以通过下面的操作得到某个类的所有的构造方法

(因为有可能某个类不止一个构造方法!);

首先获取这个类的字节码文件,然后从字节码文件中获取他所有的构造方法:

       Constructor[]  constructors = Class.forName(“java.lang.String”).getConstructors();

      如果我们想要获得某个类指定的构造方法,只需要在获得字节码文件后,在获取构造方法的函数

     的参数列表中传入相关的参数类型的字节码即可:

       Constructor  constructor =Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);

然后我们可以用反射得到的构造方法来创建对象:

      String  str =(String)constructor.newInstance(new StringBuffer(“abc”));---反射的方式;

而我们以前所使用的常规的通过构造函数new对象的方式是;

      String  str = new String(newStringBuffer(“abc”));    ---常规方式;

我们来剖析一下通过反射获取构造函数创建对象的过程:

第一步:通过字节码文件获取到构造函数;

第二步:通过获取到的构造函数以及newInstance()来完成创建对象;

      其实Class类提供了将这两步合成一步走的方法:

              String  str =(String)Class.forName(“java.lang.String”).newInstance();

      在这个过程中,现是获得默认的构造方法,然后通过该构造方法创建实例对象;

--------------------------------

成员变量的反射:

      Field代表类中的一个成员变量;对于类中的成员变量,我们是通过Filed类来描述的,所以通过反射获取

的成员变量是作为Filed类的实例对象来存储;

public  ReflectPoint{    //定义一个类,它里面有两个成员变量;
private int x; //其中1个成员变量是私有的;
public int y ; //还有一个成员变量是公有的;
public ReflectPoint(int x, int y){
this.x = x;
this.y = y;
}
}

public ReflectPointTest{
public Static void main(String[] args){
ReflectPoint pt1 = new ReflectPoint(3, 5);
Filed filedY = pt1.getClass().getFiled(“y”);

/*这里的filedY代表的是对象身上的y这个成员变量,如果我们new了多个ReflectPoint
对象,比如 叫做pt1,pt2……那么这个fieldY代表的是所有对象身上的y这个成员变量,
所以他是没有具体值的,只有当他和某个对象发生关系,才可以获取到这个具体的变量
是多少!所以我们说这个fieldY代表的是5吗?当然不是!他是类上的变量而不是具体对
象上的!*/

Sysem.out.println(fieldY.get(pt1)); //此时获得的结果才是5;
Filed filedX = pt1.getClass().getDeclaredFiled(“x”);

/*由于x成员变量是私有的,直接通过getFiled()方法是无法获取到该变量的,所以使用
getDeclaredFiled()方法,他的作用是,获取字节码中任何声明过的成员变量,
不管你是私有的还是公有的*/

fieldX.setAccessible(); //暴力反射!
System.out.println(fieldX.get(pt1)); //此时可以pt1身上的x变量的值;

/*此外我们还可以通过Class类中相关的方法一次性将所有的成员变量取出,
装到一个Filed数组中*/
Filed[] filed = pt1.getClass().getFileds();
}
}

成员变量反射的综合案例:

      需求:将任意对象中所有的String类型的成员变量对应的字符串内容“b”变成“a”;

class   ReflectFiledTest{
public static void changeStringValue(Object obj)throws Exception{
Filed[] fileds = obj.getClass().getFileds();
for(Filed filed : fileds){
String oldValue = (String)filed.get(obj);
String newValue = oldValue.replace(‘b’,‘a’);
filed. Set(obj, newValue);
}
}
}

------------------------------

成员方法的反射:

      Method代表类中的一个方法(可以是非静态的也可以是静态的!);

      通过反射获取构造方法:

              Method methodCharAt =String.class.getMethod(“charAt”,int.class);

                     //此处红色的部分代表要反射的是具体的那个方法;

                     //而紫色部分是反射的具体方法的参数列表!此处的参数列表中采用可变参数

                     //列表的新特性,所以可以直接传入多个参数,彼此之间用逗号隔开;

      如何使用通过反射获得构造方法:

              System.out.println(methodCharAt.invoke(str,1));

                     //此处红色的部分代表方法作用于那个对象;

                     //而紫色部分是传给方法的参数!

有可能我们要反射的方法是一个静态函数,在使用时我们只需在传入作用对象的位置写成null就可以了!

对于接收数组参数的成员方法进行反射:

class  TestArguments {
public static void main(String[] args){
for(String arg : args){
System.out.println(arg);
}
}
}
//我们来调用TestArguments类中的主函数;
class TestReflectDemo{
public static void main(String[] args ){
//如果是使用传统形式,我们可以通过下面的方式调用一个类中的主函数:
//TestArguments.main(new String[] {“111”,“222”,“333”});

//如果是通过反射来调用TestArguments类中的主函数,又该如何办呢?
String startClassName = args[0]; //可以通过主函数传值的方式传入目标类;
Method mainMethod = Class.forName(startClassName).
getMethod(“main”,String[].class)
mainMethod.invoke(null,(Object)String[]{“111”,“222”,“333”})
/*因为要兼容JDK1.4中的数组形式的参数列表,所以如果我们只写个
String[]{“111”,“222”,“333”}的话,虚拟机会认为我们传入了多个参数,
但是我们实际上只是传入了一个参数String[] ,为了告诉虚拟机我传入的只
是一个参数,我们通过加Object,虚拟机就会讲传入的数组当成一个参数*/
}
}

小知识点:上面我们说了通过主函数传值要反射的目标类;如果是在Dos命令行 中的话,我们可以在

                      编译时,手动输入目标类的类名完成传值,但是在

Eclipse中如何传值呢?

       选中目标类---> 按F2,出项完整类名,将类名复制;--->然后在要运行的类中右击鼠标--->

点击Run  As ---> 选择Open RunDialog---> 在(x)=argument的program Argument文本框中粘贴

完整类名---> 点击apply就完成了! 

---------------------------------

数组与Object的关系及其反射的关系:

A.   具有相同维数和类型的数组属于同一个类型;即他们具有相同的Class实例对象;

例如:int[]  al = new int[3];

         int[]  a2 = new int[]{1,2,3,4}

        System.out.println(al.getClass()==a2.getClass());   //结构为true;

B.    代表数组的Class实例对象的getSuperClass()返回的父类为Object类对应的class;

C.    基本数据类型的一维数组整体可以被当作Object类型使用,但是不能单做Object[]来使用,

       否则基本数据类型就可以当作Object了!而非基本数据类型的数组,既可以将他们整体当

       作Object类型,又可以将其当成Object[]来使用;

Arrays是对数组进行操作的工具类,他里面有许多的方法可以方便我们简化工作;例如:

Arrays.asList():可以将Object[]类型的数组转变成List集合;但是该方法无法将int[] 转换成List

集合,因为int[] 不是一个Object[];

数组反射的应用:

public  static  void  printObject(Object  obj){
Class clazz = obj.getClass(); //获取传入对象的字节码
if(clazz.isArray()){ //通过字节码判断他是否是数组;
int len = Array.getlength(obj);
for(int i=0 , i<len , i++){
System.out.println(Array.get(obj, i));
//通过Array类的get()方法得到数组中的元素
}
}else{
System.out.println(obj);
}
}

----------------------------------------------------

HashCode()方法的作用

      HashCode()通常我们在HashSet这样的集合中,可以看见。他的作用主要体现在通过哈希算法来对

集合中的元素进行位置排放的集合上;

      举个例子:往一个元素不可重复的集合中先存放一万个不同的元素,这时如果想再存放第10001个时,

需要将他和前10000个元素做比较,发现没有重复,才可以放入!这样一来比较的次数太多了,使得集合

的操作效率太低了!但是用了HashCode()就可以大大的减少比较次数,提高集合的操作效率;其原理如下:

       集合按照HashCode()的结果分成了若干个Hash值域(假设是100个)。当一个对象存进来时,首先

计算他的Hash地址值,然后在相应的值域中在查找。比如算出来是在8号值域,那么其他值域压根就不去找,

只在8号值域的100个元素中找有没有重复的,这样一来大大的减少了比较的次数,提高了集合的操作效率!

      但是通过HashCode()方法排放元素的集合,在操作时,不可以改变元素对象中参与Hash地址值的成员

变量,否则有可能导致内存泄漏!比如我们将一个元素参与Hash地址值运算的成员变量改变了,那么有可能

本来这个元素在8号值域中,而我们改变后却到12号值域中去查找该元素,自然找不到,那么后期我们针对这

个元素进行操作都是无效的,即使是删除,也删除不了,我们却会认为已经删除了,但是那个元素任然在集合

中占用内存,导致内存泄漏!

---------------------------------------------------

反射在框架技术中的作用:

      框架的概念来自于工程学;以前人们修房子,必须一层一层的往上盖,一层没有修好二层就不能动工,即

使有100个工人,修一层楼时只需要20个工人,那么剩余80个工人就必须先等着,在整个工程修建的过程中,

实际上只有20名工人在干活,这样就浪费了极大的劳动力和时间。而且最糟糕的是:万一某个负责搭架的工人

由于生病耽误了,他就影响到了其他19名工人,那么整个工程进度都得向后推迟;我们希望100个工人可以一

起上,框架的出现为这创造了可能:首先集中优势资源将框架搭好,这个步骤耗时较少,然后让100个工人

同时上去修墙。这样一来虽然在搭建框架的早期浪费了一点时间,但是后期所有工人其上阵,就将工程进度

赶回来了!而且效率更高------磨刀不误砍柴工

      在我们程序开发的工程中,使用框架的理念也可以提高下发效率,原理同上;这样大大的提高了程序的开

发效率!同时当我们需要对某一部分的代码进行改进时,直接改进即可,由于框架的存在,保证了不会牵一发

而动全身,提高了代码的灵活性!

      下面我们通过代码来演示反射在框架中的作用:

首先创建一个properties类型的配置文件,里边存有这样的一个键值对:

      className= java.util.ArrayList;

然后我们编写的程序如下:

class  Demo{
public static void main(String[] args) throws Exception{
FileInputStream fis = new FileInputStream(“配置文件名”);
Properties props = new Properties();
props.load(fis);
//通过加载文件读取流直接将配置文件中的键值对信息加载到内存中;
fis.close();
String className = props.get(“className”); //获取到键值对信息;
Collection collections = (Collection)Class.forName(className).newInstance();
/*通过反射使得这里的集合到底是什么数据结构的集合可以由使用者
自己传入,即通过控制配置文件中的键值对信息来控制。这就相当于相当于搭建了一个
框架,添加砖时到底是使用空心砖还是普通红砖,后期再确定,不需要一开始就写死,
增强了灵活性;*/
Reflectpoint pt1 = new ReflectPoin(3,3);
Reflectpoint pt2= new ReflectPoin(2,3);
Reflectpoint pt3 = new ReflectPoin(3,4);
Reflectpoint pt4 = new ReflectPoin(3,3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt4);
System.out.println(collections.size());
//因为ArrayList集合中可以有重复元素,
//所以添加的四个元素都添加进去了,因此输出结果为4

}
}

        当我们将配置文件中的className对应的值改变为HashSet以后,本程序中创建的Collection的实例

对象就是HashSet集合了!那么他里面的元素是不可以重复的,集合的大小就是3了!-----我们通过控制

配置文件来控制程序,这就是反射在框架中的作用!

----------------------------------------

配置文件的加载方式:

通常对于配置文件的加载有下面两种方式

方式一:通过获取程序安装目录的路径和配置文件在程序中的路径,二者拼写出一个完整的绝对路径来加载

                文件;然后将这个绝对路径传入读取流或者输出流中,完成对于配置文件中数据的读取和写入!

方式二:通过类加载器中的getResourceAsStream(“相对路径”);

               这里的相对路径是相对于src目录;所以如果一个配置文件是在一个包

那么应该写成:

        FileInputStream fis = ReflectPointTest.class.getClassLoader().

                  getResourceAsStream(“cn/itcast/day1/config.properties”);

此外我们还可以不获取类加载器,直接使用类的getResourceAsStream()来加载配置文件;此时传入的配置

文件的相对路径是相对于classPath,所以书写更方便:

         FileInputStream fis =ReflectPointTest.class.getResourceAsStream(“config.properties”);

之所以可以这样是因为通常我们是将配置文件和class文件放在一起的!

但是万一我们没有讲配置文件和class文件放在一起,比如配置文件在和class文件的包的一个平级的叫做

cn.icast.day1.resource的包中,那么我们该如何如何传入路径呢:

           FileInputStream fis = ReflectPointTest.class.getResourceAsStream

                            (“/cn/itcast/day1/config.properties”);

              小知识点:如果我们在源文件中写一个配置文件,Eclipse会自动的将这个配置文

                              件复制到class文件的目录中去;

       注意:上述两种获取配置文件的方式都很重要,并且方式二是不能取代方式一的,因为方式二只有

                   getResourceAsStream()方法,也就是说他只能从配置文件中读取数据,而不能往配置文件中

                    写入数据;而方式一既可以从配置文件中读取数据,还可以写入数据;

----------------------------------------------------------------------------------------------------------------------

JavaBean----一个特殊的java类

他特殊在哪里呢?

      这个类里面有getxxx()和setxxx()的方法,同时我们认为xxx代表的是他的一个属性,即使这个名叫xxx的

属性也许不寻存在;

class  Person{
private int x ;
public int getAge(){
return x;
}
public void setAge(int age){
this.x = age;
}
}

       如果我们将这个类类看作是一个javaBeanm,那么我们就可以认为这个类中有一个名叫age的属性,虽然

这个叫age的属性并不存在,但是当我们调用它里面的get和set方法时,是可以通过x这个变量来接收或者给出

相应的值的!

      当然在获取这个属性时,这个属性的名称有一个规范

      如果方法的形式是gettime----->那么我们认为属性名叫做:time;

      如果方法的形式是getTime---->  那么我们认为属性名叫做:time;

      如果方法的形式是getCPU---->  那么我们认为属性名叫做:CPU;

------------------------------------------------------

通过内省(introspector)来操作javaBean:

      我们之所以要将符合javaBean特点的类当作JavaBean自然是因为这样来处理有好处:

那么他有什么好处呢?请看下面的代码:

首先定义一个JavaBean类:

class  Person{      //定义了一个JavaBean
private int age ; //他有一个私有的age属性
public Person(int age){
if(age>0&&age<130){
this.age = age;
}

}
public int getAge(){ //此外他还有获取age属性的方法;
return age;
}
public void setAge(int age){ //定义了传入age属性值的方法;
this.age = age;
}
}

下面我们来演示通过内省来操作JavaBean类:

import  java.beans.PropertyDescriptor;
public class IntrospectorTest{
public static void main(String[] args){
Person p1 = new Person(8);

/*如果将Person类看作是javaBean,那么我们想要获取任意Person类的实例对
象身上的age属性值是多少,应该咋办呢?*/
String propertyName = “age”;
//x--> X--> getX--> methodX;
PropertyDescriptor pd = new PropertyDescriptor(propertyName, p1.getClass);
//获取p1对象身上的指定属性的属性描述器;
Method methodGetAge = pd.getReadMethod();
//通过属性描述器来获得获取属性值的方法;
Method methodSetAge = pd.getWriteMethod();
//通过属性描述器来获得设置属性值的方法;
methodSetAge.invoke(p1,10);
//通过内省获得的设置属性值的方法来设置属性值;
Object retVal = methodGetAge.invoke(p1);
//用p1对象来调用获取属性值的方法,获取相关属性的属性值;
}
}

从上面的代码中我们可以看出:

       使用内省可以很方便的通过属性名获取JavaBean类中的设置属性值和获取属性值的方法;

此外在Eclipse中提供了抽取函数的方法:

     选中要抽取函数的代码,右击鼠标右键,选择Refactor,再选择Extract  Method,然后根据需求

选择需要被函数调用的参数,就可以了。

-----------------------------------

对javaBean的复杂内省操作

      “横看成岭侧成峰,远近高低各不同”。

      对待事物,我们从这个角度来看是一种状态,有这样的处理方式。再从另外的角度看又是另外的

形态,又有另外的处理方式;举个例子:假如我家和市长有点亲戚关系,如果我把他当作叔伯来看待,

那么招呼他就用家常菜就可以了,但是如果我把他当作是一个领导,那么肯定是费劲心思上好的

(只是个例子而已,我家三代贫农,哈哈哈)!

       对于javaBean也是一样,当我们把他当作普通类来看时,他是一种形态,就像使用普通类一样的

操作他就是了!但是如果把他看作是javaBean类时,又是另外的一种形态!要使用专用的“大餐”来处

理他---比如需要导入特定的包、使用特定的方法等等!

       BeanInfo这个类就是专门用来描述我们把一个javaBean的类当作javaBean时的状态的!

这样一来就提供了更多的操作javaBean类的特有方法,我们使用起来也就更加方便;

代码演示:

class  Person{      //定义了一个Java
private int age ; //他有一个私有的age属性
public Person(int age){
if(age>0&&age<130){
this.age = age;
}

}
public int getAge(){ //此外他还有获取age属性的方法;
return age;
}
public void setAge(int age){ //定义了传入age属性值的方法;
this.age = age;
}
}

public class BeanInfoTest{
Person p1 = new Person(25);
public static void main(String[] args){
BeanInfo beanInfo = Introspector.getBeanInfo(P1.class);
//将Person这个类当作javaBean看待!
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
//获取诸多属性的属性描述器;
String propertyName = “age”;
Object retVal = null;
for(PropertyDescriptor pd : pds){
if( pd.getName().equals(propertyName) ){
//当获取的属性描述器描述的属性和我们
//传入的一样时,执行括号内的代码
method methodGetX = pd.getReadMethod();
retVal = methodGetAge.invoke(p1);
}
}
System.out.println(retVal);
}
}

-------------------------

BeanUtils工具包:

      这个包是Apache提供的专门用于操作对象属性的工具包,他里面的方法全部是静态的;我们在使用时

需要先从Apache官网上现在他的压缩包,然后解压。

      复制解压文件中最大的jar包 ---> 在工程下新建一个文件夹 ---> 将jar包复制到一个名叫libd 文件夹中

(此时jar包只是存在于工程下,而还没有配置到classpath中!)--->然后在Eclipse的工程目录的lib目录

中选中jar包 ---> 右击鼠标右键---> 选择BuildPath--->add to  buildPath ---> 出现一个小奶瓶!

      此时只是完成了一半,我们还需要再下载一个Apache提供的log包按照上面相同的操作在配置到工程

中以及工程的buildpath中,此时才可以正常使用安装的beanutils工具包;

 

BeanUtils是以字符串的形式对javaBean中的属性进行操作:

      BeanUtils.setProperty(pt1,“x”,“9”);

                                   //将pt1对象身上的x属性的值设定为9。通过字符串的形式传值!

      BeanUtils.getProperty(pt1,“x”);

                                   //获取pt1对象身上的属性的值。获取到的是字符串!

      PropertyUtils是以javaBean中属性本身的类型进行属性值的操作:

      PropertyUtils.setProperty(pt1,“x”,9);

                                                       //将pt1对象身上的x属性的值设定为9。

//通过javaBeanb本身的数据类型的进行传值!

      PropertyUtils.getProperty(pt1,“x”);

                             //获取pt1对象身上的属性的值。获取到的javaBean本身的数据类型!

此外BeanUtils还可以支持对于属性的级联操作:

        比如说一个javaBean对象身上有一个data属性,而这个data属性他还可以被看作是一个javaBean,因为

他里面有setTime()和getTime()的方法,所以我们认为这个data中还有一个time属性。如果我们想要给这个

javaBean对象身上的data属性身上的time属性设置值该怎么操作呢?

      如果我们自己通过反射来操作,步骤是很繁琐的,但是通过BeanUtils工具包就可以大大的简化操作:

      Data   birthDay = new Data();

      BeanUtils.setProperty(pt1,“birthDay.time”,“111”);

                                   //此处就完成了对于属性的级联操作,设定了time的值为111;

而且BeanUtils工具包还提供了将javaBean和map集合结合起来的方法:

      Map  map = {name : “zxx” , age : 25};  //这是Jdk7.0中定义map集合的新特性

      BeanUtils.setProperty(map, “name” , “lhm”); //将map集合中的name对应的值设置为lhm

--------------------------------------------------------------------------------------------------------------------------------------

注解(Anotation)---一个非常重要的知识点!---JDK1.5的新特性!

      注解就相当于一种标记;在程序上添加注解就相当于为程序中的某个成分打上了一个标记,这个标记主要

是在反射时,要调用者看见这个标记,从而知道应该采取什么相应的动作;

注解可以添加在包,类,字段,方法,方法的参数以及局部变量上;

      JDK的java.lang包中提供了一下三个常见的注解

      1、@SuppressWarnings(“deprecation”)

                            这个注解的作用是压缩警告注解,作用是:让编译器不要提示方法过时;

                           说明一点:即使我们添加了这个标记,但是在Eclipse中,他仍然提示我们方法过时,对于这种

                                               情况, 我们不需要去管!而在Dos命令行中编译的话,有这个标记,编译器就不会

                                               提醒我们方法过时;

      2、@Deprecated   这个注解通常是添加到方法的前面,告示编译器这个方法已经过时

      3、@Override  这个注解的作用是表明这个方法是覆盖了父类的方法;他可以帮助我们来防止覆盖方法时

                                  发生错误:比如我们并没有真正的覆盖父类中的方法,而我们却以为覆盖了,这时加上这个

                                 标记,编译器就会提示我们:你还没有覆盖呢!

----------------------------------------------------------------------

自定义注解:

      我们除了使用Java提供给我们的注解以外,有时候我们还需要自己定义注解,那么字节定义注解

有什么格式上的要求呢?

      其实注解和类是平级的,一个注解就可以看作是一个类!到目前为止我们学过的和类class平级的

有interface、 enum、javaBean,现在还多了一个注解:@interface。

       就像字节码中的任意成员方法都可以用Method来表示;这些class,interface,@interface还有

enum都可以通过TYPE来表示。

通常一个注解有三种生命周期:

Java源文件阶段:注解存在于这个阶段是为了给编译器来检查代码的,过了这个阶段注解的存在便没有

                                       意义了,所以编译器会将注解去掉。

            比如:@Override这个注解是为了保证代码完成了覆盖。当我们在源文件中实现了覆盖,那么这个

                         注解就没有价值了,所以编译器在将源文件编译成class文件的时候就将注解去掉了,所以

                         这个注解只是存在于源文件阶段!

            再如:@SuppressWarnings()是告诉编译器不要管我调用的方法是否过时,那么过了编译阶段,

                         这个注解就没有用了,所以编译的时候编译器也会讲这个注解去掉,所以这个注解也是

                           存在于源文件阶段;

Class文件阶段:通常注解默认保存在这个阶段!但是这样的话本来没有必要存在的注解也被保留下来了,

                               所以可通过元注解将注解的保留阶段进行更改:

            什么是元注解?所谓的元注解就是给我们自定义的注解所添加的标记!

                     比如:

                    @Retention(RetentionPolicy.SOURCE)

                               这就是一个元注解,他的作用就是更改下面我们自定义的注解的保留阶段! 保留阶段

                        是一个枚举,他有三个值:

                                       RetentionPolicy.SOURCE(源文件阶段)

                                        RetentionPolicy.CLASS(class文件阶段);

                                       RetentionPolicy.RUNTIME(内存的字节码中)。

                    @Target(ElementType.METHOD,ElementType.TYPE)

                     //这也是一个元注解,他的作用是限定我们自定义的注解作用范围的!

                     @interface MyAnotation{}   //这是一个我们自定义的注解;

内存中的字节码阶段:比如@Deprecated这个注解他就需要保留到字节码阶段,因为我们调用一个方法实际

                                       上是将这个方法所在的类的字节码文件加载到内存中来,如果这个阶段没有了

                                       @Deprecated这个注解,编译器咋知道调用的方法是否过时呢?所以该注解必然是

                                        保留到了内存中的字节码阶段;

------------------------------------------------------------------------

为注解增加各种属性:

      为注解添加属性的形式是这样的:

@interface MyAnotation{      //定义一个带有属性的注解;

      String  color( ) default  “blue” ;  //通过方法来定义一个属性,默认值是blue

      String  value( ) ; 

}

      下面我们来演示如何使用带有属性的注解:

              @MyAnotation(color= “red”,value = “hcc”)

              有必要说明的一点是:当一个注解中定义了属性,那么在使用这个注解时就必须给这个属性设定值,

                                                      要么通过设置默认值来传值,要么通过类似于构造函数那样传值;当一个注解

                                                     中只有value这个属性,或者虽然除了value外还有其他属性,但是其他属性都

                                                     有默认值时,可以在使用注解时写成这样:

                                                       @MyAnotation(“hcc”)      也就是说不需要写成value=“hcc”的形式

        除了给注解设置String这样的简单属性外,还可以给注解设置数组,枚举,甚至是注解这样的高级属性;

----------------------------------------------------------------------------------------------------------------------

类加载器:

      当我们在程序开发过程中要使用一个类,那么就需要将这个类的class文件加载到内存中,并处理成二进

制形式的字节码文件,那么这个工作是由谁来完成的呢?他就是类加载器;

       Java虚拟机可以安装多个类加载器,但是系统默认的类加载只有三个,他们分别是:

                  BootStrap、ExtClassLoader、AppClassLoader。

他们每一个类加载器负责加载特定位置的类。其中BootStrap是一个用C语言写出的类加载器,是根加载器;

其他的类加载器都是通过Java语言编写的,这些类加载器也是一个个的Java类;

黑马程序员--Java基础加强篇

  上图展示了类加载器之间的结构关系图,以及每个类加载器所加载的class文件的目录和范围;

类加载器加载class文件时有一个委托加载机制:

       每个类加载器加载类时,又先委托给其上级类加载器。

       比如我们自己定义了一个Java类,当调用这个Java类时,先由AppClassLoader发起加载,但是他不会马

上就去classpath指定的目录中去加载,而是请求他的上一级加载器ExtClassLoader,但是ExtClassLoader也

不会马上就在他管辖的目录中去加载,而是把加载的请求再向上一级加载器传递,此时传递到了BootStrap加载

器,而BootStrap加载器没有上一级的加载器了,所以他开始在他管辖的目录中加载,没有加载到,那么就由下

一级加载器ExtClassLoader来加载,如果ExtClassLoader也没有加载到,才由AppClassLoader来加载;

       所以如果我们定义了一个类,而这个类(比如叫做java.lang.System的类),由于System这个类会被

BootStrap类加载器加载,所以虚拟机会将Java中原来的那个System类加载了,所以不会由AppClassLoader

来加载我们自己定义的这个System类!

------------------------------------------------------------------------------

自定义类加载器的原理:

      首先需要继承ClassLoader这个抽象类;

      继承了这个类以后我们就得到了下面三个核心方法:

loadClass():主要用于实现委托加载机制;

findClass():用于找到要加载的类,但是通常我们会将这个方法覆盖了,因为父类定义

                的查找类的方法不一定使用用所有的子类,所以需要我们去自己定义;

defineClass():这个方法的作用就是将找到的类变成二进制的字节码数据;

我们自己定义一个类加载器后可以通过:

      Class clazz = new MyClassLoader().loadClass(“类名”); 来自己加载类!

-------------------------------------------------------------------------------

代理:

      生活中中的代理:比如我现在想要到买一台联想笔记本电脑,而联想的总部在北京,如果我亲自到北京

去买,除了笔记本的费用外,我还需要来回的车费,住宿,吃喝用度等!最后除了笔记本的费用外还多掏了

500多的额外费用,并且费时费力!。但是如果我在本地找到一家联想的代理店,那么这些额外的费用都可

以省下来了,顶多让代理商赚取一部分的利润!(当然有了网购,一切都不是问题了!)

      程序中的代理:  当我们需要调用一个类上的方法,当时这个方法又不能完全满足我们的所需,我们需要

对这个类进行继承和复写。但是如果我们需要调用的类很多,而且都需要进行增强,我们总不可能一个一个

的去继承和复习复写吧?这样太麻烦了!所以Java中提供了动态代理的机制,可以很方便的对需要增强的

类进行修饰和加强;

代理模式图:

黑马程序员--Java基础加强篇


Java中的代理分为静态代理和动态代理:

静态代理:就是将要代理的目标类直接写死,这样一来这个代理类就只能对绑定的目标

                   类进行增强了---所以应用面很狭窄;

动态代理:他是将目标类变为待定的,传入什么类,就代理什么类。当然这是有限制的,

那就是传入的类必须是代理类所实现的接口的实现子类;

我们可以这样理解:

       小时候没有电脑和打印机,学校每年开学典礼时发的奖状都是靠人手写的,几十个班的上百张奖状,每年

都是那个字写的好的老师的灾难;后来随着电脑和打印机的普及,奖状不再是完全靠老师手写,而是有一个

模板格式:

         “xxx 同学在20xx-20xx年度,获得xxx称号,特发此奖,以资鼓励!”

这里的xxx就是空出来的内容,等待老师手写的,但是相较于以前完全手写已经大大的减轻了工作的强度了!

这里通过电脑和打印机打印的奖状就是空白奖状的代理,他在空白奖状的基础上提供了一些新的内容,让使

用者更方便了!---这就是代理的好处!

       另外我们知道这个奖状分个人荣誉奖状和班级集体荣誉奖状。一般来说班级荣誉奖状要大些,而个人的

奖状要小些。我们不能在班级荣誉奖状上写:xxx同学……;也就是说虽然这个代理奖状可以添加待定的任意

信息,但是这个信息是有限制的,那就是奖状的类型;

       我们定义的动态代理类也是如此;他是通过实现的接口来限定后期我们输入的待定信息的。比如我们定

义了一个动态代理类,他实现了一个collection接口,那么他代理的就只能是Collection接口的实现子类;我

们只能传入ArrayList,hashSet等类的实例对象,而不能是map集合体系的成员;每个代理商有自己的代理

范围,就像我们不能跑到特步的专卖店里去买联想的电脑一样;

         有一点需要注意的是:一个代理类他可以实现多个接口,以增强其代理范围,就像我们有可能在特步的

专卖店中看到人家卖的还有李宁的衣服,因为人家同时代理了特步和李宁!

动态代理技术:

JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。但是必须给

他指定一个类加载器,因为所有的类都有类加载器,虽然他是通过虚拟机在内存中自动生成的,但是也应该给

他指定一个类加载器!那么到底指定那个类加载器呢?通常给他指定他所实现的接口的类加载器(这只是一种

通用的做法!)

JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理,

原因就是上面举得例子的原理;通过接口来明确其代理范围!

       补充一点:CGLIB库可以动态生成一个类的子类,一个类的子类也可用作该类的代理。所以如果要为一个

没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的

如下四个位置加上系统功能代码:

       1.在调用目标方法之前;

       2.在调用目标方法之后;

       3.在调用目标方法前后;

       4.在处理目标方法异常的catch块中;

创建代理类的步骤:

       第一步:现在内存中生成代理类的字节码文件:

                       Class  clazzProxy=Proxy.getProxyClass(Collection.getClassLoader(),Collection.class)  

                                //通过Proxy类的静态方法指定类加载器和需要实现的接口来在内存中生成字节码文件;

       第二步:通过字节码文件来创建代理类的实例对象:

Collection proxy1 =(Collection)clazzProxy.getClass().getConstuctor().newInStance(

                        new InvocationHandler(){

                                   public  Object invoke  throws  Exception(

                                          Object  proxy ,   //要代理的目标类;

                                          Method  method , //要代理的目标类中方法

                                          Object[]  args){   //方法中要传入的参数;

                                          Object  retVal = method .invoke(target , args);

                                          return  retVal;

                                   }

                        });

 

Java 还提供了将这两步合成一步走的方法:

Collection proxy2=(Collection)Proxy.newInstance(

                     Collection.class.getClassLoader,

                     new  class[]{Collection.class },

                     new  InvocationHandler(){

                            public  Object invoke  throws  Exception (

                                   Object  proxy ,   //要代理的目标类;

                                   Method  method , //要代理的目标类中方法

                                   Object[]  args){   //方法中要传入的参数;

                                          Object  retVal = method .invoke(target , args);

                                          return  retVal;

                                   }

                     });

 

       当我们创建一个代理类后,就可以看接口,来使用具体的方法了,比如我们实现了一个Collection接口,

就可以调用add()了!但是在这之前我们还必须明确代理目标是谁:

      ArrayList target = new ArrayList();

      proxy2. add(“hcc”);

              这时proxy2就会委托自己内部的InvocationHandler中的invoke(),来调用target上面的add(),

传入的参数是“hcc”,这样就可以通过代理来完成方法的执行了;

注意一点:proxy从Object中继承过来的11个方法中,只有toString()、HashCode()和equals()这三个方法在

                   调用时会被委托到Handler去处理,其他的方法是由自己来处理的!