• 概述
  • 泛型类
  • 泛型方法
  • 泛型接口
  • 边界符
  • 通配符
  • PECS原则
  • 类型擦除

概述

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。


泛型类

我们先看一个简单的类的定义

package com.xgj.master.java.generics;public class GenericClass {private String str;public String getStr() {return str;}public void setStr(String str) {this.str = str;}}

这是我们最常见的做法,但是这样做有一个坏处 GenericClass类中只能装入String类型的元素,如果我们以后要扩展该类,比如转入Integer类型的元素,就不想要从重写,代码得不到复用,我们使用泛型可以很好地解决这个问题。

如下代码所示:

package com.xgj.master.java.generics;
/*** * @ClassName: GenericClass* @Description: 泛型类* @author: Mr.Yang* @date: 2017年8月31日 下午3:35:38* @param <T>*/
public class GenericClass<T> {// T stands for "Type"private T  t;public T getT() {return t;}public void setT(T t) {this.t = t;}}

这样GenericClass类便可以得到复用,我们可以将T替换成任何我们想要的类型:

假设我们有一个User类

GenericClass<String> string = new GenericClass<String>();
GenericClass<User> user = new GenericClass<User>();

另外一个示例:

package com.xgj.master.java.generics;import org.junit.Test;public class NormalClass {@Testpublic void test(){Point point = new Point();point.setX(100); // int -> Integer -> Objectpoint.setY(20);int x = (Integer) point.getX(); // 必须向下转型int y = (Integer) point.getY();System.out.println("This point is:" + x + ", " + y);point.setX(25.4); // double -> Integer -> Objectpoint.setY("字符串");// 必须向下转型double m = (Double) point.getX();// 运行期间抛出异常  java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Doubledouble n = (Double) point.getY(); System.out.println("This point is:" + m + ", " + n);}/*** * @ClassName: Point* @Description: 一般内部类* @author: Mr.Yang* @date: 2017年8月31日 下午8:23:31*/class Point {Object x = 0;Object y = 0;public Object getX() {return x;}public void setX(Object x) {this.x = x;}public Object getY() {return y;}public void setY(Object y) {this.y = y;}}
}

上面的代码中,设置值的时候不会有任何问题,但是取值时,要向下转型,因向下转型存在着风险,而且编译期间不容易发现,只有在运行期间才会抛出异常,所以要尽量避免使用向下转型。

那么,有没有更好的办法,既可以不使用重载(有重复代码),又能把风险降到最低呢?

可以使用泛型类(Java Class),它可以接受任意类型的数据。所谓“泛型”,就是“宽泛的数据类型”,任意的数据类型。

package com.xgj.master.java.generics;import org.junit.Test;public class GenericClass2 {@Testpublic void test() {Point<Integer, Integer> point = new Point<Integer, Integer>();point.setX(200);point.setY(400);Integer x = point.getX();Integer y = point.getY();System.out.println("This point is:" + x + ", " + y);Point<Double, String> point2 = new Point<Double, String>();point2.setX(25.4);point2.setY("字符串");double m = point2.getX();String n = point2.getY();System.out.println("This point is:" + m + ", " + n);}/*** * * @ClassName: Point* * @Description: 泛型类, T1 T2仅表示类型* * @author: Mr.Yang* * @date: 2017年8月31日 下午8:20:26* * @param <T1>* @param <T2>*/class Point<T1, T2> {T1 x;T2 y;public T1 getX() {return x;}public void setX(T1 x) {this.x = x;}public T2 getY() {return y;}public void setY(T2 y) {this.y = y;}}
}

与普通类的定义相比,上面的代码在类名后面多出了 <T1, T2>T1, T2 是自定义的标识符,也是参数,用来传递数据的类型,而不是数据的值,我们称之为类型参数。在泛型中,不但数据的值可以通过参数传递,数据的类型也可以通过参数传递。T1, T2 只是数据类型的占位符,运行时会被替换为真正的数据类型。

传值参数(我们通常所说的参数)由小括号包围,如 (int x, double y),类型参数(泛型参数)由尖括号包围,多个参数由逗号分隔,如 <T><T, E>

类型参数需要在类名后面给出。一旦给出了类型参数,就可以在类中使用了。类型参数必须是一个合法的标识符,习惯上使用单个大写字母,通常情况下,K 表示键,V 表示值,E 表示异常或错误,T 表示一般意义上的数据类型。

泛型类在实例化时必须指出具体的类型,也就是向类型参数传值,格式为:

  className variable<dataType1, dataType2> = new className<dataType1, dataType2>();

也可以省略等号右边的数据类型,但是会产生警告,即:

    className variable<dataType1, dataType2> = new className();

因为在使用泛型类时指明了数据类型,赋给其他类型的值会抛出异常,既不需要向下转型,也没有潜在的风险,比上个例子的自动装箱和向上转型要更加实用。


泛型方法

我们可以编写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

定义泛型方法的规则如下:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前, 比如 public static <E> void printArray( E[] inputArray ){}

  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。

  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。

  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是short, int, double, long, float, byte, char等原始类型。但是传递基本类型不会报错,因为它们会自动装箱成对应的包装类。

    那如何声明一个泛型方法呢? 声明一个泛型方法很简单,只要在返回类型前面加上一个类似<K, V>的形式就可以啦。

比如:

package com.xgj.master.java.generics;/*** * @ClassName: Compare* @Description: 内部类,泛型类* @author: Mr.Yang* @date: 2017年8月31日 下午4:01:39* @param <K>* @param <V>*/
public class ComPare<K,V> {private K  key;private V value;/*** * @Title:ComPare* @Description:构造函数* @param key* @param value* @return */public  ComPare(K key, V value) {this.key = key;this.value = 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;}
}
package com.xgj.master.java.generics;/*** * @ClassName: GenericMethod* @Description: 泛型方法演示* @author: Mr.Yang* @date: 2017年8月31日 下午3:54:23*/
public class GenericMethod {/*** * @Title: cofer* @Description: 泛型方法* @param c1* @param c2* @return* @return: boolean*/public static <K,V> boolean cofer(ComPare<K,V> c1, ComPare<K,V> c2){return c1.getKey().equals(c2.getKey()) && c1.getValue().equals(c2.getValue());}// 泛型方法的调用public static void main(String[] args) {ComPare<Integer,String> c1 = new ComPare<>(1, "dog");ComPare<Integer, String> c2 = new ComPare<>(2, "cat");boolean different = GenericMethod.<Integer,String>cofer(c1, c2);System.out.println("c1 compares c2,and the result is  " + different);// 在Java1.7/1.8可以利用type inference,让Java自动推导出相应的类型参数boolean different2 = GenericMethod.cofer(c1, c2);System.out.println("自动推导 c1 compares c2,and the result is  " + different2);}
}

运行结果:

c1 compares c2,and the result is  false
自动推导 c1 compares c2,and the result is  false

第二个示例:

package com.xgj.master.java.generics;public class GenericMethod2 {public static void main(String[] args) {// 实例化泛型类Point<Integer, Integer> p1 = new Point<Integer, Integer>();p1.setX(10);p1.setY(20);p1.printPoint(p1.getX(), p1.getY());Point<Double, String> p2 = new Point<Double, String>();p2.setX(25.4);p2.setY("字符串");p2.printPoint(p2.getX(), p2.getY());}
}/*** * * @ClassName: Point* * @Description: 定义泛型类* * @author: Mr.Yang* * @date: 2017年8月31日 下午7:59:47* * @param <T1>* @param <T2>*/
class Point<T1, T2> {T1 x;T2 y;public T1 getX() {return x;}public void setX(T1 x) {this.x = x;}public T2 getY() {return y;}public void setY(T2 y) {this.y = y;}/*** * * @Title: printPoint* * @Description: 定义泛型方法* * @param x* @param y* * @return: void*/public <T1, T2> void printPoint(T1 x, T2 y) {T1 m = x;T2 n = y;System.out.println("This point is:" + m + ", " + n);}
}

上面的代码中定义了一个泛型方法 printPoint(),既有普通参数,也有类型参数,类型参数需要放在修饰符后面、返回值类型前面。一旦定义了类型参数,就可以在参数列表、方法体和返回值类型中使用了。

与使用泛型类不同,使用泛型方法时不必指明参数类型,编译器会根据传递的参数自动查找出具体的类型。泛型方法除了定义不同,调用就像普通方法一样。

注意:泛型方法与泛型类没有必然的联系,泛型方法有自己的类型参数,在普通类中也可以定义泛型方法。

泛型方法 printPoint() 中的类型参数 T1, T2 与泛型类 Point 中的 T1, T2 没有必然的联系,也可以使用其他的标识符代替:

public static <V1, V2> void printPoint(V1 x, V2 y){V1 m = x;V2 n = y;System.out.println("This point is:" + m + ", " + n);
}

泛型接口

在Java中也可以定义泛型接口, 示例如下:

package com.xgj.master.java.generics;public class GenericInterfaceDemo {public static void main(String arsg[]) {Info<String> obj = new InfoImp<String>("xiaogongjiang");System.out.println("Length Of String: " + obj.getVar().length());}
}/*** * * @ClassName: Info* * @Description: 定义泛型接口* * @author: Mr.Yang* * @date: 2017年8月31日 下午8:06:06* * @param <T>*/
interface Info<T> {public T getVar();
}/*** * * @ClassName: InfoImp* * @Description: 泛型接口实现类* * @author: Mr.Yang* * @date: 2017年8月31日 下午8:06:13* * @param <T>*/
class InfoImp<T> implements Info<T> {private T var;/*** * * @Title:InfoImp* *              定义泛型构造方法* * @param var*/public InfoImp(T var) {this.setVar(var);}public void setVar(T var) {this.var = var;}public T getVar() {return this.var;}
}

边界符

在上面的代码中 , 类型参数可以接受任意的数据类型,只要它是被定义过的。但是,很多时候我们只需要一部分数据类型就够了,用户传递其他数据类型可能会引起错误。例如,编写一个泛型函数用于返回不同类型数组(Integer 数组、Double 数组、Character 数组等)中的最大值

package com.xgj.master.java.generics;public class CountGreater {public static <T>  int  countGreaterThan(T[] array, T element){int count = 0 ;// 遍历数组for (T t : array) {if (t > element) {   // 编译报错++count;}}return count;}
}

但是这样很明显是错误的,因为除了short, int, double, long, float, byte, char等原始类型,其他的类并不一定能使用操作符>,所以编译器报错,那怎么解决这个问题呢?答案是使用边界符, 通过 extends 关键字可以限制泛型的类型.

public interface Comparable<T> {public int comparable(T t);
}

做个类似下面这样的声明,这样就等于告诉编译器类型参数T代表的都是实现了Comparable接口的类,这样等于告诉编译器它们都至少实现了compareTo方法。

public class CountGreater {public static <T extends Comparable<T>>  int  countGreaterThan(T[] array, T element){int count = 0 ;// 遍历数组for (T t : array) {if (t.comparable(element) > 0) { ++count;}}return count;}}

extends 后面可以是类也可以是接口。但这里的 extends 已经不是继承的含义了,应该理解为 T 是继承自 Comparable类的类型,或者 T 是实现了 XX 接口的类型。


通配符

在了解通配符之前,我们首先必须要澄清一个概念,还是借用我们上面定义的GenericClass类,假设我们添加一个这样的方法:

public void test(GenericClass<Number> n){/***/}

那么现在GenericClass<Number> n允许接受什么类型的参数?
我们是否能够传入GenericClass<Integer>或者GenericClass<Double>呢?
答案是否定的,虽然Integer和Double是Number的子类,但是在泛型中GenericClass<Integer>或者GenericClass<Double>GenericClass<Number>之间并没有任何的关系。

这一点非常重要,接下来我们通过一个完整的例子来加深一下理解。

首先我们先定义几个简单的类,下面我们将用到它:

class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}

我们创建了一个泛型类Reader,然后在f1()中当我们尝试Fruit f = fruitReader.readExact(apples);编译器会报错,因为List<Fruit>List<Apple>之间并没有任何的关系。

如下:

package com.xgj.master.java.generics;import java.util.Arrays;
import java.util.List;public class GenericReading {static List<Apple> apples = Arrays.asList(new Apple());static List<Orange> oranges = Arrays.asList(new Orange());static class Reader<T> {T readExact(List<T> list) {return list.get(0);}}static void f1() {Reader<Fruit> fruitReader = new Reader<Fruit>();// The method readExact(List<Fruit>) in the type// GenericReading.Reader<Fruit> is not applicable for the arguments (List<Apple>)Fruit f = fruitReader.readExact(apples); // 编译报错}public static void main(String[] args) {f1();}
}

但是按照我们通常的思维习惯,Apple和Fruit之间肯定是存在联系,然而编译器却无法识别,那怎么在泛型代码中解决这个问题呢?我们可以通过使用通配符来解决这个问题:

package com.xgj.master.java.generics;import java.util.Arrays;
import java.util.List;public class GenericReading {static List<Apple> apples = Arrays.asList(new Apple());static List<Orange> oranges = Arrays.asList(new Orange());static class CovariantReader<T> {T readCovariant(List<? extends T> list) {return list.get(0);}}static void f2() {CovariantReader<Fruit> fruitReader = new CovariantReader<Fruit>();Fruit f = fruitReader.readCovariant(oranges);Fruit a = fruitReader.readCovariant(apples);}public static void main(String[] args) {f2();}
}

这样就相当与告诉编译器, fruitReader的readCovariant方法接受的参数只要是满足Fruit的子类就行(包括Fruit自身),这样子类和父类之间的关系也就关联上了。


PECS原则

上面我们看到了类似<? extends T>的用法,利用它我们可以从list里面get元素,那么我们可不可以往list里面add元素呢?

比如

package com.xgj.master.java.generics;import java.util.ArrayList;
import java.util.List;public class GenericsAndCovariance {public static void main(String[] args) {// Wildcards allow covariance:List<? extends Fruit> flist = new ArrayList<Apple>();// The method add(capture#1-of ? extends Fruit) in the type List<capture#1-of ? extends Fruit> // is not applicable for the arguments   (Apple)// flist.add(new Apple()); 编译报错// flist.add(new Orange());编译报错// flist.add(new Fruit());编译报错// flist.add(new Object());编译报错flist.add(null); // 虽然可以添加null ,但是没有意义// We Know that it returns at least Fruit:Fruit f = flist.get(0);}
}

答案是否定,Java编译器不允许我们这样做,为什么呢?对于这个问题我们不妨从编译器的角度去考虑。因为List

List<? extends Fruit> flist = new ArrayList<Fruit>();
List<? extends Fruit> flist = new ArrayList<Apple>();
List<? extends Fruit> flist = new ArrayList<Orange>();
  • 当我们尝试add一个Apple的时候,flist可能指向new ArrayList<Orange>();
  • 当我们尝试add一个Orange的时候,flist可能指向new ArrayList<Apple>();
  • 当我们尝试add一个Fruit的时候,这个Fruit可以是任何类型的Fruit,而flist可能只想某种特定类型的Fruit,编译器无法识别所以会报错。

所以对于实现了<? extends T>的集合类只能将它视为Producer向外提供(get)元素,而不能作为Consumer来对外获取(add)元素。

如果我们要add元素应该怎么做呢?可以使用<? super T>

package com.xgj.master.java.generics;import java.util.ArrayList;
import java.util.List;public class GenericWriting {static List<Apple> apples = new ArrayList<Apple>();static List<Fruit> fruit = new ArrayList<Fruit>();static <T> void writeExact(List<T> list, T item) {list.add(item);}static void f1() {writeExact(apples, new Apple());writeExact(fruit, new Apple());}static <T> void writeWithWildcard(List<? super T> list, T item) {list.add(item);}static void f2() {writeWithWildcard(apples, new Apple());writeWithWildcard(fruit, new Apple());}public static void main(String[] args) {f1();f2();}
}

这样我们可以往容器里面添加元素了,但是使用super的坏处是以后不能get容器里面的元素了,原因很简单,我们继续从编译器的角度考虑这个问题,对于List<? super Apple> list,它可以有下面几种含义:

List<? super Apple> list = new ArrayList<Apple>();
List<? super Apple> list = new ArrayList<Fruit>();
List<? super Apple> list = new ArrayList<Object>();

根据上面的例子,我们可以总结出一条规律,”Producer Extends, Consumer Super”:

  • “Producer Extends” – 如果你需要一个只读List,用它来produce
    T,那么使用? extends T

  • “Consumer Super” – 如果你需要一个只写List,用它来consume T,那么使用? super T

如果需要同时读取以及写入,那么我们就不能使用通配符了。

如何阅读过一些Java集合类的源码,可以发现通常我们会将两者结合起来一起用,比如像下面这样:

public class Collections {public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i=0; i<src.size(); i++)dest.set(i, src.get(i));}
}

类型擦除

Java泛型中最令人苦恼的地方或许就是类型擦除了. 如果在使用泛型时没有指明数据类型,那么就会擦除泛型类型.

因为在使用泛型时没有指明数据类型,为了不出现错误,编译器会将所有数据向上转型为 Object,所以在取出坐标使用时要向下转型.

比如

public class Demo {public static void main(String[] args){Point p = new Point();  // 类型擦除p.setX(10);p.setY(20.8);int x = (Integer)p.getX();  // 向下转型double y = (Double)p.getY();System.out.println("This point is:" + x + ", " + y);}
}
class Point<T1, T2>{T1 x;T2 y;public T1 getX() {return x;}public void setX(T1 x) {this.x = x;}public T2 getY() {return y;}public void setY(T2 y) {this.y = y;}
}

因为在使用泛型时没有指明数据类型,为了不出现错误,编译器会将所有数据向上转型为 Object,所以在取出坐标使用时要向下转型,和不使用泛型没什么两样。

另外一个例子

public class Node<T> {private T data;private Node<T> next;public Node(T data, Node<T> next) {this.data = data;this.next = next;}public T getData() { return data; }// ...
}

编译器做完相应的类型检查之后,实际上到了运行期间上面这段代码实际上将转换成:

public class Node {private Object data;private Node next;public Node(Object data, Node next) {this.data = data;this.next = next;}public Object getData() { return data; }// ...
}

这意味着不管我们声明Node<String>还是Node<Integer>,到了运行期间,JVM统统视为Node<Object>。有没有什么办法可以解决这个问题呢?这就需要我们自己重新设置bounds了,将上面的代码修改成下面这样:

public class Node<T extends Comparable<T>> {private T data;private Node<T> next;public Node(T data, Node<T> next) {this.data = data;this.next = next;}public T getData() { return data; }// ...
}

这样编译器就会将T出现的地方替换成Comparable而不再是默认的Object了:

public class Node {private Comparable data;private Node next;public Node(Comparable data, Node next) {this.data = data;this.next = next;}public Comparable getData() { return data; }// ...
}

上面的概念或许还是比较好理解,但其实泛型擦除带来的问题远远不止这些,接下来我们系统地来看一下类型擦除所带来的一些问题。

  • 在Java中不允许创建泛型数组

  • Java泛型很大程度上只能提供静态类型检查,然后类型的信息就会被擦除,所以像下面这样利用类型参数创建实例的做法编译器不会通过

public static <E> void append(List<E> list) {E elem = new E();  // compile-time errorlist.add(elem);
}

但是如果某些场景我们想要需要利用类型参数创建实例,我们应该怎么做呢?可以利用反射解决这个问题:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {E elem = cls.newInstance();   // OKlist.add(elem);
}

实际上对于这个问题,还可以采用Factory和Template两种设计模式解决.

  • 我们无法对泛型代码直接使用instanceof关键字,因为Java编译器在生成代码的时候会擦除所有相关泛型的类型信息.
public static <E> void rtti(List<E> list) {if (list instanceof ArrayList<Integer>) {  // compile-time error// ...}
}
=> { ArrayList<Integer>, ArrayList<String>, LinkedList<Character>, ... }

Java-Java5.0泛型解读相关推荐

  1. java参数传入泛型类型_Java 5.0 泛型之 使用泛型统一传入的参数类型

    Java 5.0 泛型之 使用泛型统一传入的参数类型 package Demo; // 使用泛型统一传入的参数类型 class Info28 { private T var; // 此类型由外部决定 ...

  2. Java面向对象系列[v1.0.0][泛型基础]

    Java5增加泛型支持,很大程度上是为了让集合记住其元素的数据类型,在此之前不用泛型的话,一旦把一个对象放进Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理,从集合里取出对 ...

  3. Java-Java5.0注解解读

    概述 元注解Meta-annotation Target Retention Documented Inherited 自定义注解 定义注解格式 注解参数的可支持数据类型 实例 编写注解类 使用注解 ...

  4. java object转泛型_为什么Java的泛型要用擦除实现

    在 Java 中的 泛型 ,常常被称之为 伪泛型 ,究其原因是因为在实际代码的运行中,将实际类型参数的信息擦除掉了 (Type Erasure) .那是什么原因导致了 Java 做出这种妥协的呢?下面 ...

  5. Java多态与泛型 ,动态绑定,静态绑定

    文章目录 (一)多态 1.概念 1.1 使用继承: 1.2 实现接口(推荐): 2.作用 3.多态的实现原理 3.1 Java 动态绑定以及内部实现机制 程序绑定的概念 关于final.static. ...

  6. Java中创建泛型数组

    Java中创建泛型数组 使用泛型时,我想很多人肯定尝试过如下的代码,去创建一个泛型数组 T[] array = new T[]; 当我们写出这样的代码时编译器会报Cannot create a gen ...

  7. 【Java从0到架构师(1),Java中高级面试题总结(全面)

    JSP 九大内置对象 MySQL 基础 + 多表查询 [Java从0到架构师]MySQL 基础 MySQL MySQL 的使用步骤 数据库的内部存储细节 GUI 工具 SQL 语句 DDL 语句 DD ...

  8. 用 for/in 在 Java 5.0 中增强循环

    http://www.ibm.com/developerworks/cn/java/j-forin.html 简介: for/in 循环通常叫作 增强的 for 或者 foreach,它是 Java ...

  9. 14. Java基础之泛型

    一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...

最新文章

  1. [转]g++ 编译多个相关文件
  2. 李开复:天才将占领创业领域
  3. SpringBoot启动项目时提示:Error:java: 读取***.jar时出错;
  4. 日常生活小技巧 -- win10造字
  5. 感觉灵感被掏空?你需要这 9 篇论文来补一补 | PaperDaily #05
  6. Java13-day04【Integer、int和String的相转、自动装箱和拆箱、Date、SimpleDateFormat、Calendar、异常、try...catch、throws】
  7. Pytorch:GAN生成对抗网络实现二次元人脸的生成
  8. 在save中重写 AdminModel 方法 和 Signals
  9. struts -Tiles介绍
  10. OpenLDAP的安装测试及管理
  11. [python opencv 计算机视觉零基础到实战] 一 opencv的helloworld
  12. 微软Scott CIO也要代表公司拜访客户
  13. 在C++中用虚函数的作用是什么? 为什么要用到虚函数?
  14. Iptables防火墙原理
  15. Docker(二十九)k8s 创建动态存储,基于nfs 的storageclass
  16. suse linux rpm 安装
  17. 如何让ul的符号隐藏_HTML+CSS之如何找BUG
  18. onlaunch 异步_微信小程序之onLaunch与onload异步问题
  19. BAT架构技术专题合集500+
  20. java开发实例大全_java编程实例大全100例

热门文章

  1. python获取当前服务器ip_Python实现获取域名所用服务器的真实IP
  2. 想学习linux服务器、做运维、部署项目的同学看这,linux部署
  3. tf.lookup.StaticHashTable 用法
  4. 进制转换c++代码_跟小黑学漏洞利用开发之16进制字符转换
  5. Leetcode 169.多数元素 (每日一题 20210715)
  6. 强化学习及其在NLP上的应用
  7. 机器学习笔记:误差的来源(bias variance)
  8. hadoop学习-海量日志分析(提取KPI指标)
  9. python前缀表达式求值_python数据结构与算法 11 后缀表达式求值
  10. 山东财经大学python试卷_山东财经大学微观经济学试卷1及答案