Java基础

2020-05-24  本文已影响0人  安然在路上

1、泛型

jdk1.5增加了泛型支持很大程度是为了让集合记住其元素的类型。
知识点:泛型类、泛型接口、类型通配符、泛型方法

泛型出现的原因:编译时不检查类型的异常:

public class ListErr
        {
            public static void main(String[] args)
            {
                  // 创建一个只想保存字符串的List集合
                  List strList=new ArrayList();
                  strList.add("疯狂Java讲义");
                  strList.add("疯狂Ajax讲义");
                  strList.add("轻量级Java EE企业应用实战");
                  // “不小心”把一个Integer对象“丢进”了集合
                  strList.add(5);    //①
                  for (int i=0; i < strList.size() ; i++ )
                  {
                        // 因为List里取出的全部是Object,所以必须进行强制类型转换
                        // 最后一个元素将出现ClassCastException异常
                        String str=(String)strList.get(i);   //②
                  }
            }
        }

如果程序在①处“不小心”把一个Integer对象“丢进”了List集合中,这将导致程序在②处引发ClassCastException异常,因为程序试图把一个Integer对象转换为String类型。

手动实现编译时检查类型
下面程序创建了一个StrList集合类,该集合里只能存放String对象。

        // 自定义一个StrList集合类,使用组合的方式来复用ArrayList类
        class StrList
        {
            private List strList=new ArrayList();
            // 定义StrList的add方法
            public boolean add(String ele)
            {
                  return strList.add(ele);
            }
            // 重写get()方法,将get()方法的返回值类型改为String类型
            public String get(int index)
            {
                  return (String)strList.get(index);
            }
            public int size()
            {
                  return strList.size();
            }
        }
        public class CheckType
        {
            public static void main(String[] args)
            {
                  // 创建一个只想保存字符串的List集合
                  StrList strList=new StrList();
                  strList.add("疯狂Java讲义");
                  strList.add("疯狂Android讲义");
                  strList.add("轻量级Java EE企业应用实战");
// 下面语句不能把Integer对象“丢进”集合中,否则将引起编译错误
                  strList.add(5);    //①
                  System.out.println(strList);
                  for (int i=0; i < strList.size() ; i++ )
                  {
                        // 因为StrList里元素的类型就是String类型
                        //所以无须进行强制类型转换
                        String str=strList.get(i);
                  }
            }
        }

上面程序中定义的StrList类就实现了编译时的异常检查,当程序在①处试图将一个Integer对象添加到StrList集合中时,程序将无法通过编译。因为StrList只接受String对象作为元素,所以①处代码在编译时会得到错误提示。
从Java 5以后,Java引入了“参数化类型(parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型,正如List<String>,这表明该List只能保存字符串类型的对象。Java的参数化类型被称为泛型(Generic)。
List中使用泛型

public class GenericList
        {
            public static void main(String[] args)
            {
                  // 创建一个只想保存字符串的List集合
                  List<String> strList=new ArrayList<String>();  //①
                  strList.add("疯狂Java讲义");
                  strList.add("疯狂Android讲义");
                  strList.add("轻量级Java EE企业应用实战");
                  // 下面代码将引起编译错误
                  strList.add(5);   //②
                  for (int i=0; i < strList.size() ; i++ )
                  {
                        // 下面代码无须进行强制类型转换
                        String str=strList.get(i);   //③
                  }
            }
        }

1.1泛型接口

public interface List<E>
        {
            // 在该接口里,E可作为类型使用
            // 下面方法可以使用E作为参数类型
            void add(E x);
            Iterator<E> iterator();   //①
            ...
        }
        // 定义接口时指定了一个类型形参,该形参名为E
        public interface Iterator<E>
        {
            // 在该接口里E完全可以作为类型使用
            E next();
            boolean hasNext();
            ...
        }
        // 定义该接口时指定了两个类型形参,其形参名为K、V
        public interface Map<K , V>
        {
            // 在该接口里K、V完全可以作为类型使用
            Set<K> keySet();   //②
            V put(K key, V value);
        }

允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种类型形参。

1.2泛型类

// 定义Apple类时使用了泛型声明
        public class Apple<T>
        {
            // 使用T类型形参定义实例变量
            private T info;
            public Apple(){}
            // 下面方法中使用T类型形参来定义构造器
            public Apple(T info)
            {
                  this.info=info;
            }
            public void setInfo(T info)
            {
                  this.info=info;
            }
            public T getInfo()
            {
                  return this.info;
            }
            public static void main(String[] args)
            {
                  // 因为传给T形参的是String实际类型
                  // 所以构造器的参数只能是String
                  Apple<String> a1=new Apple<>("苹果");
                  System.out.println(a1.getInfo());
                  // 因为传给T形参的是Double实际类型
                  // 所以构造器的参数只能是Double或者double
                  Apple<Double> a2=new Apple<>(5.67);
                  System.out.println(a2.getInfo());
            }
        }

在内存中并不存在所谓的泛型类。

// 分别创建List<String>对象和List<Integer>对象
        List<String> l1=new ArrayList<>();
        List<Integer> l2=new ArrayList<>();
        // 调用getClass()方法来比较l1和l2的类是否相等
        System.out.println(l1.getClass()==l2.getClass());

这段代码输出true.

1.3类型通配符

如果Foo是Bar的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口, G<Foo>并不是G<Bar>的子类型!

null是所有引用类型的实例。

1.3.1使用通配符形参时设定上限

public class Canvas
        {
            // 同时在画布上绘制多个形状,使用被限制的泛型通配符
            public void drawAll(List<? extends Shape> shapes)
            {
                  for (Shape s : shapes)
                  {
                        s.draw(this);
                  }
            }
              ..
        }

List<? extends Shape>是受限制通配符的例子,此处的问号(?)代表一个未知的类型,就像前面看到的通配符一样。但是此处的这个未知类型一定是Shape的子类型(也可以是Shape本身),因此我们把Shape称为这个通配符的上限(upper bound)。

1.3.2设定类型形参的上限

public class Apple<T extends Number>
        {
            T col;
            public static void main(String[] args)
        {
              Apple<Integer> ai=new Apple<>();
              Apple<Double> ad=new Apple<>();
              // 下面代码将引发编译异常,下面代码试图把String类型传给T形参
              // 但String不是Number的子类型,所以引起编译错误
              Apple<String> as=new Apple<>();    //①
        }
    }

上面程序定义了一个Apple泛型类,该Apple类的类型形参的上限是Number类,这表明使用Apple类时为T形参传入的实际类型参数只能是Number或Number类的子类。上面程序在①处将引起编译错误:类型形参T的上限是Number类型,而此处传入的实际类型是String类型,既不是Number类型,也不是Number类型的子类型,所以将会导致编译错误。

在一种更极端的情况下,程序需要为类型形参设定多个上限(至多有一个父类上限,可以有多个接口上限),表明该类型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。如下代码所示。

// 表明T类型必须是Number类或其子类,并必须实现java.io.Serializable接口
        public class Apple<T extends Number & java.io.Serializable>
        {
            ...
        }

与类同时继承父类、实现接口类似的是,为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。

1.4 泛型方法

泛型方法的用法格式如下:

        修饰符 <T , S> 返回值类型 方法名(形参列表)
        {
            // 方法体...
        }

把上面方法的格式和普通方法的格式进行对比,不难发现泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号括起来,多个类型形参之间以逗号(,)隔开,所有的类型形参声明放在方法修饰符和方法返回值类型之间。

public class GenericMethodTest
        {
            // 声明一个泛型方法,该泛型方法中带一个T类型形参
            static <T> void fromArrayToCollection(T[] a, Collection<T> c)
            {
                  for (T o : a)
                  {
                        c.add(o);
                  }
            }
            public static void main(String[] args)
            {
                  Object[] oa=new Object[100];
                  Collection<Object> co=new ArrayList<>();
                  // 下面代码中T代表Object类型
                  fromArrayToCollection(oa, co);
                  String[] sa=new String[100];
                  Collection<String> cs=new ArrayList<>();
                  // 下面代码中T代表String类型
                  fromArrayToCollection(sa, cs);
                  // 下面代码中T代表Object类型
                  fromArrayToCollection(sa, co);
                  Integer[] ia=new Integer[100];
                  Float[] fa=new Float[100];
                  Number[] na=new Number[100];
                  Collection<Number> cn=new ArrayList<>();
                  // 下面代码中T代表Number类型
                  fromArrayToCollection(ia, cn);
                  // 下面代码中T代表Number类型
                  fromArrayToCollection(fa, cn);
                  // 下面代码中T代表Number类型
                  fromArrayToCollection(na, cn);
                  // 下面代码中T代表Object类型
                  fromArrayToCollection(na, co);
                  // 下面代码中T代表String类型,但na是一个Number数组
                  // 因为Number既不是String类型,也不是它的子类
                  // 所以出现编译错误
                  //fromArrayToCollection(na, cs);
            }
        }

??? fromArrayToCollection(sa, co);为啥这个也可以,泛型不是不支持子类的吗

好吧,我放弃了,能看懂就行了,我已经不要求自己记住了。。。

杂七杂八:
1、抽象类不能创建对象,要创建对象,必须使用它的具体子类
2、jdk8中的接口可以有静态(static)方法和默认(default)方法
3、RuntimeException比较特殊,它的名字有点误导,因为其他异常也是运行时产生的,它表示的实际含义是未受检异常(unchecked exception),相对而言,Exception的其他子类和Exception自身则是受检异常(checkedexception), Error及其子类也是未受检异常
4、应该避免在finally中使用return语句或者抛出异常,会覆盖try/catch中的异常和返回值

上一篇下一篇

猜你喜欢

热点阅读