一:泛型:jdk1.5版本以后出现的一个安全机制
1.概述:
只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个 具体的引用数据类型。
2.好处:
2.1.将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。
2.2.避免了强制转换的麻烦。
3.泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。
为什么?
因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。
在运行时,如何知道获取的元素类型而不用强转呢?
4.泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在 内部进行一次转换即可,所以使用者不用再做转换动作了。
ArrayList<Object> list = new ArrayList<Integet>();//也会报编译错误
5.什么时候用泛型类呢?
当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样 可以避免强转的麻烦,而且将运行问题转移到的编译时期。
6.泛型中的通配符:
可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用 Object类中的功能。那么可以用 ? 通配符来表未知类型。
泛型限定:
6.1上限:?extends E:可以接收E类型或者E的子类型对象。
ArrayList<? extends Number > list = new ArrayList<Integer>();//正确,因为Number是Integer的父类
上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么? 因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。
6.2.下限:?super E:可以接收E类型或者E的父类型对象。
ArrayList<? extends Number > list = new ArrayList<String>();//错误,因为Number不是String的父类
下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素 的父类型接收。7.编译器在调用泛型方法时的实际参数类型:
7.1.swap(new String[3],3,4) -> static<T> void swap(T[] a, int i,int j);不用怀疑这是String泛型参数类型7.2.add(3,5) -> static<T> T fill(T a,T b);不用怀疑这是int泛型参数类型
7.3.add(3,3.5f) -> static<T> T add(T a,T b);这种情况根据实参的交集来决定泛型参数类型(一个int,一个float,将两 个类型包装后为Integer和Float,这两个类型的父类为Number),
因此泛型参数类型为Number,若有返回值得话返回值为Integer,否则编译不通过;
7.4.fill(new Integer[3],3.5f) -> static<T> void fill(T[] a,T b);第一个为Integer,第二个Float,交集为Integer。
7.5.copy(new Integer[5],new String[5]) -> static<T> void copy(T[] a,T[] b);根据交集泛型参数类型为Integer
copy(new Vector<String>(),new Integer[5]) -> static<T> void copy(Collection<T> a,T[] b);//由于泛型参数具有 传递性,因此会有编译错误。
示例代码如下:用反射获取泛型参数类型
<pre name="code" class="java">package com.JavaSE.GenericReflect;
import java.lang.reflect.Method;
//由于泛型参数类型变量不保存在字节码中,因此在获取类的字节码时拿不到泛型参数的类型,但是可以通过反射方法拿到
//通过反射获得参数类型,根据范型参数类获得
public class GenericReflectTest {
public static void main(String[] args) throws Exception {
/**
* 1.通过反射获取类的字节码后在获取方法对象
* 2.获取到反射方法的对象之后再获取形参类型,返回值为Type数组
* 3.将Type类型转换为ParameterizedType类型
* 4.再通过ParameterizedType对象的getRawType()方法获取形参类型和getActualTypeArguments()[0]方法泛型参数类型(泛型参数类型可能有多个,根据 角标获得)
*/
Method method = GenericReflectTest.class.getMethod("applyVector1",new Class[]{Vector.class,List.class});
Type[] types = method.getGenericParameterTypes();//获取到反射方法的对象之后再获取形参类型,根据顺序获取
for(Type t : types){
System.out.println(((ParameterizedType)t).getRawType());
System.out.println(((ParameterizedType)t).getActualTypeArguments()[0]);
}
}
public static void applyVector(Vector<Date> v1){
}
public static void applyVector1(Vector<String> v1,List<Integer> v2){
}
}
二.类加载器:
1.Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载,每个类加载器负责特定位置的类:
BootStrap,ExtClassLoader,AppClassLoader。类加载器机制如图:
2.类加载器也是Java类,因为其他是Java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载不是Java类,这正是 BootStrap。
3.Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载 器对象或者默认采用系统类加载器为其父级类加载
类加载器委托机制: 解决到底派出哪个类加载器去加载呢?3.1.首先当前线程的类加载器去加载线程中的第一个类,
3.2.如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B,
3.3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类,
每个类加载器加载类时,又先委托给其上级类加载器:4.当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException异常,不是再 去找发起 者类加载的儿子,因为没有getChild方法,即使有,那有多个儿子找哪一个呢?
package com.JavaSE.classLoader;
import java.util.Date;
//为什么继承一个Date类呢?因为系统在获取到该类时也会编译该类生成字节码,从而影响测试
public class ClassLoaderAttachment extends Date {
public String toString(){
return "this is MyClassLoader";
}
}
2.自定义一个类加载器:
package com.JavaSE.classLoader
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
public static void main(String args[]) throws Exception{
String srcPath = args[0];//源文件路径
String destDir = args[1];//目的目录
String destPath = destDir +"\\"+srcPath.substring(srcPath.lastIndexOf("\\")+1);//构造目的文件路径
System.out.println(srcPath);
System.out.println(destPath);
FileInputStream fis = new FileInputStream(srcPath);//创建一个流对象读取源文件
FileOutputStream fos = new FileOutputStream(destPath);//定义一个输出流
cypher(fis,fos);//将读取到的文件加密
fis.close();//关流
fos.close();
}
//加密方法将读取到的文件经过加密后写入到另外一个文件中
private static void cypher(InputStream fis, OutputStream fos) throws Exception{
int b = -1;
while((b = fis.read()) != -1){
fos.write(b ^ 0xff);
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir+"\\"+name.substring((name.lastIndexOf(".")+1))+".class";//字节码文件路径
try{
FileInputStream fis = new FileInputStream(classFileName);//读取字节码文件
ByteArrayOutputStream bos = new ByteArrayOutputStream();//字节数组输出流
cypher(fis, bos);//将读取的字节码文件解密
fis.close();//关闭输入流
byte[] bytes = bos.toByteArray();//将字节码文件转变成字节数组
bos.close();//关闭输出流
return defineClass(bytes, 0, bytes.length);//
}catch(Exception e){
e.printStackTrace();
}
return null;
}
public MyClassLoader(){ //无参构造函数
}
private String classDir;
public MyClassLoader(String classDir){//带参构造函数
this.classDir = classDir;
}
}
<pre name="code" class="java">package com.JavaSE.classLoader;
import java.util.Date;
//通过自己定义的类加载器去加载ClassLoaderAttachment类时,先是调用系统的加载类(AppClassLoader),
//若系统的加载类没有找到字节码则返回到发起者去找字节码文件
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
<pre name="code" class="java">//去找该路径下的字节码文件
// Class clazz = new MyClassLoader("MyClassLoaderFile").loadClass("com.JavaSE.classLoader.ClassLoaderAttachment");
<pre name="code" class="java">//通过自己的类加载器去加载ClassLoaderAttachment类
Class clazz = new MyClassLoader("MyClassLoaderFile").loadClass("ClassLoaderAttachment"); Date date = (Date)clazz.n ewInstance();//这就是为什么要让 ClassLoaderAttachment继承一个Date类的原因 System.out.println(date.toString());
}
}