1 泛型的引入
问题:我们之前实现过的顺序表,只能保存 int 类型的元素,如果现在需要保存 指向 Person 类型对象的引用的顺序表,请问应该如何解决?如果又需要保存指向 Book 对象类型的引用呢?
之前写的顺序表代码示例:
import java.util.Arrays;
public class MyArrayList {
private int[] elem;
private int usedSize;
private static int capacity = 10;
public MyArrayList() {
this.elem = new int[capacity];
}
public boolean isFull() {
if (this.usedSize == capacity) {
return true;
}
return false;
}
public void add(int pos, int data) {
if (pos < 0 || pos > this.usedSize) {
System.out.println("pos位置不合法");
return;
}
if (isFull()) {
this.elem = Arrays.copyOf(this.elem, 2 * capacity);
capacity *= 2;
}
for (int i = this.usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
public boolean isEmpty() {
if (this.usedSize == 0) {
return true;
}
return false;
}
public boolean contains(int toFind) {
if (isEmpty()) {
return false;
}
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return true;
}
}
return false;
}
public int search(int toFind) {
if (isEmpty()) {
return -1;
}
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
public int getPos(int pos) {
if (isEmpty()) {
throw new RuntimeException("顺序表为空!");
}
if (pos < 0 || pos >= this.usedSize) {
throw new RuntimeException("pos不合法");
}
return this.elem[pos];
}
public int size() {
return this.usedSize;
}
public void setPos(int pos, int value) {
if (pos < 0 || pos >= this.usedSize) {
System.out.println("pos位置不合法!");
return;
}
this.elem[pos] = value;
}
public void remove(int toRemove) {
if (isEmpty()) {
return;
}
int index = search(toRemove);
if (index == -1) {
System.out.println("没有你要删除的数字!");
}
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
public void clear() {
for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = 0;
}
this.usedSize = 0;
}
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0, 1);
myArrayList.add(1, 2);
myArrayList.add(2, 3);
myArrayList.add(3, 4);
System.out.println(myArrayList.size());
myArrayList.display();
System.out.println(myArrayList.contains(3));
System.out.println(myArrayList.contains(2));
System.out.println(myArrayList.search(5));
System.out.println(myArrayList.search(2));
System.out.println(myArrayList.getPos(0));
System.out.println(myArrayList.usedSize);
myArrayList.display();
myArrayList.remove(1);
myArrayList.remove(2);
myArrayList.display();
myArrayList.remove(4);
myArrayList.display();
myArrayList.clear();
System.out.println("==============");
myArrayList.display();
}
}
- 首先,我们在学习多态过程中已知一个前提,父类的引用可以指向子类的对象。
- 其次,我们也已知 Object 是 java 中所有类的祖先类
那么,要解决上述问题,我们很自然的想到一个解决办法,将我们的顺序表的元素类型定义成 Object 类型,这样我们的 Object 类型的引用可以指向 Person 类型的对象或者指向 Book 类型的对象了。
因为代码改动较多,现在指出主要代码:
这样,我们可以就可以很自由的存储指向任意类型对象的引用到我们的顺序表了。
改编后的代码:
package test1;
import java.util.Arrays;
public class MyArrayList {
private Object[] elem;
private int usedSize;
private static int capacity = 10;
public MyArrayList() {
this.elem = new Object[capacity];
}
public boolean isFull() {
if (this.usedSize == capacity) {
return true;
}
return false;
}
public void add(int pos, Object data) {
if (pos < 0 || pos > this.usedSize) {
System.out.println("pos位置不合法");
return;
}
if (isFull()) {
this.elem = Arrays.copyOf(this.elem, 2 * capacity);
capacity *= 2;
}
for (int i = this.usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
System.out.println();
}
System.out.println();
}
public boolean isEmpty() {
if (this.usedSize == 0) {
return true;
}
return false;
}
public boolean contains(Object toFind) {
if (isEmpty()) {
return false;
}
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return true;
}
}
return false;
}
public int search(Object toFind) {
if (isEmpty()) {
return -1;
}
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
public Object getPos(int pos) {
if (isEmpty()) {
throw new RuntimeException("顺序表为空!");
}
if (pos < 0 || pos >= this.usedSize) {
throw new RuntimeException("pos不合法");
}
return this.elem[pos];
}
public int size() {
return this.usedSize;
}
public void setPos(int pos, int value) {
if (pos < 0 || pos >= this.usedSize) {
System.out.println("pos位置不合法!");
return;
}
this.elem[pos] = value;
}
public void remove(int toRemove) {
if (isEmpty()) {
return;
}
int index = search(toRemove);
if (index == -1) {
System.out.println("没有你要删除的数字!");
}
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
public void clear() {
for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = 0;
}
this.usedSize = 0;
}
public static void main(String[] args) {
MyArrayList books = new MyArrayList();
for (int i = 0; i < 10; i++) {
books.add(i,new Book("三国演义", "罗贯中", 15));
}
books.display();
// MyArrayList myArrayList = new MyArrayList();
// myArrayList.add(0, 1);
// myArrayList.add(1, 2);
// myArrayList.add(2, 3);
// myArrayList.add(3, 4);
// System.out.println(myArrayList.size());
// myArrayList.display();
// System.out.println(myArrayList.contains(3));
// System.out.println(myArrayList.contains(2));
// System.out.println(myArrayList.search(5));
// System.out.println(myArrayList.search(2));
// System.out.println(myArrayList.getPos(0));
// System.out.println(myArrayList.usedSize);
// myArrayList.display();
// myArrayList.remove(1);
// myArrayList.remove(2);
// myArrayList.display();
// myArrayList.remove(4);
// myArrayList.display();
// myArrayList.clear();
// System.out.println("==============");
// myArrayList.display();
}
}
遗留问题:现在的 MyArrayList 虽然可以做到添加任意类型的引用到其中了,但遇到以下代码就会产生问题。
写一个Person类:
接下来我称之为牛马操作:
编译竟然正确,没有报红,我们运行一下看看:
运行时会抛出了异常
提示:问题暴露的越早,影响越小。编译期间的问题只会让开发者感觉到,运行期间的错误会让所有的软件使用者承受错误风险。
所以我们需要一种机制,可以 1. 增加编译期间的类型检查 2. 取消类型转换的使用 泛型就此诞生!
2.泛型的分类
- 1. 泛型类
- 2. 泛型方法
3 泛型类的定义的简单演示
注意: 泛型类可以一次有多个类型变量,用逗号分割。
4 泛型背后作用时期和背后的简单原理
- 1. 泛型是作用在编译期间的一种机制,即运行期间没有泛型的概念。
- 2. 泛型代码在运行期间,就是我们上面提到的,利用 Object 达到的效果(这里不是很准确,以后会做说明)。
5 泛型类的使用
通过以上代码,我们可以看到泛型类的一个使用方式:只需要在所有类型后边跟尖括号,并且尖括号内是真正的类型,即 E 可以看作的最后的类型。
看具体代码:
package test1;
import java.util.Arrays;
public class MyArrayList<E> {
private Object[] elem;
private int usedSize;
private static int capacity = 10;
private E e;
public MyArrayList() {
this.elem = new Object[capacity];
this.e = e;
}
public boolean isFull() {
if (this.usedSize == capacity) {
return true;
}
return false;
}
public void add(int pos, E data) {
if (pos < 0 || pos > this.usedSize) {
System.out.println("pos位置不合法");
return;
}
if (isFull()) {
this.elem = Arrays.copyOf(this.elem, 2 * capacity);
capacity *= 2;
}
for (int i = this.usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
System.out.println();
}
System.out.println();
}
public boolean isEmpty() {
if (this.usedSize == 0) {
return true;
}
return false;
}
public boolean contains(E toFind) {
if (isEmpty()) {
return false;
}
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return true;
}
}
return false;
}
public int search(E toFind) {
if (isEmpty()) {
return -1;
}
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
public Object getPos(int pos) {
if (isEmpty()) {
throw new RuntimeException("顺序表为空!");
}
if (pos < 0 || pos >= this.usedSize) {
throw new RuntimeException("pos不合法");
}
return this.elem[pos];
}
public int size() {
return this.usedSize;
}
public void setPos(int pos, int value) {
if (pos < 0 || pos >= this.usedSize) {
System.out.println("pos位置不合法!");
return;
}
this.elem[pos] = value;
}
public void remove(E toRemove) {
if (isEmpty()) {
return;
}
int index = search(toRemove);
if (index == -1) {
System.out.println("没有你要删除的数字!");
}
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
public void clear() {
for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = 0;
}
this.usedSize = 0;
}
public static void main(String[] args) {
MyArrayList<Book> books = new MyArrayList<Book>();
books.add(0, new Book("红楼梦", "曹雪芹", 18));
books.add(2, new Person("lisi", "lll"));
// Book book1 = new Book("红楼梦", "曹雪芹", 18);
// for (int i = 0; i < 10; i++) {
// if (i == 0) {
// books.add(i, book1);
// } else {
// books.add(i, new Book("三国演义", "罗贯中", 15));
// }
// }
// books.display();
// Person person = (Person) books.getPos(0);
// MyArrayList myArrayList = new MyArrayList();
// myArrayList.add(0, 1);
// myArrayList.add(1, 2);
// myArrayList.add(2, 3);
// myArrayList.add(3, 4);
// System.out.println(myArrayList.size());
// myArrayList.display();
// System.out.println(myArrayList.contains(3));
// System.out.println(myArrayList.contains(2));
// System.out.println(myArrayList.search(5));
// System.out.println(myArrayList.search(2));
// System.out.println(myArrayList.getPos(0));
// System.out.println(myArrayList.usedSize);
// myArrayList.display();
// myArrayList.remove(1);
// myArrayList.remove(2);
// myArrayList.display();
// myArrayList.remove(4);
// myArrayList.display();
// myArrayList.clear();
// System.out.println("==============");
// myArrayList.display();
}
}
那么现在就会出现编译错误 ,这就是泛型的作用!
注意: Book 只能想象成 E 的类型,但实际上 E 的类型还是 Object。
6 泛型总结
- 1. 泛型是为了解决某些容器、算法等代码的通用性而引入,并且能在编译期间做类型检查。
- 2. 泛型利用的是 Object 是所有类的祖先类,并且父类的引用可以指向子类对象的特定而工作。
- 3. 泛型是一种编译期间的机制,即 MyArrayList<Person> 和 MyArrayList<Book> 在运行期间是一个类型。
- 4. 泛型是 java 中的一种合法语法,标志就是尖括号 <>