• Java基础-继承


    继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。

    类、超类和子类

    定义子类


    关键字“extends”表示继承。已存在的类称为超类、基类或父类。新类称为子类、派生类或孩子类。
    在通过扩展超类定义子类的时候,仅需指出子类域超类的不同之处。因此在设计类的时候,应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。

    覆盖方法(override)


    超类中有些方法对子类并不一定适用。
    创建一个超类:

      1 public class Employee {
      2     private String name;
      3     private double salary;
      4 
      5     public Employee(String n, double s){
      6         name = n;
      7         salary = s;
      8     }
      9 
     10     public String getName(){
     11         return name;
     12     }
     13 
     14     public double getSalary(){
     15         return salary;
     16     }
     17 }
     18 


    创建一个子类:

      1 public class Manager extends Employee {
      2     private String name;
      3     private double salary;
      4     private double bonus;
      5 
      6     public Employee(String n, double s){
      7         name = n;
      8         salary = s;
      9     }
     10 
     11     public String getName(){
     12         return name;
     13     }
     14 
     15     public double getSalary(){
     16         double sumSalary = super.getSalary();
     17         return sumSalary + bonus;
     18     }
     19 
     20     public void setBonus(double bonus){
     21         this.bonus = bonus;
     22     }
     23 }
     24 


    这里Employee超类中的getSalary()方法就不适用Manager子类了,所以子类中提供了一个新的方法来覆盖超类中的这个方法。

    子类构造器

      1 public Manager(String name, double salary){
      2     super(name,salary);
      3     bonus = 0;
      4 }


    这里的super是“调用超类Employee中含有name、salary参数的构造器”的简写形式。
    由于子类构造器不能访问超类的私有域,所以必须使用超类的构造器(super)对这部分私有域进行初始化。
    使用super调用构造器的语句必须是子类构造器的第一条语句。
    若子类的构造器没有显式的调用超类的构造器,则将自动调用超类默认(无参数)的构造器。
    若超类没有不带参数的构造器,并且子类中的构造器又没有显式的调用超类的其他构造器,Java编译器将会报错。

    继承层次


    继承并不仅限于一个层次:
    继承层次
    由一个公共超类派生出来的所有类的集合被称为继承层次。
    在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链。

    多态


    一个对象变量可以指示多种实例类型的现象被称为多态。
    例如:一个超类类型的变量既可以指示超类的实例,也可以指示子类的实例类型。当程序运行时可以自动的选择使用超类的方法,也可以调用子类的方法。
    这种在运行时能够自动地选择调用哪个方法的现象称为动态绑定
    动态绑定的一个非常重要的**特性**:无需对现存代码进行修改,就可以对程序进行扩展。(假设增加一个新的子类N,并且变量e有可能引用N的对象,则不需要对包含调用e.xxx的方法进行重新编译。)
    有一个用来判断是否应该设计为继承关系的简单规则,就是“is - a”规则,表明每个子类的对象也是超类的对象。
    “is - a”规则的另一种表达时*置换法则*,表明程序中出现超类对象的任何地方都可以用子类对象置换。即可以将子类的引用赋给超类变量,但是不能将超类的引用赋给子类变量。

    理解方法调用


    假设要调用x.f(args)方法,隐式参数x声明为类C的一个对象。
    1. 编译器查看对象的声明类型和方法名。假设调用x.f(param),但是C类的对象可能存在多个名字为f,但是参数类型不同的方法(f(int)或者f(String)),编译器将会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法。
    此时编译器已获得所有可能被调用的候选方法。
    2. 然后编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析。由于允许类型转换,所以这个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,则会报错。
    此时编译器已获得需要调用的方法名字和参数类型。
    3. 如果是private方法,static方法、final方法或者构造器,那么编译器将可以准确的知道应该调用哪个方法,这种调用方式称为静态绑定
    4. 当程序运行时,并且采用动态绑定调用方法时,虚拟机一定要调用与x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型时D,D类时C类的子类,现在调用x.f(String),如果D类定义了方法f(String),就直接调用它;否则将在C类中寻找f(String)方法,以此类推。
    每次调用方法都进行搜索,时间开销很大。所以虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名和实际调用的方法。这样在调用方法的时候,虚拟机仅仅需要查找这个表就可以了。

    阻止继承:final类和final方法


    有时候可能不希望将某个类作为超类来定义子类,这种不允许扩展的类被称为final类。格式如下:

      1 public final class 类名 {}


    此外类中的特定方法也可以被声明为final方法,此时就不能覆盖这个方法,final类中的所有方法自动的成为final方法。
    强制类型转换
    有时候可能需要将某个类的对象引用转换成另一个类的对象引用,就像前面有时候需要将浮点类型转换成整型数值一样,转换的语法类似。
    进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。
    一个良好的设计习惯:在进行类型转换之前,先查看一下是否可以成功进行转换,使用instenceof操作符就可以实现。

    抽象类


    如果自下而上在类的继承层次机构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度上看,祖先类更加通用,实际使用时只将它作为派生其他类的基类,而不作为想使用的特定的实例类。
    抽象类和抽象方法使用abstract关键字修饰,格式如下:

      1 public abstract class Person {
      2     public abstract String getDescription();
      3 }


    注意:

    • 抽象类不能被实例化。
    • 包含一个或多个抽象方法的类本身必须被声明为抽象类。
    • 类即使不包含抽象方法,也可以将该类声明为抽象类。

    抽象类中可以包含抽象方法,也可以包含具体数据和具体方法:

      1 public abstract class Person {
      2     private String name;
      3     public Person(String name) {
      4         this.name = name;
      5     }
      6     public abstract String getDescription();
      7     public String getName() {
      8         return name;
      9     }
     10 }


    抽象方法充当着占位的角色,具体实现在子类中。扩展抽象类可以有两种选择:
    第1种是在抽象类中定义部分抽象方法或不定义抽象类方法,这样就必须将子类也标记为抽象类。
    第2种是定义全部的抽象方法,这样子类就不是抽象的了。

    受保护的访问


    Java用户控制可见性的4个修饰符:

    • private 仅对本类可见。
    • protected 对本包和所有子类可见。
    • public 对所有类可见。
    • 默认(无修饰符) 对本包可见。

    在实际应用种,要谨慎使用protected属性。假设需要将设计的类提供给其他程序员使用,而在这个类种设置了一些受保护域,由于其他程序员可以由这个类再派生出新类,并访问其中的受保护域,因此如果需要对这个类的实现进行修改,就必须通知所有使用这个类的程序员,这违背了OOP提倡的数据封装原则。

    Object:所有类的超类


    Object类是Java中所有类的始祖,再Java中的每个类都是由它扩展而来的。
    在Java中只有*基本类型*不是对象,例如数值、字符和布尔类型的值都不是对象。所有的数组类型,不论是对象数组还是基本类型的数组都扩展了Object类。
    Object类有很多重要的方法。

    equals方法


    equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。
    Object类可以在JDK的安装路径下找到,打开JDK的安装目录,里面有一个名为src.zip的压缩包,将这个包解压缩,在java目录下的lang目录下可以找到Object类,可以使用notepad++打开Object.java文件,可以看到equals方法代码如下:

      1 public boolean equals(Object obj) {
      2     return (this == obj);
      3 }


    如果两个对象具有相同的引用,它们一定是相等的。对于多数类来说,这种判断并没有什么意义,例如采用这种方式比较两个PrintStream对象是否相等就完全没有意义,所以很多时候会重写equals方法,例如String的equals方法,就对Object的该方法进行了重写:

      1 public boolean equals(Object anObject) {
      2     if (this == anObject) {
      3         return true;
      4     }
      5     if (anObject instanceof String) {
      6         String anotherString = (String)anObject;
      7         int n = value.length;
      8         if (n == anotherString.value.length) {
      9             char v1[] = value;
     10             char v2[] = anotherString.value;
     11             int i = 0;
     12             while (n-- != 0) {
     13                 if (v1[i] != v2[i])
     14                     return false;
     15                 i++;
     16             }
     17             return true;
     18         }
     19     }
     20     return false;
     21 }


    再来看一下java.util.Objects类中的equals方法:

      1 public static boolean equals(Object a, Object b) {
      2     return (a == b) || (a != null && a.equals(b));
      3 }

    注意:
    在子类中定义equals方法时,首先调用超类的equals方法。
    如果检测失败,对象就不可能相等。如果超类中的域都相等,再比较子类中的实例域是否相等。

    相等测试与继承


    Java语言规范要求equals方法具有以下特性:

    • 自反性:对于任意非空引用x,x.equals(x)应该返回true。
    • 对称性:对于任意非空引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。
    • 传递性:对于任意非空引用x,y和z,如果x.equals(y)返回true,y.equals(z)也返回true,则x.equals(z)也应该返回true。
    • 一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
    • 对于任意非空引用x,x.equals(null)应该返回false。

    如果隐式和显式的参数不属于用一个类,这种情况该怎么处理呢。
    前面使用instanceof进行检测过,此时不能在超类中重写的equals方法中使用instanceof进行检测,因为这样做无法解决显式参数是子类的情况,因为使用instanceof检测会返回true。
    例如e是超类的一个对象,m是子类的一个对象,两个对象的域相同,如果e.equals(m)中使用了instanceof进行检测,则返回true,意味着m.equals(e)也应该返回true,因为对称性不允许这个方法调用返回false,或者抛出异常。
    因此使子类抽到了限制,子类的equals方法必须能够用自己与任何一个超类对象进行比较,而不考虑子类拥有的那部分特有的域信息,所以使用instanceof进行检测并不完美。
    可以分成两种情况看待这个问题:

    如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测。

    如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同的子类的对象之间进行相等的比较。
    如果在子类中重新定义equals方法,就要在其中包含调用super.equals()方法。

    hashCode


    hash code(散列码)是由对象导出的一个整型值。散列码是没有规律的。
    两个不同对象的hashCode()基本上不会相同。如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
    hashCode方法应该返回一个整型数值(也可以是负数),并且合理的组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
    Equals域hashCode的定义必须一致:如果x.equals(y)返回true,那么x.hashCode()就必须域y.hashCode()具有相同的值。

    toString方法


    Object类还有一个重要的方法,就是toString方法,它用于返回表示对象值的字符串。
    toString方法是随处可见的方法,因为只要对象于一个字符串通过操作符“+”连接,Java编译就会自动的调用toString方法,以便获得这个对象的字符串描述。
    注意:
    在调用x.toString()的地方可以用""+x代替。这条语句将一个空串域x的字符串相连接,这里的x就是x.toString()。
    即使x使基本类型,这条语句依然有效。

    泛型数组列表


    前面学习了数组,数组可以保存多个元素,但在某些情况下无法确定到底要保存多少个元素,此时数组将不再适用,因为一旦确定了数组的大小,就不能改变。
    在Java中解决这个问题最简单的方法使使用另一个类:ArrayList。该类使用起来有点像数组,但是在增加或者删除元素时,具有自动调节数组容量的功能。
    ArrayList是一个采用类型参数泛型类。为了指定数组列表保存的元素对象类型,需要使用一对尖括号将类名括起来加在后面,例如:ArrayList<String>。
    创建一个ArrayList的对象格式如下:

      1 ArrayList<数据类型> 变量名 = new ArrayList<数据类型>();


    但是尖括号中的类型必须是引用数据类型,不能是基本数据类型。
    基本数据类型|对应的引用数据类型的表现形式:

    基本数据类型 对应的引用数据类型的表现形式:
    byte Byte
    short Short
    int Integer
    long Long
    float Float
    double Double
    char Character
    boolean Boolean


    创建示例如下:

      1 ArrayList<String> list = new ArrayList<String>();
      2 ArrayList<Integer> list = new ArrayList<String>();
      3 ArrayList<Person> list = new ArrayList<String>();


    使用add方法可以将元素添加到数组列表中,默认新增元素是追加到集合的末尾,但是也可以给add方法传递一个位置参数,用来在数组列表中间插入元素,位于指定位置之后的所有元素都要向后移动一个位置;可以用remove方法删除数据列表中的元素;set方法实现改变元素的操作;get方法可以返回集合中指定位置上的元素;size方法返回集合中元素的个数;clear方法用于清空数组列表中的所有元素。

      1 public static void main(String[] args) {
      2     ArrayList<String> list = new ArrayList<String>();
      3     list.add("stu1");
      4     list.add("stu2");
      5     list.set(0,"stu3");
      6     System.out.println("集合的长度:" + list.size());
      7     System.out.println("第1个元素是:" + list.get(0));
      8     System.out.println("第2个元素是:" + list.get(1));
      9 }


    既然ArrayList相当于是一个长度可变的数组,所以访问集合中的元素也与数组元素的访问一样,采用索引方式访问。
    数组列表管理着对象引用的一个内部数组。最终数组的全部空间有可能被用尽。这就显现出数组列表的操作魅力:如果调用add方法且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。
    如果使用时已知初始容量(例如为10),可以将该值传递给ArrayList构造器:

      1 ArrayList<String> list = new ArrayList<>(10);


    遍历数组列表与遍历数组的方式相同,都可以使用for循环和for each循环进行遍历。

    对象包装器与自动装箱


    有时需要将int这样的基本类型转换为对象,前面的table中已经列出了所有基本类型对应的类,Integer类对应基本类型int,Integer等这些类就称为包装器。
    对象包装器类是不可变的,一旦构造了包装器,就不允许更改包装在其中的值,同时对象包装器还是final,不能定义它们的子类。
    想要声明一个整型的数组列表,不能写成ArrayList<int>,而应该写成如下形式:

      1 ArrayList<Integer> list = new ArrayList<>();


    一个很有用的特性,可以更方便与添加int类型的元素到ArrayList<Integer>中:

      1 list.add(3);


    将自动的变换成:

      1 list.add(Integer.value(3));


    这种变换就称为自动装箱
    相反当将一个Integer对象赋给一个int值时,将会自动地拆箱。编译器会将如下语句:

      1 int n = list.get(i);


    翻译成:

      1 int n = list.get(i).intValue();


    在算术表达式中也能够自动地装箱和拆箱,例如自增操作符应用于一个包装器引用:

      1 Integer n = 1;
      2 n++;


    此时编译器将自动的插入一条对象拆箱的指令,然后进行自增计算,最后再将结果装箱。
    注意:
    由于包装器类引用是可以为null的,所以自动装箱有可能会抛出一个NullPointerException异常。
    如果一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double。
    装箱和拆箱时编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机知识执行这些字节码。

    参数数量可变的方法


    现在的Java提供了可以用可变的参数数量调用的方法(“变参”方法)。
    printf方法是这样定义的:

      1 public class PrintStream {
      2     public PrintStream printf(String fmt, Object... args) {
      3         return format(fmt, args);
      4     }
      5 }


    这里的“...”是Java代码的一部分,表明这个方法除了fmt参数之外,还可以接收任意数量的对象。实际上printf方法接收两个参数,一个是格式字符串,一个是Object[]数组,其中保存着所有的参数(如果调用者提供的是整型数组或者其他基本类型的值,自动装箱功能将把它们转换成对象)。现在将扫描fmt字符串,并将第i个格式说明符与args[i]的值相匹配。

    枚举类


    前面已经定学习过如何定义枚举类型。

      1 public enum  SeasonEnum {
      2     SPRING,SUMMER,FALL,WINTER
      3 }


    实际上这个声明定义的是一个类,类中有4个实例,在此尽量不要构造新对象。
    因此在比较两个枚举类型的值时,永远不需要使用equals方法,直接使用“==”即可。
    因为示例代码中只写了内容队列,所以后面不用加分号“;”。
    枚举类型中可以添加构造器、方法和域。构造器只是在构造枚举常量的时候被调用。当添加了构造器、方法和域时,内容对列后面就需要加分号“;”。

      1 public enum  SeasonEnum {
      2     SPRING,SUMMER,FALL,WINTER;
      3     private  int  other;
      4 }


    所有的枚举类型都是Enum类的子类,所以其超类不是Object类。所以继承了Enum类的很多方法,其中toString()方法能够返回枚举常量名,例如:SeanEnum.SPRING.toString()将返回字符串“SPRING”。toString的逆方法是valueOf()。

      1 SeanEnum s = Enum.valueOf(SeanEnum.class, "SPRING");


    将s设置成SeanEnum.SPRING。
    每个枚举类型都有一个静态的values方法,返回一个包含全部枚举值的数组。

      1 SeanEnum[] v = SeanEnum.values;


    返回包含元素SeanEnum.SPRING、SeanEnum.SUMMER、SeanEnum.FALL、SeanEnum.WINTER的数组v。
    ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数。例如:SeanEnum.SPRING.ordinal()返回0。

    反射

    反射


    Java的反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有域和方法;对于任意一个对象,都能够调用它的任意一个域和方法。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
    反射机制具体功能包括:

    • 运行时分析类的能力。
    • 在运行时查看对象。
    • 实现通用的数组操作代码。
    • 利用Method对象。

    在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
    在Java中有专门的类访问这些信息,保存这些信息的类被称为Class。
    注意:这个名字很容易混淆而使人不理解,重点注意区分.class文件(字节码文件)和Class类的对象。
    反射的过程:
    加载.class文件到内存中-->系统在内存中自动生成.class文件的Class类的对象-->由这些Class类的对象可以反向获取类的信息(域、构造器、方法等)-->进而使用这些类的信息。

    类的加载


    当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对类进行初始化。
    加载就是指将编译后的.class文件读入内存,并为之创建一个.class文件(字节码文件)的Class对象。任何类被使用时,系统都会为它建立一个Class类的对象(该对象只能由系统自动创建,终生唯一,不能由使用者自定义创建)
    连接首先是验证类是或否有正确的内部结构;然后进行准备,为类的静态成员分配内存,并设置默认初始化值;第三是解析,将类的二进制数据中的符号引用替换为直接引用,以节省计算机资源。
    初始化就是Java类中的正常初始化。

    类初始化的时机

    • 创建类的实例。
    • 调用类的静态变量,或者为静态变量赋值。
    • 类的静态方法。
    • 初始化某个类的子类,其超类先加载到内存中。
    • 直接用java命令运行的类。
    • 使用反射方式去创建某个类或者接口对应的对象时。

    类加载器


    负责将编译后的.class文件加载到内存中,并且为它生成对应的Class对象。
    三种类加载器:

    • Bootstrap ClassLoader:根类加载器,也被称为引导类加载器,负责Java核心类(String、System等)的加载,在JDK目录下的JRE目录下的lib目录下的rt.jar文件中。
    • Extension ClassLoader:扩展类加载器,负责JRE的扩展目录中jar包的加载,在JDK目录下的JRE目录下的lib目录下的ext目录中。
    • System ClassLoader:系统类加载器,负责在JVM虚拟机启动时,加载来自java命令的class文件,以及CLASSPATH环境变量所指定的jar包和类路径。

    获取.class文件对象的三种方式:


    1. 对象获取
    2. Class类的静态方法获取
    3. 类名获取

      1 //1. 对象获取
      2 Employee e = new Employee();
      3 //调用其超类Object类中的getClass()方法,返回一个Class类型的实例。
      4 Class c1 = e.getClass();
      5 System.out.println(c1);
      6 
      7 //2. Class类的静态方法forName(保存在字符串中的类全名,即:包.类名)获取
      8 String className = "com.test.Employee"
      9 Class c2 = Class.forName(className);
     10  System.out.println(c2);
     11 
     12 //3. 类名获取
     13 Class c3 = Employee.class 14 System.out.println(c3);
     15 


    注意:
    Class类的静态方法forName()获取对象时,参数必须是类名或者接口名才能够执行。
    否则该方法会抛出一个checked exception,所以无论何时使用这个方法都应该提供一个异常处理器(throws ClassNotFoundException)。

    虚拟机为每个类型管理一个Class对象。因此可以使用“==”运算符(当然也可以使用equals方法)实现两个类对象的比较操作。

    利用反射操作对象


    过程是:获取.class文件对象-->从获取的.class文件对象中,获取需要的成员。
    java.lang.reflect包中的Constructor用于描述类的构造器。
    获取构造器并运行:

    获取默认无参数构造器并运行:

      1 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
      2 
      3     try {
      4         Class c = Class.forName("Employee");
      5         Constructor constructor = c.getConstructor();
      6         Object object = constructor.newInstance();
      7         System.out.println(object);
      8     } catch (ClassNotFoundException e) {
      9         System.out.println("类名错误!");
     10     }
     11     System.out.println("获取无参数构造器程序执行完成!");
     12 
     13 }
     14 



    Constructor类的newInstance()方法,可以动态的创建一个Employee类的实例,调用默认构造器初始化新创建的对象。

    快捷获取默认无参数构造器并运行:

      1 try {
      2     Class c = Class.forName("Employee");
      3     Object object = c.newInstance();
      4     System.out.println(object);
      5 } catch (ClassNotFoundException e) {
      6     System.out.println("类名错误!");
      7 }
      8 System.out.println("使用Class类的newInstance快速获取无参数构造器程序执行完成!");



    Class类的newInstance()方法,可以动态的创建一个Employee类的实例,调用默认构造器初始化新创建的对象。
    注意:
    Class类的newInstance()方法只能调用默认的无参数构造器,如果这个类没有默认的构造器,会抛出一个异常。
    需要调用传递参数的构造器,则必须使用Constructor类的newInstance()方法。

    获取有参数构造器并运行:

      1 try {
      2     Class c = Class.forName("Employee");
      3     Constructor constructor = c.getConstructor (String.class,double.class,int.class,int.class,int.class);
      4     Object object = constructor.newInstance("Dcl_Snow",10000,2019,1,1);
      5     System.out.println(object);
      6 } catch (ClassNotFoundException e) {
      7     System.out.println("类名错误!");
      8 }
      9 System.out.println("获取全参数构造器程序执行完成!");

    获取私有构造器并运行:
    先在Employee类中添加一个如下的私有构造器:

      1 private Employee( double salary, String name, int year, int month, int day) {
      2     this.name = name;
      3     this.salary = salary;
      4     hireDay = LocalDate.of(year, month, day);
      5 }



    不推荐获取私有构造器,因为破坏了封装性):

      1 try {
      2     Class c = Class.forName("Employee");
      3     Constructor constructor = c.getDeclaredConstructor(double.class, String.class, int.class, int.class,int.class);
      4     constructor.setAccessible(true);
      5     Object object = constructor.newInstance(10000, "Dcl_Snow", 2019, 1, 1);
      6     System.out.println(object);
      7 } catch (ClassNotFoundException e) {
      8     System.out.println("类名错误!");
      9 }
     10 System.out.println("获取私有构造器程序执行完成!");



    setAccessible()是Constructor的超类AccessibleObject 的方法:
    将此对象的accessible标志设置为指示的布尔值。 true的值表示反射对象应该在使用时抑制Java语言访问检查。 false的值表示反映的对象应该强制执行Java语言访问检查。
    java.lang.reflect包中的Constructor用于描述类的域。
    获取域并更改域值:
    先在Employee类中添加一个域:

      1 public String sex;



    获取域并设置域值:

      1 public static void main(String[] args) throws IllegalAccessException, InstantiationException {
      2 
      3     try {
      4         Class c = Class.forName("Employee");
      5         Object object = c.newInstance();
      6         Field field = c.getField("sex");
      7         field.set(object, "man");
      8         System.out.println(object);
      9     } catch (ClassNotFoundException | NoSuchFieldException e) {
     10         System.out.println("类名错误!");
     11     }
     12     System.out.println("获取域并设置域值执行完成!");
     13 }
     14 



    获取方法并运行:


    获取空参数的方法并运行:
    先在Employee类中添加一个如下的空参数的方法:

      1 public void work(){
      2     System.out.println("大家在工作!");
      3 }



    获取空参数的方法并运行:

      1 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
      2 
      3     try {
      4         Class c = Class.forName("Employee");
      5         Object object = c.newInstance();
      6         Method method = c.getMethod("work");
      7         method.invoke(object);
      8     } catch (ClassNotFoundException e) {
      9         System.out.println("类名错误!");
     10     }
     11     System.out.println("获取无参数方法并运行程序执行完成!");
     12 }
     13 



    使用getMethod()方法获取方法,然后使用invoke()方法执行该方法。
    invoke()方法:在具有指定参数的方法对象上调用此方法对象表示的底层方法(即获取哪个成员方法就调用哪个成员方法)。

    获取有参数的方法并运行:
    先改造Employee类中的raiseSalary方法:

      1 public void raiseSalary(double byPercent) {
      2     double raise = this.salary * byPercent / 100;
      3     this.salary += raise;
      4     System.out.println(salary);
      5 }



    获取有参数的方法并运行:

      1 try {
      2     Class c = Class.forName("Employee");
      3     Object object = c.newInstance();
      4     Method method = c.getMethod("raiseSalary",double.class);
      5     method.invoke(object,5);
      6 } catch (ClassNotFoundException e) {
      7     System.out.println("类名错误!");
      8 }
      9 System.out.println("获取有参数方法并运行程序执行完成!");



    反射泛型的擦除


    .class文件是没有泛型的:

      1 public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
      2 
      3     ArrayList<String> arrayList = new ArrayList<>();
      4      arrayList.add("first");
      5 
      6     Class c = arrayList.getClass();
      7     Method method = c.getMethod("add",Object.class);
      8     method.invoke(arrayList,100);
      9     method.invoke(arrayList,101.01);
     10     method.invoke(arrayList,true);
     11     System.out.println(arrayList);
     12 }
    



    该程序执行结果打印一个数组列表:[first, 100, 101.01, true],元素类型分别为String、int、double、boolean。可以看到利用反射将程序开始定义的泛型类型为String给擦除了,使得arrayList存储了包括String在内共四种类型的元素。
    注意:
    实际情况下arrayList这种数组列表没有任何意义,这里只是加强对反射的理解。

    继承的设计技巧

    • 将公共操作和域放在超类。
    • 不要使用受保护的域。protected机制不能够带来更好的保护由两点原因:第一,子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并编写代码以直接访问protected的实例域,从而破坏了封装性。第二,在Java程序设计语言中,在同一个包中的所有类都可以访问protected域,而不论它是否是这个类的子类。
    • 使用继承实现“is - a”关系。继承可以简化代码,但切记滥用。
    • 除非所有继承的方法都有意义,否则不要使用继承。
    • 在覆盖方法时,不要改变预期的行为。置换原则不仅应用于语法,也可以应用于行为。在覆盖一个方法时,不应该毫无缘由的改变行为的内涵。
    • 使用多态,而非类型信息。
    • 不要过多的使用反射。

    反射机制使得人们可以通过在运行时查看域和方法,让人们编写除更具有通用性的程序。这种功能对于编写系统程序来说极其实用,但是通常不适用于编写应用程序。
    反射是很脆弱的,编译器很难帮助人们发现程序中的错误,只有在运行时才发现错误并导致异常。

  • 相关阅读:
    Software Solutions CACHE COHERENCE AND THE MESI PROTOCOL
    CACHE COHERENCE AND THE MESI PROTOCOL
    Multiprocessor Operating System Design Considerations SYMMETRIC MULTIPROCESSORS
    Organization SYMMETRIC MULTIPROCESSORS
    PARALLEL PROCESSING
    1分钟内发送差评邮件
    Secure Digital
    SYMMETRIC MULTIPROCESSORS
    A Taxonomy of Parallel Processor Architectures
    parallelism
  • 原文地址:https://www.cnblogs.com/Dcl-Snow/p/10668834.html
Copyright © 2020-2023  润新知