#1024程序员节 | 征文#
- 这里有很多小伙伴的就要问啦到底什么是泛型呢?
- 泛型到底是怎么实现的呢?
- 学会了泛型对于我们来说到底有什么作用呢?
接下来就让博主带领大家一起认识学习吧~~~
1:引出泛型:
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。
(而不是像Object类型一样什么都能接收,我们就是一个模版,需要具体哪一个类型就传哪一个类型,你指定了类型,后续你传类型传错了,编译器会报警告)
1.1:Object数组
实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法
返回数组中某个下标的值(get和set方法)
思路:
1. 我们以前学过的数组,只能存放指定类型的元素,例如:int[] array = new int[10]; String[] strs = new String[10];
2. 所有类的父类,默认为Object类。我们的数组就可以创建为Object类型的数组
class MyArray{
Object [] myArray = new Object[10];
public Object get(int pos){
return myArray[pos];
}
public void set (Object val,int pos){
myArray[pos] = val;
}
}
public class Test {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.set("hello",2);
myArray.set(10,1);
String s = (String) myArray.get(2);
int a = (int) myArray.get(1);
System.out.println(s);
}
这样一个类我们就建立好了,但是我们发现我们每次拿出来数据都要强制类型转化
所以我就在想我们构建了这个数组,能不能让大家都用(这个模版呢)呢?(大家用的时候都只放一种数据类型)
其实这就是泛型的思维(就像我构造一个方法,每次调用这个方法传递数据,我都能进行使用)
1.2:这里就引出泛型
class MyArray<T>{
Object [] array = new Object[10];
public T get(int pos){
return (T)array[pos];
}
public void set( T val,int pos){
array[pos] = val;
}
}
public class Test{
public static void main2(String[] args) {
MyArray<String> myArray = new MyArray<>();
myArray.set("hello",2);
//myArray.set(10,1);
MyArray<Integer> myArray1 = new MyArray<>();
myArray1.set(10,1);
myArray1.set(9,2);
Integer a = myArray1.get(1);
}
}
这里就可以看到指定类型之后,再放其他类型的数据就会报错了;
大家新建一个对象就好了,这就是一个泛型数组
注意:
代码解释:
1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类
了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型
2. 不能new泛型类型的数组
意味着:T[] ts = new T[5];//是不对的
T[] array = (T[])new Object[10];这样写其实也不好
3.我们一开始用的是裸类型(MyArray list = new MyArray();)
注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制
下面的类型擦除部分,我们也会讲到编译器是如何使用裸类型的。
小结:
1. 泛型是将数据类型参数化,进行传递
2. 使用 <T> 表示当前类是一个泛型类。
3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
都没问题?那我可要提问了!
3:泛型如何编译的?
3.1 擦除机制
那么,泛型到底是怎么编译的?这个问题,也是曾经的一个面试问题。泛型本质是一个非常难的语法,要理解好他还是需要一定的时间打磨。
我们可以看看代码的反汇编:
输入 javap -c MyArray
大家都可以看到编译过后,所有的T都变成了Object
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息
这里我再给大家提出两个问题
1、那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?
不是,一定不能new一个T(泛型)的数组,T是一个类型变量,不是一个具体的数据类型
而数组的建立是需要指定的类型的
2、类型擦除,一定是把T变成Object吗?
前提是没有指定边界,如果,T extends Comparable<T>接口的话,只能接收实现了Comparable接口的类型了
下面是关于泛型编程擦除机制的文章,大家可以看一下
3.2:所以说能不能返回一个泛型的数组呢?
class MyArray<T>{
Object [] array = new Object[10];
public T get(int pos){
return (T)array[pos];
}
public void set( T val,int pos){
array[pos] = val;
}
public T[] getArray() {
return (T[])array;
}
}
public class Test {
public static void main(String[] args) {
MyArray<String> myArray = new MyArray<>();
String []ret = myArray.getArray();
}
我们可以看到报了类型转换的错误(运行时T已经变成Object类型了,你用String接收,你能保证数组里面的类型都是String吗?其他和String不相关的类型,你就算是强转也没有用,别这样写,尽管你一开始就指定了T为String类)(这是编译器为了防止你放裸类型)
例如:String[ ]接收就会报错
还是报错,所以说我们只能用Object类接收
代码改成如下(其实你返回T[ ] 也可以(不合理但是合法)最好还是返回Object[ ]的数组)
4:接下来我们写一个泛型类用来比较大小
不过我们先要了解一下泛型的上界:
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
class 泛型类名称<类型形参 extends 类型边界> {
...
}
稍微举一个例子
public class MyArray<E extends Number> {
...
}
MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型
这里的extends指定的泛型的上界,传过来的T类型必须是实现了Comparable接口的类型
譬如Integer和String类型
public class Test1 {
public static void main(String[] args) {
Alg1<Integer> alg1 = new Alg1<>();
Alg1<String> alg2 = new Alg1<>();
}
}
class Alg1 <T extends Comparable<T>>{ //泛型的上界
public T compareMax(T[] array) {
T max = array[0];
for (int i = 0; i < array.length; i++) {
if (max.compareTo(array[i]) < 0) {
max = array[i];
}
/* if (max < array[i]) {
max = array[i];
}*/ //我们比较两个元素的时候,一般都要实现Comparable接口,譬如两个String类型不能直接比较大小
}
return max;
}
}
如果我们传递过来的类型没有实现Comparable接口,编译器就会报错
5:泛型方法实现
那我们不想每次都进行实例化对象,我们既可以实现一个泛型方法
在方法前面加上<T>代表你是一个泛型方法,要用Comparable接口就直接实现,泛型的上界
class Test1{
public static void main(String[] args) {
Alg alg = new Alg();
Integer[] integers ={
1,2,300,199,200};
int ret= Alg.compareMax(integers);
System.out.println(ret);
}
}
class Alg {
public static <T extends Comparable<T>> T compareMax(T[] array) {
T max = array[0];
for (int i = 0; i < array.length; i++) {
if (max.compareTo(array[i]) < 0) {
max = array[i];
}
/* if (max < array[i]) {
max = array[i];
}*/
}
return max;
}
}
小结:
1. 泛型是将数据类型参数化,进行传递
2. 使用 <T> 表示当前类是一个泛型类。
3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
最后给大家提出一个问题:
这样的写法哪里出错了?
class Alg1 <T extends Comparable<T>>{
public static T compareMax(T[] array) {
T max = array[0];
for (int i = 0; i < array.length; i++) {
if (max.compareTo(array[i]) < 0) {
max = array[i];
}
/* if (max < array[i]) {
max = array[i];
}*/ //我们比较两个元素的时候,一般都要实现Comparable接口,譬如两个String类型不能直接比较大小
}
return max;
}
}
写法哪里出错了?
解释:
compareMax是一个静态方法,我们调用它的时候根本不需要实例化对象,也就是说无法指定在类级别上的泛型类型参数是Integer或者其他(我们无法指定类上面的泛型参数,因为类上面的泛型参数需要我们实例化对象的时候传过去,我们现在传不了)所以
静态成员不依赖于类的实例,因此也不依赖于实例的泛型类型参数。
所以说:你想要静态方法使用泛型类型参数,但这些参数必须直接定义在方法上,而不是类上。(如上述的静态方法法)
public static <T extends Comparable<T>> T compareMax(T[] array) {}
上述就是 【Java数据结构】—泛型编程的全部内容啦,能看到这里相信您一定对小编的文章有了一定的认可,泛型的出现让我们的代码的可重复利用性变的更高了,我们代码的性能又提高啦~~~
有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正
您的支持就是我最大的动力!!!!