java-正则表达式-集合-泛型-注解-异常
正则表达式
正则表达式到底是什么东西?
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
http://tool.oschina.net/regex/ :正则表达式在线验证
1.正则表达式基本语法
一,两个特殊的符号'^'和'$'。他们的作用是分别指出一个字符串的开始和结束。
"^The":表示所有以"The"开始的字符串("There","The cat"等);
"of despair$":表示所以以"of despair"结尾的字符串;
"^abc$":表示开始和结尾都是"abc"的字符串——只有"abc"自己了;
"notice":表示任何包含"notice"的字符串。
二, '*','+'和'?' 这三个符号,表示一个或多个字符重复出现的次数。
它们分别表示“没有或更多”,“一次或更多”还有“没有或一次”。
"ab*":表示一个字符串有一个a后面跟着零个或若干个b。("a", "ab", "abbb",……);
"ab+":表示一个字符串有一个a后面跟着至少一个b或者更多;
"ab?":表示一个字符串有一个a后面跟着零个或者一个b;
"a?b+$":表示在字符串的末尾有零个或一个a跟着一个或几个b。
//你也可以使用范围,用大括号括起,用以表示重复次数的范围。
"ab{2}":表示一个字符串有一个a跟着2个b("abb");
"ab{2,}":表示一个字符串有一个a跟着至少2个b;
"ab{3,5}":表示一个字符串有一个a跟着3到5个b。
//还有一个'|',表示“或”操作:
"hi|hello":表示一个字符串里有"hi"或者"hello";
"(b|cd)ef":表示"bef"或"cdef";
"(a|b)*c":表示一串"a""b"混合的字符串后面跟一个"c";
请注意,你必须指定范围的下限(如:“{0,2}“而不是”{,2}”)。还有,你可能注意到了,'‘,’+‘和’?'相当于"{0,}“,”{1,}“和”{0,1}"。*
三,方括号表示某些字符允许在一个字符串中的某一特定位置出现:
"[ab]":表示一个字符串有一个"a"或"b"(相当于"a|b");
"[a-d]":表示一个字符串包含小写的'a'到'd'中的一个(相当于"a|b|c|d"或者"[abcd]");
"^[a-zA-Z]":表示一个以字母开头的字符串;
"[0-9]%":表示一个百分号前有一位的数字;
",[a-zA-Z0-9]$":表示一个字符串以一个逗号后面跟着一个字母或数字结束。
你也可以在方括号里用'^'表示不希望出现的字符,
'^'应在方括号里的第一位。(如:"%[^a-zA-Z]%"表示两个百分号中不应该出现字母)。
如果表示特殊字符本身,你必须在"^.$()|*+?{\"这些字符前加上转移字符'\'。
四,'.'可以替代任意的一个字符:
"a.[0-9]":表示一个字符串有一个"a"后面跟着一个任意字符和一个数字;
"^.{3}$":表示有任意三个字符的字符串(长度为3个的字符);
五、常用的一些表达式的简写:
2.java中怎么使用正则表达式:
1、匹配验证-验证Email是否正确
public static void main(String[] args) {
// 要验证的字符串
String str="service@xsoftlab.net";
//邮箱验证规则,以数字字母开头,下划线
String regEx ="^[a-zA-Z0-9]+([._-]*|[a-zA-Z0-9]*)*@[a-zA-Z0-9]+([-.]*|[a-zA-Z0-9]*)*\\.[a-zA-Z0-9]{2,4}$";
//编译正则表达式
Pattern pattern=Pattern.compile(regEx);
// 忽略大小写的写法
// Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(str);
// 字符串是否与正则表达式相匹配
boolean rs=matcher.matches();
System.out.println(rs);
}
集合
集合的由来
通常,我们的程序需要根据程序运行时才知道创建多少个对象。但若非程序运行,程序开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不知道它的准确类型。为了满足这些常规的编程需要,我们要求能在任何时候,任何地点创建任意数量的对象,而这些对象用什么来容纳呢?首先我们前面讲解了数组,但是数组一方面只能存放同一类型的数据,另一方面其长度是固定的,显然不满足前面的需求,所以集合便应运而生了!
集合是什么?
Java集合类存放于 java.util 包中,是一个用来存放对象的容器。
①、集合只能存放对象。比如你存一个 int 型数据 1放入集合中,其实它是自动转换成 Integer 类后存入的,Java中每一种基本类型都有对应的引用类型。
②、集合存放的是多个对象的引用,对象本身还是放在堆内存中。
③、集合可以存放不同类型,但我们常用类型来限制,类型参数化,即泛型,不限数量的数据类型。
Java 集合框架图
从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
①Iterator:迭代器
它是Java集合的顶层接口(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口)
Object next():返回迭代器刚越过的元素的引用,返回值是 Object,需要强制转换成自己需要的类型
boolean hasNext():判断容器内是否还有可供访问的元素
void remove():删除迭代器刚越过的元素
所以除了 map 系列的集合,我们都能通过迭代器来对集合中的元素进行遍历。
注意:我们可以在源码中追溯到集合的顶层接口,比如 Collection 接口,可以看到它继承的是类 Iterable
那这就得说明一下 Iterator 和 Iterable 的区别:
Iterable :存在于 java.lang 包中。我们可以看到,里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器了。
Iterator :存在于 java.util 包中。核心的方法next(),hasnext(),remove()。
这里我们引用一个Iterator 的实现类 ArrayList 来看一下迭代器的使用:
public class CollctionTest {
public static void main(String[] args) {
Collection collection=new ArrayList();
collection.add("张三");
collection.add("熊少文");
collection.add("黄少华");
Iterator iterator=collection.iterator();
while(iterator.hasNext()) {
String str =(String)iterator.next();
System.out.println(str);
}
}
}
----------------------------
张三
熊少文
黄少华
②Collection:List 接口和 Set 接口的父接口
看一下 上面迭代器中用了Collection 集合的使用例子:
我们这里将 ArrayList集合作为 Collection 的实现类,它有如下方法
1.boolean isEmpty(); 判 断集合是否为空
2.boolean remove(Object obj)
3.boolean removeAll(Collection c);
4.boolean contains(Object obj)
5.add(Object obj)
public class CollctionTest {
public static void main(String[] args) {
Collection collection=new ArrayList();
collection.add("张三");
collection.add("熊少文");
collection.add("黄少华");
collection.remove("张三");
System.out.println("张三是否存在:"+collection.contains("张三"));
Iterator iterator=collection.iterator();
//while遍历
while(iterator.hasNext()) {
String str =(String)iterator.next();
System.out.println(str);
}
System.out.println();
//for遍历
for(Object obj:collection) {
System.out.println(obj.toString());
}
}
}
---------------------------------------------------------------
张三是否存在:false
熊少文
黄少华
熊少文
黄少华
③List :有序,可以重复的集合。
List 接口的三个典型实现:
①、List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢; 线程不安全,效率高,最常用的List接口实现。
②、List list2 = new Vector();
底层数据结构是数组,查询快,增删慢; 线程安全,效率低,几乎已经淘汰了这个集合
③、List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快; 线程不安全,效率高
List 接口遍历还可以使用普通 for 循环进行遍历,指定位置添加元素,替换元素等等。
List常用的方法:除了上面的5个,还有如下
① list.add(2, Object obj);在指定地方添加元素,2是索引,obj是要插入的值
② list.set(2, Object obj); 在指定地方替换元素,2是索引,obj是要替换原目标的值
③ list.indexOf(Object obj); 获得指定对象的索引
//List接口应用
List list=new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.set(1,10); //集合中第一个元素值换成10了
System.out.println("10这个值的元素,是第"+list.indexOf(10)+"个元素");
}
--------------------------------------------------------------------
10这个值的元素,是第1个元素
④Set:典型实现 HashSet()是一个无序,不可重复的
1、Set hashSet = new HashSet();
①、HashSet:不能保证元素的顺序;不可重复;不是线程安全的;集合元素可以为 NULL;
②、其底层其实是一个数组,存在的意义是加快查询速度。我们知道在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较,此时的查询效率依赖于查找过程中比较的次数。而 HashSet 集合底层数组的索引和值有一个确定的关系:index=hash(value),那么只需要调用这个公式,就能快速的找到元素或者索引。
③、对于 HashSet: 如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
1、如果 hashCode 值和现存的对象的都不同,直接把该元素存储到 hashCode() 指定的位置
2、如果 hashCode 值和现存的对象中某一个对象的相同,那么会继续判断该元素和集合对象的 equals() 作比较
2.1、hashCode 相同,equals 为 true,则视为同一个对象,不保存在 hashSet()中
2.2、hashCode 相同,equals 为 false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该约束这种情况,即保证:如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。(着也是前面强调重写equals方法一定要重写hashCode方法的原因)
注意: 每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象
对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
2、Set linkSet = new LinkedHashSet();
3、Set treeSet = new TreeSet();
TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。
-
如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口,所以, 在其中不能放入 null 元素
-
必须放入同样类的对象.(默认会进行排序) 否则可能会发生类型转换异常.我们可以使用泛型来进行限制
Set treeSet = new TreeSet();
treeSet.add(1); //添加一个 Integer 类型的数据
treeSet.add(“a”); //添加一个 String 类型的数据
System.out.println(treeSet); //会报类型转换异常的错误
⑤Map:key 不允许重复,value 可以
- 严格来说 Map 并不是一个集合,而是两个集合之间 的映射关系。
- 这两个集合每一条数据通过映射关系,我们可以看成是一条数据。即 Entry(key,value)。Map 可以看成是由多个 Entry 组成。
- 因为 Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 foreach 遍历。
Map 的常用方法:
public class MapTest {
public static void main(String[] args) {
Map hashmap = new HashMap();
//add
hashmap.put("key1", "value1");
hashmap.put("key2", "value2");
hashmap.put("key3", "value3");
hashmap.put("key4", "value4");
hashmap.put("key5", "value5");
hashmap.put("key6", "value6");
//delete
hashmap.remove("key5");
//get value
System.out.println(hashmap.get("key1"));
//通过添加,改变值
hashmap.put("key2", "value22");
//通过 map.values() 方法得到 Map 中的 value 集合
Collection values=hashmap.values();
for(Object obj:values) {
System.out.println(obj);
}
//通过keySet()拿到键集合,整合get方法又可拿到键值
Set keyset= hashmap.keySet();
for(Object obj:keyset) {
System.out.println("key: "+obj+" "+"value:"+" "+hashmap.get(obj));
}
//Entry关系映射类,通过它可以拿到,键值
Set<Map.Entry<String,String>> entryset = hashmap.entrySet();
for(Map.Entry<String,String> entry:entryset) {
System.out.println("key:"+entry.getKey()+" "+"value:"+entry.getValue());
}
}
}
-------------------------------------------------------------------------------
value1
value1
value22
value6
value3
value4
key: key1 value: value1
key: key2 value: value22
key: key6 value: value6
key: key3 value: value3
key: key4 value: value4
key:key1 value:value1
key:key2 value:value22
key:key6 value:value6
key:key3 value:value3
key:key4 value:value4
Map 和 Set 集合的关系
1、都有几个类型的集合。HashMap 和 HashSet ,都采 哈希表算法;TreeMap 和 TreeSet 都采用 红-黑树算法;LinkedHashMap 和 LinkedHashSet 都采用 哈希表算法和红-黑树算法。
2、分析 Set 的底层源码,我们可以看到,Set 集合 就是 由 Map 集合的 Key 组成。
泛型
概述
泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。
什么是泛型?为什么要使用泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),
然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参的类型,从而限制类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
举例说明
/*
* 集合不像数组那样,只能放一种数据类型,所以我们这里这样测试
* 1.先不类型参数化,不限制类型,我即往集合中加了对象,也加入基本数据类型
* 当我们遍历所有元素时,编译不会报错,但运行时会报错,类型转换异常
*/
public class ListShuoMingTest {
public static void main(String[] args) {
List list=new ArrayList();
list.add("abc");
list.add(100);
list.add("熊少文");
for(int i=0;i<list.size();i++) {
String string=(String)list.get(i);
System.out.println(string);
}
}
}
------------------------------------------------------------------------------------
abc
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to.....
ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
List<String> arrayList = new ArrayList<String>();
arrayList.add(100); 在编译阶段,编译器就会报错
public class ListShuoMingTest {
public static void main(String[] args) {
/*List list=new ArrayList();
list.add("abc");
list.add(100);
list.add("熊少文");
for(int i=0;i<list.size();i++) {
String string=(String)list.get(i);
System.out.println(string);
}*/
//加了类型限制,泛型类
List<String> list=new ArrayList<String>();
list.add("abc");
//list.add(100); //编译就会出错
list.add("熊少文");
for(int i=0;i<list.size();i++) {
String string=(String)list.get(i);
System.out.println(string);
}
}
}
------------------------------------------------------------------------
abc
熊少文
特性
泛型只在编译阶段有效。看下面的代码:
泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以限制类对外开放的接口类型。最典型的就是各种容器类,如:ArrayList、HashSet、HashMap。
泛型类的最基本写法:
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{ // T E
private 泛型标识 /*(成员变量类型)*/ var;
.....
}
}
一个最普通的泛型类:
/*
* 一个最普通的泛型类,T 是一个类型形参,传实参要一实例化时传输
*/
public class Generic<T> {
private T name;
public Generic(T name) {
this.name=name;
}
public T getName() {
return name;
}
}
实例化泛型类测试
public class GenericClassTest {
public static void main(String[] args) {
Generic<String> generic=new Generic<String>("abc");
//实例泛型类,不一定要传实参
Generic generic2=new Generic("xiong");
Generic generic3=new Generic(100);
Generic generic4=new Generic(12.36);
System.out.println(generic.getName());
System.out.println(generic2.getName());
System.out.println(generic3.getName());
System.out.println(generic4.getName());
}
}
--------------------------------------------------------------------
abc
xiong
100
12.36
泛型的类型参数只能是类类型(包括自定义类),不能是基本数据类型
泛型的类型参数只能是类类型,不能是基本数据类型。
不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
泛型接口
泛型接口与泛型类的定义及使用基本相同。
public interface Genericinterface<T> {
T next();
}
实现类有三种情况
/*public class FruitGenericImpl<T> implements Genericinterface<T>{
@Override
public T next() {
return null;
}
}*/
/*public class FruitGenericImpl implements Genericinterface{
@Override
public Object next() {
return null;
}
}*/
public class FruitGenericImpl implements Genericinterface<String>{
@Override
public String next() {
return "xiongshaowen";
}
}
测试
FruitGenericImpl fruit=new FruitGenericImpl();
System.out.println(fruit.next());
-------------------------------------------------------
xiongshaowen
泛型通配符 '?'表示泛型实参
我们知道Ingeter是Number的一个子类,我们也验证过Generic与Generic实际上是相同的一种基本类型。那么问题来了,在使用Generic作为形参的方法中,能否使用Generic的实例传入呢?在逻辑上类似于Generic和Generic是否可以看成具有父子关系的泛型类型呢?
为了弄清楚这个问题,我们使用Generic这个泛型类继续看下面的例子:
public class GenericClassTest {
public static void main(String[] args) {
//泛型通配符-?
Generic<Number> g1=new Generic<Number>(123);
Generic<Integer> g2 = new Generic<Integer>(321);
show(g1);
show(g2);
show2(g1);
//show2(g2); //出异常,即两个版本的泛型不能兼容,即使是父子类关系。
show3(g1);
show3(g2);
}
public static void show(Generic<?> generic) {
System.out.println("generic "+generic.getName());
}
public static void show2(Generic<Number> generic) {
System.out.println("generic "+generic.getName());
}
public static void show3(Generic generic) {
System.out.println("generic "+generic.getName());
}
}
------------------------------------------------------------------------------
generic 123
generic 321
generic 123
generic 123
generic 321
通过提示信息我们可以看到Generic不能被看作为`Generic的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic类型的类,这显然与java中的多态理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic和Generic父类的引用类型。由此类型通配符应运而生。
我们可以将上面的方法改一下:
public void showKeyValue1(Generic<?> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
类型通配符一般是使用 ?代替具体的类型实参,注意了,此处’?’是类型实参
,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参 ! 此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把’?'看成所有类型的父类。是一种真实的类型。可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
泛型方法,调用它像极了用了通配符’?’
在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。
尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,泛型方法和泛型类是没有必然的关系的,初学者非常容易将泛型方法和泛型类里的使用了泛型的成员方法混为一谈。
泛型方法可以定义在泛型类中,也可以定义在普通类中,不要搞混了
泛型方法的基本介绍
说明:
1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
public <T> T showName(Generic<T> generic){
System.out.println("generic key :" + generic.getName());
T test = generic.getName();
return test;
}
例:
public class GenericClassTest {
public static void main(String[] args) {
Generic<Number> g1=new Generic<Number>(123);
Generic<Integer> g2 = new Generic<Integer>(321);
//调用泛型方法
showName(g1);
showNmae(g2);
}
//泛型方法
public static <T> T showName(Generic<T> generic){
System.out.println("generic name :" + generic.getName());
T test = generic.getName();
return test;
}
}
-------------------------------------------------------------------
generic name :123
generic name :321
例:下面我定义了Apple,Persion,Generic,测类四个类。Generic是泛型类,定义了三个方法,后两个是泛型方法,其功能是一样的。
总结:1:区分清楚泛型方法和泛型类中使用了泛型的普通方法;2:在你能够做到的前提,能使用泛型方法解决问题的,就尽量用泛型方法,不必使用泛型类
泛型方法与可变参数
静态方法与泛型
静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public class StaticGenerator<T> {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
泛型上下边界即通配符?的上下界
<? extends T>和<? super T>是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。
<? extends T>:是指 “上界通配符(Upper Bounds Wildcards)”
<? super T>:是指 “下界通配符(Lower Bounds Wildcards)”
为什么要用通配符和边界?
使用泛型的过程中,经常出现一种很别扭的情况。比如我们有Fruit类,和它的派生类Apple类,类体都是空的。
public class Fruit {}
public class Apple extends Fruit {}
然后有一个最简单的容器:Plate类。盘子里可以放一个泛型的“东西”。我们可以对这个东西做最简单的“放”和“取”的动作:set( )和get( )方法。
public class Plate<T>{
private T item;
public Plate(T t){
item=t;
}
public void set(T t){
item=t;
}
public T get(){
return item;
}
}
现在我定义一个“水果盘子”,逻辑上水果盘子当然可以装苹果。
Plate<Fruit> p = new Plate<Apple>(new Apple());
Java编译器不允许这个操作。会报错,“装苹果的盘子”无法转换成“装水果的盘子”。不同版本的泛型类实例是不兼容的
当然了,用了通配符没问题
Plate<?> p = new Plate<Apple>(new Apple());
编译器脑袋里认定的逻辑是这样的:
苹果 IS-A 水果
装苹果的盘子 NOT-IS-A 装水果的盘子
为了让泛型用起来更舒服,Oracle的大脑袋们就想出了<? extends T>和<? super T>的办法,来让”水果盘子“和”苹果盘子“之间发生关系。
什么是上界?
Plate<? extends Fruit>
翻译成人话就是:一个能放水果以及一切是水果派生类的盘子。再直白点就是:啥水果都能放的盘子。这和我们人类的逻辑就比较接近了。
Plate<? extends Fruit> p = new Plate<Apple>(new Apple());
把水果体系扩大一下:
上界通配符 Plate<? extends Fruit> 覆盖下图中蓝色的区域。
什么是下界?
Plate<? super Fruit> p = new Plate<Food>(new Food());
上下界通配符的副作用
边界让Java不同泛型之间的转换更容易了。但不要忘记,这样的转换也有一定的副作用。那就是容器的部分功能可能失效。
还是以刚才的Plate为例。我们可以对盘子做两件事,往盘子里set()新东西,以及从盘子里get()东西。
上界<? extends T>不能往里存,只能往外取
<? extends Fruit>会使往盘子里放东西的set( )方法失效。但取东西get( )方法还有效。
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
//不能存入任何元素
p.set(new Fruit()); //Error
p.set(new Apple()); //Error
//读取出来的东西只能存放在Fruit或它的基类里。
Fruit newFruit1=p.get();
Object newFruit2=p.get();
Apple newFruit3=p.get(); //Error
原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。可能是Fruit?可能是Apple?也可能是Banana,RedApple,GreenApple?编译器在看到后面用Plate赋值以后,盘子里没有被标上有“苹果”。而是标上一个占位符:? ,来表示捕获一个Fruit或Fruit的子类,具体是什么类不知道,代号? 。然后无论是想往里插入Apple或者Meat或者Fruit编译器都不知道能不能和这个? 匹配,所以就都不允许`
下界<? super T>不影响往里存,但往外取只能放在Object对象里
使用下界<? super Fruit>会使从盘子里取东西的get( )方法部分失效,只能取出成到Object对象里。set( )方法正常。
使用下界<? super Fruit>会使从盘子里取东西的get( )方法部分失效,只能存放到Object对象里。set( )方法正常。
Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
//存入元素正常
p.set(new Fruit());
p.set(new Apple());
//读取出来的东西只能存放在Object类里。
Apple newFruit3=p.get(); //Error
Fruit newFruit1=p.get(); //Error
Object newFruit2=p.get();
因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Fruit的基类,那往里存粒度比Fruit小的都可以。但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。
结论性的原则:
频繁往外读取内容的,适合用上界Extends。
经常往里插入的,适合用下界Super。
关于泛型数组说明
在java中是”不能创建一个确切的泛型类型的数组”的。
也就是说下面的这个例子是不可以的:
List<String>[] ls = new List<String>[10];
而使用通配符创建泛型数组是可以的,如下面这个例子:
List<?>[] ls = new ArrayList<?>[10];
这样也是可以的:
List<String>[] ls = new ArrayList[10];
下面使用一个例子来说明这个问题:
List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,
但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,
上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。
而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK
最后
这部分讲的例子主要是为了阐述泛型中的一些思想而简单举出的,并不一定有着实际的可用性,在实际的编程过程中,自己可以使用泛型去简化开发,且能很好的保证代码质量。
注解
注解(Annotation)很重要
,现在的开发模式都是基于注解的,JPA是基于注解的,从Spring基于注解的,从Hibernate也是基于注解的,注解是JDK1.5之后才有的新特性
JDK1.5之后内部提供的三个注解
@Deprecated 意思是“废弃的,过时的”,不过并不表示不能用。
@Override 意思是“重写、覆盖”,只是提示作用,警示方法名写错作用。
@SuppressWarnings 意思是“压缩警告”,作用:用于抑制编译器产生警告信息。
例:过时标记注解打上后,函数名上有中划线
总结:
注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,没有加,则等于没有任何标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。
注解就相当于一个你的源程序要调用一个类,在源程序中应用某个注解,得事先准备好这个注解类。就像你要调用某个类,得事先开发好这个类。
自定义注解及其应用
自定义一个最简单的注解:
/**
* 这是一个自定义的注解(Annotation)类 在定义注解(Annotation)类时使用了另一个注解类Retention
* 在注解类上使用另一个注解类,那么被使用的注解类就称为元注解*/
@Retention(RetentionPolicy.RUNTIME)
//Retention注解决定MyAnnotation注解的生命周期
/*
* @Retention(RetentionPolicy.SOURCE)
* 这个注解的意思是让MyAnnotation注解只在java源文件中存在,编译成.class文件后注解就不存在了
* @Retention(RetentionPolicy.CLASS)
* 这个注解的意思是让MyAnnotation注解在java源文件(.java文件)中存在,编译成.class文件后注解也还存在,
* 被MyAnnotation注解类标识的类被类加载器加载到内存中后MyAnnotation注解就不存在了
* Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让MyAnnotation这个注解的生命周期一直到程序运行时都存在
*/
@Target( { ElementType.METHOD, ElementType.TYPE })
//Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
public @interface MyAnnotation {
}
把自定义的注解加到某个类上:
上面的注解定义的生命周期在整个执行时候,可以加在类上,和类的方法上。
@MyAnnotation
public class Test {
public static void main(String[] args) {
xiong();
}
@MyAnnotation
public static void xiong() {
System.out.println("xiongshaowen");
}
}
利用反射机制和注解来做一些业务逻辑上的事,这里只简单展示一下
@MyAnnotation
public class Test {
public static void main(String[] args) {
if(Test.class.isAnnotationPresent(MyAnnotation.class)) {
System.out.println("有注解!");
//拿到注解类对象
MyAnnotation annotation=Test.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
//.....
}else {
System.out.println("没有注解!");
//....
}
}
@MyAnnotation
public static void xiong() {
System.out.println("xiongshaowen");
}
}
----------------------------------------------------------------------------------
有注解!
@cn.ybzy.javabasic.annotation.MyAnnotation()
为注解增加属性
注解可以看成是一种特殊的类,既然是类,那自然可以为类添加属性
添加属性
语法:类型 属性名();
MyAnnotation.java注解类
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE })
public @interface MyAnnotation {
//自定义普通属性,使用注解时,要赋值,有default,就可不用赋值
String color() default "blue";
String value() default "xiong";
}
Test.java测试类
@MyAnnotation(color="red",value="xiong") //@MyAnnotation("red") //这种写法是只有一个属性,且名字为value的情况下
public class Test {
public static void main(String[] args) {
if(Test.class.isAnnotationPresent(MyAnnotation.class)) {
System.out.println("有注解!");
MyAnnotation annotation=Test.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
System.out.println("注解类属性值:"+annotation.color());
}else {
System.out.println("没有注解!");
}
xiong();
}
@MyAnnotation //有default,不用设置color,value属性值
public static void xiong() {
System.out.println("xiongshaowen");
}
}
------------------------------------------------------------------------
有注解!
@cn.ybzy.javabasic.annotation.MyAnnotation(color=red, value=xiong)
注解类属性值:red和xiong
xiongshaowen
为注解增加高级属性
1、数组类型的属性
增加数组类型的属性:int[] arrayAttr() default {1,2,4};
应用数组类型的属性:@MyAnnotation(arrayAttr={2,4,5})
如果数组属性只有一个值,这时候属性值部分可以省略大括号,如:@MyAnnotation(arrayAttr=2),这就表示数组属性只有一个值,值为2
2、枚举类型的属性
增加枚举类型的属性:EumTrafficLamp lamp() default EumTrafficLamp.RED;
应用枚举类型的属性:@MyAnnotation(lamp=EumTrafficLamp.GREEN)
3、注解的属性值又是一个注解
MetaAnnotation annotationAttr() default @MetaAnnotation("xdp");
综合应用示例:枚举类型 TrafficLamp.java,元注解类 MetaAnnotation
//枚举类 TrafficLamp
public enum TrafficLamp{
RED,
YELLOW,
GREEN
}
//注解类 MyAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE })
public @interface MyAnnotation {
//自定义普通属性,使用注解时,要赋值
String color() default "blue";
String value() default "xiong";
String[] values() default {"xiong","xiong","xiong"};
TrafficLamp lamp() default TrafficLamp.RED;
MetaAnnotation annotationAttr() default @MetaAnnotation("xdp");
}
//元注解类 MetaAnnotation
public @interface MetaAnnotation {
String value(); //元注解,设置有一个唯一的属性value
}
//测试类 Test
@MyAnnotation(color="red",value="xiong",values= {"xiongshaowen","xuhuifeng","lingjing"},
lamp=TrafficLamp.GREEN,
annotationAttr=@MetaAnnotation("gacl"))
public class Test {
public static void main(String[] args) {
if(Test.class.isAnnotationPresent(MyAnnotation.class)) {
System.out.println("有注解!");
MyAnnotation annotation=Test.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
System.out.println("注解类属性color,value值:"+annotation.color()+"和"+annotation.value());
System.out.println("注解类枚举属性值:"+annotation.lamp());
MetaAnnotation ma =annotation.annotationAttr();
System.out.println("元注解属性值:"+ma.value());
}else {
System.out.println("没有注解!");
}
xiong();
}
@MyAnnotation //有default,不用设置color,value...属性值
public static void xiong() {
System.out.println("xiongshaowen");
}
}
异常处理
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止?还是输出错误给用户?
Java提供了更加优秀的解决办法:异常处理机制。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常
。
Java中的异常类结构图,我们只关注Exception分支
错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
总体上我们根据Javac对异常的处理要求,将异常类分为2类。
1非检查异常(unckecked exception)
:Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
2检查异常(checked exception)
:除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。
需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。
例:IO异常,下面我们把一个不存在的文件,转为文件输入流,会报红,提示有可能发生文件找不到异常
/*
* IO异常,一般是检查异常,即编译时会报红,不用运行产生,我们可用try catch,或throws抛给方法上
*/
public class TryCatchTest {
public static void main(String[] args) {
try {
FileInputStream fis =new FileInputStream("C:\\xiong\\aa.txt");
} catch (FileNotFoundException e) {
//e.printStackTrace(); //打印层级调用爱到影响的栈列信息
System.out.println(e.getMessage());
}
}
}
//或,throws抛出到方法上
/*public class TryCatchTest {
public static void main(String[] args) throws FileNotFoundException {
FileInputStream fis =new FileInputStream("C:\\xiong\\aa.txt");
}
*/
---------------------------------------------------------------------
C:\xiong\aa.txt (系统找不到指定的路径。)
异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。
异常最先发生的地方,叫做异常抛出点。
try…catch…finally语句块
try{
//try块中放可能发生异常的代码。
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的 话)。
//如果发生异常,则尝试去匹配catch块。
}catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}catch(Exception exception){
//...
}finally{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
注意
- try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
- 每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。
- java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去,所有有多个catch块时,异常范围是从小到大设置(如:FileNotFoundException–Exception)。
throws 函数声明
throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
throw 异常抛出语句
throw exceptionObject
程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
public void save(User user)
{
if(user == null)
throw new IllegalArgumentException("User对象为空");
//......
}
-------------------
throw常用在登陆注册用户时,给出错误信息,当用户名输入错误时,我们这样干。