从一知半解到揭晓Java高级语法—泛型
前言
泛型是Java基础知识的重点,虽然我们在初学Java的时候,都学过泛型,觉得自己掌握对于Java泛型的使用(全是错觉),往后的日子,当我们深入去阅读一些框架源码,你就发现了,自己会的只是简单的使用,却看不懂别人的泛型代码是怎么写的,还可以这样,没错,别人写出来的代码那叫艺术,而我......
探讨
Java语言为什么存在着泛型,而像一些动态语言Python,JavaScipt却没有泛型的概念?
原因是,像Java,C#这样的静态编译型的语言,它们在传递参数的时候,参数的类型,必须是明确的,看一个例子,简单编写一个存放int类型的栈—StackInt,代码如下:
public class StackInt {
private int maxSize;
private int[] items;
private int top;
public StackInt(int maxSize){
this.maxSize = maxSize;
this.items = new int[maxSize];
this.top = -1;
}
public boolean isFull(){
return this.top == this.maxSize-1;
}
public boolean isNull(){
return this.top <= -1;
}
public boolean push(int value){
if(this.isFull()){
return false;
}
this.items[++this.top] = value;
return true;
}
public int pop(){
if(this.isNull()){
throw new RuntimeException("当前栈中无数据");
}
int value = this.items[top];
--top;
return value;
}
}
在这里使用构造函数初始化一个StackInt对象时,可以传入String字符串吗?很明显是不行的,我们要求的是int类型,传入字符串String类型,这样在语法检查阶段时会报错的,像Java这样的静态编译型的语言,参数的类型要求是明确的
泛型解决了什么问题?
参数不安全:引入泛型,能够在编译阶段找出代码的问题,而不是在运行阶段
泛型要求在声明时指定实际数据类型,Java 编译器在编译时会对泛型代码做强类型检查,并在代码违反类型安全时发出告警。早发现,早治理,把隐患扼杀于摇篮,在编译时发现并修复错误所付出的代价远比在运行时小。
避免类型转换:
未使用泛型:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); //需要在取出Value的时候进行强制转换
使用泛型:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); //不需要强制转换
重复编码::通过使用泛型,可以实现通用编码,可以处理不同类型的集合,并且类型安全且易于阅读。像上面的StackInt类,我们不能针对每个类型去编写对应类型的栈,那样太麻烦了,而泛型的出现就很好的解决了这点
扩展
在上面的StackInt类有一些不好的地方,那就是太具体了,不够抽象,不够抽象,那么它的复用性也是不高的,例如,在另外的场景下,我需要的是往栈里存String类型的字符串,或者是其他类型,那么StackInt类就做不到了,那么有什么方法能够做到呢?再写一个StackString类,不可能,那样不得累死。那就只有引入基类Object了,我们改进一下代码:
public class StackObject {
private int maxSize;
private Object[] items;
private int top;
public StackObject(int maxSize){
this.maxSize = maxSize;
this.items = new Object[maxSize];
this.top = -1;
}
public boolean isFull(){
return this.top == this.maxSize-1;
}
public boolean isNull(){
return this.top <= -1;
}
public boolean push(Object value){
if(this.isFull()){
return false;
}
this.items[++this.top] = value;
return true;
}
public Object pop(){
if(this.isNull()){
throw new RuntimeException("当前栈中无数据");
}
Object value = this.items[top];
--top;
return value;
}
}
使用StackObject可以存储任意类型的数据,那么这样做,又有什么优点和缺点呢?
优点:StackObject类变得相对抽象了,我们可以往里面存储任何类型的数据,这样就避免了写一些重复代码
缺点:
1、用Object表示的对象是比较抽象的,它失去了类型的特点,那么我们在做一些运算的时候,可能会频繁的拆箱装箱的过程
看上面的例图,我们理解的认为存放了两个数值,12345和54321,将两个进行相加,这是很常见的操作,但是报错了,编译器给我们的提示是,+操作运算不能用于两个Object类型,那么只能对其进行类型转换,这也是我们上面说到的泛型能解决的问题,我们需要这样做,int sum = (int)val1 + (int)val2;,同时在涉及拆箱装箱时,是有一定性能的损耗的,关于拆箱装箱在这里不作描述,可以参考我写过的随笔—— 深入理解Java之装箱与拆箱
2、对于我们push进去的值,我们在取出的时候,容易忘记类型转换,或者不记得它的类型,类型转换错误,这在后面的一些业务可能埋下祸根,例如下面这个场景:直到运行时错误才暴露出来,这是不安全的,也是违反软件开发原则的,应该尽早的在编译阶段就发现问题,解决问题
3、使用Object太过于模糊了,没有具体类型的意义
最好不要用到Object,因为Object是一切类型的基类,也就是说他把一些类型的特点给抹除了,比如上面存的数字,对于数字来说,加法运算就是它的一个特点,但是用了Object,它就失去了这一特点,失去类型特有的行为
引入泛型
什么是泛型?
泛型:是被参数化的类或接口,是对类型的约定
泛型类
class name<T1, T2, ..., Tn> { /* ... */ }
一般将泛型中的类名称为原型,而将 <> 指定的参数称为类型参数,<> 相当于类型的约定,T就是类型,相当于一个占位符,由我们在调用时指定
使用泛型改进一下上面StackObject类,但是,数组和泛型不能很好地结合。你不能实例化具有参数化类型的数组,例如下面的代码是不合格的:
public StackT(int maxSize){
this.maxSize = maxSize;
this.items = new T[maxSize];
this.top = -1;
}
Java 中不允许直接创建泛型数组,这是因为相比于C++,C#的语法,Java泛型其实是伪泛型,这点在后面会说到,但是,可以通过创建一个类型擦除的数组,然后转型的方式来创建泛型数组。
private int maxSize;
private T[] items;
private int top;
public StackT(int maxSize){
this.maxSize = maxSize;
this.items = (T[]) new Object[maxSize];
this.top = -1;
}
实际上,真的需要存储泛型,还是使用容器更合适,回到原来的代码上,需要知道的是,泛型类型不能是基本类型的,需要是包装类
上面说到了Java 中不允许直接创建泛型数组,事实上,Java中的泛型我们是很难通new的方式去实例化对象,不仅仅是实例化对象,甚至是获取T的真实类型也是很难的,当然通过反射的机制还是可以获取到的,Java获取真实类型的方式有 3 种,分别是:
1、类名.class
2、对象.getClass
3、class.forName("全限定类名")
但是,在这里,1和2的方式都是做不到的,虽然我们在外边明确的传入了Integer类型,new StackT<Integer>(3);但是在StackT
类,使用T.class还是获取不到真实类型的,第 2 种方式的话,并没有传入对象,前面也说到是没有办法new方式实例化的,而通过反射机制是可以做到的,这里不作演示,需要了解的话可以参考 —— Java如何获得泛型类的真实类型、 Java通过反射获取泛型的类型
但是在C#中的泛型以及C++的模板,这是很容易做到的,所以说Java的泛型是伪泛型,Java并不是做不到像C#一样,而是为了迁就老的JDK语法所作出的妥协,至于上面为什么做不到这样,这就要说到泛型的类型擦除了。
再说类型擦除之前,先说一下泛型接口,和泛型方法吧
泛型接口
接口也可以声明泛型,泛型接口语法形式:
public interface Content<T> {
T text();
}
泛型接口有两种实现方式:
- 实现接口的子类明确声明泛型类型
public class ContentImpl implements Content<Integer> {
private int text;
public ContentImpl(int text) {
this.text = text;
}
public static void main(String[] args) {
ContentImpl one = new ContentImpl(10);
System.out.print(one.text());
}
}
// Output:
// 10
- 实现接口的子类不明确声明泛型类型
public class ContentImpl<T> implements Content<T> {
private T text;
public ContentImpl(T text) {
this.text = text;
}
@Override
public T text() { return text; }
public static void main(String[] args) {
ContentImpl<String> two = new ContentImpl<>("ABC");
System.out.print(two.text());
}
}
// Output:
// ABC
泛型方法
泛型方法是引入其自己的类型参数的方法。泛型方法可以是普通方法、静态方法以及构造方法。
泛型方法语法形式如下:
public <T> T func(T obj) {}
是否拥有泛型方法,与其所在的类是否是泛型没有关系。
泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际类型参数的占位符。
使用泛型方法的时候,通常不必指明类型参数,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行推断。编译器会认为:调用泛型方法后,其返回值被赋给一个 Object 类型的变量。
public class GenericsMethod {
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 GenericVarargsMethod {
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]
类型擦除
事实上,Java的运行大致可以分为两个阶段,编译阶段,运行阶段
那么对于Java泛型来说,当编译阶段过后,泛型 T 是已经被擦除了,所以在运行阶段,它已经丢失了 T 的具体信息,而我们去实例化一个对象的时候,比如T c = new T();,它的发生时机是在运行阶段,而在运行阶段,你要new T(),就需要知道 T 的具体类型,实际上这时候 T是被替换成Integer了,而JVM是不知道T的类型的,所以是没有办法实例化的。
那么,类型擦除做了什么呢?它做了以下工作:
- 把泛型中的所有类型参数替换为 Object,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法。
- 擦除出现的类型声明,即去掉
<>的内容。比如T get()方法声明就变成了Object get();List<String>就变成了List。如有必要,插入类型转换以保持类型安全。 - 生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类;因此,泛型不会产生运行时开销。
让我们来看一个示例:
import java.util.*;
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
/* Output:
true
*/
ArrayList<String> 和 ArrayList<Integer> 应该是不同的类型。不同的类型会有不同的行为。例如,如果尝试向 ArrayList<String> 中放入一个 Integer,所得到的行为(失败)和 向 ArrayList<Integer> 中放入一个 Integer 所得到的行为(成功)完全不同。但是结果输出的是true,这意味着使用泛型时,任何具体的类型信息都被擦除了,ArrayList<Object> 和 ArrayList<Integer> 在运行时,JVM 将它们视为同一类型class java.util.ArrayList
再用一个例子来对于该谜题的补充:
import java.util.*;
class Frob {}
class Fnorkle {}
class Quark<Q> {}
class Particle<POSITION, MOMENTUM> {}
public class LostInformation {
public static void main(String[] args) {
List<Frob> list = new ArrayList<>();
Map<Frob, Fnorkle> map = new HashMap<>();
Quark<Fnorkle> quark = new Quark<>();
Particle<Long, Double> p = new Particle<>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
}
}
/* Output:
[E]
[K,V]
[Q]
[POSITION,MOMENTUM]
*/
根据 JDK 文档,Class.getTypeParameters() “返回一个 TypeVariable 对象数组,表示泛型声明中声明的类型参数...” 这暗示你可以发现这些参数类型。但是正如上例中输出所示,你只能看到用作参数占位符的标识符,这并非有用的信息。
残酷的现实是:在泛型代码内部,无法获取任何有关泛型参数类型的信息。
以上两个例子皆出《Java 编程思想》第五版 —— On Java 8中的例子,本文借助该例子,试图讲清楚Java泛型是使用类型擦除这里机制实现的,能力不足,有错误的地方,还请指正。关于On Java 8一书,已在github上开源,并有热心的伙伴将之翻译成中文,现在给出阅读地址,On Java 8
擦除的问题
擦除的代价是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、instanceof 操作和 new 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。
考虑如下的代码段:
class Foo<T> {
T var;
}
看上去当你创建一个 Foo 实例时:
Foo<Cat> f = new Foo<>();
class Foo 中的代码应该知道现在工作于 Cat 之上。泛型语法也在强烈暗示整个类中所有 T 出现的地方都被替换,就像在 C++ 中一样。但是事实并非如此,当你在编写这个类的代码时,必须提醒自己:“不,这只是一个 Object“。
继承问题
泛型时基于类型擦除实现的,所以,泛型类型无法向上转型。
向上转型是指用子类实例去初始化父类,这是面向对象中多态的重要表现。
Integer 继承了 Object;ArrayList 继承了 List;但是 List<Interger> 却并非继承了 List<Object>。
这是因为,泛型类并没有自己独有的 Class 类对象。比如:并不存在 List<Object>.class 或是 List<Interger>.class,Java 编译器会将二者都视为 List.class。
如何解决上面所产生的问题:
其实并不一定要通过new的方式去实例化,我们可以通过显式的传入源类,一个Class<T> clazz的对象来补偿擦除,例如instanceof 操作,在程序中尝试使用 instanceof 将会失败。类型标签可以使用动态 isInstance() ,这样改进代码:
public class Improve<T> {
//错误方法
public boolean f(Object arg) {
// error: illegal generic type for instanceof
if (arg instanceof T) {
return true;
}
return false;
}
//改进方法
Class<T> clazz;
public Improve(Class<T> clazz) {
this.clazz = clazz;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
}
实例化:
试图在 new T() 是行不通的,部分原因是由于擦除,部分原因是编译器无法验证 T 是否具有默认(无参)构造函数。
Java 中的解决方案是传入一个工厂对象,并使用该对象创建新实例。方便的工厂对象只是 Class 对象,因此,如果使用类型标记,则可以使用 newInstance() 创建该类型的新对象:
class Improve<T> {
Class<T> kind;
Improve(Class<T> kind) {
this.kind = kind;
}
public T get(){
try {
return kind.newInstance();
} catch (InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
class Employee {
@Override
public String toString() {
return "Employee";
}
}
public class InstantiateGenericType {
public static void main(String[] args) {
Improve<Employee> fe = new Improve<>(Employee.class);
System.out.println(fe.get());
}
}
/* Output:
Employee
*/
通过这样改进代码,可以实现创建对象的实例,但是要注意的是,newInstance();方法调用无参构造函数的,如果传入的类型,没有无参构造的话,是会抛出InstantiationException异常的。
泛型数组:
泛型数组这部分,我们在上面说到可以通过创建一个类型擦除的数组,然后转型的方式来创建泛型数组,这次我们可以通过显式的传入源类的方式来编写StackT类,解决创建泛型数组的问题,代码如下:
public class StackT<T> {
private int maxSize;
private T[] items;
private int top;
public StackT(int maxSize, Class<T> clazz){
this.maxSize = maxSize;
this.items = this.createArray(clazz);
this.top = -1;
}
public boolean isFull(){
return this.top == this.maxSize-1;
}
public boolean isNull(){
return this.top <= -1;
}
public boolean push(T value){
if(this.isFull()){
return false;
}
this.items[++this.top] = value;
return true;
}
public T pop(){
if(this.isNull()){
throw new RuntimeException("当前栈中无数据");
}
T value = this.items[top];
--top;
return value;
}
private T[] createArray(Class<T> clazz){
T[] array =(T[])Array.newInstance(clazz, this.maxSize);
return array;
}
}
边界
有时您可能希望限制可在参数化类型中用作类型参数的类型。类型边界可以对泛型的类型参数设置限制条件。例如,对数字进行操作的方法可能只想接受 Number 或其子类的实例。
要声明有界类型参数,请列出类型参数的名称,然后是 extends 关键字,后跟其限制类或接口。
类型边界的语法形式如下:
<T extends XXX>
示例:
public class GenericsExtendsDemo01 {
static <T extends Comparable<T>> T max(T x, T y, T z) {
T max = x; // 假设x是初始最大值
if (y.compareTo(max) > 0) {
max = y; //y 更大
}
if (z.compareTo(max) > 0) {
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
public static void main(String[] args) {
System.out.println(max(3, 4, 5));
System.out.println(max(6.6, 8.8, 7.7));
System.out.println(max("pear", "apple", "orange"));
}
}
// Output:
// 5
// 8.8
// pear
示例说明:
上面的示例声明了一个泛型方法,类型参数
T extends Comparable<T>表明传入方法中的类型必须实现了 Comparable 接口。
类型边界可以设置多个,语法形式如下:
<T extends B1 & B2 & B3>
注意:extends 关键字后面的第一个类型参数可以是类或接口,其他类型参数只能是接口。
通配符
通配符是Java泛型中的一个非常重要的知识点。很多时候,我们其实不是很理解通配符?和泛型类型T区别,容易混淆在一起,其实还是很好理解的,?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种 :
// 可以
T t = operate();
// 不可以
? car = operate();
但是这个并不是我们混淆的原因,虽然?和 T 都表示不确定的类型,T 通常用于泛型类和泛型方法的定义,?通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。用代码解释一下,回到文章最初说的栈类StackT,我们以这个为基础来解释,上面的观点:
public class Why {
public static void main(String[] args) {
StackT<Integer> stackT = new StackT<>(3, Integer.class);
stackT.push(8);
StackT<String> stackT1 = new StackT<>(3, String.class);
stackT1.push("7");
test(stackT1);
}
public static void test(StackT stackT){
System.out.println(stackT.pop());
}
}
// Output: 8
以我们编写的StackT类,进行测试,编写一个test方法,传入参数类型StackT,上面的程序正常输出字符串"7" ,这没有什么问题,问题在这里失去了泛型的限定,传进去的实参StackT1,是被我们限定为StackT<String> ,但是我们通过编译器可以看到stackT.pop()出来的对象,并没有String类型的特有方法,也就是说,它其实是Object类
那么我们就需要修改test方法的形参,改为:
public static void test(StackT<String> stackT){
System.out.println(stackT.pop());
}
这样子就回到了我们问题的本质来了,将形参修改为StackT<String>,这起到了泛型的限定作用,但是会出现这样的问题,如果我们需要向该方法传入StackT<Integer>类型的对象 stackT是,因为方法形参限定了StackT<String>,,这时候就报错了
这个时候就是通配符?起作用了,将方法形参改为StackT<?>就可以了,这也就确定了我们刚刚的结论,?通配符通常是用于泛型传参,而不是泛型类的定义。
public static void test(StackT<?> stackT){
System.out.println(stackT.pop());
}
但是这种用法我们通常也不会去用,因为它还是失去了类型的特点,即当无界泛型通配符作为形参时,作为调用方,并不限定传递的实际参数类型。但是,在方法内部,泛型类的参数和返回值为泛型的方法,不能使用!
这里,StackT.push就不能用了,因为我并不知道?传的是Integer还是String ,还是其他类型,所以是会报错的。
但是我们有时候是有这样的需求的,我们在接收泛型栈StackT作为形参的时候,我想表达一种约束的关系,但是又不像StackT<String>一样,约束的比较死板,而Java是面向对象的语言,那么就会有继承的机制,我想要的约束关系是我能接收的泛型栈的类型都是Number类的派生类,即不会像?无界通配符一样失去类的特征,又不会像StackT<String>约束的很死,这就引出了上界通配符的概念。
上界通配符
可以使用上界通配符来缩小类型参数的类型范围。
它的语法形式为:<? extends Number>
public class Why {
public static void main(String[] args) {
StackT<Integer> stackT = new StackT<>(3, Integer.class);
stackT.push(8);
StackT<String> stackT1 = new StackT<>(3, String.class);
stackT1.push("7");
StackT<Double> stackT2 = new StackT<>(3, Double.class);
//通过
test(stackT);
test(stackT2);
//error
test(stackT1);
}
public static void test(StackT<? extends Number> stackT){
System.out.println(stackT.pop());
}
}
这样就实现了一类类型的限定,但是需求变更了,我现在希望的约束关系是我能接收的泛型栈的类型都是Number类的父类,或者父类的父类,那么有上界,自然就有下界
下界通配符
下界通配符将未知类型限制为该类型的特定类型或超类类型。
注意:上界通配符和下界通配符不能同时使用。
它的语法形式为:<? super Number>
public class Why {
public static void main(String[] args) {
StackT<Number> stackT1 = new StackT<>(3, Number.class);
stackT1.push(8);
StackT<Double> stackT2 = new StackT<>(3, Double.class);
StackT<Object> stackT3 = new StackT<>(3, Object.class);
//通过
test(stackT1);
test(stackT3);
//error
test(stackT2);
}
public static void test(StackT<? super Number> stackT){
System.out.println(stackT.pop());
}
}
这样子的话,就确保了我们的test方法只接收Number类型以上的方法。泛型的各种高级语法可能在写业务代码的时候可以规避,但是如果你要去写一些框架的时候,由于你不知道框架的使用者的使用场景,那么掌握泛型的高级语法就很有用了。
通配符和向上转型
前面,我们提到:泛型不能向上转型。但是,我们可以通过使用通配符来向上转型。
public class GenericsWildcardDemo {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List<Number> numList = intList; // Error
List<? extends Integer> intList2 = new ArrayList<>();
List<? extends Number> numList2 = intList2; // OK
}
}
通配符边界问题,关于一些更加深入的解惑可以参考整理的转载的文章——Java泛型解惑之上下通配符
泛型约束
Pair<int, char> p = new Pair<>(8, 'a'); // 编译错误
public static <E> void append(List<E> list) {
E elem = new E(); // 编译错误
list.add(elem);
}
public class MobileDevice<T> {
private static T os; // error
// ...
}
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // 编译错误
// ...
}
}
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li; // 编译错误
List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译错误
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // 编译错误
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // 编译错误
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { } // 编译错误
}
实践总结
泛型命名
泛型一些约定俗成的命名:
- E - Element
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. - 2nd, 3rd, 4th types
使用泛型的建议
- 消除类型检查告警
- List 优先于数组
- 优先考虑使用泛型来提高代码通用性
- 优先考虑泛型方法来限定泛型的范围
- 利用有限制通配符来提升 API 的灵活性
- 优先考虑类型安全的异构容器
参考资料:
Java泛型解惑之 extends T>和 super T>上下界限
7月的直播课——Java 高级语法—泛型
从一知半解到揭晓Java高级语法—泛型的更多相关文章
- Java高级语法之反射
Java高级语法之反射 什么是反射 java.lang包提供java语言程序设计的基础类,在lang包下存在一个子包:reflect,与反射相关的APIs均在此处: 官方对reflect包的介绍如下: ...
- JAVA高级语法
高级语法 第三章:面向对象和高级语法 实例化: 不实例化,就是一个空指针 注意,声明和实例化是两个过程.声明的过程是不分配内存空间的,只有实例化才会真正分配空间 对变量的分类 实例变量只有实例化之后才 ...
- 黑马程序员:Java基础总结----泛型(高级)
黑马程序员:Java基础总结 泛型(高级) ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 泛型(高级) 泛型是提供给javac编译器使用的,可以限定集合中的输入类型 ...
- JAVA 高级特性枚举和泛型
枚举: 语法: public enum 枚举名 { 枚举值表(罗列所有值) } 例如: public enum EnumTest{MON,TUE,WED.THU,FRI,SAT,SUN} 枚举操 ...
- Java基础语法<十二> 泛型程序设计
1 意义 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用. 常见应用 : ArrayList 2 K T V E ? object等的含义 类型变量使用大写形式 E – Element ( ...
- [Java面经]干货整理, Java面试题(覆盖Java基础,Java高级,JavaEE,数据库,设计模式等)
如若转载请注明出处: http://www.cnblogs.com/wang-meng/p/5898837.html 谢谢.上一篇发了一个找工作的面经, 找工作不宜, 希望这一篇的内容能够帮助到大 ...
- Java高级之类结构的认识
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Java体系包括,各种版本的虚拟机,可执行文件,各种第三方类库,Java API类库,Java语言 ...
- 转载:[Java面经]干货整理, Java面试题(覆盖Java基础,Java高级,JavaEE,数据库,设计模式等)
原文:http://www.cnblogs.com/wang-meng/p/5898837.html 一:继承.抽象类与接口区别.访问控制(private, public, protected,默认) ...
- Java系列之泛型
自从 JDK 1.5 提供了泛型概念,泛型使得开发者可以定义较为安全的类型,不至于强制类型转化时出现类型转化异常,在没有反省之前,可以通过 Object 来完成不同类型数据之间的操作,但是强制类型转换 ...
随机推荐
- 文件上传Upload 漏洞挖掘思路
1:尽可能多的找出网站存在的上传点2:尝试使用如上各种绕过方法3:尝试 geshell4:无法上传webshel的情况下: 尝试上传html等,或可造成存储XSS漏洞 上传点构造XSS等,结合上传后的 ...
- C++中简单程序出现Segmentation fault (core dumped)段错误
段错误就是指访问的内存超出了系统所给这个程序的内存空间.一般是随意使用野指针或者数组.数组越界. ------两种简单解决方法:1.利用GDB调试,定位出错位置.(具体可查找博客详细学习)2.在可能出 ...
- JVM垃圾回收(GC)
JVM垃圾回收(GC) 1. 判断对象是否可以被回收 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收.此方法简单,但无法解决对象相互循环引用的问 ...
- ASP.NET Core 奇技淫巧之接口代理转发
前言 先讲讲本文的开发背景吧.. 在如今前后端分离的大背景下,咱的客户又有要求啦~ 要前后端分离~ 然因为种种原因..没办法用用纯前端的框架(其实是学习成本高,又没钱请前端开发人员)... 所以最终决 ...
- Java爬取先知论坛文章
Java爬取先知论坛文章 0x00 前言 上篇文章写了部分爬虫代码,这里给出一个完整的爬取先知论坛文章代码. 0x01 代码实现 pom.xml加入依赖: <dependencies> & ...
- 关于vector的自我补充
insert()函数,选择性插入. insert(v.begin()+i,x)意思就是把x插入vector数组v的第i位置上(也是迭代器的位置上),其后面的数字都会自动后移.注意i是从0开始的! er ...
- POJ2806 Square
题目描述 给定\(2*1\)和\(2 * 2\)两种规格的地砖,请问\(2 * n\)的地面总共有多少种方法? 下面是铺满\(2*17\)的地面的示意图. 输入输出格式 输入 多组数据,每组数据包括1 ...
- 基于Prometheus和Grafana打造业务监控看板
前言 业务监控对许许多多的场景都是十分有意义,业务监控看板可以让我们比较直观的看到当前业务的实时情况,然后运营人员可以根据这些情况及时对业务进行调整操作,避免业务出现大问题. 老黄曾经遇到过一次比较尴 ...
- Gulp的安装及用法
1.安装淘宝镜像 npm install cnpm -g --registry=https://registry.npm.taobao.org cnpm -v 2.生成项目描述文件 package.j ...
- DataGrid添加进度条列
DataGridColumn类型的继承树 DataGridColumn的派生类: 一般情况下DataGridBoundColumn和DataGridComboBoxColumn足以满足多数列的样式,如 ...