java基础-并不神奇的泛型

本文深入讲解Java泛型的概念,包括其历史背景、作用、定义和使用方法,以及通配符的种类与应用。通过示例代码,阐述泛型如何提升代码的重用性和安全性。
前言
开始之前,先给大家来一道测试题。
[Java] 纯文本查看 复制代码
?
1
2
3
4
List<String> strList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
         
System.out.println(strList.getClass() == integerList.getClass());

请问,上面代码最终结果输出的是什么?熟悉泛型的同学应该能够答出来,而对泛型有所了解,但是了解不深入的同学可能会答错。

带着问题
  • Java中的泛型是什么 ? 使用泛型的好处是什么?
  • 什么是泛型中的限定通配符和无界通配符 ?
  • 你可以把List<String>传递给一个接受List<Object>参数的方法吗?
  • Java的泛型是如何工作的 ? 什么是类型擦除 ?
一.泛型概述
最早的“泛型编程”的概念起源于C++的模板类(Template),Java 借鉴了这种模板理念,只是两者的实现方式不同。C++ 会根据模板类生成不同的类,Java 使用的是类型擦除的方式。

1.1为什么使用泛型
Java1.5 发行版本中增加了泛型(Generic)。
有很多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类。-- 《Java 编程思想》
容器就是要存放要使用的对象的地方。数组也是如此,只是相比较的话,容器类更加的灵活,具有更多的功能。所有的程序,在运行的时候都要求你持有一大堆的对象,所以容器类算得上最需要具有重用性的类库之一了。
看下面这个例子,
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class AutoMobile {
}
 
/**
 * 重用性不好的容器类
 */
public class Holder1 {
 
    private AutoMobile a;
 
    public Holder1(AutoMobile a) {
        this.a = a;
    }
    //~~
}
 
/**
 * 想要在java5 之前实现可重用性的容器类
 * @author Richard_yyf
 * @version 1.0 2019/8/29
 */
public class Holder2 {
 
    private Object a;
 
    public Holder2(Object a) {
        this.a = a;
    }
 
    public Object getA() {
        return a;
    }
 
    public void setA(Object a) {
        this.a = a;
    }
 
    public static void main(String[] args) {
        Holder2 h2 = new Holder2(new AutoMobile());
        AutoMobile a = (AutoMobile) h2.getA();
        h2.setA("Not an AutoMobile");
        String s = (String) h2.getA();
        h2.setA(1);
        Integer x = (Integer) h2.getA();
    }
}
 
 
 
/**
 * 通过泛型来实现可重用性
 * 泛型的主要目的是指定容器要持有什么类型的对象
 * 而且由编译器来保证类型的正确性
 *
 * @author Richard_yyf
 * @version 1.0 2019/8/29
 */
public class Holder3WithGeneric<T> {
 
    private T a;
 
    public Holder3WithGeneric(T a) {
        this.a = a;
    }
 
    public T getA() {
        return a;
    }
 
    public void setA(T a) {
        this.a = a;
    }
 
    public static void main(String[] args) {
        Holder3WithGeneric<AutoMobile> h3 = new Holder3WithGeneric<>(new AutoMobile());
        // No class cast needed
        AutoMobile a = h3.getA();
    }
}
通过上述对比,我们应该可以理解类型参数化具体是什么个意思。
在没有泛型之前,从集合中读取到的每一个对象都需要进行转换。如果有人不小心插入了类型错误的对象,在运行时的转换处理就会出错。这显然是不可忍受的。
泛型的出现,给Java带来了不一样的编程体验。
1.2泛型的作用
  • 参数化类型。与普通的 Object 代替一切类型这样简单粗暴而言,泛型使得数据的类别可以像参数一样由外部传递进来。它提供了一种扩展能力。它更符合面向抽象开发的软件编程宗旨。
  • 类型检测。当具体的类型确定后,泛型又提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过。所以说,它是一种类型安全检测机制,一定程度上提高了软件的安全性防止出现低级的失误。
  • 提高代码可读性。不必要等到运行的时候才去强制转换,在定义或者实例化阶段,因为 Holder<AutoMobile>这个类型显化的效果,程序员能够一目了然猜测出这个容器类持有的数据类型。
  • 代码重用。泛型合并了同类型对象的处理代码,使得代码重用度变高。
二.泛型的定义和使用
泛型按照使用情况可以分为 3 种。
  • 泛型类
  • 泛型方法
  • 泛型接口
2.1泛型类
  • 概述:把泛型定义在类上
  • 定义格式:
[Java] 纯文本查看 复制代码
?
1
2
3
public class 类名 <泛型类型1,...> {
    ...
}
 
  • 注意事项:泛型类型必须是引用类型(非基本数据类型)

泛型参数规范
尖括号 <>中的 字母 被称作是类型参数,用于指代任何类型。我们常看到<T> 的写法,事实上,T 只是一种习惯性写法,如果你愿意。你可以这样写。
[Java] 纯文本查看 复制代码
?
1
2
3
public class Test<Hello> {
    Hello field1;
}
 
但出于规范和可读性的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:
  • T 代表一般的任何类。
  • E 代表 Element 的意思,或者 Exception 异常的意思。
  • K 代表 Key 的意思。
  • V 代表 Value 的意思,通常与 K 一起配合使用。
  • S 代表 Subtype 的意思
2.2 泛型方法
  • 概述:把泛型定义在方法上
  • 定义格式:
[Java] 纯文本查看 复制代码
?
1
2
3
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
    ...
}
 
  • 注意事项:
  • 这里的<T> 中的T被称为类型参数,而方法中的 T 被称为参数化类型,它不是运行时真正的参数。
  • 方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。

2.3泛型接口
泛型接口和泛型类差不多。
  • 泛型接口概述:把泛型定义在接口
  • 定义格式:
[Java] 纯文本查看 复制代码
?
1
2
3
public interface 接口名<泛型类型> {
    ...
}

Demo
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
public interface GenericInterface<T> {
 
    void show(T t);
}
 
public class GenericInterfaceImpl<String> implements GenericInterface<String>{
 
    @Override
    public void show(String o) {
 
    }
}


三.通配符?
除了用 <T>表示泛型外,还有 <?>这种形式。 被称为通配符。
为什么要引进这个概念呢?先来看下下面的Demo.
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class GenericDemo2 {
 
    class Base{}
 
    class Sub extends Base{}
 
    public void test() {
        // 继承关系
        Sub sub = new Sub();
        Base base = sub;
        List<Sub> lsub = new ArrayList<>();
        // 编译器是不会让下面这行代码通过的,
        // 因为 Sub 是 Base 的子类,不代表 List<Sub>和 List<Base>有继承关系。
        List<Base> lbase = lsub;
    }
}
在现实编码中,确实有这样的需求,希望泛型能够处理某一范围内的数据类型,比如某个类和它的子类,对此 Java 引入了通配符这个概念。
所以,通配符的出现是为了指定泛型中的类型范围
通配符有 3 种形式。
  • <?>被称作无限定的通配符
  • <? extends T>被称作有上限的通配符
  • <? super T>被称作有下限的通配符


3.1无界通配符<?>
无限定通配符经常与容器类配合使用,它其中的 ? 其实代表的是未知类型,所以涉及到 ? 时的操作,一定与具体类型无关。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
// Collection.java
public interface Collection<E> extends Iterable<E> {
    
    boolean add(E e);
}
 
public class GenericDemo3 {
    /**
     * 测试 无限定通配符 <?>
     * @param collection c
     */
    public void testUnBoundedGeneric(Collection<?> collection) {
        collection.add(123);
        collection.add("123");
        collection.add(new Object());
 
        // 你只能调用 Collection 中与类型无关的方法
        collection.iterator().next();
        collection.size();
    }
}
 
无需关注 Collection 中的真实类型,因为它是未知的。所以,你只能调用 Collection 中与类型无关的方法。
有同学可能会想,<?>既然作用这么渺小,那么为什么还要引用它呢?
个人认为,提高了代码的可读性,程序员看到这段代码时,就能够迅速对此建立极简洁的印象,能够快速推断源码作者的意图。
(用的很少,但是要理解)
为了接下去的说明方便,先定义一下几个类。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Food {}
 
    class Fruit extends Food {}
 
    class Apple extends Fruit {}
 
    class Banana extends Fruit {}
 
    // 容器类
    class Plate<T> {
        private T item;
 
        public Plate(T item) {
            this.item = item;
        }
 
        public T getItem() {
            return item;
        }
 
        public void setItem(T item) {
            this.item = item;
        }
    }
3.2 上限通配符<? extends T>
<?>代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 T 及 类型 T 的子类都可以放入这个容器中。
副作用
边界让Java不同泛型之间的转换更容易了。但不要忘记,这样的转换也有一定的副作用。那就是容器的部分功能可能失效。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
public void testUpperBoundedBoundedGeneric() {
       Plate<? extends Fruit> p = new Plate<>(new Apple());
 
       // 不能存入任何元素
        p.setItem(new Fruit()); // error
        p.setItem(new Apple()); // error
 
        // 读出来的元素需要是 Fruit或者Fruit的基类
        Fruit fruit = p.getItem();
        Food food = p.getItem();
//        Apple apple = p.getItem();
    }
<? extends Fruit>会使往盘子里放东西的set( )方法失效。但取东西get( )方法还有效。比如下面例子里两个set()方法,插入Apple和Fruit都报错。
原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。可能是Fruit?可能是Apple?也可能是Banana,RedApple,GreenApple?
如果你需要一个只读容器,用它来produce T,那么使用<? extends T> 。
 
3.3下限通配符 <? super T>
相对应的,还有下限通配符 <? super T>
副作用因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Fruit的基类,往里面存比Fruit粒度小的类都可以。但是往外读取的话就费劲了,只有所有类的基类Object可以装下。但这样一来元素类型信息就都丢失了。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
public void testLowerBoundedBoundedGeneric() {
//        Plate<? super Fruit> p = new Plate<>(new Food());
        Plate<? super Fruit> p = new Plate<>(new Fruit());
 
        // 存入元素正常
        p.setItem(new Fruit());
        p.setItem(new Apple());
 
        // 读取出来的东西,只能放在Object中
        Apple apple = p.getItem(); // error
        Object o = p.getItem();
    }


3.4 PECS原则
PECS - Producer Extends Consumer Super
  • “Producer Extends” – 如果你需要一个只读容器,用它来produce T,那么使用<? extends T> 。
  • “Consumer Super” – 如果你需要一个只写容器,用它来consume T,那么使用<? super T>。
  • 如果需要同时读取以及写入,那么我们就不能使用通配符了。
内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MTPLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值