黑马程序员-Java基础加强(2)

来源:互联网 发布:pageadmin cms 漏洞 编辑:程序博客网 时间:2024/05/16 14:08

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


  • 了解注解及java提供的几个基本注解

先通过@SuppressWarnings的应用让大家认识和了解一下注解:
通过System.runFinalizersOnExit(true);的编译警告引出@SuppressWarnings("deprecation")
@Deprecated
直接在刚才的类中增加一个方法,并加上@Deprecated标注,在另外一个类中调用这个方法。
@Override
public boolean equals(Reflect other)方法与HashSet结合讲解
总结:
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
看java.lang包,可看到JDK中提供的最基本的annotation。


首先编写一个AnnotationTest类,由其中一个deprecated问题System.runFinalizersOnExit(true);, 引出SuppressWarnings注解来解决刚才的警告。

接着用如何实现这种功能引出@Deprecated的讲解,例如:
@Deprecated
public static void sayHello()
{
System.out.println("传智播客,你好!");
}

结合前面反射框架的例子,当集合为Hashset时,
    System.out.println(set.size());//期望结果为2,但实际为3
        这时候在User类的equals方法上加上@Override,发现了问题。


注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类。就像你要调用某个类,得先有开发好这个类。

自定义注解及其应用

定义一个最简单的注解:public @interface MyAnnotation {}
把它加在某个类上:@MyAnnotation public class AnnotationTest{}
用反射进行测试AnnotationTest的定义上是否有@MyAnnotation
根据发射测试的问题,引出@Retention元注解的讲解,其三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。
思考:@Override、@SuppressWarnings和@Deprecated这三个注解的属性值分别是什么?
演示和讲解@Target元注解
Target的默认值为任何元素,设置Target等于ElementType.METHOD,原来加在类上的注解就报错了,改为用数组方式设置{ElementType.METHOD,ElementType.TYPE}就可以了。
元注解以及其枚举属性值不用记,只要会看jdk提供那几个基本注解的API帮助文档的定义或其源代码,按图索骥即可查到,或者直接看java.lang.annotation包下面的类。

ItAnnotation.java

package org.javainhance02;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.javainhance01.EnumTest;//这是一个注释类@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD,ElementType.TYPE})public @interface ItAnnotation {String color() default "blue";//注释类中成成员变量的String value();int[] arrayAttr() default {3, 4, 5};EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;MetaAnnotation annotationAttr() default @MetaAnnotation("haha");}
AnnotationTest.java
package org.javainhance02;@ItAnnotation(annotationAttr=@MetaAnnotation("klaus"),color="red",value="abc",arrayAttr=1)//添加注释信息public class AnnotationTest {/** * @param args */@SuppressWarnings("deprecation")@ItAnnotation("xys")public static void main(String[] args) {// TODO Auto-generated method stubSystem.runFinalizersOnExit(true);System.out.println(AnnotationTest.class.isAnnotationPresent(ItAnnotation.class));if(AnnotationTest.class.isAnnotationPresent(ItAnnotation.class)){ItAnnotation annotation = AnnotationTest.class.getAnnotation(ItAnnotation.class);//用反射得到这个注释类//打印出注释类中的信息System.out.println(annotation);System.out.println(annotation.color());System.out.println(annotation.value());System.out.println(annotation.arrayAttr().length);System.out.println(annotation.lamp().nextLamp().name());System.out.println(annotation.annotationAttr().value());}}@Deprecatedpublic static void sayHello(){System.out.println("hello world!!!");}}




什么是注解的属性

一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。加了属性的标记效果为:@ItAnnotation(color="red")
定义基本类型的属性和应用属性:
在注解类中增加String color();
@ItAnnotation(color="red")
用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
ItAnnotation a = (ItAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(a.color());
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象
为属性指定缺省值:
String color() default "yellow";
value属性:
String value() default "zxx";
如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@ItAnnotation("lhm")。


数组类型的属性
int [] arrayAttr() default {1,2,3};
@MyAnnotation(arrayAttr={2,3,4})
如果数组属性中只有一个元素,这时候属性值部分可以省略大括
枚举类型的属性
EnumTest.TrafficLamp lamp() ;
@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
注解类型的属性:
MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
@MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
    MetaAnnotation ma =  myAnnotation.annotationAttr();
    System.out.println(ma.value());
注解的详细语法可以通过看java语言规范了解,即看java的language specification。


  • 泛型

ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如, Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
原始类型可以引用一个参数化类型的对象,编译报告警告,例如, Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
     Vector<Integer> vectorList[] = new Vector<Integer>[10];

泛型中的类型参数严格说明集合中装载的数据类型是什么和可以加入什么类型的数据,记住:Collection<String>和Collection<Object>是两个没有转换关系的参数化的类型。
  假设Vector<String> v = new Vector<Object>();可以的话,那么以后从v中取出的对象当作String用,而v实际指向的对象中可以加入任意的类型对象;假设Vector<Object> v = new Vector<String>();可以的话,那么以后可以向v中加入任意的类型对象,而v实际指向的集合中只能装String类型的对象。


思考题:下面的代码会报错误吗?
Vector v1 = new Vector<String>();
Vector<Object> v = v1;

不会。编译器是一条条语句翻译,执行检查语义错误的。


问题:
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
错误方式:
public static void printCollection(Collection<Object> cols) {
        for(Object obj:cols) {
            System.out.println(obj);
        }
        /* cols.add("string");//没错
         cols = new HashSet<Date>();//会报告错误!*/
}
正确方式:
public static void printCollection(Collection<?> cols) {
        for(Object obj:cols) {
            System.out.println(obj);
        }
        //cols.add("string");//错误,因为它不知自己未来匹配就一定是String
        cols.size();//没错,此方法与类型参数没有关系
         cols = new HashSet<Date>();
    }
总结:
使用通配符可以引用其他各种参数化的类型?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:
限定通配符总是包括自己。
只能用作引用,不能用它去给其他变量赋值
    Vector<? extends Number> y = new Vector<Integer>();
    Vector<Number> x = y;
    上面的代码错误,原理与Vector<Object > x11 = new Vector<String>();相似,
    只能通过强制类型转换方式来赋值。



Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
<T> T add(T x,T y) {
        return (T) (x+y);
        //return null;
    }
用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(E[] a, int i, int j) {
    E t = a[i];
    a[i] = a[j];
    a[j] = t;
}//或用一个面试题讲:把一个数组中的元素的顺序颠倒一下
只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。
除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}
普通方法、构造方法和静态方法中都可以使用泛型。
也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}

Java中的泛型类型(或者泛型)类似于 C++ 中的模板。但是这种相似性仅限于表面,Java 语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
例如,下面这两个方法,编译器会报告错误,它不认为是两个不同的参数类型,而认为是同一种参数类型。
private static void applyGeneric(Vector<String> v){
}


如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
    public class GenericDao<T> {
        private T field1;
        public void save(T obj){}
        public T getById(int id){}
    }
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?

package org.javainhance02;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.Date;import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.Map;import java.util.Set;import java.util.Vector;import org.javainhance01.ReflectPoint;public class GenericTest {/** * @param args * @throws Exception  * @throws NoSuchMethodException  */public static void main(String[] args) throws NoSuchMethodException, Exception {// TODO Auto-generated method stub/*ArrayList collection01 = new ArrayList();collection01.add(1);collection01.add(1L);collection01.add("abc");int i = (Integer) collection01.get(1);*/ArrayList<String> collection02 = new ArrayList<String>();//collection02.add(1);//collection02.add(1L);collection02.add("abc");String element = collection02.get(0);Constructor<String> constructor = String.class.getConstructor(StringBuffer.class);String str = constructor.newInstance(new StringBuffer("abc"));System.out.println(str.charAt(2));ArrayList<Integer> collection03 = new ArrayList<Integer>();System.out.println(collection02.getClass() == collection03.getClass());System.out.println(collection02.getClass().getName());Method method = ArrayList.class.getMethod("add", Object.class);method.invoke(collection03, "abc");System.out.println(collection03.get(0));printCollection(collection02);Class<? extends Number> clazz = Integer.class.asSubclass(Number.class);System.out.println(clazz.getName());HashMap<String, Integer> maps = new HashMap<String, Integer>();maps.put("klaus", 24);maps.put("jacob", 25);maps.put("haha", 23);Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();System.out.println(entrySet);for(Map.Entry<String, Integer> entry:entrySet){System.out.println(entry.getKey() + ":" + entry.getValue());}add(3, 5);Number x1 = add(3.5, 2);Object x2 = add(3, "abc");swap(new String[]{"abc","123","ha"}, 1, 2);//swap(int[]{1, 2, 3, 4}, 1, 3);Object obj = "abc";String str2 = autoConvert(obj);//转换数据类型GenericDAO<ReflectPoint> dao = new GenericDAO();dao.add(new ReflectPoint(3, 4));ReflectPoint s = dao.findById(1);//获取应用了泛型的类有一些信息。Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);Type[] type = applyMethod.getGenericParameterTypes();//System.out.println(type.length);ParameterizedType pType = (ParameterizedType)type[0];System.out.println(pType.getOwnerType());System.out.println(pType.getActualTypeArguments()[0]);System.out.println(pType.getRawType());System.out.println(pType.getClass());}public static void applyVector(Vector<Date> v1){}//定义泛型方法private static <T> T autoConvert(Object obj){return (T)obj;}private static <T> void swap(T[] a, int i, int j){T temp = a[i];a[i] = a[j];a[j] = temp;}private static <T>T add(T a, T b){return null;}private static void printCollection(Collection<?> collection){//collection.add(1);//collection.add("abc");collection.size();for(Object obj : collection){System.out.println(obj);}}}

  • 类加载器

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。 


package org.javainhance02;import java.util.Date;public class ClassLoaderTest {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException{/*System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());System.out.println(System.class.getClassLoader());ClassLoader loader = ClassLoaderTest.class.getClassLoader();while(loader != null){////打印出当前的类装载器,及该类装载器的各级父类装载器System.out.println(loader.getClass().getName());loader = loader.getParent();}System.out.println(loader);*///System.out.println(new ClassLoaderAttachment().toString());//调用自定义的类加载器Class c1 = new MyClassLoader("F:\\javaInhance\\javaInhanceProject\\itlib").loadClass("ClassLoaderAttachment");Object d1 =  c1.newInstance();//创建实例对象System.out.println(c1.getClassLoader().getClass().getName());System.out.println(d1);;}}


类加载器之间的父子关系和管辖范围图


用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包,再在eclipse中运行这个类,运行结果显示为ExtClassLoadr。此时的环境状态是classpath目录有ClassLoaderTest.class,ext/itcast.jar包中也有ClassLoaderTest.class,这时候我们就需要了解类加载的具体过程和原理了。


类加载器的委托机制

当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。


每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

有一道面试,能不能自己写个类叫java.lang.System,为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。

把先前编写的类加入到jdk的rt.jar中,会有怎样的效果呢?不行!!!看来是不能随意将自己的class文件加入进rt.jar文件中的。


编写自己的类加载器

知识讲解:
自定义的类加载器的必须继承ClassLoader
loadClass方法与findClass方法
defineClass方法
编程步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
实验步骤:
对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。


import java.io.*;import java.lang.reflect.*;public class MyClassLoader extends ClassLoader{private String path = null;public MyClassLoader(String path) throws Exception//检查文件是否存在{File f = new File(path);if(!f.isDirectory()){throw new RuntimeException(path + " is not a directory");}this.path = path;}public Class findClass(String name) //throws Exception //子类不能比父类抛出更广泛的异常{try{File f = new File(path,name.substring(name.lastIndexOf('.')+1) + ".class");FileInputStream fis = new FileInputStream(f);ByteArrayOutputStream bos = new ByteArrayOutputStream();cypher(fis,bos);byte [] buf = bos.toByteArray();fis.close();bos.close();return defineClass(name,buf,0,buf.length);}catch(Exception e){throw new ClassNotFoundException(name + " is not found!");}return null;}public static void cypher(InputStream istream,OutputStream ostream) throws Exception{//下面这段代码可能遇到255的字节,当成byte就成了-1/*byte b = 0;while((b = (byte)istream.read()) != -1){ostream.write(b ^ 0xff);}*/int b = 0;while((b = istream.read()) != -1){ostream.write(((byte)b) ^ 0xff);}}public static void main(String [] args) throws Exception{//下面省略了错误检查if(!args[0].endsWith("class")){ClassLoader loader = new MyClassLoader(args[1]);Class cls = loader.loadClass(args[0]);/*让自定义类继承Date类System.out.println(cls.getClassLoader().getClass().getName());java.util.Date d = (java.util.Date)cls.newInstance();System.out.println(d.toString());*/Method m = cls.getMethod("test");//m.invoke(cls.newInstance(),null);m.invoke(cls.newInstance());//((Test)cls.newInstance()).test();return;}else{FileInputStream fis = new FileInputStream(args[0]);File f = new File(args[1], new File(args[0]).getName());//不用检查目录最后是否有目录分割符FileOutputStream fos = new FileOutputStream(f);cypher(fis,fos);fis.close();fos.close();}}}//类加载器不能加载这种非public的类/*Exception in thread "main" java.lang.IllegalAccessException: Class MyClassLoader can not access a member of class MyTest with modifiers ""*//*class MyTest{public void test(){System.out.println("hello,www.it315.org");}}*/




一个类加载器的高级问题分析

编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。
把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。
父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:




package org.itweb.web.servlets;import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class MyServlet extends HttpServlet {/** * The doGet method of the servlet. <br> * * This method is called when a form has its tag value method equals to get. *  * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/html");PrintWriter out = response.getWriter();ClassLoader loader = this.getClass().getClassLoader();while(loader != null){out.println(loader.getClass().getName() + "<br>");loader = loader.getParent(); }out.close();}}





原创粉丝点击