《Java基础知识》Java 泛型详解
JDK 1.5 之后,Java 通过泛型解决了容器类型安全这一问题,而几乎所有人接触泛型也是通过Java的容器。那么泛型究竟是什么?
泛型的本质是参数化类型;也就是说,泛型就是将所操作的数据类型作为参数的一种语法。
先对比一下有泛型和无泛型的写法。
无泛型
public class Dog{ String name;
int age; /**
* 带参构造函数
* @param name
* @param age
*/
public Dog(String name,int age){
this.age = age;
this.name = name;
} public void ptint(){
System.out.println("name = "+this.name+";age = "+age);
}
}
public class Node {
private Object obj; public Object get(){
return obj;
} public void set(Object obj){
this.obj=obj;
} public static void main(String[] argv){
Dog dog=new Dog("花花",4);
Node node=new Node();
node.set(dog);
Dog dog2=(Dog)node.get();
dog2.ptint();
}
}
运行结果:
使用泛型
public class Node<T> {
private T obj; public T get(){
return obj;
} public void set(T obj){
this.obj=obj;
} public static void main(String[] argv){
Dog dog=new Dog("花花",4);
Node<Dog> node=new Node();
node.set(dog);
Dog dog2=node.get();
dog2.ptint();
}
}
分析:
第一种:通过Object 类接收,最后使用的时候需要强制转换后才能正常使用,并且强转是否正确,只有在运行时才能知道是否正确。
第二种:通过 T 接收,在使用的使用<> 中指定本次使用具体类,可以很好的做到要用什么传什么,并且改写法,可以在编译的时候就发现类型不匹配问题。T接收,代表是Object类,所以可以传如任何对象。
在上述 Node 类中,我们假设需要使用使用传入类Dog的方法。显然obj.ptint() 是不行,obj能用只有Object的方法。那要如何使用呢?
思路是:让T不再代表Object,而是我指定的子类或者父类。
案例:
public class Node<T extends Dog> {
private T obj; public T get(){
return obj;
} public void set(T obj){
this.obj=obj;
} public void printNode(){
obj.ptint();
} public static void main(String[] argv){
Node<Dog> node=new Node<>();
node.set(new Dog("花花",4));
node.printNode();
}
}
运行结果:
到这里,已经感受到泛型的美好:那就是方便。
注意:泛型传递的时候是不变的。
案例:
public class Dog1 extends Dog {
/**
* 带参构造函数
*
* @param name
* @param age
*/
public Dog1(String name, int age) {
super(name, age);
}
}
public class PublicTest {
public static void main(String[] args) {
Node<Dog> node0 = new Node<>();
node0.set(new Dog("花花",4));
// Node<Dog1> node0 = new Node<>(); 是会编译报错的
main1(node0);
} public static void main1(Node<Dog> node) {
node.printNode();
}
}
运行结果:
如果将上述代码的:Node<Dog> node0 = new Node<>(); 改成被注释的代码。会编译报错。也就是泛型是Node<Dog>只能传 Node<Dog>, 计算Dog1是Dog的子类也不行。上述写死的方式实在难受,所以Java也提供了解决方式。
案例:
public static void main(String[] args) {
Node<Dog1> node0 = new Node<>(); //不会编译报错,并且可以正常执行
node0.set(new Dog1("花花",6));
main1(node0);
} public static void main1(Node<? extends Dog> node) {
node.printNode();
}
运行结果:
上述案例中mian1方法的入参:Node<? extends Dog> node 。这样就允许传入子类了。
这个时候我们再考虑一下,既然有向下泛型,有没有向上泛型呢,答案是有的:mian1方法的入参:Node<? extends Dog> node 中的extends 改成supper就可以了。
这个案例,大家自己动手试试。
案例:
public class Node<T> {
private T obj; public T get(){
return obj;
} public void set(T obj){
this.obj=obj;
} public void printNode(){
System.out.println("只能使用Object方法");
}
}
public class PublicTest {
public static void main(String[] args) {
Node<Dog1> node0 = new Node<>(); //不会编译报错,并且可以正常执行
node0.set(new Dog1("花花",6));
main1(node0);
} public static void main1(Node<?> node) {
node.printNode();
}
}
运行结果:
上述案例:支持传入向上泛型和向下泛型,不建议使用。 因为这类泛型回到一个问题,Node<T> 类中只能使用Object 的几个方法,局限很大。
泛型使用中的注意点。
案例:
import java.util.concurrent.ThreadLocalRandom; public class PublicTest {
static <T> T[] toArray(T... args) {
return args;
} static <T> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
}
throw new AssertionError(); // Can't get here
} public static void main(String[] args) {
String[] attributes = pickTwo("Good", "Fast", "Cheap");
}
}
运行结果:
分析:上述代码第一感觉返回回来的是一个String[] 数组才对,为什么会出现类型转换错误。文章中已经提到:T 会被表示成Object。所以返回回来的数组Java也会处理成Object[] ,Object[] 是不能转换成 String[] 的。
既然知道原因了:我们试着修改一下代码:将T继承到String。这样java返回数组就会处理成String[]。
import java.util.concurrent.ThreadLocalRandom; public class PublicTest {
static <T extends String> T[] toArray(T... args) {
return args;
} static <T extends String> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
}
throw new AssertionError(); // Can't get here
} public static void main(String[] args) {
String[] attributes = pickTwo("Good", "Fast", "Cheap");
System.out.println(attributes[0]+" : "+attributes[1]);
}
}
运行结果:
基类劫持:
案例:
public interface Node<T> {
T play();
}
public class Dog implements Node<Integer> { @Override
public Integer play() {
return null;
}
}
public class Dog1 extends Dog implements Node<String> {
//...
}
编译报错:因为基类已经实现 Node<Integer> 那么子类也只能实现 Node<Integer>。
Java泛型中的标记符含义:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
S、U、V - 2nd、3rd、4th types 和T的作用一样。
总结:泛型给我们带来了方便,同时也要注意他的特点和用法。
参考资料:
https://www.cnblogs.com/dengchengchao/p/9717097.html
《Java基础知识》Java 泛型详解的更多相关文章
- java 泛型详解(普通泛型、 通配符、 泛型接口)
java 泛型详解(普通泛型. 通配符. 泛型接口) JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0.这说明Java已经有大幅度的变化.本文将讲解JDK5.0支持的新功能---- ...
- java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考java 泛型详解.Java中的泛型方法. java泛型详解 1. 概述 泛型在 ...
- Java泛型详解(转)
文章转自 importNew:Java 泛型详解 引言 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理 ...
- 【转】java 泛型详解
java 泛型详解 对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考java 泛型详解.Java中的泛型方法. java泛型详解 ...
- 【转载】Java泛型详解
[转载]http://www.importnew.com/24029.html 对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考 ...
- java基础(十二 )-----Java泛型详解
本文对java的泛型的概念和使用做了详尽的介绍. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用. 什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”.一提到 ...
- Java基础11:Java泛型详解
本文对java的泛型的概念和使用做了详尽的介绍. 本文参考https://blog.csdn.net/s10461/article/details/53941091 具体代码在我的GitHub中可以找 ...
- java 泛型详解-绝对是对泛型方法讲解
Reference: http://blog.csdn.net/s10461/article/details/53941091 1. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模 ...
- Java泛型详解,史上最全图文详解!
泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它. 毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课. 一:泛型本质 Java 泛型(gen ...
- 笔记-java泛型详解
首先,先说明一下,java泛型文章的出处:http://www.cnblogs.com/lzq198754/p/5780426.html 作为学习笔记保存. 1.为什么需要泛型 泛型在Java中有很重 ...
随机推荐
- 模块-time模块
time模块 time翻译过来就是时间,这个模块是与时间相关的模块,那么言外之意,如果我们在工作中遇到了对时间的需求(比如获取当前时间,获取时间戳等等)就要先想到time模块. time模块中对于时 ...
- ASP.NET Core MVC+EF Core项目实战
项目背景 本项目参考于<Pro Entity Framework Core 2 for ASP.NET Core MVC>一书,项目内容为party邀请答复. 新建项目 本项目开发工具为V ...
- Rust 入门 (五)
定义并介绍结构体 结构体和我们前面学习的元组类似,结构体中的每一项都可以是不同的数据类型.和元组不同的地方在于,我们需要给结构体的每一项命名.结构体较元组的优势是:我们声明和访问数据项的时候不必使用索 ...
- 【Android - 控件】之MD - CardView的使用
CardView是Android 5.0新特性——Material Design中的一个布局控件,可以通过属性设置显示一个圆角的类似卡片的视图. 1.CardView的属性: app:cardCorn ...
- Java中我常用到的十二个最基本的快捷键
自己在Java中经常用到的快捷键先记下来方便日后学习之用: 1. Ctrl+shift+r:打开资源 这可能是所有快捷键组合中最省时间的了.这组快捷键可以让你打开你的工作区中任何一个文件,而你只需要按 ...
- JavaScript的定时器是如何工作的
理解JavaScript定时器工作原理对于学习JavaScript非常重要.因为JavaScript是单线程运行的,定时器使用场合少,不是很直观.下面通过三个函数来学习JavaScript如何定义,操 ...
- 微信小程序实现,可滑动、可点击
tab.wxml: 全部 中奖 未中奖 全部 中奖 未中奖 tab.wxss: .swiper-tab { width: 100%; border-bottom: 2rpx solid #ccc; t ...
- 第五章 Unity中的基础光照(2)
目录 1. Unity中的环境光和自发光 2. 在UnityShader中实现漫反射光照模型 2.1 实践:逐顶点光照 2.2 实践:逐像素光照 2.3 半兰伯特模型 1. Unity中的环境光和自发 ...
- ThinkPHP5——route(路由)的详解
路由在框架中的作用打个比方的话,路由好比是WEB应用的总调度室,对于访问的URL地址,路由可以拒绝或者接受某个URL请求,并进行分发调度,而且还有一个副作用是因为路由规则可以随意定义,因此可以让你的U ...
- 【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick
目录 Event Loop 是什么? Event Loop 基本解释 事件循环阶段概览 事件循环细节 timers pending callbacks poll阶段 check close callb ...