参考:
http://blog.csdn.net/lonelyroamer/article/details/7864531#comments
http://blog.csdn.net/lonelyroamer/article/details/7868820#comments
http://blog.csdn.net/LonelyRoamer/article/details/7927212#comments

关于泛型的一些重要知识点

泛型由来:早期Java版本(1.4及之前)如果要代指某个泛化类对象,只能使用Object,这样写出来的代码需要增加强转,而且缺少类型检查,代码缺少健壮性。在1.5之后,Java引入了泛型的概念,提供了一套抽象的类型表示方法。
简单来说,泛型是JDK1.5中出现的安全机制。
好处:将运行时期的ClassCastException问题转到了编译时期,避免了强制转换的麻烦。
什么时候用:当操作的引用数据类型不确定的时候,就使用<>,将要操作的引用数据类型传入即可。
其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型。
泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。
运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。
为什么擦除呢?因为为了兼容运行的类加载器。
泛型的补偿:在运行时,通过获取元素的类型进行转换动作。这样就不用再手动强制转换了。
泛型的通配符【?】未知类型。
泛型的限定:
  • 【? extends E】接收E类型或者E的子类型对象。上限。一般存储对象的时候用。比如 添加元素 addAll。
  • 【? super E】接收E类型或者E的父类型对象。下限。一般取出对象的时候用。比如比较器。
利用泛型,我们可以:
  • 1、表示多个可变类型之间的相互关系:HashMap<T,S>表示类型T与S的映射,HashMap<T, S extends T>表示T的子类与T的映射关系。
  • 2、细化类的能力:ArrayList<T> 可以容纳任何指定类型T的数据,当T代指人,则是人的有序列表,当T代指杯子,则是杯子的有序列表,所有对象个体可以共用相同的操作行为。
  • 3、复杂类型被细分成更多类型:List<People>和List<Cup>是两种不同的类型,这意味着List<People> listP = new ArrayList<Cup>()是不可编译的。这种检查基于编译时而非运行时,所以说是不可编译并非不可运行,因为运行时ArrayList不保留Cup信息。另外要注意,即使People继承自Object,List<Object> listO = new ArrayList<People>()也是不可编译的,应理解为两种不同类型。因为listO可以容纳任意类型,而实例化的People列表只能接收People实例,这会破坏数据类型完整性。

泛型的基本概念

泛型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型 ParameterizedType,即带有类型参数的类型。也就是说所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。如:List<T>、Map<Integer, String>、List<? extends Number>。
public interface java.lang.reflect.ParameterizedType extends Type

GenericDeclaration接口是声明类型变量的所有实体的公共接口,也就是说,只有实现了该接口才能在对应的实体上声明类型变量。这些实体目前只有三个:Class、Construstor、Method。当这种参数化类型用在类、接口和方法的创建中时,分别称为泛型类、泛型接口和泛型方法。
注意:因为直接实现子类没有Field类,所以在属性上面不能定义类型变量。
public interface java.lang.reflect.GenericDeclaration
所有已知实现类:Class、Constructor、Method

泛型思想早在C++语言的模板(Templates)中就开始生根发芽,在Java语言处于还没有出现泛型的版本时,只能通过 "Object是所有类型的父类" 和 "类型强制转换" 两个特点的配合来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,那Object转型为任何对象成都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会被转嫁到程序运行期之中。
泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,比如 List<int> 与 List<String> 就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。
Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int> 与 ArrayList<String> 就是同一个类型。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。
使用泛型机制编写的程序代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类来说尤其有用。
泛型程序设计(Generic Programming)意味着编写的代码可以被很多不同类型的对象所重用。

实例分析

在JDK1.5之前,Java泛型程序设计是用继承来实现的。因为Object类是所用类的基类,所以只需要维持一个Object类型的引用即可。就比如ArrayList只维护一个Object引用的数组:
public class ArrayList{  public Object get(int i){......}  public void add(Object o){......}  ......  private Object[] elementData;
} 

这样会有两个问题:
  • 没有错误检查,可以向数组列表中添加任何类的对象
  • 在取元素的时候,需要进行强制类型转换
这样,很容易发生错误,比如:
/**jdk1.5之前的写法,容易出问题*/
ArrayList arrayList1=new ArrayList();
arrayList1.add(1);
arrayList1.add(1L);
arrayList1.add("asa"); int i=(Integer) arrayList1.get(1);//因为不知道取出来的值的类型,类型转换的时候容易出错  

这里的第二个元素是一个长整型,而你以为是整形,所以在强转的时候发生了错误。
所以。在JDK1.5之后,加入了泛型来解决类似的问题。例如在ArrayList中使用泛型:
/** jdk1.5之后加入泛型*/
ArrayList<String> arrayList2=new ArrayList<String>();  //限定数组列表中的类型
//arrayList2.add(1); //因为限定了类型,所以不能添加整形
//arrayList2.add(1L);//因为限定了类型,所以不能添加整长形
arrayList2.add("asa");//只能添加字符串
String str=arrayList2.get(0);//因为知道取出来的值的类型,所以不需要进行强制类型转换  

还要明白的是,泛型特性是向前兼容的。尽管 JDK 5.0 的标准类库中的许多类,比如集合框架,都已经泛型化了,但是使用集合类的现有代码(没有加泛型的代码)可以继续不加修改地在 JDK 1.5 中工作。

泛型的使用

泛型的参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。下面看看具体是如何定义的。

泛型类:类名后面

泛型类就是在声明类时,定义了一个或多个类型变量的类。
泛型类中定义的类型变量的作用范围为当前泛型类中。
泛型类中定义的类型变量用于,在多个方法签名间实施类型约束。例如,当创建一个 Map<K, V> 类型的对象时,您就在方法之间宣称一个类型约束,您 put() 的值将与 get() 返回的值的类型相同。
public class HashMap<K,V> {public V put(K key, V value) {...}public V get(Object key) {...}...
}

定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数:
public class Pair<T> {private T value;public Pair(T value) {this.value = value;}public T getValue() {return value;}public void setValue(T value) {this.value = value;}
}

现在我们就可以使用这个泛型类了:
public static void main(String[] args) throws ClassNotFoundException {Pair<String> pair = new Pair<String>("Hello");//注意,"="号左边和右边都要使用<>指定泛型的实际类型String str = pair.getValue();pair.setValue("World");
}

泛型类可以有多个类型变量,例如:
class Pair<T, S, P, U, E> { }

注意:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。需要时还可以用临近的字母U和S表示“任意类型”。

泛型接口

泛型接口和泛型类差不多:
interface Show<T,U>{  void show(T t,U u);
} 

实现类
public class ShowTest implements Show<String, Date> {@Overridepublic void show(String t, Date u) {System.out.println(t + "  " + u.getTime());}}

测试一下:
Show<String, Date> show = new ShowTest();
show.show("包青天", new Date());

泛型方法:返回值之前

泛型方法就是在声明方法时,定义了一个或多个类型变量的方法。
泛型方法中定义的类型变量的作用范围为当前泛型方法中。
泛型方法中定义的类型变量用于,在该方法的多个参数之间,或在该方法的参数与返回值之间,宣称一个类型约束。
class Person<S> {public <W> void show(W w) {//这里的【W】完全等价于Objectif (w != null) System.out.println(w.toString());}public static <Y> void staticShow(Y y) {if (y != null) System.out.println(y.toString());//静态方法不能访问在类声明上定义的类型变量//S s;//错误提示:Cannot make a static reference to the non-static type S}
}

泛型变量的类型限定

对于上面定义的泛型变量,因为在编译之前,也就是我们还在定义这个泛型方法的时候,我们并不知道这个泛型类型 T 到底是什么类型,所以,只能默认T为原始类型Object,所以它只能调用来自于Object的那几个方法。
如果我们想限定类型的范围,比如必须是某个类的子类,或者某个接口的实现类,这时可以使用类型限定对类型变量T设置限定(bound)来实现。
类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:
  • 无限定的泛型变量等价于Object(白哥添加)
  • 不管该限定是类还是接口,统一都使用关键字 extends
  • 可以使用 & 符号给出多个限定
  • 如果限定既有接口也有类,那么类必须只有一个,并且放在首位置
比如:

public static <T extends Comparable> T get(T t1,T t2)  //继承或实现都用extends
public static <T extends Comparable & Serializable> T get(T t1,T t2)  //使用 & 符号给出多个限定
public static <T extends Object & Comparable & Serializable> T get(T t1,T t2)  //继承的类Object必须放在首位

通配符?的使用

通配符有三种:
  • 无限定通配符  形式<?>
  • 上边界限定通配符 形式< ? extends Number>
  • 下边界限定通配符    形式< ? super Number>
1、泛型中的?通配符
如果定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,如果这样写
public static void main(String[] args) throws Exception {List<Integer> listInteger = new ArrayList<Integer>();printCollection(listInteger);//报错 The method printCollection(Collection<Object>) in the type Test is not applicable for the arguments (List<Integer>)
}public static void printCollection(Collection<Object> collection) {for (Object obj : collection) {System.out.println(obj);}
}

语句printCollection(listInteger);报错,这是因为泛型的参数是不考虑继承关系的,就直接报错。
这就得用?通配符
public static void printCollection(Collection<?> collection) {...}

在方法 printCollection 中不能出现与参数类型有关的方法,比如:
collection.add(new Object());//The method add(capture#1-of ?) in the type Collection<capture#1-of ?> is not applicable for the arguments (Object)

因为程序调用这个方法的时候传入的参数不知道是什么类型的。
但是可以调用与参数类型无关的方法比如 collection.size();
总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量的主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
2、?通配符的扩展:界定通配符的上边界
List<? extends S> x = new ArrayList<T>();
类型S指定一个数据类型,那么类型T就只能是类型S或者是类型S的子类
List<? extends Number> x = new ArrayList<Integer>();//正确
List<? extends Number> y = new ArrayList<Object>();//错误  Type mismatch: cannot convert from ArrayList<Object> to List<? extends Number>

3、?通配符的扩展:界定通配符的下边界
List<? super S> x = new ArrayList<T>();
类型S指定一个数据类型,那么类型T就只能是类型S或者是类型S的父类
List<? super Number> y = new ArrayList<Object>();//正确
List<? super Number> x = new ArrayList<Integer>();//错误  Type mismatch: cannot convert from ArrayList<Integer> to List<? super Number>

提示:限定通配符总是包括自己

类型擦除

前面已经说了,Java的泛型是伪泛型。为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦出(type erasure)。
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
如在代码中定义的List<object>和List<String>等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
可以通过两个简单的例子,来证明java泛型的类型擦除。
案例一:
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<Integer> list2 = new ArrayList<Integer>();
System.out.println((list1.getClass() == list2.getClass()) + "  " + (list1.getClass() == ArrayList.class));//true  true

在这个例子中,我们定义了两个ArrayList集合,不过一个是ArrayList<String>泛型类型,只能存储字符串。一个是ArrayList<Integer>泛型类型,只能存储整形。最后,我们通过两个ArrayList对象的getClass方法获取它们的类的信息,最后发现两者相等,且等于ArrayList.class。说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。
案例二:
List<Integer> list = new ArrayList<Integer>();
list.add(10086);
Method method = list.getClass().getMethod("add", Object.class);
//运行时利用反射机制调用集合的add方法,跳过编译时的泛型检查
method.invoke(list, "虽然集合中对元素限定的泛型是Integer,但是也能通过反射把字符串添加到集合中");
Object object = list.get(1);
System.out.println(object.getClass().getSimpleName() + "  " + (object.getClass() == String.class));//String  true
try {System.out.println(((Object) list.get(1)).getClass());//class java.lang.StringSystem.out.println(list.get(1).getClass());//如果不指定list.get(1)的类型,则会默认将其强制转换为集合上指定的泛型类型
} catch (Exception e) {e.printStackTrace();//java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
}

因为泛型只在编译的时候起作用,在运行的时候,你得ArrayList已经不受泛型的控制了,也就是说跟已经没有泛型限定的ArrayList没有任何区别了。而反射直接获得了add方法的字节码,跳过编译层在运行时直接添加,这样就骗过了编译。

类型擦除后保留的原始类型

在上面,两次提到了原始类型,什么是原始类型?原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除(crased),并使用其限定类型(无限定的变量用Object)替换。
例如:
class Pair<T> {  private T value;  public T getValue() {  return value;  }  public void setValue(T  value) {  this.value = value;  }
} 

Pair<T>的原始类型为:
class Pair {  private Object value;  public Object getValue() {  return value;  }  public void setValue(Object  value) {  this.value = value;  }
} 

因为在Pair<T>中,T是一个无限定的类型变量,所以用Object替换。其结果就是一个普通的类,如同泛型加入java编程语言之前已经实现的那样。在程序中可以包含不同类型的Pair,如Pair<String>或Pair<Integer>,但是,擦除类型后它们就成为原始的Pair类型了,原始类型都是Object。
从上面的那个例2中,我们也可以明白ArrayList<Integer>被擦除类型后,原始类型也变成了Object,所以通过反射我们就可以存储字符串了。
如果类型变量有限定,那么原始类型就用第一个边界的类型变量来替换。
比如Pair这样声明:
public class Pair<T extends Comparable& Serializable> { ... } 

那么原始类型就是Comparable
如果Pair这样声明
public class Pair<T extends Serializable & Comparable> 

那么原始类型就用Serializable替换,而编译器在必要的时要向 Comparable 插入强制类型转换。为了提高效率,应该将标签接口(即没有方法的接口)放在边界限定列表的末尾。

要区分原始类型和泛型变量的类型
在调用泛型方法的时候,可以指定泛型,也可以不指定泛型。
  • 在不指定泛型的时候,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级,直到Object。
  • 在指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类。
public class Test {public static void main(String[] args) {/**不指定泛型的时候,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级,直到Object*/int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型  Number f = Test.add(1, 1.2);//这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为NumberObject o = Test.add(1, "asd");//这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Object/**指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类*/int a = Test.<Integer> add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类//int b = Test.<Integer> add(1, 2.2);//编译错误,指定了Integer,不能为Float  Number c = Test.<Number> add(1, 2.2); //指定为Number,所以可以为Integer和Float  }public static <T> T add(T x, T y) {return y;}
}

其实在泛型类中,不指定泛型的时候也差不多,只不过这个时候的泛型类型为Object,就比如ArrayList中,如果不指定泛型,那么这个ArrayList中可以放任意类型的对象。

附加:GenericDeclaration 接口

public interface java.lang.reflect.GenericDeclaration

所有已知实现类:Class、Constructor、Method
声明类型变量的所有实体的公共接口。
可以声明类型变量的实体的公共接口,也就是说,只有实现了该接口才能在对应的实体上声明(定义)类型变量,这些实体目前只有三个:Class、Construstor、Method。
注意:因为直接实现子类没有Field类,所以属性上面不能定义类型变量。

方法
  • TypeVariable<?>[]  getTypeParameters() 返回声明顺序的 TypeVariable 对象的数组,这些对象表示由此 GenericDeclaration 对象表示的一般声明声明的类型变量。

    • 返回:表示由此一般声明声明的类型变量的 TypeVariable 对象的数组
    • 如果底层的一般声明未声明任何类型变量,则返回一个 0 长度的数组。
public static <T extends Person, U> void main(String[] args) throws Exception {Method method = Test.class.getMethod("main", String[].class);TypeVariable<?>[] tvs = method.getTypeParameters();//返回声明顺序的 TypeVariable 对象的数组System.out.println("声明的类型变量有:" + Arrays.toString(tvs));//[T, U]for (int i = 0; i < tvs.length; i++) {GenericDeclaration gd = tvs[i].getGenericDeclaration();System.out.println("【GenericDeclaration】" + gd);//public static void com.bqt.Test.main(java.lang.String[]) throws java.lang.ExceptionSystem.out.println(gd.getTypeParameters()[i] == tvs[i]);//true。    GenericDeclaration和TypeVariable两者相互持有对方的引用System.out.println(tvs[i] + "  " + tvs[i].getName() + "  " + Arrays.toString(tvs[i].getBounds()));//T  T  [class com.bqt.Person] 和 U  U  [class java.lang.Object]}
}

2017-9-4
来自为知笔记(Wiz)

转载于:https://www.cnblogs.com/baiqiantao/p/7475696.html

【泛型】Generic 参数化类型 类型转换相关推荐

  1. Java程序低手之关于泛型(Generic)

    虽然一直以来,Java都提供了强制类型转换,但是那确实是Java的弱点,因为你是在取数据的时候才保证了类型安全(Type-safe),而不是在存储数据的时候就做好这项工作,Java5提供了Generi ...

  2. 泛型方法的定义和使用_泛型( Generic )

    泛型(Generic) 1. 泛型概述 泛型是一个未知的, 不确定的数据类型. 比如ArrayList 中的E, 就是一个未知的不确定的数据类型, 那么他就是一个泛型 泛型虽然是一个未知的, 不确定的 ...

  3. 你真的了解泛型 Generic 嘛?

    泛型 Generic Programming[1] 通常指允许程序员在强类型程序设计语言中,编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型,即类型参数化 首先我们不是科班讨论学术, ...

  4. DotNET:LINQ对泛型(Generic)操作和泛型方法及属性

    ylbtech-DotNET:泛型(Generic)和LINQ操作 1,遍历泛型集合 2,根据索引位置移除项(RemoveAt()) 3,查询一个人(Single()) 3.2,查询一个人(Singl ...

  5. Java - 泛型 ( Generic )

    Java - 泛型 ( Generic ) > 泛型的特点 > 解决元素存储的安全性问题 > 解决获取数据元素时,需要类型强转的问题 > 不能在 static 方法中使用泛型, ...

  6. 泛型(generic)概述和基本使用

    泛型(generic)概述和基本使用 A:泛型概述 集合边上有一个尖括号,尖括号指的就是泛型. 限定集合存储的数据类型,如果加上这个引用数据类型, 就告诉你这个集合只能存储该类型对象和该类型的子类对象 ...

  7. java泛型(Generic)超详细

    目录 1.为什么要有泛型(Generic)? 2.泛型的设计背景 2.1那么为什么要有泛型呢,直接Object不是也可以存储数据吗? 3.在集合中使用泛型 4.自定义泛型结构 4.1自定义泛型结构:泛 ...

  8. java参数传入泛型类型_Java 泛型(参数化类型)

    Java 泛型 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所 ...

  9. Java泛型(1)--集合使用泛型Generic、自定义泛型、泛型在继承上的体现、通配符的使用

    文章目录 泛型的概念 集合中使用泛型 自定义泛型结构 泛型在继承上的体现 通配符的使用 泛型的概念 集合容器类在设计阶段/声明阶段不能确定这个容器实际存的是什么类型的对象,所以在JDK1.5之前只能把 ...

最新文章

  1. Atitit.Java exe bat  作为windows系统服务程序运行
  2. 读取cc2530节点的设备类型、协调器、路由器、终端。
  3. Java程序转成exe可执行程序方法和exe4_j下载地址和破解。
  4. Java编写抓取用户信息代码_[代码全屏查看]-一个基于JAVA的知乎爬虫,抓取知乎用户基本信息...
  5. 团队作业9——第二次项目冲刺2(Beta阶段)
  6. [数分提高]2014-2015-2第7教学周第2次课 (2015-04-16)
  7. OSI 参考模型与TCP/IP 参考模型的比较
  8. Wi-Fi 6 「破茧成蝶」,高通全面布局端到端最佳解决方案
  9. 最老程序员创业札记:全文检索、数据挖掘、推荐引擎应用36
  10. html 画布 重置,html5清空画布的方法有哪些
  11. MFC使用简单总结(便于以后查阅)
  12. 文献管理三剑客之noteexpress endnote 资源和论文的搬迁备份
  13. shell sort 最后一列排序_Shell 编程 排序工具 sort 和 uniq
  14. JavaScript 高级程序设计笔记
  15. 18位身份证正则及校验码计算
  16. 春考计算机c语言题,2011春江苏省计算机等级考试c语言试题答案
  17. (全程图解)Axure RP8.0安装、破解、汉化教程
  18. 个人所得综合税年度汇算,个税计算公式,个税计算案例
  19. 百度地图自定义大头针图片和添加标注
  20. Android hilt 依赖注入使用详解

热门文章

  1. OSI分层和五层协议
  2. iOS混淆 -- 生成define 替换名
  3. 算法分类整理+模板②:字符串处理
  4. Google Nexus5在linux下刷原生安卓 android6.0
  5. SQL Server2000 “mcc 无法创建管理单元”
  6. ProgressBar 类
  7. 转:如何求出grid图像每个cell对应的x,y坐标?
  8. C语言 数字和字符串的转换 error
  9. WPF 问题 PresentationCore.dll!System.Windows.Media.Composition.DUCE.Channel.SyncFlush() 分析
  10. CentOS 7 防火墙开启了哪些服务和端口?