第八章 泛型与枚举

1、泛型

1.1 泛型介绍

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。实现参数的任意化。
泛型让编程人员能够使用类型抽象,通常用于集合里面(集合框架中的类都是泛型类)。

需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?
答案是可以使用 Java 泛型。

1.2 泛型优点

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

没有泛型时,可以往集合中添加任意类型对象。但编译器没有对类型进行控制,无法进行错误检验,买下了安全隐患。
取值的时候错误的进行了强制类型转换,导致程序运行失败。

ArrayList al = new ArrayList();
// 无法进行错误检查,任何类型对象可以添加进去,编译器和运行期都可以通过
al.add("ysjian001");
al.add(1);
al.add(new Object());

String first = (String) al.get(0);//取值时进行强制类型转换,类型转换正确,运行成功
Integer first2 = (Integer)al.get(0);//类型转换失败,运行失败

使用泛型时,会在编译时检查数据类型,防止出现运行时错误。提高了程序的可读性和安全性。

ArrayList<String> al = new ArrayList<String>();
al.add( "ysjian001");
// al.add(new File()); // 定义了String类型参数,添加File对象会报错
String first =  al.get(0);// 使用泛型后取值不用进行类型转换

1.3 泛型规则

  1. 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
  2. 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
  3. 泛型的类型参数可以有多个。
  4. 泛型的参数类型可以使用extends语句,例如。习惯上称为“有界类型”。
  5. 泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName(“java.lang.String”);

1.4 泛型的使用

(1)泛型类

1、定义
泛型类型在类的定义中。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
2、格式
(2.1)类的定义

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}

(2.2)类的实例化

//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型测试","key is " + genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());

3、实例

// 泛型类:把泛型定义在类上
public class ObjectTool<T> { 
      private T obj; 
      public T getObj() { 
         return obj; 
      } 
      public void setObj(T obj) { 
           this.obj = obj;
     }
}
//  泛型类的测试 
public class ObjectToolDemo { 
    public static void main(String[] args) { 
        ObjectTool<String> ot = new ObjectTool<String>();    
        ot.setObj(new String("中国")); 
        String s = ot.getObj(); 
        System.out.println("姓名是:" + s); 
        ObjectTool<Integer> ot2 = new ObjectTool<Integer>();    
        ot2.setObj(new Integer(69)); 
        Integer i = ot2.getObj(); 
        System.out.println("年龄是:" + i); 
   }
}

(2)泛型方法

1、定义
泛型方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。

2、格式
使用泛型方法打印不同字符串的元素

public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
 
    public static void main( String args[] )
    {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
 
        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组
 
        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组
 
        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    } 
}

3、实例
(1)有参数的泛型方法类型的确定是根据参数类型自动推导

* * 泛型方法:把泛型定义在方法上 **
public class ObjectTool {  
      public <T> void show(T t) {
           System.out.println(t); 
      }
}
  public class ObjectToolDemo { 
        public static void main(String[] args) { 
             // 定义泛型方法后
            ObjectTool ot = new ObjectTool(); 
            ot.show("hello"); 
            ot.show(100);
            ot.show(true);
       }
  }

(2)无参数的泛型方法类型是根据等号左边声明的泛型进行推导

class Demo {
    
    public <T> List<T> newArrayList() {
        return new ArrayList<T>();
    }
}

public class Test {
    
    public static void main (String[] args) throws java.lang.Exception
    {
        Demo demo = new Demo();
        List<String> list = demo.newArrayList();
        list.add("www.bo56.com");
        list.add("bo56.com");
        //list.add(1); 报错。只能添加String
        for (String str:list) {
            System.out.println(str);
        }
    }
}

(3)泛型接口

1、定义
将泛型定义在接口上
2、实例

/* * 泛型接口:把泛型定义在接口上 */
public interface Inter<T> { 
  public abstract void show(T t);
}
//实现类在实现接口的时候,我们会遇到两种情况
//第一种情况:已经知道是什么类型的了
public class InterImpl implements Inter<String> { 
@Override 
public void show(String t) { 
  System.out.println(t);
}
}
//第二种情况:还不知道是什么类型的
public class InterImpl<T> implements Inter<T> { 
@Override 
public void show(T t) { 
       System.out.println(t);
}
}
public class InterDemo { 
  public static void main(String[] args) {
      // 第一种情况的测试
     Inter<String> i = new InterImpl(); 
     i.show("hello"); 
     // 第二种情况的测试
     Inter<String> i = new InterImpl<String>(); 
     i.show("hello"); 
     Inter<Integer> ii = new InterImpl<Integer>(); 
     ii.show(100);
}
}

1.5 通配符

(1)通配符概述

类型通配符一般是使用?代替具体的类型参数(是实参不是形参!)。例如 List<?> 在逻辑上是List,List 等所有List<具体类型实参>的父类。

(2)通配符特点

  1. 只能添加null。
  2. 获取的值只能赋值给Object类型。
    因为通配符?表示该集合存储的元素类型未知,可以是任何类型。往集合中加入元素需要是一个未知元素类型的子类型,正因为该集合存储的元素类型未知,所以我们没法向该集合中添加任何元素。唯一的例外是null,因为null是所有类型的子类型,所以尽管元素类型不知道,但是null一定是它的子类型。

可以解决当具体类型不确定的时候,这个通配符就是?;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

class Food {}

class Fruit extends Food {}

class Apple extends Fruit {}

public class Test {
    
    public static void main (String[] args) throws java.lang.Exception
    {
        List<?> list = new ArrayList<Fruit>();
        //list.add(new Food()); 编译错误
        //list.add(new Fruit()); 编译错误
        //list.add(new Apple()); 编译错误
        list.add(null);
        //Food food = list.get(0);编译错误
        //Fruit fruit = list.get(0);编译错误
        //Apple apple = list.get(0);编译错误
        Object object = list.get(0);
    }
}

(3)子类型(带下界)通配符 extend

1、介绍

List<? extends Number> list;

其中<? extends Number>表示通配符的下边界,即“?”只能被赋值为Number或其子类型。
2、特点

  1. 为泛型指定的类型只能是Fruit类型或者其子类。
  2. 只能为其列表添加null。
  3. get方法获取的值只能赋值给Fruit类或者其超类。

3、实例

class Food {}

class Fruit extends Food {}

class Apple extends Fruit {}

public class Test {
    
    public static void main (String[] args) throws java.lang.Exception
    {
        List<? extends Fruit> list = new ArrayList<Fruit>();
        // List<? extends Fruit> listA = new ArrayList<Food>(); 编译错误。不能为父类。
        List<? extends Fruit> listN = new ArrayList<Apple>();//为泛型制定类型只能为Fruit或者其子类
        listN.add(null);//只能为其列表添加null
        //listN.add(123); 不能add
        Fruit fruit = listN.get(0);
        Food food = listN.get(0);//get方法获取的值只能赋值给Fruit类或超类
        //Apple apple = listN.get(0); 编译错误。get获取的值,只能给父类
        listN.remove(0);
    }
}

(4)父类型(带上界)通配符 super

1、介绍

List<? super Integer> list;

其中<? super Integer>表示通配符的下边界,即“?”只能被赋值为Integer或其父类型。
2、特点

  1. 为泛型指定的类型必须为Fruit,或者其超类。
  2. 可以为其列表添加任意Fruit类型,或者其子类。
  3. get方法获取的类型,只能赋值给Object类型。

3、实例

class Food {}

class Fruit extends Food {}

class Apple extends Fruit {}

public class Test {
    
    public static void main (String[] args) throws java.lang.Exception
    {
        List<? super Fruit> list = new ArrayList<Fruit>();
        List<? super Fruit> listA = new ArrayList<Food>(); //只能指定Fruit类或其父类
        //List<? super Fruit> listN = new ArrayList<Apple>(); 编译错误,不能为子类
        listA.add(new Fruit());
        //listA.add(new Food()); 编译错误,不能为父类。
        listA.add(new Apple());//只能添加Fruit类型或其子类
        Object object = listA.get(0);//只能赋值给Object类型
        //Fruit fruit = listA.get(0);编译错误。
        //Food food = listA.get(0);编译错误。
        //Apple apple = listA.get(0); 编译错误。
    }
}

(5)实例

public class GenericDemo {
    public static void main(String[] args) {
        // 泛型如果明确的写的时候,前后必须一致 Collection<Object> c1 = new ArrayList<Object>();
        // Collection<Object> c2 = new ArrayList<Animal>();//报错
        // Collection<Object> c3 = new ArrayList<Dog>();//报错
        // Collection<Object> c4 = new ArrayList<Cat>();//报错

        // ?表示任意的类型都是可以的
        Collection<?> c5 = new ArrayList<Object>();
        Collection<?> c6 = new ArrayList<Animal>();
        Collection<?> c7 = new ArrayList<Dog>();
        Collection<?> c8 = new ArrayList<Cat>();

        // ? extends E:向下限定,E及其子类
        // Collection<? extends Animal> c9 = new ArrayList<Object>();//报错
        Collection<? extends Animal> c10 = new ArrayList<Animal>();
        Collection<? extends Animal> c11 = new ArrayList<Dog>();
        Collection<? extends Animal> c12 = new ArrayList<Cat>();

        // ? super E:向上限定,E极其父类
        Collection<? super Animal> c13 = new ArrayList<Object>();
        Collection<? super Animal> c14 = new ArrayList<Animal>();
        // Collection<? super Animal> c15 = new ArrayList<Dog>();//报错
        // Collection<? super Animal> c16 = new ArrayList<Cat>();//报错
    }
}

class Animal {
}

class Dog extends Animal {
}

class Cat extends Animal {
}

(6)通配符总结

  1. 通过带有通配符,限制了与泛型相关方法的使用。
  2. 下边界通配符:可以使用返回值为泛型变量的方法;
  3. 上边界通配符:可以使用参数为泛型变量的方法。

2、枚举

发布了74 篇原创文章 · 获赞 15 · 访问量 6256

猜你喜欢

转载自blog.csdn.net/qq_29966203/article/details/93708880