目录
- 泛型是什么
- 为什么要引入泛型
- 泛型的使用
- 1.泛型类
- 2.泛型方法
- 3.泛型接口
- 4.泛型类派生的子类
- 5.类型通配符
- 6.有界类型参数
- 7.通配符和泛型方法
- 8.泛型擦除
- 9.兼容性
- 10.泛型应用
泛型是什么
- 泛型是java5的新特性,它是类和接口的扩展,它是带一个或者多个类型参数的类和接口。类型参数中的类型可以是接口或者类,但不能是基本数据类型。
- 设计原则:只要在编译时期没有出现错误,那么运行时期就不会出现
ClassCastException
异常。 - 泛型:它把类型明确的工作推迟到创建对象或调用方法时才去明确的特殊的类型
- 参数化类型:把类型当做参数来传递,<>中只能是引用类型
为什么要引入泛型
- 因为Object类是所有类的父类,所以java早期是使用object来代表任意类型的,但是向下转型有强转的问题,会发生
ClassCastException
异常,程序不太安全,所以就引入了泛型。 - 在没有泛型时,集合中可以装载各种类型的元素,集合不知道元素类型是什么,统一当做
object
处理。在获取元素时,由于返回的是object
类型对象,所以要进行类型转换才能使用。 - 例子来说明
复制代码
1
2
3
4
5
6
7
8
9List arrayList = new ArrayList(); arrayList.add("aaaa"); arrayList.add(100); for(int i = 0; i< arrayList.size();i++){ String item = (String)arrayList.get(i); System.out.println("泛型测试","item = " + item); }
结果程序崩溃
复制代码
1
2java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
为了使这种程序出现的问题在编译阶段就可以解决引入了泛型
复制代码
1
2
3
4List<String> arrayList = new ArrayList<String>(); ... //arrayList.add(100); 在编译阶段,编译器就会报错
- 有了泛型后,
1.代码更加简洁【不用强制转换】
2.程序更加健壮【只要编译时没有警告,运行时就不会出现ClassCastException
】
3.可读性和稳定性【编写集合时就限定了类型】
泛型的使用
泛型有三种使用方式:泛型类,泛型接口,泛型方法
1.泛型类
- 泛型类就是把普通的类泛型化,在泛型类实例化时,可以根据不同需求给出类的具体类型,使用时也不用担心类型强转,转换异常的问题了。
- 基本形式
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Node<T>{ private T value;//value这个成员变量的类型为T,T的类型由外部指定 public Node(T value){ this.value=value; } public T set(T value){ this.value=value; } public T get(T value){ return value; } }
- 测试
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static void main(String[] args) { //创建对象并指定元素类型 Node<String> node= new Node<>();//java7以后,编译器可以自动推断后边的类型 node.set(new String("hello")); String s = node.get(); System.out.println(s); //创建对象并指定元素类型 Node<Integer> node1= new Node<>(); /** * 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了. */ node1.set(10);//自动装箱和拆箱,编译器自动执行 int i = node1.get(); System.out.println(i); }
2.泛型方法
- 一个方法是否是泛型方法和所在的类是否是泛型类没有关系,java中任何方法都可以声明为泛型方法。泛型方法除了定义不同,调用时和普通方法相同。
- 定义方法
复制代码
1
2
3
4
5//定义泛型方法..,先定义后使用 public <T> void show(T t) { System.out.println(t); }
- 测试
复制代码
1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) { //创建对象 Node node= new Node (); //调用方法,传入的参数是什么类型,返回值就是什么类型 node.show("hello"); node.show(12); node.show(12.5); }
3.泛型接口
- 泛型接口和泛型类的定义及使用基本相同。泛型接口常用在各种类 的生产器中。
复制代码
1
2
3
4
5//定义一个泛型接口 public interface Generator<T> { public T next(); }
4.泛型类派生的子类
- 泛型类是拥有泛型这个特性的类,它本质上还是一个Java类,那么它就可以被继承。继承方法分两种,1.子类明确泛型类的类型参数变量 2.子类不明确泛型类的类型参数变量。
- 子类明确泛型类的类型参数变量
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/* 把泛型定义在接口上 */ public interface Inter<T> { public abstract void show(T t); } /** * 子类明确泛型类的类型参数变量: */ public class InterImpl implements Inter<String> { @Override public void show(String s) { System.out.println(s); } }
- 子类不明确泛型类的类型参数变量
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14/** * 子类不明确泛型类的类型参数变量: * 实现类也要定义出<T>类型的 * */ public class InterImpl<T> implements Inter<T> { @Override public void show(T t) { System.out.println(t); } }
- 测试
复制代码
1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) { //测试第一种情况 //Inter<String> i = new InterImpl(); //i.show("hello"); //第二种情况测试 Inter<String> ii = new InterImpl<>(); ii.show("100"); }
- 注意:实现类的要是重写父类的方法,返回值的类型是要和父类一样的!
类上声明的泛形只对非静态成员有效
5.类型通配符
- 为什么需要类型通配符??现在有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?
- 按照我们没有学习泛型之前,我们可能会这样做:
复制代码
1
2
3
4
5
6
7
8
9
10public void test(List list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
上面的代码是正确的,只不过在编译的时候会出现警告,说没有确定集合元素的类型….这样是不优雅的…
- 那我们学习了泛型了,现在要怎么做呢??有的人可能会这样做:
复制代码
1
2
3
4
5
6
7
8
9
10public void test(List<Object> list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
- 这样做语法是没毛病的,但是这里十分值得注意的是:该test()方法只能遍历装载着Object的集合!!!强调:泛型中的并不是像以前那样有继承关系的,也就是说List和List是毫无关系的!!!!那现在咋办???我们是不清楚List集合装载的元素是什么类型的,List这样是行不通的………于是Java泛型提供了类型通配符 ? 所以代码应该改成这样:
复制代码
1
2
3
4
5
6
7
8
9
10public void test(List<?> list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
- ?号通配符表示可以匹配任意类型,任意的Java类都可以匹配……
现在非常值得注意的是,当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。因为add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。
6.有界类型参数
- 为了限制传入类型参数的类型种类,引入了有界类型参数,分为上界和下限
6.1设定通配符上界
- 当我们想接收一个个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做???我们学习了通配符,但是如果直接使用通配符的话,该集合就不是只能操作数字了。因此我们需要用到设定通配符上限
List<? extends Number>
上面的代码表示的是:List集合装载的元素只能是Number的子类或自身
复制代码
1
2
3
4
5
6
7
8
9
10
11
12public static void main(String[] args) { //List集合装载的是Integer,可以调用该方法 List<Integer> integer = new ArrayList<>(); test(integer); //List集合装载的是String,在编译时期就报错了 List<String> strings = new ArrayList<>(); test(strings); } public static void test(List<? extends Number> list) { }
6.2设定通配符下限
复制代码
1
2
3//传递进来的只能是Type或Type的父类 <? super Type>
- 设定通配符的下限这并不少见,在TreeSet集合中就有….我们来看一下
复制代码
1
2
3
4public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
- 那它有什么用呢??我们来想一下,当我们想要创建一个TreeSet类型的变量的时候,并传入一个可以比较String大小的Comparator。
那么这个Comparator的选择就有很多了,它可以是Comparator,还可以是类型参数是String的父类,比如说Comparator….
这样做,就非常灵活了。也就是说,只要它能够比较字符串大小,就行了
值得注意的是:无论是设定通配符上限还是下限,都是不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的!
7.通配符和泛型方法
复制代码
1
2
3
4
5
6
7
8//大多时候,我们都可以使用泛型方法来代替通配符的….. //使用通配符 public static void test(List<?> list) { } //使用泛型方法 public <T> void test2(List<T> t) { }
- 区别:
- 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法
- 如果没有依赖关系的,就使用通配符,通配符会灵活一些.
8.泛型擦除
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12List<String> stringArrayList = new ArrayList<String>(); List<Integer> integerArrayList = new ArrayList<Integer>(); Class classStringArrayList = stringArrayList.getClass(); Class classIntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)){ System.out.println("泛型测试","类型相同"); } //结果 泛型测试: 类型相同。
9.兼容性
JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。
当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。
值得注意的是:它保留的就类型参数的上限
复制代码
1
2
3
4
5
6
7
8
9List<String> list = new ArrayList<>(); //类型被擦除了,保留的是类型的上限,String的上限就是Object List list1 = list; //把没有类型参数的集合赋值给带有类型参数的集合赋值 //它也不会报错,仅仅是提示“未经检查的转换 List list = new ArrayList(); List<String> list2 = list;
10.泛型应用
当我们写网页的时候,常常会有多个DAO,我们要写每次都要写好几个DAO,这样会有点麻烦。那么我们想要的效果是什么呢??只写一个抽象DAO,别的DAO只要继承该抽象DAO,就有对应的方法了。
要实现这样的效果,肯定是要用到泛型的。因为在抽象DAO中,是不可能知道哪一个DAO会继承它自己,所以是不知道其具体的类型的。而泛型就是在创建的时候才指定其具体的类型。
抽象DAO
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public abstract class BaseDao<T> { //模拟hibernate.... private Session session; private Class clazz; //哪个子类调的这个方法,得到的class就是子类处理的类型(非常重要) public BaseDao(){ Class clazz = this.getClass(); //拿到的是子类 ParameterizedType pt = (ParameterizedType) clazz.getGenericSuperclass(); //BaseDao<Category> clazz = (Class) pt.getActualTypeArguments()[0]; System.out.println(clazz); } public void add(T t){ session.save(t); } public T find(String id){ return (T) session.get(clazz, id); } public void update(T t){ session.update(t); } public void delete(String id){ T t = (T) session.get(clazz, id); session.delete(t); } }
继承抽象DAO,该实现类就有对应的增删改查的方法了。
CategoryDao
复制代码
1
2
3
4public class CategoryDao extends BaseDao<Category> { }
BookDao
复制代码
1
2
3
4
5public class BookDao extends BaseDao<Book> { }
参考java3y的文章
最后
以上就是悲凉大侠最近收集整理的关于java基础(泛型小结)的全部内容,更多相关java基础(泛型小结)内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复