【java基础-2】类Class和接口Interface

本文详细介绍了Java中的核心概念和技术,包括修饰符、类与对象、抽象类与接口、反射机制、动态代理等内容,帮助读者深入理解Java编程的基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1,修饰符

1)访问权限修饰符

修饰符作用域说明
public【当前类】【当前package】【子孙类】【其它package】
protected【当前类】【当前package】【子孙类】
friendly【当前类】【当前package】不写修饰符默认为friendly
private【当前类】

好的编程习惯:类的成员属性按作用域由大到小排列,方便阅读。

2)访问控制

修饰符作用对象名称特点备注
final终态类不能被继承java中的java.lang.String就是final类
abstract抽象类不能新建对象
static变量(variable)静态变量、类变量直接通过类名就可以访问
final变量(variable)常量定义后不能修改
transient变量(variable)告诉编译器,在类对象序列化的时候,此变量不需要持久保存
volatile变量(variable)指出可能有多个线程修改此变量,要求编译器优化以保证对此变量的修改能够被正确的处理。
static方法(method)静态方法直接通过类名就可以访问
final方法(method)常方法所有子类不能覆盖该方法,但可以重载
abstract方法(method)抽象方法在抽象类中没有实现的方法,由子类强制实现
native方法(method)本地方法java调用非java代码的接口。实现由非java实现

1>final修饰

a)final类―终态类
  1. final类不能被继承;
  2. 所有方法都是final;
  3. Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大。
    但不鼓励这种优化方式,推荐让编译器和JVM来处理效率问题。

内联调用(inline call):复制方法中的代码到代替方法调用。如果方法过大会导致代码膨胀严重。
正常的方法调用:将参数压入栈,跳到方法代码处并执行。

实际使用中,我们无法预判哪个方法或类会被其它程序员继承使用,除非不能被继承,否则不添加final修饰类。(优化不考虑用final)

b)final常量:定义后不能修改

如果是引用类型,final只保证这个引用所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

c)空白final

final字段定义时没有给值,必须在构造方法中初始化。
==》这样可以让final字段在每个对象中都不一样且不被修改。

d)final参数(最终参数):

方法内部不修改该引用。
==》但是对象本身是可以修改的。

2>static variable―静态变量

被类的所有实例共享。静态变量随着类的加载而加载,所以也被称为类变量。===》该变量与类同寿,如果类不被卸载,那么B指向的B对象会常驻内存,gc不会回收这个对象所占用的堆内存,直到程序终止。

对于整型静态变量,如果没有赋初值,则默认为0。

static修饰的变量可以在定义时初始化,也可以在静态代码块中赋值(不可在构造方法中赋值)。

3>静态代码块

public class Test{
	
	static int a;//静态整型,默认为0.
	//静态语句块
	static{
	   int x=5;//局部变量,静态代码块执行完后,即销毁。
	   a=3;//全局变量
	}
	static int x,y;//x不受上述局部变量的影响,初始值为0.
}

在类被加载的时候执行且仅会被执行一次,一般用来初始化静态变量和调用静态方法。

执行顺序(优先级从高到低):静态代码块 > main方法 > 构造代码块 > 构造方法。

4>静态方法

并不需要创建类的实例就可以访问静态方法。

a)限制(main方法同理)
  1. 仅能调用其他的static 方法
  2. 只能访问static数据
  3. 它们不能以任何方式引用this 或super(因为this是与实例相关的,super 与继承有关)
b)null调用
   private static void testMethod(){
        System.out.println("testMethod");
    }
    public static void main(String[] args) {
        ((test4)null).testMethod();//输出testMethod
    }

此处是类对方法的调用,不是对象对方法的调用。
方法是static静态方法,直接使用"类.方法"即可,因为静态方法使用不依赖对象是否被创建。
null可以被强制类型转换成任意类型(不是任意类型对象),于是可以通过它来执行静态方法。

如果testMethod为非静态的方法,则会报错NullPointerException。
对于非静态方法,用"对象.方法"的方式,必须依赖对象被创建后才能使用,若将testMethod()方法前的static去掉,则会报 空指针异常 。

2,类(Class)

1)定义

java用new来新建对象生成一个引用实例,引用变量表示地址,其指向堆中的对象。

变量包括:

  1. 成员变量
    定义在类中的变量,可以不进行初始化,Java会自动进行初始化,如果是引用类型默认初始化为null,如果是基本类型例如int则会默认初始化为0 ;
  2. 局部变量
    定义在方法中的变量,必须要进行初始化,否则不能通过编译。

2)区别

1>类和数组的区别

  1. 类是方法和变量的集合体。
  2. 数组是相同类型的无序数据的集合;
    当创建一个对象数组时,实际上创建了一个存储对象引用的数组。
    java中数组属于一种原生类吗?不是,属于引用数据类型,因为声明数组需要分配堆栈空间。

2>静态类和单例模式区别

  1. 单例可以继承类(方法可以被覆写)、实现接口,而静态类不能(可以继承类,但不能继承实例成员);
  2. 单例可以被延迟初始化,静态类一般在第一次加载时初始化;
  3. 单例类可以被用于多态而无需强迫用户只假定唯一的实例。
    举个例子,你可能在开始时只写一个配置,但是以后你可能需要支持超过一个配置集,或者可能需要允许用户从外部文件中加载一个配置对象,或者编写自己的。你的代码不需要关注全局的状态,因此你的代码会更加灵活。

3>静态方法和单例模式区别

静态方法中产生的对象,会随着静态方法执行完毕而释放掉,而且执行类中的静态方法时,不会实例化静态方法所在的类。如果是用singleton, 产生的那一个唯一的实例,会一直在内存中,不会被GC清除的(原因是静态的属性变量不会被GC清除),除非整个JVM退出了。

由于DAO的初始化,会比较占系统资源的,如果用静态方法来取,会不断地初始化和释放。

3)常用方法

方法说明举例
instanceof①判断某个实例变量是否属于某种类的类型。②判断某个类是否属于某个类的子类的类型。
isAssignableFrom本地方法,表示本class所代表的类/接口是否是cls所代表的类/接口的父类/父接口。

1>isAssignableFrom

public void demoForIsAssignableFrom() {
        System.out.println(Number.class.isAssignableFrom(Integer.class)); // true
    }

4)内部类(Inner Class)

对应外部类(Outer Class),即包含内部类的类。能修饰外部类的修饰符只有四种:public、default、abstract、final.

Java 语言强烈建议禁止对内部类(包括局部类和匿名类)进行序列化。

一个".java"源文件中是否可以包括多个类,但只能有一个public的类(内部类可以是public),并且public的类名必须与文件名相一致。一个文件中可以只有非public类,如果只有一个非public类,此类可以跟文件名不同。
java将public类作为每个编译单元的数据接口,只能有一个,不然不能处理存在多个类的java文件。当一个编译单元(java文件)有多个非public类时,运行时需要对数据来源进行选择。

1>编译

类A内部的匿名内部类,编译后为:A 1. c l a s s 类 A 内部的内部类 B ,编译后: A 1.class 类A内部的内部类B,编译后:A 1.classA内部的内部类B,编译后:AB.class
接口A编译后:A.class

为什么内部类会持有外部类的引用?

内部类虽然和外部类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件,内部类通过this访问外部类的成员。因为内部类的产生依赖于外部类,持有的引用是.this。
原理:

  1. 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用;
  2. 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;
  3. 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。

2>特性

  1. 由于内部类与实例相关联,因此不能在内部类中定义任何静态成员。

3>成员内部类

public class OuterClass {
    private class InnerClass {
    }
}
实例化:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

4>局部内部类

在方法和作用域内的内部类,称为局部内部类。
匿名内部类就是一种局部内部类。

局部部内部类(包括匿名内部类)只能访问final修饰的外部变量。

public class OutterType {   
    public void function() {
        /** 局部内部类Inner*/
        class Inner {
            public void print() {
                System.out.println("局部内部类...");
            }
        }
    }
}

5>匿名内部类

使用new生成的内部类。

局部部内部类(包括匿名内部类)只能访问final修饰的外部变量。

public class TryUsingAnonymousClass {
    public void useMyInterface() {
        final Integer number = 123;
        System.out.println(number);

        MyInterface myInterface = new MyInterface() {
            @Override
            public void doSomething() {
                System.out.println(number);
            }
        };
        myInterface.doSomething();

        System.out.println(number);
    }
}

编译后的结果:
内部类MyInterface名为:TryUsingAnonymousClass$1

class TryUsingAnonymousClass$1
        implements MyInterface {
    private final TryUsingAnonymousClass this$0;
    private final Integer paramInteger;

    TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) {
        this.this$0 = this$0;
        this.paramInteger = paramInteger;
    }

    public void doSomething() {
        System.out.println(this.paramInteger);
    }
}

因为匿名内部类最终用会编译成一个单独的类,而被该类使用的变量会以构造函数参数的形式传递给该类,例如:Integer paramInteger,如果变量 不定义成final的,paramInteger在匿名内部类被可以被修改,进而造成和外部的paramInteger不一致的问题,为了避免这种不一致的情况,因为Java 规定匿名内部类只能访问final修饰的外部变量。

6>静态内部类

使用static修饰的内部类。

  1. 静态内部类可以有静态成员,而非静态内部类则不能有静态成员。
  2. 静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量。
  3. 非静态内部类的非静态成员可以访问外部类的非静态变量。
Static class(内部静态类) 与non static class(非静态内部类)的区别

内部静态类 不需要有指向外部类的引用。
非静态内部类 需要持有对外部类的引用。

非静态内部类能够访问外部类的静态和非静态成员。
静态类不能访问外部类的非静态成员,只能访问外部类的静态成员。

一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。

5)对象的创建

  1. 浅拷贝(浅复制):创建一个对象,如果是基本类型会复制一份新值;如果是引用类型会复制一份引用但是指向内存中的同一个对象。
    在性能敏感的场景中,浅拷贝的开销相对较小。
  2. 深拷贝(深复制):创建对象时,递归复制对象的所有属性。
    数据备份或者多线程编程中。

1>new

2>clone():浅拷贝

MyObject anotherObject = new MyObject();
MyObject object = anotherObject.clone();
  1. 被克隆的类要实现Cloneable 接口。
  2. 被克隆的类要重写clone()方法。
  3. 这种创建对象时,不会调用构造函数

3>反射

  1. 使用class类的newInstance()方法
    这个方法调用无参构造函数创建对象。
User user = (User)Class.forName("xx.xx.User").newInstance();
User user = User.class.newInstance();
  1. 使用Constructor类的newInstance()方法
    这个方法调用有参构造函数和私有构造函数去创建对象。
Constructor constructor = User.class.getConstructor();
User user = constructor.newInstance();

4>反序列化:深拷贝

反序列化也是基于反射实现的。通过 java.io.ObjectInputStream 对象的readObject()方法可以创建对象。

//序列化 将obj写入文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(user);

//反序列化 将文件转成对象
File file = new File("xx");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User user = (User)ois.readObject();

3,抽象类和接口

1)共同点:

  1. 抽象类和接口不能被实例化。(因为有抽象方法未实现)

2)区别

1>定义

抽象类定义了对象是什么,是对根源的抽象; ==》常用模板方法。
接口定义了对象的行为,是对动作的抽象。 ==》 面向接口编程,主要用于制定规范。

2>方法

抽象类接口备注
抽象方法有(所有方法默认缺省public abstract
普通方法没有
构造方法没有
接口里的变量都是final类型,由于没有构造函数必须定义时初始化。
静态方法通过类名加方法名进行调用
default方法(默认方法)没有default boolean isNotNull() { ... }
1. 可以同时有多个;
2. 子类不需要强制实现,可以直接调用、可以重写。
3. 一个类如果实现了多个接口,且接口default方法一致,强制要求重写该方法(菱形继承)
java8引入

3>变量

抽象类:各种修饰符都可以用;

接口:都是全局变量,必须用public static final修饰(可省略不写)。
==》这意味着在定义接口的时候,就要给这些变量赋值,且以后不能修改。

4>继承

接口可以多重继承,抽象类单继承。
子类拥有父类所有的成员与方法,但private类型的 子类无权调用。

3)接口回调

1>概念:

可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。

2>实现:

最少也是需要三个类共同来完成这个回调机制。典型的例子是android按钮点击监听事件的接口回调。
a.创建一个接口A,规定执行的操作。
b.创建一个功能类B,比如这个类可以显示一个对话框、可以滑动菜单、可以下载数据等等。
c.在功能类B里面声明接口A的对象,创建要执行的方法,在这个方法里面为声明的接口对象赋值(传参获得值)。
d.在其他的类中使用这个功能类就可以了。

public interface OnClickListener {  
   //a.创建一个接口A
    void onClick(View v);  
}  
//b.创建一个功能类B
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
   //c.声明接口A的对象
    protected OnClickListener mOnClickListener;  
      
     //c.创建要执行的方法,并为接口对象赋值
    public void setOnClickListener(OnClickListener l) {  
        if (!isClickable()) {  
            setClickable(true);  
        }  
        mOnClickListener = l;  
    }  
}

3>接口回调和向上转型的区别:

接口回调和上转型是设计模式的解耦核心。两者实质相同。区别在于:
如果a是类A的一个引用,那么,a可以指向类A的一个实例。或者说指向类A的一个子类,这是向上转型的情形。
如果a是接口A的一个引用,那么,a必须指向实现了接口A的一个类的实例。这是接口回调的情形。

5,类的生命周期

  1. 加载(链接、初始化)
    链接过程:验证、准备、解析;
  2. 使用
  3. 卸载

1)类的加载

java中类加载主要思想是延迟加载,需要时才加载。
加载过程:

  1. 加载:将类的.class文件中的二进制数据读入内存,放在方法区。创建一个java.lang.Class对象(堆中)用来封装类在方法区的数据结构。
  2. 链接-验证:
    验证类的正确性。

动态连接
在类运行期间才能确定某些目标方法的直接引用。

  1. 链接-准备:
    为类的静态变量分配内存,为final变量初始化为默认值。
  2. 链接-解析:
    将类的符号引用转为直接引用:类或接口、字段、类方法、接口方法、方法类型、方法句柄、访问控制符。

静态解析
在类加载阶段或第一次使用时转化为直接引用。
成立的前提是:方法在程序真正执行前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。 被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段确定唯一的调用版本。
主要有:①静态方法;②私有方法;③实例构造器;④父类方法四类。这些方法可以称为非虚方法(还包括 final方法),与之相反,其他方法就称为虚方法(final 方法除外)。 这里要特别说明下 final 方法,虽然调用 final 方法使用的是 invokevirtual 指令,但是由于它无法覆盖,没有其他版本,所以也无需对方发接收者进行多态选择。Java 语言规范中明确说明了 final 方法是一种非虚方法。

  1. 初始化
    静态代码块加载。
    懒加载,使用的时候才初始化、执行类的构造器。

1>场景

  1. JVM启动,自动加载一些基础类。
    如java.lang.Object类和java.lang.Object类等。
  2. 使用类的静态变量或静态方法时,如果未被加载则加载。
  3. 创建类的实例时,如果未被加载则加载。
  4. 使用反射访问类时,如果未被加载则加载。

2)类加载器(java.lang.ClassLoader)

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由Java 应用开发人员编写的。
这里写图片描述

①系统类加载器

i>引导类加载器(bootstrap class loader)

它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。主要负责jdk_home/lib目录下的核心api 或 -Xbootclasspath 选项指定的jar包装入工作(其中的jdk_home是指配置jdk环境变量是java_home的配置路径,一般是jdk/jre所在目录)。

ii>扩展类加载器(extensions class loader)

它用来加载 Java 的扩展库。Java虚拟机的实现会提供一个扩展库目录,扩展类加载器在此目录里面查找并加载 Java 类,主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。

iii>系统类加载器(system class loader)

它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。主要负责CLASSPATH/-Djava.class.path所指的目录下的类与jar包装入工作.

②自定义类加载器

除了系统提供的类加载器以外,开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,从而进行动态加载class文件,以满足一些特殊的需求,这体现java动态实时类装入特性。

③父类加载器

除了引导类加载器之外,所有的类加载器都有一个父类加载器,通过getParent()方法可以得到。
对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;
对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。
因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。下图中给出了一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类加载器。

3)类的卸载

场景:

  1. 该类所有实例都被GC回收。
  2. 该类的ClassLoader被GC回收。

6,Object类

1)概念

Object类是层次结构的根,所有类都继承这个类。
Object类是java语言中唯一没有父类的类。
在不明确给出超类的情况下,Java会自动把Object作为要定义类的超类。

对象的传输:使用ObjectOutputStream类完成对象存储,使用ObjectInputStream类完成对象读取。

2)方法

①Object()

默认构造方法

②clone()

为对象A创建并返回一个副本对象B。
A与B是两个独立的对象,但B的初始值是由A对象确定的,B的创建只分配内存但不调用构造方法。
只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

③equals(Object obj)

指示某个其他对象是否与此对象“相等”。在Object中与==是一样的,子类一般需要重写该方法。

④finalize()

当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

⑤getClass()

final方法,返回一个对象的运行时类。
举例:求以下代码输出结果。

package test;
import java.util.Date; 
public class SuperTest extends Date{ 
    private static final long serialVersionUID = 1L; 
    private void test(){ 
       System.out.println(super.getClass().getName()); 
    } 
      
    public static void main(String[]args){ 
       new SuperTest().test(); 
    } 
}

结果为:test.SuperTest。
要返回Date类的名字需要写super.getClass().getSuperclass()

⑥hashCode()

返回该对象的哈希码值。

该方法用于哈希查找,重写了equals方法必须重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

  1. 如果两个对象相同,那么它们的hashCode值一定要相同;
  2. 如果两个对象的hashCode相同,它们并不一定相同
  3. hashCode()的默认行为是对堆上的对象产生独特值。如果没用重写hashCode,那么该class的两个对象无论如何都不会相等(即使他们拥有同样的数据)

哈希查找为什么快?
原理:一个对象要插入HashSet,直接去hashCode值来判断对象加入的位置,如果该位置无值,直接插入;如果该位置有值,equals()判断是否同一个对象,如果是同一个对象不让其插入;如果不是同一个对象,重新散列到其它位置。

⑦notify()

i>概念

唤醒在此对象监视器上等待的单个线程。

ii>关于锁

a)唤醒一个线程去等待获取锁
notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁,但不惊动其他同样在等待被该对象notify的线程们。
如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait 状态,直到这个对象发出一个 notify 或 notify。
b)只能在同步方法或同步块中调用
如果调用 notify()时没有持有适当的锁,抛出 IllegalMonitorStateException。

⑧notifyAll()

i>概念

唤醒在此对象监视器上等待的所有线程。

ii>关于锁

该方法与 notify ()方法的工作方式基本相同。
a)唤醒所有线程去竞争锁
如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
b)只能在同步方法或同步块中调用

⑨wait

i>概念

使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。

--wait()
直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
--wait(long timeout)
直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
--wait(long timeout, int nanos)
直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
ii>关于锁

该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。
a)调用时释放锁
b)只能在同步方法或同步块中调用
在调用 wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait()方法。
如果调用 wait()时,没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException 的一个子类,因此,不需要 try-catch 结构。

iii>wait和notify为什么必须再synchronized 中执行?

通过synchronized 来实现共享变量的互斥与资源竞争,从而达到一个线程执行完唤醒另一个的目的。

⑩toString()

返回该对象的字符串表示。转换成字符串,一般子类都有重写,否则打印句柄。
这个字符串的格式是固定的:类名@hashcode。这个hashcode是一串数字(16进制),在Java中叫句柄/(虚拟)地址。但是句柄不是内存地址

7,JAVA反射机制

1)概念

java反射机制是程序在运行时能够获取自身信息。在java中,只要给定类的全限定名,就能通过反射获取所有属性和方法。

2)功能

使用反射的主要作用是方便程序的扩展,容易在运行期间干很多事情。

1>在运行时判断任意一个对象所属的类;

//方法一:(常用)
try {
      Class clz = Class.forName("com.entity.Book");
  } catch (ClassNotFoundException e) {
      e.printStackTrace();
  }
//方法二:
Class clz = Book.class;
//方法三:
Book book = new Book();
Class clz = book.getClass();    
  

2>在运行时构造任意一个类的对象;

实例化对象:newInstance(Object initargs)

第一种方式

Class clz = Book.class;
Object obj =  clz.newInstance();        //将创建一个无参book对象

第二种方式

Class clz = Book.class;
Constructor constructor = clz.getDeclaredConstructor();   //获得无参构造 
Object obj = constructor.newInstance();    //实例化book对象

3>在运行时判断任意一个类所具有的成员变量和方法;

方法说明调用对象举例
int getModifiers()获取修饰符, 返回值为整数,多个修饰符则返回和Class,Method、Field
getName()获取全名 如:com.bean.BookClass,Method,Constructor
getSimpleName()获取类名 如:BookClass,Method,Constructor
Package aPackage = clz.getPackage();获取包Class
Class[] interfaces = clz.getInterfaces();返回类型Class
Class superclass = clz.getSuperclass();获取父类/超类Class
Method[] methods = clz.getMethods();获取所有公开的方法(包括系统自带)Class
Method m = clz.getMethod("setA_", String.class)获取单个的公开方法Class
ethod[] methods1 = clz.getDeclaredMethods();获取所有的方法(不会获取系统自带的方法)Class
Field[] getFields()获取所有的公开字段Class
Field getField(String name)参数可以指定字段,获取单个public字段Class
Field[] getDeclaredFields()获取所有的字段Class
Field getDeclaredField(String name)获取单个字段 参数可以指定字段Class
setAccessible设置访问属性 true表示可访问,这样就可以访问私有方法了Class,Field
method.invoke(Object obj,Object… args)使用方法,obj:如果是实例方法,则放个该方法的类对象给它;静态方法,写null。args:方法的参数值,没有写null,或不写都行Methodmethod.invoke(clz.newInstance(),null);

getModifiers()返回值整数定义:

0–默认不写
1public
2private
4protected
8static
16final
32synchronized
64volatile
128transient
256native
512interface
1024abstract

4>在运行时调用任意一个对象的方法;

////获取所有的构造函数
Constructor[] constructors = class.getDeclaredConstructors();
//获取一个 参数类型为String,两个参数的 构造函数
cConstructor constructor = clz.getDeclaredConstructor(String.class, String.class);;   
class.getConstructors();         //获取所有公开的构造函数
class.getConstructor(参数类型);      //获取单个公开的构造函数

5>生成动态代理。

3)缺点

  1. 代码可读性及可维护性低;
  2. 使用反射时,参数需要保证(boxing)成Object[]类型,真正执行的时候又要拆包(unboxing)成真正的类型。这些动作耗时又产生了很多对象,从而导致GC、卡顿。
  3. 反射调用方法时会从方法数组遍历查找,并且检查可见性,参数也要做额外的检查,这些都会消耗性能。
  4. 破坏了代码的封装性。

4)原理

普通的java对象是通过new关键字把对应类的字节码文件加载到内存,然后创建该对象的。
反射是通过一个名为Class的特殊类,用Class.forName("className");得到类的字节码对象,然后用newInstance()方法在虚拟机内部构造这个对象(针对无参构造函数)。
也就是说反射机制让我们可以先拿到java类对应的字节码对象,然后动态的进行任何可能的操作。

4)应用

方法说明举例备注
Class.forName()通过类名获取对象MyObject object = (MyObject) Class.forName("subin.rnd.MyObject").newInstance();
object.getClasss()获取类的定义信息Class clazz = object.getClasss();
class.getDeclaredFields()获取类的数据成员Field[] fields = class.getDeclaredFields()不能获取到父类的属性
calzz.getFields()获取类的所有公共属性
class.getSuperclass().getDeclaredFields()获取父类的数据成员
field.getName()获取Field的字段名
field.getType()获取Field的字段类型
field.get(field)获取Field对应的字段的值
class.getMethods()拿到函数成员Method[] methods = c.getMethods();
method.getName()获取函数名

1>为了降低系统各部分的依赖和避免魔法数,将switch语句用多态替换掉。

点击查看demo:通过多态和发射机制替换switch

//将请求参数封装成不同的对象,共同继承同一个基类AccessDatabase
Class messageObeject = Class.forName(requestStr);
AccessDatabase accessDatabaseObject = (AccessDatabase) messageObject.newInstance();
//每次请求执行基类的公共方法。
accessDatabaseObject.AccessXlDatabase();

2>利用反射实现简单工厂

3>控制反转(IoC)与依赖注入(DI)

4>通过反射批量修改属性值

public static Object  FieldReflection(Object oldObj) throws Exception{
    // 获取对象所有的实例域
    Field[] fields = oldObj.getClass().getDeclaredFields();
    // 获得访问私有实例域的权限
    AccessibleObject.setAccessible(fields, true);
    for (Field field : fields) {
      Class<?> type = field.getType();
      if ("String".equals(type.getSimpleName())) {
        Object obj = field.get(oldObj);
        if (obj != null) {
            String str = obj.toString();
            field.set(oldObj,WebAppAESUtil.encrypt(str));
        }
      }


      if ("CompanyDTO".equals(type.getSimpleName())){
        CompanyDTO companyDTO = (CompanyDTO) field.get(oldObj);
        if (companyDTO != null){
          companyDTO = (CompanyDTO) FieldReflection(companyDTO);
          field.set(oldObj,companyDTO);
        }
      }

      if ("List".equals(type.getSimpleName())){
        List<UserInfoDTO> userInfoDTOList = (List<UserInfoDTO>) field.get(oldObj);
        if (userInfoDTOList != null && userInfoDTOList.size()>0){
          List<UserInfoDTO> userInfoList = new ArrayList<>();
          for (UserInfoDTO item: userInfoDTOList ){
            if (item != null){
              item = (UserInfoDTO) FieldReflection(item);
              userInfoList.add(item);
            }
          }
          field.set(oldObj,userInfoList);
        }
      }

     
    }
    return oldObj;
  }

5)java内省(Introspector)

内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法。Java JDK中提供了一套 API 用来访问某个属性的getter/setter 方法,这就是内省。内省机制是通过反射来实现的,BeanInfo用来暴露一个bean的属性、方法和事件,以后我们就可以操纵该JavaBean的属性。

BeanInfo相关的类有:Introspector、PropertyDescriptor 、MethodDescriptor。

1>PropertyDescriptor类(不推荐)

PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。主要方法:

方法说明
getPropertyType()获得属性的Class对象;
getReadMethod()获得用于读取属性值的方法;getWriteMethod(),获得用于写入属性值的方法;
hashCode()获取对象的哈希值;
setReadMethod(Method readMethod)设置用于读取属性值的方法;
setWriteMethod(Method writeMethod)设置用于写入属性值的方法。

set或get某个类的某个属性字段:

package com.peidasoft.Introspector;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
 
public class BeanInfoUtil { 
  
  public static void setProperty(UserInfo userInfo,String userName)throws Exception{
    PropertyDescriptor propDesc=new PropertyDescriptor(userName,UserInfo.class);
    Method methodSetUserName=propDesc.getWriteMethod();
    methodSetUserName.invoke(userInfo, "wong");
    System.out.println("set userName:"+userInfo.getUserName());
  }
  
  public static void getProperty(UserInfo userInfo,String userName)throws Exception{
    PropertyDescriptor proDescriptor =new PropertyDescriptor(userName,UserInfo.class);
    Method methodGetUserName=proDescriptor.getReadMethod();
    Object objUserName=methodGetUserName.invoke(userInfo);
    System.out.println("get userName:"+objUserName.toString());
  }
} 

2>BeanUtils API(推荐)

Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包。

package org.apache.commons.beanutils;
方法说明备注
cloneBean(obj)克隆对象
copyProperties(myBean1, myBean)copy相同属性给另一个对象可以用于对象之间的传值,比如po给vo值
copyProperty(myBean1, "filed_name", “value”)将某个值赋值给另一个对象的某个字段
Map<String, String> describe(Object bean)返回对象的field-value 键值对
String getProperty(Object bean, String name)返回对象的某个属性的值
setProperty(myBean, "filed_name", "value")设置某个对象的某个属性的值为…
populate(myBean1, map)将map的键值对数据装入对象中。key为属性名

8,动态代理

1)静态代理

静态代理是编译时确定的,动态代理是运行时确定的。
静态代理的实现需要大量的代码,如果要代理的类中方法比较多或者要代理多个对象时复杂度也会大大增加。通过反射进行动态代理能大大减轻这些烦恼。

1>举例

模拟保存动作。
定义一个保存动作的接口:IUserDao.java,然后目标对象实现这个接口的方法UserDao.java,此时如果使用静态代理方式,就需要在代理对象(UserDaoProxy.java)中也实现IUserDao接口.调用的时候通过调用代理对象的方法来调用目标对象。
需要注意的是,代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。

public interface IUserDao {

    void save();
}
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已经保存数据!----");
    }
}
public class UserDaoProxy implements IUserDao{
    //接收保存目标对象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("开始事务...");
        target.save();//执行目标对象的方法
        System.out.println("提交事务...");
    }
}
  public static void main(String[] args) {
        //目标对象
        UserDao target = new UserDao();

        //代理对象,把目标对象传给代理对象,建立代理关系
        UserDaoProxy proxy = new UserDaoProxy(target);

        proxy.save();//执行的是代理的方法
    }

通过上面的代理代码,我们可以看出代理模式的特点,代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。但是也有缺点,每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任

2)JDK代理

1>场景

目标对象实现了一个或多个接口时使用。

2>实现方式

java.lang.reflect包中的Proxy类和InvocationHandler接口提供了生成动态代理的能力。

JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

--ClassLoader loader
指定当前目标对象使用类加载器,获取加载器的方法是固定的
--Class<?>[] interfaces
目标对象实现的接口的类型,使用泛型方式确认类型
--InvocationHandler h
事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

3>Demo

接口类IUserDao.java以及接口实现类,目标对象UserDao是一样的,没有做修改.在这个基础上,增加一个代理工厂类(ProxyFactory.java),将代理类写在这个地方,然后在测试类(需要使用到代理的代码)中先建立目标对象和代理对象的联系,然后用代理对象的中同名方法。

/**
 * 创建动态代理对象
 * 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory{

    //维护一个目标对象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }

   //给目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("开始事务2");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事务2");
                        return returnValue;
                    }
                }
        );
    }

}
public static void main(String[] args) {
        // 目标对象
        IUserDao target = new UserDao();
        // 【原始的类型 class cn.itcast.b_dynamic.UserDao】
        System.out.println(target.getClass());

        // 给目标对象,创建代理对象
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        // class $Proxy0   内存中动态生成的代理对象
        System.out.println(proxy.getClass());

        // 执行方法   【代理对象】
        proxy.save();
    }

3)Cglib代理(Code Generation Library)

Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

1>场景

目标对象没有实现接口。

2>实现方式

由第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

  1. 需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可;
  2. 引入功能包后,就可以在内存中动态构建子类;
  3. 代理的类不能为final,否则报错;
  4. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

3>demo

/**
 * 目标对象,没有实现任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已经保存数据!----");
    }
}
/**
 * Cglib子类代理工厂
 * 对UserDao在内存中动态构建一个子类对象
 */
public class ProxyFactory implements MethodInterceptor{
    //维护目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始事务...");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("提交事务...");
        return returnValue;
    }
}
/**
 * 测试类
 */
public class App {

    @Test
    public void test(){
        //目标对象
        UserDao target = new UserDao();

        //代理对象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //执行代理对象的方法
        proxy.save();
    }
}

4)代理的场景

  1. AOP
  2. 拦截器
  3. 过滤器

9,好的编码习惯——POJO定义

在这里插入图片描述

1)PO(persistent object,持久对象)

一般以entity、bean命名。
1 .有时也被称为Data对象,对应数据库中的entity,可以简单认为一个PO对应数据库中的一条记录。
PO是数据库表中的记录在java对象中的显示状态,可以把一条记录作为一个对象处理,可以方便的转为其它对象。
2 .在hibernate持久化框架中与insert/delet操作密切相关。
3 .PO中不应该包含任何对数据库的操作。

2)BO(business object,业务对象)

一般以service、manager、business等命名。

业务对象主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。
比如一个简历,有教育经历、工作经历、社会关系等等。我们可以把教育经历对应一个PO,工作经历对应一个PO,社会关系对应一个PO。
建立一个对应简历的BO对象处理简历,每个BO包含这些PO。
这样处理业务逻辑时,我们就可以针对BO去处理。
封装业务逻辑为一个对象(可以包括多个PO,通常需要将BO转化成PO,才能进行数据的持久化,反之,从DB中得到的PO,需要转化成BO才能在业务层使用)。

关于BO主要有三种概念
1 、只包含业务对象的属性;
2 、只包含业务方法;
3 、两者都包含。

在实际使用中,认为哪一种概念正确并不重要,关键是实际应用中适合自己项目的需要。

3)VO(value object/view object,值对象 / 表现层对象)

1 .主要对应页面显示(web页面/swt、swing界面)的数据对象。
2 .可以和表对应,也可以不,这根据业务的需要。

VO、PO区别

1.VO是用new关键字创建,由GC回收的。
PO则是向数据库中添加新数据时创建,删除数据库中数据时削除的。并且它只能存活在一个数据库连接中,断开连接即被销毁。
2.VO是值对象,精确点讲它是业务对象,是存活在业务层的,是业务逻辑使用的,它存活的目的就是为数据提供一个生存的地方。
PO则是有状态的,每个属性代表其当前的状态。它是物理数据的对象表示。使用它,可以使我们的程序与物理数据解耦,并且可以简化对象数据与物理数据之间的转换。
3.VO的属性是根据当前业务的不同而不同的,也就是说,它的每一个属性都一一对应当前业务逻辑所需要的数据的名称。
PO的属性是跟数据库表的字段一一对应的。
PO对象需要实现序列化接口。

4)DTO/TO(Data Transfer Object,数据传输对象)

1 .用在需要跨进程或远程传输时,它不应该包含业务逻辑。
2 .比如一张表有100个字段,那么对应的PO就有100个属性(大多数情况下,DTO内的数据来自多个表)。但view层只需显示10个字段,没有必要把整个PO对象传递到client,这时我们就可以用只有这10个属性的DTO来传输数据到client,这样也不会暴露server端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO。

6)PO状态

在Hibernate中,最核心的概念就是对PO的状态管理。一个PO有三种状态:
  1.未被持久化的VO,此时就是一个内存对象VO,由JVM管理生命周期。
  2.已被持久化的PO,并且在Session生命周期内,此时映射数据库连接,由数据库管理生命周期。
  3.曾被持久化过,但现在和Session已经托管(detached)了,以VO的身份在运行。它还可以进入另一个Session,继续PO状态管理。

7)DAO(data access object,数据访问对象)

1 .主要用来封装对DB的访问(CRUD操作)。
2 .通过接收Business层的数据,把POJO持久化为PO。

8)DO(Domain Object,领域对象)

从现实世界中抽象出来的有形或无形的业务实体。

9)O/R Mapping ( Object Relational Mapping,对象关系映射)

将对象与关系数据库绑定,用对象来表示关系数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值