java基础笔记
java
==与equals的区别
类和对象
构造方法
用来创建对象的方法
特征:
1.方法名和类名完全相同
2.构造方法没有返回类型
3.在默认情况下,不写任何构造方法时,系统会默认给一个无参的构造方法
4.当写了有参的构造方法时,系统默认无参的构造方法失效,如果想用,需要显示声明
调用时机:
作用:
在实例化对象时,直接为其赋值
方法重载
条件:
1.在同一作用域下(同一类中)
2.方法名相同,参数列表不同(顺序,个数,类型)
3.与返回值无关
this 关键字是 java常用的关键字,可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用
this.属性名
大部分时候,普通方法访问其他方法、成员变量时无须使用 this 前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用 this 前缀
this.方法名
this 关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。
this( )访问构造方法
this( ) 用来访问本类的构造方法(构造方法是类的一种特殊方法,方法名称和类名相同,没有返回值。详细了解可参考《Java构造方法》一节),括号中可以有参数,如果有参数就是调用指定的有参构造方法
注意:
-
this( ) 不能在普通方法中使用,只能写在构造方法中。
-
在构造方法中使用时,必须是第一条语句。
super
1.出现在子类的构造方法中,实现调用父类的构造方法,且只能出现在第一行.若不写super()或者写了括号里啥也不填就和没写一样,则默认调用父类的无参构造方法-
2.出现在普通方法里时,实现调用父类的方法 格式super.方法名
1. super用于引用直接父类实例变量
可以使用super关键字来访问父类的数据成员或字段。 如果父类和子类具有相同的字段,则使用super来指定为父类数据成员或字段
2. 通过 super 来调用父类方法
super关键字也可以用于调用父类方法。 如果子类包含与父类相同的方法,则应使用super关键字指定父类的方法。 换句话说,如果方法被覆盖就可以使用 super 关键字来指定父类方法。
3. 使用 super 来调用父类构造函数
super关键字也可以用于调用父类构造函数。下面来看一个简单的例子
方法重写(方法覆盖)
//头大,身子一样,尾巴小
1.发生在父子关系上;(头大)
2.子类访问权限要大(子类的访问权限不能比父类更严格); (身子一样)
3.子类方法的返回值 方法名 参数列表和父类一致;(身子一样),仅当返回值为类类型的时候,才可以:子类返回值[层次] <= 父类返回值[层次]
4.子类不能比父类抛出更多的异常;(尾巴小)
方法重写(方法覆盖)
//头大,身子一样,尾巴小
1.发生在父子关系上;(头大)
2.子类访问权限要大(子类的访问权限不能比父类更严格); (身子一样)
3.子类方法的返回值 方法名 参数列表和父类一致;(身子一样),仅当返回值为类类型的时候,才可以:子类返回值[层次] <= 父类返回值[层次]
4.子类不能比父类抛出更多的异常;(尾巴小)
静态方法
1.静态方法只能操作静态变量,不能操作成员变量
2.静态方法不能使用this关键字
3.成员方法可以操作静态变量
静态块:没有方法名的方法,jvm(java虚拟机)加载类时调用,只会调用一次 //一般用于位静态变量赋初值
封装
//解决了数据的安全性问题
继承
//解决了代码重用的问题
//子类调用父类私有化的属性
1.继承是单向的 父类的东西子类可用 子类的东西父类不可用
2.继承是单一的,只能单继承,父类只能有一个,父类的父类子类也可以用
3.实例化子类时,会依次向上实例化,默认情况,都是调用父类的无参构造方法
//解决了程序扩展的问题
同一个类中,调用同一个方法,产生不同结果,叫多态
条件:1.必须有继承,2、必须重写,子类对象指向父类引用
/*
* 面向对象之三:多态性
*
* 1.理解多态性:可以理解为一个事物的多种态性。
* 2.何为多态性:
* 对象的多态性:父类的引用指向子类的对象(或子类的对象赋值给父类的引用)
*
* 3.多态的使用:虚拟方法调用
* 有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法
* 简称:编译时,看左边;运行时,看右边。
*
* 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
* 多态情况下,
* “看左边”:看的是父类的引用(父类中不具备子类特有的方法)
* “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
*
* 4.多态性的使用前提:
* ① 类的继承关系
* ② 方法的重写
* 5.对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)
*/
编译期类型(父类的) 对象名 = new 运行期类型(子类的)
编译时调用父类的方法 ,实际运行是子类的方法
抽象
抽象方法: abstract修饰的方 法为抽象方法,特征:没有方法体;
抽象方法和抽象类的关系: 1.有抽象方法的类,必须是抽象类; 2.抽象类不一定有抽象方法;
如果一个类继承了一个抽象类, 要么需要实现所有的抽象方法,要么该类也得是抽象类 抽象方法的意义: 在于强制要求子类必须实现某些功能 抽象类不能被new 实例化
类和类之间的三种关系
1.继承:父子关系:英文单词 ;is a(java老师是不是一个老师)
2.依赖:一个类是另一个类的方法的参数;英文单词: use a(使用一辆公交车回家)
3.关联:一个类是另一类的属性;英文单词 : has a(我有一个xx东西)
接口
1.接口:interface(相当于父类),implements(用它来继承)实现接口的关键字
2.接口的方法默认是public abstract的
3.接口可以多重实现
4.接口中只能定义静态常量
5.类与类之间单一继承,接口与接口之间多继承
6.jdk1.8之后,接口可以写静态方法和default方法(带方法体)
7.jdk1.8后,一个类实现多个接口,切磨能量个或者两个以上的接口有相同名字的default方法,则实现类需要重写该方法
final常量
1.final修饰常量 值不能被修改,一般常量全部大写,要赋值
2.final修饰的方法不能被重写,final不可以修饰构造方法
-
final修饰类时,被修饰的类不能被继承;
-
finale修饰变量时,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
-
补充知识点:
final意义:最终的,不可改变的。
-
1、修饰变量,为常量,值不可变;
-
2、修饰对象,值可变,引用不变;
-
3、修饰方法,方法不可重写;
-
4、修饰类,无子类,不可以被继承,更不可能被重写
-

java.util
date
Date类对象用来表示时间和日期; 该类提供一系列操纵日期和时间各组成部分的方法; Date类最多的用途是获取系统当前的日期和时间
Date类的构造方法有6种重载方式,以下是比较常用的几种
| 构 造 方 法 | 说 明 |
|---|---|
| Date() | 使用系统当前时间创建日期对象 |
| Date(long date) | 使用自**1970年1月1日以后的指定毫秒数创建日期对象** |
| Date(int year, int month, int date) | 创建指定年、月、日的日期对象 |
| Date(int year, int month, int date,**int hrs, int min, int sec)** |
Date类示例
public class DateDemo { public static void main(String[] args) { Date date = new Date(); //获得当前的系统日期和时间 System.out.println("今天的日期是:" + date); long time = date.getTime(); //获得毫秒数 System.out.println("自1970年1月1日起以毫秒为单位的时间(GMT):" + time); //截取字符串中表示时间的部分 String strDate = date.toString(); String strTime = strDate.substring(11, (strDate.length() - 4)); System.out.println(strTime); strTime = strTime.substring(0, 8); System.out.println(strTime); } }
calendar
Calendar类也是用来操作日期和时间的类,但它可以以整数形式检索类似于年、月、日之类的信息; Calendar类是抽象类,无法实例化,要得到该类对象只能通过调用getInstance方法来获得; Calendar对象提供为特定语言或日历样式实现日期格式化所需的所有时间字段。
public class CalendarDemo { public static void main(String[] args) { //创建包含有当前系统时间的Calendar对象 Calendar cal = Calendar.getInstance();
| 说 明 | |
|---|---|
| Calendar getInstance() | 返回默认地区和时区的**Calendar对象** |
| int get(int fields) | 返回调用对象中**fields指定部分的值** |
| void set(int fields, int value) | 将**value中指定的值设置到fields指定的部分** |
| void add(int fields, int amount) | 将**amount值添加到fields指定的时间或日期部分** |
| Date getTime() | 返回与调用对象具有相同时间的**Date对象** |
| Object clone() | 返回调用对象的副本 |
| void clear() | 清除当前对象中所有的时间组成部分 |
| boolean after(Object obj) | 如果调用对象时间在**obj之后,返回true** |
| boolean before(Object obj) | 如果调用对象时间在**obj之前,返回true** |
| boolean equals(Object obj) | 判断调用对象与**obj |
//打印Calendar对象的各个组成部分的值 System.out.print("当前系统时间:"); System.out.print(cal.get(Calendar.YEAR) + "年"); System.out.print((cal.get(Calendar.MONTH) + 1) + "月"); System.out.print(cal.get(Calendar.DATE) + "日 "); System.out.print(cal.get(Calendar.HOUR) + ":"); System.out.print(cal.get(Calendar.MINUTE) + ":"); System.out.println(cal.get(Calendar.SECOND)); //将当前时间添加30分钟,然后显示日期和时间 cal.add(Calendar.MINUTE, 30); Date date = cal.getTime(); System.out.println("将当前时间添加30分钟后的时间:" + date); } }
日期格式化
在显示日期的时候,可以使用很多格式,这时我们就可以借助java.text包中的DateFormat类及其子类进行调整
利用格式字符串进行调整
利用预设格式进行调整
yyyy--年
MM--月
dd--日
HH--时
mm--分
ss--秒
w--周(一年52周)
示例演示
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = formatter.format(date); System.out.println(dateString);
random
Random类专门用来生成随机数; 该类的构造方法有2种重载方式。
| 构 造 方 法 | 说 明 |
|---|---|
| Random() | 直接创建一个**Random类对象** |
| Random(long seed) | 使用**seed作为随机种子创建一个Random类对象** |
| 方 法 原 型 | 说 明 |
|---|---|
| int nextInt() | 从随机数生成器返回下一个整型值 |
| long nextLong() | 从随机数生成器返回下一个长整型值 |
| float nextFloat() | 从随机数生成器返回**0.0到1.0之间的下一个浮点值** |
| double nextDouble() | 从随机数生成器返回**0.0到1.0之间的下一个双精度值** |
| double nextGaussian() | 从随机数生成器返回下一个高斯分布的双精度值。中间值为**0.0,而标准差为1.0** |
Random类示例
public class RandomDemo { public static void main(String[] args) { //创建一个Random类对象 Random rand = new Random(); //随机生成20个随机整数,并将其显示出来 for (int i = 0; i < 20; i++) { int num = rand.nextInt(); System.out.println("第" + (i + 1) + "个随机数是:" + num); } } }
集合是将多个元素组成一个单元的对象;
类似于数组,但数组最大的缺点是:长度受到限制(一经创建,就不可再改变),并且只能存放相同数据类型的元素;
集合的长度没有限制,可以存放任意多的元素,而且元素的数据类型也可以不同;
集合还提供一系列操纵数据的方法,如存储、检索等等
ArrayList
ArrayList线程不同步,vector线程同步(一个进入上锁,结束才可以下一个)
ArrayList是长度可变的对象引用数组,称为动态数组; 随着元素的添加,元素数目的增大,数组容量也会随之自动扩展; 访问和遍历数组元素时,ArrayList的性能优越; ArrayList类继承了AbstractList类并实现了List接口
List是一个接口,ArrayList继承于List接口
List<String> list = new ArrayList<String>(); String[] arr = {"aaa","bbb","ccc","aaa","ccc","ddd"}; list=Arrays.asList(arr);
| 构 造 方 法 | 说 明 |
|---|---|
| ArrayList() | 创建一个空的**ArrayList对象** |
| ArrayList(Collection c) | 根据指定的集合创建**ArrayList对象** |
| ArrayList(int initialCapacity ) | 使用给定的大小创建**ArrayList对象** |
| int size() | 返回**ArrayList对象的大小,即元素的数量** |
|---|---|
| boolean isEmpty() | 判断**ArrayList对象是否为空,为空返回true,否则返回false** |
| void clear() | 清空**ArrayList对象中的所有元素** |
| boolean add(Object element) | 向**ArrayList对象中添加一个元素,该元素可以是任何类的对象** |
| Object remove(**int** index) | 从**ArrayList对象中删除指定索引位置的元素** |
| Object get(**int** index) | 返回指定索引位置的元素 |
| Object set(int index, Object elem) | 将元素**elem存放到由index指定的索引位置上** |
| int indexOf**(Object element)** | 判断**element在ArrayList对象中是否存在,存在返回对应的索引,否则返回-1** |
ArrayList类示例1
public class ArrayListDemo1 { public static void main(String[] args) { ArrayList al = new ArrayList(); //创建一个空ArrayList对象 for (int i = 0; i < 10; i++) { Integer num = new Integer(i); //创建整型包装类对象 al.add(num); //将该对象存放到ArrayList中 } System.out.println("数组中的元素:"); for (int i = 0; i < al.size(); i++) { Integer temp = (Integer)(al.get(i)); //获得ArrayList中索引为i的元素 System.out.println(temp); } System.out.println("*********************************"); al.clear(); //清空 System.out.println("数组被清空后的情况:"); System.out.println("数组长度为:" + al.size()); if (al.isEmpty()) { //判断是否为空 System.out.println("数组现在为空。"); } else { System.out.println("数组现在不为空。"); } } }
ArrayList类示例2
public class ArrayListDemo2 { public static void main(String[] args) { ArrayList al = new ArrayList(); //创建一个空的ArrayList对象 //往动态数组中添加元素 al.add("苹果"); al.add("梨子"); al.add("香蕉"); al.add("西瓜"); al.add("榴莲"); System.out.println("目前数组的长度:" + al.size()); for (int i = 0; i < al.size(); i++) { System.out.println((String)(al.get(i))); } String str = new String("西瓜"); int index = al.indexOf(str); //判断某个元素是否存在 if (index < 0) { System.out.println(str + "在数组中不存在。"); } else { System.out.println(str + "存在,索引为:" + index); } al.remove(3); //删除某个索引位置的元素 System.out.println("删除索引为3的元素后的情况:"); for (int i = 0; i < al.size(); i++) { System.out.println((String)(al.get(i))); } } }
LinkedList类
LinkedList类用于创建链表数据结构; 链表中元素的数量不受任何限制,可以随意地添加和删除; 与ArrayList相比,如果需要频繁地添加和删除元素,LinkedList的性能更加优越; LinkedList类继承了AbstractSequentialList类,并实现了List接口;
LinkedList类的构造方法有2种重载方式。
| 构 造 方 法 | 说 明 |
|---|---|
| LinkedList() | 创建一个空链表 |
| LinkedList(Collection c) | 根据指定的集合创建链表 |
| 方 法 原 型 | 说 明 |
|---|---|
| int size() | 返回链表的大小,即元素的数量 |
| boolean isEmpty() | 判断链表是否为空,为空返回**true,否则返回false** |
| void clear() | 清空链表中的所有元素,使其成为空链表 |
| boolean add(Object element) | 向链表中添加一个元素,该元素可以是任何类的对象 |
| Object remove(int index) | 从链表中删除指定索引位置的元素 |
| Object get(int index) | 返回指定索引位置的元素 |
| Object set(int index, Object elem) | 将元素**elem存放到由index指定的索引位置上** |
| int indexOf(Object element) | 判断**element在链表中是否存在,存在返回对应的索引,否则返回-1** |
| 方 法 原 型 | 说 明 |
|---|---|
| void addFirst(Object element) | 将指定元素添加到链表的开始处 |
| void addLast(Object element) | 将指定元素添加到链表的结尾处 |
| Object removeFirst() | 删除链表中的第一个元素 |
| Object removeLast() | 删除链表中的最后一个元素 |
| Object getFirst() | 返回链表中的第一个元素 |
| Object getLast() | 返回链表中的最后一个元素 |
LinkedList类示例
public class LinkedListDemo { public static void main(String[] args) { LinkedList ll = new LinkedList(); //创建空的链表 for (int i = 1; i <= 10; i++) { Double temp = new Double(Math.sqrt(i)); //创建包装类对象 ll.add(temp); //将包装类对象添加到链表中 } System.out.println("链表中的元素:"); //循环打印链表中的元素 for (int i = 0; i < ll.size(); i++) { System.out.println(ll.get(i)); } System.out.println("*********************************"); ll.removeFirst(); //删除第一个元素 ll.removeLast(); //删除最后一个元素 System.out.println("删除第一个元素和最后一个元素后的链表:"); for (int i = 0; i < ll.size(); i++) { System.out.println(ll.get(i)); } } }
Vector(jdk1.0的方法,jdk1.5之后使用CopyOnWriteArrayList)
Vector类的构造方法有4种重载方式
| 构 造 方 法 | 说 明 |
|---|---|
| Vector() | 创建一个空的**Vector对象。初始容量为10,容量增量为0** |
| Vector(Collection c) | 根据指定的集合创建**Vector对象** |
| Vector(int initialCapacity) | 创建一个**Vector对象,初始容量由initialCapacity指定,容量增量为0** |
| Vector(int initialCapacity, int capacityIncrement) | 创建一个**Vector对象,初始容量由initialCapacity指定,容量增量由capacityIncrement指定** |
| 方 法 原 型 | 说 明 |
|---|---|
| int size() | 返回**Vector对象的大小,即元素的数量** |
| boolean isEmpty() | 判断**Vector对象是否为空,为空返回true,否则返回false** |
| void clear() | 清空**Vector对象中的所有元素** |
| boolean add(Object element) | 向**Vector对象中添加一个元素,该元素可以是任何类的对象** |
| Object remove(int index) | 从**Vector对象中删除指定索引位置的元素** |
| Object get(int index) | 返回指定索引位置的元素 |
| Object set(int index, Object elem) | 将元素**elem存放到由index指定的索引位置上** |
| int indexOf(Object element) | 判断**element在Vector对象中是否存在,存在返回对应的索引,否则返回-1** |
| 方 法 原 型 | 说 明 |
|---|---|
| int capacity() | 返回**Vector对象的容量,即可以存放元素的个数** |
| void addElement(Object element) | 将指定元素插入到**Vector对象的末尾处** |
| void insertElementAt(Object elem, int index) | 将指定元素插入到指定索引位置 |
| void setElementAt(Object elem, int index) | 将指定对象替换位于指定索引处的对象 |
| Object ElementAt(int index) | 检索位于指定索引处的元素 |
| boolean contains(Object elem) | 如果**Vector对象包含指定元素,返回true** |
| Object firstElement() | 返回**Vector对象中的第一个元素** |
| Object lastElement() | 返回**Vector对象中的最后一个元素** |
| void removeAllElements() | 删除**Vector对象中的所有元素** |
| void copyInto(Object[] anArray) | 将**Vector对象中的元素复制到指定数组中** |
| void setSize(int newSize) | 根据**newSize的值设置Vector对象的容量** |
Vector类示例
public class VectorDemo { public static void main(String[] args) { Vector vec = new Vector(); //创建空的Vector //往Vector中添加元素 vec.addElement("Java"); vec.addElement("C#"); vec.addElement("Oracle"); vec.addElement("C++"); vec.addElement("HTML"); System.out.println(vec.toString()); //打印Vector中的元素 vec.removeElement("C++"); //删除其中的元素 System.out.println(vec.toString()); } }
map
map是一个接口,HashMap继承于这个接口
| 构 造 方 法 | 说 明 |
|---|---|
| HashMap() | 创建一个空的**HashMap对象** |
| HashMap(Map m) | 根据指定的**Map集合创建HashMap对象** |
| HashMap(int initialCapacity) | 创建一个指定容量和默认负载系数的**HashMap对象** |
| HashMap(int initialCapacity, float loadFactor) | 创建一个指定容量和指定负载系数的**HashMap对象** |
| 方 法 原 型 | 说 明 |
|---|---|
| int size() | 返回**HashMap对象的大小,即元素的个数** |
| boolean | 判断**HashMap对象是否为空,为空返回true,否则返回false** |
| void clear() isEmpty() | 清空**HashMap对象中的所有元素** |
| Object put(Object key, Object value) | 向**HashMap对象中添加一个元素,必须指定该元素的键和值** |
| Object remove(Object key) | 通过键对象删除相对应的值对象 |
| Object get(Object key) | 通过键对象查找相对应的值对象 |
| boolean containsKey(Object key) | 查找指定的键对象在**HashMap对象中是否存在** |
| boolean containsValue(Object value) | 查找指定的值对象在**HashMap对象中是否存在** |
| map.keySet() | 查询map中的所有key |
public class MaoDemo1 { public static void main(String[] args){ Map<String,String> map = new HashMap<String,String>(); Map<Integer,String> map1 = new HashMap<Integer,String>(); map.put("野位", "橘右京"); map.put("中单", "杨玉环"); map.put("上单", "廉颇"); map.put("辅助","鬼谷子"); map.put("射手", "马可波罗"); //获得辅助的名字 System.out.println(map.get("辅助")); map1.put(1, "张三"); map1.put(2, "李四"); map1.put(3, "隔壁老王"); map1.put(2, "李信"); map1.remove(1);//删除的方法 //遍历map的方法1 对值 for(String value:map1.values()){ System.out.println(value); } //遍历map的方法2 对键 for(Integer key:map1.keySet()){ System.out.println(key+"-"+map1.get(key)); } } }
map在对象的使用
public class TestCard { public static void main(String[] args) { // TODO Auto-generated method stub Card card1 = new Card(1,"a","aa"); Card card2= new Card(2,"b","bb"); Card card3 = new Card(3,"c","cc"); Map<Integer,Card> map = new HashMap<Integer,Card>(); map.put(card1.getId(),card1); map.put(card2.getId(),card2); map.put(card3.getId(),card3); for(Card value:map.values()){//有toString方法时可以用此方法遍历输出 System.out.println(value); } /*for(Integer key:map.keySet()){ if(map.get(key).getId()==2) System.out.println(key+"号的email:"+map.get(key).getEmail()); }*/ System.out.println(map.get(2).getEmail()); map.remove(3); for(Card value:map.values()){//一览 System.out.println(value); } } }
Map遍历
Set<Map.Entry<String, Integer>> entryseSet=map.entrySet(); for (Map.Entry<String, Integer> entry:entryseSet) { System.out.println(entry.getKey()+","+entry.getValue()); }
Treemap

Set集合
Set集合的主要特点是,其中不允许出现重复的元素 Set集合类都实现了Set接口,其中典型的例子有HashSet,TreeSet类
| 方法摘要 | |
|---|---|
| boolean | add(E e) 如果 set 中尚未存在指定的元素,则添加此元素(可选操作)。 |
| boolean | addAll(Collection<? extends E> c) 如果 set 中没有指定 collection 中的所有元素,则将其添加到此 set 中(可选操作)。 |
| void | clear () 移除此 set 中的所有元素(可选操作)。 |
| boolean | contains(Object o) 如果 set 包含指定的元素,则返回 true。 |
| boolean | containsAll(Collection<?> c) 如果此 set 包含指定 collection 的所有元素,则返回 true。 |
| boolean | equals(Object o) 比较指定对象与此 set 的相等性。 |
| int | hashCode() 返回 set 的哈希码值。 |
| boolean | isEmpty() 如果 set 不包含元素,则返回 true。 |
| Iterator<E> | iterator() 返回在此 set 中的元素上进行迭代的迭代器。 |
| boolean | remove(Object o) 如果 set 中存在指定的元素,则将其移除(可选操作)。 |
| boolean | removeAll(Collection<?> c) 移除 set 中那些包含在指定 collection 中的元素(可选操作)。 |
| boolean | retainAll*(Collection<?> c) 仅保留 set 中那些包含在指定 collection 中的元素(可选操作)。 |
| int | size() 返回 set 中的元素数(其容量)。 |
| Object[] | toArray() 返回一个包含 set 中所有元素的数组。 |
| <T> T [ ] | toArray(T[] a) 返回一个包含此 set 中所有元素的数组;返回数组的运行时类型是指定数组的类型。 |
TreeSet
TreeSet 类同时实现了 Set 接口和 SortedSet 接口。. SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序, 因此使用 TreeSet 类实现的 Set 接口默认情况下是自然排序的,这里的自然排序指的是升序排序。. TreeSet 只能对实现了 Comparable 接口的类对象进行排序 ,因为 Comparable 接口中有一个 compareTo (Object o) 方法用于比较两个对象的大小。. 例如 a.compareTo (b),如果 a 和 b 相等,则该方法返回 0;如果 a 大于 b,则该方法返回大于 0 的值;如果 a 小于 b,则该方法返回小于 0 的值。
Collection与Collections
Collection是集合类的上级接口,继承与他的接口主要有Set 和List. Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作.
list 与set与map
list可以存放重复数据,有序的,可以存放空值
set不可以存放重复数据,无序的,可以存放空值(只可一个)
map是键值对形式的,key可为null,如果多个key值相同则后值覆盖前值,可以有重复的(map是无序的)
set = new HashSet<String>(list);//将list传给set
迭代器
public class Demo1 { public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("aaa"); list.add("bbb"); //获得迭代器 Iterator<String> it = list.iterator(); while(it.hasNext()){//判断是否有下一个元素 System.out.println(it.next());//获得元素 } } }
异常
| 单 词 | 说 明 |
|---|---|
| try | 监视,考验,审问**,尝试** |
| catch | 捕捉,捕获 |
| finally | 最后,终于,不可更改地 |
| throw | 抛出,扔 |
| throws | 抛出,扔 |
| exception | 异常 |
| error | 错误 |
| 异 常 | 说 明 |
|---|---|
| Exception | 异常层次结构的根类 |
| RuntimeException | 许多**java.lang异常的基类** |
| ArithmeticException | 算术异常,如:除数为**0** |
| IllegalArgumentException | 方法接收到非法参数 |
| ArrayIndexOutOfBoundsException | 数组下标越界 |
| NullPointerException | 访问空引用 |
| ClassNotFoundException | 不能加载所需的类 |
| NumberFormatException | 字符串转换数字失败 |
| IOException | I/O**异常的根类** |
catch
多重catch的原则,只能进入其中一个,粒度越大越往下放
finally{//不管是否发生异常,异常是否被处理,都会执行的代码 System.out.println(6); }
throw
出现在方法中,手动抛出异常,同一分支throw后不可写其他语句,但判断语句可以
public void setAge(int age) throws AgeException { if(age<25||age>60){ throw new AgeException(); } this.age = age; }
public class AgeException extends Exception{ public AgeException(){ super("年龄异常"); }
内部解决
public static void main(String[] args) { // TODO Auto-generated method stub Scanner scanner = new Scanner(System.in); System.out.println("请输入两个整数"); try{ int a= scanner.nextInt(); int b= scanner.nextInt(); try{ System.out.println(a/b); }catch(ArithmeticException ex){ System.out.println("除数为0"); } }catch(InputMismatchException ex){ System.out.println("格式异常"); } }
throws
声明方法时,表示向外抛出异常,调用者需要处理
如果throw和throws配合,则throw抛出的异常不能比throws声明的大,可以小,往往不这么写.
//自己创建异常
包装类
原始数据类型
数据类型和包装类对照原始表
| 原始数据类型 | 包 装 类 |
|---|---|
| boolean**(布尔型)** | Boolean |
| byte**(字节型)** | Byte |
| char**(字符型)** | Character |
| short**(短整型)** | Short |
| int**(整型)** | Integer |
| long**(长整型)** | Long |
| float**(浮点型)** | Float |
| double**(双精度浮点型)** | Double |
| 单 词 | 说 明 |
|---|---|
| language | 语言 |
| integer | 整数(可以写null) |
| character | 字符 |
| buffer | 缓冲器 |
| math | 数学 |
| parse | 转换 |
| equals | 相等 |
| compare | 比较,相比 |
| replace | 替换,取代 |
可以使用原始类型作为参数,实例化相应的包装类对象
public class LangDemo { public static void main(String[] args) { Boolean objBool = new Boolean(true); Character objChar = new Character('X'); Integer objInt = new Integer(100); Long objLong = new Long(2568); Double objDou = new Double(3.1415); System.out.println(objBool); System.out.println(objChar); System.out.println(objInt); System.out.println(objLong); System.out.println(objDou); } }
| 原始数据类型 | 包 装 类 |
|---|---|
| boolean**(布尔型)** | Boolean |
| byte**(字节型)** | Byte |
| char**(字符型)** | Character |
| short**(短整型)** | Short |
| int**(整型)** | Integer |
| long**(长整型)** | Long |
| float**(浮点型)** | Float |
| double**(双精度浮点型)** | Double |
valueOf方法
每个包装类都有一个静态的valueOf方法,用于将字符串转换成相应包装类的对象。
public class LangDemo { public static void main(String[] args) { String str = "120"; //如果转换失败,将会引发NumberFormatException异常 Byte objByte = Byte.valueOf(str); Short objShort = Short.valueOf(str); Integer objInt = Integer.valueOf(str); Long objLong = Long.valueOf(str); System.out.println(objByte); System.out.println(objShort); System.out.println(objInt); System.out.println(objLong); } }
除了Boolean类和Character类以外,其它的包装类都有静态的parseXxx方法(Xxx指代具体的数据类型),用于将字符串转换成相对应的原始数据类型值。
public class ParseTest { public static void main(String[] args) { String str = "116"; //分别调用各个包装类的parseXxx方法对字符串进行转换,如果转换失败,将报异常 int i = Integer.parseInt(str); short s = Short.parseShort(str); byte b = Byte.parseByte(str); long l = Long.parseLong(str); float f = Float.parseFloat(str); double d = Double.parseDouble(str); System.out.println(i); System.out.println(s); System.out.println(b); System.out.println(l); System.out.println(f); System.out.println(d); } }
Character
| 方 法 原 型 | 说 明 |
|---|---|
| boolean isLetter**(char** ch**)** | 判断字符**ch是否为英文字母** |
| boolean isDigit**(char** ch**)** | 判断字符**ch是否为0~9之间的数字** |
| boolean isUpperCase(char ch) | 判断字符**ch是否为大写形式** |
| boolean isLowerCase(char ch) | 判断字符**ch是否为小写形式** |
| boolean isWhitespace(char ch) | 判断字符**ch是否为空格a或换行符** |
以上方法都是静态方法,可以直接通过类名调用,返回值均 为boolean类型,如果是返回true,否则返回false。
public class CharacterDemo { public static void main(String[] args) { char[] charArray = {'*', '7', 'b', ' ', 'A'}; for (int i = 0; i < charArray.length; i++) { if (Character.isDigit(charArray[i])) { System.out.println(charArray[i] + "是一个数字。"); } if (Character.isLetter(charArray[i])) { System.out.println(charArray[i] + "是一个字母。"); } if (Character.isWhitespace(charArray[i])) { System.out.println(charArray[i] + "是一个空格。"); } if (Character.isLowerCase(charArray[i])) { System.out.println(charArray[i] + "是小写形式。"); } if (Character.isUpperCase(charArray[i])) { System.out.println(charArray[i] + "是大写形式。"); } } } }
字符串
String类
String str1="";//str1是基本数据类型(传的是值)
String str2 = new String();//str2是高级引用类型对象(传的是地址)
String类的构造方法共有13种重载方式,以下是常用的几个:
| 构造方法 | 说 明 |
|---|---|
| String() | 将创建一个空字符串 |
| String(String original) | 将新建一个字符串作为指定字符串的副本 |
| String(char[] value) | 将根据字符数组构造一个新字符串 |
| String(byte[] tytes) | 将通过转换指定的字节数组新建一个字符串 |
Java中,字符串是String类的对象; 可以通过使用String类提供的方法来完成对字符串的操作; 创建一个字符串对象之后,将不能更改构成字符串的字符; 每当更改了字符串版本时,就创建了一个新的字符串对象,并在其内包含所做的修改,原始字符串保持不变。
public class StringDemo { public static void main(String[] args) { char[] aryChar = {‘I', 'C', ‘S', ‘S'}; String str1 = “ETC"; //利用一个字符串常量值创建新的字符串 String str2 = new String(“ICSSETC"); //利用一个字符型数组创建新的字符串 String str3 = new String(aryChar); System.out.println(str1); System.out.println(str2); System.out.println(str3); } }
比较
要判断两个字符串是否相等,可以使用“==”运算符和equals()方法,但是得到的结果可能不完全相同(String重载了equals方法); ==运算符用于比较两个引用是否指向同一个对象; 而equals()方法则是比较两个字符串中的内容是否相同,其原型: boolean equals(Object anObject) 如果相等返回true,否则返回false。
搜索
如果需要搜索某个字符(或某个子串)在字符串中是否出现过,这就要使用到indexOf方法和lastIndexOf方法。
| 方 法 原 型 | 说 明 |
|---|---|
| int indexOf(int ch) | 搜索字符**ch在当前字符串中第一次出现的索引,没有出现则返回-1** |
| int indexOf(String str) | 搜索字符串**str在当前字符串中第一次出现的索引,没有出现则返回-1** |
| int lastIndexOf(int ch) | 搜索字符**ch在当前字符串中最后一次出现的索引,没有出现则返回-1** |
| int lastIndexOf(String str) | 搜索字符串**str在当前字符串中最后一次出现的索引,没有出现则返回-1** |
| boolean contains(Char s) | 当且仅当此字符串包含指定的 char 值序列时,返回 true。 |
public class StringDemo { public static void main(String[] args) { String strEmail = "java@sun.com"; int index; System.out.println("E-mail地址:" + strEmail); index = strEmail.indexOf('@'); System.out.println("@字符出现的索引:" + index); index = strEmail.indexOf("sun"); System.out.println("字符串\"sun\"出现的索引:" + index); index = strEmail.lastIndexOf('a'); System.out.println("a字符最后一次出现的索引:" + index); } }
| 方 法 原 型 | 说 明 |
|---|---|
| char charAt**(int** index) | 用于从指定位置提取单个字符,该位置由**index指定,索引值必须为非负** |
| String substring(**int** index) | 用于提取从**index指定的位置开始的字符串部分** |
| String substring(**int** begin, int end) | 用于提取 begin 和 end 位置之间的字符串部分(包含begin不包含end) |
| String concat**(str1** str**)** | 用于连接两个字符串,并新建一个包含调用字符串的字符串对象 |
| String replace(char oldChar**, char** newChar**)** | 用于将调用字符串中出现**oldChar指定的字符全部都替换为newChar指定的字符** |
| replaceAll**(String regex, String replacement)** | 用于将调用字符串中出现或者匹配**regex的字符串全部都替换为replacement指定的字符** |
| String trim() | 用于返回一个前后不含任何空格的调用字符串的副本(去掉两头空格) |
public class StringDemo { public static void main(String[] args) { String str1 = " abcdfff"; String str2 = new String(“icss"); System.out.println(str1.charAt(2)); System.out.println(str1.substring(5)); System.out.println(str1.substring(2, 4));//cd System.out.println(str1.concat(str2)); System.out.println(str1 + str2); System.out.println(str1.replace('a', 'e')); System.out.println(str1.trim()); } }
大小写
| 方 法 原 型 | 说 明 |
|---|---|
| String toUpperCase() | 返回当前字符串的全大写形式 |
| String toLowerCase() | 返回当前字符串的全小写形式 |
public class StringDemo { public static void main(String[] args) { String str1 = "Java is OOP"; String str2; str2 = str1.toLowerCase(); System.out.println(str2); str2 = str1.toUpperCase(); System.out.println(str2); } }
在某些特定的场合,我们可能需要将字符串转化成其它格式的数据进行操作;
| 方 法 原 型 | 说 明 |
|---|---|
| byte[] getBytes**()** | 返回当前字符串转化成**byte型数组的形式(即字符串在内存中保存的最原始的二进制形态)** |
| char[] toCharArray**()** | 返回当前字符串的字符数组形式,类似于**C语言中字符串的保存形式** |
拆分字符串split
public class StringDemo { public static void main(String[] args) { String str = "oo-ff-uo-qw-pp"; //使用-拆分字符串 String [] ss= str.split("-"); for(String s:ss){
StringBuffer类
StringBuffer与StringBuilder都提供了一系列插入、追加、改变字符串里的字符序列的方法,它们的用法基本相同,只是StringBuilder是线程不安全的,StringBuffer是线程安全的,。. 如果只是在单线程中使用字符串缓冲区,则StringBuilder的效率会高些,但是当多线程访问时,最好使用StringBuffer。. 综上,在执行效率方面,StringBuilder最高,StringBuffer次之,String最低,对于这种情况,一般而言,如果要操作的数量比较小,应优先使用String类;如果是在单线程下操作大量数据,应优先使用StringBuilder类;如果是在多线程下操作大量数据,应优先使用StringBuilder类
StringBuffer类用于表示可以修改的字符串; 使用+运算符的字符串将自动创建字符串缓冲对象; 以下是StringBuffer类的构造方法有4种重载方式,以下是常用的几个:
| 构造方法 | 说 明 |
|---|---|
| StringBuffer() | 创建一个空的**StringBuffer对象,默认保留16个字符的缓冲空间** |
| StringBuffer(String str) | 根据字符串**str的内容创建StringBuffer对象,并默认保留** 16 个字符的缓冲空间 |
| StringBuffer(int capacity) | 创建一个空的**StringBuffer对象,缓冲空间大小由capacity指定** |
| 方 法 原 型 | 说 明 |
|---|---|
| StringBuffer insert(int index, x x) | 将**x插入到索引为index的位置,x可以为任何类型的数据** |
| int length() | 获得当前**StringBuffer对象的长度** |
| void setCharAt(int index, char ch) | 使用 ch 指定的新值替换 index**指定的位置上的字符** |
| String toString() | 转换为字符串形式 |
| StringBuffer reverse() | 将当前**StringBuffer对象中的字符序列倒置** |
| StringBuffer delete(int start, int end) | 删除当前对象中从**start位置开始直到** end 指定的索引 位置的字符序列 |
| StringBuffer deleteCharAt(int index) | 将删除 index 指定的索引处的字符 |
| StringBuffer replace(int start, int end, String str) | 此方法使用一组字符替换另一组字符。将用替换字符串从 start**指定的位置开始替换,直到** end 指定的位置结束 |
| StringBuffer append | 插入的方法 |
public class StringBufferDemo { public static void main(String[] args) { StringBuffer strBuf = new StringBuffer("Java"); strBuf.append(" Guide Ver1/"); //连接 System.out.println(strBuf); strBuf.append(3); System.out.println(strBuf); strBuf.insert(5, "Student"); //插入 System.out.println(strBuf); strBuf.setCharAt(20, '.'); //替换字符 System.out.println(strBuf); strBuf.reverse(); //倒序 System.out.println(strBuf); String str = strBuf.toString(); System.out.println(str); } }
Math类中提供了一系列基本数学运算和几何运算的方法; 该类的构造方法被修饰为private,因此不能实例化; 该类中的所有方法都是静态的,可以通过类名直接调用; 该类被修饰为final,因此没有子类
| 方 法 原 型 | 说 明 |
|---|---|
| static int abs(int a) | 求**a的绝对值,有4种重载,还有float,double和long** |
| static double pow(double a, double b) | 求**a的b次方幂** |
| static double sqrt(double a) | 求**a的平方根** |
| static int round(float a) | 求**a的四舍五入结果** |
| static double ceil(double a) | 返回不小于**a的最小整数值** |
| static double floor(double a) | 返回不大于**a的最大整数值** |
| static double sin(double a) | 返回**a的正弦值** |
| static double cos(double a) | 返回**a的余弦值** |
Math类中还包括两个常用的常量: PI:圆周率π E:自然常量 以上常量在Math类中都被声明成静态,可以直接通过类名进行访问。
Object类
Java中的类体系遵循单根结构,即任何一个类往上追溯都到达同一个父类; Object类就是这个单根体系的根,也就是说它是其它所有类的共同父类; 如果用户定义的类没有扩展任何其它类,则默认扩展自Object类; Object类中定义的一些方法,会被继承到所有类中。
| 方 法 原 型 | 说 明 |
|---|---|
| boolean equals(Object obj**)** | 判断当前对象是否与参数**obj(内容)相等,如果有必要,应该在自定义的类中覆盖该方法** |
| String toString**()** | 返回当前对象的字符串表示,如果有必要,应该在自定义的类中覆盖该方法 |
| Class getClass**()** | 返回当前对象的类描述对象,此方法被继承到所有类中 |
| protected void finalize()**throws Throwable** | 当前对象被垃圾回收时调用此方法(类似于**C++的析构函数),但无法确定具体何时调用** |
| public final void wait()**throws InterruptedException** | 使当前线程进入等待状态 |
Class类
Java应用程序实际上都是由一个个对象组成,这些对象分别属于什么类,是从哪个类继承而来,这一系列的信息都是由Class类的实例来记录的; Class类的实例用于记录对象的类描述信息; 如果在程序运行时,需要检索某个对象的相关类信息,可以调用该对象的getClass方法来获得记录其描述信息的Class类实例; Class类没有公共的构造方法,无法通过new运算符实例化,只能通过对象的getClass方法,或是通过Class的静态方法forName来获得实例。
| 方 法 原 型 | 说 明 |
|---|---|
| static Class forName**(String** className**)throws** ClassNotFoundException | 使用参数**className来指定具体的类,来获得相关的类描述对象,该方法有可能抛出类加载异常(ClassNotFoundException),必须捕捉** |
| Class getSuperclass() | 获得当前类描述对象的父类的描述对象 |
| String getName() | 返回当前类描述对象的类名称 |
public class ClassDemo { public static void main(String[] args) { try { /*使用forName方法获得任意一个类的类描述对象 这里以StringBuffer类为例 forName方法有可能抛异常,必须捕捉*/ Class cls = Class.forName("java.lang.StringBuffer"); //循环打印父类信息,直到没有父类 while (cls != null) { System.out.println(cls); cls = cls.getSuperclass(); } } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } } }
输入输出流
File类
File类的对象不但可以表示文件,还可以表示目录,在程序中一个File类对象可以代表一个文件或目录; 当创建一个文件对象后,就可以利用它来对文件或目录的属性进行操作,如:文件名、最后修改日期、文件大小等等; 需要注意的是,File对象并不能直接对文件进行读/写操作,只能查看文件的属性;
| 构 造 方 法 | 说 明 |
|---|---|
| File(String pathname) | 指定文件(或目录)名和路径创建文件对象 |
//在当前目录下创建一个与aaa.txt文件名相关联的文件对象 File f1 = new File("aaa.txt"); //指明详细的路径以及文件名,请注意双斜线 File f2 = new File("D:/Java/Hello.java");
| 方 法 原 型 | 说 明 |
|---|---|
| boolean exists() | 判断文件是否存在,存在返回**true,否则返回false** |
| boolean isFile**()** | 判断是否为文件,是文件返回**true,否则返回false** |
| boolean isDirectory**()** | 判断是否为目录,是目录返回**true,否则返回false** |
| String getName**()** | 获得文件的名称 |
| String getAbsolutePath**()** | 获得文件的绝对路径 |
| long length() | 获得文件的长度(字节数) |
| boolean createNewFile**()throws** IOException | 创建新文件,创建成功返回**true,否则返回false,有可能抛出IOException异常,必须捕捉** |
| boolean delete() | 删除文件,删除成功返回**true,否则返回false** |
| File[] listFiles**()** | 返回文件夹内的子文件与子文件夹的数组 |
File file = new File("xxx"); file.mkdir();//创建文件夹必须有file才可与i创建 File file1 = new File("xxx/a"); file1.createNewFile();//在xxx下创建目录 public class FileDemo { public static void main(String[] args) { //创建一个文件对象,使之与一个文件关联 File file = new File("test.txt"); //显示与文件有关的属性信息 System.out.println("文件或目录是否存在:" + file.exists()); System.out.println("是文件吗:" + file.isFile()); System.out.println("是目录吗:" + file.isDirectory()); System.out.println("名称:" + file.getName()); System.out.println("绝对路径:" + file.getAbsolutePath()); System.out.println("文件大小:" + file.length()); } }
流的类型
字节流
使用FileInputStream类读文件
FileInputStream类称为文件输入流,继承于InputStream类,是进行文件读操作的最基本类; 它的作用是将文件中的数据输入到内存中,我们可以利用它来读文件; 由于它属于字节流,因此在读取Unicode字符(如中文)的文件时可能会出现问题。
FileInputStream类的构造方法有3种重载方式,以下是常用的几种。
| 构 造 方 法 | 说 明 |
|---|---|
| FileInputStream**(File** file**)throws** FileNotFoundException | 使用**File对象创建文件输入流对象,如果文件打开失败,将抛出异常** |
| FileInputStream**(String name)throws** FileNotFoundException | 使用文件名或路径创建文件输入流对象,如果文件打开失败,将抛出异常 |
| 方 法 原 型 | 说 明 |
|---|---|
| int read()**throws** IOException | 读取文件中的数据,一次读取一个字节,读取的数据作为返回值返回,如果读到文件末尾则返回**-1,有可能抛异常,必须捕捉** |
| int read(byte[] b)**throws** IOException | 读取文件中的数据,将读到的数据存放到**byte型数组中,并返回读取的字节的数量,未读到数据返回-1,有可能抛异常,必须捕捉**(读中文) |
| void close()**throws IOException** | 关闭流对象,有可能抛异常,必须捕捉 |
public class FileInputStreamDemo1 { public static void main(String[] args) { try { File file = new File("test.txt"); //创建文件对象 //使用文件对象创建文件输入流对象,相当于打开文件 FileInputStream fis = new FileInputStream(file); for (int i = 0; i < file.length(); i++) { char ch = (char)(fis.read()); //循环读取字符 System.out.print(ch); } System.out.println(); fis.close(); //关闭流 } catch (FileNotFoundException fnfe) { System.out.println("文件打开失败。"); } catch (IOException ioe) { ioe.printStackTrace(); } } }
使用FileOutputStream类写文件
FileOutputStream类称为文件输出流,继承于OutputStream类,是进行文件写操作的最基本类; 它的作用是将内存中的数据输出到文件中,我们可以利用它来写文件。
| 构 造 方 法 | 说 明 |
|---|---|
| FileOutputStream**(File** file**)throws** FileNotFoundException | 使用**File对象创建文件输出流对象,如果文件打开失败,将抛出异常** |
| FileOutputStream**(File** file**,** boolean append)**throws** FileNotFoundException | 使用**File对象创建文件输出流对象,并由参数append指定是否追加文件内容,true为追加,false为不追加,异常情况同上** |
| FileOutputStream**(String name)throws** FileNotFoundException | 直接使用文件名或路径创建文件输出流对象,异常情况同上 |
| FileOutputStream(String name, boolean append)**throws FileNotFoundException** | 直接使用文件名或路径创建文件输出流对象,并由参数**append指定是否追加,异常情况同上** |
| 方 法 原 型 | 说 明 |
|---|---|
| void write(**int** b)**throws** IOException | 往文件中写数据,一次写一个字节,有可能抛异常,必须捕捉 |
| void write(byte[] b)**throws** IOException | 往文件中写数据,将**byte数组中的数据全部写入到文件中,有可能抛异常,必须捕捉** |
| void close()**throws** IOException | 关闭流对象,有可能抛异常,必须捕捉 |
public class FileOutputStreamDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub File file = new File("a"); FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(file); String str = "hellow"; byte[] buffer = str.getBytes(); fileOutputStream.write(buffer); }catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ if(fileOutputStream!=null){ fileOutputStream.close(); } } } }
字符流
FileInputStram类和FileOutputStream类虽然可以高效率地读/写文件,但对于Unicode编码的文件,使用它们有可能出现乱码; 考虑到Java是跨平台的语言,要经常操作Unicode编码的文件,使用字符流操作文件是有必要的; 使用字符流将涉及到以下4个类: FileReader类和FileWriter类; BufferedReader类和BufferedWriter类。
FileReader类
FileReader类称为文件读取流,允许以字符流的形式对文件进行读操作,其构造方法有3种重载方式,以下是常用的几种:
| 构 造 方 法 | 说 明 |
|---|---|
| FileReader**(File** file**)throws** FileNotFoundException | 使用**File对象创建文件读取流对象,如果文件打开失败,将抛出异常** |
| FileReader(String name)**throws FileNotFoundException** | 使用文件名或路径创建文件读取流对象,如果文件打开失败,将抛出异常 |
该类将从文件中逐个地读取字符,效率比较低下,因此一般将该类对象包装到缓冲流中进行操作。
BufferedReader类
BufferedReader类主要为字符流提供缓冲,以提高效率,其构造方法有2种重载方式,以下是常用的几种:
| 构 造 方 法 | 说 明 |
|---|---|
| BufferedReader**(Reader in)** | 将字符读取流对象包装成缓冲读取流对象 |
以下是BufferedReader类的常用方法:
| 方 法 原 型 | 说 明 |
|---|---|
| String readLine**() throws** IOException | 从缓冲读取流中读取一行字符,以字符串的形式返回,有可能抛异常,必须捕捉 |
| void close() throws IOException | 关闭流对象,有可能抛异常,必须捕捉 |
FileReader配合BufferedReader读文件示例
public class ReaderDemo { public static void main(String[] args) throws IOException { File file = new File("test.txt"); //通过文件对象创建文件读取流对象 FileReader fr = new FileReader(file); //将文件读取流包装成缓冲读取流 BufferedReader br = new BufferedReader(fr); String str; while ((str = br.readLine()) != null) //逐行读取数据 { System.out.println(str); } br.close(); //关闭流 fr.close(); //关闭流 } }
FileWriter类
FileWriter类称为文件写入流,以字符流的形式对文件进行写操作,其构造方法有5种重载,以下是常用的几种:
| 构 造 方 法 | 说 明 |
|---|---|
| FileWriter**(File** file**)throws** IOException | 使用**File对象创建文件写入流对象,如果文件打开失败,将抛出异常,必须捕捉** |
| FileWriter**(File** file**,** boolean append)**throws** IOException | 使用**File对象创建文件写入流对象,并由参数append指定是否追加,异常情况同上** |
| FileWriter**(String name)throws** IOException | 直接使用文件名或路径创建文件写入流对象,异常情况同上 |
| FileWriter(String name, boolean append)**throws IOException** | 直接使用文件名或路径创建文件写入流对象,并由参数**append指定是否追加,异常情况同上** |
与FileReader类相似,FileWriter类同样需要使用缓冲流进行包装。
BufferedWriter类
BufferedWriter类可以为FileWriter类提供缓冲,其构造方法有2种重载方式,以下是常用的几种:
| 构 造 方 法 | 说 明 |
|---|---|
| BufferedWriter(Writer out) | 将字符写入流对象包装成缓冲写入流对象 |
以下是BufferedWriter类的常用方法:
| 方 法 原 型 | 说 明 |
|---|---|
| void write(String str**)throws** IOException | 将一行字符串写入到缓冲写入流中,有可能抛异常,必须捕捉 |
| void newLine()**throws IOException** | 将一个回车换行符写入到文件中,从而达到换行的效果,有可能抛异常,必须捕捉 |
FileWriter配合BufferedWriter写文件示例
public class WriterDemo { public static void main(String[] args) throws IOException { File file = new File("test.txt"); //通过文件对象创建文件输出字符流对象 FileWriter fw = new FileWriter(file); //将文件输出字符流包装成缓冲流 BufferedWriter bw = new BufferedWriter(fw); bw.write("大家好!"); bw.write("我正在学习Java。"); bw.newLine(); //换个新行 bw.write("请多多指教。"); bw.newLine(); //换新行 bw.write("Luckybug@21cn.com"); bw.close(); //关闭流 fw.close(); //关闭流 } }
文件的封冻与解冻
ObjectOutputStream 类
1、概述
ObjectOutputStream:对象的序列化流,作用:把对象转成字节数据的输出到文件中保存,对象的输出过程称为序列化,可实现对象的持久存储。
2、构造方法
ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。
3、特有的成员方法
void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。
4、使用步骤
1 创建ObjectOutputStream对象,构造方法中传递字节输出流
2 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
3 释放资源
5、序列化操作
一个对象要想序列化,必须满足两个条件
1 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException 。
每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。
2 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
代码:
public class Person implements Serializable{ // 实现序列化接口 private static final long serialVersionUID = 1L; private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public static void main(String[] args) throws IOException { //1.创建ObjectOutputStream对象,构造方法中传递字节输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\person.txt")); //2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中 oos.writeObject(new Person("张三",18)); //3.释放资源 oos.close(); }
注意
① 序列化和反序列化的时候,会抛出NotSerializableException不能序列化异常
② 类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。
③ Serializable接口也叫标记型接口,要进行序列化和反序列化的类必须实现Serializable接口,就会给类添加一个标记
④ 当进行序列化时会检查是否有这个标记,有:就可以序列化和反序列化;没有:就会抛出 NotSerializableException异常
Static 关键字:静态关键字
静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
被static修饰的成员变量不能被序列化的,因为静态的变量不属于某个对象,而是整个类的,所以不需要随着对象的序列化而序列化。序列化的都是对象
如果上面的 Person类中的 age 声明为:private static int age;这时再对 Person 这个对象序列化时,age 始终为0,不能被序列化。
transient 关键字:瞬态关键字
被transient修饰成员变量,不能被序列化
代码:
private transient int age; oos.writeObject(new Person("张三",18)); Object o = oos.readObject(); Person{name='张三', age=0}
ObjectInputStream 类
1、概述
java.io.ObjectInputStream extends InputStream
ObjectInputStream **反序列化流**,将之前使用 ObjectOutputStream 序列化的原始数据恢复为对象,以流的方式读取对象。
2、构造方法
ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream。
参数:InputStream in:字节输入流
3、特有的成员方法
Object readObject() 从 ObjectInputStream 读取对象。
4、使用步骤
1 创建ObjectInputStream对象,构造方法中传递字节输入流
2 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
3 释放资源
4 使用读取出来的对象(打印)
5、注意
readObject方法声明抛出了ClassNotFoundException(class文件找不到异常)
当不存在对象的 class 文件时抛出此异常:
反序列化的前提:
1 类必须实现 Seriaizable
2 必须存在类对应的 class 文件
6、反序列化操作1
如果能找到一个对象的class文件,可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法:
public final Object readObject () : 读取一个对象。 public static void main(String [] args) { Employee e = null; try { // 创建反序列化流 FileInputStream fileIn = new FileInputStream("employee.txt"); ObjectInputStream in = new ObjectInputStream(fileIn); // 读取一个对象 e = (Employee) in.readObject// 若要读取“Object对象”,则使用readObject() // 释放资源 in.close(); fileIn.close(); }catch(IOException i) { // 捕获其他异常 i.printStackTrace(); return; }catch(ClassNotFoundException c) { // 捕获类找不到异常 System.out.println("Employee class not found"); c.printStackTrace(); return; } // 无异常,直接打印输出 System.out.println(e); } }
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常
7、反序列化操作2
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个 InvalidClassException 异常。
原因如下:
1 该类的序列版本号与从流中读取的类描述符的版本号不匹配
2 该类包含未知数据类型
3 该类没有可访问的无参数构造方法
Serializable 接口给需要序列化的类,提供了一个**序列版本号**。 **serialVersionUID** 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
解决方法:
1 修改本地的serialVersionUID为流中的serialVersionUID
2 在当初实现Serializable接口时,就固定一个serialVersionUID,这样每次编译就不会自动生成一个新的serialVersionUID
代码:
public class Employee implements java.io.Serializable { // 加入序列版本号 private static final long serialVersionUID = 1L; public String name; public String address; // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值. public int eid; public void addressCheck() { System.out.println("Address check : " + name + " ‐‐ " + address); } }
线程
概念
运行在操作系统之上的每个应用程序,都会占用一个独立的进程(process),而进程内又允许运行多个线程(thread),这意味着一个程序可以同时执行多个任务的功能; 在基于线程的多任务而处理环境中,线程是执行特定任务的可执行代码的最小单位; 多线程帮助你写出CPU最大利用率的高效程序,因为空闲时间保持最低,这对Java运行的交互式的网络互连环境是至关重要的,例如:网络的数据传输速率远低于计算机的处理能力,在传统的单线程环境中,你的计算机必须花费大量的空闲时间来等待,多线程能够使你充分利用这些空闲时间。
与进程的区别
进程是指系统中正在运行中的应用程序,它拥有自己独立的内存空间; 线程是指进程中一个执行流程,一个进程中允许同时启动多个线程,他们分别执行不同的任务; 线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中,这些线程可以共享数据,因此线程间的通信比较简单,消耗的系统开销也相对较小
多线程
Java支持编写多线程的程序; 多线程最大的好处在于可以同时并发执行多个任务,当程序的某个功能部分正在等待某些资源的时候,此时又不愿意因为等待而造成程序暂停,那么就可以创建另外的线程进行其它的工作; 多线程可以最大限度地减低CPU的闲置时间,从而提高CPU的利用率;
主线程
任何一个Java程序启动时,一个线程立刻运行,它执行main方法,这个线程称为程序的主线程; 也就是说,任何Java程序都至少有一个线程,即主线程; 主线程的特殊之处在于: 它是产生其它线程子线程的线程; 通常它必须最后结束,因为它要执行其它子线程的关闭工作
| 单 词 | 说 明 |
|---|---|
| thread | 线,线程 |
| runnable | 可追捕的,可猎取的 |
| current | 当前的,最近的 |
| sleep | 睡,睡眠 |
| synchronized | 同步的 |
Thread类中的常用静态方法
Thread类中的常用静态方法
| 方 法 原 型 | 说 明 |
|---|---|
| static Thread currentThread**()** | 返回对当前正在执行的线程对象的引用 |
| static void sleep(long millis**)throws** InterruptedException | 让当前正在执行的线程休眠(暂停执行),休眠时间由**millis(毫秒)指定** |
| static void sleep(long millis**,** int nanos**)throws** InterruptedException | 让当前正在执行的线程休眠,休眠时间由**millis(毫秒)和nanos(纳秒)指定** |
| static void yield() | 暂停当前正在执行的线程,转而执行其它的线程 |
| static boolean interrupted() | 判断当前线程是否已经中断 |
主线程
任何一个Java程序启动时,一个线程立刻运行,它执行main方法,这个线程称为程序的主线程; 也就是说,任何Java程序都至少有一个线程,即主线程; 主线程的特殊之处在于:
它是产生其它线程子线程的线程;
通常它必须最后结束,因为它要执行其它子线程的关闭工作。
实现线程的两种方法
继承Thread和实现Runnable接口,但是要重写run方法
自定义线程
在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法,方法原型如下: public void run() 如: public class MyThread extends Thread { public void run() { …… } } Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行run方法。
Thread类的构造方法
| 构 造 方 法 | 说 明 |
|---|---|
| Thread() | 创建一个新的线程 |
| Thread(String name) | 创建一个指定名称的线程 |
| Thread(**Runnable** target) | 利用**Runnable对象创建一个线程,启动时将执行该对象的run方法** |
| Thread(Runnable target, String name) | 利用**Runnable对象创建一个线程,并指定该线程的名称** |
Thread类的常用方法
| 方 法 原 型 | 说 明 |
|---|---|
| void start() | 启动线程 |
| final void setName**(String name)** | 设置线程的名称 |
| final String getName**()** | 返回线程的名称 |
| final void setPriority**(int** newPriority**)** | 设置线程的优先级 |
| final int getPriority() | 返回线程的优先级 |
yield是把线程从运行状态,变为可运行状态。
wait是要求线程解锁并放弃运行状态,进入等待队列。
notify是用来从等待队列里面唤醒一个线程。
notifyAll是用来从等待队列里面唤醒所有线程。
sleep是暂停线程的执行。当前线程被唤醒后会进入可运行状态。
synchronized:
public class TickThread implements Runnable{ private int stickNum = 1; private final int TICKNUM =10; private boolean flag = true; @Override public void run() { //启动线程进行买票 while(flag) { //什么条件退出 this.buy(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * */ private synchronized void buy() { //两种方式 // if(this.stickNum>TICKNUM) { // flag=false; // return; // } // System.out.println(Thread.currentThread().getName()+"买了第"+this.stickNum+"张票:"+System.currentTimeMillis()); // stickNum++; //张三 synchronized (this){ if(this.stickNum>TICKNUM) { flag=false; return; } System.out.println(Thread.currentThread().getName()+"买了第"+this.stickNum+"张票:"+System.currentTimeMillis()); stickNum++; } } }
public class TickMain { public static void main(String[] args) { TickThread target = new TickThread(); new Thread(target,"张三").start(); new Thread(target,"李四").start(); new Thread(target,"王五").start(); } }
lock
public class TickThread implements Runnable{ private ReentrantLock lock = new ReentrantLock(); private int stickNum = 1; private final int TICKNUM =10; private boolean flag = true; @Override public void run() { //启动线程进行买票 while(flag) { //什么条件退出 this.buy(); lock.unlock(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * */ private void buy() { lock.lock(); if(this.stickNum>TICKNUM) { flag=false; return; } System.out.println(Thread.currentThread().getName()+"买了第"+this.stickNum+"张票:"+System.currentTimeMillis()); stickNum++; } }
反射
什么是反射
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。
反射能做什么
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
反射的具体体现
Person类
package com.ys.reflex; public class Person { //私有属性 private String name = "Tom"; //公有属性 public int age = 18; //构造方法 public Person() { } //私有方法 private void say(){ System.out.println("private say()..."); } //公有方法 public void work(){ System.out.println("public work()..."); } }
对于一个Person类,得到class的三种方式
//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object // 类型的对象,而我不知道你具体是什么类,用这种方法 Person p1 = new Person(); Class c1 = p1.getClass(); //2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高 // 这说明任何一个类都有一个隐含的静态成员变量 class Class c2 = Person.class; //3、通过 Class 对象的 forName() 静态方法来获取,用的最多, // 但可能抛出 ClassNotFoundException 异常 Class c3 = Class.forName("com.ys.reflex.Person");
需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true
通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
查阅 API 可以看到 Class 有很多方法:
getName():获得类的完整名字。
•getFields():获得类的public类型的属性。
•getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
•getMethods():获得类的public类型的方法。
•getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
•getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
•getDeclaredMethod(String name, Class[] parameterTypes)获得私有的获得类的特定方法
•getConstructors():获得类的public类型的构造方法。
•getDeclaredConstructors()获得类的private类型的构造方法。
•getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
•getDeclaredConstructor()获得私有类的特定构造方法
•newInstance():通过类的不带参数的构造方法创建这个类的一个对象。(Object obj = cls.newInstance();)
•getType(),拿到当前的属性
•getModifiers():getModifiers()方法返回int类型值表示该字段的修饰符,对应如下:
修饰符 对应的int类型
public 1
private 2
protected 4
static 8
final 16
synchronized 32
volatile 64
transient 128
native 256
interface 512
abstract 1024
strict 2048
• void set(Object obj, Object value):设置指定调用者中对应成员变量的数据
• Object get(Object obj):获取指定调用者中指定成员变量的数据
• invoke:
1、invoke是Method类下的方法,只有方法才能调用invoke方法,
2、invoke方法的含义:反射调用实例方法,返回的是实例方法的返回值。
3、哪个方法调用invoke方法,返回的就是哪个方法的返回值。
4、invoke方法的参数含义:第一个参数的含义是:实例对象,第二个方法的含义是:调用invoke方法的方法中的参数值。
以上方法实现的例子:
//获得类完整的名字 String className = c2.getName(); System.out.println(className);//输出com.ys.reflex.Person //获得类的public类型的属性。 Field[] fields = c2.getFields(); for(Field field : fields){ System.out.println(field.getName());//age } //获得类的所有属性。包括私有的 Field [] allFields = c2.getDeclaredFields(); for(Field field : allFields){ System.out.println(field.getName());//name age } //获得类的public类型的方法。这里包括 Object 类的一些方法 Method [] methods = c2.getMethods(); for(Method method : methods){ System.out.println(method.getName());//work waid equls toString hashCode等 } //获得类的所有方法。 Method [] allMethods = c2.getDeclaredMethods(); for(Method method : allMethods){ System.out.println(method.getName()+","+method.getModifiers()+","+method.getType());//work say } //获得指定的属性 Field f1 = c2.getField("age"); System.out.println(f1); //获得指定的私有属性 Field f2 = c2.getDeclaredField("name"); //启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消 f2.setAccessible(true); System.out.println(f2); //创建这个类的一个对象 Object p2 = c2.newInstance(); //将 p2 对象的 f2 属性赋值为 Bob,f2 属性即为 私有属性 name f2.set(p2,"Bob"); //使用反射机制可以打破封装性,导致了java对象的属性不安全。 System.out.println(f2.get(p2)); //Bob //获取构造方法 Constructor [] constructors = c2.getConstructors(); for(Constructor constructor : constructors){ System.out.println(constructor.toString());//public com.ys.reflex.Person() } //invoke使用方法 Class<?> cls = User.class; Method methods = cls.getMethod("S", int.class); Object obj = cls.newInstance(); int a = (int) methods.invoke(obj,12); System.out.println(a); //使用newInstance获得有参构造并赋值 Class<?> cls = User.class; Constructor constructor = cls.getConstructor(int.class,String.class); Object obj = constructor.newInstance(1001,"s"); User user = (User)obj; System.out.println(user.getId()+"\n"+user.getName());
对于注解:
@Test public void test10(){ //通过反射获取user中的value数据 //注解对象能否获取到 Class<?> cls = Stu.class; Annotation[] annotations = cls.getAnnotations(); for (Annotation anno:annotations){ if(anno instanceof MyAnno){ MyAnno myAnno=(MyAnno)anno; System.out.println(myAnno.value()); } } } @Test public void test11() throws Exception{ //能否获取到当前在id上的注解,比关切将数据获取到 Class<?> cls = Stu.class; //获取到域(属性)对象 根据名字找到对象 Field field = cls.getDeclaredField("id"); //判断当前field对象上有没有注解 //用于判断注解是否在这个field上 Boolean flag =field.isAnnotationPresent(MyAnno.class); if(flag){ //getAnnotations此方法返回注释类的指定对象数组 Annotation[] annotations =field.getAnnotations(); for(Annotation annotation:annotations){ //需要向下转型 转成MyAnno类型 //应为当前需求只有一个,所以可以直接强转 MyAnno myanno = (MyAnno)annotation; System.out.println(myanno.value()); } } } @Test public void test12() throws Exception{ Class<?> cls = Stu.class; Object obj = cls.newInstance(); Method method = cls.getDeclaredMethod("setSex",char.class); Boolean flag = method.isAnnotationPresent(MyAnno.class); if(flag){ //getAnnotation此方法返回注释类的指定对象 Annotation annotation = method.getAnnotation(MyAnno.class); MyAnno myanno = (MyAnno)annotation; System.out.println(myanno.value()); method.invoke(obj,myanno.value().charAt(0)); System.out.println(obj); } }
对于泛型:
@Test public void test09(){ //获取泛型中的具体类型 User<String,String> user = new User<>(); Class<?> cls = user.getClass(); Field[] fields = cls.getDeclaredFields(); for (Field field:fields){ System.out.println(field.getType()+","+field.getGenericType()); Type t = field.getGenericType(); } } public class RefTest { @Test public void Test(){ } /* * 使用反射获取全部的stu中的属性 */ @Test public void Test01(){//结果为num //模板 Class cls = Stu.class; //通过字节码对象获取全部的属性,全部的域存储在数组中 Field[] fieldArray=cls.getFields(); //field 只可以获得公开的属性 for(Field f:fieldArray){ System.out.println(f.getName()); } } @Test public void Test02(){//结果为id name sex num Class cls = Stu.class; Field[] declareFields=cls.getDeclaredFields();//拿到所有的域 for(Field f:declareFields){ System.out.println(f.getName()); } } @Test public void Test04(){//拿到所有方法(除了构造方法) Method[] d = this.cls.getDeclaredMethods(); for(Method m:d){ System.out.println(m.getModifiers()+" "+m.getReturnType()+" "+m.getName()+" "+m.getParameterCount()); } } @Test public void Test05(){//获得所有公开的方法(除了构造方法) Method[] d = this.cls.getMethods(); for(Method m:d){ System.out.println(m.getModifiers()+" "+m.getReturnType()+" "+m.getName()+" "+m.getParameterCount()); } } @Test public void Test03() throws NoSuchFieldException, SecurityException{//获得单独的域 Field idFiled = this.cls.getDeclaredField("id"); System.out.println(idFiled.getName()); }//结果:id @Test public void Test06() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{//获取构造方法 Constructor[] constructor=this.cls.getConstructors(); for(Constructor c:constructor){ System.out.println(c.getName()); c.newInstance();//调用无参构造方法 } }
类加载器
类加载器按照层次,从顶层到底层,分为以下三种:
类型及使用
类型
(1)启动类加载器/引导类加载器(Bootstrap ClassLoader)
1.这个类加载器使用c/c++语言实现的,嵌套在JVM内部
2.它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
3.并不继承自java.lang.ClassLoader,没有父加载器。
4.加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
5.处于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
(2)扩展类加载器(Extension ClassLoader)
1.Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
2.派生于ClassLoader类
3.父类加载器为启动类加载器
4.从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
(3)应用程序类加载器(Application ClassLoader)
这个加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Classpath)上所指定的类库,可直接使用这个加载器,如果应用程序没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器
1.java语言编写,由sun.misc.Launcher$AppClassLoader实现
2.派生于ClassLoader类
3.父类加载器为扩展类加载器
4.它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
5.该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
6.通过ClassLader#getSystemClassLoader()方法可以获取到该类加载器
运行:
public static void main(String[] args){ System.out.println("hello java"); System.out.println("==输出classLoader对象=="); //1、如何获取classLoader对象 获取当前系统类加载器==就是当前工程类的加载器,即应用程序类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); //2、想办法获取扩展类加载器 //2.1、systemClassloader对象 ClassLoader parentclassloader = systemClassLoader.getParent(); System.out.println(parentclassloader); //3、引导类加载器 ClassLoader parent= parentclassloader.getParent(); System.out.println(parent); }
运行结果:
hello java
==输出classLoader对象==
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null
结论:
1、可以获得扩展类加载器和应用程序类加载器,三个类加载器有父子关系
2、bootstarp这个顶层加载器,无法在程序中获取到,只能得到null
原因:因为当前bootstarp之外还需要进行c和c++,所以不让应用这进行获取,如果程序员获取到当前对象,认为不安全

public class ClassLoaderTest { @Test public void test04() throws Exception { //对于类加载器而言,加载过后的位置位于target/classes位置 //ClassLoaderTest.class是调用当前ClassLoaderTest这个类,使用getClassLoader()方法来获取当前类的类加载器,getResourceAsStream()用来读取文件 InputStream in = ClassLoaderTest.class.getClassLoader().getResourceAsStream("sys2.properties"); Properties p =new Properties(); p.load(in); System.out.println(p); in.close(); } @Test public void test05() throws Exception { //对于类加载器而言,加载过后的位置位于target/classes位置,所以要继续访问则自行加路径 InputStream in = ClassLoaderTest.class.getClassLoader().getResourceAsStream("com\\dl\\tt\\sys3.properties"); Properties p =new Properties(); p.load(in); System.out.println(p); in.close(); } }
Class类的作用
用来做什么?为什么会有这个类?
A.java==A.class
B.java==B.class
C.java==C.class
具有相同属性和行为的事物 叫类
将A.class .class .class看作三个对象
这个类封装了 属性,方法,构造方法
当前这个Class类型的对象如何获取?
三种方式获取到当前Class对象
情况一:java虚拟机已经启动,但是我写的是后写的 @Test public void test01() throws ClassNotFoundException { //当前系统中使用A User这个类 //告诉与1个全路径:包名字和类名字 //这个forName()到当前编译路径翔安区找全路经,如果找到,那么注册新的类型 //Class.forName作用:主要功能Class.forName(xxx.xx.xx)返回的是一个类。Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。 Class<?> cls = Class.forName("com.dl.tt.bean.User"); System.out.println(cls); }
情况二:当前这个类已经注册成功,如何获取到当前类型对象 @Test public void test02(){ Class<?> cls = String.class;//字节码对象 System.out.println(cls); } //或者写成 @Test public void test04(){ Class<String> cls1= String.class; System.out.println(cls1); }
情况三:已经知道一个对象,如何获取当前类型对象 @Test public void test03(){ //已经在java虚拟机中存在一个对象 String str = new String("abc"); //通过对象获取类型 Class<?> cls = str.getClass(); System.out.println(cls); }
JVM的类加载机制主要有3种
1、全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入。 2、双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。 3、缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
双亲委派:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器区执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父加载器无法完成此加载任务,子加载器才会尝试自己去加载,如果均加载失败,就会抛出ClassNotFoundException异常,这就是双亲委派模式。即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了了时,儿子自己才想办法去完成。
双亲委派优点:
1、安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String。,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
2、避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)。Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
泛型
01、为什么要有泛型
泛型:标签
1.1、举例
中药店,每个抽屉外面贴着标签
超市购物架上很多瓶子,每个瓶子装的是什么,有标签。
1.2、泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList这个就是类型参数,即泛型。
1.3、其他说明
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
从JDK1.5以后,Java引入了“参数化类型(Parameterizedtype)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
1.4、那么为什么要有泛型呢
-
那么为什么要有泛型呢,直接Object不是也可以存储数据吗?
1、解决元素存储的安全性问题,好比商品、药品标签,不会弄错。2.解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。


2、Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
/** * 泛型的使用 * 1.jdk5.0新增的特征 */ public class GenericTest { //在集合中使用泛型之前的情况: @Test public void test(){ ArrayList list = new ArrayList(); //需求:存放学生的成绩 list.add(78); list.add(49); list.add(72); list.add(81); list.add(89); //问题一:类型不安全 // list.add("Tom"); for(Object score : list){ //问题二:强转时可能出现类型转化异常 int stuScore = (Integer)score; System.out.println(stuScore); } } }
02、在集合中使用泛型
注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
2.1、举例
import org.junit.Test; import java.util.*; /** * 泛型的使用 * 1.jdk5.0新增的特征 * * 2.在集合中使用泛型: * 总结: * ①集合接口或集合类在jdk5.0时都修改为带泛型的结构。 * ②在实例化集合类时,可以指明具体的泛型类型 * ③指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。 * 比如:add(E e) --->实例化以后:add(Integer e) * ④注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换 * ⑤如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。 * * 3.如何自定义泛型结构:泛型类、泛型接口;泛型方法。见 GenericTest1.java * */ public class GenericTest { //在集合中使用泛型的情况:以HashMap为例 @Test public void test3(){ // Map<String,Integer> map = new HashMap<String,Integer>(); //jdk7新特性:类型推断 Map<String,Integer> map = new HashMap<>(); map.put("Tom",87); map.put("Tone",81); map.put("Jack",64); // map.put(123,"ABC"); //泛型的嵌套 Set<Map.Entry<String,Integer>> entry = map.entrySet(); Iterator<Map.Entry<String, Integer>> iterator = entry.iterator(); while(iterator.hasNext()){ Map.Entry<String, Integer> e = iterator.next(); String key = e.getKey(); Integer value = e.getValue(); System.out.println(key + "----" + value); } } //在集合中使用泛型的情况:以ArrayList为例 @Test public void test2(){ ArrayList<Integer> list = new ArrayList<Integer>(); list.add(78); list.add(49); list.add(72); list.add(81); list.add(89); //编译时,就会进行类型检查,保证数据的安全 // list.add("Tom"); //方式一: // for(Integer score :list){ // //避免了强转的操作 // int stuScore = score; // // System.out.println(stuScore); // } //方式二: Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ int stuScore = iterator.next(); System.out.println(stuScore); } } }
2.2、练习

1、MyDate类
/** * MyDate类包含: * private成员变量year,month,day;并为每一个属性定义getter, setter 方法; * */ public class MyDate implements Comparable<MyDate>{ private int year; private int month; private int day; public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } public MyDate() { } public MyDate(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } @Override public String toString() { return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}'; } // @Override // public int compareTo(Object o) { // if(o instanceof MyDate){ // MyDate m = (MyDate)o; // // //比较年 // int minusYear = this.getYear() - m.getYear(); // if(minusYear != 0){ // return minusYear; // } // //比较月 // int minusMonth = this.getMonth() - m.getMonth(); // if(minusMonth != 0){ // return minusMonth; // } // //比较日 // return this.getDay() - m.getDay(); // } // // throw new RuntimeException("传入的数据类型不一致!"); // // } @Override public int compareTo(MyDate m) { //比较年 int minusYear = this.getYear() - m.getYear(); if(minusYear != 0){ return minusYear; } //比较月 int minusMonth = this.getMonth() - m.getMonth(); if(minusMonth != 0){ return minusMonth; } //比较日 return this.getDay() - m.getDay(); } }
2、Employee类
/** * 定义一个Employee类。 * 该类包含:private成员变量name,age,birthday, * 其中birthday 为MyDate 类的对象; * 并为每一个属性定义getter, setter 方法; * 并重写toString 方法输出name, age, birthday * */ public class Employee implements Comparable<Employee>{ private String name; private int age; private MyDate birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public MyDate getBirthday() { return birthday; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } public Employee() { } public Employee(String name, int age, MyDate birthday) { this.name = name; this.age = age; this.birthday = birthday; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'; } //没有指明泛型时的写法 //按name排序 // @Override // public int compareTo(Object o){ // if(o instanceof Employee){ // Employee e = (Employee)o; // return this.name.compareTo(e.name); // } return 0; // throw new RuntimeException("传入的数据类型不一致"); // } //指明泛型时的写法 @Override public int compareTo(Employee o) { return this.name.compareTo(o.name); } }
3、测试类
import org.junit.Test; import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; /** * 创建该类的5 个对象,并把这些对象放入TreeSet 集合中 * (下一章:TreeSet 需使用泛型来定义)分别按以下两种方式 * 对集合中的元素进行排序,并遍历输出: * * 1). 使Employee 实现Comparable 接口,并按name 排序 * 2). 创建TreeSet 时传入Comparator对象,按生日日期的先后排序。 */ public class EmployeeTest { //问题二:按生日日期的先后排序 @Test public void test2(){ TreeSet<Employee> set = new TreeSet<>(new Comparator<Employee>() { //使用泛型以后的写法 @Override public int compare(Employee o1, Employee o2) { MyDate b1 = o1.getBirthday(); MyDate b2 = o2.getBirthday(); return b1.compareTo(b2); } //使用泛型之前的写法 //@Override // public int compare(Object o1, Object o2) { // if(o1 instanceof Employee && o2 instanceof Employee){ // Employee e1 = (Employee)o1; // Employee e2 = (Employee)o2; // // MyDate b1 = e1.getBirthday(); // MyDate b2 = e2.getBirthday(); // //方式一: //比较年 int minusYear = b1.getYear() - b2.getYear(); if(minusYear != 0){ return minusYear; } //比较月 int minusMonth = b1.getMonth() - b2.getMonth(); if(minusMonth != 0){ return minusMonth; } //比较日 return b1.getDay() - b2.getDay(); // // //方式二: // return b1.compareTo(b2); // // } return 0; // throw new RuntimeException("传入的数据类型不一致!"); // } }); Employee e1 = new Employee("liudehua",55,new MyDate(1965,5,4)); Employee e2 = new Employee("zhangxueyou",43,new MyDate(1987,5,4)); Employee e3 = new Employee("guofucheng",44,new MyDate(1987,5,9)); Employee e4 = new Employee("liming",51,new MyDate(1954,8,12)); Employee e5 = new Employee("liangzhaowei",21,new MyDate(1978,12,4)); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator<Employee> iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } //问题一:使用自然排序 @Test public void test(){ TreeSet<Employee> set = new TreeSet<Employee>(); Employee e1 = new Employee("wangxianzhi",41,new MyDate(334,5,4)); Employee e2 = new Employee("simaqian",43,new MyDate(-145,7,12)); Employee e3 = new Employee("yanzhenqin",44,new MyDate(709,5,9)); Employee e4 = new Employee("zhangqian",51,new MyDate(-179,8,12)); Employee e5 = new Employee("quyuan",21,new MyDate(-340,12,4)); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator<Employee> iterator = set.iterator(); while (iterator.hasNext()){ Employee next = iterator.next(); System.out.println(next); } } }
03、自定义泛型结构
3.1、自定义泛型类举例
1、OrderTest类
/** * 自定义泛型类 在类名后面加上<T>代表当前类是一个泛型类 T就是一个个占位符号,可以是String 都有可能 * T是一个类型,使用当前类型 泛型只可以存放引用类型,不可以存放基本数据类型 */ public class OrderTest<T> { String orderName; int orderId; //类的内部结构就可以使用类的泛型 T orderT; public OrderTest(){ }; public OrderTest(String orderName,int orderId,T orderT){ this.orderName = orderName; this.orderId = orderId; this.orderT = orderT; } //如下的三个方法都不是泛型方法 public T getOrderT(){ return orderT; } public void setOrderT(T orderT){ this.orderT = orderT; } @Override public String toString() { return "Order{" + "orderName='" + orderName + '\'' + ", orderId=" + orderId + ", orderT=" + orderT + '}'; } //泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。 //换句话说,泛型方法所属的类是不是泛型类都没有关系。 //泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。 public static <E> List<E> copyFromArrayToList(E[] arr){ ArrayList<E> list = new ArrayList<>(); for(E e : arr){ list.add(e); } return list; } }
2、SubOrder类
public class SubOrder extends OrderTest<Integer>{ //SubOrder:不是泛型类 }
3、SubOrder1类
public class SubOrder1<T> extends OrderTest<T> {//SubOrder1<T>:仍然是泛型类 }
4、GenericTest1类
import org.junit.Test; /** * 如何自定义泛型结构:泛型类、泛型接口;泛型方法。 * * 1.关于自定义泛型类、泛型接口: */ public class GenericTest1 { @Test public void test(){ /** * 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型 * 要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。 */ OrderTest order = new OrderTest(); order.setOrderT(123); order.setOrderT("ABC"); //建议:实例化时指明类的泛型 OrderTest<String> order1 = new OrderTest<String>("orderAA",1001,"order:AA"); order1.setOrderT("AA:hello"); } @Test public void test2(){ SubOrder sub1 = new SubOrder(); //由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。 sub1.setOrderT(1122); SubOrder1<String> sub2 = new SubOrder1<>(); sub2.setOrderT("order2..."); } }
3.2、自定义泛型类泛型接口的注意点
注意点:
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass(){}
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
泛型不同的引用不能相互赋值。尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
经验:泛型要使用一路都用。要不用,一路都不要用。
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
异常类不能是泛型的
//异常类不能声明为泛型类
//public class MyException<T> extends Exception{
//}
代码演示:
不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
1、Person类
public class Person { }
2、OrderTest类
/** * 自定义泛型类 */ public class OrderTest<T> { String orderName; int orderId; //类的内部结构就可以使用类的泛型 T orderT; public OrderTest(){ //编译不通过 // T[] arr = new T[10]; //编译通过 T[] arr = (T[]) new Object[10]; }; public OrderTest(String orderName,int orderId,T orderT){ this.orderName = orderName; this.orderId = orderId; this.orderT = orderT; } public T getOrderT(){ return orderT; } public void setOrderT(T orderT){ this.orderT = orderT; } @Override public String toString() { return "Order{" + "orderName='" + orderName + '\'' + ", orderId=" + orderId + ", orderT=" + orderT + '}'; } //静态方法中不能使用类的泛型。 // public static void show(T orderT){ // System.out.println(orderT); // } public void show(){ //编译不通过 // try{ // // // }catch(T t){ // // } } }
3、GenericTest1类
import org.junit.Test; import java.util.ArrayList; /** * 如何自定义泛型结构:泛型类、泛型接口;泛型方法。 * * 1.关于自定义泛型类、泛型接口: * */ public class GenericTest1 { @Test public void test3(){ ArrayList<String> list1 = null; ArrayList<Integer> list2 = new ArrayList<Integer>(); //泛型不同的引用不能相互赋值。 //list1 = list2; Person p1 = null; Person p2 = null; p1 = p2; } }
4、在继承中使用泛型
-
子类不保留父类的泛型:按需实现
-
没有类型擦除
-
具体类型
-
-
子类保留父类的泛型:泛型子类
-
全部保留
-
部分保留
-
class Father<T1, T2> {} // 子类不保留父类的泛型 // 1)没有类型擦除 class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{} } // 2)具体类型 class Son2 extends Father<Integer, String> {} // 子类保留父类的泛型 // 1)全部保留 class Son3<T1, T2> extends Father<T1, T2> {} // 2)部分保留 class Son4<T2> extends Father<Integer, T2> {}
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。
3.3、自定义泛型方法举例
说明:
-
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
-
泛型方法的格式
[访问权限] <泛型> 返回类型 方法名([泛型标识参数名称]) 抛出的异常 如:public static <E> List<E> copyFromArrayToList(E[] arr) throws Exception{ }
代码演示:
1、OrderTest类
import java.util.ArrayList; import java.util.List; /** * 自定义泛型类 */ public class OrderTest<T> { /** * 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。 * 换句话说,泛型方法所属的类是不是泛型类都没有关系。 * 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。 */ public static <E> List<E> copyFromArrayToList(E[] arr){ ArrayList<E> list = new ArrayList<>(); for(E e : arr){ list.add(e); } return list; } }
2、测试类
import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * 如何自定义泛型结构:泛型类、泛型接口;泛型方法。 * * 1.关于自定义泛型类、泛型接口: * */ public class GenericTest1 { //测试泛型方法 @Test public void test4(){ OrderTest<String> order = new OrderTest<>(); Integer[] arr = new Integer[]{1,2,3,4}; //泛型方法在调用时,指明泛型参数的类型。 List<Integer> list = order.copyFromArrayToList(arr); System.out.println(list); } }
3、SubOrder类
import java.util.ArrayList; import java.util.List; public class SubOrder extends OrderTest<Integer>{ //SubOrder:不是泛型类 public static <E> List<E> copyFromArrayToList(E[] arr){//静态的泛型方法 ArrayList<E> list = new ArrayList<>(); for(E e : arr){ list.add(e); } return list; } }
3.4、举例泛型类和泛型方法的使用情境
1、DAO类
import java.util.List; public class DAO<T> { //表的共性操作的DAO //添加一条记录 public void add(T t){ } //删除一条记录 public boolean remove(int index){ return false; } //修改一条记录 public void update(int index,T t){ } //查询一条记录 public T getIndex(int index){ return null; } //查询多条记录 public List<T> getForList(int index){ return null; } //泛型方法 //举例:获取表中一共有多少条记录?获取最大的员工入职时间? public <E> E getValue(){ return null; } }
2、Customer类
public class Customer { //此类对应数据库中的customers表
}
3、CustomerDAO类
public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
}
4、Student类
public class Student {
}
5、StudentDAO类
public class StudentDAO extends DAO<Student> {//只能操作某一个表的DAO
}
6、DAOTest类
import org.junit.Test; import java.util.List; public class DAOTest { @Test public void test(){ CustomerDAO dao1 = new CustomerDAO(); dao1.add(new Customer()); List<Customer> list = dao1.getForList(10); StudentDAO dao2 = new StudentDAO(); Student student = dao2.getIndex(1); } }
04、泛型在继承上的体现【通配符】
import org.junit.Test; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; /** * 1.泛型在继承方面的体现 * * 2.通配符的使用 * */ public class GenericTest { /** * 1.泛型在继承方面的体现 * 虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。 * 补充:类A是类B的父类,A<G> 是 B<G> 的父类 */ @Test public void test(){ Object obj = null; String str = null; obj = str; Object[] arr1 = null; String[] arr2 = null; arr1 = arr2; //编译不通过 // Date date = new Date(); // str = date; List<Object> list1 = null; List<String> list2 = new ArrayList<String>(); //此时的list1和list2的类型不具有子父类关系 //编译不通过 // list1 = list2; /** * 反证法: * 假设list1 = list2; * list1.add(123);导致混入非String的数据。出错。 */ show(list1); show2(list2); } public void show2(List<String> list){ } public void show(List<Object> list){ } @Test public void test2(){ AbstractList<String> list1 = null; List<String> list2 = null; ArrayList<String> list3 = null; list1 = list3; list2 = list3; List<String> list4 = new ArrayList<>(); } }
05、通配符的使用
说明:
通配符:?
比如:List<?> ,Map<?,?> List<?>是List、List等各种泛型List的父类。 2.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。
唯一的例外是null,它是所有类型的成员。
将任意元素加入到其中不是类型安全的:
- Collection<?> c = new ArrayList();
- c.add(new Object()); // 编译时错误因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
4.另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object。
import org.junit.Test; import java.util.AbstractList; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 1.泛型在继承方面的体现 * 2.通配符的使用 */ public class GenericTest { /** * 2.通配符的使用 * 通配符:? * * 类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?> */ @Test public void test3(){ List<Object> list1 = null; List<String> list2 = null; List<?> list = null; list = list1; list = list2; //编译通过 print(list1); print(list2); } public void print(List<?> list){ Iterator<?> iterator = list.iterator(); while(iterator.hasNext()){ Object obj = iterator.next(); System.out.println(obj); } } }
5.1、使用通配符后数据的读取和写入要求

import org.junit.Test; import java.util.AbstractList; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 1.泛型在继承方面的体现 * * 2.通配符的使用 */ public class GenericTest { /** * 2.通配符的使用 * 通配符:? * * 类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?> */ @Test public void test3(){ List<Object> list1 = null; List<String> list2 = null; List<?> list = null; list = list1; list = list2; //编译通过 // print(list1); // print(list2); List<String> list3 = new ArrayList<>(); list3.add("AA"); list3.add("BB"); list3.add("CC"); list = list3; //添加(写入):对于List<?>就不能向其内部添加数据。 //除了添加null之外。 // list.add("DD"); // list.add('?'); list.add(null); //获取(读取):允许读取数据,读取的数据类型为Object。 Object o = list.get(0); System.out.println(o); } }
5.2、有限制条件的通配符的使用
说明:
-
<?> 允许所有泛型的引用调用
-
通配符指定上限
extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
-
通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=
-
举例:
<?extends Number> (无穷小, Number] 只允许泛型为Number及Number子类的引用调用
<? super Number> [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用
<? extends Comparable> 只允许泛型为实现Comparable接口的实现类的引用调用
代码演示:
1、
public class Person {
}
2、Student类
public class Student extends Person{
}
3、测试类
import org.junit.Test; import java.util.AbstractList; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 1.泛型在继承方面的体现 * * 2.通配符的使用 */ public class GenericTest { /** * 3.有限制条件的通配符的使用。 * * ? extends A: * G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类 * * ? super A: * G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类 */ @Test public void test4(){ List<? extends Person> list1 = null; List<? super Person> list2 = null; // List<Student> list3 = null; // List<Student> list4 = null; // List<Student> list5 = null; List<Student> list3 = new ArrayList<Student>(); List<Person> list4 = new ArrayList<Person>(); List<Object> list5 = new ArrayList<Object>(); list1 = list3; list1 = list4; // list1 = list5; // list2 = list3; list2 = list4; list2 = list5; //读取数据: list1 = list3; Person p = list1.get(0); //编译不通过 //Student s = list1.get(0); list2 = list4; Object obj = list2.get(0); 编译不通过 // Person obj = list2.get(0); //写入数据: //编译不通过 // list1.add(new Student()); //编译通过 list2.add(new Person()); list2.add(new Student()); } }
06、泛型应用举例
6.1、泛型嵌套
public static void main(String[] args) { HashMap<String, ArrayList<Citizen>> map= new HashMap<String, ArrayList<Citizen>>(); ArrayList<Citizen> list= new ArrayList<Citizen>(); list.add(new Citizen("刘恺威")); list.add(new Citizen("杨幂")); list.add(new Citizen("小糯米")); map.put("刘恺威", list); Set<Entry<String, ArrayList<Citizen>>> entrySet= map.entrySet(); Iterator<Entry<String, ArrayList<Citizen>>> iterator= entrySet.iterator(); while(iterator.hasNext()) { Entry<String, ArrayList<Citizen>> entry= iterator.next(); String key= entry.getKey(); ArrayList<Citizen> value= entry.getValue(); System.out.println("户主:"+ key); System.out.println("家庭成员:"+ value); } }

interface Info{ // 只有此接口的子类才是表示人的信息 } class Contact implements Info{ // 表示联系方式 private String address ; // 联系地址 private String telephone ; // 联系方式 private String zipcode ; // 邮政编码 public Contact(String address,String telephone,String zipcode){ this.address = address; this.telephone = telephone; this.zipcode = zipcode; } public void setAddress(String address){ this.address = address ; } public void setTelephone(String telephone){ this.telephone = telephone ; } public void setZipcode(String zipcode){ this.zipcode = zipcode; } public String getAddress(){ return this.address ; } public String getTelephone(){ return this.telephone ; } public String getZipcode(){ return this.zipcode; } @Override public String toString() { return "Contact [address=" + address + ", telephone=" + telephone + ", zipcode=" + zipcode + "]"; } } class Introduction implements Info{ private String name ; // 姓名 private String sex ; // 性别 private int age ; // 年龄 public Introduction(String name,String sex,int age){ this.name = name; this.sex = sex; this.age = age; } public void setName(String name){ this.name = name ; } public void setSex(String sex){ this.sex = sex ; } public void setAge(int age){ this.age = age ; } public String getName(){ return this.name ; } public String getSex(){ return this.sex ; } public int getAge(){ return this.age ; } @Override public String toString() { return "Introduction [name=" + name + ", sex=" + sex + ", age=" + age + "]"; } } class Person<T extends Info>{ private T info ; public Person(T info){ // 通过构造器设置信息属性内容 this.info = info; } public void setInfo(T info){ this.info = info ; } public T getInfo(){ return info ; } @Override public String toString() { return "Person [info=" + info + "]"; } } public class GenericPerson{ public static void main(String args[]){ Person<Contact> per = null ; // 声明Person对象 per = new Person<Contact>(new Contact("北京市","01088888888","102206")) ; System.out.println(per); Person<Introduction> per2 = null ; // 声明Person对象 per2 = new Person<Introduction>(new Introduction("李雷","男",24)); System.out.println(per2) ; } }
07、自定义泛型类练习

代码演示:
import java.util.*; /** * 定义个泛型类 DAO<T>,在其中定义一个Map 成员变量,Map 的键为 String 类型,值为 T 类型。 * * 分别创建以下方法: * public void save(String id,T entity): 保存 T 类型的对象到 Map 成员变量中 * public T get(String id):从 map 中获取 id 对应的对象 * public void update(String id,T entity):替换 map 中key为id的内容,改为 entity 对象 * public List<T> list():返回 map 中存放的所有 T 对象 * public void delete(String id):删除指定 id 对象 */ public class DAO<T> { private Map<String,T> map = new HashMap<String,T>(); //保存 T 类型的对象到 Map 成员变量中 public void save(String id,T entity){ map.put(id,entity); } //从 map 中获取 id 对应的对象 public T get(String id){ return map.get(id); } //替换 map 中key为id的内容,改为 entity 对象 public void update(String id,T entity){ if(map.containsKey(id)){ map.put(id,entity); } } //返回 map 中存放的所有 T 对象 public List<T> list(){ //错误的: // Collection<T> values = map.values(); // return (List<T>) values; //正确的: ArrayList<T> list = new ArrayList<>(); Collection<T> values = map.values(); for(T t : values){ list.add(t); } return list; } //删除指定 id 对象 public void delete(String id){ map.remove(id); } }
2、User类
/** * 定义一个 User 类: * 该类包含:private成员变量(int类型) id,age;(String 类型)name。 */ public class User { private int id; private int age; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } public User() { } @Override public String toString() { return "User{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (id != user.id) return false; if (age != user.age) return false; return name != null ? name.equals(user.name) : user.name == null; } @Override public int hashCode() { int result = id; result = 31 * result + age; result = 31 * result + (name != null ? name.hashCode() : 0); return result; } }
3、测试类
import java.util.List; /** * 创建 DAO 类的对象, 分别调用其 save、get、update、list、delete * 方法来操作 User 对象,使用 Junit 单元测试类进行测试。 */ public class DAOTest { public static void main(String[] args) { DAO<User> dao = new DAO<User>(); dao.save("1001",new User(1001,34,"周杰伦")); dao.save("1002",new User(1002,20,"昆凌")); dao.save("1003",new User(1003,25,"蔡依林")); dao.update("1003",new User(1003,30,"方文山")); dao.delete("1002"); List<User> list = dao.list(); // System.out.println(list); list.forEach(System.out::println); } }
枚举
01、枚举类的使用
1.1、枚举类的理解
类的对象只有有限个,确定的。举例如下:
星期:Monday(星期一)、…、Sunday(星期天)
性别:Man(男)、Woman(女)
季节:Spring(春节)…Winter(冬天)
支付方式:Cash(现金)、WeChatPay(微信)、Alipay(支付宝)、BankCard(银行卡)、CreditCard(信用卡)
就职状态:Busy、Free、Vocation、Dimission
订单状态:Nonpayment(未付款)、Paid(已付款)、Delivered(已发货)、Return(退货)、Checked(已确认)Fulfilled(已配货)、
线程状态:创建、就绪、运行、阻塞、死亡
当需要定义一组常量时,强烈建议使用枚举类
枚举类的实现
JDK1.5之前需要自定义枚举类
JDK 1.5 新增的enum 关键字用于定义枚举类
若枚举只有一个对象, 则可以作为一种单例模式的实现方式。
1.2、自定义枚举类
枚举类的属性
- 枚举类对象的属性不应允许被改动, 所以应该使用`private final`修饰
- 枚举类的使用`private final` 修饰的属性应该在构造器中为其赋值
- 若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数
/** * 一、枚举类的使用 * 1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类。 * 2.当需要定义一组常量时,强烈建议使用枚举类 * 3.若枚举只有一个对象, 则可以作为一种单例模式的实现方式。 * * 二、如何定义枚举类 * 方式一:JDK1.5之前需要自定义枚举类 * 方式二:JDK 1.5 新增的enum 关键字用于定义枚举类 * */ public class SeasonTest { public static void main(String[] args) { Season spring = Season.SPRING; System.out.println(spring); } } //自定义枚举类 class Season{ //1.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //2.私有化类的构造器,并给对象属性赋值 private Season(String seasonName,String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //3.提供当前枚举类的多个对象 public static final Season SPRING = new Season("春天","万物复苏"); public static final Season SUMMER = new Season("夏天","烈日炎炎"); public static final Season AUTUMN = new Season("秋天","金秋送爽"); public static final Season WINTER = new Season("冬天","白雪皑皑"); //4.其他诉求:获取枚举类对象的属性 public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } //4.其他诉求1:提供toString() @Override public String toString() { return "Season{" + "seasonName='" + seasonName + '\'' + ", seasonDesc='" + seasonDesc + '\'' + '}'; } }
1.3、使用enum关键字定义枚举类
使用说明
使用enum定义的枚举类默认继承了java.lang.Enum类,因此不能再继承其他类
枚举类的构造器只能使用private 权限修饰符
枚举类的所有实例必须在枚举类中显式列出(, 分隔; 结尾)。列出的实例系统会自动添加public static final 修饰
必须在枚举类的第一行声明枚举类对象
JDK 1.5 中可以在switch 表达式中使用Enum定义的枚举类的对象作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举类作为限定。
/** * 使用enum关键字定义枚举类 * 说明:定义的枚举类默认继承于java.lang.Enum类 */ public class SeasonTest1 { public static void main(String[] args) { Season1 summer = Season1.SUMMER; //toString(): System.out.println(summer.toString()); System.out.println(Season1.class.getSuperclass()); } } //使用enum关键字枚举类 enum Season1{ //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束 SPRING("春天","万物复苏"), SUMMER("夏天","烈日炎炎"), AUTUMN("秋天","金秋送爽"), WINTER("冬天","白雪皑皑"); //2.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //3.私有化类的构造器,并给对象属性赋值 private Season1(String seasonName,String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //4.其他诉求:获取枚举类对象的属性 public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } //4.其他诉求1:提供toString() // @Override // public String toString() { // return "Season{" + // "seasonName='" + seasonName + '\'' + // ", seasonDesc='" + seasonDesc + '\'' + // '}'; // } }
1.4、Enum类中的常用方法

Enum类的主要方法:
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
toString():返回当前枚举类对象常量的名称
/** * 使用enum关键字定义枚举类 * 说明:定义的枚举类默认继承于java.lang.Enum类 * * 三、Enum类的常用方法 * values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。 * valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。 * toString():返回当前枚举类对象常量的名称 */ public class SeasonTest1 { public static void main(String[] args) { Season1 summer = Season1.SUMMER; //toString(): System.out.println(summer.toString()); // System.out.println(Season1.class.getSuperclass()); System.out.println("**************************"); //values():返回所有的枚举类对象构成的数组 Season1[] values = Season1.values(); for(int i = 0;i < values.length;i++){ System.out.println(values[i]); } System.out.println("****************************"); Thread.State[] values1 = Thread.State.values(); for(int i = 0;i < values1.length;i++){ System.out.println(values1[i]); } //valueOf(String objName):返回枚举类中对象名是objName的对象。 Season1 winter = Season1.valueOf("WINTER"); //如果没有objName的枚举类对象,则抛异常:IllegalArgumentException // Season1 winter = Season1.valueOf("WINTER1"); System.out.println(winter); } } //使用enum关键字枚举类 enum Season1{ //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束 SPRING("春天","万物复苏"), SUMMER("夏天","烈日炎炎"), AUTUMN("秋天","金秋送爽"), WINTER("冬天","白雪皑皑"); //2.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //3.私有化类的构造器,并给对象属性赋值 private Season1(String seasonName,String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //4.其他诉求:获取枚举类对象的属性 public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } //4.其他诉求1:提供toString() // @Override // public String toString() { // return "Season{" + // "seasonName='" + seasonName + '\'' + // ", seasonDesc='" + seasonDesc + '\'' + // '}'; // } }
1.5、使用enum关键字定义的枚举类实现接口
/** * 使用enum关键字定义枚举类 * 说明:定义的枚举类默认继承于java.lang.Enum类 * * 四、使用enum关键字定义的枚举类实现接口的情况 * 情况一:实现接口,在enum类中实现抽象方法 * 情况二:让枚举类的对象分别实现接口中的抽象方法 */ public class SeasonTest1 { public static void main(String[] args) { //values():返回所有的枚举类对象构成的数组 Season1[] values = Season1.values(); for(int i = 0;i < values.length;i++){ System.out.println(values[i]); values[i].show(); } //valueOf(String objName):返回枚举类中对象名是objName的对象。 Season1 winter = Season1.valueOf("WINTER"); winter.show(); } } interface Info{ void show(); } //使用enum关键字枚举类 enum Season1 implements Info{ //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束 SPRING("春天","春暖花开"){ @Override public void show() { System.out.println("一元复始、万物复苏"); } }, SUMMER("夏天","夏日炎炎"){ @Override public void show() { System.out.println("蝉声阵阵、烈日当空"); } }, AUTUMN("秋天","秋高气爽"){ @Override public void show() { System.out.println("天高气清、金桂飘香"); } }, WINTER("冬天","冰天雪地"){ @Override public void show() { System.out.println("寒冬腊月、滴水成冰"); } }; //2.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //3.私有化类的构造器,并给对象属性赋值 private Season1(String seasonName,String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //4.其他诉求:获取枚举类对象的属性 public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } //4.其他诉求1:提供toString() // @Override // public String toString() { // return "Season{" + // "seasonName='" + seasonName + '\'' + // ", seasonDesc='" + seasonDesc + '\'' + // '}'; // } // @Override // public void show() { // System.out.println("这是一个季节。"); // } }
注解的使用
2.1、注解的理解
从JDK 1.5 开始, Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解)
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在Annotation 的“name=value” 对中。
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架= 注解+ 反射+ 设计模式
2.2、Annotation的使用示例
使用Annotation 时要在其前面增加@ 符号, 并把该Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
示例一:生成文档相关的注解
@author标明开发该类模块的作者,多个作者之间使用,分割
@version标明该类模块的版本
@see参考转向,也就是相关主题
@since从哪个版本开始增加的
@param对方法中某参数的说明,如果没有参数就不能写
@return对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不能写其中
@param@return和@exception这三个标记都是只用于方法的。
@param的格式要求:@param形参名形参类型形参说明
@return的格式要求:@return返回值类型返回值说明
@exception的格式要求:@exception异常类型异常说明
@param和@exception可以并列多个
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。
spring框架中关于“事务”的管理
内置的注解
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
作用在代码的注解是
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
作用在其他注解的注解(或者说 元注解)是:
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 成员。(方法,属性,类型)
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
从 Java 7 开始,额外添加了 3 个注解:
@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
import java.util.ArrayList; import java.util.Date; /** * 注解的使用 * * 1. 理解Annotation: * ① jdk 5.0 新增的功能 * * ② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, * 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。 * * ③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android * 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 * 代码和XML配置等。 * * 2. Annocation的使用示例 * 示例一:生成文档相关的注解 * 示例二:在编译时进行格式检查(JDK内置的三个基本注解) * @Override: 限定重写父类方法, 该注解只能用于方法 * @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择 * @SuppressWarnings: 抑制编译器警告 * * 示例三:跟踪代码依赖性,实现替代配置文件功能 */ public class AnnotationTest { public static void main(String[] args) { Person p = new Student(); p.walk(); Date date = new Date(2020, 10, 11); System.out.println(date); @SuppressWarnings("unused") int num = 10; // System.out.println(num); @SuppressWarnings({ "unused", "rawtypes" }) ArrayList list = new ArrayList(); } } class Person{ private String name; private int age; public Person() { super(); } public Person(String name, int age) { this.name = name; this.age = age; } public void walk(){ System.out.println("学习中……"); } public void eat(){ System.out.println("摸鱼中……"); } } interface Info{ void show(); } class Student extends Person implements Info{ @Override public void walk() { System.out.println("喷子走开"); } @Override public void show() { } }
2.3、如何自定义注解
定义新的Annotation类型使用**@interface**关键字
自定义注解自动继承了**java.lang.annotation.Annotation**接口
Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用**default**关键字
如果只有一个参数成员,建议使用参数名为value
如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名=参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据Annotation
注意:自定义注解必须配上注解的信息处理流程才有意义。
public @interface MyAnnotation { String value(); } /** * 注解的使用 * * 3.如何自定义注解:参照@SuppressWarnings定义 * ① 注解声明为:@interface * ② 内部定义成员,通常使用value表示 * ③ 可以指定成员的默认值,使用default定义 * ④ 如果自定义注解没有成员,表明是一个标识作用。 * * 如果注解有成员,在使用注解时,需要指明成员的值。 * 自定义注解必须配上注解的信息处理流程(使用反射)才有意义。 * 自定义注解通过都会指明两个元注解:Retention、Target * */ @MyAnnotation(value = "hello")
2.4、jdk中4个基本的元注解的使用1
JDK 的元Annotation 用于修饰其他Annotation 定义
JDK5.0提供了4个标准的meta-annotation类型,分别是:
Retention
Target
Documented
Inherited
元数据的理解:String name = “MyBlog”;
@Retention: 只能用于修饰一个Annotation定义, 用于指定该Annotation 的生命周期, @Rentention包含一个RetentionPolicy类型的成员变量, 使用@Rentention时必须为该value 成员变量指定值:
RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行Java 程序时, JVM 不会保留注解。这是默认值
RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。

import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { String value(); } /** * 注解的使用 * * 4.jdk 提供的4种元注解 * 元注解:对现有的注解进行解释说明的注解 * Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME * 只有声明为RUNTIME生命周期的注解,才能通过反射获取。 * Target: * Documented: * Inherited: * */ public class AnnotationTest { public static void main(String[] args) { } } @MyAnnotation(value = "hello") class Person{ private String name; private int age; public Person() { super(); } @MyAnnotation(value = "jack") public Person(String name, int age) { this.name = name; this.age = age; } public void walk(){ System.out.println("学习中……"); } public void eat(){ System.out.println("摸鱼中……"); } }
2.5、jdk中4个基本的元注解的使用2
-
@Target: 用于修饰Annotation 定义, 用于指定被修饰的Annotation 能用于修饰哪些程序元素。@Target 也包含一个名为value 的成员变量。

@Documented: 用于指定被该元Annotation 修饰的Annotation 类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。
定义为Documented的注解必须设置Retention值为RUNTIME。
@Inherited: 被它修饰的Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的Annotation, 则其子类将自动具有该注解。
比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解
实际应用中,使用较少
import org.junit.Test; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Date; /** * 注解的使用 * * 4.jdk 提供的4种元注解 * 元注解:对现有的注解进行解释说明的注解 * Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME * 只有声明为RUNTIME生命周期的注解,才能通过反射获取。 * Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素 * *******出现的频率较低******* * Documented:表示所修饰的注解在被javadoc解析时,保留下来。 * Inherited:被它修饰的 Annotation 将具有继承性。 * * 5.通过反射获取注解信息 ---到反射内容时系统讲解 */ public class AnnotationTest { public static void main(String[] args) { } @Test public void testGetAnnotation(){ Class clazz = Student.class; Annotation[] annotations = clazz.getAnnotations(); for(int i = 0;i < annotations.length;i++){ System.out.println(annotations[i]); } } } @MyAnnotation(value = "hello") class Person{ private String name; private int age; public Person() { super(); } @MyAnnotation public Person(String name, int age) { this.name = name; this.age = age; } @MyAnnotation public void walk(){ System.out.println("学习中……"); } public void eat(){ System.out.println("摸鱼中……"); } } @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE}) public @interface MyAnnotation { String value() default "book"; }
2.6、利用反射获取注解信息
JDK 5.0 在java.lang.reflect包下新增了AnnotatedElement接口, 该接口代表程序中可以接受注解的程序元素
当一个Annotation 类型被定义为运行时Annotation 后, 该注解才是运行时可见, 当class文件被载入时保存在class 文件中的Annotation 才会被虚拟机读取
程序可以调用AnnotatedElement对象的如下方法来访问Annotation 信息

2.7、jdk8新特性:可重复注解
Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。此外,反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解
可重复注解示例:
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; @Retention(RetentionPolicy.RUNTIME) @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) public @interface MyAnnotations { MyAnnotation[] value(); }
2.8、jdk8新特性:类型注解
JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER,TYPE_USE。
在Java8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中。
import java.util.ArrayList; /** * 注解的使用 * * 6.jdk 8 中注解的新特性:可重复注解、类型注解 * * 6.1可重复注解:① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class * ② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。 * * 6.2类型注解: * ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。 * ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。 * */ public class AnnotationTest { } class Generic<@MyAnnotation T>{ public void show() throws @MyAnnotation RuntimeException{ ArrayList<@MyAnnotation String> list = new ArrayList<>(); int num = (@MyAnnotation int) 10L; } }
MyAnnotation
import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; @Retention(RetentionPolicy.RUNTIME) @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE}) public @interface MyAnnotation { String value() default "hello"; }
2.9、如何自定义注解
注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。因此可以得出自定义注解使用的基本流程:
第一步,定义注解——相当于定义标记; 第二步,配置注解——把标记打在需要用到的程序代码中; 第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。
基本语法(第一步)
注解类型的声明部分:
注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
public @interface CherryAnnotation {
}
注解类型的实现部分:
根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。咱们来看看其语法:
public @interface CherryAnnotation { public String name(); int age() default 18;; int[] array(); }
定义注解类型元素时需要注意如下几点:
1、访问修饰符必须为public,不写默认为public; 2、该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组; 3、该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作); 4、()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法; 5、efault代表默认值,值必须和第2点定义的类型一致; 6、如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在后面的章节中可以看到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。
常用的元注解
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。
2.9.1@Target
@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下
public enum ElementType { /** 类,接口(包括注解类型)或枚举的声明 */ TYPE, /** 属性的声明 */ FIELD, /** 方法的声明 */ METHOD, /** 方法形式参数声明 */ PARAMETER, /** 构造方法的声明 */ CONSTRUCTOR, /** 局部变量声明 */ LOCAL_VARIABLE, /** 注解类型声明 */ ANNOTATION_TYPE, /** 包的声明 */ PACKAGE }
//@CherryAnnotation被限定只能使用在类、接口或方法上面 @Target(value = {ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) public @interface CherryAnnotation { String name(); int age() default 18; int[] array(); }
2.9.2 @Retention
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。 注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。同样使用了RetentionPolicy枚举类型定义了三个阶段:
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. * (注解将被编译器忽略掉) */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. * (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为) */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到) * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
1、如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到; 2、如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到; 3、如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME; 4、在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。
2.9.3 @Documented
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
2.9.4@Inherited
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
在具体的Java类上使用注解(第二步)
首先,定义一个注解、和一个供注解修饰的简单Java类
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @Documented public @interface CherryAnnotation { String name(); int age() default 18; int[] score(); }
public class Student{ public void study(int times){ for(int i = 0; i < times; i++){ System.out.println("Good Good Study, Day Day Up!"); } } }
1、CherryAnnotation的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方,即:public void study(int times)之上; 2、由于我们在CherryAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;
最终写法(第三步,使用自定义后的注解)
public class Student { @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77}) public void study(int times){ for(int i = 0; i < times; i++){ System.out.println("Good Good Study, Day Day Up!"); } } }
特殊语法
特殊语法一
如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.TYPE}) @Documented public @interface FirstAnnotation { }
//等效于@FirstAnnotation() @FirstAnnotation public class JavaBean{ //省略实现部分 }
特殊语法二
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.TYPE}) @Documented public @interface SecondAnnotation { String value(); }
//等效于@ SecondAnnotation(value = "this is second annotation") @SecondAnnotation("this is annotation") public class JavaBean{ //省略实现部分 }
特殊用法三
如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.TYPE}) @Documented public @interface ThirdAnnotation { String[] name(); }
//等效于@ ThirdAnnotation(name = {"this is third annotation"}) @ ThirdAnnotation(name = "this is third annotation") public class JavaBean{ //省略实现部分 }
特殊用法四
Lambda表达式
函数式接口
在java中一个接口中有且只有一个 抽象方法叫做函数式接口
@FunctionalInterface注解
这个注解有以下特点:
1、该注解只能标记在"有且仅有一个抽象方法"的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
public class MyFunApp { public static void main(String[] args) { System.out.println(getResult((num)->num,2)); } public static int getResult(MyFun my,int a){ return my.getResult(a); } } public interface MyFun { public int getResult(int a); } public class MyFunImpl implements MyFun { @Override public int getResult(int a) { return a; } }
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
特征:
以下是lambda表达式的重要特征:
-
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
-
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
-
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
-
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值
Lambda 表达式实例
// 1. 不需要参数,返回值为 5 () -> 5 // 2. 接收一个参数(数字类型),返回其2倍的值 x -> 2 * x // 3. 接受2个参数(数字),并返回他们的差值 (x, y) -> x – y // 4. 接收2个int型整数,返回他们的和 (int x, int y) -> x + y // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) (String s) -> System.out.print(s)
在 Java8Tester.java 文件输入以下代码:
public class Java8Tester { public static void main(String args[]){ Java8Tester tester = new Java8Tester(); // 类型声明 MathOperation addition = (int a, int b) -> a + b; // 不用类型声明 MathOperation subtraction = (a, b) -> a - b; // 大括号中的返回语句 MathOperation multiplication = (int a, int b) -> { return a * b; }; // 没有大括号及返回语句 MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); // 不用括号 GreetingService greetService1 = message -> System.out.println("Hello " + message); // 用括号 GreetingService greetService2 = (message) -> System.out.println("Hello " + message); greetService1.sayMessage("Runoob"); greetService2.sayMessage("Google"); } interface MathOperation { int operation(int a, int b); } interface GreetingService { void sayMessage(String message); } private int operate(int a, int b, MathOperation mathOperation){ return mathOperation.operation(a, b); } }
使用 Lambda 表达式需要注意以下两点:
-
Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
-
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
在 Java8Tester.java 文件输入以下代码:
public class Java8Tester { final static String salutation = "Hello! "; public static void main(String args[]){ GreetingService greetService1 = message -> System.out.println(salutation + message); greetService1.sayMessage("Runoob"); } interface GreetingService { void sayMessage(String message); } }
我们也可以直接在 lambda 表达式中访问外层的局部变量:
public class Java8Tester { public static void main(String args[]) { final int num = 1; Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); // 输出结果为 3 } public interface Converter<T1, T2> { void convert(int i); } }
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1; Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); num = 5; //报错信息:Local variable num defined in an enclosing scope must be final or effectively final
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量
String first = ""; Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
浙公网安备 33010602011771号