类型信息
运行时类型识别(RTTI)运行你在程序运行时查看和使用类型信息
- 好处:让我们从面向类型操作的禁锢中脱离出来,我们可以只面向基类编写代码(由于多态,会产生正确的行为),而且有良好的可复用性
JAVA有两种让我们运行时识别(引用所指向的)对象和类的信息的方法
- RTTI
- Reflect
Class对象
为什么要有Class对象
- 他保存了类型信息
- 用来生成普通对象
Class对象特性
- 每个类只有一个Class对象
- 使用ClassLoader(类加载器)生成Class对象,然后就可以用这个Class对象来生成普通对象了
触发”类加载“的任一条件
- new这个类的对象
- 访问这个类的static域,或staic方法
- Class.forName方法
怎样获取某个类的Class对象
- Class.forName方法
- getClass方法,(这个是Object类的方法)
- 类的名.class
Class类常用方法
- getName:获取全类名
- getSimpleName:获取简单类名
- getInterfaces:获取实现的接口的Class对象数组
- getSuperclass:获取当前类的父类的Class对象
- newInstance,虚拟构造器的理念:我不知道你的切确类型,但是我要正确的构造你的对象,可以直接当成是构造器来用
//上电的
interface HasBtteries {
}
//防水的
interface Waterproof {
}
//有炮弹发射的
interface Shoots {
}
class Toy {
public Toy() {
}
public Toy(int i) {
}
}
class FancyToy extends Toy implements HasBtteries,Waterproof,Shoots{
}
public class ToyTest {
static void printInfo(Class c) {
System.out.println("className:\t"+c.getName()+"\t is it interface:\t["+c.isInterface()+"]");
System.out.println("simpleName:\t"+c.getSimpleName());
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class c = Class.forName("com.zeng.class_test.FancyToy");
printInfo(c);
System.out.println("----------");
for (Class face : c.getInterfaces()) {//获取实现的接口,总之当类型来处理就行了
printInfo(face);
}
System.out.println("----------");
Class sup = c.getSuperclass();//获取当前类的父类
Object o = sup.newInstance();//虚拟构造器,牛逼吧
printInfo(o.getClass());
}
}
//output
className: com.zeng.class_test.FancyToy is it interface: [false]
simpleName: FancyToy
----------
className: com.zeng.class_test.HasBtteries is it interface: [true]
simpleName: HasBtteries
className: com.zeng.class_test.Waterproof is it interface: [true]
simpleName: Waterproof
className: com.zeng.class_test.Shoots is it interface: [true]
simpleName: Shoots
----------
className: com.zeng.class_test.Toy is it interface: [false]
simpleName: Toy
虚拟机中类的加载过程
- 加载,由类加载器去查找类的字节码,并通过找到的字节码创建一个Class对象
- 链接,此时将验证字节码,为静态域分配空间
- 初始化,如果这个类有基类则是初始化基类,执行静态代码块,
- 类加载初始化不是一定会执行的,而是调用静态方法(构造器也是隐式的静态方法),或者是访问类的非常数静态域时才会触发初始化
class Initable1 {
static final int staticFinal=1;
static final int staticFinal2 = ClassInitialization.random.nextInt(100);
static {
System.out.println("initializing initable1");
}
}
class Initable2 {
static int staticNonFinal=2;
static {
System.out.println("initializing initable2");
}
}
class Initable3 {
static int staticNonFinal=3;
static {
System.out.println("initializing initable3");
}
}
public class ClassInitialization {
static Random random = new Random(47);
public static void main(String[] args) throws ClassNotFoundException {
//不会触发初始化
Class<Initable1> initableClass = Initable1.class;
System.out.println("after creat initableClass ref");
//不会触发初始化
System.out.println(Initable1.staticFinal);
//会触发初始化
System.out.println(Initable1.staticFinal2);
//会触发初始化
System.out.println(Initable2.staticNonFinal);
Class<?> aClass = Class.forName("com.zeng.class_test.Initable3");
System.out.println("after creat initable2Class ref");
System.out.println(Initable3.staticNonFinal);
}
}
泛化的Class引用
- Class对象可以创建类的对象,并且包含所有可以作用于这些对象的方法,还包括静态域,故可以用Class引用来表示所指向的对象的切确类型,故可以把Class类型实际类型
- JAVASE5允许你限定Class对象的类型,让类型更具体,如Class
- 通配符
?
,使用Class<?>使代码更加规范,表明你并非是疏忽或者是碰巧使用非具体类型的Class引用,- 使用泛型创建一个Class引用,
- 限定为某种类型,如Class
- 限定为某种类型的子类,Class<? extends Number>
- 使用泛型创建一个Class引用,
转型前先类型检查
- instanceof,将判断(不是看引用)对象是不是指定类型的实例
- instanceof用于Class的等价性
- instanceof与isInstance结果一样,判断规则:你是这个类,或者是这个类的子类吗?
- ==与equals结果一样,equals没重写,当然一样,判断规则:你是这个类的确切类型吗?或者不是
反射
为什么需要反射?
- RTTI有个缺陷,通过RTTI识别类必须在编译时已知,如果需要运行时获取类信息,则是要通过反射
反射实现的支撑
- Class类和reflect类库(包),里面有Field、Method、Contructor类,分别用来代表:类的字段,方法,构造器
RTTI与反射的区别
- RTTI是编译时打开和检查.class文件,我们可以常规的方式调用对象的方法,访问对象成员
- 而反射则是在运行时打开和检查.class文件,需要用自己的描述语来调用方法,访问对象成员
- 反射相当于说明书的产品(已提供术语),而RTTI则是需要自己来总结出说明书的产品(用自己的术语)
public class ShowMethods {
public String desc = "i am description";
public String getDesc() {
return desc;
}
//args:com.zeng.typeinfo.ShowMethods,即这个类的全类名
public static void main(String[] args) throws ClassNotFoundException {
Class<?> c = Class.forName(args[0]);
Constructor<?>[] constructors = c.getConstructors();
Method[] methods = c.getMethods();
Field[] fields = c.getFields();
Pattern pattern = Pattern.compile("\\w+\\.");
for (Field field : fields) {
System.out.println(pattern.matcher(field.toString()).replaceAll(""));
}
for (Method method:methods) {
System.out.println(pattern.matcher(method.toString()).replaceAll(""));
}
for (Constructor constructor : constructors) {
System.out.println(pattern.matcher(constructor.toString()).replaceAll(""));
}
}
}
//output
public String desc
public static void main(String[]) throws ClassNotFoundException
public String getDesc()
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods()
代理
代理是基本的设计模式之一,它是你为了额外的操作或不同的操作而插入的用来代替实际对象的对象!这些操作通常涉及与实际对象的通信,故代理通常充当中间人的角色
interface Interface {
void doSomething();
void doSomethingElse(String arg);
}
class RealObject implements Interface {
@Override
public void doSomething() {
System.out.println("do something");
}
@Override
public void doSomethingElse(String arg) {
System.out.println("do something else "+arg);
}
}
class SimpleProxy implements Interface{
private Interface proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
@Override
public void doSomething() {
System.out.println("simpleProxy do something");
proxied.doSomething();
}
@Override
public void doSomethingElse(String arg) {
System.out.println("simpleProxy do something else");
proxied.doSomethingElse(arg);
}
}
public class SimpleProxyTest {
public static void consumer(Interface i) {
i.doSomething();
i.doSomethingElse("Hi");
}
public static void main(String[] args) {
Interface realObject = new RealObject();
SimpleProxy simpleProxy = new SimpleProxy(new RealObject());
consumer(realObject);
System.out.println("------------");
consumer(simpleProxy);
}
}
//output
do something
do something else Hi
------------
simpleProxy do something
do something
simpleProxy do something else
do something else Hi
动态代理
动态代理比代理思想更进一步,他可以动态地创建代理且可以动态地处理所代理的方法的调用
class SimpleDynamicProxyHandler implements InvocationHandler {
private Object proxied;
public SimpleDynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("*** proxied:"+proxied.getClass()+" \t method:"+method+" \t args:"+args);
if (args != null) {
System.out.print("args:");
for (Object o : args) {
System.out.print(o+",");
}
System.out.println();
}
return method.invoke(proxied,args);
}
}
public class SimpleDynamicProxyTest {
public static void consumer(Interface i) {
i.doSomething();
i.doSomethingElse("Hi");
}
public static void main(String[] args) {
RealObject realObject = new RealObject();
Interface proxy = (Interface) Proxy.newProxyInstance(
SimpleDynamicProxyTest.class.getClassLoader(),
new Class[]{Interface.class},
new SimpleDynamicProxyHandler(realObject)
);
consumer(realObject);
consumer(proxy);
}
}
//output
do something
do something else Hi
*** proxy:class com.zeng.typeinfo.RealObject
method:public abstract void com.zeng.typeinfo.Interface.doSomething()
args:null
do something
*** proxy:class com.zeng.typeinfo.RealObject
method:public abstract void com.zeng.typeinfo.Interface.doSomethingElse(java.lang.String) args:[Ljava.lang.Object;@2503dbd3
args:Hi,
do something else Hi
空对象
为什么需要空对象?
- 使用null的缺陷:使用null执行任何操作除了会产生NullPointerException外,不会有其他行为,故需要引入空对象,这样我就不用去检查null了,但是要检测空对象,哈哈哈,好处就是,我可以认为所有对象都是有效的(没有null)
- 当然,平时使用null也没有什么不妥
- 通过null来调用toString会抛NullPointerException
//Position.java
interface Null {
}
//不能Person直接实现Null接口,(接口是代表100%的意思)而是要通过静态内部类或者是匿名内部类去实现
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "{" +
"name=\'" + name + '\'' +
'}';
}
//为什么要静态内部类?因为这样就不用通过外部类对象来创建内部类了,.new语法,哈哈,
public static class NullPerson extends Person implements Null {
public NullPerson() {
super("None");
}
@Override
public String toString() {
return "NullPerson";
}
}
public static final Person NULL = new NullPerson();
}
public class Position {
private String titile;
private Person person;
public Position(String titile, Person person) {
this.titile = titile;
this.person=person;
if (person == null) {
this.person=Person.NULL;
}
}
public Position(String titile) {
this.titile = titile;
this.person=Person.NULL;
}
public String getTitile() {
return titile;
}
public void setTitile(String titile) {
this.titile = titile;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "{" +
"titile='" + titile + '\'' +
", person=" + person +
'}';
}
}
//Stuffs.java
public class Stuffs extends ArrayList<Position> {
public Stuffs(String... titles) {
add(titles);
}
// 只是添加单个职位
public void add(String title, Person person) {
add(new Position(title, person));
}
// 添加多个职位
public void add(String... titles) {
for (String title:titles)
add(new Position(title));
}
public Boolean positionAvailable(String title) {
for (Position position : this) {
if (position.getTitile().equals(title) && position.getPerson() == Person.NULL) {
return true;
}
}
return false;
}
public void fillPosition(String title, Person person) {
for (Position position : this) {
if (position.getTitile().equals(title)&&position.getPerson()==Person.NULL) {
position.setPerson(person);
return;
}
}
throw new RuntimeException("position " + title + " is not available");
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Position position : this) {
sb.append(position.toString()+"\n");
}
return sb.toString();
}
public static void main(String[] args) {
Stuffs stuffs = new Stuffs("Persident","CTO","Marketing Manager"
,"Product Manager","SoftWare Engineer"
,"SoftWare Engineer");
stuffs.fillPosition("Persident",new Person("zenghanmu"));
stuffs.fillPosition("CTO",new Person("zenghua"));
if (stuffs.positionAvailable("SoftWare Engineer")) {
stuffs.fillPosition("SoftWare Engineer",new Person("zenghanhuang"));
}
System.out.println(stuffs);
}
}
//output
{titile='Persident', person={name='zenghanmu'}}
{titile='CTO', person={name='zenghua'}}
{titile='Marketing Manager', person=NullPerson}
{titile='Product Manager', person=NullPerson}
{titile='SoftWare Engineer', person={name='zenghanhuang'}}
{titile='SoftWare Engineer', person=NullPerson}
接口和类型信息
我发现一个牛逼的反射。。。他能够访问私有方法,哪怕是私有内部类(甚至是匿名类)的私有方法也能访问,拿到你的方法名和方法签名,
//com.zeng.typeinfo.A.java
public interface A {
void a();
}
//com.zeng.typeinfo.hidden.HiddenC.java
class C implements A {
@Override
public void a() {
System.out.println("public C.a()");//我觉得这个提示很有用,不仅说明了访问权限,还说明说明了类和方法,
}
protected void c() {
System.out.println("protected C.c()");
}
void c1() {
System.out.println("package C.c1()");
}
public void c2() {
System.out.println("private C.c2()");
}
}
public class HiddenC {
public static A makeA() {
return new C();
}
}
//com.zeng.typeinfo.HiddenImplement.java
public class HiddenImplement {
//你也可以用for循环来完成,这样就不用传方法名了
public static void callHiddenMethod(Object o,String methodName) throws Exception{
Method method = o.getClass().getDeclaredMethod(methodName);
method.setAccessible(true);
method.invoke(o);
}
public static void main(String[] args) throws Exception {
A a = HiddenC.makeA();
System.out.println(a.getClass().getName());
callHiddenMethod(a,"a");
callHiddenMethod(a,"c");
callHiddenMethod(a,"c1");
callHiddenMethod(a,"c2");
}
}
//output
com.zeng.typeinfo.hidden.C
public C.a()
protected C.c()
package C.c1()
private C.c2()
通过反射也可以访问私有域,但是不修改final域,但是对final的修改是安全的,因为,他允许在不抛出异常的情况下接受任何修改的尝试,虽然事实上不会发生任何修改,
为什么要有反射
- 反射违反了访问权限(留了后门),但是方便了设计者日后解决问题,如果没有反射,这些问题将难以解决或者是无法解决
class WithPrivateFinalField {
private int i=1;
private String s = "am i safe?";
private final String s1 = "i am totally safe";
@Override
public String toString() {
return "WithPrivateFinalField{" +
"i=" + i +
", s='" + s + '\'' +
", s1='" + s1 + '\'' +
'}';
}
}
public class ModifiedPrivateFinalField {
public static void main(String[] args) throws Exception {
WithPrivateFinalField m = new WithPrivateFinalField();
Field i = m.getClass().getDeclaredField("i");
i.setAccessible(true);
System.out.println("getInt(i): "+i.getInt(m));
i.setInt(m,2);
System.out.println(m);
Field s = m.getClass().getDeclaredField("s");
s.setAccessible(true);
System.out.println("get(s) "+s.get(m));
s.set(m, "you are not safe");
System.out.println(m);
Field s1 = m.getClass().getDeclaredField("s");
s1.setAccessible(true);
System.out.println("get(s1): "+s1.get(m));
s1.set(m, "you are not safe");
System.out.println(m);
}
}
//output
getInt(i): 1
WithPrivateFinalField{i=2, s='am i safe?', s1='i am totally safe'}
get(s) am i safe?
WithPrivateFinalField{i=2, s='you are not safe', s1='i am totally safe'}
get(s1): you are not safe
WithPrivateFinalField{i=2, s='you are not safe', s1='i am totally safe'}
注意
- 为什么说反射没什么神奇的?哈哈
- IDEA怎么给main函数设置参数?
- program arguments那一栏,不用加参数名,不用写字符串