什么是泛型
《Java编程思想》对 泛型的介绍:一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。
因此,泛型:就是适用于多种类型。从代码上讲,就是对类型实现了参数化。
语法
class 泛型类名称 < 类型形参列表 > {// 这里可以使用类型参数}class ClassName < T1 , T2 , ..., Tn > {}
例如:我们要实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值
class MyArray2<T>{
public T[] array=(T[])new Object[10];
public T getValue(int pos){
return array[pos];
}
public void setValue(int pos, T val){
array[pos]=val;
}
}
public class Test2 {
public static void main(String[] args) {
//<>里面指定了类型,说明此时这个类里面只能放这个数据类型的数据
//并且<>中是引用类型
MyArray2<String> myArray2=new MyArray2<String>();
myArray2.setValue(0,"hello");
myArray2.setValue(1,"world");
System.out.println(myArray2.getValue(0));
System.out.println(myArray2.getValue(1));
MyArray2<Integer> myArray3=new MyArray2<Integer>();
myArray3.setValue(2,100);
System.out.println(myArray3.getValue(2));
}
}
1.<>当中指定类型后,编译器会根据你指定的类型参数来进行类型的检查
2.取元素时不用进行强转
T[]array=new T[10]//error是错误的
泛型的使用
语法
泛型类 < 类型实参 > 变量名 ; // 定义一个泛型类引用new 泛型类 < 类型实参 > ( 构造方法实参 ); // 实例化一个泛型类对象
例如上面代码:MyArray2<String> myArray2=new MyArray2<Stirng>();//后面<>中的类型可写可不写,编译器可以根据上下文推导出类型实参。
泛型如何编译
擦除机制
我们通过字节码发现在编译时,所有的T都变成了Object.
所以擦除机制就是在 编译的时候 把泛型T 擦除成了Object.运行期间没有这个泛型这个概念。
思考:既然T都被擦除成了Object了,为什么不能实例化泛型类型的数组?
T[]array=new T[10]//等价于Object[]array=new Object[10]; 错误
class MyArray2<T>{
public T[] array=(T[])new Object[10];
public T getValue(int pos){
return array[pos];
}
public void setValue(int pos, T val){
array[pos]=val;
}
public T[] getArray(){
return array;
}
}
public class Test2 {
public static void main(String[] args) {
MyArray2 myArray2=new MyArray2();
myArray2.setValue(0,10);
myArray2.setValue(1,"hello");
Integer[] integers= (Integer[]) myArray2.getArray();
System.out.println(Arrays.toString(integers));
}
}
因为返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候,直接转给Intefer类型的数组,编译器认为是不安全的。
正确的创建方法(了解即可)
class MyArray3<T>{
T[] array;
/**
* 通过放射创建,指定类型的数组
* @param clazz
* @param capacity
*/
public MyArray3(Class<T>clazz,int capacity){
array=(T[]) Array.newInstance(clazz,capacity);
}
public T getPos(int pos){
return array[pos];
}
public void setArray(int pos,T val){
array[pos]=val;
}
public T[] getArray(){
return array;
}
}
public class Test3 {
public static void main(String[] args) {
MyArray3<Integer> myArray3=new MyArray3<>(Integer.class,10);
myArray3.setArray(0,10);
myArray3.setArray(1,15);
Integer[] integers=myArray3.getArray();
System.out.println(Arrays.toString(integers));
}
}
泛型的边界
有上边界,无下边界。
语法
class 泛型类名称 < 类型形参 extends 类型边界 > {...}
例如:
public class MyArray < E extends Number > {...}
这里的extends不是继承的意思,表示边界是Number及其子类
应用:实现一个泛型类,找出数组最大值
因为泛型类T不是基本类型,不能简单的进行大于小于这样的比较,T是引用类型要使用比较器,也就是将T擦除成Comparable接口。
class Alg<T extends Comparable<T>>{//将T擦成Comparable接口
public T findMax(T[] array){
T max=array[0];
for (int i = 0; i < array.length; i++) {
if(max.compareTo(array[i])<0){
max=array[i];
}
}
return max;
}
}
泛型方法
1.成员方法:方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
class Alg{
public<T extends Comparable<T>> T findMax(T[] array){
T max=array[0];
for (int i = 0; i < array.length; i++) {
if(max.compareTo(array[i])<0){
max=array[i];
}
}
return max;
}
}
public class Test {
public static void main(String[] args) {
Alg alg=new Alg();
Integer[] array=new Integer[]{1,23,4,56,7,8};
System.out.println(alg.findMax(array));
}
}
56
2.静态方法,静态的泛型方法 需要在static后用<>声明泛型类型参数
class Alg2{
//静态方法不依赖于对象的调用
public static<T extends Comparable<T>> T findMax(T[] array){
T max=array[0];
for (int i = 0; i < array.length; i++) {
if(max.compareTo(array[i])<0){
max=array[i];
}
}
return max;
}
}
通配符
class Message<T>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test2 {
public static void func(Message<?> message){
//message.setMessage(100);//不能修改,因为不知道参数是什么类型的
System.out.println(message.getMessage());
}
public static void main(String[] args) {
Message<String> message=new Message<>();
String s="中国世界第一";
message.setMessage(s);
func(message);
Message<Integer> message2=new Message<>();
Integer a=10;
message2.setMessage(a);
func(message2);
}
}
通过使用通配符,既可以传String也可以传Integer,扩大了参数的范围
通配符的上界(常用于获取元素)
语法
<? extends 上界 ><? extends Number > // 可以传入的实参类型是 Number 或者 Number 的子类
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Banana extends Fruit{
}
class Alg2<T>{
private T val;
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
}
public class Test3 {
/**
* ?通配符extends上限是Fruit及其子类
* @param temp
*/
public static void func(Alg2<? extends Fruit> temp){
/*
Banana banana=temp.getVal();
Apple apple=temp.getVal();
不能进行修改,因为temp接收的是Fruit及其子类,无法确定参数是什么类型
*/
Fruit fruit= temp.getVal();//temp放的是Fruit及其子类,所以可以用Fruit进行接收
System.out.println(temp.getVal());
}
public static void main(String[] args) {
Alg2<Banana> alg2=new Alg2<>();
alg2.setVal(new Banana());
func(alg2);
Alg2<Apple> alg3=new Alg2<>();
alg3.setVal(new Apple());
func(alg3);
}
}
通配符的下限(常用于设置元素)
语法
<? super 下界 ><? super Integer > // 代表 可以传入的实参的类型是 Integer 或者 Integer 的父类类型
应用:
class Food{
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Alg4<T>{
private T val;
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
}
public class Test4 {
/**
* 通配符?下限super是Fruit及其父类
* @param temp
*/
public static void func(Alg4<? super Fruit> temp){
/**
* 因为最小的下限是Fruit,所以可以使用setVal进行修改
*/
temp.setVal(new Banana());//向上转型
temp.setVal(new Apple());//向上转型
/**
* temp可能是Fruit或者他的父类,无法用子类(Fruit)接收
*/
//Fruit fruit=temp.getVal();
System.out.println(temp.getVal());
}
public static void main(String[] args) {
Alg4<Fruit> alg4=new Alg4<>();
alg4.setVal(new Fruit());
Alg4<Food> alg41=new Alg4<>();
alg41.setVal(new Food());
}
}
(自动)装箱
public static void main(String[] args) {
int a=10;
Integer integer1=a;//自动装箱
Integer integer2=new Integer(a);//显示装箱
Integer integer3=Integer.valueOf(a);//显示装箱
}
通过字节码我们发现,装箱的底层是采用Integer.valueOf
(自动)拆箱
public static void main(String[] args) {
Integer integer=10;
int val=integer;//自动拆箱
System.out.println(val);
int val2=integer.intValue();//显示拆箱
double val3=integer.doubleValue();//显示拆箱
}
面试题:输出什么
public static void main(String[] args) {
Integer a=127;
Integer b=127;
System.out.println(a==b);
Integer c=128;
Integer d=128;
System.out.println(c==d);
}
答案:
true
false
why?
装箱的底层是valueOf,查看他的源码发现在【-127,128】范围内有一个数组,不在这个范围内需要new 对象,所以127在数组内相同,128需要new 对象,地址不同