给大忙人看的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 //公有构造函数参数是一个Int 5 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); }); }
http://blog.csdn.net/giserstone/article/details/17199755 java静态代理和动态代理