1. 面向对象基本概念和特征
(1)要点:
所谓的面向对象其实一种编程的思维,即要用我们在日常生活中的思维去解决程序的问题。生活中我们主要面对的就是一些具体存在的物体,以及他们之间的相互关系。而面向对象编程就是将生活中的这些客观存在的事物以及他们之间的关系用程序来实现。
(2)核心概念:
类:同类事物所具有的共性(共同的特征和行为)进行抽象,泛化。例如说到桌子他就是一个类。我们会联想到桌子的特性。
对象:类的实例,它是具体的。例如:某一张具体的桌子
(3)特征:
1)继承:在现有的类的基础上增加一些新的特征或行为产生一个新的类就是继承。被继承的类叫做基类或者父类,新生成的类叫子类或者派生类。继承可以实现代码重用。
2)封装:通常把类的属性做成私有字段,然后用get/set访问器实现属性的读写,隐藏了内部的实现逻辑,这个过程就是封装。编写一个类就是对数据和数据操作的封装。封装的好处就是代码安全,对外界提供最简单的编程接口。
3)多态:多态通常指同一个行为不同的对象有不同的响应方式。一般情况下是指一个父类引用指向了子类的对象,不同的子类对于同一个方法有不同的实现形。多态分为编译时多态—方法重载,和运行时多态---方法重写。
4)通常我们回答这三个特性就可以了,如果面试官还问到第四种特性,可以加上抽象这个特征。抽象:抽取类的过程就是一个抽象的过程,他关注的是共性(特征和行为),不关心具体的实现。
2 . Java中的4种访问修饰符(public,private,protected, 以及不写)的区别
修饰符 | 当前类 | 同包 | 子类 | 其它包 |
---|---|---|---|---|
public(公有) | √ | √ | √ | √ |
protected(保护) | √ | √ | √ | × |
friendly(不写) | √ | √ | × | × |
private(私有) | √ | × | × | × |
3 . Java中原始数据类型(primitive type)和引用类型区别
(1)基本数据类型:在申请内存时,只分配一块内存区域,用于存储数据本身,存储在栈内存中。
类型 | byte | short | int | long | float | double | char | boolean |
---|---|---|---|---|---|---|---|---|
长度(字节) | 1 | 2 | 4 | 8 | 4 | 8 | 2 | 不定 |
(2)引用类型:数组,字符串,类等都属于引用类型。引用类型实际上需要两块内存,一块内存用于存储地址(即引用)分配在栈内存,另一块分配在堆内存中,用于存储实际数据。
如:Student stu=null;这条语句的意义就是在栈内存中分配了一块内存,但是没有实例化,即没有分配用于存储数据的内存,如果这时访问stu则会报空引用异常。stu=new Student(1,”accp“),则会在内存中开辟存放数据的内存区域。
4. java中==和equals区别
(1)==是个运算符,在java中如果比较两个值类型变量,则比较两个变量的内容是否相等。如果比较引用类型变量,则比较两个引用指向的内存地址是否相等。
(2)equals是java顶层父类Object的方法,默认比较的是两个引用的地址是否相等,每个类都默认继承自Object,所以使用equals默认比较地址,但是开发人员可以在子类中重写该方法。例如:两个学生如果所有的属性(学号,姓名…)都相等,可以重写Student类的equals方法,让两个对象只要属性的值相等就判断两个对象equals为true。Java中的String类就是重写过equals方法,因此我们经常使用equals比较两个字符串。
总结:==号是编译器的行为,而equals是程序员的行为。
5. String,StringBuilder和StringBuffer区别
(1)String是不可变的,即只读的,意味着String 引用的字符串内容是不能被改变的。
String str=”hello,accp”; str=”wecome”;
str是一个引用,它指向字符串对象”hello,accp”,当执行str=”welcome”时,它已经指向另外一个字符串对象了。
(2)StringBuffer是可变的字符串,可以直接修改。所有的方法都有同步关键字synchroized修饰,因此在多线程环境下是数据安全的。但是效率低。适合于多线程环境下操作字符串缓冲区的大量数据。
(3)StringBuilder是可变字符串,在JDK1.5之后引入的,方法和StringBuffer相同,适合于单线程环境,运行效率更高。没有同步关键字,因此在多线程环境中无法确保数据安全。
6. Java中的包装类
在java中主要的数据类型分为基本数据类型和引用类型。基本数据类型只存储数据,操作效率高。但有时候需要涉及到将基本类型转换为字符串,或者默写情况下不能提供基本数据类型的初值,只能是null。
Java为基本数据类型提供了相应的包装类,将基本类型包装为引用类型,并且提供了一系列的方法实现数据类型转换以及空值的处理。
原生类型 | byte | short | int | long | float | double | char | boolean |
---|---|---|---|---|---|---|---|---|
包装类 | Byte | Short | Integer | Long | Float | Double | Char | Boolean |
iint a=3;
Integer b=a; //自动装箱, 将基本类型转换为引用类型
int c=b; //自动拆箱,将引用类型转换为基本类型,相当于int c=b.intValue();
7. JDK、JRE、JVM和JIT的概念和关系
JDK(Java Development Kit) 是Java开发的核心,即开发工具包。包含Java运行环境,Java编译工具以及基础类库。
JRE(Java Runtime Environment)是Java程序的运行环境,主要包括Java基础类库和JVM的标准实现。
JVM(Java Virtual Machine) Java虚拟机,主要实现将class文件转换为最终机器对应的二进制。也是java实现跨平台的核心,不同的操作系统有不同的JVM实现。
JIT(Just In Time Compilation)代表即时编译,当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码。例如:主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能
总结:JDK > JRE > JVM > JIT
8 . Error和Exception区别
在Java中Error和Exception都是Throwable的子类
Error 类一般是指与虚拟机相关的问题,如系统崩溃,内存空间不足,方法调用堆栈溢出等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Exception 类分为运行时异常(RuntimeException)和检查异常(Checked Exception)。
运行时异常例如ArithmaticException(算数异常),ArgumentException(参数异常)等,编译能通过但是程序不会处理运行时异常,一旦运行出现这类异常,程序会终止。而检查异常,要么用 try…catch 捕获,要么在方法的声明处用 throws 语句抛出,由父类处理,否则编译通不过。
9. throw 和throws 的区别
(1)throw
throw 语句用在方法体内,用于抛出异常,如:”throw new Exception()”。
(2)throws
1)throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
2)throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
3) throws 表示出现异常的一种可能性,并不一定会发生这种异常
如:”void methodA() throws ServiceException { … }”
10. final、finally、finalize 的区别
(1)final关键字
声明属性表示常量,修饰方法表示该方法不能被重写,修饰类表示该是个最终的类不能被继承。
(2)finally关键字
异常处理语句结构的一部分,表示一定会执行的代码,通常和try...catch..finally一起使用。
(3)finalize()方法
它是Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以重写。如果在自定义的类中引用了文件或者数据库等资源,希望垃圾收集线程自动关闭文件或者数据库等资源。可以重写该方法。它更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将被回收(“死亡")。但是需要注意的是,我们主动调用该方法并不会导致该对象“死亡”,这是一个被动的方法,不需要我们调用。
11. 重载和重写的区别
(1)重载是指在同一个类中,一组方法名相同,参数(个数或者类型)不同的方法。跟返回值,修饰符等没有关系。
注意:重载的一组方法一般有相同的功能,只是参数不同时才需要重载。例如系统提供的System.out.println();
(2)重写是指在父子类中,子类重新定义父类的方法。重写时子类的方法名,参数,返回类型必须和父类的保持一致。访问权限不能比父类的方法低。
注意:构造方法不能被重写,声明为 final 的方法不能被重写。
12. 描述java中的多态。
多态就是指同一种行为,不同的对象有不同的表现形式。在程序中主要是指父类的引用指向子类的对象,当调用方法时是根据指向的对象类型调用相应的方法。多态根据执行的时刻分为编译时的多态性和运行时多态性。
重载就是在编译的时刻根据参数来决定调用的方法,即编译时的多态性。
重写是在运行时根据指向的对象类型调用具体的子类方法,即运行时的多态性。
13. 接口和抽象类的区别
抽象类使用abstract修饰;,它不能实例化,即不能使用new关键字来实例化对象;抽象类可以含有抽象方法,也可以不包含抽象方法,抽象类中可以有具体的方法;如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类;
接口使用interface修饰;接口不能被实例化;一个类只能继承一个类,但是可以实现多个接口;接口中方法均为抽象方法;接口中不能包含实例域或静态方法。
注意:抽象类主要用来做父类,提供公共的属性和方法,便与重用。而接口是为了提供一种规范,目的是为了降低耦合,接口比抽象类抽象程度更高。
14. 什么是序列化,如何实现?
序列化就是把对象转换为字节序列保存到流中的过程。内存中的对象如果需要完整的将对象状态保存在介质中(磁盘,网络)等,可以使用序列化技术实现。例如:内存中有学生对象Student,它具有多个属性(姓名,性别,年龄等),如果需要将当前Student的对象状态保存到磁盘,按照传统的文件操作的方式非常复杂。需要将一个个属性读出来写到磁盘,反之,还原的时候再解析字符串进行处理。整个过程是比较麻烦的。如果序列化就很简单。
实现方式:
(1)被序列化的类需要实现可序列化接口(Serializable), 该接口是个空接口,用来标识该类可以被序列化。
class Student implements Serializable {…}
(2)使用流进行对象写入
ObjectOutputStream objectStream=new ObjectOutputStream(
new FileOutputStream(new File(“d:\student.txt”));
objectStream.writeObject(objectStream);
objectStream.close();
(3) 反序列化就是将字节序列恢复为对象的过程。
ObjectInputStream objectStream=new ObjectInputStream(
new FileInputStream(new File(“d:\student.txt”)));
Studentstudent=(Student)objectStream.readObject();
用途:将对象保存到磁盘文件,或者在网络中传输对象
15. Java中集合框架
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些接口或实现类。
Collection接口是List、Set等集合高度抽象出来的接口,它包含了这些集合的基本操作,主要又分为两大部分:List和Set。
Set和List接口是Collection接口派生的两个子接口,Queue是Java提供的队列实现,类似于List。
Map是一个映射接口,其中的每个元素都是一个key-value键值对,抽象类AbstractMap通过适配器模式实现了Map接口中的大部分函数,TreeMap、HashMap、WeakHashMap等实现类都通过继承AbstractMap来实现。不常用的HashTable直接实现了Map接口。Set、List和Map三种集合,最常用的实现类分别是HashSet、ArrayList和HashMap三个实现类.
16. List和 Map、Set 的区别
List集合代表一个有序、可重复集合,集合中每个元素都有其对应的顺序索引。List集合默认按照元素的添加顺序设置元素的索引,可以通过索引(类似数组的下标)来访问指定位置的集合元素。
实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
Set接口通常表示一个集合,其中的元素不允许重复,常用实现类有HashSet、LinkedHashSet和TreeSet。
Map接口采用键值对Map<K,V>的存储方式,保存具有映射关系的数据,因此,Map集合里保存两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value,key和value可以是任意引用类型的数据。key值不允许重复,可以为null。如果添加key-value对时Map中已经有重复的key,则新添加的value会覆盖该key原来对应的value。常用实现类有HashMap、LinkedHashMap、TreeMap等。
17. HashMap 和 HashTable 有什么区别?
HashMap与Hashtable是Map接口的两个典型实现。都是采用了hash表数据结构来实现,可以快速定位元素。存储时,根据key的哈希值决定元素存放的位置。
HashMap继承自AbstractMap类。实现了Map接口。
Hashtable继承自Dictionary类,Dictionary类是一个已经被废弃的类。父类已经不推荐使用,因此它的子类Hashtable也很少用了。
Hashtable是JDK1.0引入的,是线程安全的,适用于多线程环境
HashMap是JDK1.2引入的,非线程安全的,适用于单线程环境。不保证存取的顺序性的,也就是说遍历HashMap的时候,得到的元素的顺序与添加元素的顺序是不同的。
HashMap是允许key和value为null值的,只能有一个key为null。它用containsValue和containsKey方法判断是否包含对应键值对;
HashTable键值对都不能为空,否则包空指针异常。
18. Java 中 ArrayList 和 LinkedList 以及Vector的区别?
ArrayList是一个动态数组Object[],是List类的典型实现。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。
ArrayList擅长于随机访问。同时ArrayList是非同步的。
LinkedList是List接口的另一个实现,除了可以根据索引访问集合元素外,LinkedList还实现了Deque接口,可以当作双端队列来使用,也就是说,既可以当作“栈”使用,又可以当作队列使用。
LinkedList的实现机制与ArrayList的实现机制完全不同,ArrayLiat内部以数组的形式保存集合的元素,所以随机访问集合元素有较好的性能;LinkedList内部以链表的形式保存集合中的元素,所以随机访问集合中的元素性能较差,但在插入删除元素时有较好的性能。
Vector与ArrayList相似,但是Vector是线程同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。
19. HashSet是如何保证数据不可重复的?
HashSet的底层其实就是HashMap,只是HashSet是实现了Set接口并且把数据作为Key值,而value值一直使用一个相同的虚值来保存.由于HashMap的Key值本身就不允许重复,并且在HashMap中如果Key/value相同时,会用新的Value覆盖掉旧的Value,然后返回旧的Value,内部执行终会返回一个false,导致插入失败,这样就保证了数据的不可重复性.
20. Collection和Collections区别
Collection是集合的顶层结构,提供了集合的公共操作的规范
Collections是一个静态的工具类,提供了集合常见的操作实现,比如排序,搜索,反序等,简化开发中常见的功能。
void reverse(List list); //反转
void shuffle(List list); //随机排序
void sort(List list); //按自然排序的升序排序
void sort(List list, Comparator c); //定制排序
void swap(List list, int i , int j); //交换两个索引位置的元素
int binarySearch(List list, Object key); //对List进行二分查找
int max(Collection coll); //根据元素的自然顺序,返回最大的元素。
void fill(List list, Object obj); //用指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o); //统计元素出现次数
int indexOfSubList(List list, List target); //统计target在list中第一次出现的索引
boolean replaceAll(List list, Object oldVal, Object newVal); //用新元素替换旧元素
21. 线程和进程区别
进程就是运行中的程序,例如:你打开了QQ程序,则操作系统会分配资源,创建进程,拥有独立的内存空间,这就是进程。
线程是进程的一部分,一个进程默认就是一个主线程。线程是CPU调度的最小单位,线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。
22. 实现线程的方式
有两种创建线程的方法:(1)实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;(2)直接继承Thread类,重写run方法。
使用接口的方式更加灵活,因为java支持实现多个接口,还可以继承其他类。而继承Thread类就意味着不能扩展其他类了。因此使用接口方式更加灵活。
23. Thread 类中的start() 和 run() 方法有什么区别?
start()方法被用来启动新创建的线程,使被创建的线程状态变为可运行状态。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动。只有start()方法才会启动新线程。如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,直接运行run方法。
因此,为了在新的线程中执行我们的代码,必须使用Thread.start()方法。
24. 线程的生命周期
新建:new Thread() ,一个新产生的线程从新状态开始了它的生命周期。它保持这个状态直到程序 start这个线程。
就绪:当一个新状态的线程被 start 以后,线程就变成就绪状态。这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行。
运行:当线程获得了CPU的资源,开始执行线程就处于运行状态,执行run方法的方法体。
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
终止:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源。
25. 什么是线程池?为什么要使用它?
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)
26. sleep() 和 wait() 有什么区别
sleep()来自Thread类,而wait()来自Object类。调用sleep()方法的过程中,线程不会释放对象锁,不会让出系统资源,而调用 wait 方法线程会释放对象锁,出让CPU资源供其他线程使用。
sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用。
27. 线程同步
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各个线程之间要有个先来后到,不能一窝蜂挤上去一起执行。线程同步的真实意思就是是排队。几个线程之间要排队,一个一个按顺序对共享资源进行操作,而不是同时进行操作。所以,线程同步就是线程排队。
(1)同步方法
public synchronized void run() {
while(true){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() "购买了第" ticket "张票");
ticket--;
}
}
}
synchronized 方法控制对类成员的访问:每个类实例对应一把锁,每个synchronized方法都必须获得调用该方法的实例的锁才能执行,否则所属线程阻塞,方法一旦 执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保同一时刻对于每一个实例,其所声明为 synchronized的成员函数中至多只有一个处于可执行的状态。
(2) 同步块
public synchronized void run() {
while(true){
synchronized(SellTicket.class) {
if(ticket>0){
try {
Thread.sleep(100);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() "购买了第" ticket "张票");
ticket--;
}
}
}
}
当一个线程访问对象的一个synchronized(SellTicket.class)同步代码块时,它就获得了这个object的对象锁。另一个线程仍然可以访问该object中的非synchronized(SellTicket.class)同步代码块。同步块和同步方法相比缩小了需要安全的地方,提高了程序运行的效率。
28. 字节流和字符流的区别
Java中的字节流处理的最基本单位为单个字节,它通常用来处理二进制数据。Java中最基本的两个字节流类是InputStream和OutputStream,它们分别代表了组基本的输入字节流和输出字节流。InputStream类与OutputStream类均为抽象类,我们在实际使用中通常使用Java类库中提供的它们的一系列子类。
Java中的字符流处理的最基本的单元是Unicode码元(大小2字节),它通常用来处理文本数据。Java中的String类型默认就把字符以Unicode规则编码后存储在内存中。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式。使用不同的编码方式,相同的字符会有不同的二进制表示。实际上字符流是这样工作的:
输出字符流:把要写入文件的字符序列转为指定编码方式下的字节序列,然后再写入到文件中;
输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列)从而可以存在内存中。
29. GC是什么?为什么要有GC?
GC(Garbage Collection)是垃圾收集的意思,负责清除对象并释放内存。Java 提供的 GC 功能可以自动检测对象是否超过作用域从而达到自动回收内存的目的,从而防止内存泄漏。
Java的内存管理实际就是对象的管理,其中包括对像的分配和释放。我们开发人员使用new关键字创建对象,释放对象时只是将对象赋值为null,让这个对象不可用。GC将负责回收所有“不可用”对象的内存空间。当我们创建对象时,GC就开始监控这个对象地址、大小以及使用情况。
建议:
(1)尽早释放无用对象的引用,特别注意一些复杂对象,如数组等。此类对象,GC回收它们的效率一般较低,如果允许,应尽早将不用的引用对象赋为null,加速GC的工作。
(2)尽量少用finalize函数。finalize是java提供给程序员用来释放对象或资源的函数,但是它会加大GC的工作量,因此尽量少采用finalize函数回收资源。
30. 设计模式——Singleton(单例模式)
(1)定义:在程序运行期间,确保类的实例只有一个,并且提供了一个公共的入口点。实现的思路就是将构造函数私有,并且提供一个全局静态的方法返回一个实例。
(2)应用场景:
多线程中的线程池、应用程序的日志对象、数据库的连接池、应用的配置对象、缓存等常常被设计成单例。
(3)示例代码:
简单版(饿汉模式):
public class Singleton {
private static Singleton instance = new Singleton(); //饿汉模式:立即初始化
private Singleton() { } //私有化构造器
public static Singleton getInstance() { //提供公有的单例访问点
return instance;
}
}
双重检查版(懒汉模式):
public class Singleton{
private volatile static Singleton instance; //懒汉模式:延时到用时才初始化
private Singleton() { }
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) { //块避免多线程并发,同步块 双重检查
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
31. 设计模式——Simple Factory(简单工厂)设计模式
(1)定义:提供一个抽象产品(父类),由一个工厂类根据当前环境决定创建出哪一种实际产品(子类)的实例。
(2)应用场合:
Spring中作为DI容器使用时,可以看作就是一个工厂。又如Spring事务处理时需要一个抽象的产品“事务管理器”——PlatformTransactionManager,实际配置的则是具体的事务管理,如HibernateTransactionManager或者是DataSourceTransactionManager。当然Spring本身的功能非常复杂,用到的远远不仅是简单工厂一个模式。
(3)示例代码:
interface Vehicle{ //车辆抽象父类
void run();
}
class Car implements Vehicle{ //小轿车子类
public void run() {
System.out.println("---小车在行驶---");
}
}
class Tank implements Vehicle{ //坦克子类
public void run() {
System.out.println("---轰隆隆,坦克在行驶---");
}
}
class Factory{ //工厂类
public static Vehicle getVehicle(String name) {
if("tank".equals(name)) { //根据条件返回子类
return new Tank();
}else {
return new Car();
}
}
}
32. 设计模式——Proxy(代理)模式
(1)定义:创造一个代理类出来,包装并替原对象进行操作。
(2)应用场景:
拦截器、AOP和ORM的懒加载等地方都使用了代理模式。
(3)示例:
//接口
public interface UserDao{
void save();
}
//目标类
public class UserDaoImpl implements UserDao{
public void save(){
System.out.println("保存用户");
}
}
//代理类
public class UserDaoProxy implements UserDao{
private UserDao target;
public void setTarget(UserDao userDao){
this.target = userDao;
}
public void save(){
System.out.println("---预处理---"); //注入增强代码
this.target.save();
System.out.println("---后处理---"); //注入增强代码
}
}
33. 反射
在java中获取类或者对象的信息有两种方式,一种是编译时,例如我们在写代码时使用new 的方式创建对象。另一种方式就是在运行时,根据提供的类的信息动态创建的方式就是通过反射实现。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。