##泛型
1、泛型定义及好处
Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
/*创建集合,使用泛型
* 好处:1、避免了类型转换的麻烦,存储什么类型,取出什么类型
* 2、把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
*
* 弊端:泛型是什么类型,就只能存储什么类型的数据
*
* */
泛型好处与坏处代码示例:
package Collection.FanXing.demo01; import java.util.ArrayList; import java.util.Iterator; public class Demo01Generic { public static void main(String[] args) { demo02(); } /*创建集合,使用泛型 * 好处:1、避免了类型转换的麻烦,存储什么类型,取出什么类型 * 2、把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错) * * 弊端:泛型是什么类型,就只能存储什么类型的数据 * * */ private static void demo02() { ArrayList<String> list = new ArrayList<>(); list.add("abc"); list.add("def"); //使用迭代器遍历list集合 Iterator<String> it = list.iterator(); while (it.hasNext()){ String s1 = it.next(); System.out.println(s1); } } /*创建集合对象,不使用泛型 * 好处:集合不使用泛型,那么就是Object类型,可以存储任何类型的数据 * 弊端:不安全,会产生异常 * */ private static void demo01() { ArrayList list = new ArrayList(); list.add("ftj"); list.add(1); Iterator it = list.iterator(); while (it.hasNext()){ //这里取出的数据也是Object类型的 Object obj = it.next(); System.out.println(obj); //想要使用String类特有的方法,length获取字符串的长度;不能使用多态 Object obj = "abc"; //需要向下转型 //会抛出一个异常ClassCastException 不能把Integer类型转换为String类型 String str = (String)obj; System.out.println(str.length()); } } }
2、定义泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来....这样的话,用户明确了什么类型,该类就代表着什么类型...用户在使用的时候就不用担心强转的问题,运行时转换异常的问题了。
package Collection.FanXing.demo02; /* * 定义一个含有泛型的类,模拟ArrayList集合 * 泛型是一个未知的数据类型,当我们不确定什么数据类型的时候,可以使用泛型 * 泛型可以接受任意的数据类型,可以使用Integer,String,Student... * 创建对象的时候确定泛型的数据类型 * */ public class GenericClass<E> { private E name; public E getName() { return name; } public void setName(E name) { this.name = name; } }
用户想要使用哪种类型,就在创建的时候指定类型。使用的时候,该类就会自动转换成用户想要使用的类型了。
package Collection.FanXing.demo02; public class Demo02GenericClass { public static void main(String[] args) { //不写默认为Object类型 GenericClass gc = new GenericClass(); gc.setName("只能是字符串"); /*String name1 = gc.getName();*/ Object obj = gc.getName(); //创建GenericClass对象,泛型使用Integer GenericClass<Integer> gc1 = new GenericClass<>(); gc1.setName(1); //自动拆箱 Interger ————>int int name1 = gc1.getName(); System.out.println(name1); GenericClass<String> gc2 = new GenericClass<>(); gc2.setName("小小心"); Object name2 = gc2.getName(); System.out.println(name2); } }
3、泛型方法
现在呢,我们可能就仅仅在某一个方法上需要使用泛型....外界仅仅是关心该方法,不关心类其他的属性...这样的话,我们在整个类上定义泛型,未免就有些大题小作了。
package Collection.FanXing.demo03; /** * 定义含有泛型的方法:泛型定义载方法的修饰符和返回值类型之间 * * 格式: * 修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){ * 方法体 * } * * 含有泛型的方法,在调用方法的时候确定泛型的数据类型 * 传递什么类型的参数,泛型就是什么类型 */ public class GenericClass { //定义泛型方法 public <M> void method(M m){ System.out.println(m); } //定义一个含有泛型的静态方法 public static <S> void method02(S s){ System.out.println(s); } }
用户传递进来的是什么类型,返回值就是什么类型了
package Collection.FanXing.demo03; public class Demo01Done { public static void main(String[] args) { GenericClass gc = new GenericClass(); gc.method("ftj"); gc.method('中'); GenericClass.method02(123321); } }
4、泛型接口
定义泛型接口
package Collection.FanXing.demo04; /** * 定义含有泛型的接口, */ public interface GenericInterface<I> { public abstract void method(I i); }
接口实现类(方式1:指定接口的泛型)
package Collection.FanXing.demo04; /** * 含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型 */ public class GenericInterfaceImpl implements GenericInterface<String> { @Override public void method(String s) { System.out.println(s); } }
接口实现类(方式2:接口用什么泛型,实现类就是什么泛型)
package Collection.FanXing.demo04; /** * 含有泛型的接口第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走。 * 就相当于定义了一个含有泛型的类,创建对象的时候就确定了泛型的类型 * public interface List<E>{ * boolean add(E e); * E get(int index); * } * public class ArrayList<E> implements List<E>{ * public boolean add(E e); * public E get(int index); * } */ public class GenericInterfaceImpl2<I> implements GenericInterface<I>{ @Override public void method(I i) { System.out.println(i); } }
5、类型通配符
现在有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?
我们都无法确定集合的类型,于是Java泛型提供了类型通配符 。
package Collection.FanXing.demo05; import java.util.ArrayList; import java.util.Iterator; /** * 泛型的通配符: * ?:代表任意的数据类型 * 使用方式: * 不能创建对象使用 * 只能作为方法的参数使用 */ public class Demo05Generic { public static void main(String[] args) { ArrayList<Integer> list01 = new ArrayList<>(); list01.add(1); list01.add(2); ArrayList<String> list02 = new ArrayList<>(); list02.add("a"); list02.add("b"); /*定义一个方法,能遍历所有类型的ArrayList集合 * 这时候我们不知道ArrayList集合使用什么数据类型,可以用泛型的通配符?来接收数据类型 * */ printArray(list01); } public static void printArray(ArrayList<?> list){ Iterator<?> it = list.iterator(); while (it.hasNext()){ Object next = it.next(); System.out.println(next); } } }
只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。
6、通配符的上下限设定
首先,我们来看一下设定通配符上下限用在哪里....
现在,我想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做???
我们学习了通配符,但是如果直接使用通配符的话,该集合就不是只能操作数字了。因此我们需要用到设定通配符上限
那么设定通配符的下限也不是陌生的事了。
package Collection.FanXing.demo05; import java.util.ArrayList; import java.util.Collection; /** * 泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身 * 泛型的下限限定:? super E 代表使用的泛型只能是E类型的父类/本身 */ public class Demo06Generic { public static void main(String[] args) { Collection<Integer> coll1 = new ArrayList<>(); Collection<String> coll2 = new ArrayList<>(); Collection<Number> coll3 = new ArrayList<>(); Collection<Object> coll4 = new ArrayList<>(); getElements1(coll1); //getElements1(coll2); 报错 getElements1(coll3); //getElements1(coll4); 报错 //getElements2(coll1); 报错 //getElements2(coll2); 报错 getElements2(coll3); getElements2(coll4); } public static void getElements1(Collection<? extends Number> coll){}; public static void getElements2(Collection<? super Number> coll){}; }