什么是泛型?
泛型就是参数化类型,它的作用就是把变量或者属性或者方法的参数或者方法的返回值等的类型看做是一个参数。这样就可以写出来通用的代码,让代码更加简洁。在使用的时候,传入真正的类型。这个过程类似于方法的形参和实参。
为什么要用泛型?
原因一:优化代码,让代码更简洁更通用。
例如:拿打印数组来说,可以定义一个工具类ArrayTool,ArrayTool提供打印数组的方法。
public class ArrayTool {
public void printArray(int[] arr) {
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public void printArray(Integer[] arr) {
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public void printArray(Double[] arr) {
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public void printArray(Person[] arr) {
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
上述代码里,ArrayTool提供了若干个打印数组的方法(使用的技术是方法重载)。但是仔细观察,你会发现这些方法除了参数的类型不同之外,没有什么区别(核心逻辑是一模一样的)。
如果这些方法的参数类型能是一个变量,代码是不是就简洁了。Java的泛型就提供了这么一个功能,允许类型参数化。
优化方案如下:
public class ArrayTool<T> {
public void printArray(T[] arr) {
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
在上述代码里,T用来代指某种数据类型,它就像方法的形参一样,在使用的时候传入真实的类型。
public class Test {
public static void main(String[] args) {
ArrayTool<Integer> tool1 = new ArrayTool<>();
Integer[] arr = {1,3,4,5};
tool1.printArray(arr);
ArrayTool<Double> tool2 = new ArrayTool<>();
Double[] arr2 = {1.2, 3.8, 7.7};
tool2.printArray(arr2);
Person[] arr3 = {
new Person("zhangsan", 22),
new Person("李四", 25)
};
ArrayTool<Person> tool3 = new ArrayTool<>();
tool3.printArray(arr3);
}
}
原因二:编写代码期间预报错误,防止运行期间出错,减少运行期间的类型转换。
不使用泛型的时候,List中可以存放任意对象类型。会出现以下问题:
- 报了一堆警告
- 有可能取元素的时候,记错类型,进而产生转换类型的异常。
- 取元素需要类型转换(因为默认Object类型)。
List list = new ArrayList();
list.add(10);
list.add("hello");
list.add("world");
list.add(new Random());
System.out.println(list);
for(int i = 0; i < list.size(); i++) {
if(i == 1) {//假如记错了,记成了下标为1的是整数。
Integer num = (Integer)list.get(i);
num *= 5;
}
}
使用泛型以后,在创建List的时候就指定了元素的类型。因此可以避免你误存其他类型(存放其他类型,会报编译错误)。另外因为已经指定了元素的类型,所以在取的元素的时候,无需类型转换。
List<String> list2 = new ArrayList<>();
list2.add("string");
list2.add("aaa");
list2.add("bbb");
for(int i = 0; i < list2.size(); i++) {
String str = list2.get(i);
System.out.println(str);
}
泛型如何使用?
在Java中,泛型有3种用法:泛型类,泛型方法,泛型接口。
泛型类
所谓的泛型类,就是对类进行泛型的设定。对类设定泛型,这个泛型将作用于类的属性,类的方法,类的方法的返回值,类中的局部变量。
泛型类的语法格式:
在类名的后面加上<类型占位符>
public class Generic<T> {
private String name;
private T aa; //属性可以是泛型类型
public void method(String str) {
System.out.println(str);
}
public void method2(T t) {
System.out.println(t);
}
public T method3() {
return aa;
}
public Generic(String name, T aa) {
super();
this.name = name;
this.aa = aa;
}
public void method4() {
T a = aa;
System.out.println(a);
}
}
泛型类的使用,是在用的时候指定类型。
public class Test {
public static void main(String[] args) {
Generic<Integer> g1 = new Generic<>("张三", 100);
g1.method2(200);
System.out.println(g1.method3());
g1.method4();
Generic<String> g2 = new Generic<String>("lisi", "hello");
g2.method2("world");
System.out.println(g2.method3());
g2.method4();
}
}
泛型类的注意事项
-
指定的类型不能是基本数据类型。(int short long byte float double boolean char),必须是类类型。如果真的想放基本数据类型,使用基本数据类型的包装类(Integer Short Long Byte Float Double Boolean Character)。
平时我们往集合框架里存放100,3.5等。实际上内部做了隐式的包装。
例如:list.add(100) 实际上100是Integer类型。系统自动把int转换为了Integer。
-
静态方法不能使用泛型。使用的时候会报语法错误。
如果真想让静态方法使用泛型,使用泛型方法而泛型类。
-
如果泛型没有指定具体类型,默认是Object类型。
泛型方法
使用场景有2个:
- 当你不想定义泛型类,但是又想让某个方法能够接受泛型的参数。
- 静态方法想要使用泛型的特征。
泛型方法的语法格式
在方法的返回值前面加上<类型占位符>
public class Generic {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public <T> void method1(T t) {
System.out.println(t);
}
public <T> T method2(T t) {
return t;
}
}
在使用的时候,可以指定具体类型,具体做法:对象.<真实类型>方法名(参数)
如果不指定真实类型,系统会根据你传递的参数去推断类型。
public class Test {
public static void main(String[] args) {
Generic g = new Generic();
g.<Integer>method1(100);
String num = g.<String>method2("hello");
System.out.println(num);
g.method1(new Random());
Integer num2 = g.method2(100);
System.out.println(num2);
}
}
泛型方法可以作用于静态方法:
public class Generic {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public <T> void method1(T t) {
System.out.println(t);
}
public <T> T method2(T t) {
return t;
}
public static <T> void method3(T t) {
System.out.println(t);
}
}
静态泛型方法的使用:
public class Test {
public static void main(String[] args) {
Generic.method3("快七夕了");
Generic.method3(100);
Generic.<Double>method3(200.0);
}
}
注意事项:
- 泛型类中的方法(使用了泛型参数或者泛型返回值的方法) 不叫泛型方法!它只是使用了泛型的方法,但不是泛型方法。
- 满足泛型方法定义的方法,才叫泛型方法。
- 泛型类中的类型占位符 和 泛型方法中的类型占位符 是相互独立的。哪怕命名一样,其实也是2个占位符。
一个类可以有几个类型占位符?即泛型类可以有几个类型占位符
泛型类可以根据自己的需要添加若干个占位符。
一个方法可以有几个类型占位符?即泛型方法可以有几个类型占位符
泛型方法可以根据自己的需要添加若干个占位符。
泛型接口
与泛型类类似,在接口名后面添加<类型占位符>,这样的接口叫泛型接口。
一旦一个接口定义为泛型,它内部声明的方法可以使用泛型作为参数类型或者返回值类型。
public interface GenericInterface<T> {
public abstract void method1(T t);
public abstract T method2();
}
接口定义为泛型之后,实现类可以明确泛型类型,也可以不明确泛型类型。
实现类 明确 泛型接口的类型
接口中并没有给出类型的具体值,实现类在实现接口的时候,可以指定类型是什么类型。
public class GenericInterfaceImpl implements GenericInterface<String>{
@Override
public void method1(String t) {
System.out.println(t.length());
}
@Override
public String method2() {
return "你好,周杰棍";
}
}
public class GenericInterfaceImpl2 implements GenericInterface<Double> {
@Override
public void method1(Double t) {
System.out.println(t);
}
@Override
public Double method2() {
return 3.14;
}
}
public interface MyComparable<T> {
public int compareTo(T t);
}
public class Person implements MyComparable<Person>{
private String name;
private String sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, String sex, int age) {
super();
this.name = name;
this.sex = sex;
this.age = age;
}
public Person() {
super();
}
@Override
public String toString() {
return "Person [name=" + name + ", sex=" + sex + ", age=" + age + "]";
}
@Override
public int compareTo(Person t) {
return age - t.getAge();
}
}
public class Rectangle implements MyComparable<Rectangle> {
private double width;
private double height;
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public Rectangle(double width, double height) {
super();
this.width = width;
this.height = height;
}
public Rectangle() {
super();
}
@Override
public String toString() {
return "Rectangle [width=" + width + ", height=" + height + "]";
}
@Override
public int compareTo(Rectangle t) {
return (int)(width * height - t.getWidth() * t.getHeight());
}
}
实现类 不明确 泛型接口中的类型
如果实现类在实现接口的时候,不明确接口中的类型,那这个实现类也必须声明为泛型类。
public class GenericInterfaceImpl3<T> implements GenericInterface<T> {
@Override
public void method1(T t) {
System.out.println(t);
}
@Override
public T method2() {
return null;
}
}
泛型的通配符
类和类之间存在继承关系,可以把子类当成父类看待。
但是泛型类中的泛型实际类型是没有继承关系的。
public class A<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public A(T t) {
super();
this.t = t;
}
public A() {
super();
}
@Override
public String toString() {
return "A [t=" + t + "]";
}
}
public class Test {
public static void main(String[] args) {
A<Integer> a1 = new A<>(100);
System.out.println(a1);
A<String> a2 = new A<>("zhangsan");
System.out.println(a2);
Object o = new Object();
A<Object> a3 = new A<>(o);
System.out.println(a3);
// show(a1);//会报错
show(a2);
// show(a3);//会报错
}
public static void show(A<String> a) {
System.out.println(a);
}
}
如果想让泛型的作用域更广,更通用,可以使用通配符“?”,用来指定任意类型。
public class Test {
public static void main(String[] args) {
A<Integer> a1 = new A<>(100);
System.out.println(a1);
A<String> a2 = new A<>("zhangsan");
System.out.println(a2);
Object o = new Object();
A<Object> a3 = new A<>(o);
System.out.println(a3);
// show(a1);
show(a2);
// show(a3);
System.out.println("====");
show2(a1);
show2(a2);
show2(a3);
}
public static void show(A<String> a) {
System.out.println(a);
}
public static void show2(A<?> a) {
System.out.println(a);
}
}
指定泛型的上界。 <? extends 某类型> 表示某类型,或者某类型的子类。
public class Test {
public static void main(String[] args) {
A<Integer> a1 = new A<>(100);
System.out.println(a1);
A<String> a2 = new A<>("zhangsan");
System.out.println(a2);
Object o = new Object();
A<Object> a3 = new A<>(o);
System.out.println(a3);
// show(a1);
show(a2);
// show(a3);
System.out.println("====");
show2(a1);
show2(a2);
show2(a3);
System.out.println("=====");
//show3(a1);
show3(a2);
//show3(a3);
}
public static void show(A<String> a) {
System.out.println(a);
}
public static void show2(A<?> a) {
System.out.println(a);
}
public static void show3(A<? extends String> a) {
System.out.println(a);
}
}
指定泛型的下届。 <? super 某类型> 代表某类型或者某类型的父类型。
public class Test {
public static void main(String[] args) {
A<Integer> a1 = new A<>(100);
System.out.println(a1);
A<String> a2 = new A<>("zhangsan");
System.out.println(a2);
Object o = new Object();
A<Object> a3 = new A<>(o);
System.out.println(a3);
// show(a1);
show(a2);
// show(a3);
System.out.println("====");
show2(a1);
show2(a2);
show2(a3);
System.out.println("=====");
//show3(a1);
show3(a2);
//show3(a3);
System.out.println("======");
// show4(a1);
show4(a2);
show4(a3);
}
public static void show(A<String> a) {
System.out.println(a);
}
public static void show2(A<?> a) {
System.out.println(a);
}
public static void show3(A<? extends String> a) {
System.out.println(a);
}
public static void show4(A<? super String> a) {
System.out.println(a);
}
}
总结:
泛型的本质是参数化类型,把类或者接口或者方法中的类型看做一个参数。在用的时候指定真正的类型。
泛型类:在类名后加<类型占位符>
在创建对象的时候,指定泛型的真正类型。 类名<真实类型> 变量名 = new 类名<>();
泛型方法:在返回值前面加<类型占位符>
在方法调用的时候,指定泛型的真实类型.
非静态方法: 对象.<真实类型>方法名(实参列表)
静态方法: 类名.<真实类型>方法名(实参列表)
<真实类型>可以省略不写,编译器会根据实参自行推断类型。
泛型接口:在接口名后面加<类型占位符>
在实现类里面使用,public class 类名 implements 接口名<真实类型>
或者 public class 类名<类型占位符> implements 接口名<类型占位符>
当泛型类的对象作为参数的时候,可以根据需要指定上界 (<? extends 某类型>),下界(<? super 某类型>),通配(< ? >)或者精确类型(< Integer >)。
List<? extends 某类型> 指定类型上界 表示我们传入的类型必须是T类型本身或其子类!
List<? super 某类型> 指定类型下界 表示我们传入的类型必须是T类型本身或其父类!
这是理解和定义, 在实际编程时我们的传入往往与理解相反!