精华内容
下载资源
问答
  • 2018.4.19泛型泛型简述。[问题]1.发现ArrayList可以放入任意类型的数据,但是实际操作中发现数据类型 不一致会导致更多的错误。 2.就是知道取出的数据是一个String类型,但是还是要通过【强转】才能真正拿到想要...

    2018.4.19

    泛型

    泛型简述。

    [问题]

    1.发现ArrayList可以放入任意类型的数据,但是实际操作中发现数据类型
     	不一致会导致更多的错误。
     	2.就是知道取出的数据是一个String类型,但是还是要通过【强转】才能真正拿到想要的
     	String类型数据,这个操作很麻烦。

    【期望】

    集合中的数据类型能够统一。
     	数据类型一致化问题。

    【解决问题】

    泛型
     	java jdk1.5之后的新特征。
    
    public class Demo1 {
    	public static void main(String[] args) {
    		ArrayList<Object> list = new ArrayList<Object>();
    		
    		list.add(new Demo1());//自定义类对象
    		list.add("今天周四");
    		list.add(3);
    		
    		
    		System.out.println(list);
    	
    		Object object = list.get(1);
    		
    		String string = (String) object;
    		System.out.println(string.length());
    		System.out.println(string);
    		
    		Object[] arr = list.toArray();
    		
    		//Arrays.sort(arr);
    		
    		System.out.println(arr.toString());
    	}
    }
    
    结果:
        [genericity.Demo1@6486b4d5, 今天周四, 3]
        4
        今天周四
        [Ljava.lang.Object;@47ca3f82

    泛型实例

    使用泛型来解决之前遇到的问题
     		1.解决集合中数据类型一致化问题,要求保存什么数据就保存什么数据,添加其他数据的话报错。
     		异常提前。
     		2.从集合中取出数据,保存的是什么类型,拿出来的就是什么类型,不需要无意义的【强制类型转换】。
     		
     标准格式:
     		ArrayList<String> list = new ArrayList<String>();  <E>占位符。无意义
     以下情况也允许:
     	1.ArrayList list = new ArrayList<String>();
     	2.ArrayList<String> list = new ArrayList();
     	为了照顾不同的版本和不同IDE工具。
     	
    【 但是以下情况不允许】
    	ArrayList<Object> list = new ArrayList<String>();
    	ArrayList<String> list = new ArrayList<Object>();
    
    
    public class Demo2 {
    	public static void main(String[] args) {
    		//<String>这是泛型,要求这个ArrayList集合中有且只能保存String类型的数据
    		ArrayList<String>list = new ArrayList<String>();
    		
    		list.add("今天阿根廷被灌了6个球。");
    		list.add("西班牙真牛逼");
    		list.add("梅西提前离场了");
    		
    		
    		//这里无法保存除string类型之外的任意其他类型。
    		//list.add(new Demo2());
    		//list.add(1);
    		
    		String str = list.get(2);
    		System.out.println(str);
    	}
    }
    
    结果:
    
    梅西提前离场了

    泛型的运用

    需求:
    	定义一个方法,可以接受任意数据类型,而且要求返回的数据类型,就是你传入的数据类型。
    	例如:
    		传入String返回String
    		传入Demo3类型返回Demo3类型
    		
    泛型的使用需要:
    	占位符!<一个大写字母>,只是一个占位符,没有实际含义,而且不同地方定义的占位符没有联系。
    	
    	
    泛型在函数中使用的格式:
    	修饰符<声明的自定义泛型占位符> 返回值类型(可以使用自定义泛型) 函数名(形式参数列表“也可以使用泛型”) {
    		函数体
    		在函数体中,所有用到自定义泛型的地方,都可以被替换 
    	} 
    	
    包装类:
    	JAVA是完全面向对象的语言,在JAVA中万物皆对象,如果是要保存类对象,那么八大基本数据类型就无法使用,
    	所以JAVA提供了一个包装机制,包装基本数据类型,让他们成为类对象,自动封箱。
    	Integer --> int
    	Byte --> byte
    	Long --> long
    	Short --> short
    	
    	Double --> double
    	Float --> float
    	
    	Boolean --> boolean
    	
    	Character -> char
    	
    	如果使用包装类直接赋值给普通的基本数据类型,这个操作称之为拆箱。
    
    public class Demo3 {
    	public static void main(String[] args) {
    		String string = getType("String");
    		Demo3 demo3 = getType(new Demo3());
    		
    		int num = getType(5);//integer int的包装类。
    		
    	}

    自定义泛型的占位符<E>

    <E>是自定义泛型的占位符,表示在该函数中可以使用占位符E,而E的具体数据类型,由传入参数控制
     这样操作可以让函数多样化,多元化,代码更加简单。
    
    
    public static <E> E getType(E e) {
    	
    	return e;
    }

    }

    类内使用泛型。

    在类内使用泛型。

    格式:
    
    	class 类名<自定义泛型的占位符> {
    		//在这里所用的泛型和用户创建对象时声明的一致。
    	}
    	
    注意事项:
    	1.一个类声明的自定义泛型,如果在创建类对象时定了泛型的具,确体数据类型,那么在整个类内所有用到
    	该泛型占位符的非静态成员方法,使用的数据类型都是创建时创建的类型。
    	
    	2.如果创建使用了自定义泛型的类对象,但是没有确定泛型的具体类型,那么编译器会把这个
    	泛型认为是Object类型。
    	
    	3.类中声明的自定义泛型,不能在内的静态方法使用,如果想让静态方法使用泛型,自己声明自己使用
    	类似于方法中使用泛型。
    	
    	4.建议:如果在代码中出现了多个使用泛型的地方,请使用多个名字。 T E
    
    class InvalidArrayException extends Exception {
    
    	public InvalidArrayException(String message) {
    		super(message);
    	}
    }
    
    class invalidComparatoraException extends Exception {
    	public invalidComparatoraException(String message) {
    		super(message);
    	}
    }
    
    class ArrayTools<A> {
    	/**
    	 * 利用泛型来满足不同数据类型的排序算法,可以在创建类对象时约束!!
    	 * @param array A类型,泛型的数组,可以是任意类型
    	 * @param com	<? super A>是A类型的比较器或者其父类的比较器
    	 * @throws InvalidArrayException	数组无效异常
    	 * @throws invalidComparatoraException	比较器无效异常
    	 */
    	public void selectSortUsingCompare(A[] array,Comparator<? super A> com) throws InvalidArrayException, invalidComparatoraException {//?在A之上。
    		//参数合法性判断
    		if(null == array || array.length == 0) {
    			throw new InvalidArrayException("数组无效");
    		}else if(null == com) {
    			throw new invalidComparatoraException("比较器无效");
    			
    		}
    		for (int i = 0; i < array.length - 1; i++) {
    			int index = i;
    			
    			for(int j = i + 1;j <array.length;j++) {
    				if(com.compare(array[index], array[j]) > 0) {
    					index = j;
    				}
    			}
    			if (index != i) {
    				A tempA = array[index];
    				array[index] = array[i];
    				array[i] = tempA;
    			}
    		}
    	}
    	
    	public void printArray(A[] array) {
    		for (A a : array) {
    			System.out.println(a);
    		}
    	}
    • 静态成员方法加载的比类早,所以类声明的泛型是在创建类的时候需求,是不能约束静态方法的,是无关的,,可以在静态方法中自己声明泛型
    public static <T> void test(T a) {
    		System.out.println(a);
    	}
    	
    }
    public class Demo4 {
    	public static void main(String[] args) 
    			throws InvalidArrayException, invalidComparatoraException {
    		Integer[] array = {1,3,5,6,7,8,2,4};
    		
    		ArrayTools<Integer> tools = new ArrayTools<Integer>();//
    		
    		//匿名内部类的匿名对象。  心脏是成员变量描述合适还是成员方法描述合适? 发动机对于汽车? 都不是,属于成员内部类。
    		tools.selectSortUsingCompare(array, new Comparator<Integer>() {
    
    			@Override
    			public int compare(Integer o1, Integer o2) {
    				
    				return o1 - o2;
    			}			
    		});
    		
    		tools.printArray(array);
    		
    	}
    }

    结果:

    1
    2
    3
    4
    5
    6
    7
    8

    HashMap

    ---| Collection

    ------| List(接口)

    ---------| ArrayList(实现类)

    ---------| LinkedList(实现类)

    ------| Set(接口)

    ---------| HashSet(实现类)

    ---------| TreeSet(实现类)

    比较器:
    		Comparable接口 实现 compareTo方法
    		Comparator接口 实现 compare方法
    				
    生活中,有关系的数据更多一点
    	账号 密码
    	钥匙 锁
    	英文 解释
    	
    	NSDictionary Objective-C Next Step公司。
    	
    	---| Map<K,V> 双列集合,这是一个接口 Key值不能重复
    	------| HashMap 实现类
    	------| TreeMap 实现类
    	
    	K:key 键! 是一个不允许重复的唯一值。
    	V: Value 值 一个键(key)对应一个值(value) 可以重复的
    	
    	在Map<K,V> 双列集合中,保存的只能是一个键(key)值(value)对。
    	
    	
    	Map中要学习的方法:
    		增
    			put(K key, V value);//添加一个键(K)值(value)对
    			putAll(Map<? extends K,? extends V> map);//添加一个符合数据类型的Map双列集合
    		删
    			remove(Object key);//根据key删除对应的键值对。
    			clear();//清空所有键值对。
    		改
    			put(K key,V value);//当键key存在的时候,这个操作时重新修改值。size();//获取键值对个数
    			get(Object key);//通过键获取对应的值value。
    			containsKey(Object key);//查看这个key是否在Map中存在。
    			containsValue(Object value);//查看这个value是否在map中存在
    			
    			keySet();//返回所有键(key)的set的集合
    			values();//返回所有值(value)的Collection集合
    
    public class Demo1 {
    	public static void main(String[] args) {
    		Map<String, String>map = new HashMap<String,String>();
    		
    		//使用put(k,v)添加元素
    		map.put("薛之谦", "高磊鑫");
    		map.put("鹿晗", "关晓彤");
    		map.put("宋仲基", "宋慧乔");
    		map.put("余文乐", "王棠云");
    		map.put("王宝强", "马蓉");
    		
    		System.out.println(map);
    		
    		Map<String,String> map2 = new HashMap<String,String>();
    		
    		map2.put("科比", "瓦妮莎");
    		
    		//添加另一个Map
    		map.putAll(map2);
    		System.out.println(map);
    		
    		//清空当前map双列集合
    		map2.clear();
    		System.out.println(map2.isEmpty());
    		System.out.println(map2);
    		
    		//根据Key删除对应的键值对
    		map.remove("科比");
    		System.out.println(map);
    		
    		//当key值存在时,这个操作是修改对应的value
    		map.put("王宝强", null);
    		System.out.println(map);
    		
    		System.out.println(map.size());
    		
    		System.out.println(map.containsKey("谢霆锋"));
    		System.out.println(map.containsKey("薛之谦"));
    		
    		System.out.println(map.containsValue("高磊鑫"));
    		System.out.println(map.containsValue("王菲"));
    		
    		System.out.println(map.get("科比"));
    		System.out.println(map.get("鹿晗"));
    		
    		Set<String> set = map.keySet();
    	
    		for (String string : set) {
    			System.out.println(string);
    		}
    		System.out.println("........................");
    		Collection<String> c = map.values();
    		for (String string : c) {
    			System.out.println(string);
    		}
    		System.out.println(c.toString());
    	}
    }

    MAP集合遍历

    public class Demo1 {
    	public static void main(String[] args) {
    		HashMap<String, Integer> map = new HashMap<String,Integer>();
    		
    		map.put("macpro",28888);
    		map.put("iphoneX", 8300);
    		map.put("ipad pro",5190);
    		
    		System.out.println(map);
    		
    		//第一种遍历方式,借助于keySet
    		
    		Set<String> set = map.keySet();
    		
    		//使用Set集合的Iterator迭代器
    		Iterator<String> it = set.iterator();
    		while(it.hasNext()) {
    			String key = it.next();//获取每一个map中key值
    			int value = map.get(key);
    			
    			System.out.println(key+"="+value);
    			
    		}
    		//以上方法,其实不太合适,获取的是key值,再借助于Map里面的get方法,获取相应的value
    		//并没有获取到完整的键值对。
    		
    		//第二种方式,借助于values
    		Collection<Integer> c = map.values();
    		
    		for (Integer i : c) {
    			System.out.println(i);
    		}
    		
    		//以上方法不合适,只能拿到value,不能获得Key
    		
    		//在java中,万物皆对象。[]
    		
    		/*
    		 这里把键值对认为是一个对象组成一个类,称之为Entry。 
    		 
    		 class Entry<k,v> {
    		 	K key;
    		 	V value;
    		 }
    		 
    		 //这里可以认为在map集合中,保存的每一个键值对都是一个entry对象,把这些entry对象获取出来,
    		   作成一个集合,进行遍历。
    		   entrySet();
    		   map.Entry
    		 */
    		Set<Entry<String,Integer>> entrySet = map.entrySet();
    		
    		Iterator<Entry<String,Integer>> it2 = entrySet.iterator();
    		
    		while (it2.hasNext()) {
    			System.out.println(it2.next());
    		}
    	}
    }

    泛型复习--------错误提前

    为了解决数据类型一致化的问题
    避免没有意义的强制类型转换。
    
    自定义泛型使用的格式
    	<大写字母> 一般用T E。
    占位符 没有任何含义。
    
    泛型在函数中的使用
    		格式:
    		权限修饰符<自定义泛型> 返回值类型(可以使用泛型) 函数名(形式参数列表“自定义泛型”) {
    				同样可以使用泛型
    		}
    
    泛型在类中使用
    		格式:
    		class 类名<自定义泛型> {
    			非静态的成员变量或者方法都可以使用类中定义的<自定义泛型>
    			
    			静态方法不能使用类中自定义泛型,但是可以方法中自己定义泛型
    		}
    		
    		Array.sort(T[] t,Comparator<? super T> c)
    		
    泛型在接口中的使用
    		格式:
    		interface 接口名<自定义泛型> {
    				//成员变量 缺省属性: public static final 必须初始化因为final不可变
    				//成员方法 缺省属性: abstract
    		}
    		一个类遵从带有自定义泛型的有两种方式:
    			例如:
    				interface A<T> {
    					public void testA(T t);
    				}
    				
    			1.方式1class Test1<T> implements A<T> {
    					public void testA(T t) {
    					
    					实现方法
    					}
    				}
    			更加自由 在创建类对象时,才对泛型进行约束。	
    			
    			2.方式2class Test implements A<String> {
    					public void testA(String t) {
    							//实现方法
    						}
    				}
    			遵从接口时,接口直接确定了泛型的具体类型。
    			
    	
    	泛型的上下限:
    		<? super T>
    			表示数据类型是T对象或者是其父类对象
    		<? extends T>
    				表示数据类型是T对象或者其子类对象
    				
    ##Map
    	Map<K,V> K不可重复,V可重复 1,1对应。
    	---| HashMap
    	---| TreeMap
    	
    	put(K key, V value);
    	putAll(map<? extends k,? extends v> map);
    	
    	clear();
    	remove(Object k);
    	
    	size();
    	containsKey(object Key);
    	containsValue(Object value);
    	
    	keySet();
    	values();
    	
    	get(Object l);
    展开全文
  • 自定义泛型类、泛型方法、泛型参数编写一段泛型程序来实现LRU缓存?1. 什么是泛型泛型 指的是可以将类型作为参数进行传递,其本质上就是类型参数化。比如:我们平时定义一个方法的时候,常会指定要传入一个具体类...

    flag:

    泛型是什么?

    使用泛型的好处是什么?

    java中的泛型是如何工作的?关于类型擦除的原理?

    泛型中的通配符有哪些?有什么区别和作用?

    自定义泛型类、泛型方法、泛型参数

    编写一段泛型程序来实现LRU缓存?

    1. 什么是泛型

    ​ 泛型 指的是可以将类型作为参数进行传递,其本质上就是类型参数化。比如:我们平时定义一个方法的时候,常会指定要传入一个具体类对象作为参数。而如果使用泛型,那么这个具体传入类的对象,就可以指定为某个类型,而不必指定具体的类。也就是我们将某个类型作为参数进行传递了。

    //普通方法

    public void testValue(String s) {}

    //泛型方法

    public void testValue(T t) {}

    2.为什么要有泛型

    关于使用泛型与使用 Object 有什么区别?

    ​ 如果我们使用Object,就要将传入的类型强制转换成我们需要的类型,如果 传入的类型不匹配 将会导致程序包 ClassCastException 异常。

    泛型在集合中的使用为例:

    public class genericity {

    public static void main(String[] args) {

    List list = new ArrayList();

    list.add("aaa");

    list.add("bbb");

    list.add("ccc");

    list.add("ddd");

    // 不使用泛型可能会存在数据安全问题

    list.add(111);

    list.add(new Object());

    }

    }

    List list2 = new ArrayList();

    list2.add(111); // 声明为 String 类型时,赋值为 111 会报错

    为什么要有泛型(Generic)?

    解决元素存储的安全性问题

    解决获取数据元素时,需要类型强转的问题

    图解上述代码:

    6e93890f1b35db9ba466ac0337193131.png

    372e1e2f69f06f4f1a247debd808eea7.png

    在以上例子中,我们可以总结出,使用泛型的一些好处:

    它可以避免类型强制转换,而引起的程序异常。

    可以使代码更加简洁规范。

    是代码更加灵活,可定制型强。

    最重要的一点: 解决了传参类型的安全性问题 。 在声明了 List 后只有指定类型才可以 进行传参,实现类型安全**,并且相对的,如果取值也使用该泛型方式,则可以实现 读取出来的对象不需要强转 ,达到快速便捷的目的。**

    3. Java的泛型是如何工作的?

    ​ 泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。

    ​ 当泛型被擦除后,他有两种转换方式,第一种是如果泛型没有设置类型上限,那么将泛型转化成Object类型,第二种是如果设置了类型上限,那么将泛型转化成他的类型上限。

    ​ 例如 List 在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。

    //未指定上限

    public class Test1 {

    T t;

    public T getValue() {

    return t;

    }

    public void setVale(T t) {

    this.t = t;

    }

    }

    //指定上限

    public class Test2 {

    T t;

    public T getT() {

    return t;

    }

    public void setT(T t) {

    this.t = t;

    }

    }

    //通过反射调用获取他们的属性类型

    @Test

    public void testType1() {

    Test1 test1 = new Test1<>();

    test1.setVale("11111");

    Class extends Test1> aClass = test1.getClass();

    for (Field field : aClass.getDeclaredFields()) {

    System.out.println("Test1属性:" + field.getName() + "的类型为:" + field.getType().getName());

    }

    Test2 test2 = new Test2();

    test2.setT(new ArrayList());

    Class extends Test2> aClass2 = test2.getClass();

    for (Field field : aClass2.getDeclaredFields()) {

    System.out.println("test2属性:" + field.getName() + "的类型为:" + field.getType().getName());

    }

    }

    上面方法打印的结果:

    Test1属性:t的类型为:java.lang.Object

    Test1属性:this$0的类型为:com.company.genericity

    test2属性:t的类型为:java.util.List

    test2属性:this$0的类型为:com.company.genericity

    4.泛型晋级使用(泛型与继承的关系)

    继承关系

    即设置泛型上限,传入的泛型必须是Parent类型或者是他的子类

    public void testType(T t) {}

    依赖关系的使用

    泛型间可以存在依赖关系,比如下面的C是继承自E。即传入的类型是E类型或者是E类型的子类

    public void testDependys(E e, C c) {}

    5. 泛型中的通配符有哪些?有什么区别和作用?

    当我们不知道或者不关心实际操作类型的时候我们可以使用 无限通配符 ,当我们不指定或者不关心操作类型,但是又想进行一定范围限制的时候,我们可以通过添加 上限 或 下限 来起到限制作用。

    无限通配符

    无限通配符表示的是未知类型,表示不关心或者不能确定实际操作的类型,一般配合容器类使用。

    public void testV(List> list) {}

    需要注意的是: 无限通配符只能读的能力,没有写的能力。

    public void testV(List> list) { Object o = list.get(0); //编译器不允许该操作 // list.add("jaljal");}

    上面的List>为无限通配符,他只能使用get()获取元素,但不能使用add()方法添加元素。(即使修改元素也不被允许)

    定义了上限,其只有读的能力。此方式表示参数化的类型可能是所 指定的类型 ,或者是 此类型的子类 。

    //t1要么是Test2,要么是Test2的子类public void testC(Test1 extends Test2> t1) { Test2 value = t1.getValue(); System.out.println("testC中的:" + value.getT());}

    定义了下限,有读的能力以及部分写的能力,子类可以写入父类。此方式表示参数化的类型可能是 指定的类型 ,或者是 此类型的父类

    //t1要么是Test5,要么是Test5的父类public void testB(Test1 super Test5> t1) { //子类代替父类 Test2 value = (Test2) t1.getValue(); System.out.println(value.getT());}

    通配符不能用作返回值

    如果返回值依赖类型参数,不能使用通配符作为返回值。可以使用类型参数返回方式:

    public T testA(T t, Test1 test1) { System.out.println("这是传入的T:" + t); t = test1.t; System.out.println("这是赋值后的T:" + t); return t;}

    要从泛型类取数据时,用extends;

    要往泛型类写数据时,用super;

    既要取又要写,就不用通配符(即extends与super都不用)。

    泛型中只有通配符可以使用super关键字,类型参数不支持 这种写法

    6. 什么时候使用通配符

    什么时候使用通配符

    通配符形式和类型参数经常 配合使用

    类型参数 的形式都可以 替代 通配符的形式

    能用通配符的就用通配符 ,因为通配符形式上往往更为 简单 、 可读性也更好 。

    类型参数之间有 依赖关系 、 返回值 依赖类型参数或者需要 写操作 ,则只能用 类型参数 。

    源码中对泛型的使用可以参考 Collections 类的一些方法:

    public static > void sort(List list)​public static void sort(List list, Comparator super T> c)​public static void copy(List super T> dest, List extends T> src)​public static T max(Collection extends T> coll, Comparator super T> comp)

    7. 编写一段泛型程序来实现LRU缓存?

    ​ 这里使用 LinkedHashMap 来实现。

    ​ LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满 了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put() 和putAll()调用来删除最老的键值对。

    class LRUCacheLinkedHashMap extends LinkedHashMap{​ private int cacheSize;​ public LRUCacheLinkedHashMap(int cacheSize) {​ // true 表示让 linkedHashMap按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。 super(16, 0.75F, true); this.cacheSize = cacheSize; }​ @Override protected boolean removeEldestEntry(Map.Entry eldest) {​ // 当map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 return size()> cacheSize; }}

    展开全文
  • Java泛型,枚举,注解

    2019-06-12 22:57:08
    Java基础7(泛型,枚举,注解) 1 泛型 1.1 什么是泛型 泛型:即参数化类型。在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。 解决集合中的两个问题:(1)元素存储的安全问题(添加...

                                          Java泛型,枚举,注解

    1 泛型

    1.1 什么是泛型

    泛型:即参数化类型。在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时确定。

    集合类在声明阶段不能确定这个容器是存储什么类的对象,在JDK1.5之前只能把元素的类型设计为Object,JDK1.5之后采用泛型类解决。这个时候除了元素的类型不确定其他部分是确定的,如元素如何保存,如何管理等,所以把元素的类型设计成一个参数,这个类型参数就是泛型。常见的List<E>,ArrayList<E>中的<E>就是泛型。

    JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

    为什么要有泛型呢 ,解决集合中的两个问题:(1)元素存储的安全问题(添加元素时,检查添加元素的类型),比如商品标签不会搞混;(2)元素的类型转换,问题泛型可以起到对类的限定作用,获取数据元素时,就不需要类型强制转换 。

    1.2 泛型的特性

    泛型只有在编译阶段有效(在编译期正确检验泛型结果后,将泛型的相关信息擦除,并在对象进入和离开方法的边界处添加类型检查和类型装换方法),不会进入到运行时阶段

    List<String> string1 = new ArrayList<String>();
    List<Integer> integer1 = new ArrayList<Integer>();
    
    Class classString1 = string1.getClass();
    Class classInteger1 = integer1.getClass();
    
    if(classString1.equals(classInteger1)){
        System.out.println("类型相同")
    }
    //输出结果为:类型相同

    1.3 泛型的使用

    泛型类:用于类的定义中

    //此处T为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型,在实例化泛型类时,必须指定T的具体类型
    public class Dao<T> {
        //name这个成员变量的类型为T,T的类型由外部指定
        private T name;
        //泛型构造方法形参的类型也为T,T的类型由外部指定
        public Dao(T name) {
    		this.name = name;
    	}
    	//添加,方法的形参值类型为T,T的类型由外部指定
    	public void add(T u) {
    		//添加数据 
    	}
    	//查询多个
    	public List<T>  query() {
    		//查询数据库
    		return null;
    	}
    	//修改
    	public boolean modificate(T u) {
    		//修改操作
    		return true;
    	}
    }

    在使用泛型的时候传入泛型实参,根据传入的实参做相应的限制;如果不传入泛型的实参,在泛型类中使用泛型的方法或成员变量定义的类型可以为任意类型。

    Dao d1 = new Dao("String");
    Dao d2 = new Dao(9);
    Dao d3 = new Dao(6.6);

    注意:泛型的类型参数只能是类类型,不能是简单类型;不能对确切的泛型类型使用instanceof操作。

    if(values instanceof List<T>) {  //这是错误的,应该是(values instanceof List)
    	List<T> list = (List<T>)values;
    	return list;
    }

    泛型接口:泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

    public interface Genericity<T> {
    	
    	//添加
    	void add(T u);
    	//查询多个
    	List<T>  getUser();
    	//修改
    	boolean updateUser(T u);
    	
    }
    

    泛型方法:调用方法,决定参数返回值或类型。格式:[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称])

    泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

    public class Dao<T>{
        String T name;
        //定义泛型方法 
        //public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
        //)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
        public <T> E[] show(T[] e) {
           	return e;
        }
        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
              System.out.println(t.toString());
        }
    
      
        public <S,E>  E show3(S s,E e) {
            return e;
        }
      /** 
         * 这才是一个真正的泛型方法。
         * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
         * 这个T可以出现在这个泛型方法的任意位置.
         * 泛型的数量也可以为任意多个 
         *    如:public <T,K> K showKeyName(Generic<T> container){
         *        ...
         *        }
         */
        //虽然在方法中使用了泛型,但是这并不是一个泛型方法。这只是类中一个普通的成员方法,只不过他的            返回值是在声明泛型类已经声明过的泛型。所以在这个方法中才可以继续使用 T 这个泛型。
        public T getName(){
            return Name;
        }
    
            
        //这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
        public E setName(E Name){
            this.Name= Name;
        }
            */
        }
    
      
        //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Dao<Number>这个泛型类做形参而已。
        public void showNameValue1(Dao<Number> obj){
           
        }
    
        //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
        //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
        public void showKeyName2(Dao<?> obj){
          
        }
    
         /**
         * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
         * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
         * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
        public <T> T showKeyName(Dao<E> container){
            ...
        }  
        */
    
    
    }

    泛型方法与可变参数

    public <T> void printMsg( T... args){
        for(T t : args){
            System.out.println("t is " + t);
        }
    }

    静态方法与泛型。如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

    public class StaticGenerator<T> {
        /**
         * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
         * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
         * 如:public static void show(T t){..},此时编译器会提示错误信息:
              "StaticGenerator cannot be refrenced from static context"
         */
        public static <T> void show(T t){
    
        }
    }

    1.4 泛型通配符

    IngeterNumber的一个子类,但是List<Ingeter>和List<Number>是平行关系,List<Ingeter>不能看做是List<Number>的子类。因此通配符产生了。类型通配符一般是使用?代替具体的类型实参,而不是类型形参 。可以把?看成所有类型的父类。是一种真实的类型。

    1.5 泛型继承关系

    (1)List<?> 是List<A>,List<B>等类型的父类,List<A>和List<B>是平行关系

    (2)List<?> 可以遍历,但不可以修改

    (3)List<? extends Number> 可以小于或等于Number

    (4)List<? super Number> 可以大于或等于Number

     

    2 枚举

    枚举的全称为 enumeration,用于创建有限个对象。创建枚举类型要使用 enum 关键字。枚举类型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。

    在什么情况下适合用枚举:在一个类的对象有限且固定的时候,如星期:Monday(星期一)、......、Sunday(星期天) 。当需要定义一组常量时,强烈建议使用枚举类

    2.1 枚举类

    枚举类的实现,JDK1.5之前需要自定义枚举类。JDK 1.5 新增的 enum 关键字用于定义枚举类 。

    为什么不用静态常量代替枚举?(1)类型不安全,如定义一个季节的int类型的静态常量,开发者可以传入任何int类型的参数,如果是枚举类型的话就只能传入枚举类型的类中包含的对象;(2)没有命名空间,开发者要在命名的时候加个见名知意的前缀才知道这四个常量分别代表季节。

    若枚举只有一个对象, 则可以作为一种单例模式的实现方式。枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰。枚举类的使用 private final 修饰的属性应该在构造器中为其赋值。若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数

    (1)自定义枚举类

    /**
     * 步骤:
     * 1 私有化类的构造器,保证不能在类的外部创建其对象
     * 2 在类的内部创建枚举类的实例。声明为:public static final
     * 3 对象如果有实例变量,应该声明为private final,并在构造器中初始化
     */
    
    class TestEnum {
        private final String name;
        private TestEnum(String name) {
            this.name = name;
        }
        public static final TestEnum SPRING = new TestEnum("春天");
        public static final TestEnum SUMMER = new TestEnum("夏天");
        public static final TestEnum AUTUMN = new TestEnum("秋天");
        public static final TestEnum WINTER = new TestEnum("冬天");
    }

     

    (2)使用enum定义枚举类

    注意:①使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再 继承其他类;②枚举类的构造器只能使用 private 权限修饰符 ;③枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的 实例系统会自动添加 public static final 修饰;④必须在枚举类的第一行声明枚举类对象。

    枚举类可以定义属性和方法,可以是静态的也可以是非静态的。例子如下

    public enum TestEnum {
        SPRING("春季"),SUMMER("夏季"),FALL("秋季"),WINTER("冬季");
        
        private final String name;  
        private TestEnum(String name){
            this.name = name;
        }
        public String getName() {
            return name;
        }
    }

    注意:(1)第一行写枚举类实例的时候,默认是调用了构造器的;(2)构造器需定义成私有的,就不能在在其他类中生成该对象;(3)枚举类应该要设计成不可变类,所以字段用private final修饰。

    2.2 枚举实现接口

    枚举实现接口与普通类是一样的,可以实现一个也可以实现多个,需要重写接口中的方法,如果没有完全实现,需要定义成抽象的,可以不需要显示添加abstract修饰,系统会默认加上。

    public enum TestEnum implements Paly {
        SPRING("春季"),SUMMER("夏季"),FALL("秋季"),WINTER("冬季");
        
        private final String name;  
        private TestEnum(String name){
            this.name = name;
        }
        public String getName() {
            return name;
        }
    	@Override
    	public void setPaly() {
    		System.out.println("实现接口");
    	}
    }
    interface Paly {  
        public void setPaly();  
    } 

    上述代码只是为了说明枚举的功能,重点在下面的代码。枚举的常量值本质就是枚举对象,那么我们可以采用匿名内部类。

    public enum TestEnum implements Paly {
        SPRING(){
    		@Override
    		public void setPaly() {
    			System.out.println("春季踏青");
    		}   	
        },
        SUMMER(){
    		@Override
    		public void setPaly() {
    			System.out.println("夏季游泳");
    		}   	
        },
        FALL(){
    
    		@Override
    		public void setPaly() {
    			System.out.println("秋季收获");
    		}    	
        },
        WINTER(){
    		@Override
    		public void setPaly() {
    			System.out.println("冬季堆雪人");
    		} 	
        };  
    }
    interface Paly {  
        public void setPaly();  
    }  

    2.3 Switch语句中的枚举

    Java5新增了enum关键字,同时扩展了switch,case表达式中直接写入枚举值,不需加入枚举类作为限定。

    public class TestEnumSwitch {
    	    public void play(TestEnum t){
    	        switch(t){
    	        case SPRING:
    	            System.out.println("春");
    	            break;
    	        case SUMMER:
    	            System.out.println("夏");
    	            break;
    	        case FALL:
    	            System.out.println("秋");
    	            break;
    	        case WINTER:
    	            System.out.println("冬");
    	            break;
    	        }
    	    }     
    }

     

    3 注解

    从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解) 。

    什么是注解:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。简单的理解为:代码的一个特殊标记,可以在编译,加载,运行时读取,程序可以在不改变原有的代码的基础上可以在文件嵌入补充信息。

    注解可以修饰:包,类,方法,构造器,参数,局部变量。这些信息被保存在 Annotation的“name=value” 对中。

    3.1 注解的定义

    注解通过 @interface关键字进行定义,和 classs 和 interface 一样,注解也属于一种类型。自定义注解自动继承了java.lang.annotation.Annotation接口。

    Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。

    public @interface MyAnnotation {
    }

    注解的的使用方法是在类定义的地方的上面加上@MyAnnotation,如下

    @MyAnnotation
    public class Test {
    }

    注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。注解中属性可以有默认值,默认值需要用 default 关键值指定

    @Target({TYPE, FIELD, METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
    	String name();
    	int age() defalut 20;
    }

    随属性赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开。如下

    @MyAnnotation(name="aa",age=18)
    public class Test {
    
    }

    注意:如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。要是没有任何属性的话可以把括号省略

    public @interface MyAnnotation {
    	String name();
    }
    
    @MyAnnotation("cc")
    public class Test{
    }
    ------------------------------------
    
    public @interface MyAnnotation {
    }
    
    @MyAnnotation
    public class Test{
    }

    3.2 常见注解使用案例

    (1)生成文档相关的注解

    @author标明开发该类模块的作者,多个作者之间使用,分割

    @version 

    标明该类模块的版本
    @see参考转向,也就是相关主题
    @since从哪个版本开始增加的
    @param对方法中某参数的说明,如果没有参数就不能写
    @return对方法返回值的说明,如果方法的返回值类型是void就不能写
    @exception对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不能写其中

    @param @return 和 @exception 这三个标记都是只用于方法的。@param的格式要求:@param 形参名形参类型 形参说明 @return 的格式要求:@return 返回值类型返回值说明@exception的格式要求:@exception 异常类型异常说明@param和@exception可以并列多个

    /**
     * @author zs
     * @version 1.0
     * @see String
     */
    public class DocAnnotation {
        /**
         * @param args String[] 命令行参数
         */
        public static void main(String[] args) {
    
        }
    
        /**
         *
         * @param name 名称
         * @return String
         */
        public static String getName(String name){
            return "My name is "+name;
        }
    }

    (2)在编译时进行格式检查(JDK内置的三个基本注解)

    @Override限定重写父类方法, 该注解只能用于方法
    @Deprecated用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
    @SuppressWarnings抑制编译器警告
    public class AnnotationTest {
        public static void main(String[] args) {
            @SuppressWarnings("unused")
            String str = "zs";
        }
        @Deprecated
        public void deprecatedMethod(){
            System.out.println("已过时");
        }
        @Override
        public String toString(){
            return "重写";
        }
    }

    (3)跟踪代码依赖性,实现替代配置文件功能

    Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署

    @WebServlet("/login")
    public class LoginServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.doGet(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.doPost(req, resp);
        }
    }

    代替

    <servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>com.servlet.LoginServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>LoginServlet</url-pattern>
    </servlet-mapping>

     

    3.3 元注解

    元注解就是可以注解到注解上的注解,也可以理解为元注解是一种基本注解,但是它可以运用到其他注解上。

    元注解有:@Target,@Retention,@Documented,@Inherited,@Repeatable 5种

    (1)@Target:target是目标的意思,@Target指定注解可以运用的地方。他的取值如下:

    例:@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})

    ElementType.TYPE给一个类型进行注解,比如类、接口、枚举
    ElementType.FIELD可以给属性进行注解
    ElementType.METHOD可以给方法进行注解
    ElementType.PARAMETER 可以给一个方法内的参数进行注解
    ElementType.CONSTRUCTOR可以给构造方法进行注解
    ElementType.LOCAL_VARIABLE可以给局部变量进行注解

    ElementType.ANNOTATION_TYPE

    可以给一个注解进行注解
    ElementType.PACKAGE可以给一个包进行注解

    (2)@Retention:retention的意思为保留,@Retention指定注解的存活时间,他的取值如下

    例:@Retention(RetentionPolicy.SOURCE)
    RetentionPolicy.SOURCE注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
    RetentionPolicy.CLASS注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
    RetentionPolicy.RUNTIME注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

    (3)@Documented:他的作用是能够将注解中的元素包含到 Javadoc 中去。

    (4)@Inherited:Inherited的意思为继承。他的作用是:如果一个超类被一个被@Inherited注解注解过的注解进行注解的话,如果子类没有被任何注解应用,子类就继承了超类的注解。

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @interface Inherit {}
    
    @Inherit
    public class A {}
    public class B extends A {}
    
    //注解Inherit被@Inherited 修饰,之后类A被Inherit注解,类B继承A,类B也拥有Inherit这个注解。

    (5)@Repeatable:repeatable是可重复的意思,jdk1.8才加进来的。在需要对同一种注解多次使用时,往往需要借助@Repeatable。比如人有多中身份把身份当成注解

    //先声明一个Persons类用来包含所有的身份
    @interface Persons {
    	Person[]  value();
    }
    
    //@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个用来保存该注解内容的容器。
    @Repeatable(Persons.class)
    @interface Person{
    	String role default "";
    }
    
    //声明一个People类,给该类加上一些身份。
    @Person(role="son")
    @Person(role="father")
    @Person(role="CEO")
    public class People{
    	
    }
    

    3.3 java预置的注解

    (1)@Deprecated:用来标记过时的元素。

    (2)@Override:提示子类要复写父类中被 @Override 修饰的方法;业可以做检查,编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错比如你如果没写@Override而你下面的方法名又写错了,这时你的编译器是可以通过的。

    (3)@SuppressWarnings:阻止警告

    (4)@SafeVarargs:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。

    (5)@FunctionalInterface:函数式接口注解,jdk1.8新特性

    3.4 注解的提取

    注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解;然后通过 getAnnotation() 方法来获取 Annotation 对象(返回指定类型的注解);或者是 getAnnotations() 方法(返回注解到这个元素上的所有注解);

    3.5 自定义注解

    public class TestPerson {
    	public static void main(String[] args) {
    		Test1.show(Test1.class);
    	}
    }
    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface Person{
    	String name() default "aa";  //default "aa" 默认值 
    	int age() default 18;    //default 18  默认值 
    }
    @Person(name="li",age=20)
    class Test1{
    	public static void show(Class c) {
    		System.out.println(c.getName());   //Test1类的全路径
    		Person p =(Person)c.getAnnotation(Person.class);
    		System.out.println("name:"+p.name()+",age:"+p.age());
    	}
    }

     

     

     

    展开全文
  • Java泛型前言没有泛型程序带有泛型程序泛型的定义定义泛型类定义泛型方法泛型的继承泛型的多继承泛型的继承规则泛型通配符泛型通配符和extends关键字泛型通配符和super关键字T和?通配符的区别泛型擦除泛型和数组...

    前言

    Java当中的泛型是JDK1.5版本中引入的一项技术,使用泛型可以使得我们的代码更加安全,而且具备更好的可读性。泛型大家都有所了解,可能觉得非常简单,然而,泛型也有许许多多需要注意的细节,本文的主要目的就是将泛型的使用细节及注意事项一一归纳。

    没有泛型的程序

    下面是一段没有使用泛型的代码示例:

    package com.zwx.coreJava.genericity;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyItem {
        public static void main(String[] args) {
            List list = new ArrayList<>();
            list.add(1);
            list.add("中国");
            for (int i=0;i<list.size();i++){
                 int a = (int)list.get(i);
                 System.out.println(a);
            }
        }
    }
    

    显然,上面这段代码可以正常编译,但是运行的时候就会报错,这就是没有泛型约束造成的后果,我们可以向list中添加任意数据类型,但是取值的时候就很容易出现类型转换异常,所以为了使Java程序更加安全,泛型技术就应运而生了。

    带有泛型的程序

    现在我么通过泛型将上面的程序进行改造一下:

    List<Integer> list = new ArrayList<>();
    

    因为改造之后的程序加入了泛型的约束,那么就限制了我们只能向list中添加Integer类型的数据,而如果添加其他类型,比如String类型(如下所示),那么就会在编译阶段无法通过:

    list.add("中国"); //编译不通过
    

    上面没有添加泛型时,我们每次取出集合中的元素都需要进行类型强转,而加了泛型,在取值的时候我们也不需要再手动去类型强转,直接取出来(用约束的类型接收)就可以了,无需强转,代码又美观又安全:

     int a = list.get(i);//无需强转
     System.out.println(a);
    

    PS:注意:8种基本数据类型不能作为泛型,泛型必须要使用其对应的包装类

    泛型的定义

    上面通过一个简单的对比示例展示了如何用泛型以及使用泛型的好处,而上面使用的泛型实际上是一种指定了具体类型的泛型。

    直接指定具体泛型的方式是最简单的,我们大部分场景下也都是使用具体类型的方式,但是泛型还有其他非具体类型的定义方式,接下来我们继续介绍一下泛型的非具体类型定义方式。

    泛型可以定义在类上,也可以定义在方法上。

    定义泛型类

    下面就是一个泛型类的定义方式:

    package com.zwx.coreJava.genericity;
    
    import java.util.Map;
    
    public class GenericClass<K,V,M> {
        private M name;
        private Map<K,V> map;
    
        public V test(M m){
            return null;
        }
    }
    

    定义的泛型可以只有一个,也可以定义多个,一般使用大写字母来作为泛型定义,不同的字母之前也没有什么特殊含义,只不过一般默认的写法都遵循以下默契:

    • 1、E表示元素类型
    • 2、K和V表示关键字与值的类型(比如map里面的key和value)
    • 3、T则表示其他类型类型

    PS:示例中我特意用了一个M来表示,就是想说明到底用什么字母来表示泛型,并没有语法要求。

    定义泛型类的时候需要注意的是,类上定义的泛型不能使用在静态变量和静态方法上。比如上面的方法改成静态方法则会编译不通过,原因我们后面再解释。

    需要注意的是,接口也算是一种特殊的类,所以这些规则也适用于接口

    定义泛型方法

    如果我们不想在类上面定义泛型,只想在方法上定义泛型也可以,这就是泛型方法。泛型方法可以定义在泛型类里面,也可以定义在普通类里面
    泛型方法的定义需要把<>放在修饰符(如public static)之后,返回值之前
    如下就是一个泛型的定义示例:

    package com.zwx.coreJava.genericity;
    
    public class GenericMethod {
        public <T> T test(T arg){
            System.out.println("generic method");
            return null;
        }
    }
    

    那么定义了泛型的方法应该如何调用呢?
    我们把上面例子中的参数改为多参数看一下

    package com.zwx.coreJava.genericity;
    
    public class GenericMethod {
        public <T> T test(T...arg){
            System.out.println("generic method");
            return null;
        }
    
        public static void main(String[] args) {
            GenericMethod genericMethod = new GenericMethod();
            genericMethod.test("1",1);
        }
    }
    

    上面这段代码可以正常编译输出,并没有起到限制的作用,所以如果非要限制,可以采用如下调用方式:

    genericMethod.<String>test("1",1);
    

    在方法前面加上泛型就可以起到限制作用,这时候代码就会编译不通过了。

    泛型的继承

    上面的泛型定义实际上并没有太多限制,还是可以传入任意类型,那么假如我想限制一下泛型,比如说只能是某一个类的子类,又该如何定义呢?

    如果想限定泛型为某一个类的子类,那么就需要用到extends关键字。注意:泛型的继承只能使用extends,而不能使用implements(即使是接口也只能用extends关键字)

    package com.zwx.coreJava.genericity;
    
    import java.io.Serializable;
    
    public class GenericClass2<T extends Serializable>{
        public static void main(String[] args) {
            GenericClass2 genericClass2 = new GenericClass2<MyItem>();
        }
    }
    

    上面这个示例就会编译不通过,因为我们的MyItem类并没有实现Serializable接口,只有实现了Serializable接口的类才能被正常传进去。

    泛型的多继承

    在泛型里面支持多继承,可以通过“&”或者“,”来实现。

    我们先看一个&的例子:

    package com.zwx.coreJava.genericity;
    
    import java.io.Serializable;
    
    public class GenericClass2<T extends MyItem & Serializable & Cloneable>{
        public static void main(String[] args) {
            GenericClass2 genericClass2 = new GenericClass2<MyItemSub>();
        }
    }
    

    这里要求必须只能有一个类,而且如果有一个类,那么这个类必须放在第一位。如上面的例子,MyItem只能放在第一位。而下面这句话需要编译通过,那么MyItemSub必须是如下定义:

    public class MyItemSub extends MyItem implements Serializable,Cloneable {
    }
    

    也就是说必须同时满足所有条件。

    接下来再看一个逗号的例子:

    package com.zwx.coreJava.genericity;
    
    public class GenericClass2<T extends MyItem,Serializable,Cloneable>{
        public static void main(String[] args) {
            GenericClass2 genericClass2 = new GenericClass2<MyItemSub,MyItemSub,MyItemSub>();
        }
    }
    

    但是这种写法似乎只对第一个限制类型有用,后面的限制并没有用上,因为我除了第1个参数,后面2个参数我传任意类型都可以编译通过。

    泛型的继承规则

    我们新建一个类继承MyItem:

    package com.zwx.coreJava.genericity;
    
    public class MyItemSub extends MyItem {
    }
    

    这时候我们再去测试一下这个例子:

    package com.zwx.coreJava.genericity;
    
    public class GenericClass3<T extends MyItem>{
        public static void main(String[] args) {
            MyItemSub myItemSub = new MyItemSub();
            MyItem myItem = myItemSub;//编译通过
    
            GenericClass3<MyItemSub> myItemSubGenericClass = new GenericClass3<>();
            GenericClass3<MyItem> myItemGenericClass = myItemSubGenericClass;//编译失败
            
        }
    }
    

    可以看到最后这一句话编译不通过,也就是说:GenericClass3<MyItem>和GenericClass3<MyItemSub>没有任何关系(当然这个仅限于编译阶段),并不会因为泛型里面有继承关系就影响到了外面的继承关系。

    泛型通配符

    在某些时候,我们可以通过通配符“?”来作为泛型,?表示不限制类型。(下面例子中Manager类是Employee的子类,两个类都是空类,就不贴代码了

    package com.zwx.coreJava.genericity.wildcard;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyWildcard {
        public static void main(String[] args) {
            List<?> list = new ArrayList<>();
            list.add(null);//编译通过
            list.add(1);//报错
    }
    

    可以看到,虽然?表示允许任意类型,但是和List<Object>还是有本质区别的,使用通配符作为泛型的不能直接设置元素,除了null值
    不过虽然不能直接设值,但是我们可以通过间接的方式设置值进去。

    package com.zwx.coreJava.genericity.wildcard;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyWildcard {
        public static void main(String[] args) {
            List<?> list = MyWildcard.init();
            Object obj = list.get(0);//通过
            list.add(new Employee());//不通过
        }
    
        public static List<?>  init(){
            List<Employee> list = new ArrayList<>();
            list.add(new Employee());
            return list;
        }
    }
    

    虽然我不能直接设置值进去,但是我可以get获得list的元素。这是因为设置的时候无法确认?到底是什么类型,所以Java会拒绝编译,但是get元素的时候我可以直接赋值给Object,因为任何对象都是Object的子类,所以相当于知道了list内部对象的上界就是Object对象。用Object来接收不会有问题。

    泛型通配符和extends关键字

    通配符也可以使用extends关键字。

    package com.zwx.coreJava.genericity.wildcard;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyWildcard {
        public static void main(String[] args) {
            List<? extends Employee> list = MyWildcard.init();
            Employee employee = list.get(0);//通过
            list.add(new Manager());//不通过
    
        public static List<? extends Employee>  init(){
            List<Employee> list = new ArrayList<>();
            list.add(new Employee());
            return list;
        }
    }
    

    使用extends关键字和直接使用通配符区别不大,唯一的区别就是取值的时候我可以确定对象一定是Employee的子类,所以我可以直接使用Employee对象来接收。

    换句话说就是:这种写法我可以知道list内部元素的上界,但是我不知道你的下界,所以我只允许取值(用上界接收),不允许设值(设值要用下界)

    泛型通配符和super关键字

    通配符还可以使用super关键字,限定类型为某个类的超类:

    package com.zwx.coreJava.genericity.wildcard;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyWildcard {
        public static void main(String[] args) {
            List<? super Employee> list = MyWildcard.init();
            Employee employee = list.get(0);//不通过
            Object obj = list.get(0);//编译通过
            list.add(new Employee());//通过
        }
    
        public static List<? super Employee>  init(){
            List<Employee> list = new ArrayList<>();
            list.add(new Employee());
            return list;
        }
    }
    

    可以看到,这里就允许设值,因为super关键字限定了元素必须为Employee的父类,也就是我知道了元素的下限,所以我可以设值,但是取值因为不知道上界,所以只能用最顶层的Object。

    T和?通配符的区别

    这两种表示方法有区别吗?这两个有本质的区别。总结一下主要有如下区别:

    • 1、T表示的是形参,也就是说如果我们想要用T,那么必须要先定义才能用;而?通配符则表示的是实参,也就是说不需要定义,我就可以直接拿来使用。
    • 2、T相当于是占位符,如果我们定义了T,而实际传进去的是String,那么实际的约束类型就是String类型,而不是T;而?通配符可以理解为就是一种真实存在的特殊类型。
    • 3、T可以使用多继承,而?通配符不行,只能用单继承
    • 4、T不能使用super关键字,而?通配符可以使用super关键字。
    • 5、?通配符表示的对象是存在继承关系的,而T表示的不同泛型之间没有任何关系,所以下面这个示例是成立的。
    List<?> list = new ArrayList<>();
    List<? extends Employee> list1 = new ArrayList<>();
    list = list1;
    

    泛型擦除

    我们先看一个例子:

    package com.zwx.coreJava.genericity.erasure;
    
    import com.zwx.coreJava.genericity.wildcard.Employee;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class TestErasure {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            List<Integer> list1 = new ArrayList<>();
            List<?> list2 = new ArrayList<>();
    
            System.out.println(list.equals(list1));//true
            System.out.println(list.equals(list2));//true
        }
    }
    

    两句话的输出结果都是true。

    这是因为Java中的泛型只是在编译阶段有效,而实际运行之后会擦除掉,相当于没有泛型,上面的3个list都等价于List list,所以Java中的的泛型并不是真正的泛型。

    所以让我们回到上面的例子,为什么类泛型不能使用在静态方法上?因为静态方法不属于某一个实例对象,而泛型在运行阶段就被擦除了,那么静态方法在加载的时候根本就无法知道这个泛型到底是什么类型。而非静态方法是属于实例对象,可以根据对象实例化状态信息得出泛型属于什么类型。

    泛型和数组

    Java官方明确说明,不能实例化一个明确参数类型的数组。
    请看下面一个例子(ArrElement是一个空类):

    package com.zwx.coreJava.genericity.array;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class TestArray {
        public static void main(String[] args) {
            ArrElement<String>[] arr = new ArrElement<String>[10];//编译不通过
            ArrElement<String>[] arr1 = new ArrElement[10];//编译通过
            ArrElement<String>[] arr2 = (ArrElement<String>[])new ArrElement<?>[10];
        }
    }
    

    可以看到上面第1句话不允许编译通过,而后面两句话允许通过,这就是因为Java中禁止直接通过具体泛型来初始化一个数组,但是孕育通过具体泛型来接收一个数组。

    那么Java中为什么要禁止通过实际类型来初始化数组呢?
    因为数组在初始化之后就会记住数据的数据类型,但是泛型又能被擦出,这就会出现问题
    比如上面这个例子中第一句话,泛型被擦除后,实际代码为:

    ArrElement[] arr = new ArrElement[10];
    

    那么这时候假如我执行以下两句代码:

    Object[] objArr = arr;
    objArr[0] = new ArrElement<Integer>();
    

    这两句话是完全可以通过数组的安全性检查的,但是这个元素类型和初始化的元素类型不一致,所以会导致类型错误。正是因为数组会记住元素类型和泛型擦除导致了冲突,才禁止带泛型初始化数组,这样数组初始化的时候只会记住ArrElement对象,而不限定类型

    总结

    本文对Java泛型相关知识点进行了一个归纳整理,主要包括泛型类,泛型方法,多继承,通配符和非通配符的区别,以及多继承和数组泛型相关知识点。

    请关注我,和孤狼一起学习进步

    展开全文
  • Java浅谈泛型

    2018-09-24 00:29:46
    泛型,什么是泛型泛型就是把一种类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊类型。参数化类型,把类当做参数进行传递! 那么我们使用泛型有什么好处呢? 好处就是: A.把运行时期的问题提前到...
  • Java 泛型

    2021-07-14 15:52:54
    一、为什么使用泛型? 多种数据类型执行相同代码需要...public class Genericity<K, V> { private K data; private V result; public K getData() { return data; } public void setData(K data) { ..
  • Java系列之泛型

    2018-11-19 13:59:48
    自从 JDK 1.5 提供了泛型概念,泛型使得开发者可以定义较为安全的类型,不至于强制类型转化时出现类型转化异常,在没有反省之前,可以通过 Object 来完成不同类型数据之间的操作,但是强制类型转换(向下转型)在不...
  • 深入理解 Java 泛型

    2021-09-16 01:14:50
    泛型的产生泛型最开始是在 C++ 中提出的,实现为模块方法和模板类,主要为了解决与类型相关的算法的重用问题,比如对栈的描述:classstack{ push(参数类型)//入栈算...
  • 自定义泛型:extends,super关键字的使用 1.泛型类的使用 public ...2.泛型方法的使用 public static <T extends String> T concat(T... ts) {} 3.泛型接口的使用 public interface StringBufferInterface&...
  • java基础之泛型基础

    2018-04-26 11:13:36
    以下是一些基础的泛型案例,通过在案例中对泛型更加深入的去学习: 1 泛型类 /** * 泛型类是带有一个或多个类型参数的类 * */ class Entry&lt;K,V&gt;{ private K key; private V value; public ...
  • java泛型基本示例

    2019-11-28 18:27:59
    为什么要用java泛型2.1 java泛型擦除3.java泛型的常用方式3.1泛型类、方法3.2 java泛型通配符3.3 泛型通配符代码示例 1.什么是java泛型 Java泛型是j2SE 1.5中引入的一个新特性,即参数化类型,通俗来说就是给定一个...
  • java新特性—泛型

    2017-11-25 21:23:05
    泛型genericity泛型就是指类中的属性的类型在实例化对象的时候通过外部进行了指定。class 类名称<泛型类型,泛型类型,…>{ }一个泛型的小程序。package org.study.RobertChao;public class Info<T>{ private T x;//x...
  • 泛型Java中有很重要的地位,在面相对象编程及各种设计模式中有非常广泛的应用。 什么是泛型?为什么要使用泛型泛型,即“参数化类型”。一提到参数,定义方法时有形参,调用方法时传递实参。 泛型的本质是...
  • 本文转自:http://m.blog.csdn.net/jackielee_first/article/details/53365708 首先,创建一个Students的实体类 ... import java.io.Serializable; import java.util.Date;...public class Students implem...
  • Java 泛型 ~ generic。

    2021-01-08 13:18:11
    Java 泛型 ~ genericity。 文章目录Java 泛型 ~ genericity。what。 what。 Java 推出泛型之前,程序员可以构建一个元素类型为 Object 的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要...
  • JAVA泛型与栈应用

    千次阅读 2018-04-27 09:49:08
    从JDK1.5以后引入了三大常用新特性:泛型(Genericity)、枚举(enum)、注解(Annotation)。其中在JDK1.5中泛型是一个非常重要的实现技术,它可以帮助我们解决程序的参数转换问题。 泛型假设需要定义一个描述坐标的...
  • 泛型与通配符详解

    2021-03-15 13:35:46
    1回顾泛型泛型类:具有一个或多个泛型变量的类被...public voidtest11(T bb) {//................}//静态方法 在类上面定义的泛型,不能再静态方法里面使用public static voidtest12(A cc) {//..................}...
  • package com.itheima.d7_genericity; import java.util.ArrayList; import java.util.List; /** * 目标:泛型的概念 * * 什么是泛型? * 泛型就是一个标签:<数据类型> *泛型可以在编译阶段约束只能...
  • Java泛型使用

    千次阅读 热门讨论 2020-02-24 21:35:28
    Java泛型使用 1. 泛型 1.1 什么是泛型,为什么引入泛型 在项目中,我们的功能代码往往要考虑之后的复用!!!这要求功能代码尽可能的支持更多类型,在不考虑泛型的情况下,如果需要更换数据类型,只能重新完成对应...
  • Java泛型详解

    2017-05-08 01:51:23
    如何可以使集合“记住”元素的类型,并在运行时不会出现java.lang.ClassCastException的异常呢?
  • JAVA中的泛型

    2017-08-30 13:34:54
    1 泛型的引出 现在要求定义一个表示 坐标的类(Point),这个类需要能保存数字:x = 10,y =20; 保存小数:x = 10.2 ,y = 20.4;保存字符串: x = 东经20度 , 北纬32度。 这三组数据不同之处就是数据类型不一致,...
  • java泛型

    2017-02-06 11:21:03
    定义泛型Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?...
  • java 泛型深入理解

    2019-08-15 16:39:22
    1. 泛型的好处 减少了cast带来的运行时异常, 使算法和框架更为通用,减少了冗余代码 2. 泛型的使用 ...java中常见的泛型类有:ArrayList、Set、Map等 //此处T可以随便写为任意标识,常见的如T、E、K、V等...
  • 泛型 泛型概述 泛型:是JDK5中引入的特性,它提供了编译时类型安全监测机制 泛型的好处: 把运行时期的问题提前到了编译期 避免了强制类型转换 泛型可以使用的地方: 类后面,这样的类称为泛型类 方法申明上,...
  • 故事的小黄花从出生那年就飘着童年的荡秋千随记忆一直晃到现在---《晴天》1. 为什么要使用泛型js VS java强制指定引申泛型概念1.1 js VS java1》 在j...
  • Java泛型篇)

    2019-09-18 16:17:52
    一、泛型入门 Java集合有一个缺点,就是把一个对象“丢进”集合之后,集合会“忘记”这个对象的数据类型。当再次取出该对象时,该对象的类型就变成了Object类型。Java之所以这样设计,是因为集合的设计者也不知道...
  • Java泛型

    千次阅读 2016-04-07 17:07:10
    Java泛型         泛型介绍 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所...
  • Java基础之泛型

    千次阅读 2021-07-25 20:37:03
    泛型genericity 格式: <自定义泛型无意义大写英文字母占位符> 例:<T> Type <E> Element <K> Key <V> value 泛型可以在方法,类,接口中使用 泛型在方法中使用 使用静态方法的泛型...
  • Java中的泛型

    2020-03-04 17:27:19
    1 泛型 1.1 泛型的作用和定义 目前的项目中,我们的功能代码是可以考虑之后的复用!!!当前代码有且只支持Student类型,如果需要更换数据类型,按照目前的技术,只能重新...Java泛型就是来解决对应的问题  ...

空空如也

空空如也

1 2 3 4 5 ... 18
收藏数 359
精华内容 143
关键字:

java程序改成泛型publicstaticvoidmain(string[]args){genericityint

java 订阅
友情链接: chuankou5.0使用版.rar