- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
泛型是能够将一个类型转变为多个类型使用。例如一个单链表中不用泛型则我们实现的时候只能放入一种类型的数据,但是用泛型后能够将多种类型的数据都能放入。
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, …, Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, …, Tn> extends ParentClass<T 1> {
// 可以只使用部分类型参数
}
尖括号当中也可以定义多个类型实参。意义是能够指定多种类型的存储。
类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型
class Stack<T> {
public T[] objects;
public int top;
public Stack() {
this.objects = (T[])new Object[10];
}
public void push(T obj) {
objects[this.top++] = obj;
}
public T get() {
return objects[this.top-1];
}
}
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
示例:MyArrayList<String> list = new MyArrayList<String>();
不过后面尖括号当中的类型实参可以省略,但是必须要加上;省略里面的类型实参编译器会自动推导。如:MyArrayList<String> list = new MyArrayList<>(); // 可以推导出实例化需要的类型实参为 String
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型。如:MyArrayList list = new MyArrayList();
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
语法:
class 泛型类名称<类型形参 extends 类型边界> { ...
}
示例:
public class MyArrayList<E extends Number> {
...
}
extends后面的称为泛型的上界,即传入的实参类型只能是泛型上界它的子类或者泛型上界它本身。
在上面示例的基础上,在创建泛型类的对象时,只能创建Number类的子类或者它本身。如:
MyArrayList<Integer> l1; // 正常,因为 Integer 是 Number 的子类型 MyArrayList<String> l2; // 编译错误,因为 String 不是 Number 的子类型
了解: 没有指定类型边界 E,可以视为 E extends Object 。
泛型是没有下界的,只有上界;而通配符是存在上界和下界的,下面会讲到。
**泛型是在编译时期的一种机制–擦除机制。编译的时候,会因为擦除机制泛型参数全部被擦除为Object类型,因此它能够将泛型里面的引用类型参数化了。**因此,我们在一个泛型类当中定义一个泛型数组时,创建的泛型数组实例化要用Object[]的数组强制转换为泛型类型。如:T[] objects = (T[])new Object[10];
。
注:我们不能直接new 泛型类型的数组,如T[] t = new T[];
,因为泛型是先检查后编译的,检查的时候不知这个T是哪个类型,而编译的时候直接将其擦除为Object。因此有T[] t = (T[])new Object[]
。
泛型的<>里面的内容,不构成类型的组成。
如:
public class Test {
public static void main(String[] args) {
Algorithm<Integer> algorithm = new Algorithm<>();
System.out.println(algorithm);
}
}
打印结果:Algorithm后面<>的参数类型不见了,可见它不构成类型的组成
如:定义一个泛型类搜索树。
public class BSTree<K extends Comparable<K>> {
...
}
传入的K必须是实现了Comparable接口的引用类型,并且Comparable里面的K是比较的类型。
它的意思是:将泛型传入的参数擦除到实现Comparable接口的类型当中。又因为Object类中没有实现Comparable接口,因此我们要实现引用的比较时要定义上界的类有实现Comparable接口。
泛型的意义:
例如:
Stack当中的objects数组的类型为Object[],里面能够放入所有类型的数据,若取出某一个数据时,需要强制类型转换。
class Stack {
public Object[] objects;
public int top;
public Stack() {
this.objects = new Object[10];
}
public void push(Object obj) {
objects[this.top++] = obj;
}
public Object get() {
return objects[this.top-1];
}
}
public class TestDemo {
public static void main(String[] args) {
Stack stack = new Stack();
stack.push(1);
stack.push(2);
stack.push("zjr");
String str = (String)stack.get();
}
而泛型能够完美地解决这一问题。它能够指定一个数组当中放入的是什么类型(可以指定一种类型,也可以指定多种类型)的数据,因此它能够自动帮我们做类型的检查。当我们取出某个数据时,如上面例子中的String str = (String)stack.get();
,在泛型下不需要强制类型转换,直接String str =stack.get();
即可。
?
用于在泛型的使用,即为通配符。
?
与T
有相同的地方也有不同的地方。
相同:都能够指定任意类型的数据。
不同:?
能够自己定义上界与下界,而T
只能定义上界。
public class MyArrayList<E> {...}
// 可以传入任意类型的 MyArrayList
public static void printAll(MyArrayList<?> list) {
...
}
// 以下调用都是正确的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Double>());
printAll(new MyArrayList<Number>());
printAll(new MyArrayList<Object>());
当ArrayList<T>
,则遍历的时候每个元素都先默认为T类型,并且在方法中的static后加上< T >去识别T是泛型。
当ArrayList<?>
,每个元素的类型不是?类型,因此不能直接把T改成?。因为编译期有擦除机制将类型擦为Object类型,因此可以先将每个元素当成Object类型,并且Object类型是所有类型的父类。
class Test {
public static<T> void print(ArrayList<T> list) {
for (T t : list) {
System.out.println(t);
}
}
//代表通配符 擦除机制 Object
public static void print2(ArrayList<?> list) {
for (Object t : list) {
System.out.println(t);
}
}
}
public class TestDemo2 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Test.print(list);
System.out.println("===============");
Test.print2(list);
}
语法:<? extends 上界>
。
它跟?
一样,传入的类型只能是上界的子类或者上界它本身。
// 可以传入类型实参是 Number 子类的任意类型的 MyArrayList
public static void printAll(MyArrayList<? extends Number> list) {
...
}
// 以下调用都是正确的
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Double>());
printAll(new MyArrayList<Number>());
// 以下调用是编译错误的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Object>());
语法:<? super 下界>
。
传入的类型只能是下界的父类或者下界它本身。
例子:
// 可以传入类型实参是 Integer 父类的任意类型的
MyArrayList public static void printAll(MyArrayList<? super Integer> list){
...
}
// 以下调用都是正确的
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Number>());
printAll(new MyArrayList<Object>());
// 以下调用是编译错误的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Double>());
public class MyArrayList<E> { ... }
// MyArrayList<Object> 不是 MyArrayList<Number> 的父类型
// MyArrayList<Number> 也不是 MyArrayList<Integer> 的父类型
// 需要使用通配符来确定父子类型
// MyArrayList<?> 是 MyArrayList<? extends Number> 的父类型
// MyArrayList<? extends Number> 是 MyArrayList<Integer> 的父类型
首先我们在泛型方法中传入ArrayList<T>
,调用ArrayList中的add方法。发现add方法中能够放入的是我们指定的类型。
而在泛型方法中传入ArrayList<?> list
,调用ArrayList中的add方法,发现放入的不知道是什么类型。
所以通配符一般存放的地方大多是在源码当中。
若都调用get方法,则获取的都是整型数据。
因此在ArrayList< T >下适合写东西,在ArrayList<?>下适合从某个东西里面读。
例子:当我们要求一个数组当中的最大的数。
(1) 用泛型类实现:在类的后面extends Comparable接口说明上界是实现Comparable接口的类型。在编译的擦除机制中将T类型擦除为实现Comparable接口的类型。
因为在泛型中的参数是引用类型,不能直接比较max<array[i]
,要用到Comparable比较器。
如:
class Algorithm<T extends Comparable<T>> {
public T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
//max < array[i]
if(max.compareTo(array[i]) < 0) {
max = array[i];
}
}
return max;
}
}
(2)用泛型方法实现
普通类当中实现泛型方法,在返回值T前加上<T extends Comparable<T>>
,说明T在擦除机制处理后擦除为实现Comparable接口的类型,因此m有max.compareTo(array[i])
。
class Algorithm2 {
//泛型方法
public static<T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(max.compareTo(array[i]) < 0) {
max = array[i];
}
}
return max;
}
}
泛型方法会根据形参的类型推导出整个泛型的类型参数,因此跟普通方法的调用方式是一致的。
class Algorithm2 {
//泛型方法
public static<T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(max.compareTo(array[i]) < 0) {
max = array[i];
}
}
return max;
}
}
public static void main(String[] args) {
Integer[] integers = {1,2,13,4,5};
//会根据 形参的类型 推导出 整个泛型的类型参数
Integer ret = Algorithm2.findMax(integers);
System.out.println(ret);
//一般这个就省略了
Integer ret2 = Algorithm2.<Integer>findMax(integers);
System.out.println(ret2);
}
特点:
1.在实例内部类中不能定义静态的成员变量。原因:静态的成员变量不依赖于对象,而实例内部类是依赖于对象去获得的。如果非要定义,可以定义为static final常量类型,因此在编译期间能确定的值就可以。
2.实例内部类的实例化,需要运用到外部类的对象。左边拿类型,右边拿对象。
class Test {
public static void main1(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
OuterClass.InnerClass innerClass1 = new OuterClass().new InnerClass();
}
}
3.若要调用实例内部类中的方法,则直接用InnerClass类型的名字调用实例类中方法的名字即可。
class OuterClass {
public void funcInner() {
System.out.println("OuterClass::func()");
}
/**
* 实例内部类:
* 可以把实例内部类 当做就是一个实例成员
*/
class InnerClass {
public void funcInner() {
System.out.println("InnerClass::funcInner");
}
}
}
class Test {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.funcInner();
}
}
4.实例内部类中访问外部类不同名的成员变量可以直接访问,而同名的成员变量需要用到外部类对象。
class OuterClass {
public int data1 = 1;
private int data2 = 2;
public static int data3 = 3;
public void func() {
System.out.println("OuterClass::func()");
}
class InnerClass {
public int data1 = 1000;
public int data4 = 4;
private int data5 = 5;
//public static int data6 = 6;
public static final int data6 = 6;
public void funcInner() {
System.out.println("InnerClass::funcInner");
System.out.println(this.data1);//1000
System.out.println(data2);//2
System.out.println(data3);//3
System.out.println(OuterClass.this.data1);//1
}
}
}
class Test {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.funcInner();
}
}
可见,this关键字可以看作OuterClass中的一个静态变量,否则无法用类名去调用this。可以这样去理解。
5.内部类的字节码文件表现形式:
一个类对应一个字节码文件,但是内部类的字节码文件是由:外部类$内部类.class组成。
特点:
1.静态内部类里面可以定义静态属性,并且什么类型的属性都可以定义。
2.静态内部类访问属性是依赖于类的,而外部类是访问属性要依赖于对象。因此在静态内部类中是无法访问外部类的属性的。若非要访问,则可以:
class OuterClass {
public int data1 = 1;
public int data2 = 2;
public static int data3 = 3;
static class InnerClass {
public OuterClass out;//内部类当中设置外部类的对象
public InnerClass(OuterClass out) {
this.out = out;
}
public void func1() {
System.out.println(data1);//编译无法通过
System.out.println(out.data1);//1
}
}
}
class Test {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = new OuterClass.InnerClass(outerClass);
innerClass.func1();
}
}
注意:InnerClass里面的构造方法就是为了实例化public OuterClass out; 因此在main方法当中要传入一个已经实例化的outerClass引用。
1.**匿名内部类的使用是在实例化new 对象时后面直接加花括号。**花括号当中可以重写实例化对象的类当中的方法。该方法可以自由使用。若要调用匿名内部类中重写的方法,则直接在匿名内部类花括号的后面调用即可。
class OuterClass {
public void func() {
System.out.println("OuterClass::func()");
}
}
class Test {
public static void main(String[] args) {
new OuterClass(){
@Override
public void func() {
System.out.println("haha");
}
}.func();
}
}
//打印结果:haha
2.匿名内部类可以在接口中使用。如:
class Test {
interface A {
public void func();
}
A a = new A(){
@Override
public void func() {
System.out.println("当前是个匿名内部类,实现了A接口,重写了接口的方法");
}
};
}
有一个匿名内部类在接口中使用,说明定义了一个类是实现了该接口的,并且重写接口中的方法。
3.**在匿名内部类当中,访问的数据一定是在运行的过程中没有发生改变的量,这样的现象称为变量捕获。**如:
class OuterClass {
public void func() {
System.out.println(111);
}
}
class Test {
public static void main(String[] args) {
int local = 199;
//匿名子类
new OuterClass(){
@Override
public void func() {
local = 777;//编译出错
System.out.println("我是重写的func()");
System.out.println(local);
}
}.func();
}
}
之前,我们都是这样定义的:以树为例,结点的类与树的类是分开写的。
class Node {
public int data;
public Node left;
public Node right;
}
class Tree {
}
但是学过内部类后,我们可以将结点的类定义到树当中,说明树是由一个个结点组成的。可以将结点的类定义成一个内部类。
class Tree {
public static class Node {
public int data;
public Node left;
public Node right;
}
}
这是我的测试用例。 http://tobeythorn.com/isi/dummy2.svg http://tobeythorn.com/isi/isitest.html 如果我自己打开 svg,内部
这是我的测试用例。 http://tobeythorn.com/isi/dummy2.svg http://tobeythorn.com/isi/isitest.html 如果我自己打开 svg,内部
我正在尝试做类似的事情: SELECT SUM( CASE WHEN ( AND EXISTS(SELECT 1
我想问如何在外部 ng-repeat 内部正确使用内部 ng-repeat: 这意味着你想使用这样的东西: {{milestone.id}} {{
我希望在 wordpress 的仪表板内编辑 css 样式并且如果可能的话不必编辑 php 文件。 我知道至少可以编辑一些属性,所以我希望我可以直接在仪表板中编辑所有属性。 更具体地说如何更改自定义类
我在安装在 windows10 上的 vmware 中的 Ubuntu 上安装了伪分布式独立 hadoop 版本。 我从网上下载了一个文件,复制到ubuntu本地目录/lab/data 我在 ubun
我有一个如下所示的 WHERE 语句: WHERE ((@Value1 IS NULL AND [value1_id] IS NULL) OR [value1_id] = ISNULL(@Va
我有一个如下所示的 WHERE 语句: WHERE ((@Value1 IS NULL AND [value1_id] IS NULL) OR [value1_id] = ISNULL(@Va
在我的一些测试帮助程序代码中,我有一个名为 FakeDbSet(Of T) 的 IDbSet(Of T) 实现,它模拟了许多 EF 行为,但没有实际的数据库。我将类声明为 Friend ,因为我想强制
我正在寻找 Cassandra/CQL 的常见 SQL 习语 INSERT INTO ... SELECT ... FROM ... 的表亲。并且一直无法找到任何以编程方式或在 CQL 中执行此类操作
如何防止内部 while 循环无限运行?问题是,如果没有外部 while 循环,内部循环将毫无问题地运行。我知道它必须对外循环执行某些操作,但我无法弄清楚是什么导致了问题。 import java.u
我正在努力学习更多有关 C++ 的知识,但在国际象棋程序中遇到了一些代码,需要帮助才能理解。我有一个 union ,例如: union b_union { Bitboard b; st
这是我项目网页中的代码片段。这里我想显示用户选择的类别,然后想显示属于该类别的主题。在那里,用户可以拥有多个类别,这没有问题。我可以在第一个 while 循环中打印所有这些类别。问题是当我尝试打印主题
我想知道如何在 swing 中显示内部框架。这意味着,当需要 JFrame 时,通常我所做的是, new MyJFrame().setVisible(true); 假设之前的表单也应该显示。当显示这个
我最近发现了一些有趣的行为,这让我想知道对象如何知道存在哪些全局变量。例如,假设我有一个文件“test.py”: globalVar = 1 toDelete = 2 class Test(objec
我知道它已经在这里得到回答: google maps drag and drop objects into google maps from outside the Map ,但这并不完全是我所需要的
我目前正在学习Javascript DOM和innerHTML,发现在理解innerHTML方面存在一些问题。 这是我的代码:http://jsfiddle.net/hphchan/bfjx1w70/
我构建了一个布局如下的库: lib/ private_class_impl.cc private_class_decl.h public_class_impl.cc include/
我有一个使用 bootstrap 3 的组合 wordpress 网站。它基本上是一个图像网格。当屏幕展开时,它会从三列变为四列。移动时它是一列。 我想出了如何调整图像的顶部和底部边距,但我希望图像的
我正在试用 MSP-EXP430G2 的教程程序,使用 Code Composer Studio 使 LED 闪烁。最初,它有一个闪烁的无限循环: for(;;) // This emp
我是一名优秀的程序员,十分优秀!