Java
3.5 引用类型转换
3.5.1 为什么要转型
多态的写法就无法访问子类独有功能了。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
回顾基本数据类型转换
- 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
- 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
3.5.2 向上转型(自动转换)
- 向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。 当父类引用指向一个子类对象时,便是向上转型。 使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
2
**原因是:父类类型相对于子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。**所以子类范围小可以直接自动转型给父类类型的变量。
3.5.3 向下转型(强制转换)
- 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。 一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
2
3
3.5.4 案例演示
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
转型演示,代码如下:
定义类:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
定义测试类:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}
2
3
4
5
6
7
8
9
10
11
3.5.5 转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
2
3
4
5
6
7
8
9
10
11
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
3.5.6 instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
2
3
所以,转换前,我们最好先做一个判断,代码如下:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
关键字
2.7 super(...)和this(...)
2.7.1 引入
class Person {
private String name;
private int age;
public Person() {
System.out.println("父类无参");
}
// getter/setter省略
}
class Student extends Person {
private double score;
public Student() {
//super(); // 调用父类无参构造器,默认就存在,可以不写,必须再第一行
System.out.println("子类无参");
}
public Student(double score) {
//super(); // 调用父类无参构造器,默认就存在,可以不写,必须再第一行
this.score = score;
System.out.println("子类有参");
}
// getter/setter省略
}
public class Demo07 {
public static void main(String[] args) {
// 调用子类有参数构造器
Student s2 = new Student(99.9);
System.out.println(s2.getScore()); // 99.9
System.out.println(s2.getName()); // 输出 null
System.out.println(s2.getAge()); // 输出 0
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
我们发现,子类有参数构造器只是初始化了自己对象中的成员变量score,而父类中的成员变量name和age依然是没有数据的,怎么解决这个问题呢,我们可以借助与super(...)去调用父类构造器,以便初始化继承自父类对象的name和age.
2.7.2 super和this的用法格式
super和this完整的用法如下:
this.成员变量 -- 本类的
super.成员变量 -- 父类的
this.成员方法名() -- 本类的
super.成员方法名() -- 父类的
2
3
4
5
接下来我们使用调用构造器格式:
super(...) -- 调用父类的构造器,根据参数匹配确认
this(...) -- 调用本类的其他构造器,根据参数匹配确认
2
2.7.3 super(....)用法演示
代码如下:
class Person {
private String name ="凤姐";
private int age = 20;
public Person() {
System.out.println("父类无参");
}
public Person(String name , int age){
this.name = name ;
this.age = age ;
}
// getter/setter省略
}
class Student extends Person {
private double score = 100;
public Student() {
//super(); // 调用父类无参构造器,默认就存在,可以不写,必须再第一行
System.out.println("子类无参");
}
public Student(String name , int age,double score) {
super(name ,age);// 调用父类有参构造器Person(String name , int age)初始化name和age
this.score = score;
System.out.println("子类有参");
}
// getter/setter省略
}
public class Demo07 {
public static void main(String[] args) {
// 调用子类有参数构造器
Student s2 = new Student("张三",20,99);
System.out.println(s2.getScore()); // 99
System.out.println(s2.getName()); // 输出 张三
System.out.println(s2.getAge()); // 输出 20
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
注意:
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
super(..)是根据参数去确定调用父类哪个构造器的。
2.7.4 super(...)案例图解
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造器调用时,一定先调用父类的构造器。理解图解如下:
2.7.5 this(...)用法演示
- 默认是去找本类中的其他构造器,根据参数来确定具体调用哪一个构造器。
- 为了借用其他构造器的功能。
package com.itheima._08this和super调用构造器;
/**
* this(...):
* 默认是去找本类中的其他构造器,根据参数来确定具体调用哪一个构造器。
* 为了借用其他构造器的功能。
*
*/
public class ThisDemo01 {
public static void main(String[] args) {
Student xuGan = new Student();
System.out.println(xuGan.getName()); // 输出:徐干
System.out.println(xuGan.getAge());// 输出:21
System.out.println(xuGan.getSex());// 输出: 男
}
}
class Student{
private String name ;
private int age ;
private char sex ;
public Student() {
// 很弱,我的兄弟很牛逼啊,我可以调用其他构造器:Student(String name, int age, char sex)
this("徐干",21,'男');
}
public Student(String name, int age, char sex) {
this.name = name ;
this.age = age ;
this.sex = sex ;
}
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 char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2.7.6 小结
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
super(..)和this(...)是根据参数去确定调用父类哪个构造器的。
super(..)可以调用父类构造器初始化继承自父类的成员变量的数据。
this(..)可以调用本类中的其他构造器。
容器
数据库
JDBC
JDBC(Java DataBase Connectivity),是一种用于执行SQL语句的Java API,是一套接口规范,为多种关系数据库提供统一访问;它由一组用Java语言编写的类和接口组成。在Java的标准库java.sql里放着,不过这里面大部分都是接口。接口并不能直接实例化,而是必须实例化对应的实现类,然后通过接口引用这个实例。因为JDBC接口并不知道我们要使用哪个数据库,所以,用哪个数据库,我们就去使用哪个数据库的“实现类”,我们把某个数据库实现了JDBC接口的jar包称为JDBC驱动。因为我们选择了MySQL 5.x作为数据库,所以我们首先得找一个MySQL的JDBC驱动。所谓JDBC驱动,其实就是一个第三方jar包。
数据库厂商想与Java进行连接,数据库厂商自己必须实现JDBC这套接口,而数据库厂商的JDBC实现,我们就叫它此数据库的数据库驱动;
有了JDBC,程序员只需用JDBC API写一个程序,就可以访问所有数据库;将Java语言和JDBC结合起来使程序员不必为不同的平台编写不同的应用程序,只须写一遍程序就可以让它在任何平台上运行,这也是Java语言“编写一次,处处运行”的优势。
JDBC API
- 提供者:Sun公司
- 内容:供程序员调用的接口与类,集成在java.sql和javax.sql包中,如
- DriverManager类 作用:管理各种不同的JDBC驱动
- Connection接口
- Statement接口
- ResultSet接口
JDBC 驱动
- 提供者:数据库厂商
- 作用:负责连接各种不同的数据库
- JDBC对Java程序员而言是API,对实现与数据库连接的服务提供商而言是接口模型。
三方关系
- SUN公司是规范制定者,制定了规范JDBC(连接数据库规范)
- 数据库厂商微软、甲骨文等分别提供实现JDBC接口的驱动jar包
- 程序员学习JDBC规范来应用这些jar包里的类。
JDBC访问数据库步骤
加载Driver驱动
加载JDBC驱动是通过调用方法java.lang.Class.forName(),下面列出常用的几种数据库驱动程序
加载语句的形式 :
Class.forName("oracle.jdbc.driver.OracleDriver"”);//使用Oracle的JDBC驱动程序
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");//使用SQL Server的JDBC驱动程序
Class.forName("com.ibm.db2.jdbc.app.DB2Driver");//使用DB2的JDBC驱动程序
Class.forName("com.mysql.jdbc.Driver");//使用MySql的JDBC驱动程序
2
3
4
创建数据库连接(Connection)
与数据库建立连接的方法是调用DriverManager.getConnection(String url, String user, String** **password )
方法
Connection conn=null;
String url="jdbc:oracle:thin:@localhost:1521:orcl";
String user=“scott";
String password=“tiger";
conn = DriverManager.getConnection(url, user, password);
2
3
4
5
建立连接(连接对象内部其实包含了Socket对象,是一个远程的连接。比较耗时!这是Connection对象管理的一个要点!)真正开发中,为了提高效率,都会使用连接池来管理连接对象!
创建Statement并发送命令
Statement对象用于将 SQL 语句发送到数据库中,或者理解为执行sql语句
有三种 Statement对象:
Statement:用于执行不带参数的简单SQL语句;但容易发生SQL注入。
String id = "5 **or 1=1** "; //数据库被全部删除 String sql = "delete from t_user where id="+id; stmt.execute(sql);
1
2
3PreparedStatement(从 Statement 继承):用于执行带或不带参数的预编译SQL语句;
String sql = "insert into t_user (username,pwd,regTime) values (?,?,?)"; //?占位符 ps = conn.prepareStatement(sql); // ps.setString(1, "高淇3"); //参数索引是从1开始计算, 而不是0 // ps.setString(2, "123456"); // ps.setDate(3, new java.sql.Date(System.currentTimeMillis())); //可以使用setObject方法处理参数 ps.setObject(1, "高淇5"); ps.setObject(2, "234567"); ps.setObject(3, new java.sql.Date(System.currentTimeMillis()));
1
2
3
4
5
6
7
8
9
10
- CallableStatement(从PreparedStatement 继承):用于执行数据库存储过程的调用。

处理结果
- ResultSet对象是executeQuery()方法的返回值,它被称为结果集,它代表符合SQL语句条件的所有行,并且它通过一套getXXX方法(这些get方法可以访问当前行中的不同列)提供了对这些行中数据的访问。
- ResultSet里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。我们如果想要取得某一条记录,就要使用ResultSet的next()方法 ,如果我们想要得到ResultSet里的所有记录,就应该使用while循环。
- ResultSet对象自动维护指向当前数据行的游标。每调用一次next()方法,游标向下移动一行。
- 初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录。循环完毕后指向最后一条记录的后面。

关闭数据库资源
- ResultSet
- Statement
- Connection
- 作为一种好的编程风格,应在不需要Statement对象和Connection对象时显式地关闭它们。关闭Statement对象和Connection对象的语法形式为:
- public void close() throws SQLException
- 用户不必关闭ResultSet。当它的 Statement 关闭、重新执行或用于从多结果序列中获取下一个结果时,该ResultSet将被自动关闭。
- 注意:要按先ResultSet结果集,后Statement,最后Connection的顺序关闭资源,因为Statement和ResultSet是需要连接是才可以使用的,所以在使用结束之后有可能其他的Statement还需要连接,所以不能先关闭Connection。
IO
多线程
注解
注解概述
注解的概念
注解是JDK1.5的新特性。
注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。
Java的注解可以分为三类
- 第一类是由编译器使用的注解
例如:
- @Override:让编译器检查该方法是否正确地实现了覆写;
- @SuppressWarnings:告诉编译器忽略此处代码产生的警告。
这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。
第二类是由工具处理.class文件使用的注解 比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。 例如,一个配置了
@PostConstruct
的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。定义一个注解时,还可以定义配置参数。配置参数可以包括:
- 所有基本类型;
- String;
- 枚举类型;
- 基本类型、String以及枚举的数组。
因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。
注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值。
此外,大部分注解会有一个名为
value
的配置参数,对此参数赋值,可以只写常量,相当于省略了value
参数。如果只写注解,相当于全部使用默认值。
举个栗子,对以下代码:
public class Hello { @Check(min=0, max=100, value=55) public int n; @Check(value=99) public int p; @Check(99) // @Check(value=99) public int x; @Check public int y; }
1
2
3
4
5
6
7
8
9
10
11
12
13@Check
就是一个注解。第一个@Check(min=0, max=100, value=55)
明确定义了三个参数,第二个@Check(value=99)
只定义了一个value参数,它实际上和@Check(99)
是完全一样的。最后一个@Check
表示所有参数都使用默认值。
注解的作用
注解的作用就是给程序带入参数。
以下几个常用操作中都使用到了注解:
生成帮助文档**:@author和@version**
@author:用来标识作者姓名。
@version:用于标识对象的版本号,适用范围:文件、类、方法。
使用**@author和@version注解就是告诉Javadoc工具**在生成帮助文档时把作者姓名和版本号也标记在文档中。如下图:
编译检查**:@Override**
@Override:用来修饰方法声明。
用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图
框架的配置(框架=代码+配置)
元注解
有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。
@Target
最常用的元注解是@Target
。使用@Target
可以定义Annotation
能够被应用于源码的哪些位置:
- 类或接口:ElementType.TYPE;
- 字段:ElementType.FIELD;
- 方法:ElementType.METHOD;
- 构造方法:ElementType.CONSTRUCTOR;
- 方法参数:ElementType.PARAMETER。
例如,定义注解@Report
可用在方法上,我们必须添加一个@Target(ElementType.METHOD)
:
@Target(ElementType.METHOD)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
2
3
4
5
6
定义注解@Report
可用在方法或字段上,可以把@Target
注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }
:
@Target({
ElementType.METHOD,
ElementType.FIELD
})
public @interface Report {
...
}
2
3
4
5
6
7
实际上@Target
定义的value
是ElementType[]
数组,只有一个元素时,可以省略数组的写法。
@Retention
另一个重要的元注解@Retention
定义了Annotation
的生命周期:
- 仅编译期:RetentionPolicy.SOURCE;
- 仅class文件:RetentionPolicy.CLASS;
- 运行期:RetentionPolicy.RUNTIME。
如果@Retention
不存在,则该Annotation
默认为CLASS
。因为通常我们自定义的Annotation
都是RUNTIME
,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)
这个元注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
2
3
4
5
6
@Repeatable
使用@Repeatable
这个元注解可以定义Annotation
是否可重复。这个注解应用不是特别广泛。
@Repeatable
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
2
3
4
5
6
7
经过@Repeatable
修饰后,在某个类型声明处,就可以添加多个@Report
注解:
@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {
}
2
3
4
@Inherited
使用@Inherited
定义子类是否可继承父类定义的Annotation
。@Inherited
仅针对@Target(ElementType.TYPE)
类型的annotation
有效,并且仅针对class
的继承,对interface
的继承无效:
@Inherited
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
2
3
4
5
6
7
在使用的时候,如果一个类用到了@Report
:
@Report(type=1)
public class Person {
}
2
3
则它的子类默认也定义了该注解:
public class Student extends Person {
}
2
自定义注解
自定义注解的格式
public @interface 注解名{
}
如:定义一个名为Student的注解
public @interface Student {
}
2
3
4
5
6
7
注解的属性
属性的格式
- 格式1:数据类型 属性名();
- 格式2:数据类型 属性名() default 默认值;
属性定义示例
// 姓名 String name(); // 年龄 int age() default 18; // 爱好 String[] hobby();
1
2
3
4
5
6属性适用的数据类型
* 八种数据数据类型(int,short,long,double,byte,char,boolean,float) * String,Class,注解类型,枚举类 * 以上类型的数组形式
1
2
3
自定义注解的步骤
我们总结一下定义Annotation的步骤:
- 第一步,用@interface定义注解:
public @interface Report {
}
2
- 第二步,添加参数、默认值:
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
2
3
4
5
把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。
- 第三步,用元注解配置注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
2
3
4
5
6
7
其中,必须设置@Target
和@Retention
,@Retention
一般设置为RUNTIME
,因为我们自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited
和@Repeatable
。
使用自定义注解
定义注解
- 定义一个注解:Book
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 代码实现
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String value();
double price() default 100;
String[] authros();
}
2
3
4
5
6
7
使用注解
- 定义类在成员方法上使用Book注解
@Book(value = "mybook", authors = {"张三","李四"})
public class AnnotationDemo01 {
}
2
3
4
- 使用注意事项
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
4.读取注解
Java的注解本身对代码逻辑没有任何影响。根据@Retention
的配置:
- SOURCE类型的注解在编译期就被丢掉了;
- CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
- RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。
如何使用注解完全由工具决定。SOURCE
类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS
类型的注解主要由底层工具库使用,涉及到class
的加载,一般我们很少用到。只有RUNTIME
类型的注解不但要使用,还经常需要编写。
因此,我们只讨论如何读取RUNTIME
类型的注解。
因为注解定义后也是一种class
,所有的注解都继承自java.lang.annotation.Annotation
,因此,读取注解,需要使用反射API。
Java提供的使用反射API读取Annotation
的方法包括:
判断某个注解是否存在于Class
、Field
、Method
或Constructor
:
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
例如:
// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);
2
使用反射API读取Annotation
:
- Class.getAnnotation(Class)
- Field.getAnnotation(Class)
- Method.getAnnotation(Class)
- Constructor.getAnnotation(Class)
例如:
// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();
2
3
4
使用反射API读取Annotation
有两种方法。
方法一是先判断
Annotation
是否存在,如果存在,就直接读取:Class cls = Person.class; if (cls.isAnnotationPresent(Report.class)) { Report report = cls.getAnnotation(Report.class); ... }
1
2
3
4
5第二种方法是直接读取
Annotation
,如果Annotation
不存在,将返回null
:Class cls = Person.class; Report report = cls.getAnnotation(Report.class); if (report != null) { ... }
1
2
3
4
5
读取方法、字段和构造方法的Annotation
和Class
类似。但要读取方法参数的Annotation
就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。例如,对于以下方法定义的注解:
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}
2
要读取方法参数的注解,我们先用反射获取Method
实例,然后读取方法参数的所有注解:
// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
if (anno instanceof Range) { // @Range注解
Range r = (Range) anno;
}
if (anno instanceof NotNull) { // @NotNull注解
NotNull n = (NotNull) anno;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
5.注解案例
5.1 案例说明
模拟Junit测试的@Test
5.2 案例分析
- 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
- 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。
5.3 案例代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
public class TestMyTest {
@MyTest
public void tests01(){
System.out.println("test01");
}
public void tests02(){
System.out.println("test02");
}
@MyTest
public void tests03(){
System.out.println("test03");
}
}
public class AnnotationDemo05 {
public static void main(String[] args) throws Exception {
// 获得Class对象
Class c = TestMyTest.class;
Object obj = c.newInstance();
// 获得所有成员方法
Method[] methods = c.getMethods();
for(Method m:methods){
// 判断m方法是否使用了MyTest注解
if(m.isAnnotationPresent(MyTest.class)){
// 调用方法
m.invoke(obj);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
反射
ORM
Object Relational Mapping 对象关系映射
把数据库表和实体类及实体类的属性对应起来,让我们通过操作实体类就实现操作数据库表。
对应关系
- 类和表结构对应
- 属性和字段对应
- 对象和记录对应
动态语言
程序运行时,可以改变程序结构或变量类型。典型的语言:
• Python、ruby、javascript等。
• 如下javascript代码:
function test(){
var s = "var a=3;var b=5;alert(a+b);";
eval(s);
}
2
3
4
• C, C++, JAVA不是动态语言,JAVA可以称之为“准动态语言”。但是JAVA有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java代码

反射机制
- 将类的各个组成部分封装为其他对象;
- 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
- 加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个类只有一个 Class 对象,不论通过哪一种方式获取的Class对象都是同一个),这个对象就包含了完整的类的结构信息。
Class类
java.lang.Class类十分特殊,用来表示java中类型;(class/interface/enum/annotation/primitive type/void)本身;
Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个Class对象;
当一个class被加载,或当加载器(classloader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象;
Class类是Reflection的根源;
针对任何您想动态加载、运行的类,唯有先获得相应的Class对象;
Class类的对象获取
- Class.forName("全类名")(最常被使用):将字节码文件加载进内存,返回Class对象;多用于配置文件,将类名定义在配置文件中。读取文件,加载类。(Source源码阶段)
- 类名.class:通过类名的属性class获取:多用于参数的传递。(Class类对象阶段)
- 对象.getClass():getClass()方法在Object类中定义着。多用于对象的获取字节码的方式。(Runtime运行时阶段)
- 结论: 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
Class对象功能
获取功能:
- 获取成员变量们
Field[] getFields()://获取所有public修饰的成员变量 Field getField(String name) //获取指定名称的public修饰的成员变量 Field[] getDeclaredFields() //获取所有的成员变量,不考虑修饰符 Field getDeclaredField(String name)
1
2
3
4
5
6
7- 获取构造方法们
Constructor<?>[] getConstructors() Constructor<T> getConstructor(类<?>... parameterTypes) Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) Constructor<?>[] getDeclaredConstructors()
1
2
3
4
5
6
7- 获取成员方法们
Method[] getMethods() Method getMethod(String name, 类<?>... parameterTypes) Method[] getDeclaredMethods() Method getDeclaredMethod(String name, 类<?>... parameterTypes)
1
2
3
4
5
6
7- 获取全类名
String getName()
1
反射机制的作用
• 动态加载类、动态获取类的信息(属性、方法、构造器)
• 动态构造对象
• 动态调用类和对象的任意方法、构造器
• 动态调用和处理属性
• 获取泛型信息
• 处理注解
- 反射操作泛型
• Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。
• 为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType,GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
• ParameterizedType: 表示一种参数化的类型,比如Collection
• GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
• TypeVariable: 是各种类型变量的公共父接口
• WildcardType: 代表一种通配符类型表达式,比如?, ? extends Number, ? super Integer【wildcard是一个单词:就是“通配符”】
反射操作注解
反射机制性能问题
• setAccessible
– 启用和禁用访问安全检查的开关,值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是为true就能访问为false就不能访问。
– 禁止安全检查,可以提高反射的运行速度。
• 可以考虑使用:cglib/javaassist字节码操作
反射
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。
正常情况下,如果我们要调用一个对象的方法,或者访问一个对象的字段,通常会传入对象实例。但是,如果不能获得该对象的类,这个时候就要用到反射机制了。
所以,反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)
认识反射
反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person() ; // 正着操作
System.out.println(per.getClass().getName()); // 反着来
}
}
2
3
4
5
6
7
以上的代码使用了一个getClass()
方法,而后就可以得到对象所在的“包.类”名称,这就属于“反”了,但是在这个“反”的操作之中有一个getClass()
就作为发起一切反射操作的开端。
Person的父类是Object类,而上面所使用getClass()方法就是Object类之中所定义的方法。
3.获取Class对象
取得Class对象:public final Class<?> getClass()
,反射之中的所有泛型都定义为?,返回值都是Object。
而这个getClass()
方法返回的对象是Class类的对象,所以这个Class就是所有反射操作的源头。但是在讲解其真正使用之前还有一个需要先解释的问题,既然Class是所有反射操作的源头,那么这个类肯定是最为重要的,而如果要想取得这个类的实例化对象,Java中定义了三种方式。
3.1 三种获取方式
1. 通过Object类的getClass()
方法取得,基本不用
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person() ; // 正着操作
Class<?> cls = per.getClass() ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
2
3
4
5
6
7
8
2. 使用“类.class”取得
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
2
3
4
5
6
7
3. 使用Class类内部定义的一个static方法, Class.forName("全限定类名")
public static Class<?> forName(String className) throws ClassNotFoundException;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.github.krislinzhao.demo.Person") ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
2
3
4
5
6
7
3.2 Class类常用方法
String getSimpleName()
: 获得类名字符串:类名String getName()
: 获得类全名:包名+类名T newInstance()
: 创建Class对象关联类的对象
示例代码:
public class ReflectDemo02 {
public static void main(String[] args) throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类名字符串:类名
System.out.println(c.getSimpleName());
// 获得类全名:包名+类名
System.out.println(c.getName());
// 创建对象
Student stu = (Student) c.newInstance();
System.out.println(stu);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
4.反射机制的应用
1.通过反射实例化对象
那么现在一个新的问题又来了,取得了Class类的对象有什么用处呢?对于对象的实例化操作之前一直依靠构造方法和关键字new完成,可是有了Class类对象之后,现在又提供了另外一种对象的实例化方法:
public T newInstance() throws InstantiationException, IllegalAccessException;
class Person {
@Override
public String toString() {
return "Person Class Instance .";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.github.krislinzhao.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance() ; // 实例化对象,和使用关键字new一样
Person per = (Person) obj ; // 向下转型
System.out.println(per);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
那么现在可以发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而且这个操作要比之前使用的new复杂一些,可是有什么用?
对于程序的开发模式之前一直强调:尽量减少耦合,而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字new
,所以实际上new
是造成耦合的关键元凶。
范例:回顾一下之前所编写的工厂设计模式
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Factory {
public static Fruit getInstance(String className) {
if ("apple".equals(className)){
return new Apple() ;
}
return null ;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("apple") ;
f.eat() ;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
以上为之前所编写最简单的工厂设计模式,但是在这个工厂设计模式之中有一个最大的问题:如果现在接口的子类增加了,那么工厂类肯定需要修改,这是它所面临的最大问题,而这个最大问题造成的关键性的病因是new
,那么如果说现在不使用关键字new
了,变为了反射机制呢?
反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式。
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Orange implements Fruit {
public void eat() {
System.out.println("吃橘子。");
};
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null ;
try {
f = (Fruit) Class.forName(className).newInstance() ;
} catch (Exception e) {
e.printStackTrace();
}
return f ;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("com.github.krislinzhao.demo.Orange") ;
f.eat() ;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
发现,这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。如果单独从开发角度而言,与开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在日后的程序开发上,如果发现操作的过程之中需要传递了一个完整的“包.类”名称的时候几乎都是反射机制作用。
2.获取指定构造器方法
//获取字节码文件
Class clazz1=Class.forName("Reflect.User");
//先获取有参构造器,parameterTypes:表示参数列表,有多少写多少,也可以不写,不写就是调用无参构造器Constructor constructor=clazzl.getConstructor(int.class,String.class);
//通过构造器来实例化对象,将实际的参数传进去。
User user=(User)constructor.newInstance(12,"小明");
2
3
4
5
总结上面创建实例对象:Class类的newInstance()
方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)
方法获取一个指定的构造函数然后再调用Constructor类的newInstance("张三",20)
方法创建对象
获取全部构造方法
Class clazz1=Class.forName("Reflect.User");
//获得所有构造方法
Constructor[] constructors = clazz1.getConstructors();
//遍历所有构造方法
for(int i=0;i< constructors.length;i++)
//获取每个构造函数中得参数类型字节码对象。
Class[] parameterTypes = constructors[i].getParameterTypes();
System.out.println("第"+i+"个构造函数");
for(int j=0; j< parameterTypes.length; j++){
//获取构造函数中参数类型
System.out.print(parameterTypes[j].getName()+",")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
3.获取成员变量并使用Field对象
获取指定成员变量
//获取字节码文件
Class clazz1=Class.forName=("Reflect.User");
//获得其实例对象
User user=(User)clazz1.newInstance();
//获取成员变量clazz1.getField(name);通过name来获取指定成员变量,
//如果该成员变量是私有的,则应该使用getDeclaredField(name);
Field field=clazz1.getDeclaredField("id");
//因为属性是私有的,获得其属性对象后,还要让其打开可见权限
field.setAccessible(true);
//对其成员变量进行操作
//赋值操作
field.setInt(user,5);
//获取成员变量的值,field.get(obj);obj为所表示字段的值的对象。也就是该属性对应类的实例对象
System.out.printin(field.getInt(user));
2
3
4
5
6
7
8
9
10
11
12
13
14
Class.getField(String)
方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField("name")
方法获取,通过set(obj, "李四")
方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)
设置访问权限,用获取的指定的字段调用get(obj)
可以获取指定对象中该字段的值
获取全部成员变量
// 获取全部成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field:fields){
// 将权限打开
field.setAccessible(true);
System.out.println(field.toString());
}
2
3
4
5
6
7
4.获得方法并使用Method
获取指定方法
//获取字节码文件对象
Class clazz1=Class.forName("Reflect.User");
//实例化
User user=(User)clazz1.newInstance();
//不带参数的方法,eat为不带参数的public方法
/*
*clazz1.getMethod(name,parameterTypes)
*name:方法的名字
*parameterTypes:方法的参数类型的Class类型,没有则什么都不填,比如参数为String,则填String.class
*/
Method method =clazz1.getMethod("eat");
//调用方法
/*
*method.invoke(obj,args)
* obj:方法的对象
*args:实际的参数值,没有则不填
*/
method.invoke(user);
//带参数的方法,sing为带一个String类型参数的方法
Method method1=clazz1.getMethod("sing",String.class);
method1.invoke(user,"小明");
//获取私有的方法,和获取私有属性一样,say为私有方法
Method method2=clazz1.getDeclaredMethod("say");
method2.setAccessible(true)
method2.invoke(user);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Class.getMethod(String, Class...)
和 Class.getDeclaredMethod(String, Class...)
方法可以获取类中的指定方法, 如果为私有方法,则需要打开一个权限。setAccessible(true);
用invoke(Object, Object...)
可以调用该方法,
获取全部方法
// 获取全部方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method:methods){
// 把权限打开
method.setAccessible(true);
System.out.println(method.getName());
// 获得方法的参数
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType:parameterTypes){
// 获取构造函数中的参数类型
System.out.print(parameterType.getName()+',');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
5.获得该类的所有接口
Class[] getInterfaces():
确定此对象所表示的类或接口实现的接口
返回值:接口的字节码文件对象的数组
6.获取指定资源的输入流
InputStream getResourceAsStream(String name)
返回值:一个 InputStream 对象;如果找不到带有该名称的资源,则返回 null
参数:所需资源的名称,如果以"/"开始,则绝对资源名为"/"后面的一部分。
Reference
学习java应该如何理解反射? (opens new window) [Java中反射机制详解](
网络编程
名词解释
vo类,model类,dto类的作用及划分
- entity里的每一个字段,与数据库相对应,
- dto里的每一个字段,是和你前台页面相对应,
- VO,这是用来转换从entity到dto,或者从dto到entity的中间的东西。