Java-Java5.0泛型解读
- 概述
- 泛型类
- 泛型方法
- 泛型接口
- 边界符
- 通配符
- 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泛型解读相关推荐
- java参数传入泛型类型_Java 5.0 泛型之 使用泛型统一传入的参数类型
Java 5.0 泛型之 使用泛型统一传入的参数类型 package Demo; // 使用泛型统一传入的参数类型 class Info28 { private T var; // 此类型由外部决定 ...
- Java面向对象系列[v1.0.0][泛型基础]
Java5增加泛型支持,很大程度上是为了让集合记住其元素的数据类型,在此之前不用泛型的话,一旦把一个对象放进Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理,从集合里取出对 ...
- Java-Java5.0注解解读
概述 元注解Meta-annotation Target Retention Documented Inherited 自定义注解 定义注解格式 注解参数的可支持数据类型 实例 编写注解类 使用注解 ...
- java object转泛型_为什么Java的泛型要用擦除实现
在 Java 中的 泛型 ,常常被称之为 伪泛型 ,究其原因是因为在实际代码的运行中,将实际类型参数的信息擦除掉了 (Type Erasure) .那是什么原因导致了 Java 做出这种妥协的呢?下面 ...
- Java多态与泛型 ,动态绑定,静态绑定
文章目录 (一)多态 1.概念 1.1 使用继承: 1.2 实现接口(推荐): 2.作用 3.多态的实现原理 3.1 Java 动态绑定以及内部实现机制 程序绑定的概念 关于final.static. ...
- Java中创建泛型数组
Java中创建泛型数组 使用泛型时,我想很多人肯定尝试过如下的代码,去创建一个泛型数组 T[] array = new T[]; 当我们写出这样的代码时编译器会报Cannot create a gen ...
- 【Java从0到架构师(1),Java中高级面试题总结(全面)
JSP 九大内置对象 MySQL 基础 + 多表查询 [Java从0到架构师]MySQL 基础 MySQL MySQL 的使用步骤 数据库的内部存储细节 GUI 工具 SQL 语句 DDL 语句 DD ...
- 用 for/in 在 Java 5.0 中增强循环
http://www.ibm.com/developerworks/cn/java/j-forin.html 简介: for/in 循环通常叫作 增强的 for 或者 foreach,它是 Java ...
- 14. Java基础之泛型
一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...
最新文章
- [转]g++ 编译多个相关文件
- 李开复:天才将占领创业领域
- SpringBoot启动项目时提示:Error:java: 读取***.jar时出错;
- 日常生活小技巧 -- win10造字
- 感觉灵感被掏空?你需要这 9 篇论文来补一补 | PaperDaily #05
- Java13-day04【Integer、int和String的相转、自动装箱和拆箱、Date、SimpleDateFormat、Calendar、异常、try...catch、throws】
- Pytorch:GAN生成对抗网络实现二次元人脸的生成
- 在save中重写 AdminModel 方法 和 Signals
- struts -Tiles介绍
- OpenLDAP的安装测试及管理
- [python opencv 计算机视觉零基础到实战] 一 opencv的helloworld
- 微软Scott CIO也要代表公司拜访客户
- 在C++中用虚函数的作用是什么? 为什么要用到虚函数?
- Iptables防火墙原理
- Docker(二十九)k8s 创建动态存储,基于nfs 的storageclass
- suse linux rpm 安装
- 如何让ul的符号隐藏_HTML+CSS之如何找BUG
- onlaunch 异步_微信小程序之onLaunch与onload异步问题
- BAT架构技术专题合集500+
- java开发实例大全_java编程实例大全100例
热门文章
- python获取当前服务器ip_Python实现获取域名所用服务器的真实IP
- 想学习linux服务器、做运维、部署项目的同学看这,linux部署
- tf.lookup.StaticHashTable 用法
- 进制转换c++代码_跟小黑学漏洞利用开发之16进制字符转换
- Leetcode 169.多数元素 (每日一题 20210715)
- 强化学习及其在NLP上的应用
- 机器学习笔记:误差的来源(bias variance)
- hadoop学习-海量日志分析(提取KPI指标)
- python前缀表达式求值_python数据结构与算法 11 后缀表达式求值
- 山东财经大学python试卷_山东财经大学微观经济学试卷1及答案