打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
给大忙人看的Java核心技术笔记(4、继承与反射)

继承是在现有类的基础上创建新类的过程。(实例变量和静态变量统称为域,类中的域、方法、嵌套类、接口统称为类成员)

反射机制:在程序运行期间查找类及其成员的能力

abstract方法没有实现;abstract类不能被实例化。

  子类不能直接访问父类的私有实例变量。

  不同于this引用,super不是对象的引用,而是绕过动态查找方法并调用特定方法的指令。

  覆盖一个方法时,可以将返回类型改成子类型(协变返回类型是允许的)

  重载一个方法时,子类方法的可见性至少与父类方法一样。父类方法是公有的,子类方法也必须声明为公有的。

1、子类的构造

  因为子类的构造函数不能访问父类的私有变量,所以必须通过父类的构造函数来初始化他们。

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

2、父类赋值

  将一个子类对象赋值给父类变量是合法的

1 Manager boss = new Manager();2 Employee empl = boss;

  ※此时在父类变量上调用方法时。即便类型是父类型,但是还是会调用子类型的方法。当调用方法时,虚拟机会查看对象的实际类型,并且定位方法的版本。这个过程被称作动态方法查找

  有了动态方法查找,就可以编写父类同意的操作,根据不同的子类返回不同的结果(一样的代码,返回不同的结果)。

  在Java中,父类赋值同样适用于数组。java数组是协变的。

1 Manager[] bosses = new Manager[10];2 Employee[] empls = bosses;    //合法的3 empls[0] = new Employee()    //编译通过,运行时错误,父类不能放在实际为子类数组里,ArrayStoreException

  将子类放入父类对象中,只能调用父类的方法。

1 Employee empl = new Manager()2 empl.setBonus(100);    //编译报错,父类中没有setBonus方法3 4 //可以将父类引用转化成子类引用5 if(empl instanceof Manager){6     Manager mgr = (Manager)empl;7     mgr.setBonus(100);8 }

  将一个方法声明为final时,子类不可以覆盖它。final并不能提高效率,现代虚拟机会进行推理进行自动内联(http://blog.csdn.net/zq602316498/article/details/40266633?utm_source=tuicool&utm_medium=referral)

3、抽象方法和类

  一个类可以定义没有实现的方法,强迫子类去实现方法,这样的方法以及其所属的类被称为抽象方法和类。必须用abstract修饰。

  抽象类中可以拥有非抽象方法。

  ※不同于接口,抽象类可以拥有实例变量和构造函数。!接口可以定义常量,public static final int MAX = 10;

  构造抽象类的实例时不可能的。但是可以拥有一个类型为抽象类的变量,前提是该变量引用一个具体子类的对象

4、protected类型

  同一个包下的类都能访问,不同包下的子类能访问。(缺省类型仅包内可访问)

5、类比接口优先

  假设某类继承了另一个类,而且它又实现了一个接口,碰巧被继承的类和被实现接口有一个同名方法。

 1 public interface Named{ 2     default String getName(){retrun;} 3 } 4  5 public class Person{ 6     public String getName(){return ;} 7 } 8  9 public class Student extends Person implements Named{10 }11 //父类的实现总是“赢过”接口的实现

6、toString方法

  当一个对象与一个字符串连接时,java编译器自动调用该对象的toString方法。“”+x自动将x toString,及时x为null也可以工作。

  打印数组用Arrays.toString,打印多维数组Array.deepToString

7、hashcode方法

  哈希码是个整数,hashcode和equals方法必须是兼容的,如果相等则哈希码相同。

  Object.hashCode方法以与实现相关的某种方法生成哈希码。可以来自内存空间,或者是用于对象缓存的一个数字(顺序的或伪随机数)。Object.equals用来检测相同的对象,唯一要注意的是相同对象拥有同样的哈希码。

  如果重定义了equals方法,也要重定义hashcode方法,来兼容equals方法,否则,当用户将你的类插入HashMap,HashSet时,可能会丢失。

8、clone方法

  clone就是创建一个拥有与原对象相同状态的不同对象。Object.clone是浅拷贝,将原对象的所有实例变量拷贝到被拷贝对象里。如果实例变量有对象的引用,就会发生问题。

1 public final class Message{2     private String sender;3     private ArrayList<String> recipients;4 }5 //克隆对象和原对象共享对recipients的引用。

  所以此时需要覆盖Message的clone方法,进行深拷贝。这是你的类必须实现Cloneable接口(没有任何方法的接口,tagging/marker接口)。Object.clone在执行之前,会检查这个接口是否被实现。同时要处理CloneNotSupportedException异常。

 1 public class Employee implements Cloneable{ 2     ... 3     public Employee clone() throws CloneNotSupportedException{ 4         return (Employee) super.clone(); 5     } 6 } 7  8 //深拷贝,Message.clone 9 public Message clone(){10     Message cloned = new Message(sender, text);11     cloned.recipients = new ArrayList<>(recipients);12     return cloned;13 }

  也可以用ArrayList.clone但是这个方法对于list中的内容还是浅拷贝。

9、枚举

  每个枚举都有固定的实例集,所以对于枚举的值来说不需要用equals,直接用==比较。也不需要toString。

  每个枚举都有一个静态方法values,该方法返回一个按照其申明次序排列的包含所有枚举实例的数组。

    for(Size s : Size.values())

  ordinal返回实例在枚举声明中的位置。枚举自动实现了Comparable<E>比较是局域序数值的。

 1 public enum Size{ 2     SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL"); 3  4     private String abbreviation; 5     Size(String abbreviation){ 6         this.abbreviation = abbreviation; 7     } 8  9     public String getAbbreviation(){return abbreviation;}10 }11 //每个枚举类型实例保证只被构造一次。12 //枚举的构造函数总是私有的。

  ※每个枚举类型实例保证只被构造一次。

  ※枚举类型的构造函数总是私有的,可以省略private修饰符。声明为public、protected会报错

  可以给单个的enum实例添加方法,但是只能覆盖枚举类中定义的方法。

 1   public enum Operation{ 2     ADD{ 3       public int eval(int arg1, int arg2){return arg1 + arg2;} 4     }, 5     SUBTRACT{ 6       public int eval(int arg1, int arg2){return arg1 - arg2;} 7     }; 8      9     public abstract int eval(int arg1, int arg2);10   }11   Operation op = Operation.ADD;12   int result = op.eval(1, 2);

  从技术上说,每个枚举常量都属于Operation的一个匿名子类,你可以放入匿名子类体的任何东西,当然也可以添加到成员的实现体。

  ※枚举类可以拥有静态成员,但是,枚举常量在静态成员之前构建,所以不能在构造函数中引用任何静态成员。可在一个静态初始化块中进行初始化工作。

1  public enum Modifier{2      PUBLIC,PRIVATE,PROTECTED,STATIC,FINAL,ABSTRACT;3      private static int maskBit = 1;4      private int mask;5      Modifier(){6        mask = maskBit; //错误-静态变量初始化在构造函数之后。7      }8  }

  可以改成:

  

 1  public enum Modifier{ 2      PUBLIC,PRIVATE,PROTECTED,STATIC,FINAL,ABSTRACT; 3      private int mask; 4     static{ 5       int maskbit = 1; 6       Modifier.FINAL.mask = maskbit; //错误-静态变量初始化在构造函数之后。 7       //或者 8       for(Modifier m: Modifier.values()){ 9         m.mask = maskbit;10       }11      }12  }

  枚举应用在switch中时,case后不用写Operation.ADD,写ADD即可。操作类型是根据switch计算的表达式类型推断出来的。

  如果想在Switch外,通过简单名称来引用枚举类型的实例,就要使用静态导入

  import static com.horstmann.corejava.Size.*;  就能用SMALL来替代Size.SMALL。

10、反射

  Class.forName方法主要用于构造那些可能在编译时还不被知晓的类的Class对象。

  在Java中Arrays属于类,但接口、基本类型、void都不是类。 String[].class.getName()返回是[Ljava.lang.String;int[]是[I。用Class.forName要传入这样的参数,而不是getCanonicalName返回的java.lang.String[]

  虚拟机为每种类型管理一个唯一的Class对象,所有可以用if(other.getClass()==Employee.class)

  Class类可以定位程序可能需要的资源,如果将资源与类文件放在同一位置,就可以打开一个文件流

  2、类加载器

    虚拟机指令存储在类文件。每个文件都包含单个类或接口的指令。一个类文件可以存放在文件系统、jar文件、远程、甚至内存中动态创建。类加载器负责加载字节流,在虚拟机中转化为一个类或接口。

    当执行Java程序是,至少会涉及三个类加载器。

      bootstrap类加载器(加载java类库,jre/lib/rt.jar,这是虚拟机的一部分)

      扩展类加载器(加载jre/lib/ext中的“标准库扩展”部分)

      系统类加载器(加载应用程序类)

    ※((URLClassLoader) MainClass.class.getClassLoader()).getURLs可以得到一个URL对象数组,里面包含了classpath上的目录和JAR文件信息。

  3、上下文类加载器  

  当一个类别其他类需要时,他会被透明地加载。但,某方法动态的加载类,并调用改方法的类又是被另一个类加载器加载的。

1 public class Utils{2     Object createInstance(String className){3         Class<?> cl = Class.forName(className);4     }5 }

  你用另一个类加载器加载一个插件,该加载器从插件Jar文件中读取信息,该插件调用Utils.createInstance()来实例化插件Jar中的一个类。

  Utils.createInstance使用它自己的类加载器来执行Class.forName方法,并且那个类加载器不会查看插件Jar文件。这个现象被称为类加载器反转(classloader inversion)

  解决方法是将类加载器传递给utility方法,然后传给forName方法

  反射机制允许程序在运行时检查任意对象的内容,并调用他们的任意方法。

    getModifiers返回一个整数,该整数比特为信息描述了方法所使用的修饰符。通过Modifier.isPublic和Modifier.isStatic来分析返回的整数。

    getFields、getMethods、getConstructions方法会返回该类支持的公有域、方法、构造函数还包括其他方法继承的公有成员

    getDeclaredField只包含本类中声明的。

  如果编译类时采用-parameters标记,则参数名称只在运行时可用。

  用Field.get(Obj)可以获得类域的内容

1 Object obj = new Object;2 for(Field f : obj.getClass().getDeclaredFields()){3     //让所有域都可以访问。4     f.setAccessible(true)5     //获取域中的内容6     Object value = f.get(obj);7     //设置域中内容8     f.setDouble(obj, value*1.2);9 }

  调用Method

Person p = ...;Method m = P.getClass().getMethod("setName",String.class);Object result = m.invoke(obj, arg1, arg2, ...);//如果方法是静态的,则给初始参数赋予null

  对象构造

  使用无参构造函数构造对象,可直接调用Class对象的newInstance方法:

1 Class<?> cl = ...;2 Object obj = cl.newInstance();3 //其他构造函数4 //公有构造函数参数是一个Int5 Constructor constr = cl.getConstructor(int.class)6 Object obj = constr.newInstance(42);

  JavaBean

    对于Boolean属性,用isPropert作为Getter更好。

    对于JavaBean的反射,对于给定类使用PropertyDescriptor获取BeanInfo对象

1 Class<?> cl = ...;2 BeanInfo info = Introspector.getBeanInfo(cl);3 PropertyDescriptor[] props = info.getPropertyDescriptors();

    对于给定的PropertyDescriptor,调用getName和getPropertyType方法获取属性的名称和类型。getReadMethod和getWriteMethod产生Method对象的Getter和setter方法。

1 String propertyName = ...;2 Object propertyValue = null;3 for(PropertyDescriptor prop : props){4     if(prop.getName().equals(propertyName))5         propertyValue = prop.getReadMethod().invoke(obj)6 }

11、代理

  Proxy类可以在运行时创建实现了给定的接口或者接口集的新类。只有当你在编译时还不知道要实现哪一接口时,才需要这样的代理。

    不能直接调用newInstance方法——因为接口不能实例化。

  代理类包含了特定接口所要求的所有方法,并且这些方法是在Object类中定义的(toString、equals等)。但是,由于你不能在运行时为这些方法编写新代码。因此需要提供一个调用处理器,一个实现了InvocationHandler接口的类对象。

    InvocationHandler接口只有一个方法。

      Object invoke(Object proxy, Method method, Object[] args)

  无论何时,在代理对象上调用方法时,调用处理器的invoke方法都会被以Method对象和原调用的参数调用。

  调用处理器必须知道如何处理这个调用。调用处理器可能采取的行为有很多种(将调用送到远程服务器、为了调试目的而跟踪此调用)

  创建一个代理对象,要使用Proxy类的newProxyInstance方法。该方法有三个参数

    一个类加载器,null使用默认类加载器

    一个Class对象数组,每个被实现的接口都有一个Class对象

    调用处理器

Object[] values = new Object[1000];for(int i=0;i<values.length;i++){    Object value = new Integer(i);    values[i] = Proxy.newProxyInstance(null,value.getClass.getInterface(),//调用处理器的lambda表达式(Object proxy, Method m ,Object[] margs)->{    System.out.println(value+m.getName()+Arrays.toString(margs));    retrun m.invoke(value,margs);    });}
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
静态代理与动态代理、CGLIB
java的多态、重载、构造函数、析构函数的理解
浅析Java中的构造代码块、静态代码块与构造方法
Java类和对象及实例
c#基础五 面向对象高级编程(封装 继承 多态 抽象类 密封类 接口)
精选30道Java笔试题解答
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服