Java基础

1、枚举:枚举本质上是一个类,而且是一个特殊类,其内部成员对应的都是本身的一个实例,这就省去了外部实例化的过程,而且也不允许外部进行实例化,因为枚举的构造函数必须为私有的,可以重写,但是访问级别必须为private,就是为了限制外部实例化,常用方法有valueOf("")把字符串转为枚举的静态方法等,如下:

 1 public class TestInnerClass {
 2 
 3     @Test
 4     public void func()
 5     {
 6         MyEnum mon = MyEnum.valueOf("Mon");//把字符串直接转为枚举对象的静态方法valueOf
 7         //MyEnum mon2 = MyEnum.Mon; //直接调用成员变量也可以获得具体的枚举类实例,默认调用的是无参构造方法
 8         
 9     } 
10     
11     public enum MyEnum
12     {
13          Mon,Sun(),//成员变量必须写在枚举类的构造方法前面,成员变量默认为静态的,类加载的时候就会把所有成员变量都实例化,Sun成员变量默认写法就是Sun(),一般省去无参()
14          Fri(5);//如果就像实例化时调用其他构造方法,那么必须自己显示指定,如Fri(5)
15          private MyEnum(){//枚举类的构造方法必须为private
16              System.out.println("first.....");
17          }
18          private MyEnum(int i)
19          {
20              System.out.println("second.....");
21          }
22     }
23 }
View Code

 2、反射:

一份代码编译后会生成.class结尾的二进制文件,这份二进制文件就是字节码文件,当程序运行时会把这份calss文件加载到JVM中,字节码只有一份,JVM会依据此字节码进行实例化对象,在JVM中获取字节码的方式有:类名.class、实例名.getClass(),Class.forName("字节码全路径类名"),Java中预置了8大基本类型字节码以及一个void,是原始基本数据类型,8大基本类型是int,boolean,double等小写形式,其他对象都不是原始数据类型,其和包装类型Integer等属于不同字节码,也就是对应不同的类,但是保证类型中持有对应的基本数据类型属性,属性名为TYPE;数组类型的字节码和普通类型字节码不一样,如String[].class ,区别就是不同类对应不同字节码,如下:

		System.out.println(Integer.class == int.class);//false
		System.out.println(Integer.TYPE == int.class);//true
		System.out.println(int.class.isPrimitive()); //true,isPrimitive查看是否为原始的基本数据类型
		System.out.println(Integer.class.isPrimitive());//false,只有8大基本数据类型,Integer等不是原始基本数据类型

反射获取构造方法:构造函数区分就是通过参数类型和个数来区分的,所以可以通过如下方法获取指定构造函数

		Constructor<Jerry> constructor = Jerry.class.getConstructor(String.class);//指定的构造方法参数类型
		Jerry newInstance = constructor.newInstance("jerry");//通过获取的构造方法实例化对象的时候必须传入对应类型的参数
		
		//简便方法,直接通过Class对象实例化对象,其内部其实还是调用构造器实例化,而且只是调用无参构造方法,如:
		Jerry jerry = Jerry.class.newInstance(); //通过这种方式调用必须保证有无参构造方法,不然会报错

反射操作Field字段,默认只能操作public的字段,如果想操作private的字段,分两步,先使用getDeclaredField方法获取私有字段,然后暴力改变访问级别setAccessible

		 Jerry jerry = Jerry.class.newInstance();
		 jerry.setName("jerry");
		 Field field = Jerry.class.getField("name");//默认只能访问public的字段
		 Object object = field.get(jerry);//字节码上的字段是没有值的,得依赖具体的实例对象初始化值之后才可以取到值,所以得传入实例对象
		 System.out.println(object);

访问私有字段:

		 Jerry jerry = Jerry.class.newInstance();
		 jerry.setAge(100);
		 Field field = Jerry.class.getDeclaredField("age");//调用私有的成员变量得用getDeclaredField方法
		 field.setAccessible(true);//暴力设置访问级别,直接强行访问
		 System.out.println(field.get(jerry));

获取字段的字节码类型:   

		 Field field = Jerry.class.getDeclaredField("age");//调用私有的成员变量得用getDeclaredField方法
		 System.out.println(field.getType()==Integer.class);//getType()可以得到字段的字节码类型

给字段设置值:

		 Jerry jerry = Jerry.class.newInstance();
		 jerry.setAge(100);
		 Field field = Jerry.class.getDeclaredField("age");//调用私有的成员变量得用getDeclaredField方法
		 field.setAccessible(true);//暴力设置访问级别,直接强行访问		 
		 System.out.println(field.get(jerry));//设值之前
		 field.set(jerry, 101);//设值
		 System.out.println(field.get(jerry));//设值之后

Method方法反射调用:

		 Jerry jerry = Jerry.class.newInstance();
		 Method method = Jerry.class.getMethod("say", String.class);//获取字节码上的方法,传入方法名,和方法需要参数类型
		 String resVal = (String) method.invoke(jerry, "hello world ");//方法调用也得依赖与实例对象,字节码上的方法得关联具体实例方法调用才有具体值和意义,传入参数为实例对象,参数值
	     System.out.println(resVal);

反射如何调用静态方法呢?只需要将invoke方法的第一个参数设为null即可!    

Class<?> threadClazz = Class.forName("java.lang.Math");  
Method method = threadClazz.getMethod("abs", long.class);  
System.out.println(method.invoke(null, -10000l));

反射调用方法时,由于JDK的可变参数是1.5才推出来的,先前传递多个参数都是采用数组形式传递,这样1.5为了兼容之前版本,还支持传递数组作为一组参数,但是如果整合调用的方法本身就是需要一个数组类型的参数,那么这时JDK运行时还是会把参数数组拆开,这样就会造成参数类型不匹配,这时如果需要传递数组类型参数,得在数组外面再套一层Object数组,如下:

通过当前类 得到父类的字节码,调用getSuperclass:

		 Jerry jerry = Jerry.class.newInstance();
		 System.out.println(jerry.getClass().getSuperclass().getName());//getSuperclass得到父类字节码

反射操作数组,框架中经常有用到   

 1     public static void main(String[] args) throws Exception, SecurityException {
 2         
 3          Object obj="jerry";
 4          reflectObj(obj);//传的不是数组,直接打印 jerry
 5          Object obj2=new String[]{"a","b","c"};
 6          reflectObj(obj2);//传的是数组,循环打印结果
 7     }
 8 
 9     private static void reflectObj(Object obj) {
10         Class clazz = obj.getClass();
11         if(clazz.isArray())//如果传过来的对象是数组
12         {
13             int length = Array.getLength(obj);//Array是属于java.lang.reflect.Array包下的操作数组的反射类,getLength反射获取到数组长度
14             for (int i = 0; i < length; i++) {
15                 System.out.println(Array.get(obj, i));//反射打印数组的各个索引下的数据
16             }
17         }
18         else
19         {
20             System.out.println(obj); 
21         }
22         
23     }
View Code

 3、hashCode()和eqauls()的作用,一般这两个方法无需自己手写,在eclipse中按快捷键直接选择生成就行,如:

 1     @Override
 2     public int hashCode() {
 3         final int prime = 31;
 4         int result = 1;
 5         result = prime * result + ((age == null) ? 0 : age.hashCode());
 6         result = prime * result + ((name == null) ? 0 : name.hashCode());
 7         return result;
 8     }
 9 
10     @Override
11     public boolean equals(Object obj) {
12         if (this == obj)
13             return true;
14         if (obj == null)
15             return false;
16         if (getClass() != obj.getClass())
17             return false;
18         Jerry other = (Jerry) obj;
19         if (age == null) {
20             if (other.age != null)
21                 return false;
22         } else if (!age.equals(other.age))
23             return false;
24         if (name == null) {
25             if (other.name != null)
26                 return false;
27         } else if (!name.equals(other.name))
28             return false;
29         return true;
30     }
View Code

作用是:这两个方法都是搭配使用,作用主要是用来据此判断要存放对象在内存中地址值的哈希码,根据哈希码可以快速找到具体位置,这个是HashSet以及HashMap集合查询效率高的原因,hashCode()判断存放地址,equals判断存放地址上的对象和当前对象是否是同一个对象,如果是同一个就过滤掉,有了hashCode就可以快速定位到具体地址,找到该元素,可以提高查询速度,不然得一个个遍历地址查找,然后再用equals判断两对象是否相等,要想是hashCode生效,必须数据是存放在实现了hashCode的集合中,这样才能与hashCode集合算出的内存地址一一对应上,才能精确存放,不然普通集合如ArrayList根本没实现哈希算法,你把要存放的对象算出个hashCode有个毛用,算出来也不知道存哪里,所以得配合具体集合才能生效,所以在存放集合后,不要修改生成hashCode的字段值,不然会造成hashCode变化,这样就会找不到先前存放进集合的对象了,就好像人还在酒店先前房间,但是这时给你的门牌号变了,你根据变化后的门牌号再去找先前的房间肯定找不到,如下操作就会导致出现这种结果:

 当hashCode算出的值相同,但是对象equals比较却是两个不同对象,这时就是hashCode值冲突,会造成两个不同对象存放在同一个位置,这时后来的对象与先前对象组成链表,HashMap内部采用的就是这种单链表形式避免hashCode冲突的,其实还有另一张形式避免hashCode冲突,就是一看这个位置已经被占了,此时就算hashCode相同,也让其再去找槽位进行存放

 

  Hashmap里面的bucket出现了单链表的形式,散列表要解决的一个问题就是散列值的冲突问题,通常是两种方法:链表法和开放地址法。链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。java.util.HashMap采用的链表法的方式,链表是单向链表。  

 HashMap里面没有出现hash冲突时,没有形成单链表时,hashmap查找元素很快,get()方法能够直接定位到元素,但是出现单链表后,单个bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。

       当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 0.75,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap 的 get() 与 put() 方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。

4、克隆  

java里面的克隆分浅克隆和深克隆,浅克隆就是引用类型只复制一个引用拷贝,任何一方修改都会导致引用对象发生改变,深克隆就是引用类型各自独立,是持有另一份引用对象,要实现浅克隆只要实现Cloneable接口,重写clone方法就行,jvm会调用本地C代码实现拷贝,调用的时候直接Sheep sheep2 = sheep1.clone();就行,如果想实现深克隆那么简单的方法可以仍然实现Cloneable接口,重写clone方法的时候在方法内部自己new一份新的引用对象给拷贝对象,但这种方式如果有多个引用对象就会很繁琐,所以还有一种快捷的方式实现深克隆,就是采用对象序列化和反序列化,只要对象实现Serializable接口就行,无需实现Cloneable接口,深拷贝序列化代码如下:

 1     public static void main(String[] args) throws Exception {
 2         Sheep sheep1 = new Sheep("a");    
 3         
 4         DateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd");
 5         Date myDate1 = dateFormat1.parse("2009-06-01");
 6         sheep1.setBirthday(myDate1);
 7         
 8         System.out.println(sheep1);  
 9         System.out.println("=================");
10         
11         ByteArrayOutputStream bos=new ByteArrayOutputStream();
12         ObjectOutputStream oos=new ObjectOutputStream(bos);//先反序列化对象到字节数组流中
13         oos.writeObject(sheep1);
14         byte[] outArray = bos.toByteArray();
15         
16         
17         ByteArrayInputStream bis=new ByteArrayInputStream(outArray);
18         ObjectInputStream ois=new ObjectInputStream(bis);  //从字节数组流中读取到对象
19         Sheep sheep2=(Sheep) ois.readObject(); //拷贝了一份新对象
20         
21         
22         myDate1.setTime(dateFormat1.parse("2019-06-01").getTime());
23         sheep1.setBirthday(myDate1);//这时就算修改先前对象的引用对象值,新对象也不会发生变化
24         
25         System.out.println(sheep2);
26         
27     }
View Code

 5、读取属性文件:

属性文件是以properties结尾的文本文件,其是一个键值对形式,只不过是特殊的键值对,键和值都只能是字符串,读取属性文件可以通过如下方式实现:

方式一(不推荐): 

1          Properties properties=new Properties();
2          FileInputStream inStream=new FileInputStream("aa.properties");
3          //这种写法有个弊端就是相对工程路径,需要放置在与工程目录同级目录下才能找到,不然就会报错,或者使用绝对路径,所以实际项目中不这么干
4          properties.load(inStream);
5          inStream.close();
6          String myname = properties.getProperty("myname");
7          System.out.println(myname);
View Code

方式二,使用类加载器(推荐),但是仅限于读取,写操作不支持,不过配置文件一般都是用于读取:  

1         //InputStream inputStream = MyReflect.class.getClassLoader().getResourceAsStream("com/cb/test/myReflect/aa.properties");//从编译后的classes文件夹下查找
2         InputStream inputStream = MyReflect.class.getResourceAsStream("aa.properties");//getResourceAsStream是一个简便方法内部仍然调用的是getClassLoader,但是其使用的是相对当前类的路径加载,所以不需要从calsses下开始写路径
3         //如果想从classes下开始加载,那么可以写完整项目路径,从根目录下找/com/cb/test/myReflect/aa.properties,前面加/
4         Properties properties=new Properties();
5         properties.load(inputStream);
6         inputStream.close();
7         String myname = properties.getProperty("myname");
8         System.out.println(myname);
View Code

 6、内省

 内省也就是反射的简化版,主要是用来操作javabean的属性和方法的,一般属性对应有getXxx和setXxx方法,其命名规则是把字段名首字母大写加上get和set,通过内省机制老操作javabean的方式主要有如下几种:

 

 1     public static void main(String[] args) throws Exception {
 2         
 3         Jerry jerry=new Jerry("tom");
 4         String propertyName="name";
 5         PropertyDescriptor propertyDescriptor=new PropertyDescriptor(propertyName, Jerry.class);//PropertyDescriptor方法参数:操作哪个属性,操作哪个类
 6         //通过内省操作属性,不需要传入完整的getXxx()方法名,达到简便操作效果
 7         Method readMethod = propertyDescriptor.getReadMethod();//得到getXxx()方法
 8         String resVal = (String) readMethod.invoke(jerry);//调用get方法获取属性值
 9         System.out.println(resVal);
10         
11         Method writeMethod = propertyDescriptor.getWriteMethod();//得到setXxx()方法
12         writeMethod.invoke(jerry, "jerry");//set方法无返回值        
13         
14         System.out.println(jerry.getName());//再调用一次set改变后的值
15     }
View Code

一般内省机制,平常很少用得到,基本都是直接调用Javabean的get,set方法来操作属性值,内省机制主要在框架中用的多,因为框架没法知道具体的Javabean名,只能通过内省来操作所有Javabean,比较常用的第三方工具有BeanUtils,复杂点操作Javabean的内省方式可以通过获取整个Javabean信息然后进行遍历,Introspector上的静态方法getBeanInfo可以获取到beanInfo信息,不过不用自己用原生方式操作Javabean内省,一般借助第三方工具,如beanUtils,可以用这个工具实现两个对象的属性值拷贝,常用的是把一个对象属性值拷贝到新对象中操作新对象,而不动源对象

 1     public static void main(String[] args) throws Exception {
 2         //这种遍历的方式,就完全只适合框架开发了,会遍历出所有方法
 3         Jerry jerry=new Jerry("tom");
 4         BeanInfo beanInfo = Introspector.getBeanInfo(Jerry.class);
 5         MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();//只能得到所有属性方法,然后遍历
 6         for (MethodDescriptor methodDescriptor : methodDescriptors) {//会遍历出所有方法,包括基类的所有方法和属性,如tostring,hashCode等方法也会遍历出来,所以得具体过滤
 7             if(methodDescriptor.getName().equals("getName"))//过滤出具体方法
 8             {
 9                 Method method = methodDescriptor.getMethod();
10                    Object resval = method.invoke(jerry);
11                 System.out.println(resval);     
12             } 
13         } 
14     }
View Code

 7、注解

注解其实也就是一种特殊的类,自己定义注解和定义普通接口差不多,只不过需要加上@,JDK中默认提供了一些常用注解,如@SuppressWarnings,@Override等,注解和xml配置的效果一样,其原理也就是通过注解能拿到和在xml中配置的元信息一样的信息,这些信息被定义在注解类中,在这些注解类中定义有各种属性,在使用时需要为属性赋值,通过反射解析注解中的属性值,拿到元数据信息,然后就可以实现自己的业务逻辑,这个spring,struts等既可以通过xml配置,也可以通过注解实现,其原理就是这样的,反正都是拿到元数据信息,最终根据这些元数据信息去new对象或做其他业务操作,注解和xml只是传递这些元数据信息的不同方式而已,使用步骤图解如下:先定义注解,再使用注解,最后框架反射解析注解

注解的生命周期可以由自己通过JDK的注解指定,默认的生命周期范围是一个枚举值,其有3种取值范围,其指定也是通过JDK的一种注解@Retention()来指定,其取值范围有3个,RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME,分别为RetentionPolicy.SOURCE阶段只停留在源文件上,javac编译时会会用到,如@override重写检查,会在编译时进行检查,检查不通过会编译报错,检查通过后生成的class文件中会把此注解擦除;RetentionPolicy.CLASS会把注解保留到编译后的class中,会在类加载器加载前进行检查,检查通过后不会放入JVM字节码文件中,会在JVM字节码中擦除此注解;RetentionPolicy.RUNTIME会保留到JVM字节码文件中,会在程序调用时进行注解检查,此时如拿到一下元数据配置信息,可以在此步获取,这是最长的一步生命周期。具体自定义的注解需要在哪个阶段发生作用,就得定义不同生命周期范围的注解。

 注解的开发步骤如下:

1)、新建一个注解类:在eclipse中可以在新建中选择other选择annotation进行新建,生成的关键字的@interface,注解属性返回值支持8大基本类型,calss类型,枚举类型,其他不支持

2)、使用注解

3)、框架反射解析注解

 1 @Target({ElementType.METHOD,ElementType.TYPE}) //@Target注解说明JerryAnnotation这个自定义注解使用范围,是使用在类上,还是使用在方法上,其内部接收一个ElementType的枚举
 2 @Retention(RetentionPolicy.RUNTIME)//此步说明把注解留到运行时,不然默认会编译后进行注解擦除
 3 public @interface JerryAnnotation {
 4     //在注解接口中定义方法,也叫属性,可以在注解使用中传递值用,类似与Javabean中的get/set方法的合体
 5     String color() default "blue" ; //定义默认属性值,如果使用时没有输入,那么就使用默认值
 6     String value(); //如果使用时,注解中只用到一个属性,而此属性正好是value,那么可以省去value前缀,直接写属性值,但是有多个属性时,不能省去value名
 7     int[] arrInt();  //定义一个数组类型属性,使用时用{}包裹数组值
 8     Lamp lamp();  //定义枚举类型属性
 9     
10 }
11 //=========================================
12 //通过注解传递元数据信息
13 @JerryAnnotation(value="tom",arrInt={1,2,3},lamp=Lamp.GREEN)
14 public class AnnotationUse {
15     //此步是模拟使用自定义好的注解类,如spring的Contrller中使用的注解 
16 }
17 //=========================================
18     //此步模拟框架最终运行业务类是,根据判断是否加有注解来获取相应元数据信息进行下一步处理 
19     public static void main(String[] args) {
20         
21         //反射检查业务类上是否有@JerryAnnotation这个注解
22         if(AnnotationUse.class.isAnnotationPresent(JerryAnnotation.class))//走到此步的注解,其生命周期是runtime级别的
23         {
24             //如果有@JerryAnnotation这个注解,进入if内部,进行注解中元数据获取
25             JerryAnnotation jerryAnnotation = AnnotationUse.class.getAnnotation(JerryAnnotation.class);//从使用注解的类上反射获取到具体注解的实例
26             String color = jerryAnnotation.color();//从注解中获取到元数据信息
27             String value = jerryAnnotation.value(); //获取value属性值
28             int[] arrInt = jerryAnnotation.arrInt();//获取数组属性
29             Lamp lamp = jerryAnnotation.lamp();//获取枚举属性
30              
31         }
32         else
33         {
34             //读取xml配置信息
35         }
36         
37     }
View Code

 8、泛型:

 泛型主要作用就是在编译期进行类型检查,方便集合和类的操作,但是其泛型参数的生命周期也仅先于编译期,编译通过后生成的class文件中已经把泛型参数擦除,如果直接通过反射来操作泛型集合,那么由于calss文件中这时已经擦除泛型检查,而反射是操作jvm中的字节码,所以更加没有泛型约束了,可以插入任何Object类型值,如:

 1     public static void main(String[] args) throws Exception{
 2         
 3         ArrayList<Integer> list=new ArrayList<Integer>();
 4         list.add(1);//整数泛型集合编译期检查只能存放整数    
 5         //list.add("abc"); //直接插入字符串abc,由于有泛型检查,插入不成功
 6         list.getClass().getMethod("add", Object.class).invoke(list, "abc");//通过反射插入了字符串,越过了泛型检查,所以说泛型检查生命周期只在编译期
 7         for (Object obj : list) {
 8             System.out.println(obj);//打印出字符串 abc
 9         }
10          
11     }
View Code

 泛型通配符?表示任意类型,比如如下方法func(ArrayList<Object>)泛型检查的参数类型为Object就只能接收Object的泛型参数类型

 1     public static void main(String[] args) throws Exception{
 2         
 3         ArrayList<Integer> list=new ArrayList<Integer>();
 4         list.add(1);//整数泛型集合编译期检查只能存放整数    
 5         func(list); //传入参数类型报错
 6         //实际传入的集合泛型参数为Integer,如果从类的继承来说Object可以接收Integer,但是这种关系不适合泛型参数,泛型参数只做检查,
 7         //不关心他们的继承关系,既然不一样,那就不让存放         
 8     }
 9     
10     void func(ArrayList<Object> list)//方法定义的参数为Object
11     {
12         
13     }
View Code

如果想要泛型参数接收任意类型,这时就得用通配符?,把方法修改一下就可以存入任意类型的泛型参数了

 1     public static void main(String[] args) throws Exception{
 2         
 3         ArrayList<Integer> list=new ArrayList<Integer>();
 4         list.add(1);//整数泛型集合编译期检查只能存放整数    
 5         func(list); //传入成功
 6                   
 7     }
 8     
 9     static void func(ArrayList<?> list)//方法定义的参数为Object
10     {
11         
12     }
View Code

 但是通配符的范围毕竟太宽泛了,连个具体范围也不知道,如果这时想知道接收参数的大致范围,那么这时得确定泛型参数的上限和下限,采用extends表示接收的参数是泛型参数限定类型或其子类,而super表示表示接收参数是限定类型或此类型的父类,既然是当前类型父类,那么通用父类Object类型也接收

下限例子:

 1     public static void main(String[] args) throws Exception{
 2         
 3         ArrayList<Integer> list=new ArrayList<Integer>();
 4         list.add(1);//整数泛型集合编译期检查只能存放整数    
 5         func(list);//
 6         //=======================
 7         ArrayList<Object> list2=new ArrayList<Object>();
 8         list2.add("abc");
 9         func(list2); //由于Object是Integer的父类,故可以接收此中泛型类型的集合
10     }
11     
12     static void func(ArrayList<? super Integer> list) //接收泛型参数集合为Integer或Integer的父类
13     {
14          //内部由于是一个泛型范围,所以无法确定外部传入的具体类型,故方法内部无法调用需要具体泛型类型的add等方法,
15         //但是可以操作与泛型参数类型无关的方法,如size(),for遍历等
16     } 
View Code

上限例子:

 1     public static void main(String[] args) throws Exception{
 2         
 3         ArrayList<Integer> list=new ArrayList<Integer>();
 4         list.add(1);//     
 5         func(list);//
 6         //=======================
 7         ArrayList<Double> list2=new ArrayList<Double>();
 8         list2.add(3.4d);
 9         func(list2);//Double是Number的子类,故可以接收此类型集合
10         
11     }
12     
13     static void func(ArrayList<? extends Number> list) //接收泛型参数集合为Number或Number的子类
14     { 
15         //集合类型参数只限定集合类型,至于集合内部的数据操作,只能在外部操作,内部无法具体兼容各种类型,但是可以调用非泛型方法
16     }
View Code

 泛型方法必须在方法上加上<T>表示是一个泛型方法,泛型方法没办法限制参数类型,只能进行参数类型推断,所以不太好控,一般依靠使用者依法传入,或代码进行修正,但是类上的泛型,由于在new的时候就指定了泛型参数类型,所以起到了类型限制作用,但是泛型方法没法做到,但是可以加通配符限制范围

 1 public class GenericClass<T> {//类上的泛型有限制作用
 2 
 3     public T update(T t)
 4     {
 5         return t; //这不是泛型方法,只是根据类上泛型限制的具体参数类型,在类实例化时会进行确定
 6     }
 7     
 8     public <K,E> void add(K k,E e,T t)
 9     {
10         //泛型方法,必须在方法上显示指定泛型方法参数类型,用尖括号包裹用到的泛型,但是其无法进行类型限制,只能进行类型推断
11         //如果参数上显示指定了尖括号,那么就代表泛型方法,就算其参数和类泛型参数重名,也并不受类上泛型限制,两者是重名的独立参数类型,叫啥名无所谓,
12         //不过为了不引起混淆,最好定义自己的名称,如果需要类上泛型限制,那就不要加到尖括号中,直接使用类上的就行
13     }    
14 }
15 
16 //=====================================
17 
18     public static void main(String[] args) throws Exception{
19          
20         GenericClass<String> g=new GenericClass<String>();//实例化类是,指定了泛型参数类型为字符串
21         
22         g.add("ccc","2","33");//这是泛型方法使用,由于没法进行参数类型限制,
23         //所以第一,第二两个参数只能根据传过来的具体参数类型来进行推断,第三个参数由于使用了类上的泛型参数限制,所以只能传字符串
24         g.update("1");//这个是普通方法调用,由于类上限制了,也只能传字符串        
25         
26     }
View Code

 由于泛型类上泛型参数是在实例化时指定的,所以类中的静态方法不能使用泛型类上的泛型参数类型,因为加载时机不一样,所以只能自己指定泛型类型,改为泛型方法方式

 9、类加载器:

java程序是通过把JDK加载仅虚拟机变成二进制字节码来运行,负责把JDK和目标项目jar加载仅虚拟机的就是类加载器,java虚拟机默认提供3个类加载器,其中有一个不是java程序写的,是底层C++写的一个二进制加载器,在JVM启动时就会运行此加载器,这就是BootStrap类加载器,其负责加载其他类加载器ExtClassLoader和AppClassLoader,3个类加载器组成父子继承树状结构,如下,java也提供了类自定义类加载器的接口,只需要自己实现ClassLoader抽象接口实现自定义加载器,挂载到JVM的类加载器上,如tomcat就实现了很多类加载器并挂载到JVM中,在tomcat启动时就会让jvm也加载其需要的jar包:

 类加载器加载过程是先从当前线程类加载器出发进行加载,但是自己先不加载,而是继续往上寻找看是否有继承父类,如果有父类加载器,那么就让父类加载器去加载,依次往上委托父类加载,如果最顶层父类没有加载到,那么就用下一级类加载器加载,也就是最先干活的永远是祖宗,最后才是小辈,这种加载机制可以避免重复加载,而且对于JDK的加载也必须用顶层加载进来让下面人使用

原文地址:https://www.cnblogs.com/javabg/p/7402683.html