Java之泛型和类加载器



一:泛型:jdk1.5版本以后出现的一个安全机制

        1.概述:

                   只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个                      具体的引用数据类型。

        2.好处:

                    2.1.将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。

                    2.2.避免了强制转换的麻烦。

       3.泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。

                                为什么? 

                                因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。

                                在运行时,如何知道获取的元素类型而不用强转呢?

       4.泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在                                                              内部进行一次转换即可,所以使用者不用再做转换动作了。

                                只有引用类型才能应用在泛型变量中,8大基本数据类型不行 
                                原始类型可以转换为带有泛型的类型:如:ArrayList<Integet> list = new ArrayList();//编译通过但会报警告.
                                带有泛型类型的变量之间不能转换:如:ArrayList<Integet> list = new ArrayList<Object>();//会报编译错误                  
                                                                                                    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方法,即使有,那有多个儿子找哪一个呢?

示例代码如下:
1.需要加载的类:
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;
	}	
}

3.测试类
<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());
        }
}


 
   
 
  

总结:
         泛型可以让数据的存放更加合理,这让对数据库的操作更加方便,而类加载器更是可以理解虚拟机的操作方法和一些框架对类的处理。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值