黑马程序员——><基础加强一(eclipse-静态导入-享元设计模式-自动拆装箱-枚举-反射-类加载器)>

-------android培训java培训、期待与您交流-------

01课程价值与目标介绍
    这几个课程的学习是因为根据学员的反馈信息知道这个jdk1.5的新特性在面试中很重要,毕竟我们是为了工作(至少目前阶段是这样)而学习java,要跟着市场方向走!因此 学习这些知识点很重要!

02eclipse及IDE开发工具介绍

eclipse 是运行在java基础上的程序

myeclipse是eclipse的插件  先安装上eclipse  然后为eclipse打个补丁 就扩展了eclipse的功能,让它可以开发java意义的程序(web项目)

Java EE(Java Platform,Enterprise Edition)是sun公司推出的企业级应用程序版本。这个版本以前称为 J2EE。能够为我们帮助开发和部署可移植、健壮、可伸缩且安全的服务器端 Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web 2.0 应用程序。

 IDE(Integrated Development Environment,集成开发环境)。其缩写形式IDE同时也代指“电子集成驱动器”。IDE集成开发环境(简称IDE)软件是用于程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面工具。该程序可以独立运行,也可以和其它程序并用。

jms即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。

JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API。命名服务将名称和对象联系起来,使得我们可以用名称访问对象。目录服务是一种命名服务,在这种服务里,对象不但有名称,还有属性。

03eclipse工程管理与快捷键配置

顶级的目录就是工程,  打开工程后 里面会有一个以包名分级展现的java源文件  以及这个工程要用到的第三方类

window  preferences(首选项)就是你的喜好  java  compiler 编译器(按哪个版本进行编译) 

  这里有两个设置  做开发时有两个阶段,一个是编译时阶段,一个是运行时阶段  要保证编译时和运行时用的版本一样,否则会报错

配置快捷键  General->keys,设置alt+/键进行内容提示时,要注意解除alt+/键原来的绑定关系,直接输入alt+/就可以找到它的绑定关系,删除绑定关系时也可以使用remove binding这个按钮

一个工作间  包含多个工程   一个工作间的设置,影响这个工作间的所有工程

新建工作间:
 file  switch workspace   other  进行工作间切换

当当前状态下没有你要找的工作间,这时就可以重新建立一个新的工作间,会在硬盘上自动创建工作间
 因为它在创建工作间时,要把原来的工作间关掉,要用eclipse 重新打开新的工作间,加载这个工作间特有的配置信息,所以花的时间要长一点。

这时就可以在工作间创建工程了。  file new  project 在弹出的对话框中选择 java project(普通的java工程)
refactor 重构包名   右键点击包名

工程的名字  尽量用英语  
要养成良好的编程习惯  都要有包名 前面是公司的网址,域名  倒过来写   去哪个公司面试就写 哪个公司

工程 包名  类名  变量名  等等  最好都用英语   逼着自己将英语学好
 面试时主考官 不会去看你的代码,你的算法   人家就看你的整体效果,

04eclipse试图管理与程序调试

怎么调试一个变量 
在编辑区域内单击右键   选中 debug  as   所谓不同的透视图就是指有不同的若干个小窗口的集合

在不同的应用情景下 要调用一些不同的小窗口   只要切换到不同的窗口即可
若某个窗口没有找到  就  window  show view去进行查找

对这些视图要会分类会揣测

05配置eclipse的编译与运行环境

设置单个工程的javac与java。
 高版本的java能否运行低版本的javac编译的程序?  能
 低版本的java能否运行高版本的javac编译的程序?  不能

工作台和工程 类似于java中的继承与覆盖的关系(工作台上面的工程,所用的配置都是工作台的配置,但是它也可以单独进行配置,覆盖掉工作台原有的配置)

06在eclipse中导入已有的工程

首先将希望要导入的工程放到工作台下面来  这个工作台下面就多了一个工程,但是eclipse就没有发现你放进来的那个工程 这时就需要将工程进行导入操作 

file  import  general   existing projects into workspace 

这个存在的工程要选择其存在的根目录  就导入了  这时要注意你导入工程jdk的安装目录  jar包是否一样    若不一样  则  

右键点击工程 build path    configure build path   在第三个  libraries中将jdk这个库删除掉  然后增加你自己的库  在这里进行增加时   要选择  add library  不要选add  jar  因为这个是增加一个单独的jar包,对于你工程内部的jar包(你将某个jar包拷贝到你的工程中来了,就选add jar)

07java5中的静态导入与编译器语法设置

import语句可以导入一个类或某个包中的所有类
import static语句导入一个类中的某个静态方法或所有静态方法
语法举例:
import static java.lang.Math.sin;
import static java.lang.Math .*;  

import static java.lang.Math.max;          
以前是导入一个类  现在 导入的是类下的一个方法    这时调用该方法时就可以省略类名了

08可变参数与OverLoad相关面试题分析

可变参数的特点:
只能出现在参数列表的最后;    (int x,int...args)  这样可以
...位于变量类型和变量名之间,前后有无空格都可以;        (int x, int...args,int y) 这样不对
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数

public static int add(int x, int... args)
{
 int sum = x;
 for(int i=0;i<args.length;i++)
 {
  sum += args[i];
 }
 return sum;
}

面试题:
overload vs override的区别      重载       重写

overload  两个参数的个数 列表 类型完全一样  只有返回值不一样 不行  还要说出为什么不行
override  如果父类的方法是私有的,子类写一个方法跟它一样  是不是叫重写  是不是叫覆盖  不对  这等于是一个全新的方法
不仅仅说两个的区别   要有一些很深入的东西

09java5的增强for循环

语法:
for ( type 变量名:集合变量名 )  { … }
用变量逐一去取集合中的元素,取出来后就执行循环体

注意事项:
迭代变量必须在( )中定义!
集合变量可以是数组或实现了Iterable接口的集合类
举例:

 public static int add(int x,int ...args) {
  int sum = x;

  for(int arg:args) {  // 变量的名字叫arg   迭代的集合叫args
   sum += arg;
  }
  return sum;
 }

10基本数据的自动拆装箱及享元设计模式

 基本数据类型的自动拆箱与装箱

自动装箱:
Integer num1 = 12;     自动将一个基本数据  封装成一个Integer对象, 再赋值给引用变量
自动拆箱:
System.out.println(num1 + 12);  将num1转换成基本数据类型再进行加法   这个过程叫做拆箱

Integer i1 = 13;  变成137  又不是同一个了         因为大的数据使用频率比较低 就没有用享元模式
Integer i2 = 13;
System.out.println(i1 == i2);

基本数据类型的对象缓存:
Integer num1 = 12;
Integer num2 = 12;
System.out.println(num1 == num2);

Integer num3 = 129;
Integer num4 = 129;
System.out.println(num3 == num4);  数值超过后就不相同了
他们装出来的Integer对象是否是同一个 
若在-128~127之间的数字  一旦将其封装成integer对象,就会将其缓存起来,当下次又要将一个整数封装成integer对象时,先看缓存池里有没有   有的话直接拿,这样就节省了内存空间,因为这些比较小的数使用频率会很高,反复的出现,一出现就把它装成一个对象,到最后对象会越来越多;这个对象本来就是不怎么改变的,对象又很小,大家又都是用它,又不去改变它的数据,没必要每一个数字对应一个对象,

这是一种设计模式      这叫享元模式  flyweight
 当这个数据对象很小,又有很多个地方要整出很多个这样的对象来,但是搞出来的对象在用的过程中永远都是一模一样,那就搞一个就可以了。

 Integer num5 = Integer.valueOf(12); 
Integer num6 = Integer.valueOf(12);
System.out.println(num5 == num6);

Integer.valueOf(12);这个方法可以将一个整数变成integer  这个不是自动装箱,而是我手动调用静态方法,把一个基本类型的整数变成一个包装类型的对象,

12枚举的作用介绍  enum

从1.5开始,将从c语言去掉的东西又给加上了

  枚举的权重比较大 

为什么要有枚举
问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。

 枚举的高级应用

枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。

枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。

带构造方法的枚举
  构造方法必须定义成私有的
  如果有多个构造方法,该如何选择哪个构造方法?
  枚举元素MON和MON()的效果一样,都是调用默认的构造方法。

带方法的枚举
  定义枚举TrafficLamp
  实现普通的next方法
  实现抽象的next方法:每个元素分别是由枚举类的子类来生成的实例对象,这些子类采用类似内部类的方式  进行定义。
  增加上表示时间的构造方法

枚举只有一个成员时,就可以作为一种单例的实现方式。

13用普通类模拟枚举的实现原理

定义一个新的类型,按这个类型定义的变量,指向的值只能是固定变量的值

public class WeekDay
{
 private WeekDay()();//首先将构造方法创建成私有的 别人不能创建对象
 public final static WeekDay SUN = new WeekDay();//定义一个静态变量  类型还是WeekDay 变量的名字 由于属于一种常量 final修饰,叫SUN  值为 new WeekDay();写出来七个就行了,  别人用时只能调用这个类的SUN常量
 WeekDay weekDay = WeekDay.MON;         //MON是一个常量,一个对象,是 WeekDay这个类型的对象,  所以 可以将它赋值赋给这个类型的引用变量weekDay  如果你要想给这个引用变量赋值,只能赋我在这个类里面定义好的那些常量
                                                                 //不可以为这个常量指定我规定好的值以外的其他值
public WeekDay nextDay() {           //这个方法不能是静态了,  这个是SUN对象身上的方法,
 if(this==SUN){                            //假如当前的对象=SUN这个引用变量指向的对象
  
  return MON;  
}
 else{
  return SUN;
}
}

public String toString() {                              //  打印枚举对象
 return this == SUN?"SUN":"MON" ;
}

每一个枚举元素都是一个对象
可以有若干公有方法或抽象方法。采用抽象方法定义nextDay就将大量的if.else语句转移成了一个个独立的类

14java5的枚举的基本应用

public static void main(String[] atgs){
 WeekDay weekDay2 = WeekDay.FRI;  //用枚举定义变量 只能定义给定的元素  FRI相当于一个对象    枚举是一个类  枚举中的元素 就相当于这个类搞出来的实际对象
 System.out.println(weekDay2);                           //自动实现了toString方法
 System.out.println(weekDay2.name());                   // 和上一句打印结果相同
 System.out.println(weekDay2.ordinal());               //自己的排行数列 从0开始
 System.out.println(weekDay.valueOf("SUN").toString());//把字符串变成对应的那个枚举元素
 System.out.println(weekDay.values().length);          //返回一个数组 将枚举中的元素一个个装进数组里的子类的实例对象,子类的构造方法内部要调用父类的构造方法,调用父类的有参数的构造方法

 new Data(3000){}; //newData                      
}

 

public enum WeekDay{                      //基本的枚举类   最简单
 SUN,MON,TUE,WED,THU,FRI,SAT
}

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

 //定义带有参数的构造方法
public enum WeekDay{  

 SUN(1),MON(),TUE,WED,THU,FRI,SAT;//元素列表必须位于所有成分之前,  构造方法要放在列表之后
                                          //元素列表之后若有内容 则需要加;
 private WeekDay()(System.out.println("first");)  //构造方法必须是私有的  空参数构造方法

 private WeekDay(int day)(System.out.println("second");) // 接收参数的构造方法 使其调用某个元素
}


只要用到了枚举类,里边的静态变量都会被初始化,  执行  就会调用构造方法   所以会执行结果 first

只要用到了类  里边的静态代码都会执行,

在枚举元素后面跟上括号,就表示创建这个元素指向实例对象的时候使用哪个构造方法

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

public enum TrafficLamp{        //都是内部类  交通灯
 RED(30){                // RED是由TrafficLamp的一个子类来写的  必须要完成父类的抽象方法
  public TrafficLamp nextLamp(){
   return GREEN;
  }
 },                       // 这里就相当于  new子类的实例对象,并且调用父类有参数的构造方法实例对象new完后,用RED引用名称去引用对象
 GREEN(45){
  public TrafficLamp nextLamp(){
   return YELLOW;
  }
 },
 YELLOW(5){
  public TrafficLamp nextLamp(){
   return RED;
  }
 };
 public abstract TrafficLamp nextLamp();  //这个方法的返回值还是这个类
 private int time;
 private TrafficLamp(int time){this.time = time;}  //把别人传过来的值给我自己的成员变量
} 

每个元素是这个类的实例对象,这个实例对象不能直接拿到这个类去new,只能拿这个类的子类去new,new有个对象,并且对象的名字叫RED。

先定义三个元素  每个元素后面跟着一堆大括号   然后在大括号中写子类的实现代码
枚举   顺带复习内部类  运用到内部类的很多知识
提高一种编程思想
返回方法类型 可以是自己本身的类型
类里边可以定义静态常量  常量指向的结果就是自己这个类型的实例对象

枚举只有一个成员时,就可以作为一种单例的实现方式。

如果要写单例,可以定义一个枚举,并且这个枚举里只有一个元素,因为枚举就是一个特殊的类, 为什么要用枚举来写单例
 构造方法不用自己写,外部类是私有的,不带参数的  元素是自然的就new出来,

17透彻分析反射的基础-Class类

反射的基石Class类

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class

如何得到各个字节码对应的实例对象( Class类型)
类名.class            例如,System.class
对象.getClass()       例如,new Date().getClass()
Class.forName("类名") 例如,Class.forName("java.util.Date");

实例:

public static void main(String[] args)throws Exception
{
 String str1 = "abc";
 Class cls1 = str1.getClass();   //得到字符串对象的字节码 
 Class cls2 = String.class;      //这个还是String的字节码
 Class cls3 = Class.forName("java.lang.String");  
 System.out.println(cls1 == cls2); true
 System.out.println(cls1 == cls3); true
//一份字节码能写出多个实例对象           这三个的字节码都一样

 System.out.println(cls1.isPrimitive());  //是否是 基本(原始)类型   false
 System.out.println(int.class.isPrimitive());          true
 System.out.println(int.class == Integer.class);//基本类型字节码 和包装类字节码是否一样  false
 System.out.println(int.class == Integer.TYPE);// 包装类所代表的基本类型字节码  true
 System.out.println(int[].class.isPrimitive()); // false
 System.out.println(int[].class.isArray());  //判断class是否是数组   true
}

任何类型都能用class来表示

18理解反射的概念

反射就是把Java类中的各种成分映射成相应的java类。

clazz3 = Class.forName("java.util.Date");   //Class类中的静态方法,指定一个类的完整的名称
                                           //这个也是得到这个类的字节码

面试题
Class.forName的作用            

返回字节码  分两种情况
 这个类的字节码已经加载进内存了,现在想要得到它的字节码,直接找到那份字节码将其返回,
 虚拟机中还没有这个类的字节码,用类加载器去加载,加载进来以后,就把那份字节码给缓存起来,同时这个方法返回这个方法刚才加载进来的那份字节码

得到字节码的方式由三种:

1.类名.class,例如,System.class  

在源程序中直接写上类的名字,然后.class这时固定写法  没有为什么  编译器一看这样就认定时要得到这个类的字节码
2.对象.getClass(),例如,new Date().getClass()
有了一个对象,对象一定是一个字节码创建出来的  得到创建那个对象的字节码  这个字节码也就是我们通常说的类

3.Class.forName("类名"),例如,Class.forName("java.util.Date");

查询或者加载这个字符串所对应的类的字节码  做反射时  主要用第三种  也就是这一种(因为在写源程序时还不知道类的名字,在运行时  别人传给我一个字符串,这个字符串包含了这个类的名字,)  可以将 java.util.Date换成一个字符串类型变量  等程勋运行起来后,这个变量的值从一个配置文件里面装载进来,  这个是在写程序时不知道类的名字   而在运行程序时由你传进来

19构造方法的反射应用

Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();

得到某一个构造方法:
例子:      Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);//获得方法时要用到类型

创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式:
String str = (String)constructor.newInstance(new StringBuffer("abc"));

 //调用获得的方法时要用到上面相同类型的实例对象

Constructor constructor1 = String.class.getConstructor(StringBuffer.class);

String类的字节码
编译  就是将其翻译成class文件,接着才去运行,运行的时候才执行=后面的代码 (也就是说在编译时不知道是谁的constructor )  才把代码执行的结果给 constructor1   在翻译的时候严格进行语法检查 没有执行=右边的代码  只是说生成一行二进制代码放在那里,下次运行的时候再来执行这行代码

Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
用这个构造方法的时候,还在传一个StringBuffer对象进去
constructor1.newInstance(new StringBuffer("abc"));拿字节码搞出来的对象

String str2 = constructor1.newInstance(new StringBuffer("abc"));
在编写源程序时 对方只知道这个是constructor 而不知道是具体哪个类的constructor  只知道是个构造方法  只有在程序运行时 才执行这行代码  才知道是String 在编写时是不知道的  创建出来的构造方法对方不知道是String 就要对其进行类型强制转换
String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));
这句话就是在跟编译器说   这个构造方法返回值是String  (编译器只看变量的定义,不看代码的执行)

要牢固的树立一个思想:程序分编译时  运行时     要清楚错误是出在哪个阶段

得到一个构造方法,再用构造方法newinstance   这就是反射
//需要注意两点:得到方法时需要类型  StringBuffer
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));

//在调用这个方法时,也需要对其传递同样类型的对象new StringBuffer("abc")

反射会导致程序性能下降

20成员变量的反射

Field类代表某个类中的一个成员变量
演示用eclipse自动生成Java类的构造方法
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。
为了来演示这个成员变量  要自己定义一个类,这个类里边写两个成员变量,

public class RefiectPoint{
 private int x;
 public int y;

 public RefiectPoint(int x,int y){
  super();
  this.x = x;
  this.y = y;
}
} 有了这个类后对其进行反射

RefiectPoint pt1 = new RefiectPoint(3,5);
Field fieldY= pt1.getClass().getField("y"); // 得到一个字段  先要得到这个类的字节码,字节码中就有成员变量的信息字节啊就可以得到某个字段   

//fieldY只是代表这个类字节码生成的变量    用pt1可以搞好多对象,每个对象身上都有一个fieldY  在pt1上面是5   在其他上面就不知道是多少了  fieldY不代表某个变量身上具体的值,
                                            //field Y的值是多少?是5,错! field y不是对象身上的变量,而是类上,要用它去获取某个对象上对应的值
System.out.println(fieldY.get(pt1));  //取出变量在某个对象身上的值  因为它代表是类上面的变量不是                                       //对象身上的
Field fieldX = pt1.getClass().getDeclaredField("x");// 只要是里边声明过的,      getField方法只能                                                    //看到可见的
fieldX.setAccessible(true);                //设置可以访问
System.out.println(fieldX.get(pt1));
                                            //这就叫暴力反射

Field类的作用:代表字节码里边的一个变量   不代表某个对象身上的变量  我们在知道有y这个变量后,在各个对象上去取对象身上那个y

射的作用   换掉一个对象里的字段  可以改变对象的值, 你在配置文件里配置了很多东西,我自动扫描你的文件,将你一写内置文件的配置换掉

21成员方法的反射

Method类代表某个类中的一个成员方法   不是一个对象身上的方法
得到类中的某一个方法:
例子:      Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)  可变参数
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,想要传多个参数时,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。  这个数组长度为1  里头装一个integer对象 值为1

 Method类代表某个类中的一个成员方法   不是一个对象身上的方法  可以拿着这个方法去调用类中的对象  方法和对象时没有关系的  我们在调用这个方法时必须通过某个对象去调用,你得到了方法  这个方法和对象没有关系   而紧跟着调用某个对象身上的方法      先得到方法,然后再针对某个对象去调用这个方法

//str1.charAt(1);  现在想干的事是 调用str1身上的charAt(1)方法
Method methodcharAt = String.class.getMethod("charAt",int.class)

这个方法是从String字节码身上拿出来的,getMethod这个方法接收两个参数,第一个参数是方法名字,第二个参数是(一个类里面同名字的方法,有多种重载形式,你到底想从这个多个重载方法当中选取哪一个      你平时写程序时靠什么来识别的,靠参数的列表,列表就包括了列数和类型           这里每一个参数类型都对应一个class来表示, 到底有多少个参数就看你在此处写多少个class          只有一个参数  int)
  用反射进行调用

System.out.println(methodCharAt.invoke(str1,1))
调用一个方法一定是在某个对象身上   第一个参数就是那个字符串对象,在这个字符串对象身上调用CharAt  第二个是  你调用charAt 方法的时候,要传一个参数进去,  得到的里边有几个参数  这里也得有几个参数int.class

这就是由反射的方式得到这个字节码当中的方法,再拿这个方法作用于某个对象,invoke表示调用  调用这个方法,  invoke这个方法执行调用的动作,
  invoke是这个方法对象身上的方法                方法的调用
  画圆 这个方法   画那个动作在圆身上
  人在黑板上画圆,三个对象,人 黑板 圆   画圆时需要哪些参数 信息   需要圆心和半径,圆心和半径是圆身上的,那画圆这个方法要用到圆心和半径, 圆心和半径是私有的,如果你将画圆这个方法分配给了人,通过人要去访问圆里边的私有方法,不合适,通过黑板也不合适,只有分配给圆,圆有一个动作叫画我,  画是我的动作,我才知道怎么画,

所以你调用的时候会感到别扭,circle.draw();这时给圆发信号 把自己画出来,你自己知道怎么画  我不知道

 列车司机把列车给杀住了, 想一想  列车司机有这么大的功力么?他只是踩了离合器,实际上实在给列车发信号,你停车吧!你的离合器系统就开始运作,你的发动机熄火,  所以是列车停车  司机给列车发信号,这个停车的动作只有列车才能干地了  所以画圆的动作只有圆能做了  
  比如人关门   谁把门关上了,门   你只是推了一下   不是你关的 是它自己旋转 给关上的

 面向对象其实非常简单:只要把变量搞成私有的,如果谁要操作这个变量,那么这个变量在谁身上,方法就应该在谁身上。这叫专家模式  谁拥有数据,谁就是干这个的专家, 那么这个方法就应该分配给谁 
  所以你看到的这个invoke是这个methodCharAt方法的invoke     但是调用invoke的时候是str1对象身上调用  一调用还要给参数

invoke(null,1)  若接收的方法为null  这时此方法是静态的  或者说  又一个静态方法 你想调用,  在这里写上null  因为静态方法调用时不需要对象,  不需要对象肯定是静态方法,

22接收数组参数的成员方法进行反射

 目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?
问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

我给你的数组,你不会当作参数,而是把其中的内容当作参数。
Class clazz = Class.forName(arg[0]);
Method mMain = clazz.getMethod("main", String[].class);
mMain.invoke(null,new Object[]{new String[]{"aaa","bbb"}});
mMain.invoke(null,(Object)new String[]{"aaa","bbb"});

}做一个程序 调用这个main方法
平时  TestArguments.main(new String[]{"111","222","333"});  还可以传参数
 这就是在程序里面用静态代码的方式直接调用它的main方法
直接调用 TestArguments类的main方法,  newString类型的数组就搞定了

首先明白一个问题  为什么要用反射的 方式调用main方法
 我并不知道要调用的那个类的名字, 你在执行这个main方法时同样给那个main方法中传进来一些参数,  而这个方法中就带有说  你去启动哪个类,假设里边第一个参数就是那个类的名字, 你告诉我  要去执行哪个类,我知道args[0]就是要启动的那个String className=args[0]; 类名就是String startingclassName=args[0];即将启动的className  那么我的源程序里边就没有出现到底是哪个类名,你通过一个参数给我说执行a这个类,我就执行a这个类,你在运行时说执行b这个类,我就执行b这个类,我的源程序中根本就不知道要执行哪个类,接下来你把一个类给我了,我知道这个类里面一定有一个方法
Method mainMethod = Class.forName(startingClassName).getMethod(name,parameterType)   ("main",String[].class)
               接收一个参数  并且是一个String类型的参数

接下来就要去调用main方法
 mainMethod.invoke(null,new String[]{"111","222","333"}); 静态的不需要传递对象,不通过对象就调用这个方法  要传递上面的参数    这样会报错   数组角标空指针异常

jdk1.5为了兼容jdk1.4,  当你给它一个字符串数组,(这就相当于object数组)。当收到一个object数组后不会将其当成一个参数,会将数组以包的形式打开,将打开的东西中的每一个元素分别作为一个参数(以数组的角度看就是一个参数,若将其看成一包东西就是好多个参数)
mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
这时再打一个包,new一个数组  这个数组里面装的第一个元素就是一个 数组   每个数组的父类都是object  就是每个数组都是object  是一个对象  现在给你一包东西  一打开 是一个数组

就是说 会将你拆开  这时就先把整个数组装成一个包   你拆包  露出来的就是数组

mainMethod.invoke(null,(Object)new String[]{"111","222","333"});黎老师方法

因为数组也是一个object  将其强制转换成object  (Object)这样写就相当于在和编译器说  我给你的是一个对象不是一个数组  别拆包

说明类方法如何调用:因为我们要传一个数组,而数组在jdk1.4中被当做多个参数,我们想办法让其不让当做多个参数,
一种是 把数组打包成另外一个数组  你拆完一层皮后剩下来的就是那个数组   还有一种就是直接类型转换  还是当成object不要拆

23数组与Object的关系及其反射类型

具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。
Array工具类用于完成对数组的反射操作。

数组也是一种类型,由jdk帮助文档能够知道数组的类型  每一个数组都属于同一个class 如果你用反射,反射出来的字节码都是同一个(具有相同的元素类型以及具有相同的维度),

int[] a1 = new int[3];     //有一个数组 里面装的是int  int不是object   基本类型不是object
int[] a2 = new int[4];
int[] a3 = new int[2][3];
String [] a4 = new String[3];  //有一个数组,里面装的是String,String也是object

//这是对象   对象要得到字节码  要用方法  
System.out.println(a1.getclass() == a2.getclass());//具有相同的维数  都是一维数组  且具有相同的类型  true
System.out.println(a1.getclass() == a4.getclass());//不等于   false 
                             
System.out.println(a1.getclass() == a3.getclass());//不等于     false                                
System.out.println(a1.getClass().getName());     //打印类名  [I  数组  整数
System.out.println(a1.getClass().getSuperclass().getName());  //打印父类的名字
System.out.println(a4.getClass().getSuperclass().getName());

 

Object aObj1 = a1;
Object aObj2 = a4;
Object[] aObj3 = a1;   //基本类型的一维数组不能转换成object数组的  因为Object[]里边装的是int  不是obj
//若是二维的  Object[]这个数组里装的是一维数组  一维数组是object       

Object[] aObj4 = a3; //a3是一个数组的数组,假设将其当做一个数组,=左边的object[]就等于第二维的那个数组  把a3 当做一个数组来看待  它里边装的就是int类型的一维数组  而这个int类型的一维数组就是object类   这表示一个数组,数组里边装的是object  也可以将其理解成   有一个数组,数组里头装的是int类型的数组,就等效于有一个数组,数组里边装的是object
Object[] aObj5 = a4;       //这个数组里边装的是String  String也是Object

int[] a1 = new int[]{1,2,3};     
int[] a2 = new int[4];
int[] a3 = new int[2][3];
String [] a4 = new String[]{"a","b","c"};

System.out.println(a1);  //[I@1cfb549  int类型数组的哈希code值
System.out.println(a4);  // [ljava.lang.String;@186d4c1       String类型的数组的哈希code值


arrays类  对数组进行操作的类
 转换成list对象   aslist

System.out.println(Arrays.asList(a1));//[[I@1cfb549]
System.out.println(Arrays.asList(a4)); // a b c  字符串成功的转换成了List对象 数组等效于多个元素

asList
public static List asList{object[] a}
  后面结束的是object[]  如果是一个String类型的数组传进来,就按jdk1.4编译成List   如果传的是int类型,人家觉得跟这个对象对不上  就让你回1.5去处理  
public static <T> List<T> asList(T...a)  不当做object数组类型 只当做一个object  就等效于一个参数  刚才的String类型的数组,符合1.4的  就把String数组里边的每一个元素列举出来放到list中去  如果你是int类型的数组, 不符合就按1.5的走,当成一个object   一个参数

24ArrayList_HashSet的比较及Hashcode分析

public static void main(String[] args){
 Collection collectons = new HashSet();   //  使用HashSet当容器
 ReflectPoint pt1 = new ReflectPoint(3,3);
 ReflectPoint pt2 = new ReflectPoint(5,5);
 ReflectPoint pt3 = new ReflectPoint(3,3);

 collections.add(pt1);
 collections.add(pt2);
 collections.add(pt3);
 collections.add(pt1);

 System.out.println(collections.size());  // 3
}


public static void main(String[] args){
 Collection collectons = new ArrayList(); // 使用ArrayList当容器
 ReflectPoint pt1 = new ReflectPoint(3,3);
 ReflectPoint pt2 = new ReflectPoint(5,5);
 ReflectPoint pt3 = new ReflectPoint(3,3);

 collections.add(pt1);
 collections.add(pt2);
 collections.add(pt3);
 collections.add(pt1);
 System.out.println(collections.size());  // 4
}

collections.add(new ReflectPoint(3,3)); // 也可以定义称为独立的变量  向上面一样

上面只是因为使用容器不同造成打印结果的集合长度不同
ArrayList是一种有顺序的集合 就相当于一种数组,当你要放一个对象要进来的时候,它首先找到第一个空位置放进去,不是真正的把对象放进去了 而是将对象的引用在数组中记住了  当你再放一个对象进去时   它会按顺序找到第二个位置  当你放第三个时   第三个与第一个相同  还放的进去,就相当于  每放一下里边就多一个引用变量,好多个引用变量 引用的是同一个对象  没关系  它是按照先后顺序依次放进去,这样就是有顺序的  你可以明确的说从第几个取,有位置顺序  甚至可以插队   这个顺序不是比较顺序(排序),而是指位置顺序  每放一个   每一个都能放进去

 HashSet();  你要放进去的时候,先判断里面有没有这个对象,就是比较两个对象是否相等,如果一旦有了 就不放;如果你要想放一个对象覆盖掉原来的对象(你必须要把原来的删除掉remove)再把新的给插进去

 如果想要让pt1和pt3相等 必须要去写equals方法  否则  默认 equals比较的是hashcode的值  通常是由内存地址换算出来的
这是两个独立的对象,==肯定不等,equals我们没有覆盖  所以也不等
面试题
hashCode方法的作用  如果想查找一个集合中是否包含有某个对象,大概的程序代码怎么写呢?你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则,返回否定的信息。如果一个集合中有很多歌元素,譬如一万个元素,并且没有包含要查找的对象时,则意味着你的程序需要从该集合中取出一万个元素进行逐一比较才能得到结论。有人发明了一种哈希算法来提高从集合中查找元素的效果,这种方式将集合分成若干个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域 

 只有说文件的存储集合必须存储在这种hash集合当中才有这种价值

HashSet就是采用哈希算法存取对象的集合,它内部采用对某个数字n进行取余的方式对哈希吗进行分组和划分对象的存储区域。Object类中共蒂尼了一个hashCode()方法来返回每个java对象的哈希吗。当从HashSet集合中查找某个对象时,java系统首先调用对象的hashCode()方法获得该对象的哈希码,然后根据哈希吗找到相应的存储区域,最后取出该存储区域内的每个元素与该对象进行equals方法比较,这样不用遍历集合中的所有元素就可以得到结论,可见,HashSet集合具有很好的对象检索性能,但是,HashSet集合存储对象的效率相对要低些,因为向hashset集合中添加一个对象时,要先计算出对象的哈希吗和根据这个哈希吗确定对象在集合中的存放位置

没有实现hashcode算法就=3
因为如果  这两个对象比较equals比较相等了  但是你算出来的hashcode值是按照你的内存地址算的  这两个本来该认为相同的对象,分别被存放到了不同的区域,当我要去找这个对象的时候,我在我这个区域里面找,不在那个区域里边找,那个区域确实存在一个跟我相等的对象,但是我不去那个区域里边找,我就被放进数组中去了。
为了让相等的对象也放在相同的区域,所以人家就有一个说法  如果两个对象equals相等的话,应该按照他们的hashcode也相等,如果对象不存到hash集合里边,就没必要搞hashcode了

当一个对象被存储进hashset集合中以后,就不能够修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进hashset集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去hashset集合中检索对象,也将返回找不到对象的结果,这也会导致无法从hashset集合中单独删除当前对象,从而造成内存泄露

public static void main(String[] args){
 Collection collectons = new ArrayList();  
 ReflectPoint pt1 = new ReflectPoint(3,3);
 ReflectPoint pt2 = new ReflectPoint(5,5);
 ReflectPoint pt3 = new ReflectPoint(3,3);

 collections.add(pt1);
 collections.add(pt2);
 collections.add(pt3);
 collections.add(pt1);

 pt1.y = 7;   //改完后就取不走了     原来的数据找不着了  就叫做  内存泄露
 collections.remove(pt1);
 System.out.println(collections.size());  
}

java中有内存 泄露吗   为什么
 所谓内存泄露就是这个对象我不要用了  却还在一直占用内存空间,没有被释放,这就叫做内存泄露
 举例就是上面的例子

25框架的概念及用反射技术开发框架的原理

什么是框架,例如,我们要写程序扫描.java文件中的注解,要解决哪些问题:读取每一样,在每一个中查找@,找到的@再去查询一个列表,如果@后的内容出现在了列表中,就说明这是一个我能处理和想处理的注解,否则,就说明它不是一个注解或者说至少不是一个我感兴趣和能处理的注解。接着就编写处理这个注解的相关代码。现在sun提供了一个apt框架,它会完成所有前期工作,只需要我们提供能够处理的注解列表,以及处理这些注解的代码。Apt框找到我们感兴趣的注解后通知或调用我们的处理代码去处理。
你做的门调用锁,锁是工具,你做的门被房子调用,房子是框架,房子和锁都是别人提供的。

程序中不处理异常,而是main方法声明抛出异常,便于大家可以集中看主要的关键代码。

Class类也提供getResourceAsStream方法的比喻:如果你每次都找我给你商店买可乐,那我还不如直接向你买可乐,即直接提供一个买可乐的方法给你。

 String startingclassName=args[0];  就是要调用某个类的main方法,而到底调用哪个类的main方法,在写程序的时候是不知道的,而是等程序运行起来以后,你再传递一个字符串给我,   这个字符串对应的这个类和现在这个类哪个先写出来的  args这个类还没写出来,源程序就编好了(我要调用你的类,结果我写程序的时候 你的类还没有写,我就可以调,只要你等我运行的时候写出来就可以,在我写的时候你还没有写出来,没关系    框架 用strans做框架, 在还没有用strans做项目的时候 框架就已经写完了   在用strans做项目时就写了好多个我们自己的类,我们写的类在运行的时候给strans框架区调用  框架先写完  我们的程序后写     这就是反射的一个好处)

大家以后在用别人写的类  有两种使用方式:一种是你去调用别人的类,还有一种是 别人的类来调用你的类  不管怎么弄  都是你在用别人的类 ,   别人调用你  也算你用别人的   你用strans框架  是不是 框架在调用你   这两种有区别  一个叫框架  一个叫工具   举个买房子的例子  

框架和工具类的区别:  都是别人写的 但是用法不一样  一个是 人家调用你  一个是你调用人家      

 大家做的一个一个的strans项目,每个项目都不一样,但是每个人都做的很快,因为大家首先上来就是在一个框架基础上开始干(在一个半成品的基础上开始),
 人家找你们开发用框架   因为做事效率高,项目完成效率高
 框架要解决的核心问题是:  要解决  我若干年前写程序 能够调用你若干年后写的程序  不能直接new某个对象,只能.class().forName()一个字符串,那个字符串等运行的时候再给我   这就是反射

public static void main(String[] args)throws Exception{


 InputStream ips = new FileInputStream("config.properties"); 
 Properties props = new Properties();/* 定义一个Properties对象,就等效于hashmap  内存里边装的是键值对  但是它在hashmap基础上扩展了一点功能  它可以将自己内存中的键值对存到硬盘的文件里边去,它也可以在初始化的时候 从一个文件里边将自己的键值对加载进来,  我们有一个空的hashmap  就得一个个手动的填充值,而这个Properties上来就可以从一个文件里边搞出来一堆键值对。*/
 props.load(ips);
 ips.close();  /*良好的习惯  关门  否则会有一点小小的内存泄露   这个内存泄露不是 这个ips对象不被释放,而是这个对象关联的系统资源没有被释放(比如桌面上的window,是操作系统上的一个窗口,在内存上是占内存的。现在写一个java程序 c window   由这个java对象对应这个窗口,现在内存中就有两个东西,一个是原始的窗口,一个是java对应的对象,你能看到的就是java对象变成垃圾进行回收了,但是那个窗口还在桌面上   在程序里边要操作window 需要一个对象,代表实际窗口的对象,由垃圾回收器管理,操作系统的窗口由操作系统管理,调用close是在跟操作系统说把窗口关掉  但是本对象还在  本对象以后由java虚拟机作为垃圾回收  就是自己在被回收之前,先把自己关联的物理资源关掉  如果不关掉 就是你这个对象没有了,但是原来你指向这个操作系统的资源还在,),这句话不是将ips对象关掉,这个对象是由java虚拟机垃圾回收机制管理的,而是将资源释放了*/
 String className = props.getProperty("className");//这句话中的两个class必须要一样,  起名字 尽量有意义
 Collection collections = (Collection)Class.forName(className).newInstance();  //调用不带参数的构造方法类加载器

 //Collection collectons = new ArrayList();    //  所谓反射就是这里ArrayList不要出现具体 的类的名字,而是从一个配置文件里边读取出来的
 ReflectPoint pt1 = new ReflectPoint(3,3);
 ReflectPoint pt2 = new ReflectPoint(5,5);
 ReflectPoint pt3 = new ReflectPoint(3,3);

 collections.add(pt1);
 collections.add(pt2);
 collections.add(pt3);
 collections.add(pt1);

 //pt1.y = 7;    //为什么这个现在能删呢   因为和hashcode没关系,  要是hashset就删不了
 //collections.remove(pt1);


 System.out.println(collections.size());  

26用类加载器的方式管理资源和配置文件

配置文件:  new  file     名字:config properties  里边写  className=java.util.ArrayList  4
 className=java.util.HashSet   2 
      等到程序最终运行的时候,就不用改java源程序,只要改这个文件  那个类就换了          在运行的时候,我把程序交给了你,你作为客户你没有javac,不能改源文件,但是你又记事本,将这个文件改一下就可以了。

配置文件要放在哪里?  要放在工程下面
  程序开发完后,不能将整个java目录给对方, 只要将.class文件打包成jar包个对方就行   在实际开发中没有用相对路径搞的,  你不知道这个相对路径是相对于谁
 c:\Documents and Settings\ibm>java.MyClass xx.file              在这个路径下用到了xx.file文件,xx文件是相对于MyClass文件还是java.exe  谁都不是,而是相对于当前工作目录ibm下    这时就感觉到这个相对路径是飘忽不定的,要用就用绝对路径

而用绝对路径又出现一个问题:  一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的
InputStream ips = new FileInputStream("d\\config.properties");  将其放在d盘,结果人家机器上小硬盘 没有d盘,只有c盘,  这是一种解决方案,绝对路径,但是绝对路径这个d盘,不是硬编码写进去的。而是用某种方法get出来的。  譬如说你在一个配置文件里边,  而对方在系统的某个地方进行配置,说config.properties的位置  所在的目录,配完后,就可以去读 也就是说用户想把它放在哪个位置都可以,放完后,需要在配置文件中写一下说这个文件放在了哪里  这样我的程序就可以运算出来那个绝对路径 学javaweb时就会学到这种方式  得到绝对路径  在javaweb中你的东西肯定是放在你的web项目里边,  得到web项目在硬盘上的对应的具体目录,getRealPath();  而你的配置文件肯定是在web项目文件内部  你得到了web项目的绝对位置,再拼上你内部的那个位置就拼到一个真实的完整的位置  得到你总的目录在硬盘上对应的绝对位置

 类加载器:
 每一个.class文件在使用时都要加载到内存中来,就是说一定要有个东西,将硬盘上的.class文件搞到内存里面来,
既然类加载器能够加载class文件 那是不是也能够加载普通文件呢

InputString ips = eefiectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");//在classpath指定的目录下,逐一的去查找你要加载的那个文件,
InputString ips = RefiectTest2.class.getResourceAsStream("config.properties");

 

-------android培训java培训、期待与您交流-------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值