JAVA中的泛型机制详解

news2024/11/17 18:21:59

 1.泛型的概念

java泛型是java5引入的一个特性,它允许我们为类,接口,方法指定类型参数,从而提供编译时类型安全检查。泛型的本质是参数化类型,即在声明类,接口或者方法时不指定具体的类型,而是使用一个或者多个类型参数表示

 泛型的好处主要有两个:

  • 类型安全:在编译的时候就能检查到类型的错误,而不是在运行的时候抛出ClassCastException
  • 消除强制类型转换:泛型可以自动进行类型的转换,从而减少代码中显性类型转换的需求,使得代码更加的简洁。

举个例子,泛型究竟是怎么诞生的:

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
    String item = (String)arrayList.get(i);
    Log.d("泛型测试","item = " + item);
}

运行结果:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

ArrayList可以存放任意类型,例子中添加了一个String类型,又添加了一个Integer类型,例子中我们从Arraylist中获取值用String类型去获取Integer不能自动类型转换,程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

为了避免在运行时会出现这种情况,我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。

List<String> arrayList = new ArrayList<String>();

这样子,ArrayList里面只能存储String类型的值。

2.泛型方法

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 <E>)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。

java 中泛型标记符:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • ? - 表示不确定的 java 类型

泛型标记符在概念上都是相同的,它们都是用来指定泛型列,接口或者方法可以接受的类型。你可以根据自己的喜好或者代码的上下文来随意选择使用哪个标记符。

下面我们来做一个题目来练习一下: 

假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?

答案是可以使用 Java 泛型

使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。


    public class Main {
        //泛型方法PrintArray
        public static < E > void printArray(E[] inputArray){
            //输出数组元素
            for(E element:inputArray){
                System.out.printf("%s",element);
            }
            System.out.println();
        }
        public static void main(String[] args){
            Integer[] intArray ={1,2,3,4,5};
            Double[] doubles ={1.1,2.2,3.3,4.4};
            Character[] characters ={'H','E','L','L','O'};

            System.out.println("整数数组元素为、:");
            printArray(intArray);//传递一个整数数组

            System.out.println("\n双精度型数组元素为:");
            printArray(doubles);

            System.out.println("\n字符型数组元素为:");
            printArray(characters);
        }

    }

运行结果为:

整数数组元素为、:
12345

双精度型数组元素为:
1.12.23.34.4

字符型数组元素为:
HELLO

泛型的基本使用:

在类名之后使用尖括号声明类型参数,声明的类型参数可以像普通类型一样用在类型声明处使用,到使用时再决定其具体类型,然后编译器会帮我们处理一些类型类型转换的细节。

public class Holder<T> {
    T val;

    public Holder(T val) {
        this.val = val;
    }

    public T getVal() {
        return val;
    }
    
    public void setVal(T val) {
        this.val = val;
    }
    
    public static void main(String[] args) {
        Holder<String> strHolder = new Holder<String>("abc");
        String s = h.getVal();
    }
}

在使用时指定了的 Holder 的类型参数为 String。可以将 getVal() 的返回值直接赋给一个 String 变量,而不用显示的转型。在使用 setVal 时也必须传入 String 类或其子类,若入参不是 String 或其子类那么编译时会报错。

在Java7之前 new 参数化类型时需要指定类型,但在Java7之后 new 操作可以不用显示指定类型,编译器会自动推导出来:

 Holder<String> h = new Holder<>("abc");

2.1泛型方法的使用

泛型类,在创建类的对象的时候确定类型参数的具体类型;
泛型方法,在调用方法的时候再确定类型参数的具体类型。

泛型方法签名中声明的类型参数只能在该方法里使用,而泛型接口、泛型类中声明的类型参数则可以在整个接口、类中使用。
当调用泛型方法时,根据外部传入的实际对象的数据类型,编译器就可以判断出类型参数 T所代表的具体数据类型。
 

举例如下:

public class Demo {  
  public static void main(String args[]) {  
    GenericMethod d = new GenericMethod(); // 创建 GenericMethod 对象  
    
    String str = d.fun("汤姆"); // 给GenericMethod中的泛型方法传递字符串  
    int i = d.fun(30);  // 给GenericMethod中的泛型方法传递数字,自动装箱  
    System.out.println(str); // 输出 汤姆
    System.out.println(i);  // 输出 30

	GenericMethod.show("Lin");// 输出: 静态泛型方法 Lin
  }  
}

class GenericMethod {
	// 普通的泛型方法
	public <T> T fun(T t) { // 可以接收任意类型的数据  
    	return t;
  	} 

	// 静态的泛型方法
	public static <E> void show(E one){     
         System.out.println("静态泛型方法 " + one);
    }
}  

3.认识泛型的写法

先从一个简单的泛型类开始:

public class Main {
    class point<T>{//此处可以随便写标识符号,T是type的简称
        private T var;//var的类型由T指定,即:有外部指定
        public T getVar(){
            return var;
        }
        public void setVar(T var){//设置的类型也由外部决定
            this.var=var;
        }
    }

    public void main(String[] args) {
        point<String> p =new point<String>();//里面的var类型为String类型
        p.setVar("it");
        System.out.println(p.getVar().length());
    }

}

多元泛型:

public class Main {
    static class Notepad<K,V>{
        private K key;
        private V value;

        public K getKey() {
            return key;
        }

        public void setKey(K key) {
            this.key = key;
        }

        public V getValue() {
            return value;
        }

        public void setValue(V value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        Notepad<String,Integer> t =null;
        t=new Notepad<String,Integer>();
        t.setKey("汤姆");
        t.setValue(20);
        System.out.println("姓名"+t.getKey());
        System.out.println("年龄"+t.getValue());
    }

}

多个类型参数使用逗号分隔:

public class Holder<A, B, C> {

    public A v1;
    public B v2;
    public C v3;

    public Holder(A v1, B v2, C v3) {
        this.v1 = v1;
        this.v2 = v2;
        this.v3 = v3;
    }

    public static void main(String[] args) {
        Holder<String, Integer, Float> h = new Holder<>("abc", 1, 2.5);
    }
}

简单的泛型接口:

public class Main {
    interface Info<T>{        // 在接口上定义泛型
        public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
    }
    static class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类
        private T var ;             // 定义属性
        public InfoImpl(T var){     // 通过构造方法设置属性内容
            this.setVar(var) ;
        }
        public void setVar(T var){
            this.var = var ;
        }
        public T getVar(){
            return this.var ;
        }
    }

        public static void main(String arsg[]){
            Info<String> i = null;        // 声明接口对象
            i = new InfoImpl<String>("汤姆") ;  // 通过子类实例化对象
            System.out.println("内容:" + i.getVar()) ;
        }
    }

非泛型的类(或者泛型类)中定义泛型方法

class Arraylist<E> {
    public <T> T[] toArray(T[] a) {
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    }
}

4.泛型通配符        

泛型通配符是JAVA泛型中的一个概念,它允许你在编写代码的时候对泛型类型参数的类型进行限制。

为什么要使用通配符?

  • 灵活性:通配符提供了一种灵活的方式来处理不同类型的集合,而不需要为每种类型编写特定的代码。
  • 类型安全:尽管通配符允许一定程度的类型不确定性,但它仍然提供了类型安全,防止了类型不匹配的错误。
  • 扩展性:使用通配符可以更容易的扩张代码。

这里的?代表类型是未知的,所以编译器不知道要检查哪种类型,因此不允许你向这样的列表中添加任何的元素。

Object是所有类的超类,因此任何类型的对象都可以安全的转换为Object类型。

无限制通配符:

使用?表示,它不指定任何类型的限制。这种通配符可以代表任何的类型,但使用它的时候,不能对它进行类型转换或者调用实例的方法

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

这个例子中,list可以存储任何类型的对象,但你不能调用list.get(0).toString()这样的操作,因为他的类型是未知的。

List<?> list = new ArrayList<String>();
list.add("Hello"); // 编译错误:不能添加具体类型
String s = list.get(0); // 编译错误:不能直接赋值给String
Object obj = list.get(0); // 正确:可以赋值给Object

使用无界通配符的集合可以被用来读取数据,但不能用来插入数据,因为插入需要知道具体的类型。

上界限定通配符:

使用?extend T 表示,它指定了参数类型的上限。意味着参数类型必须是T或者T的子类型。这种通配符常用于方法参数或者返回类型,以便能够处理更加广泛的类型。

   public void printList(List<? extends Number> list){
          for (Number num:list){
              System.out.println(num);

在这个例子中,printList方法可以接受任何的Number类型或其子类型的列表。

List<? extends Number> numberList = new ArrayList<Integer>();
numberList.add(5); // 编译错误:不能添加具体类型
Integer i = numberList.get(0); // 正确:可以赋值给Integer
Number num = numberList.get(0); // 正确:可以赋值给Number

在这个例子中,`numberList` 可以存储任何 `Number` 类型或其子类(如 `Integer`、`Double` 等)的对象。但是,你不能向这样的列表添加元素,因为添加操作需要知道具体的类型。

 下界限定通配符:

使用?super T表示,它指定了类型参数的下限,这意味着类型参数必须是T或者T的超类型。这种通配符常用于方法参数,特别是当你需要想集合中添加元素时。

public class Main {
      public void addToList(List<? super String>list, String item){
          list.add(item);
      }
    }

在这个例子中,addToList方法可以接受任何可以添加String类型元素的列表

List<? super Integer> list = new ArrayList<Number>();
list.add(5); // 编译错误:不能添加Number类型
list.add(new Integer(5)); // 正确:可以添加Integer类型
Number num = list.get(0); // 编译错误:不能赋值给Number
Integer i = list.get(0); // 正确:可以赋值给Integer

下界限定通配符使用 `? super T` 表示,它指定了类型参数的下限。这意味着类型参数必须是 `T` 或 `T` 的超类。

非泛型的类(或者泛型类)中定义泛型方法

class Arraylist<E> {
    public <T> T[] toArray(T[] a) {
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    }
}

5.类型擦除

JAVA中的类型擦除是一种语言特性,它允许编译器在编译的时候使用泛型类型,但是在运行的时候将泛型类型擦除,以保持向后兼容性。这意味着在运行的时候,泛型信息不再存在,所有的泛型类型都会被转换为它们的原始类型(即它们的边界类型,如果没有指定边界,则为object)

1.无限制类型擦除

当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如<T><?>的类型参数都被替换为Object。

2.有限制类型擦除 

当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如<T extends Number><? extends Number>的类型参数被替换为Number<? super Number>被替换为Object。

3.擦除方法定义中的类型参数

擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的,这里仅以擦除方法定义中的有限制类型参数为例。

如何证明类型擦除呢?

举个例子:


public class Main {
    static class Box<T>{
        private T t;

        public T getT() {
            return t;
        }

        public void setT(T t) {
            this.t = t;
        }
    }

    public static void main(String[] args) {
        Box<Integer> integerBox=new Box<>();
        integerBox.setT(123);
        Integer value =integerBox.getT();
        System.out.println("value: "+value);//输出结果为123
        
        //尽管Box<Integer>在编译的时候是Integer类型,但是在运行的时候它只是Box
        System.out.println(integerBox instanceof Box<Integer>);//编译的时候就报错了
        System.out.println(integerBox instanceof Box);//ture
        
    }
}

原始类型相等

public class Test {

    public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);

        System.out.println(list1.getClass() == list2.getClass()); // true
    }
}

在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList<String>泛型类型的,只能存储字符串;一个是ArrayList<Integer>泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。

4.如何理解泛型的编译期检查?

既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

public static  void main(String[] args) {  

    ArrayList<String> list = new ArrayList<String>();  
    list.add("123");  
    list.add(123);//编译错误  
}

在上面的程序中,使用add方法添加一个整型,在IDE中,直接会报错,说明这就是在编译之前的检查,因为如果是在编译之后检查,类型擦除后,原始类型为Object,是应该允许任意引用类型添加的。可实际上却不是这样的,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。

因为类型检查就是编译时完成的,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正涉及类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。

public class Test {  

    public static void main(String[] args) {  

        ArrayList<String> list1 = new ArrayList();  
        list1.add("1"); //编译通过  
        list1.add(1); //编译错误  
        String str1 = list1.get(0); //返回类型就是String  

        ArrayList list2 = new ArrayList<String>();  
        list2.add("1"); //编译通过  
        list2.add(1); //编译通过  
        Object object = list2.get(0); //返回类型就是Object  

        new ArrayList<String>().add("11"); //编译通过  
        new ArrayList<String>().add(22); //编译错误  

        String str2 = new ArrayList<String>().get(0); //返回类型就是String  
    }  
} 

通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象

泛型中参数话类型为什么不考虑继承关系

在Java中,像下面形式的引用传递是不允许的:

ArrayList<String> list1 = new ArrayList<Object>(); //编译错误  
ArrayList<Object> list2 = new ArrayList<String>(); //编译错误

实际上,在第4行代码的时候,就会有编译错误。那么,我们先假设它编译没错。那么当我们使用list2引用用get()方法取值的时候,返回的都是String类型的对象(上面提到了,类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object类型的对象,这样就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。(这也是泛型出现的原因,就是为了解决类型转换的问题,我们不能违背它的初衷)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1949428.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

sed利用脚本处理文件

一、sed是什么 sed 命令是利用脚本来处理文本文件。它可以依照脚本的指令来处理、编辑文本文件。主要用来自动编 辑一个或多个文件、简化对文件的反复操作、编写转换程序等。 二、sed的原理 读入新的一行内容到缓存空间&#xff1b; 从指定的操作指令中取出第一条指令&…

C++ 列式内存布局数据存储格式 Arrow

Apache Arrow 优点 : 高性能数据处理&#xff1a; Arrow 使用列式内存布局&#xff0c;这特别适合于数据分析和查询操作&#xff0c;因为它允许对数据进行高效批量处理&#xff0c;减少CPU缓存未命中&#xff0c;从而提升处理速度。 零拷贝数据共享&#xff1a; Arrow …

【PyTorch】基于YOLO的多目标检测项目(一)

【PyTorch】基于YOLO的多目标检测项目&#xff08;一&#xff09; 【PyTorch】基于YOLO的多目标检测项目&#xff08;二&#xff09; 目标检测是对图像中的现有目标进行定位和分类的过程。识别的对象在图像中显示有边界框。一般的目标检测方法有两种&#xff1a;基于区域提议的…

javaEE-02-servlet

文章目录 Servlet 技术servlet程序示例通过实现Servlet接口实现Servlet程序通过继承 HttpServlet 实现 Servlet 程序 Servlet的声明周期 ServletConfig 类ServletContext 类HttpServletRequest 类请求的转发 HttpServletResponse 类请求重定向 HTTP 协议GET 请求Post请求常用请…

三维影像系统PACS源码,图像存储与传输系统,应用于医院中管理医疗设备如CT,MR等产生的医学图像的信息系统

PACS&#xff0c;即图像存储与传输系统&#xff0c;是应用于医院中管理医疗设备如CT&#xff0c;MR等产生的医学图像的信息系统。目标是支持在医院内部所有关于图像的活动&#xff0c;集成了医疗设备&#xff0c;图像存储和分发&#xff0c;数字图像在重要诊断和会诊时的显示&a…

unity ui toolkit的使用

UIToolkitExamples (github)样例 GitHub - ikewada/UIToolkitExamples: チュートリアル動画「使ってみようUI Toolkit」のためのサンプルプロジェクトです官网 Unity - Manual: UI Toolkit视频教程 使用 UI Toolkit - 上集_哔哩哔哩_bilibili 使用 UI Toolkit - 下集_哔哩哔哩_…

vue3前端开发-小兔鲜项目-使用pinia插件完成token的本地存储

vue3前端开发-小兔鲜项目-使用pinia插件完成token的本地存储&#xff01;实际业务开发中&#xff0c;token是一个表示着用户登录状态的重要信息&#xff0c;它有自己的生命周期。因此&#xff0c;这个参数值必须实例化存储在本地中。不能跟着pinia。因为pinia是基于内存设计的模…

go语言day18 reflect反射

Golang-100-Days/Day16-20(Go语言基础进阶)/day19_Go语言反射.md at master rubyhan1314/Golang-100-Days (github.com) 一、interface接口 接口类型内部存储了一对pair(value,Type) type interface { type *Type // 类型信息 data unsafe.Pointer // 指向具体数据 } 1)创建R…

Git基本原理讲解、常见命令、Git版本回退、Git抛弃本地分支拉取仓库最新分支

借此机会写篇博客汇总一下自己去公司实习之后遇到的一些常见关于Git的操作。 Git基本认识 Git把数据看作是对小型文件系统的一组快照&#xff0c;每次提交更新&#xff0c;或在Git中保存项目状态时&#xff0c;Git主要对当时的全部文件制作一个快照并保存这个快照的索引。同时…

嵌入式C++、MQTT、数据库、Grafana、机器学习( Scikit-learn):智能建筑大数据管理平台(代码示例)

项目概述 智能建筑管理系统&#xff08;Intelligent Building Management System, IBMS&#xff09;是一个集成多种技术的复杂系统&#xff0c;旨在通过智能化手段提升建筑的管理效率、节能效果和居住舒适度。该系统涉及嵌入式系统、物联网&#xff08;IoT&#xff09;、大数据…

数据库-触发器,存储过程

按照题目要求完成下列题目&#xff1a; 1.触发器 mysql> use mydb16_trigger; Database changed mysql> create table goods(-> gid char(8) primary key,-> name varchar(10),-> price decimal(8,2),-> num int); Query OK, 0 rows affected (0.01 sec)my…

01 Redis引入和概述

Redis引入和概述 一、Redis的历史和发展过程 ​ Redis是在2008年由意大利的一家创业公司Merzia的创始人Salvatore Sanfilippo(萨尔瓦托.圣菲利波)创造的。 ​ 当时&#xff0c;Salvatore 正在开发一款基于MySQL的网站实时统计系统LLOOGG&#xff0c;然而他发现MySQL的性能并…

VAE、GAN与Transformer核心公式解析

VAE、GAN与Transformer核心公式解析 VAE、GAN与Transformer&#xff1a;三大深度学习模型的异同解析 【表格】VAE、GAN与Transformer的对比分析 序号对比维度VAE&#xff08;变分自编码器&#xff09;GAN&#xff08;生成对抗网络&#xff09;Transformer&#xff08;变换器&…

计算机网络(四)数字签名和CA认证

什么是数字签名和CA认证&#xff1f; 数字签名 数字签名的过程通常涉及以下几个步骤&#xff1a; 信息哈希&#xff1a;首先&#xff0c;发送方使用一个哈希函数&#xff08;如SHA-256&#xff09;对要发送的信息&#xff08;如电子邮件、文件等&#xff09;生成一个固定长度…

GIS场景升级:支持多种影像协议与天气效果

在GIS场景编辑领域&#xff0c;升级视效的需求日益增加。有一款名为山海鲸可视化的免费工具&#xff0c;本人亲测能够完美满足这一需求。山海鲸可视化不仅支持多种GIS影像协议&#xff08;如TMS、WMS、WMTS等&#xff09;&#xff0c;还能一键添加天气效果&#xff0c;瞬间提升…

【Unity】 HTFramework框架(五十三)使用 Addressables 可寻址系统

更新日期&#xff1a;2024年7月25日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 Addressables 可寻址系统使用 Addressables 可寻址系统一、导入 Addressables二、切换到 Addressables 加载模式三、切换资源加载助手四、加载资源五、注…

【全面介绍Python多线程】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 🦇目录 1. 🦇前言2. 🦇threading 模块的基本用法3. 🦇Thre…

编程类精品GPTs

文章目录 编程类精品GPTs前言种类ChatGPT - GrimoireProfessional-coder-auto-programming 总结 编程类精品GPTs 前言 代码类的AI, 主要看以下要点: 面对含糊不清的需求是否能引导出完整的需求面对完整的需求是否能分步编写代码完成需求编写的代码是否具有可读性和可扩展性 …

【个人亲试最新】WSL2中的Ubuntu 22.04安装Docker

文章目录 Wsl2中的Ubuntu22.04安装Docker其他问题wsl中执行Ubuntu 报错&#xff1a;System has not been booted with systemd as init system (PID 1). Can‘t operate. 参考博客 &#x1f60a;点此到文末惊喜↩︎ Wsl2中的Ubuntu22.04安装Docker 确定为wsl2ubuntu22.04&#…

57 数据链路层

用于两个设备&#xff08;同一种数据链路节点&#xff09;之间传递 目录 对比理解“数据链路层” 和 “网络层”以太网 2.1 认识以太网 2.2 以太网帧格式MAC地址 3.1 认识MAC地址 3.2 对比理解MAC地址和IP地址局域网通信MTU 5.1 认识MTU 5.2 MTU对ip协议的影响 5.3 MTU对UDP的…