----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
java基础加强(下)
1、泛型
JDK1.5以后出现的机制,用于解决安全问题,是一个类型安全机制。
泛型出现的原因:
由于集合可以存储不同类型的数据,所以取元素时有可能会导致类型转换错误,所以在开始
定义集合的时候就限定集合只能存某种引用数据类型的对象,减少操作集合时出错的几率。
泛型的书写格式:通过<>来定义要操作的引用数据类型:ArrayList<Person> al = new ArrayList<Person>();
泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分开。例如:
public static <K,V> getVlaue(K key) {return map.get(key);}
泛型出现的好处:
* 提高了程序的安全性
* 将运行期遇到的问题转移到了编译期(ClassCastException)
* 省去了类型强转的麻烦
* 泛型类的出现优化了程序设计
对于参数化的泛型,编译生成的字节码会去掉泛型的类型信息,getClass()方法的返回值和原始类型完全一样,
所以用反射的方式就可以向集合中添加其它类型的数据。
getclas().getMethod(String name,Class Parameters).(集合名字,要添加的元素)
泛型没出现之前:
class Tool{
private Object obj;
public void setObj(Object obj){
this.obj = obj;
}
public Object getObj(){
return obj;
}
public static void mian(String[] args) {
Tool t = new Tool();
t.setObj(new Worker()); //编译不会报错,运行会报类型转换异常
Student s = (Student)t.getObj();//需要强转类型
}
}
泛型出现之后:
class Tool<Q> {
private Q obj;
public void setObj(Q obj) {
this.obj = obj;
}
public Q getObj() {
return obj;
}
public static void mian(String[] args) {
Tool<Worker> t = new Tool<Worker>();
t.setObject(new Worker());//如果传入t.setObject(new Student());编译时会出现失败。
//将运行时出现的问题转移到了编译时期。
Worker w = t.getObj(); //不需要再进行强转。
}
}
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
ArrayList<E>:泛型类型。没有指定具体的泛型,E称为类型变量或类型参数。
ArrayList<Integer>:参数化的类型。指定了具体的泛型,Integer称为类型参数的实例或实际类型参数。
ArrayList<Integer>:<>念typeof。
ArrayList称为原始类型。
参数化类型可以引用一个原始类型的对象 Collection<String> c = new Vector();
原始类型可以引用一个参数化类型的对象 Collection c = new Vector<String>();
编译器不允许创建类型变量的数组,即在创建数组实现时,数组的元素不能使用参数化的类型
例如:Vector<integer>[] vectorList = new Vector<Integer>[10];
参数化类型不考虑类型参数的继承关系 Vector<String> = new Vector(Object);//错误,反之亦然
Vector v = new Vector<String>(); Vector<Object> v1 = v;//不要把两行代码结合起来
可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但不能用于catch语句中。
private static <T extends Exception> sayHello() throws T {
try {} catch (Exception e) {throw (T)e}}
}
泛型的应用:
自定义泛型:类或者接口的旁边有<>时,给<>传递实际参数,这个参数是具体引用数据类型(不能是基本数据类型)
使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可
自定义泛型类:当类中要操作的引用数据类型不确定的时候,就使用泛型类。具体操作什么类型的对象,由使用
该类的使用者来明确,将具体的类型做为实际参数传递给<>。泛型类定义的泛型,在整个类中有
效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已固定。
swap(new int[3],3,5);//该语句会报错,编译器不会对int[]中的int数进行自动装箱和拆箱
因为new int[]本身就是一个对象了。swap(String[],3,5);该语句不报错
泛型定义在方法上:为了让不同方法可以操作不同类型,而且类型还不确定。泛型方法的类型参数只能是引用数
据类型,不能是基本数据类型
成员方法泛型:
class Tool<T>{
public void show(T t){}
public <Q> void print(Q q){}
}//show()方法的泛型和类泛型一致,类中指定了类型,调用show()方法就不能再添加别的类型
//print()方法是在方法上指定泛型,可以操作任意类型而不受类中泛型限制
静态方法泛型:
class Tool<T>{
public void show(T t){
System.out.println("show:"+t);
}
public static <W> void method(W t){ //如果静态方法操作的应用数据类型不确定,可以将泛型
System.out.println("method:"+t);//定义在方法上,但不能访问类上定义的泛型。
}
}
定义泛型类型:
如果类的实例对象中多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时
要采用泛型类型的方式定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T> {//在类上定义泛型,方法中用到的类型和类的泛型类型是一致的。
private T find1;
public void add(T object) {}
public T getByid(int id) {}
}
dao-->data access object:数据访问对象(crud: create remove update delete)
类级别的泛型是根据引用该类名时,指定的类型信息,来参数化类型变量的。以下两种方式都可以:
GenericDao<String> dao = null; new GenericDao<String> ();
当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为
静态成员是被所有参数化的类所共享的,所以静态成员应该有类级别的泛型参数。
类型参数类型推断:
编译器判断泛型方法实际类型参数的过程称为类型推断。类型推断是相对于知觉推断的,其实现方法是很复杂的过程
根据调用泛型方法时实际传递的参数类型或返回值类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应
用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值类型来决定泛
型参数的类型。如:swap(new String[3],3,4) --> static<E> void swap(E[] a,int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类
型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:add(3,3)-->static<T> T add(T a,T b)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类
型对应到了不同类型,且没有返回值类型,这时候取多个类型的最大交集类型。下面语句实际对应的类型是
Number,编译没有问题,运行时出问题:fill(new Integer[3],3,3.5f)-->static<T> void fill(T[],T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类
型对应到了不同类型,且有返回值,这时优先考虑返回值类型。下面语句实际对应的类型是Integer,编译报
错,将变量x的类型改为float,对比Eclipse报告的错误提示,接着再将变量x类型改为Number,则不会出错:
int x = (3,3.5f)-->static<T> T add(T a, T b);
参数类型的类型推断具有传递性,下面第一种情况推断实际类型参数为Object,编译没有问题,而第二种情况则
根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5])-->static<T> void copy(T[] a,T[] b);
泛型接口:
interface Inter<T> {
public void show(T t);
}
class Demo1<T> implements Inter<T>{ //类在实现接口的时候没有指定引用数据类型
public void show(T t){
System.out.println("show :"+t);
}
}
class Demo2 implements Inter<String>{ //类在实现接口的时候指定了引用数据类型
public void show(String s){
System.out.println(s);
}
}
泛型通配符:<?>,可以理解为占位符。
ArrayList<String> al = new ArrayList<String>;
al.add("abc1");
al.add("abc2");
HashSet<Integer> hs = new HashSet<Integer>();
hs.add(1);
hs.add(2);
boolean b = al.containsAll(hs);//false
通过上面的示例分析:
在要比较的类型不确定时候用 ? ,表示任意类型。而T是声明具体类型变量,只能比较该类类型,或该类类型的子类
?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能与参数化有关的方法。
泛型的限定:
? extends E(上限): 接收E或者E的子类型,固定父类型,只要是该类型的子类就可以传入,查看API(ArrayList)
? super E(下限):接收E或者E的父类型,固定了子类型,只要是该类型的父类或者接口就可以传入,查看API(TreeSet)
并且可以用&符号来指定多个边界。如:<V extends Serializable & cloneable> void method() {}
2、类加载器:是加载类的工具。
作用:jvm把一个类的二进制表现形式(.class也就是字节码)文件加载到内存中去,通常这个字节码的原始信息放在硬盘上classpath指定的目录
下。把字节码文件中的内容加载到硬盘里面,再对它进行一些处理,处理完的结果就是字节码文件。处理这些工作
的工具就是类加载器。
类加载器也是java类,因为其它是java类的类加载器本身也要被类加载器加载,所以必须由第一个不是java类的类加载器
来加载,这个加载器就是BootStrap,不需要被加载。它是嵌套在jvm内核里面的,虚拟机内核一启动BootStrap就已经
存在,它是由C++语言编写的一段二进制代码。
java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个
父级类加载器对象或默认采用系统类加载器为其父类加载。java虚拟机中可以安装多个类加载器,系统默认三个主要
类加载器,每个类加载器负责加载特定位置的类。类加载器之间的父子关系和管辖范围如下所示:
BootStrap -->JRE/lib/rt.jar
|--ExtClassLoader -->JRE/lib/ext/*.jar
|--AppClassLoader -->Classpath指定的所有jar或目录
|--自定义类加载器(extends ClassLoader) -->加载特定的目录
类加载的委托机制:
当java虚拟机要加载一个类时:
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B。java虚拟机将使用加载类A的类加载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,先委托给其上级类加载器:
当所有父类加载器没有加载到类,回到发起者类加载器,还加载不了,抛ClassNotFoundExcepiton,不是再去
再去找发起者类加载器的子类,因为没有getChild()。即使有,那么有多个子类也不知道找哪一个。
自定义类加载器:其实就是模板设计模式(TemplatePattern)
自定义的类加载器继承ClassLoader
loadClass()方法不用重写
重写findClass()方法
通过defineClass()将得到的二进制数据文件转换成字节码文件
编程步骤;
①编写一个对文件内容进行简单加密的程序。
②编写一个自己的类加载器,可以实现对加密过的类进行装载和解密。
③编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,
程序中除了可以使用ClassLoad.load()方法外,还可以使用设置线程的上下文类加载器或者系统类加载器,
然后在使用Class.forName()。
实验步骤:
1、对不带包名的class文件进行加密,加密结果存放到另外一个目录。
2、运行加载类的程序,结果能够被正常加载,但打印出来的类加载器名称为:
AppClassLoader:java MyClassLoader MyTest F:\itcast
3、用加密后的类文件替换Classpath环境下的类文件,再执行上一步操作就会出现问题,错误说明是:
AppClassLoader类加载器加载失败。
4、删除Classpath环境下的类文件,再执行上一步操作就没有任何问题。
class MyClassLoader extends ClassLoader { //自定义类加载器
private String classDir;
public MyClassLoader() {};
public MyClassLoader(String classDir) { //传递一个文件目录
this.classDir = classDir;
} //重写父类加载器中的findClass()方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name + ".class";
try {
File InputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos); //将文件中的内容进行加密
fis.close();
byte[] bytes = bos.toByteArray();
return difineClass(bytes,0,bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
public static void cypher(InputStream ips,OutputStream ops) throws Exception {
int b = -1; //加密文件方法cypher()
while((b = ips.read()) != -1) {
ops.write(((byte)b) ^ 0xff);
}
}
public static void main(String[] args) throws Exception {
String srcPath = args[0];
String destDir = args[1];
File InputStream fis = new FileInputStream(srcPath);
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\') + 1);
String destPath = destDir + "\\" + destFileName;
File OutputStream fos = new FileOutputStream(destPath);
cypher(fis,fos);
fis.close;
fos.close;
}
}
class ClassLoaderAttachment extends Date {
public String toString() {
return "Hello,Itcast!";
}
}
class ClassLoaderTest {
public static void main(String[] args) {
//父类加载器只能加载带包名的文件
Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
Date d = (Date)clazz.newInstance();//不能用类名定义引用变量,因为编译器无法识别这个类
System.out.println(d1);
}
}
3、代理的概念与作用
要为已存在的具有多个相同接口目标类的各个方法增加一些系统功能,就要编写一个与目标类具有相同接口的代理类,
代理类的每个方法必须要调用与目标类对应相同的方法,并在调用方法的前面或者后面加上系统功能的代码。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序。在配置文件中配置是使用目标类,还是代理
类,这样以后很容易切换。譬如:想要日志功能时,就配置代理类,否则配置目标类。这样增加系统功能很容易,
以后运行一段时间后,再去掉系统功能也很容易。
代理架构:
Client(客户端调用程序)-->Target(目标类)-->Interface(接口)
程序中如过没有使用代理,就会直接引用目标类
-->Poxy(代理类)-->Target(目标类)-->Interface(接口)
如果使用代理,直接引用接口,这样就可以看切换
AOP:
系统存在交叉业务,一个交叉业务就是要切入到系统中的一个方面。
安全 事物 日志 ---->交叉业务(穿插到很多个对象中)
StudentService ------|-------|--------|----
CourseService ------|-------|--------|---- ---->模块(对象,也就是系统)
MiscService ------|-------|--------|----
具体的程序代码描述交叉业务
Method1 { Method2 { Method3 {
---------------------------------------- 切面 ---->交叉业务切入到系统中就像一个切面
.... .... ....
---------------------------------------- 切面
} } }
交叉业务的编程即面向方面的编程(Aspect oriented program 简称AOP),AOP的目标就是要使交叉业务模块化。可
以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的
---------------------------------------- 切面
func1 { func2 { func3 {
.... .... ....
} } }
---------------------------------------- 切面
使用代理技术就可以解决这个问题,代理是实现AOP功能的核心和关键技术
动态技术代理:
要为系统中各个接口的类增加代理功能,会需要很多的代理类,全部采用静态代理方式非常麻烦。
jvm可以在运行期生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
jvm生成的动态类必须实现一个或多个接口,所以jvm生成的动态代理类必须要和目标类具有有相同的接口
如果目标类没有实现接口,CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理。所
CBLIB库是第三方类库。
代理类的各个方法中通常除了要调用目标的相应方法和返回目标返回的结果外,还可以在代理类方法中的如下
四个位置加上系统功能代码:
在调用目标方法之前
在调用目标方法之后
在调用目标方法前后
在处理目标方法异常的catch块中
分析jvm生成的动态类:
创建实现了Collection接口的动态类和查看其名称。
编码列出动态类中所有的鄂构造方法和参数签名
编码列出动态类中所有的方法和参数签名
分析Proxy.getProxyClass()的各个参数:
ClassLoader:内存生成的字节码没有ClassLoader,没有通过类加载器加载,但必须要指定一个类加载器。这时可以使用
和要实现的接口相同的类加载器。
Interfaces:要实现的接口
创建动态类的实例对象
用反射方法获得构造方法
编写一个最简单的invokcationHandler类
调用构造方法创建动态类的实力对象,便将编写的invokcationHandler类实例对象传递进去
打印创建的对象和调用对象的没有返回值的方法和getClass(),演示调用其它有返回值的方法报告异常
总结:让jvm创建动态类及其实例对象,需要提供三个方面的信息:
生成的类中有哪些方法,通过让其实例那些接口的方式进行告知;
产生的类字节码文件有一个关联的类加载器对象;
生成类中方法的代码是怎样的,也要由我们提供。把代码写在一个约定好了接口对象的方法中,把对象
传递给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象是InvokcationHandler
对象。它是在创建动态类实例对象的构造方法时传递进去的。在上面InvokcationHandler对象的
invoke()中加一些代码,就可以看到这些代码被调用运行了。
用Proxy.newProxyInstance()方法直接创建出代理对象,需要使用匿名内部类。
动态生成的类的内部代码:
动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口的所有方法和一个
接收InvokcationHandler参数的构造方法。
构造方法接受一个InvokcationHandler对象,该方法内部的代码如下所示:
InvocationHandler handler;
public Proxy(InvocationHandler handler) {this.handler = handler}
Collection接口中所有实现的方法,只要被调用就会调用InvocationHandler接口中定义的invoke()。而
invoke(Object proxy,Method method,Object[] args)方法中的三个参数分别是调用invoke()的方法
的对象、方法、参数。例如:proxy.add("zxx");
动态语言:
代码不是在编程时编好的,而是程序运行时,把一串字符串当作代码运行。
class Proxy { //创建实现了Collection接口的动态类并查看方法列表
public static void main(String[] args) {
Class clazzProxy1 = Proxy.getProxyClass(Collectoin.class.getClassLoader(),Collectoin.class);
System.out.println(clazzProxy1.getName());
System.out.println("----Constructors and Methods list");
Constructors[] Constructors = calzzProxy1.getConstructors(); //获取动态类的构造器
for(Constructor constructor : constructors) {
String name = constructor.getName(); //获取构造器的名称
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = constructor.getParameterTypes(); //获取构造器的参数列表字节码
for(Class clazzParam : calzzParams) {
sBuilder.append(clazzParam.getName()).append(','); //把字节码文件添加到容器中
}
if(calzzParam.length() !== null && clazzParam.length() != 0)//如果不为空长度不为0
sBuilder.deleteCharAt(sBuilder.length()- 1); //删除最后的','
sBuilder.append(')'); //改为添加')
System.out.println(sBuilder.toString());
}
Method[] Methods = calzzProxy1.getMethods(); //获取动态类的方法
for(Method method : Methods) {
String name = method.getName(); //获取方法的名称
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = method.getParameterTypes(); //获取方法的参数列表字节码
for(Class clazzParam : calzzParams) {
sBuilder.append(clazzParam.getName()).append(','); //把字节码文件添加到容器中
}
if(calzzParam.length() !== null && clazzParam.length() != 0)//如果不为空长度不为0
sBuilder.deleteCharAt(sBuilder.length()- 1); //删除最后的','
sBuilder.append(')'); //改为添加')
System.out.println(sBuilder.toString());
}
System.out.println("----begin create instance"); //建立代理类实力对象,取构造器
Constructor constructor = clazzProxy1.getConstructor(invokcationHandler.class);
/*
class MyInvokcationHandler1 implements InvokcationHandler { //构造器参数是一个接口,子类实现
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
return null;
}
//该对象是Collection实现类对象
Collection proxy1 = (Collection)constructor.newInstance(new MyIncokcationHandler1());
System.out.println(proxy1); //返回null,对象没有创建成功或者toString()返回的是null
proxy1.clear();
proxy1.size();//出现空指针异常,size()调用了invoke(),内部的返回值是null
}*/
//匿名内部类实现上述代码
Collection proxy2 = constructor.newInstance(new InvokcationHandler() {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
return null;
}
});
Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader,
new Class[] {Collection.class},
new InvokcationHandler() {
public Object invoke(Object proxy,Method method) throws Throwable {
Arraylist target = new ArrayList();
return method.incoke(target,args);
}
});
proxy.add("zxx");
proxy.add("lhm");
proxy.add("bxd");
System.out.println(proxy3.size());//长度为0,add()调用了invoke(),每次都会创建新target
}
}