泛型(Generic)
概述
泛型就是为了提高安全性,接口,类和方法上都可以使用泛型,特别是集合中常用到,对于泛型,是在编译器起到作用,当你存到了与指定的泛型类型不相同的对象,那么编译器会提示错误,但是在运行期,泛型是起不到作用的,下面我们就利用反射存储和指定泛型类型不一样的对象,来验证泛型是在编译器其作用。
import java.util.ArrayList;
public class FanXing {
public static void main(String[] args) throws Exception {
ArrayList list = new ArrayList();// 这里编译器会给警告,不安全的警告
ArrayList<Integer> list1 = new ArrayList<Integer>();// 这里编译器就不会提示警告了
list1.add(new Integer(12));
list1.add(new Integer(34));
System.out.println("list1=" + list1);
/**
* 利用反射向集合中存储和类型不一样的对象先利用反射获得方法,然后调用方法来进行存储
*/
ArrayList.class.getMethod("add", Object.class).invoke(list1, "abc");// true,插入成功
System.out.println("list1=" + list1);
}
}
结果:
list1=[12, 34]
list1=[12, 34, abc]
从结果可以看出,已经把”abc”存储进去了,所以我们只要跳过了编译器,那么我们就会存储任何对象了。
在使用java提供的对象时,什么时候写泛型?
通常在集合框架中很常见,只要见到<>就要定义泛型。
其实<>就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
通配符
1、当传入的类型不确定时,可以使用通配符?。也可以理解为占位符。使用通配符的好处是可以不用明确传入的类型,这样在使用泛型类或者泛型方法时,提高了扩张性。
Collection<?> a可以与任意参数化的类型匹配,但到底匹配的是什么类型,只有以后才知道,所以:a=newArrayList<Integer>和a=new ArrayList<String>都可以,但a.add(new Date())或a.add(“abc”)都不行。
问题:
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
import java.util.ArrayList;
import java.util.List;
public class FanXing {
public static void main(String[] args) throws Exception {
ArrayList<String> list = new ArrayList<String>();// 这里编译器就不会提示警告了
list.add("abc");
list.add("hello");
show(list);
ArrayList<Integer> list1 = new ArrayList<Integer>();// 这里编译器就不会提示警告了
list1.add(new Integer(12));
list1.add(new Integer(34));
show(list1);
}
public static void show(List<?> list) {// ?表示可以接收任何类型的List集合
System.out.println(list);
}
}
结果:
[abc, hello]
[12, 34]
2、泛型限定
对于一个范围内的一类事物,可以通过泛型限定的方式定义,有两种方式:
1)? extends E:可接收E类型或E类型的子类型;称之为上限。
如:ArrayList<? extends Number>x = new ArrayList<Integer>();
2)? super E:可接收E类型或E类型的父类型;称之为下限。
如:ArrayList<? super Integer>x = new ArrayList<Number>();
import java.util.ArrayList;
import java.util.List;
public class FanXing {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();// 这里编译器就不会提示警告了
list.add(new Integer(12));
list.add(new Integer(34));
show(list);
ArrayList<Object> list1 = new ArrayList<Object>();// 这里编译器就不会提示警告了
list1.add(new Integer(12));
list1.add("abc");
show1(list1);
}
// ?表示可以接收Number的类型或者是NUmber子类的类型的List集合,其他的类型不可以
public static void show(List<? extends Number> list) {
System.out.println(list);
}
// ?表示可以接收Number的类型或者是NUmber父类的类型的List集合,其他的类型不可以
//这里可以知道Number的父类只有Object,所以只能接受Number和Object类型
public static void show1(List<? super Number> list) {
System.out.println(list);
}
}
结果:
[12, 34]
[12, abc]
泛型类
语法格式:
class Utils<XX>
{
private XX s;
public void setxx(XX s)
{
this.s=s;
}
public XX getXX()
{
return s;
}
}
注意:
1、在对泛型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
2、当一个变量被声明为参数时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类共享的,所以静态成员不应该有类级别的类型参数。
泛型方法
为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。
class Demo<Q>
{
public void run(Q q){}
public <T> void show(T t) {}
public <E> void print(E t){}
public static <W> void method(W t){}
}
其中方法中的泛型可以不和类泛型相同。
特殊之处:
静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
泛型方法的特点:
1、位置:用于放置泛型的类型参数的<>应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。
2、只有引用类型才能作为泛型方法的实际参数。
3、除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable& cloneable> void method(){}。
4、普通方法、构造函数和静态方法中都可以使用泛型。
5、可以用类型变量表示异常,称之为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。
6、在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开。例如:
public static <K,V> V getValue(K key) { return map.get(key);}
看代码
public static<?> void printColl(ArrayList<?> al)
{
Iterator<?> it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next().toString());
}
}
public static<T> void printColl(ArrayList<T> al)
{
Iterator<T> it = al.iterator();
while(it.hasNext())
{
//T t=it.next();//T代表具体类型,可以操作和接收这个类型
System.out.println(it.next().toString());
}
}
上面两段代码中T和?有什么区别:
1、T限定了类型,传入什么类型即为什么类型,可以定义变量,接收赋值的内容。
2、?为通配符,也可以接收任意类型但是不可以定义变量。
但是这样定义,虽然提高了扩展性,可还是有一个局限性,就是不能使用其他类对象的特有方法。
通配符方案要比泛型方法更有效,当一个类型变量用来表达两个参数之间或参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用,而不是仅在签名的时候使用,才需要使用泛型方法。
小结:
对泛型的定义:
第一、定义泛型:当又不确定的类型需要传入到集合中,需要定义泛型。
第二、定义泛型类:如果类型确定后,所操作的方法都是属于此类型,则定义泛型类。
第三、定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法。
Collections
作用:专门用来对集合进行操作的类,如对List集合排序sort();
sort(List<T>list)
根据元素的自然顺序 对指定列表按升序进行排序。
sort(List<T>list, Comparator<? super T> c)
根据指定比较器产生的顺序对指定列表进行排序。
例子
public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("4");
al.add("111");
al.add("1");
al.add("3");
Collections.sort(al);
System.out.println(al);
Collections.sort(al,new Mycom2());
System.out.println(al);
}
}
class Mycom2 implements Comparator<String>
{
public int compare(String o1, String o2) {
int num = o1.length()-o2.length();
if(num==0)
{
return o1.compareTo(o2);
}
return num;
}
}
输出为:
[1, 111, 3, 4]
[1, 3, 4, 111]
介绍其中几个比较重要的方法
public static<T> Comparator<T> reverseOrder()
返回一个比较器,它强行逆转实现了 Comparable 接口的对象 Collection 的自然顺序。
如下:
TreeSet<String> ts = new TreeSet<String>();
ts.add("2");
ts.add("1");
ts.add("4");
ts.add("8");
System.out.println(ts);
//输出 1,2,3,8
TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder());
ts.add("2");
ts.add("1");
ts.add("4");
ts.add("8");
System.out.println(ts);
则//输出 8,3,2,1
public static<T> Comparator<T> reverseOrder(Comparator<T> cmp)
返回一个比较器,它强行逆转指定比较器的顺序。
例如:
public class Collectionnn {
public static void main(String[] args)
{
TreeSet<String> ts = new TreeSet<String>(new Mycom());
ts.add("2");
ts.add("11");
ts.add("422");
ts.add("8888");
System.out.println(ts);
//得到 [2, 11, 422, 8888]
TreeSet<String> ts = new TreeSet<String>(Collections. reverseOrder(new Mycom()));
ts.add("2");
ts.add("11");
ts.add("422");
ts.add("8888");
System.out.println(ts);
//得到 [ 8888,422,11,2]
}
}
class Mycom implements Comparator<String>
{
public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
}
Arrays
用于操作数组的工具类
1. 对基本数据类型的折半查找的方法。
例如
binarySearch(byte[]a, byte key)
使用二分搜索法来搜索指定的 byte 型数组,以获得指定的值。
2.对数组的复制
copyOf(boolean[]original, int newLength)
复制指定的数组,截取或用 false 填充(如有必要),以使副本具有指定的长度。
最重要的方法,将数组变成List集合
使用Arrays.asList();
String [] arre={"11","23","22"};
List<String> list = Arrays.asList(arre);
System.out.println(list);
//结果为 [11, 23, 22]
为什么将数组变为集合?
可以使用集合的思想和方法来操作数组中的元素。
数组是一个对象,但是它的功能比较少,而集合的方法很多。
注意:将数组变成集合,不可以使用集合的增删方法
因为数组的长度是固定的。
如果使用增删,会发生UnsupportedOperationException
注意当使用
int []arr ={1,33,3,4,5};
List list =Arrays.asList(arr);
System.out.println(list);
打印的不是元素,而是这个数组地址值。。
它是将arr作为一个集合的元素存在,而这个集合的类型就是int[]
所以当使用泛型的时候,应该这样去定义
List<int[]> list = Arrays.asList(arr);
如果数组中的元素都是对象,变成集合时候,数组中的元素就直接转成集合中的元素
比如说String,如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
当我们这样定义时候
Integer []arr = {1,33,3,4,5};
List<Integer> list= Arrays.asList(arr);
System.out.println(list);
结果为[1, 33, 3, 4, 5]
上述是数组变成集合使用Arrays中的asList方法。
当集合变为数组的时候,就用到了Collection接口中的toArray方法。
ArrayList<String> al = new ArrayList<String>();
al.add("1");
al.add("2");
al.add("3");
String [] arr=al.toArray(new String[0]);
System.out.println(Arrays.toString(arr));
输出为 [1, 2, 3]
这里指定类型的数组要定义多长呢?当指定类型数组小于集合的size,那么该方法内部会创建一个新的数组,长度为集合的size,当指定类型的数组长度大于了集合的size,就不会新创建数组,而是使用传递进来的数组。
所以应该这样创建。String [] arr=al.toArray(new String[al.size()]);
为什么要将集合变成数组?
是为了限定对元素的操作。当返回一个集合时候,使用者对这个集合依然可以使用增删的方法对集合进行修改。当返回一个数组的时候,就不能改变其集合的大小了,限定了对使用着的操作。
高级for循环,方便对集合进行操作。
前提:只有继承自Iterable接口的集合接口才能使用foreach(高级for循环),比如Collection,而Map集合不能使用这个高级for循环。
格式
for(数据类型 变量名 :被遍历的集合(Collection)或者数组)
{
}
这个遍历的局限性:只能对集合中的元素进行取出,而不能做修改动作。
迭代器至少有remove,如果使用列表迭代器的话,增删改查都行。
例子:
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(31);
for(int a:al)
{
System.out.println(a);
}
还可以对数组进行遍历,例子:
int[] arr = {1,2,3,4,5};
for(int num:arr)
{
System.out.println(num);
}
传统for循环和高级for循环的区别:高级for有一个局限性。必须有被遍历的目标。
对HashMap进行高级for循环遍历的例子:
由于Map不能使用高级for循环,所以必须将Map转成Set后,再使用高级for循环。
HashMap<String,String> hm = new HashMap<String,String>();
hm.put("1","2");
hm.put("2", "3");
Set<Map.Entry<String,String>> set = hm.entrySet();
for(Map.Entry<String,String> me:set)
{
System.out.println(me.getKey()+"....."+me.getValue());
}
扩展知识---1.5版本新特性
静态导入
静态导入:导入的是静态的方法,这样在调用类的时候就可以不用写类名了,直接可以使用静态方法。
格式:import static 包名.类名.方法名;这是导入的是一个方法
import static 包名.类名.*;导入的是这个类中的所有静态方法。
普通的导入:导入的是类,调用其中的方法还得用类名或者是对象,格式:import 包名.类; 或者是 import 包名.*;
import static java.lang.Math.*;//导入的是静态方法
ublic class Demo {
public static void main(String[] args) {
System.out.println(abs(-1));//求绝对值
}
可变参数
可变参数是用…来表示的,其实里面封装了一个可变长度的数组。注意点:在定义参数的时候,一定要把其放到所有参数的后面。
public class Demo {
public static void main(String[] args) {
sum("求和:",2,3,4,5);
sum("求和:",4,34);
}
public static void sum(String str,int ...ss ){
System.out.print(str);
int sum=0;
for(int s:ss)
sum+=s;
System.out.println(sum);
}
}
增强for循环
格式:for(类型 变量:集合变量)
此变量:必须实现了Iterator接口
只能读取,不能对其进行其他操作(修改,删除,增加)
String [] ss={"abc","123","de12"};
for(String s:ss)
System.out.println(s);
自动装箱和拆箱
在-128~127之间,基本数据它们都共享同一个对象,这样使用的享元模式,这样可以节约资源,但是超过这个范围,那么他们就会创建新的对象。
public class Demo {
public static void main(String[] args) {
Integer a=28;//装箱,自动装箱把Int类型封装成了Integer
Integer b=28;
System.out.println(a==b);//true
//a和b是共享同意个对象
Integer c=128;
Integer d=128;
System.out.println(c==d);//false
//因为超出了-128~127范围,所以c和d不会共享同一个对象,是两个对象
Integer e=28;
int f=e+12;//自动拆箱,Integer没有+的运算,使用的是int的+运算,所以是把Integer类型的转换成int类型,做的加法运算
}
}