标签:day 地方 -- dir ant 等于 prot can 用途
雇员用 Employee 类表示,但是经理是雇员但他有独自的域和方法,所以我们可以添加一个新类 Manager。我们在新类中增加新功能,用继承的方法将 Employee 类中的域和方法保留下来。每个经理都是一个雇员,is-a
关系是继承的一个明显特征。
关键字extends
表示继承:
class Manager extends Employee{
...
}
关键字 extends 表明正在构造的新派生类生于一个已存在的类。已存在的类成为超类、基类或父类;新类成为子类、派生类或孩子类。子类继承了父类的域和方法,子类比超类拥有的功能更加丰富。
然而子类中定义的域和方法,超类不可以使用它们。
因此我们在设计类的时候,因该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。
重写:若超类有些方法不适应子类,子类可以提供一个新的方法来覆盖超类中的这个方法。在覆盖超类方法时,子类方法不能低于超类方法的可见性。
调用重写父类后的方法:如果该方法用到了超类的被重写的方法,此时可以在子类的新方法中调用超类的方法(通过关键字 super.父类方法()来调用超类方法),在进行后面的更改实现自己的需求。
调用父类的私有域:子类不能直接使用父类的私有域,只能通过公有的接口来调用父类的私有域。
class Manager extends Employee{
//不能访问超类的私有域baseSalary
public double getSalary(){
return baseSalary + bonus;//err
}
}
class Manager extends Employee{
double = bonus;
//覆盖重写
public double getSalary(){
return super.getSalary() + bonus;
}
}
子类只能增加域、增加方法或覆盖超类的方法,不能删除继承的任何域和方法。
super 在子类构造器中的应用:
public Manager(String n, double s, int year, int month, int day){
super(n, s, year, month, day);
bonus = 0;
}
继承层次:由一个公共父类派生出来的所有类的集合。
继承链:在继承层次中,从某个特定的类到其祖先的路径。
java 不支持多继承,但有多继承的方式(接口)。
is-a
规则表明,子类的每个对象也是超类的对象。
置换法则,表明程序中出现父类对象的任何地方都可以用子类对象置换。
Employee e;
e = new Employee();
e = new Manager();
所有在 java 程序设计语言中,对象变量是多态的。父类类型的对象变量可以引用一个父类的任何一个子类对象。
不能将一个超类的引用赋值给子类变量,将产生错误。不是所有的雇员都是经理。
多态的特点:
Manager manager = new Manager();
Employee employee = new Employee();
System.out.println(employee.getSalary());//调用了 Employee 的方法
employee = new Manager();
employee.getSalary();
System.out.println(employee.getSalary());//调用了 Manager 重写的方法
manager = (Manager) employee;//强制转换,可以成功,因为 employee 是多态,manager现在是Manager类的对象
employee = manager;//转换回来,employee 是多态
警告:java 中子类数组的引用可以转换为超类的引用,而不需要强制类型转换。
因为两个数组都指向了同一个引用,此时managers[0]
会调用一个不存在的实例。
Manager manager = new Manager();
Employee employee = manager;
employee = new Employee();//变量改变了地址
System.out.println(employee == manager);//false
Manager[] managers = new Manager[3];
Employee[] employees = managers;
employees[0] = new Employee();//数组变量没有改变地址,employees[0] 和 managers[0] 引用同一个对象
System.out.println(managers[0] == employees[0]);//上一句异常 java.lang.ArrayStoreException
Manager[] managers = new Manager[3];
Employee[] employees = managers;
try{
employees[0] = new Employee();//数组变量没有改变地址,employees[0] 和 managers[0] 引用同一个对象
} catch (Exception ignored){}
System.out.println(managers[0] == employees[0]);//true
调用对象方法的执行过程:
当类使用 final 时,就表示这个类不能扩展:
final class Executive extends Manager{
......
}
类的方法声明为 final ,它的子类就不能覆盖这个类的方法。(类声明为 final 时,类的方法自动成为 fiinal 方法,不包括域)
class Executive extends Manager{
public final String getName(){
return name;
}
}
目的:确保他们不会在子类中改变语义。
可以将浮点型数值强制转换为整型数值,也可以将某个类的对象引用转换成另外一个类的对象引用。
double x = 3.14;
int nx = (int) x;
Manager boss = (Manager) staff[0];
原因:在暂时忽视对象的实际类型之后,使用对象的全部功能。
将一个值存入变量时,编译器将检查是否允许该操作。
如果在继承链上进行向下的类型转换,并且“谎报”有关对象包含的内容,运行程序时将发生异常:
Manager manager = new Manager();
Employee employee = new Employee();
manager = (Manager) employee;//Exception java.lang.ClassCastException
Manager manager = new Manager();
Employee employee = new Manager();
manager = (Manager) employee;//正确,多态的情况下
所以强制转换之前因该使用 instanceof 判断是否能够成功转换。
if(employee instanceof Manager){
manager = (Manager) employee;
}
总结:
应用场景:一般情况下不需要进行类型转换,实现多态性的动态绑定机制能够自动地找到相应的方法,只有在父类对象需要调用子类引用并要使用子类的特有方法时,才需要进行类型转换。
重新设计一个超类,添加子类特有的方法,实现重写,完全可以避免使用强制类型转换。一般情况下,尽量少用类型转换和 instanceof 运算符。
当祖先类更加通用,人们只将它作为派生其他类的基类,而不想作为使用的特定的实例类,这是就可以使用关键字 abstract 关键字,将超类定义为抽象类。
例如,一个父类有两个子类,子类有同样的方法,子类各自实现这个方法很简单,那么父类该提供什么内容呢?可以让父类这方法返回一个空字符串,更好的方法是,使用关键字 abstract ,这样就不需要实现这个方法了。
abstract class Person{
private String name;
public Person(String n){
name = n;
}
public abstract String getDescription();
public String getName(){
retruen name;
}
}
包含一个或多个抽象方法的类本身必须声明为抽象的。
抽象类不仅包含抽象方法,还可以包含具体数据和具体方法。
子类不定义父类的抽象方法,那么子类也标记为抽象类;子类定义父类全部的抽象方法,子类就不是抽象类。
类即使不含抽象方法,也可以将类声明为抽象类。目的为不让其实例化,不能创建这个类的对象。
注意的是,虽然抽象类不能创建对象。但可以定义一个抽象类的对象变量,并且只能引用非抽象子类的对象(这点和接口一样)。
java Person P = new Student(); //此时和多态的个性一样
子类不能访问超类的私有域,如果想要允许,可以使用 protected 修饰符取代 private 修饰符。
不过子类调用的这个受保护方法只能访问超类被调用受保护方法的方法域,不能访问超类的其他域。这种机制有助于避免滥用受保护机制,是的子类只能获得访问受保护域的权利。
控制可见性的修饰符:
作用:Object 类中的 equals 方法,用于检测一个对象是否等于另一个对象,也就是判断两个对象是否具有相同的引用。
意义不大:比较两个 PrintStream 对象是否引用相等完全没有意义。
正常需求:需要检测两个对象状态的相等性,如果两个对象的状态相等,就认为这两个对象是相等的。例如,有两个雇员实例对象,如果他们的 ID、姓名 、薪水和雇用日期都一样,那么就认为他们是相等的。
按需求在一个类中定义 equals 方法:
class Employee{
...
public boolean equals(Object otherObject){
if(this == otherObject) return true;
if(otherObject == null) return false;
if(getClass() != otherObject.getClass()) return false;//getClass获取的是当前运行实例所属的类
//getClass() 、 super.getClass() 、 this.getClass() 都一样的结果,属性为 final 不可重写。
//getClass().getName() 只返回类的名字
//getClass().getSuperclass() 返回这个类的超类信息
Employee other = (Employee) otherObject;
return name.equals(other.name)
&& salary == other.salary
&& hireDay.equals(other.hireDay);//判断想个对象的域是否相同
//采用以下方法,可以防止 name 和 hireDay 为 null 时的空指针异常,Objects.equals(a, b)对 null 值进行了判断。
//return Objects.equals(name,other.name)
// && salary == other.salary
// && Objects.equals(hireDay,other.hireDay);
}
}
//源码
// Object equals()
public boolean equals(Object obj) {
return (this == obj);
}
// Objects.equals()
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
在子类中定义 equals 方法:
class Manager extends Employee{
...
public boolean equals(Object otherObject){
if(!super.equlas(otherObject)) return false;//首先与超类比较,确定和超类的域都相等。
Manager other = (Manager) otherObject;
return bonus == other.bonus;
}
}
instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
用法:
result = object instanceof class
参数:
Result:布尔类型。
Object:必选项。任意对象表达式。
Class:必选项。任意已定义的对象类。
//Person类所在的继承树是:Object<--Person<--Student<--Postgraduate
Person p = new Student();
if (p instanceof Postgraduate) System.out.println("p是类Postgraduate的实例");//false
if (p instanceof Person) System.out.println("p是类Person的实例");//true
if (p instanceof Student) System.out.println("p是类Student的实例");//true
if (p instanceof Object) System.out.println("p是类Object的实例");//true
if(p instanceof Animal){System.out.println("p是类Animal的实例");}//编译出错
问题:如果隐式和显式的参数不属于同一个类,equals 方法将如何处理呢?
方法一:发现类不匹配,equals 方法就返回 false。
方法二:使用 instanceof 进行检测。if(!(otherObject instanceof Employee)) return false;
方法二的缺点:不但没有解决 otherObject 是子类的情况,并且还有可能招致一些麻烦。
java 语言规范要求 equals 方法具有的特性:
从两个角度看问题:
编写完美的 equals 方法的建议:
显示参数命名为 otherObject ,稍后需要将它转换为另一个叫做 other 的变量。
检测 this 与 otherObject 是否引用同一个对象:
if(this == otherObject) return true
if(otherObject == null) return false
如果 equals 的语义在每个子类中有所改变就是用 getClass 检测。
if(getClass() != otherObject.getClass()) return false;
如果所有的子类都拥有统一的语义,就使用 instanceof 检测。
if(!(otherObject instanceof ClassName)) return false;
ClassName other = (ClassName) otherObject;
对象域使用Objects.equals(a, b)
数组使用Arrays(a, b)
java return field1 == other.field1 && Objects(field2, other.field2) && ...;
super.equals(other)
错误的实现 equals 方法:
public boolean equals(Empliyee other){
...
}
//正确
public boolean equals(Object other){
...
}
方法声明的显示参数类型是 Employee 结果并没有覆盖 Object 的 equals 方法。
散列码:由对象导出的一个整形值。它没有规律,两个不同的对象,x.hashCode() 和 y.hashCode() 基本不会相同。
hashCode 方法定义在 Object 中,即每个对象都有一个默认的散列码,值为对象的存储地址。
public native int hashCode();
//是一个本地方法,它的实现是根据本地机器相关的。
众多类都重写了 hashCode 方法,如 String、Integer、Double。。。。等等 。
String 中重写的 hashCode 方法如下:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
这是会出现两个如下情况:
String s = "OK";
String t = new String("OK");
System.out.println(s.hashCode() + " " + t.hashCode());
//结果他们的hashCode相同
因为字符串的散列码被重写了,它是由内容导出的。
例如,如果用定义的 Employee.equals 比较雇员的 ID,那么 hashCode 方法就需要散列 ID,而不是雇员的姓名或存储地址。
hashCode 方法返回一个整形数值(也可以是负数),合理的组合实例域的散列码,让不同对象产生的散列码更加均匀。
class Employee{
public int hashCode(){
retrun 7 * name.hashCode()
+ 11 * new Double(salary).hashCode()
+ 13 * hireDay.hashCode();
}
}
//java 7 Objects.hashCode() 它是 null 安全的,为 null 返回 0
class Employee{
public int hashCode(){
retrun 7 * Objects.hashCode(name)
+ 11 * new Double(salary).hashCode()
+ 13 * Objects.hashCode(hireDay);
}
}
//更好的获取散列值方法
class Employee{
public int hashCode(){
retrun Objects.hash(name, salary, hireDay);
}
}
查看数组散列码的方法:
Arrays.hashCode(数组);
用于返回对象值的字符串。这个方法在很多地方都会自动调用。
+
连接字符串为每个类重写 toString 方法,程序员可以通过日志记录支持中受益匪浅。
public String toString(){
return getClass().getName() + "[name=" + name + ", salary=" + salary + ", hireDay=" + hireDay + "]";
}
Object 类定义了 toString 方法,用来打印输出对象所属的类和散列码:
System.out.println(System.out);
//输出:java.io.PrintStream@2f6684
数组也继承了 Object 的 toString 方法,如果想要输出数组的值:
Arrays.toString(数组);
Arrays.deepToString(多维数组);
java 能在运行时确定数组的大小。
int actualSize = ...;
Employee[] staff = new Employee[actualSize];
但是并没有完全解决运行时动态更改数组的问题。一旦确定了数组的大小,改变就不太容易了。
解决这个问题可以使用ArrayList
的类,使用有点像数组,但在添加或删除元素时,具有自动调节数组容量的功能,而不需要为此编写任何代码。
ArrayList 是一个采用类型参数的泛型类,为了指定数组保存的类型对象,需要用一对尖括号将类名括起来加在后面。
ArrayList<Employee> staff = new ArrayList<Employee>();
Java 7 中,可以省略后面的括号内容:
ArrayList<Employee> staff = new ArrayList<>();
也可以定义初始容量
ArrayList<Employee> staff = new ArrayList<>(100);
//跟数组不用一样,数组分配100个元素的存储空间,数组就有100个空间位置可以使用。
//而容量为100个元素的数组列表只是拥有保存100个元素的潜力,实际上重新分配空间的话,将会超过100个。
此时,泛型类型为 Employee。
java 5.0之前没有泛型类,但有一个 ArrayList 类,保存类型为 Object 元素:
ArrayList arrayList = new ArrayList();
arrayList.add(new Object());
arrayList.add(new Object());
System.out.println(arrayList);//[java.lang.Object@1540e19d, java.lang.Object@677327b6]
对应的动态数组,使用的是 Vector
类,不过现在 ArrayList 类更加有效。
添加元素
使用 add 方法可以将元素添加到数组列表中,数组列表管理着对象引用的一个内部数组,当数组空间用尽,添加时,数组列表将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。
staff.add(new Employee("Harry Hacker", ...);
staff.add(new Employee("Tony Tester", ...);
分配对应个数的内部数组
使用 ensureCapacity 方法,分配 n 个对象的内部数组,然后调用 n 次 add 方法,而不用重新分配空间。
staff.ensureCapacity(100);
实际元素个数
size 方法返回数组列表中包含的实际元素数目。
staff.size();
清楚空的位置
一旦确认数组列表的大小不在发生变化,就可以调用 trimToSize 方法。它将存储区域的大小调整为当前元素数量所需要的存储空间数目。
staff.trimToSize();
改变和访问数组列表元素
虽然数组列表自动扩展容量,但是增加了访问元素语法的复杂程度。使用 set 和 get 方法实现改变和访问数组元素的操作。
staff.set(i, harry);//等价 a[i] = harry;
Employee e = staff.get(i);//等价 Employee e = a[i];
结合他们两个的优点
集合添加完毕后,可以使用toArray方法,将数组列表元素拷贝到一个数组中。
ArrayList<X> list = new ArrayList<>();
while(...){
x = ...;
list.add(x);
}
X[] a = new X[list.size()];
list.toArray(a);
优点:
中间插入
使用带索引的 add 方法,可以在数组列表的中间添加元素,n 后面的元素都要向后退一格。
int n = staff.size() / 2;
staff.add(n, e);
删除元素
也可以删除一个元素,删除之后所有后面的元素向前移动一格。
Employee e = staff.remove(n);
数组列表的性能缺点
对数组实施插入和删除元素的操作其效率比较低,如果对数组存储的元素比较多,又要经常在中间位置插入、删除元素,就因该考虑使用链表,但链表对数组的查询比不上数组列表,所以看实际业务场景使用数组列表或链表。
有时需要将基本类型转换为对象,这是就要用到包装器。
种类:Integer、Long、Float、Double、Short、Byte(这6个派生于超类 Number)、Character、Void和Boolean
特点:对象包装器类是不可变的,一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装类还是fianl,因此不能定义他们的子类。
ArrayList<int> list = new ArrayList<>();//错误
ArrayList<Integer> list = new ArrayList<>();//正确
自动装箱:当数组列表添加基础类型对象时,会自动打包成包装器对象。
list.add(3);//自动执行下面这句
list.add(Integer.valueOf(3));
自动拆箱:当一个Integer对象赋值给一个int值时,会自动拆箱变成基本类型。
int n = list.get(i);
int n = list.get(i).intValue();
自动拆箱自动装箱:执行算术表达式时,会先拆箱,再执行运算,最后在装箱。
Integer n = 3;
n++;
假象:假象为,基本类型与它们的对象包装是一样的,只是它们的相等性不同。==
运算符可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域。因此下面的值因该为false
。
Integer i = 1000;
Integer j = 1000;
System.out.println(i == j);
java 有时却可以让他为true
,这不是我们希望的,解决这个方法的办法是比较两个包装器对象时调用 equals
方法。
Integer i1 = 200;
Integer j1 = 200;
System.out.println(i1 == j1);//false
Integer i2 = 100;
Integer j2 = 100;
System.out.println(i2 == j2);//true
自动装箱规范要求boolean
、byte
、char
<= 127,介于 -128 ~ 127 之间的 short 和 int 被包装到固定的对象中。
包装器为 null
运行后会抛出异常,不像 int 不能赋值为 null 。
Integer n = null;
System.out.println(2 * n);//Throws NullPointerException
Integer 和 Double
Integer 首先拆箱,提升为 double ,再装箱为 Double:
Integer n = 1;
Double x = 2.0;
System.out.println(true ? n : x);//1.0
强调
装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
//.java
public class DataType {
public static void main(String[] args){
Integer n = 10;
}
}
//.class
public class DataType {
public DataType() {
}
public static void main(String[] args) {
Integer n = Integer.valueOf(10);
}
}
利用静态方法转换类型
想要将字符串转换为整合可以使用这个方法:
int x = Integer.parseInt(s);
错误认为包装器类可以用来实现修改数值参数的方法
public class DataType {
public static void triple(Integer x){
x = x * 3;
}
public static void main(String[] args){
Integer n = 10;
triple(n);
System.out.println(n);//10
}
}
Integer 对象是不可改变的:包含在包装器中的内容不会改变。不能使用这些包装器创建修改数值参数的方法。
public class DataType {
public static void triple(String x){
x = x + "0";
}
public static void main(String[] args){
String n = "10";
triple(n);
System.out.println(n);//10
}
}
public class DataType {
public static void triple(IntHolder x){
x.value = x.value * 3;
}
public static void main(String[] args){
IntHolder n = new IntHolder(10);
triple(n);
System.out.println(n.value);//30
}
}
举个例子,printf 方法:
System.out.printf("%d", n);
System.out.printf("%d %s", n, "widgets");
它可以调用好几个参数,但是他们都是同一个方法。
public class PrintStream{
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
}
int n = 10;
System.out.printf("%d %s", n, "widgets");
System.out.printf("%d %s", new Object[]{n, "widgets"});//可以写成 Object 类数组
省略号...
是java代码的一部分,它表明这个方法可以接收任意数量的对象。他是 Object[]
数组,基本类型会进行自动封装。
用户也可以自定义,功能为计算若干个数值的最大值:
public static double max(double... values){
double largest = Double.MIN_VALUE;
for(double v : values){
if(v > largest){
largest = v;
}
}
return largest;
}
double x = max(1.0, 2.0, 3.0, 4.0);
System.out.println(x);//4.0,找出最大数
public enum Size{ SMALL, MEDIUM, LARGE, EXTRA_LARGE };
声明定义的类型是一个类,它有 4 个实例,比较两个枚举类型的值时,永远不要使用equals
,而直接使用==
就可以了。
可以在枚举类中添加构造器、方法和域。所有枚举类型都是 Enum 类的子类。
public enum Size{
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
private String abbreviation;
private Size(String abbreviation){
this.abbreviation = abbreviation;
}
public String getAbbreviation(){
return abbreviation;
}
}
常用的一些方法:
System.out.println(Size.SMALL.toString());//返回枚举类常量名
Size s = Enum.valueOf(Size.class, "SMALL");//toString 的逆方法是静态方法 valueOf
Size[] values = Size.values();//静态方法返回一个包含全部枚举值的数组
Size.MEDIUM.ordinal();//返回 1 ,返回 enum 中枚举常量的位置,位置从 0 开始计数
反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵 Java 代码的程序。
反射功能大量的应用在 JavaBeans 中,他是 Java 组件的体系结构。
反射可在设计或运行中添加新类时,能够快速地应用开发工具动态地查询新添加类的能力。
能够分析类能力的程序成为反射,反射机制可以用来:
使用反射的主要人员是工具构造者,而不是应用程序员。
程序运行期间,系统为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方式执行。
方法 getClass() 获取 Class 实例
保存这些信息的类成为 Class。Object 中个 getClass() 方法会返回一个 Class 类型的实例。
Employee e;
Class cl = e.getClass();
Class 对象表示一个特定类的属性,getName 方法返回类的名字,如果类在包里,包的名字也作为类名的一部分:
Employee e = new Employee();
System.out.println(e.getClass());//class di3.Employee 返回了 类和类名(包名也作类名一部分)
System.out.println(e.getClass().getName());//di3.Employee 返回了类名(包名也作类名一部分)
静态方法 forName() 获取 Class 实例
只需提供类路径名,调用 Class 静态方法 forName 方法可以获得类对应的 Class 对象:
String className = "java.util.Random";
try {
Class cl = Class.forName(className);
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
因为存在提供的类路径名是错误的情况,这里必须添加异常处理器 try catch 语句,不然无法编译。
class 获取 Class 实例
如果 T 是任意的 java 类型(或 void 关键字),T.class 将代表匹配的类对象:
Class cl1 = Random.class;//class java.util.Random
Class cl2 = int.class;//int
Class cl3 = void.class;//void
Class cl4 = Employee.class;//class di3.Employee
Class cl5 = Enum.class;//class java.lang.Enum
Class cl6 = float.class;//float
Class Cl7 = Float.class;//class java.lang.Float
Class 实际上是一个泛型类,调用.class
不一定是类,如int
就不是类,但int.class
是一个类。
Class cl1 = Random.class;
Class cl2 = Integer.TYPE;
Class cl3 = Void.TYPE;
Class cl4 = Employee.class;
Class cl5 = Enum.class;
Class cl6 = Float.TYPE;
Class Cl7 = Float.class;
//Integer.TYPE 代码
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
//static native Class<?> getPrimitiveClass(String name);
//返回指定的虚拟机的Class对象
//原始类型
这是编译后的代码,可见:
Interger.TYPE == int.Class
是基本类型 int 的 Class 实例,int 是基本类型在虚拟机运行时就已经加载了他的常量静态变量 TYPE,返回了 Class 实例。比较两个类对象
虚拟机为每个类都管理一个 Class 对象,所以才可以利用 ==
比较两个类对象:
Employee e = new Employee();
if(e.getClass() == Employee.class) ...
//true
动态创建实例
newInstance 方法调用默认的构造器初始化新创建的对象。这个类没有默认构造器,就会抛出一个异常。
String s = "java.util.Random";
try {
Object o = Class.forName(s).newInstance();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
通常通过捕获异常的处理器对异常情况进行处理。
发现异常并没有提供处理器,程序会终止,并在控制台打印异常的类型。
异常的类型:
当 try 语句块发生异常时,就执行 catch 语句块。
反射用到的类的概述:
java.lang.Class //用于生成类的 Class 实例
java.lang.reflect.Field //用于描述类的域
java.lang.reflect.Constructor //用于描述类的构造器
java.lang.reflect.Method //用于描述类的方法
java.lang.reflect.Modifier //主要用于提供静态分析方法,判断、打印修饰符
反射用到的类的方法:
java.lang.Class //用于生成类的 Class 实例
Field[] getField() //返回包含 Field 对象的数组,这个类或超类的公有域
Field[] getDeclaredFields() //返回包含 Field 对象的数组,这个类的公有域
Method[] getMethods() //返回包含 Method 对象的数组,返回这个类和超类的公有方法。
Method[] getDeclarMethods() //返回包含 Method 对象的数组,返回这个类或接口的全部方法,不包括在超类继承的。
Constructor[] getConstructors() //返回包含 Constructor 对象的数组,包含这个类的所有公有构造器
Constructor[] getDeclaredConstructors() //返回包含 Constructor 对象的数组,包含这个类的所有构造器
java.lang.reflect.Field //用于描述类的域
java.lang.reflect.Constructor //用于描述类的构造器
java.lang.reflect.Method //用于描述类的方法
int getModifiers() //返回一个描述构造器、方法或域的修饰符的整形数值。
String getName() //返回构造器、方法或域名的字符串。
Class[] getParameterTypes() //Constructor 和 Method 类中,返回描述参数类型的 Class 对象数组。
Class getDeclaringClass() //返回描述类中定义的构造器、方法或域的 Class 对象。
Class[] getExceptionTypes() //Constructor 和 Method 类中,返回描述方法抛出的异常类型的 Class 对象数组。
Strng toString() //打印构造器、方法或域的整段。
Class getReturnType() // Method类中,返回描述返回类型的 Class 对象。
java.lang.reflect.Modifier //主要用于提供静态分析方法,判断、打印修饰符
static String toString(int modifiers) //打印整形数值的修饰符字符串
static boolean isAbstract(int modifiers)
static boolean isFinal(int modifiers)
static boolean isInterface(int modifiers)
static boolean isNative(int modifiers)
static boolean isPrivate(int modifiers)
static boolean isProtected(int modifiers)
static boolean isPublic(int modifiers)
static boolean isStatic(int modifiers)
static boolean isStrict(int modifiers)
static boolean isSynchronized(int modifiers)
static boolean isVolatile(int modifiers) //判断是否是这个修饰符
实现一个能加载任何类的程序:
package dirfanshe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
public class ReflectionTest {
public static void main(String[] args) {
String name;
if (args.length > 0) name = args[0];
else {
Scanner in = new Scanner(System.in);
System.out.println("Enter class name (e.g. java.util.Date):");
name = in.next();
}
try {
//获取输入的类的 Class 类实例
Class cl = Class.forName(name);
//获取输入的类的超类 Class 类实例
Class supercl = cl.getSuperclass();
//cl.getModifiers() 返回一个整形数值描述对应的修饰符,Modifier.toString 方法返回修饰符
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + "");
System.out.print(" class " + name);
if (supercl != null && supercl != Object.class) System.out.print(" extends " + supercl.getName() + "{\n");
//打印类的域
printFields(cl);
System.out.println();
//打印类的构造器
printConstructors(cl);
System.out.println();
//打印类的方法
printMethods(cl);
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.exit(0);
}
private static void printConstructors(Class cl) {
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c : constructors) {
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(name + "(");
Class[] paramTypes = c.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
private static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods();
for (Method m : methods) {
Class retType = m.getReturnType();
String name = m.getName();
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(retType.getName() + " " + name + "(");
Class[] paraTypes = m.getParameterTypes();
for (int j = 0; j < paraTypes.length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(paraTypes[j].getName());
}
System.out.println(");");
}
}
private static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for (Field f : fields) {
Class type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
System.out.println(type.getName() + " " + name + ";");
}
}
}
利用反射机制可以查看编译时还不清楚的对象域,查看对象域的值的关键方法是 Field 类中的 get 方法。
Employee harry = new Employee("xuhongliang");
Class cl = harry.getClass();
try {
Field f = cl.getDeclaredField("name");
//末尾没有 s ,表示提供方法的域名指定获取的域的 Field 类型
Object v = f.get(harry);
//获取一个对象,其值为 harry 对象的 f 域的当前值
//如果f域的访问控制禁止访问(如为private,此语句不在一个泪下执行时),那么获取值时会抛出异常 IllegalAccessException。
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
反射机制的默认行为受限于 Java 的访问控制,如果 Java 程序没有受到安全管理器的控制,就可以覆盖访问控制。
达到这个目的的方法为 Field 、 Method 和 Constructor 对象的 setAccessible 方法:
f.setAccessible(true);//为 true 时,f.get(harry) 就不会存在异常,访问控制无效,可访问或修改域的值
Object v = f.get(harry);
获取不同类型数值的问题
name 类型为 String 他可以作为 Object 对象返回。但是如果值为 double 类型就有问题,java 中数值类型并不是对象。
解决方法为:
f.get(harry);//也可以,会将域值打包进对象包装器
f.getDouble(harry);//不用说明
修改域的值
使用 set 方法:
Employee harry = new Employee("xuhongliang");
Class cl = harry.getClass();
try {
Field f = cl.getDeclaredField("name");
//末尾没有 s ,表示提供方法的域名指定获取的域的 Field 类型
f.setAccessible(true);
f.set(harry, "sanded");
//获取一个对象,其值为 harry 对象的 f 域的当前值
System.out.println(f.get(harry).toString());//sanded
System.out.println(harry.getName());//sanded
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
API
//java.lang.reflect.AccessibleObject
void setAccessible(boolean flag) //设置可访问标记。true 表明屏蔽访问检察;false 反之。
boolean isAccessible() //返回反射对象的可访问标志的值。
static void setAccessible(AccessibleObject[] array, boolean flag) //设置对象数组可访问标志
//java.lang.Class
Field getField(String name) //获取指定名称的公有域
Field[] getField() //获取包含公有域的数组
Field getDeclaredField(String name) //获取指定名称的域
Field[] getDeclaredField(String name) //获取包含声明的全部域的数组
//java.lang.reflect.Field
Object get(Object obj) //返回 obj 对象中用 Field 对象表示的域值
void set(Object obj, Object newValue) //设置 obj 对象中 Field 对象表示的域的值
还记得扩大数组长度的方法吗:
int[] a = Arrays.copyOf(new int[2], 100);
System.out.println(a.length);//100
它的源码使用了重载,针对了不同类型的数组类型,针对不同类的源码如下:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
可见源码使用了泛型来实现。
我们不使用泛型,使用反射:
public static Object goodCopyOf(Object a, int newLength){
Class cl = a.getClass();
if (!cl.isArray()) return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
API
//java.lang.reflect.Array
static Object get(Object array, int index)
static xxx getXxx(Object array, int index)
// xxx 是 boolean byte char double float int long short
// 方法返回存储在给定位置上的给定数组的内容
static void set(Object array, int index, Object newValue)
static void setXxx(Object array, int index, xxx newValue)
// 将一个新值存储到给定位置上的给定数组中
static int getLength(Object array)
// 获取数组的长度
static Object newInstance(Class componentType, int length)
static Object newInstance(Class componentType, int[] lengths)
// 返回一个具有给定类型、给定维数的新数组
反射机制允许你调用任意方法。
Method 类中的 invoke 方法,允许调用包装在当前 Method 对象中的方法:
Object invoke(Object obj, Object... args)
//第一个参数是隐式参数,其余的对象提供了显式参数
//如果没有隐式参数,使用 null 代替,比如静态方法没有隐式参数就设置为 null
假设 ml 代表 Employee 类的 getName 方法,下面显式了调用方法:
String n = (String) ml.invoke(harry);
//如果返回类型是基本类型,invoke 会返回其包装器类型。
因为方法可以重载,所以不能像域那样方便的获取。为了准确的得到想要的方法,必须提供想要的方法的参数类型,getMethod 的签名:
Method getMethod(String name, Class... parameterTypes)
获取类方法的方法指针:
Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);
用反射调用 Math.sqrt 静态方法:
Method f = Math.class.getMethod("sqrt", double.class);
double y = (Double) f.invoke(null, x);
总结:
标签:day 地方 -- dir ant 等于 prot can 用途
原文地址:https://www.cnblogs.com/lovezyu/p/9127456.html