Java 基础 一文搞懂泛型
本文将从以下四个方面来系统的讲解一下泛型,基本上涵盖了泛型的主体内容。
- 什么是泛型?
- 为什么要使用泛型?
- 如何使用泛型?
- 泛型的特性
1. 什么是泛型?
泛型的英文是Generics,是指在定义方法、接口或类的时候,不预先指定具体的类型,而使用的时候再指定一个类型的一个特性。
写过Java代码的同学应该知道,我们在定义方法、接口或类的时候,都要指定一个具体的类型。比如:
public class test {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
上面代码就定义了字段name的类型为String,方法getName的返回类型为String,这种写法就是预先指定了具体的类型。而泛型就是不预先指定具体的类型。
Java中有一个类型叫ArrayList,相当于一个可变长度的数组。在ArrayList类型中就没有预先指定具体的类型。因为数组可以存放任何类型的数据,如果要预先指定一个数组类型的话,那要满足大家对各种类型的需求,就要写很多类型的ArrayList,要为每个class写一个单独的ArrayList,比如:
IntegerArrayListStringArrayListFloatArrayListLongArrayList...
这显然不太现实,因为class有上千种,还有自己定义的class。那么在ArrayList中预先指定具体的类型就无法满足需求。这个时候就需要使用泛型,即不指定存储数据的具体的类型,这个类型由使用者决定。
为了解决类型的问题,我们必须把ArrayList变成一种模板:ArrayList<T>,代码如下:
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}
T可以是任何class,这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList:
// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();
因此,泛型也可以说是定义一种模板,例如ArrayList<T>,然后在代码中为用到的类创建对应的ArrayList<类型>。(泛型是指在定义方法、接口或类的时候,不预先指定具体的类型,而使用的时候再指定一个类型的一个特性。)后面这种定义可能会更好理解其本质。
更为官方的定义是:泛型指“参数化类型”。泛型的本质是为了参数化类型(将类型参数化传递)(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型,可以在类、接口和方法中,分别被称为泛型类,泛型接口,泛型方法。
2. 为什么要使用泛型?
参考自:Oracle 泛型文档
与非泛型的代码相比,使用泛型的代码具有很多优点:
在编译时会有更强的类型检查
Java编译器对泛型代码进行强类型检查,如果代码违反类型安全,则会发出错误。修复编译时的错误比修复运行时的错误会更加简单,运行时的错误会更难找到。
说人话就是,使用泛型时,编译器会对输入的类型的进行检查,类型与声明的类型不一致时就会报错。而不使用泛型,编译器可能就检测不到这个类型错误,就会在运行的时候报错。
消除类型转换
下面的代码是没有使用泛型的情况,这时候需要对类型进行转换
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
使用泛型,就不需要对类型进行转换
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
可以实现更通用的算法
通过使用泛型,程序员可以对不同类型的集合进行自定义操作以实现通用算法,并且代码类型会更加安全、代码更易读
3. 如何使用泛型?
还是以ArrayList为例,如果不定义泛型类型时,泛型类型此时就是Object:
// 编译器警告:
List list = new ArrayList();
list.add("Hello");
list.add("World");
String first = (String) list.get(0);
String second = (String) list.get(1);
此时,只能把<T>当作Object使用,没有发挥泛型的优势。
当我们定义泛型类型<String>后,List<T>的泛型接口变为强类型List<String>:
// 无编译器警告:
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);
编译器看到泛型类型List<String>就可以自动推断出后面的ArrayList<T>的泛型类型必须是ArrayList<String>,因此,可以把代码简写为:
// 可以省略后面的Number,编译器可以自动推断泛型类型:
List<String> list = new ArrayList<>();
3.1 泛型类
泛型类的语法形式:
class name<T1, T2, ..., Tn> { /* ... */ }
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。由尖括号(<>)分隔的类型参数部分跟在类名后面。它指定类型参数(也称为类型变量)T1,T2,...和 Tn。
一般将泛型中的类名称为原型,而将 <> 指定的参数称为类型参数。
在泛型出现之前,一个类要想处理所有类型的数据,只能使用Object做数据转换。实例如下:
public class Info {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
使用泛型之后,其实就是将Object换成T,并声明<T>:
public class Info<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
在上面的例子中,在初始化一个泛型类时,使用 <> 指定了内部具体类型,在编译时就会根据这个类型做强类型检查。
实际上,不使用 <> 指定内部具体类型,语法上也是支持的(不推荐这么做),这样的调用就失去泛型类型的优势。如下所示:
public static void main(String[] args) {
Info info = new Info();
info.setValue(10);
System.out.println(info.getValue());
info.setValue("abc");
System.out.println(info.getValue());
}
上面是单个类型参数的泛型类。
下面我们看一下多个类型参数的泛型类该如何编写。
例如,我们定义Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>:
public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public K getLast() {
return last;
}
}
使用的时候,需要指出两种类型:
Pair<String, Integer> p = new Pair<>("test", 123);
Java标准库的Map<K, V>就是使用两种泛型类型的例子。它对Key使用一种类型,对Value使用另一种类型。
小结
编写泛型时,需要定义泛型类型<T>;
泛型可以同时定义多种类型,例如Map<K, V>。
3.2 泛型接口
接口也可以声明泛型。
泛型接口语法形式:
public interface Content<T> {
T text();
}
泛型接口有两种实现方式:
实现接口的子类明确声明泛型类型
预先声明继承的具体类型的接口类,下面就是继承的
Integer类型的接口类。public class IntContent implements Content<Integer> {
private int text; public IntContent(int text) {
this.text = text;
} @Override
public Integer text() {
return text;
}
}
因为子类并没有泛型类型,所以正常使用就行。
InContent ic = new IntContent(10);
实现接口的子类不明确声明泛型类型
public class GenericsContent<T> implements Content<T> {
private T text; public GenericsContent(T text) {
this.text = text;
} @Override
public T text() {
return text;
}
}
此时子类也使用了泛型类型,就需要指定具体类型
Content<String> gc = new GenericsContent<>("ABC");
3.3 泛型方法
泛型方法是引入其自己的类型参数的方法。泛型方法可以是普通方法、静态方法以及构造方法。
泛型方法语法形式如下:
public <T> T func(T obj) {}
注意:是否拥有泛型方法,与其所在的类是否是泛型没有关系。
泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际类型参数的占位符。
使用泛型方法的时候,通常不必指明类型参数,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行推断。
编译器会认为:调用泛型方法后,其返回值被赋给一个 Object 类型的变量。
public class GenericsMethodDemo01 {
public static <T> void printClass(T obj) {
System.out.println(obj.getClass().toString());
}
public static void main(String[] args) {
printClass("abc");
printClass(10);
}
}
// Output:
// class java.lang.String
// class java.lang.Integer
泛型方法中也可以使用可变参数列表
public class GenericVarargsMethodDemo {
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
Collections.addAll(result, args);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
}
}
// Output:
// [A]
// [A, B, C]
4. 泛型的特性
4.1 类型擦除(Type Erasure)
Java 语言引入泛型是为了在编译时提供更严格的类型检查,并支持泛型编程。不同于 C++ 的模板机制,Java 泛型是使用类型擦除来实现的,使用泛型时,任何具体的类型信息都被擦除了。
那么,类型擦除做了什么呢?它做了以下工作:
- 把泛型中的所有类型参数替换为 Object,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法。
- 擦除出现的类型声明,即去掉
<>的内容。比如T get()方法声明就变成了Object get();List<String>就变成了List。如有必要,插入类型转换以保持类型安全。 - 生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类;因此,泛型不会产生运行时开销。
Java 泛型的实现方式不太优雅,但这是因为泛型是在 JDK5 时引入的,为了兼容老代码,必须在设计上做一定的折中。
简单来说类型擦除是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
例如,我们编写了一个泛型类Pair<T>,这是编译器看到的代码:
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
而虚拟机根本不知道泛型。这是虚拟机执行的代码:
public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
因此,Java使用类型擦拭实现泛型,导致了:
- 编译器把类型
<T>视为Object; - 编译器根据
<T>实现安全的强制转型。
因此,Java使用擦拭法实现泛型,导致了:
- 编译器把类型
<T>视为Object; - 编译器根据
<T>实现安全的强制转型。
使用泛型的时候,我们编写的代码也是编译器看到的代码:
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
而虚拟机执行的代码并没有泛型:
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
所以,Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。
泛型的局限
了解了Java泛型的实现方式——类型擦除,我们就知道了Java泛型的局限:
局限一:<T>不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型:
Pair<int> p = new Pair<>(1, 2); // compile error!
局限二:无法取得带泛型的Class。观察以下代码:
public class test {
public static void main(String[] args) {
List<Object> list1 = new ArrayList<Object>();
List<String> list2 = new ArrayList<String>();
System.out.println(list1.getClass());
System.out.println(list2.getClass());
}
}
// Output:
// class java.util.ArrayList
// class java.util.ArrayList
因为T是Object,我们对ArrayList<Object>和ArrayList<String>类型获取Class时,获取到的是同一个Class,也就是ArrayList类的Class。
换句话说,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是ArrayList<Object>。
局限三:无法判断带泛型的类型:
List<Integer> p = new ArrayList<>();
// Compile error:
if (p instanceof List<String>) {
}
原因和前面一样,并不存在List<String>.class,而是只有唯一的List.class。
泛型和继承
正是由于泛型时基于类型擦除实现的,所以,泛型类型无法向上转型。
向上转型是指用子类实例去初始化父类,这是面向对象中多态的重要表现。

Integer 继承了 Object;ArrayList 继承了 List;但是 List<Interger> 却并非继承了 List<Object>。
这是因为,泛型类并没有自己独有的 Class 类对象。比如:并不存在 List<Object>.class 或是 List<Interger>.class,Java 编译器会将二者都视为 List.class。
4.2 上边界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
extend通配符
为泛型添加上边界,即传入的类型实参必须是指定类型的子类型
// 可以限制传入方法的参数的类型
<? extends xxx>
// 也可以限制T的类型
<T extends XXX>
// 类型边界可以设置多个,语法形式如下:
<T extends B1 & B2 & B3>
注意:extends 关键字后面的第一个类型参数可以是类或接口,其他类型参数只能是接口。
<? extends xxx>
举个例子:
public class test {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
int n = add(p);
System.out.println(n);
}
static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
通过使用<? extends Number>,我们可以传入Number类型的子类类型的数组。就可以执行数值类型的加法。
这种使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了。除了可以传入Pair<Integer>类型,我们还可以传入Pair<Double>类型,Pair<BigDecimal>类型等等,因为Double和BigDecimal都是Number的子类。
如果我们考察对Pair<? extends Number>类型调用getFirst()方法,实际的方法签名变成了:
<? extends Number> getFirst();
接下来,我们再来考察一下Pair<T>的set方法:
public class test {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
int n = add(p);
System.out.println(n);
}
static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
p.setFirst(new Integer(first.intValue() + 100));
p.setLast(new Integer(last.intValue() + 100));
return p.getFirst().intValue() + p.getFirst().intValue();
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
public void setFirst(T first) {
this.first = first;
}
public void setLast(T last) {
this.last = last;
}
}
// 会得到一个编译错误
// The method setFirst(capture#3-of ? extends Number) in the type Pair<capture#3-of ? extends Number> is not applicable for the arguments (int)Java(67108979)
编译错误的原因在于,如果一开始我们传入的p是Pair<Double>,显然它满足参数定义Pair<? extends Number>,然而,Pair<Double>的setFirst()显然无法接受Integer类型。
这就是<? extends Number>通配符的一个重要限制:方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)。
这里唯一的例外是可以给方法参数传入null:
p.setFirst(null); // ok, 但是后面会抛出NullPointerException
p.getFirst().intValue(); // NullPointerException
使用extends限定T类型
在定义泛型类型Pair<T>的时候,也可以使用extends通配符来限定T的类型:
public class Pair<T extends Number> { ... }
现在,我们只能定义:
Pair<Number> p1 = null;
Pair<Integer> p2 = new Pair<>(1, 2);
Pair<Double> p3 = null;
因为Number、Integer和Double都符合<T extends Number>。
非Number类型将无法通过编译:
Pair<String> p1 = null; // compile error!
Pair<Object> p2 = null; // compile error!
因为String、Object都不符合<T extends Number>,因为它们不是Number类型或Number的子类。
小结
使用类似<? extends Number>通配符作为方法参数时表示:
- 方法内部可以调用获取
Number引用的方法,例如:Number n = obj.getFirst();; - 方法内部无法调用传入
Number引用的方法(null除外),例如:obj.setFirst(Number n);。
即一句话总结:使用extends通配符表示可以读,不能写。
使用类似<T extends Number>定义泛型类时表示:
- 泛型类型限定为
Number以及Number的子类。
4.3 下边界
super 下界通配符将未知类型限制为该类型的特定类型或超类类型。
和extends通配符相反,这次,我们希望接受Pair<Integer>类型,以及Pair<Number>、Pair<Object>,因为Number和Object是Integer的父类,setFirst(Number)和setFirst(Object)实际上允许接受Integer类型。
我们使用super通配符来改写这个方法:
void set(Pair<? super Integer> p, Integer first, Integer last) {
p.setFirst(first);
p.setLast(last);
}
注意到Pair<? super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型。
这里注意到我们无法使用Integer类型来接收getFirst()的返回值,即下面的语句将无法通过编译:
Integer x = p.getFirst();
因为如果传入的实际类型是Pair<Number>,编译器无法将Number类型转型为Integer。
因此,使用<? super Integer>通配符表示:
- 允许调用
set(? super Integer)方法传入Integer的引用; - 不允许调用
get()方法获得Integer的引用。
唯一例外是可以获取Object的引用:Object o = p.getFirst()。
换句话说,使用<? super Integer>通配符作为方法参数,表示方法内部代码对于参数只能写,不能读。
对比extends和super通配符
我们再回顾一下extends通配符。作为方法参数,<? extends T>类型和<? super T>类型的区别在于:
<? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);<? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。
一个是允许读不允许写,另一个是允许写不允许读。
4.4 无限定通配符
我们已经讨论了<? extends T>和<? super T>作为方法参数的作用。实际上,Java的泛型还允许使用无限定通配符(Unbounded Wildcard Type),即只定义一个?:
void sample(Pair<?> p) {
}
因为<?>通配符既没有extends,也没有super,因此:
- 不允许调用
set(T)方法并传入引用(null除外); - 不允许调用
T get()方法并获取T引用(只能获取Object引用)。
无界通配符有两种应用场景:
- 可以使用 Object 类中提供的功能来实现的方法。
- 使用不依赖于类型参数的泛型类中的方法。
语法形式:<?>
public class GenericsUnboundedWildcardDemo {
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
}
}
// Output:
// 1 2 3
// one two three
小结
使用类似<? super Integer>通配符作为方法参数时表示:
- 方法内部可以调用传入
Integer引用的方法,例如:obj.setFirst(Integer n);; - 方法内部无法调用获取
Integer引用的方法(Object除外),例如:Integer n = obj.getFirst();。
即使用super通配符表示只能写不能读。
无限定通配符<?>很少使用,可以用<T>替换,同时它是所有<T>类型的超类。
4.5 泛型命名
泛型一些约定俗成的命名(实际并无意义,但是建议对应着来命名泛型):
- E - Element
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. - 2nd, 3rd, 4th types
5. end
理解泛型之后可以方便我们更好的阅读Java框架的源码,实际编程来说不一定会用到,但是可以用到泛型编程的地方,建议使用,可以简化代码。
6. 参考资料
Java 基础 一文搞懂泛型的更多相关文章
- Java基础-一文搞懂位运算
在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+.-.*./.%).关系运算(<.>.<=.>=.==.!=)和逻辑运算(&&.||.!), ...
- Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!
本文原作者: Wizey,作者博客:http://wenshixin.gitee.io,即时通讯网收录时有改动,感谢原作者的无私分享. 1.引言 典型的Web端即时通讯技术应用场景,主要有以下两种形式 ...
- 一文搞懂所有Java集合面试题
Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...
- 基础篇|一文搞懂RNN(循环神经网络)
基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...
- 三文搞懂学会Docker容器技术(下)
接着上面一篇:三文搞懂学会Docker容器技术(上) 三文搞懂学会Docker容器技术(中) 7,Docker容器目录挂载 7.1 简介 容器目录挂载: 我们可以在创建容器的时候,将宿主机的目录与容器 ...
- 一文搞懂如何使用Node.js进行TCP网络通信
摘要: 网络是通信互联的基础,Node.js提供了net.http.dgram等模块,分别用来实现TCP.HTTP.UDP的通信,本文主要对使用Node.js的TCP通信部份进行实践记录. 本文分享自 ...
- 一文搞懂指标采集利器 Telegraf
作者| 姜闻名 来源|尔达 Erda 公众号 导读:为了让大家更好的了解 MSP 中 APM 系统的设计实现,我们决定编写一个<详聊微服务观测>系列文章,深入 APM 系统的产品.架构 ...
- 一文搞懂Google Navigation Component
一文搞懂Google Navigation Component 应用中的页面跳转是一个常规任务, Google官方提供的解决方案是Android Jetpack的Navigation componen ...
- 一文搞懂Flink Window机制
Windows是处理无线数据流的核心,它将流分割成有限大小的桶(buckets),并在其上执行各种计算. 窗口化的Flink程序的结构通常如下,有分组流(keyed streams)和无分组流(non ...
随机推荐
- Linux文件常用指令
目录 Linux文件常用指令 1.pwd 显示当前目录 2.cd 切换目录 3.mkdir 创建目录 4.touch 修改或创建文件 5.ls 显示目录下的内容 6.cat 查看文件信息 7.echo ...
- 行业动态 | 通过使用Apache Cassandra实现实时供应链管理
借助基于Apache Cassandra的DataStax Enterprise,C&S Wholesale确实得到了他们所需要的东西--一个持续在线的仓库运作整体视图. 视图中包含了原本 ...
- teprunner测试平台部署到Linux系统Docker
本文是一篇过渡,在进行用例管理模块开发之前,有必要把入门篇开发完成的代码部署到Linux系统Docker中,把部署流程走一遍,这个过程对后端设计有决定性影响. 本地运行 通过在Vue项目执行npm r ...
- 初见pyecharts
pyecharts(可互动的可视化) 模块准备 在Anaconda Prompt中下载pyecharts v1版本(>=python3.6) pip install pyecharts 可视化最 ...
- HTML的基础语法
区别于c语言这类高级语言,HTML不是编程语言,而好似一种描述型语言,用于描述网页中内容的显示方式. HTML标记以<>来进行标记.HTML中的标记按其是否成对出现,可以分为单标记和双标记 ...
- 解析库--XPath
from lxml import etree 2 text = ''' 3 <div> 4 <ul> 5 <li class = "item-0"&g ...
- python网络编程--TCP客户端的开发
#导入socket模块 2 import socket 3 #参数说明 4 """ 5 socket类的介绍 6 创建客户端socket对象 7 socket.socke ...
- JVM之对象回收
finalize /** *此代码演示了两点: *1.对象可以在被GC时自我拯救. *2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次 */ public ...
- P1601_A+B Problem(高精)(JAVA语言)
思路:BigInteger first blood! //四行搞定 题目背景 无 题目描述 高精度加法,x相当于a+b problem,[b][color=red]不用考虑负数[/color][/b] ...
- Python爬虫系列之爬取美团美食板块商家数据(二)
今天为大家重写一个美团美食板块小爬虫,说不定哪天做旅游攻略的时候也可以用下呢.废话不多说,让我们愉快地开始吧~ 开发工具 Python版本:3.6.4 相关模块: requests模块: argpar ...