【JavaSE】之泛型

Source

1.JDK1.5新特性

1.1可变参数

要求方法可以接收任意个整数并且返回它们的相加结果

可变参数:类型...参数名称

一个方法有且只能有一个可变参数,并且放在方法的最后一个参数。可变参数的本质还是数组

例:早期实现方式

public class Test1{
    public static void main(String[] args) {
        System.out.println(add(new int[]{1,3}));
        System.out.println(add(new int[]{1,3,5,7}));
        System.out.println(add(new int[]{1,3,5,7,9}));
    }
    public static int add(int[] data){
        int result = 0;
        for(int i=0; i<data.length; i++){
            result += data[i];
        }
        return result;
    }
}

        这种最初的实现方式本身存在缺陷,现在要求设计的不是数组,而是任意多个参数。从JDK1.5之后追加了可变参数,这种方法的定义方式为:

public [static] [final] 返回值 方法名称([参数类型 参数名称][参数类型 ... 参数名称]){}

//这个参数上使用到的 ... 实际上表示一个数组的结构

例:方法的可变参数

public class Test1{
    public static void main(String[] args) {
        System.out.println(add(1,3));
        System.out.println(add(new int[]{1,3,5,7}));
        System.out.println(add(new int[]{1,3,5,7,9}));
    }
    public static int add(int ... data){  //本身还是一个数组
        int result = 0;
        for(int i=0; i<data.length; i++){
            result += data[i];
        }
        return result;
    }
}

注意:如果要传递多类参数,可变参数一定要放在最后,并且只能设置一个可变参数

1.2for each 循环用于数组与类集的简单输出

格式:

for(数据类型 临时变量 : 数组(集合)){
    //循环次数为数组的长度,而每一次循环都会顺序取出数组中的一个元素赋值给临时变量
}

例:

public class Test1{
    public static void main(String[] args) {
        int[] data = new int[]{1,3,5,7,9};
        for(int i:data){
            System.out.print(i+" ");
        }
    }
}

通过此方式可以很好的避免数组越界问题,但是这种数组的操作只适合简单输出模式。

1.3静态导入

定义一个Test1类,这个类提供了static方法

package www.bit.java.util;

public class Test1{
    public static int add(int x,int y){
        return x+y;
    }
    public static int mul(int x,int y){
        return x*y;
    }
}

使用Test1类

package www.bit.java;
import www.bit.java.util.Test1;

public class Test{
    public static void main(String[] args) {
        //通过类名调用
        System.out.println(Test1.add(10,20));
        System.out.println(Test1.mul(10,20));
    }
}

        从JDK1.5开始,如果类中方法全是static方法,则可以直接把这个类的方法导入进来,就好比像在主类中定义的方法一样,可以被主方法直接调用。

例:静态导入

package www.bit.java;
import static www.bit.java.util.Test1.*;  //静态导入

public class Test{
    public static void main(String[] args) {
        //静态导入与和在自己的类中使用一样
        System.out.println(add(10,20));
        System.out.println(mul(10,20));
    }
}

2.泛型

假设需要定义一个描述坐标的程序类Point,需要提供两个属性x、y。对于这两个属性的内容可能有如下选择:

1.x = 10、 y = 20;

2.x = 10.1、y = 20.1;

3.x = 东经80度、y = 北纬20度;

此时需要保存的有int、double、String,所以在java中只有一种类型可以保存所有类型:Object型

例:

class Point{
    private Object x;
    private Object y;
    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
}
public class Test1{
    public static void main(String[] args) {
        Point point = new Point();
        point.setX(10);  //自动装箱并且向上转型为Object
        point.setY("北纬20度");
        //取出数据
        String x = (String) point.getX();
        String y = (String) point.getY();
        System.out.println(x+"、"+y);
    }
}

        这个时候由于设置方的错误,经坐标内容设置成了int与String,但是接收方不知道,于是在执行时就会出现ClassCastException。

ClassCastException指的是两个没有关系的对象进行强转出现的异常。

为了解决ClassCastException,引出泛型。

2.1泛型类

泛型类指的是在类定义的时候并不会设置类的属性或方法中参数的具体类型,而是在类使用时在定义。

泛型类的基本语法:


class MyClass<T>{

    T value1;

}

        尖括号<>中的T被称作是类型参数,用于指定代任何类型。实际上这个T可以任意写,但出于规范,我们用单个大写字母来代表类型参数。

T:被称为类型参数,用于指代任何类型,代表一般的类

E:代表Element,常用于泛型类中的属性

K:代表Key的意思

V:代表Value的意思,通常与K一起配合使用

(K、V:键值对,见Map集合)

S:代表Subtype的意思

泛型只允许接收类,所有基本类型必须使用包装类

class Myclass<T>{
    private T value;
    public T getValue() {
        return value;
    }
    public void setValue(T value) {
        this.value = value;
    }
}
public class Test1{
    public static void main(String[] args) {
        Myclass<String> myclass = new Myclass<String>();
        myclass.setValue("hello");
        System.out.println(myclass.getValue());
        Myclass<Integer> myclass1 = new Myclass<Integer>();
        myclass1.setValue(10);
        System.out.println(myclass1.getValue());
    }
}

有两个属性需要不同类型时:

class Myclass<T,E>{
    private T value1;
    private E value2;
    public T getValue1() {
        return value1;
    }
    public void setValue1(T value1) {
        this.value1 = value1;
    }
    public E getValue2() {
        return value2;
    }
    public void setValue2(E value2) {
        this.value2 = value2;
    }
}
public class Test1{
    public static void main(String[] args) {
        Myclass<String,Integer> myclass = new Myclass<String,Integer>();
        myclass.setValue1("hello");
        System.out.println(myclass.getValue1());
        Myclass<String,Integer> myclass1 = new Myclass<String, Integer>();
        myclass1.setValue2(10);
        System.out.println(myclass1.getValue2());
    }
}

JDK1.7 新增 Myclass<String,Integer> myclass = new Myclass<>(); 后面尖括号中的内容可以省略

2.2泛型方法

public <T> T MyMethod(T t){
    System.out.print(t);
}

第一个T代表此方法是泛型,第二个T代表返回值是T,第三个T代表类型参数

class Myclass{
    public <T> void print(T t){
        System.out.println(t);
    }
}
public class Test1{
    public static void main(String[] args) {
        Myclass myclass = new Myclass();
        myclass.print("Hello");
        myclass.print(10);
    }
}

        当泛型类与泛型方法共存时,泛型类中的类型参数与泛型方法中的类型参数没有关系,泛型方法中的类型参数始终以自己定义的类型参数为准

规范:泛型方法类型参数与泛型类的类型参数不要同名。

class Myclass<T>{ //泛型类
    public void method1(T t){
        System.out.println(t);
    }
    //泛型方法
    public <E> void method2(E e){
        System.out.println(e);
    }
}
public class Test1{
    public static void main(String[] args) {
        Myclass<String> myclass = new Myclass();
        myclass.method1("hello");
        myclass.method2(10);
    }
}

因为Object向下转型要强转。并且在运行时才会出错,所以引入泛型。

引入泛型后,泛型方法的参数不统一,要写许多重载,所以引入通配符。

2.3通配符(重点)

2.3.1 ?-可以接收任意类型(仅用于方法参数)

用于方法中,表示参数可以接收任意类型的泛型类

只能取得类中数据,不能修改数据,因为类型不确定,无法设置确定类型。

class Myclass<T>{
    private T value1;
    public T getValue1() {
        return value1;
    }
    public void setValue1(T value1) {
        this.value1 = value1;
    }
}
public class Test1{
    public static void main(String[] args) {
        Myclass<String> myclass = new Myclass<>();
        myclass.setValue1("hello");
        print(myclass);
    }
    public static void print(Myclass<?> myclass){
        //不知道传进来的是什么类型
        System.out.println(myclass.getValue1());
    }
}

2.3.2 ? extend 类:设置/取得泛型上限

eg:? extends Number:表示泛型必须是Number及其子类的泛型类

只能取得类中属性值,不能修改值(发生父类到子类的向下转型,需要强转,由于具体子类不确定,因此无法转型)

class Myclass<T extends Number>{
    private T value1;
    public T getValue1() {
        return value1;
    }
    public void setValue1(T value1) {
        this.value1 = value1;
    }
}
public class Test1{
    public static void main(String[] args) {
        Myclass<Integer> myclass = new Myclass<>();
        myclass.setValue1(123);
        print(myclass);
    }
    public static void print(Myclass<? extends Number> myclass){
        //知道父类但是不知道传进来的是什么子类(如果要改相当于向下转型,但是没有经过向上转型)
        System.out.println(myclass.getValue1());
    }
}

2.3.3 ? super 类:取得泛型下限-只能用于方法中

eg: ? super String:表示此方法只能取得String以及其父类Object

可以设置属性值(子类到父类是自动的向上转型)

class Myclass<T>{
    private T value1;
    public T getValue1() {
        return value1;
    }
    public void setValue1(T value1) {
        this.value1 = value1;
    }
}
public class Test1{
    public static void main(String[] args) {
        Myclass<String> myclass = new Myclass<>();
        print(myclass);
    }
    public static void print(Myclass<? super String> myclass){
        //取得泛型下限,子类到父类是自动的向上转型
        myclass.setValue1("hello");
        System.out.println(myclass.getValue1());
    }
}

2.4泛型接口

interface IInterface<T> { //在接口上定义了泛型
    T test(T t);
}

对于这个接口的实现子类有两种做法:

例1:在子类定义时继续使用泛型

//第一个T表示子类是一个泛型,第二个T表示接口是一个泛型,两个T是一个类型T,因为子类T是从接口继承的
interface IInterface<T> { // 在接口上定义了泛型
    public void print(T t) ;
}
class InterfaceImpl<T> implements IInterface<T> {
    @Override
    public void print(T t) {
        System.out.println(t);
    }
}

public class Test1{
    public static void main(String[] args){
        IInterface<String> msg = new InterfaceImpl();
        msg.print("hello");
    }
}

例2:子类定义时确定好类型

interface IInterface<T> { // 在接口上定义了泛型
    public void print(T t) ;
}
class InterfaceImpl implements IInterface<String> {
    @Override
    public void print(T t) {
        System.out.println(t);
    }
}

public class Test1{
    public static void main(String[] args){
        IInterface<String> msg = new InterfaceImpl();
        msg.print("hello");
    }
}

2.5类型擦除(语法糖)

        语法糖:仅存在与源码阶段,编译后就消失不见。Java中典型的语法糖:泛型、自动拆装箱。

        泛型信息仅存在于代码编译阶段,进入JVM之前,与泛型相关的信息会被擦除掉。专业术语:类型擦除。也就是说,泛型类与普通类在Java虚拟机中没有任何区别。

        泛型类进入JVM之前会进行类型擦除,之前泛型类的类型参数若没有指定上限,会被擦除为Object类型。如果指定上限,则类型参数会被替换为相应类型上限。

class Myclass<T>{
    T t;
}
public class Test1{
    public static void main(String[] args) {
        Myclass<String> myclass1 = new Myclass<>();
        Myclass<Integer> myclass2 = new Myclass<>();
        System.out.println(myclass1.getClass() == myclass2.getClass());
    }
}

例:观察类型擦除

class Myclass<T,E extends Number>{
    public T t;
    public E e;
}
public class Test1{
    public static void main(String[] args) {
        Myclass<Integer,Double> myclass1 = new Myclass<>();
        Field[] fields = myclass1.getClass().getDeclaredFields();
        for(Field field : fields){
            System.out.println(field.getType());
        }
    }
}