1、jdk和jre的区别?
-
本质区别:
JDK(Java Development Kit)是Java开发工具包,是Sun Microsystems针对Java开发员的产品。
JRE(Java Runtime Environment)是运行java程序所必须的环境和集合。
-
组成区别:
JDK包含一组java工具,例如(javadoc等);java运行环境(JRE);java核心类库API
JRE则包含有JVM和java的核心类库
2、==和equals的区别?
-
对于==,比较的是值是否相等
a、如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
b、如果作用于引用类型的变量,则比较的是所指向的对象的地址
-
对于equals方法,注意:equals方法不能作用于基本数据类型的变量,equals继承Object类,其拥有比较是否是同一个对象
a、如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
b、如果对equals方法进行重写,则按重写后的方式比较。例如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
3、两个对象的hashCode()相同,则equals()也一定为true吗?
- 结论:在hashCode()和equals()方法没有被重写的前提下,两个对象equals相等,则它们的hashcode必须相等,反之则不一定。
- 原因:hashCode ()方法继承自Object,其本质是通过将该对象的内部地址通过计算转换成一个整数值,(其本质是使用杂凑算法运算出来的,建议回答问题的时候不要说这个算法,以防面试官提问这个算法的相关问题),既然是算出来的,那么就是不准确的(接近准确但还是有误差),肯定会有一种情况,在某一时刻,多个对象传回相同的整数值,所以就能够出现“hashCode相同,对象不一定相等”的情况;那么,反过来,“相等的对象,hashCode一定相同么?“,是的,两对象相等,hashCode 一定相同,因为hashCode()是同一种计算方法,所以,相等的对象,通过HashCode计算得出的值一定相同。
4、final在java中的作用
- 使用final修饰符修饰一个类,那么这个类被称为终态类,其含义是不能被其它的java类继承(也就是该类不能被扩展,例如String类)
- 使用final修饰符修饰一个方法,那么这个方法就被称为终态方法,其含义就是不能被子类重写这个方法
- 使用final修饰符修饰属性,那么这个属性就是一个常量,其含义就是在程序运行期间不能给该属性再次赋值
5、Math.round(1.5)和Math.round(-1.5)分别是多少?为什么?
Math.round(1.5)是2,Math.round(-1.5)的值是-1。
原因是Math.round()的实现原理是在参数值基础上加0.5,然后对相加后的结果做向下取最小整数。
1.5+0.5为2.0,最小整数即为2
-1.5+0.5为-1.0,最小整数即为-1
6、String属于基础数据类型吗?
String不属于基础数据类型,它是引用数据类型,表示字符串对象。
Java中的数据分为基础类型和引用类型,其区别在于:
- 基础类型只能表示简单的数据和字符,而引用数据类型可以复杂的数据结构
- 基础类型只能表示数据本身,而引用数据类型还可以表示对数据的操作(也就是对象中的方法)
- 基础类型在内存中是分配存储空间直接存储数据值,而引用数据类型则是存储的是对象在内存中的引用(也就是对象地址)
7、java中操作字符串的类有哪些?它们有什么区别?
-
Java中操作字符串的类主要有String、StringBuffer和StringBuilder类
-
区别:
-
运行效率不同:
从运行效率上,StringBuilder大于StringBuffer,StringBuffer大于String
String是字符串常量值其改变都是创建新对象,而StringBuffer和StringBuilder都是字符串变量值,其改变不用创建新对象。所以StringBuffer和StringBuilder效率高于String
StringBuffer在多线程中具有线程锁,所以在多线程的情况下执行效率低于StringBuilder
-
线程安全不同:
StringBuilder是线程不安全的;而StringBuffer是线程安全的
-
数据值是否能改变:
String是字符串常量值,所以一旦创建不能改变其值;而StringBuffer和StringBuilder则是字符串变量值,所以其值是可以直接改变的
所以如果创建一个String类的字符串”abc”,在其值上添加字符串”de”其本质是创建新的字符串对象”abcde”,而不是直接改其值。
但是注意如下代码:
String str = “abc” + “de”;
//此句不是先创建两个字符串对象”abc”和”de”,然后在创建字符串”abcde”,而是直接创建字符串”abcde”,等同于String str = “abcde”。这是JVM做了优化的原因
-
8、String两种创建对象方式的区别?
String两种创建对象的方法分别为常量式创建和对象式创建
常量式创建:String str = “abc”;
对象式创建:String str = new String(“abc”);
区别:
-
常量式创建的String对象“abc”是存放在字符串常量池中。创建过程是,首先在字符串常量池中查找有没有"abc"对象;如果有则将str直接指向它,如果没有就在字符串常量池中创建出来“abc”,然后在将str指向它。当有另一个String变量被赋值为abc时,直接将字符串常量池中的地址给它。因此,这个对象可共享的,也是不可更改的
-
对象式创建字符串则是先在字符串池中创建字符串对象,然后在堆内存中创建一个新的字符串对象,堆中字符串对象去引用字符串池中的字符串,所以这种方式实质是创建了两个对象
9、如何将一个字符串反转?
-
方法一:使用字符串拼接方法
String str1 = "hello world!"; String str2 = ""; for(int i=str1.length()-1;i>=0;i--) str2 += str1.charAt(i); str1 = str2; System.out.println(str1);
-
方法二:使用StringBuilder方法
String str1 = "hello wrold!"; StringBuilder str2 = new StringBuilder(str1); str1 = str2.reverse().toString(); System.out.println(str1);
-
方法三:使用首尾交换字符的方法
String str1 = "hello world!"; char[] cs = str1.toCharArray(); int len = cs.length - 1; int half = (int)Math.floor(len /2); char c ; for(int i= 0 ; i <= half; i++) c = cs[i]; cs[i] = cs[len - i]; cs[len - i] = c; str1 = String.valueOf(cs); System.out.println(str1);
10、String类的常用方法?
- length():获取字符串的长度
- charAt():获取字符串指定索引位的字符
- concat():在原字符串末尾拼接一个字符串,并返回新的字符串
- trim():去掉字符串首尾两端的空格
- indexOf():查找原字符串中是否存在指定的字符或字符串,并返回其索引位。如果未找到返回-1
- equals():比较两个字符串的值是否相等
- toLowerCase():将原字符串中的英文大写字符转换成小写字符
- toUpperCase():将原字符串中的英文小写字符转换成大写字符
- substring():从原字符串中截取一个新的字符串
- split():按照指定的分隔符号将原始字符串分割成一个字符串数组
11、抽象类必须有抽象方法吗?
在java中,抽象类不一定具有抽象方法,也就是可以有抽象方法,也可以没有抽象方法。抽象方法不是抽象类的必要条件。
抽象类指用abstract修饰符修饰的类,该类不能被实例化。
例如:public abstract class Car{}
抽象方法指用abstract修饰符修饰的方法,该方法没有方法体,必须被子类重写
例如:public abstract void open();
12、普通类和抽象类有哪些区别?
- 抽象类不能被实例化,而普通类可以被实例化;
- 抽象类不能被声明为静态的,而普通类可以被声明为静态的;
- 抽象类中可以有抽象方法,而普通类中不能有抽象方法;
- 抽象类不能被声明为final的,而普通类可以被声明为final的;
13、抽象类能够使用final修饰吗?
抽象类不能被final修饰,原因抽象类一般都是继承中的顶层类,而继承的顶层类就是要子类去继承扩展的;而final修饰的类是终态类,是不能被子类继承的,这就与其本意冲突。所以抽象类不能是final修饰的。
14、接口和抽象类的联系和区别?
![](https://i-blog.csdnimg.cn/blog_migrate/32fcd8e2f5d9c8d598fb344d43b3fe2b.png)
15、java中的io流分为几种?
-
按流向分:输入流和输出流
-
按类型分:
-
字节流:InputStream,OutputStream
-
字符流:Reader和Writer
-
节点流:FileInputStream FileOutputStream FileReader FileWriter PrintStream PrintWriter 等
-
处理流:
a. 转换流:InputStreamReader/OutputStreamWriter
b. 缓冲流:BufferedInputStream/BufferedOutputStream BufferedReader/BufferedWriter等
备注:处理流和节点流的区分方法,节点流在新建时需要一个数据源(文件、网络)作为参数,而处理流需要一个节点流作为参数
-
16、BIO和NIO和AIO的区别?
- 同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务端资源要求比较高,并发局限于应用中,在jdk1.4以前是唯一的io现在,但程序直观简单易理解
- 同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1,4开始支持
- 异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk1.7开始支持。
- 总结:BIO是一个连接一个线程;NIO是一个请求一个线程;AIO是一个有效的请求一个线程
17、Files的常用方法有哪些?
- copy(InputStream,Path filepath):将输入流中的所有字节复制到文件
- copy(Path filepath,OutputStream):将文件中所有字节复制到输出流
- copy(Path source,Path target):将源文件的所有字节复制到目标文件
- createDirectory:创建一个目录
- createFile:创建一个文件
- newBufferedReader:创建一个文件的缓存字符输入流读取文件内容
- newBufferedWriter:创建一个文件的缓存字符输出流写入文件内容
18、java的容器有哪些?
所谓容器指专门用于存储数据的java对象。
-
广义理解:
- 数组:用于存储固定长度的固定类型数据的容器
- 字符串:用于存储固定长度字符的容器,其底层实现就是一个char型数组
- 集合:用于存放任意类型对象,不确定长度的对象容器,包括List,Set和Ma
-
狭义理解:集合容器
19、Collection和Collections的区别?.
Collection是集合类的上级接口,继承与他有关的接口主要有List和Set
Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作
20、List和Set以及Map有什么区别?
- List的元素以线性方式存储,可以存放重复对象,主要实现类ArrayList和LinkedList
- Set中的对象不按特定(HashCode)的方式排序,并且没有重复对象,Set主要有以下两个实现类:HashSet和TreeSet
- Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一个键对象和值对象。 Map主要有以下两个实现类:HashMap,LinkedHashMap和HashTable
21、HashMap和Hashtable有什么区别?
-
继承不同:
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
-
Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
-
Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
-
两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
-
哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
-
Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
22、如何决定使用HashMap还是TreeMap?
- TreeMap的key值要求实现Comparable接口,其默认按照key值升序排序;
- HashMap的key值实现散列hashCode(),不支持排序
- 对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快(TreeMap插入数据需要进行排序,要消耗更多的时间),但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择
23、说一下HashMap的实现原理?
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序。
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。如图:
创建一个HashMap就是初始化一个Entry类型的数组。Entry代表的就是Map中的一个键值对,其具有key,value和一个指向下一个元素的引用,通过这样的方式就构成了链表结构。
在map集合put(存)数据时,先根据key的hashCode计算其hash值,根据hash值获取当前数据在Entry数组中的下标,如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
24、HashSet的实现原理?
- HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
- 当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
- HashSet的其他操作都是基于HashMap的。
25、ArrayList和LinkedList的区别和优缺点对比?
- 区别:
- ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。
- 对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
- 优缺点对比:
- 对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是 统一的,分配一个内部Entry对象。
- 在ArrayList集合中添加或者删除一个元素时,当前的列表所所有的元素都会被移动。而LinkedList集合中添加或者删除一个元素的开销是固定的。
- LinkedList集合不支持 高效的随机随机访问(RandomAccess),因为可能产生二次项的行为。
- ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
- 总结:查询用ArrayList,增删改用LinkedList
26、如何实现数组和List的转换?
-
List转数组:
toArray(arraylist.size())
方法List<String> strList = new ArrayList<String>(); strList.add("aaa"); strList.add("bbb"); strList.add("ccc"); String[] strArray = strList.toArray(new String[strList.size()]); System.err.println(Arrays.toString(strArray));
-
数组转List:
Arrays.asList
和Collections.addAll
两种方法String[] strArray = {"111","222","333"}; //方法一 List<String> strList = Arrays.asList(strArray); System.err.println("方法一:"+strList); //方法二 List<String> sList = new ArrayList<String>(); Collections.addAll(sList, strArray); System.err.println("方法二:"+sList);
27、ArrayList和Vecotor的区别?
- Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
- 当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
28、Array和ArrayList有何区别?
- 存储数据类型不同:Array可以存储基本型数据,也可以存储对象;而ArrayList只能存储对象数据;
- 是否支持异构数据不同:Array只能存储相同类型的数据,不支持异构数据的存储;而ArrayList可以存储不同类型的数据,支持异构数据的存储;
- 长度是否可以改变:Array一旦创建,其长度不能改变;而ArrayList的长度可以在程序运行期间动态改变;
29、在Queue中poll()和remove()有什么区别?
-
Queue接口与List、Set同一级别,都是继承了Collection接口。
-
Queue接口表示队列,其是一种数据结构。队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
-
remove() : 移除队列头的元素并且返回,如果队列为空则抛出异常
-
poll() : 移除队列头的元素并且返回,如果队列为空则返回null
-
区别:在移除队列头元素时,当队列为空的时候,用remove()方法会抛出异常,用poll()方法则会返回null
备注:1和2点属于了解内容,回答问题可以不用回答
30、哪些集合类线程是安全的?
- Vector:就比Arraylist多了个同步化机制(线程安全)。
- Hashtable:就比Hashmap多了个线程安全。
- ConcurrentHashMap:是一种高效但是线程安全的集合。
- Stack:栈,也是线程安全的,继承于Vector
- ConcurrentHashMap ,Map接口下实现线程安全的类
了解内容:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
31、迭代器Iteraotr是什么?
- 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
- Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
- 简单的说,迭代器就是一个接口Iterator,实现了该接口的类就叫做可迭代类,可以通过这些类和接口实现对集合元素的遍历。
32、Iterator如何使用?有什么特点?
-
Iterator的使用
-
通过集合对象的iterator()方法获取迭代器对象
-
循环遍历,在循环条件部分判断迭代器hasNext()方法的返回值是否为真,如果是真,则在循环中使用迭代器对象的next()方法获取当次循环要遍历的迭代器对象,例如:
List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); Iterator<String> it = list.iterator(); while(it.hasNext()) { String s = it.next(); }
-
-
Iterator的特点:
- Iterator遍历集合元素的过程中不允许线程对集合元素进行修改,否则会抛出ConcurrentModificationEception的异常。
- Iterator遍历集合元素的过程中可以通过remove方法来移除集合中的元素。
- Iterator必须依附某个Collection对象而存在,Iterator本身不具有装载数据对象的功能。
- Iterator.remove方法删除的是上一次Iterator.next()方法返回的对象。
33、ListIterator和Iterator有什么区别?
- ListIterator有add()方法,可以向List中添加对象,而Iterator不能;
- ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以;
- ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能;
- 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改;
34、怎么确保一个集合不能被修改?
-
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java.lang.UnsupportedOperationException 异常。
-
实例:
List<String> list = new ArrayList<>(); list.add("x"); Collection<String> clist = Collections.unmodifiableCollection(list); clist.add("y"); // 运行时此行报错 System.out.println(list.size());
35、并发和并行有什么区别?
-
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
-
并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
-
并行在多台处理器上同时处理多个任务,并发是在一台处理器上“同时”处理多个任务。
例如:
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 (不一定是同时的)
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
36、线程和进程的区别?
- 根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
- 在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
- 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
- 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
- 包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
37、守护线程是什么?
-
守护线程,是运行在后台的一种特殊线程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
-
守护线程的特点:如果进程中的其它用户线程都已经结束,那么守护线程也就结束并退出JVM。简言之,就是守护线程具备自动结束生命周期的作用。
-
相关内容:
如何创建守护线程
Thread t = new Thread( ()->{ 线程操作内容 }); t.setDaemon(true); t.start();
备注:守护线程的设置必须在该线程调用start()方法之前,否则会报出IllegalThreadStateException异常
38、线程创建有几种方式?
-
第一种为继承Thread类;
import org.springframework.web.bind.annotation.RestController; @RestController public class MyThread1 extends Thread{ @Override public void run() { System.err.println("运行过程"); } }
-
第二种为实现Runnable接口,创建线程时必须创建Thread类的实例,通过Thread类的构造函数将Runnable对象转为Thread对象;
import org.springframework.web.bind.annotation.RestController; @RestController public class MyThread2 implements Runnable{ @Override public void run() { System.err.println("运行过程2"); } }
-
第三种为实现Callable接口,创建线程时需要一个FutureTask对象,再使用Thread类的构造函数包装构建线程,这种方式可以获取线程执行后的返回值
import java.util.concurrent.Callable; import org.springframework.web.bind.annotation.RestController; @RestController public class Mythread3 implements Callable<Integer>{ @Override public Integer call() throws Exception { System.err.println("运行过程3"); return null; } }
面试题:有线程A、B、C,A、B同时执行,A、B执行完毕之后,执行C
**分析:**考同步运行和异步运行,A、B异步,AB和C同步(AB阻塞,执行完成后才能执行C)
代码:(实现Callable,利用FutureTask创建A、B线程,调用get()方法阻塞A、B线程,最后启动线程C)
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication public class CallableTest { public static void main(String[] args) throws InterruptedException, ExecutionException { //线程A、B同时运行,线程C在A、B之后运行 Callable<A> a = new Callable<A>(){ @Override public A call() throws Exception { for(int i = 0; i < 10; i++){ System.out.print(" A" + i); } return new A(); } }; Callable<B> b = new Callable<B>(){ @Override public B call() throws Exception { for(int i = 0; i < 10; i++){ System.out.print(" B" + i); } return new B(); } }; FutureTask<A> taskA = new FutureTask<A>(a); FutureTask<B> taskB = new FutureTask<B>(b); new Thread(taskA).start(); new Thread(taskB).start(); if(taskA.get() != null && taskB.get() != null){ new Thread(new C()).start(); } } static class A{} static class B{} static class C extends Thread{ @Override public void run() { for(int i = 0; i < 10; i++){ System.out.print(" C" + i); } } } }
39、说一下runnable和callable有什么区别?
-
两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
-
Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛出
**备注:**Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
40、线程有哪些状态?
-
新建状态(New): •线程创建但是还没有调用start()方法
-
就绪状态(Runnable):一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程。当start()方法返回后,线程就处于就绪状态。
-
运行状态(Running):当准备就绪的线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
-
阻塞状态(Blocked)
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态
线程运行过程中,可能由于各种原因进入阻塞状态:
A、线程通过调用sleep方法进入睡眠状态;
B、线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
C、线程试图得到一个锁,而该锁正被其他线程持有;
D、线程在等待某个触发条件;
-
死亡状态(Dead)
有两个原因会导致线程死亡:
run方法正常退出而自然死亡,
一个未捕获的异常终止了run方法而使线程猝死。
备注:为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
41、线程的sleep()方法和Object的wait()方法有什么区别?
sleep()
方法是线程Thread类的静态方法,调用此方法会让当前线程暂停执行指定时间.将CPU时间片分给其他线程,但是对象的锁依然保持.休眠时间结束后会自动恢复到就绪状态;wait()
是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁,线程暂停执行,进入对象的等待池,只有调用对象的notify()方法或者notifyAll()方法时,才能唤醒等待池中的线程进入等锁池,如果线程重新获得对象的锁就可以进入到就绪状态;
42、notify()和notifyAll()有什么区别?
notify()
是唤醒线程等待池中的随机一个线程;而notifyAll()
是唤醒线程等待池中的所有线程
**备注:**以下部分不用回答。理解即可
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
相关概念:
**锁池:**假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
**等待池:**假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
43、线程的run()和start()有什么区别?
run()
相当于线程的任务处理逻辑的入口方法,它由Java虚拟机在运行相应线程时直接调用,而不是由应用代码进行调用。start()
的作用是启动相应的线程。启动一个线程实际是请求Java虚拟机运行相应的线程,而这个线程何时能够运行是由线程调度器决定的。start()调用结束并不表示相应线程已经开始运行,这个线程可能稍后运行,也可能永远也不会运行
44、创建线程池有几种方式?
-
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
特点:
-
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
-
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
-
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ThreadPoolExecutorTest { public static void main(String[] args) { ExecutorService cachedThreadPool =Executors.newCachedThreadPool(); for (int i = 0; i <= 10; i++) { final int index=i; try { Thread.sleep(index*500); } catch (InterruptedException e) { e.printStackTrace(); } cachedThreadPool .execute(new Runnable() { @Override public void run() { System.err.println(index); } }); } } }
-
-
newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ThreadPoolExecutorTest { public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);//线程池大小为3 for (int i = 0; i <= 10; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { @Override public void run() { System.err.println(index); try { Thread.sleep(2000);//每个任务睡眠2秒 } catch (InterruptedException e) { e.printStackTrace(); } } }); } } } //因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。 //定长线程池的大小最好根据系统资源进行设置如Runtime.getRuntime().availableProcessors()。
-
newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ThreadPoolExecutorTest { public static void main(String[] args) { ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i <= 10; i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { @Override public void run() { System.err.println(index); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } }
-
newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
-
延迟3秒执行,延迟执行
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ThreadPoolExecutorTest { public static void main(String[] args) { ScheduledExecutorService scheduledThreadPool=Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.err.println("延迟3秒"); } }, 3, TimeUnit.SECONDS); } }
-
延迟1秒后每3秒执行一次
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ThreadPoolExecutorTest { public static void main(String[] args) { ScheduledExecutorService scheduledThreadPool=Executors.newScheduledThreadPool(5); scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.err.println("延迟1秒后每3秒执行一次"); } }, 1, 3, TimeUnit.SECONDS); } }
备注:线程池概念:
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。
-
45、线程池有几种状态?
-
RUNNING:运行状态
- 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
- 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
-
SHUTDOWN:关闭状态
- 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
- 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
-
STOP:停止状态
- 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
- 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
-
TIDYING:整理状态
- 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
- 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
-
TERMINATED:终止状态
- 状态说明:线程池彻底终止,就变成TERMINATED状态。
- 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
46、线程池中submit()和execute()有什么区别?
-
线程池中的execute方法大家都不陌生,即开启线程执行池中的任务。还有一个方法submit也可以做到,它的功能是提交指定的任务去执行并且返回Future对象,即执行的结果
-
submi()
和execute()
区别:- 方法接收的参数不同:submit()可以接收Runnable接口对象,也可以接收Callable接口对象;execute()只能接收Runnable接口对象
- 方法返回值不同:submit()有Future类型的返回值;execute()没有返回值
- 能够将线程中的异常交给外部调用者处理:Submit()比exeucte()更方便将线程执行中的异常交给外部调用者处理,只要调用submit()方法返回的Fature对象的get()方法即可捕获处理线程执行中产生的异常对象
47、在java中怎么保证多线程的运行安全?
-
线程的安全性体现
- 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
- 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
- 有序性:程序执行的顺序按照代码的先后顺序执行
-
导致原因
- 缓存导致的可见性问题
- 线程切换带来的原子性问题
- 编译优化带来的有序性问题
-
解决办法
- JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
- synchronized、volatile、LOCK,可以解决可见性问题
- Happens-Before 规则可以解决有序性问题
-
具体实现
-
使用synchronized关键字修饰多线程需要使用的公有资源(可以是共同调用的方法,也可以是共同执行的语句块),依赖JVM实现锁,因此在这个关键字作用对象的作用范围内是同一时刻只能有一个线程进行操作;
-
同步代码块
public class Main { public static void main(String[] args) { SynchronizedBlockThread blockThread = new SynchronizedBlockThread(); Thread t1 = new Thread(blockThread, "窗口1--"); Thread t2 = new Thread(blockThread, "窗口2--"); t1.start(); t2.start(); } }
public class SynchronizedBlockThread implements Runnable { private Object obj = new Object(); private int ticketCount = 100; @Override public void run() { while (ticketCount > 0) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } sale(); } } public void sale(){ synchronized (obj) { //使用同步代码块使线程间同步 if (ticketCount > 0) { System.err.println(Thread.currentThread().getName() + "正在出售第" + (100-ticketCount+1) + "张票"); ticketCount --; } } } }
-
同步函数
public class SellTicketMain { public static void main(String[] args) { SynchronizedMethodThread methodThread = new SynchronizedMethodThread(); Thread t1 = new Thread(methodThread, "窗口1--"); Thread t2 = new Thread(methodThread, "窗口2--"); t1.start(); t2.start(); } }
public class SynchronizedMethodThread implements Runnable{ private int ticketCount = 100; @Override public void run() { while (ticketCount > 0) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } sale(); } } public synchronized void sale(){ //使用同步函数使线程间同步 if (ticketCount > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (100-ticketCount+1) + "张票"); ticketCount --; } } }
-
静态同步函数
public class Main { public static void main(String[] args) { StaticSynchronizedThread thread = new StaticSynchronizedThread(); Thread t1 = new Thread(thread, "窗口1--"); Thread t2 = new Thread(thread, "窗口2--"); t1.start(); try { Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); } thread.flag = false; t2.start(); } }
public class StaticSynchronizedThread implements Runnable { private static int ticketCount = 100; public boolean flag = true; @Override public void run() { if (flag) { while (ticketCount > 0) { synchronized (StaticSynchronizedThread.class) { // 同步代码块 if (ticketCount > 0) { try { Thread.sleep(50); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - ticketCount + 1) + "票"); ticketCount--; } } } } else { // 执行静态同步函数 while (ticketCount > 0) { sale(); } } } public static synchronized void sale() { //静态同步函数 if (ticketCount > 0) { try { Thread.sleep(50); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - ticketCount + 1) + "票"); ticketCount--; } } }
注意:
- 要使用synchronized,必须要有两个以上的线程。单线程使用没有意义,还会使效率降低。
- 要使用synchronized,线程之间需要发生同步,不需要同步的没必要使用synchronized,例如只读数据。
- 使用synchronized的缺点是效率非常低,因为加锁、释放锁和释放锁后争抢CPU执行权的操作都很耗费资源。
-
-
使用LOCK接口类型对象,通过在代码中显示的添加同步锁。这种方式需要用代码创建Lock接口对象,在同步资源开始时调用lock()获取锁,执行完毕后调用unlock()释放锁。ReentrantLock是Lock接口的一个典型实现类
public class Main { public static void main(String[] args) { TicketThread t1 = new TicketThread(); TicketThread t2 = new TicketThread(); TicketThread t3 = new TicketThread(); t1.start(); t2.start(); t3.start(); } }
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TicketThread extends Thread { // 定义变量记录总票数 private static int tickets = 100; // 创建互斥锁对象 private static Lock lock = new ReentrantLock(); @Override public void run() { // 使用循环保证票能够被卖完 while (true) { try { // 模拟休眠 Thread.sleep(500); // 获取锁 lock.lock(); // 开始卖票 if (tickets > 0) { // 卖一张 System.err.println(this.getName() + " 卖了一张票,还剩 " + (--tickets) + " 张"); continue; } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 释放锁 lock.unlock(); } System.out.println("票没了..."); break; } } }
-
48、什么是死锁?如何防止死锁?
-
概念:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
-
死锁通俗的例子(这个不用回答,但可以比较形象的类比死锁的概念,在面试中可以适当用这样的生活中例子来解释一些比较抽象的java概念):假设张三和李四两个不同班级的同学今天都承接打扫卫生的工作任务,在工作中,需要用到拖布和扫把(这些是学校的公有资源且各自只有一个),张三和李四在同一时间开始工作,张三先去拿扫把再去取拖布,李四先取了拖布再拿扫把。这个时候就出现了张三拿了扫把,李四拿了拖布,两个人都需要对方的工具才能开始打扫卫生,但是两个人都不愿意放下手上已经拿到的工具,此时就是死锁(换句话说张三,李四谁也干不了活)
-
避免死锁的方法:
- 从加锁顺序解决:当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。
- 给锁上添加时限:获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。
- 进行死锁检测
49、ThreadLocal是什么?有哪些使用场景?
-
ThreadLocal概念:
ThreadLocal,很多地方叫做线程本地变量,或者叫做线程本地存储,其用于为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量
-
使用场景:
- 线程使用的数据对象不需要在多线程之间共享;
- 线程使用的数据对象需要在线程内部调用的多个方法之间传递
- 例如线程A和B有都需要各自数据data,且这些数据放在线程外部保存并准备在线程内部的多个方法中使用(你可以理解为线程中a()方法用于存储数据data,b()方法负责使用),那么如果保证每个线程存储的数据data不会被别的线程所修改。此时就可以使用ThreadLocal,它本质是一个Map,以每个线程的标识为key,可以存储任何数据值。因为每个线程只能获取自身的标识,不能获取别的线程标识,所以多个线程可以使用它存储各自的数据,彼此之间不发生影响
50、什么是反射?
-
概念:Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。
-
作用:我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
-
优点: 反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创和控制任何类的对象,无需提前硬编码目标类
-
缺点:
-
**性能第一 **
反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
-
**安全限制 **
使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了。
-
内部暴露
由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
-
51、什么是java的序列化?什么时候需要使用对象的序列化?
-
Java序列化就是指对象的序列化,其本质就是通过对象流将程序中的对象信息输出到指定位置(序列化)以及把指定位置的对象信息读取到内存中重新构建一个对象(反序列化)
-
什么时候需要使用序列化:
假如我们有两台计算机中都在执行java程序,其中A计算机上有一个张三学生对象信息,这个信息B计算机想进行使用。但是AB是两台不同的计算机,是无法直接调用的。那么此时我们就可以使用对象的序列化了。将A计算机上的学生张三对象序列化之后通过网络传输到B计算机,B计算机经过反序列化就可以获取和A计算机上一样的学生对象张三进行使用。
52、动态代理是什么?有哪些应用?
-
动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。
-
动态代理实现:首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。
-
动态代理的应用:Spring的事务管理器(给业务方法上添加事务的回滚,提交等行为)
/** * 接口类 */ public interface Sell { void sell(); void ad(); }
/** * 委托类 * 实现Sell方法 */ public class Vendor implements Sell{ @Override public void sell() { System.err.println("sell"); } @Override public void ad() { System.err.println("ad"); } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 中介类 */ public class DynamicProxy implements InvocationHandler{ private Object obj;//obj为委托类对象 public Object getProxyInstance(Object obj) { this.obj = obj; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.err.println("before"); //在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理) Object result=method.invoke(obj, args); System.err.println("after"); return result; } }
public class Main { public static void main(String[] args) { // 创建中介类实例 DynamicProxy dynamicProxy = new DynamicProxy(); // 获取代理类实例sell Sell sell = (Sell)dynamicProxy.getProxyInstance(new Vendor()); // 通过代理类对象调用代理类方法,实际上会转到invoke方法调用 sell.sell(); sell.ad(); } }
Spring AOP 之JDK动态代理和CGLIB代理的区别
-
描述和实现原理
-
JDK动态代理
-
通过实现 InvocationHandler 接口创建自己的调用处理器;
-
通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
-
通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
-
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
-
-
GCLIB代理
- cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
- cglib封装了asm,可以在运行期动态生成新的class。
-
-
区别
-
两者之间的区别
-
JDK的动态代理必须基于接口,CGLIB没有这个要求
-
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
-
-
JDK动态代理和CGLIB字节码生成的区别?
-
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
-
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final
-
-
-
具体应用
-
什么情况下会用哪种方式实现动态代理
-
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
-
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
-
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
-
-
如何强制使用CGLIB实现AOP?
-
添加CGLIB库,
SPRING_HOME/cglib/*.jar
-
在spring配置文件中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
-
-
53、为什么要使用对象的克隆?
在java程序中,有时候我们需要多个类型相同的对象,例如我们需要100个学生对象,这100个学生都是男生,年龄都是18岁,就是学号和姓名不同。如果我们按照原始方式每个学生对象单独创建(也就是new),那么效率是比较低的。更好的方式是我们只需要先创建一个学生,赋予它学号,姓名,性别和年龄。然后以第一个学生对象去克隆99个新的学生对象,这些克隆的学生对象只需要修改学号和姓名,不需要单独在设置年龄和性别。那么效率比原始创建的方式要高。所以在这种场合下需要使用对象克隆。
54、如何实现对象的克隆?
-
方法一:通过重写clone()方法,该方法要求类实现Cloneable接口,且不能实现深克隆
public class Main { public static void main(String[] args) { Student stu=new Student(1,"张三"); for (int i = 0; i < 10; i++) { stu.clone(); } } }
import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Student implements Cloneable { private Integer sid; private String sname; @Override protected Student clone() { Student stu = null; try { stu = (Student) super.clone(); System.err.println(stu.getSname()+";"+stu.getSid()+";"+stu.toString()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return stu; } }
-
方法二:通过对象的序列化和反序列化实现,此种方式可以实现深克隆
55、深克隆和浅克隆的区别是什么?
- 浅克隆,在clone对象时,只会把基本数据类型的数据进行复制过去;如果是引用类型,只会把引用复制过去,也就是被克隆对象和原始对象信息,共同引用一个引用类型的属性。
- 深克隆:在克隆时,会把基本数据类型的数据和引用类型的数据,同时复制。克隆对象和原始对象不共同引用一个引用类型
56、throw和throws的区别?
- 含义不同:throw是抛出异常对象,而throws是方法运行有可能会抛出异常的声明
- 书写位置不同:throw写在要抛出异常的方法体中,而throws写在要抛出异常的方法签名之后
- 语法不同:throw之后是异常的对象,而throws之后是要抛出异常的类型名称
- throw只能抛出一个异常对象,而throws可以声明多种不同类型的异常抛出声明
57、final和finally和finalize有什么区别?
- final是修饰符,可以修饰类,属性和方法,分别表示终态类,常量和终态方法
- finally是异常处理关键字,表示异常处理完毕最后 一定要执行的操作
- finalize()是Object继承的方法,表示对象在内存空间释放前要执行的操作
58、try-catch-finally中那个部分可以省略?
catch或finally都可以省略,也就是可以是try{}catch{}
,也可以是try{}finally{}
59、try-catch-finally中,如果catch中return了,那么finally还会执行吗?
finally还是会执行,因为finally表示最后一定要执行,在之前出现return,那么也会在return之前先执行finally,然后再return
60、常见的异常类有哪些?
- 算术异常类:ArithmeticException
- 空指针异常类:NullPointerException
- 类型强制转换异常:ClassCastException
- 数组负下标异常:NegativeArrayException
- 数组下标越界异常:ArrayIndexOutOfBoundsException
- 违背安全原则异常:SecturityException
- 文件已结束异常:EOFException
- 文件未找到异常:FileNotFoundException
- 字符串转换为数字异常:NumberFormatException
- 操作数据库异常:SQLException
- 输入输出异常:IOException
- 方法未找到异常:NoSuchMethodException
61、Error和Exception的区别
- Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。对于这类错误,Java编译器不去检查他们。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。
- Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
62、对象排序Comparable和Comparator
-
Comparable
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User implements Comparable<User> { private String name; private Integer age; private Integer password; /** * 从小到大排序 */ @Override public int compareTo(User user) { if (name.hashCode() == user.getName().hashCode()) { if (age==user.getAge()) { return password<user.getPassword()?-1:1; } return age<user.getAge()?-1:1; } return name.hashCode() < user.getName().hashCode()?-1:1; } }
import java.util.ArrayList; import java.util.Collections; import java.util.List; import vip.cbpro.po.User; public class Sort { public static void main(String[] args) { List<User> list=new ArrayList<User>(); list.add(new User("A",11,123)); list.add(new User("B",11,123)); list.add(new User("A",12,124)); list.add(new User("B",11,123)); list.add(new User("B",11,1233)); list.add(new User("C",12,1232)); list.add(new User("C",14,123)); list.add(new User("A",13,1235)); list.add(new User("A",13,123)); list.add(new User("A",13,123)); list.add(new User("A",11,1236)); Collections.sort(list); for (int i = 0; i < list.size(); i++) { System.err.println("排序后"+list.get(i)); } } }
-
Comparator
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User{ private String name; private Integer age; private Integer password; }
import java.util.Comparator; import vip.cbpro.po.User; /** * 创建排序方法 * 实现Comparator接口 */ public class UserComparator implements Comparator<User>{ @Override public int compare(User o1, User o2) { if (o1.getName().hashCode()==o2.getName().hashCode()) { if (o1.getAge()==o2.getAge()) { return o1.getPassword()<o2.getPassword()?-1:1; } return o1.getAge()<o2.getAge()?-1:1; } return o1.getName().hashCode()<o2.getName().hashCode()?-1:1; } }
import java.util.ArrayList; import java.util.Collections; import java.util.List; import vip.cbpro.po.User; public class Sort { public static void main(String[] args) { List<User> list=new ArrayList<User>(); list.add(new User("A",11,123)); list.add(new User("B",11,123)); list.add(new User("A",12,124)); list.add(new User("B",11,123)); list.add(new User("B",11,1233)); list.add(new User("C",12,1232)); list.add(new User("C",14,123)); list.add(new User("A",13,1235)); list.add(new User("A",13,123)); list.add(new User("A",13,123)); list.add(new User("A",11,1236)); UserComparator userComparator=new UserComparator(); Collections.sort(list,userComparator); for (int i = 0; i < list.size(); i++) { System.err.println("排序后"+list.get(i)); } } }
Java IO之装饰器&适配器模式
-
装饰器模式
**概念:**装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。
应用场景:
- 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
- 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
- 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
public class InputTest { public static void main(String[] args){ int c; try ( //InputStream类是抽象组件, //FileInputStream是具体组件, //FilterInputStream是装饰器角色, //BufferedInputStream是具体装饰器角色 InputStream in = new BufferedInputStream(InputTest.class.getResourceAsStream("test.txt")); ){ byte[] bytes = new byte[in.available()]; String s = new String(bytes, 0 , in.read(bytes)); System.out.println(s); }catch (IOException e){ e.printStackTrace(); } } }
在字节流InputStream/OutputStream的使用中,FilterInputStream/FilterOutputStream是作为装饰器的存在。
-
适配器模式
概念: 适配就是由 “源” 到 “目标” 的适配,主要目的是兼容性,让原本因为接口不匹配导致不能工作的两个类可以一起工作
应用场景:
- 目标接口(Target):客户端要的接口
- 实际接口(Adaptee):需要被适配的接口
- 适配器(Adapter):适配器把适配源的接口转换成目标接口
public class ReaderTest { public static void main(String[] args){ //这里的InputStream就是被适配接口(源角色) try (InputStream in = ReaderTest.class.getResourceAsStream("file.txt") ){ //Reader是目标接口,InputStreamReader是适配器 Reader reader = new InputStreamReader(in); char[] chars = new char[1024]; reader.read(chars); System.out.println(new String(chars)); } catch (IOException e) { e.printStackTrace(); } //对象适配器模式充满着良好的OO设计原则: //使用对象组合,以修改的接口包装被适配者,被适配者的任何子类,都可以搭配着适配器使用 } }
**在字符流Reader/Writer的使用中,InputStreamReader/OutputStreamWriter是作为适配器的存在。 **
-
Java的几种设计模式
java的设计模式大体上分为三大类:
- 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
- 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
- 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式遵循的原则有6个:
1、开闭原则(Open Close Principle)
对扩展开放,对修改关闭。
2、里氏代换原则(Liskov Substitution Principle)
只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
使用多个隔离的借口来降低耦合度。
5、迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
1. 工厂模式(Factory Method)
常用的工厂模式是静态工厂,利用static方法,作为一种类似于常见的工具类Utils等辅助效果,一般情况下工厂类不需要实例化。
interface food{}
class A implements food{}
class B implements food{}
class C implements food{}
public class StaticFactory {
private StaticFactory(){}
public static food getA(){ return new A(); }
public static food getB(){ return new B(); }
public static food getC(){ return new C(); }
}
class Client{
//客户端代码只需要将相应的参数传入即可得到对象
//用户不需要了解工厂类内部的逻辑。
public void get(String name){
food x = null ;
if ( name.equals("A")) {
x = StaticFactory.getA();
}else if ( name.equals("B")){
x = StaticFactory.getB();
}else {
x = StaticFactory.getC();
}
}
}
2. 抽象工厂模式(Abstract Factory)
一个基础接口定义了功能,每个实现接口的子类就是产品,然后定义一个工厂接口,实现了工厂接口的就是工厂,这时候,接口编程的优点就出现了,我们可以新增产品类(只需要实现产品接口),只需要同时新增一个工厂类,客户端就可以轻松调用新产品的代码。
抽象工厂的灵活性就体现在这里,无需改动原有的代码,毕竟对于客户端来说,静态工厂模式在不改动StaticFactory类的代码时无法新增产品,如果采用了抽象工厂模式,就可以轻松的新增拓展类。
interface food{}
class A implements food{}
class B implements food{}
interface produce{ food get();}
class FactoryForA implements produce{
@Override
public food get() {
return new A();
}
}
class FactoryForB implements produce{
@Override
public food get() {
return new B();
}
}
public class AbstractFactory {
public void ClientCode(String name){
food x= new FactoryForA().get();
x = new FactoryForB().get();
}
}
3. 单例模式(Singleton)
在内部创建一个实例,构造器全部设置为private,所有方法均在该实例上改动,在创建上要注意类的实例化只能执行一次,可以采用许多种方法来实现,如Synchronized关键字,或者利用内部类等机制来实现。
-
懒汉式,线程不安全
**是否 Lazy 初始化:**是
**是否多线程安全:**否
**实现难度:**易
**描述:**这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
-
懒汉式,线程安全
**是否 Lazy 初始化:**是
**是否多线程安全:**是
**实现难度:**易
**描述:**这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
-
饿汉式
**是否 Lazy 初始化:**否
**是否多线程安全:**是
**实现难度:**易
**描述:**这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
-
双检锁/双重校验锁(DCL,即 double-checked locking)
**JDK 版本:**JDK1.5 起
**是否 Lazy 初始化:**是
**是否多线程安全:**是
**实现难度:**较复杂
**描述:**这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
-
登记式/静态内部类
**是否 Lazy 初始化:**是
**是否多线程安全:**是
**实现难度:**一般
**描述:**这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
-
枚举
**JDK 版本:**JDK1.5 起
**是否 Lazy 初始化:**否
**是否多线程安全:**是
**实现难度:**易
**描述:**这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。public enum Singleton { INSTANCE; public void whateverMethod() { } }
4.建造者模式(Builder)
在了解之前,先假设有一个问题,我们需要创建一个学生对象,属性有name,number,class,sex,age,school等属性,如果每一个属性都可以为空,也就是说我们可以只用一个name,也可以用一个school,name,或者一个class,number,或者其他任意的赋值来创建一个学生对象,这时该怎么构造?
难道我们写6个1个输入的构造函数,15个2个输入的构造函数…吗?这个时候就需要用到Builder模式了。给个例子,大家肯定一看就懂:
public class Builder {
static class Student{
String name = null ;
int number = -1 ;
String sex = null ;
int age = -1 ;
String school = null ;
//构建器,利用构建器作为参数来构建Student对象
static class StudentBuilder{
String name = null ;
int number = -1 ;
String sex = null ;
int age = -1 ;
String school = null ;
public StudentBuilder setName(String name) {
this.name = name;
return this ;
}
public StudentBuilder setNumber(int number) {
this.number = number;
return this ;
}
public StudentBuilder setSex(String sex) {
this.sex = sex;
return this ;
}
public StudentBuilder setAge(int age) {
this.age = age;
return this ;
}
public StudentBuilder setSchool(String school) {
this.school = school;
return this ;
}
public Student build() {
return new Student(this);
}
}
public Student(StudentBuilder builder){
this.age = builder.age;
this.name = builder.name;
this.number = builder.number;
this.school = builder.school ;
this.sex = builder.sex ;
}
}
public static void main( String[] args ){
Student a = new Student.StudentBuilder().setAge(13).setName("LiHua").build();
Student b = new Student.StudentBuilder().setSchool("sc").setSex("Male").setName("ZhangSan").build();
}
}
5. 原型模式(Protype)
原型模式就是讲一个对象作为原型,使用clone()方法来创建新的实例。
public class Prototype implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}finally {
return null;
}
}
public static void main ( String[] args){
Prototype pro = new Prototype();
Prototype pro1 = (Prototype)pro.clone();
}
}
此处使用的是浅拷贝,关于深浅拷贝,大家可以另行查找相关资料。
6.适配器模式(Adapter)
适配器模式的作用就是在原来的类上提供新功能。主要可分为3种:
-
类适配:创建新类,继承源类,并实现新接口,例如
class adapter extends oldClass implements newFunc{}
-
对象适配:创建新类持源类的实例,并实现新接口,例如
class adapter implements newFunc { private oldClass oldInstance ;}
-
接口适配:创建新的抽象类实现旧接口方法。例如
abstract class adapter implements oldClassFunc { void newFunc();}
7.装饰模式(Decorator)
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构 。例如:
interface Source{ void method();}
public class Decorator implements Source{
private Source source ;
public void decotate1(){
System.out.println("decorate");
}
@Override
public void method() {
decotate1();
source.method();
}
}
8.代理模式(Proxy)
客户端通过代理类访问,代理类实现具体的实现细节,客户只需要使用代理类即可实现操作。
这种模式可以对旧功能进行代理,用一个代理类调用原有的方法,且对产生的结果进行控制。
interface Source{ void method();}
class OldClass implements Source{
@Override
public void method() {
}
}
class Proxy implements Source{
private Source source = new OldClass();
void doSomething(){}
@Override
public void method() {
new Class1().Func1();
source.method();
new Class2().Func2();
doSomething();
}
}
9.外观模式(Facade)
为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使得这一子系统更加容易使用。这句话是百度百科的解释,有点难懂,但是没事,看下面的例子,我们在启动停止所有子系统的时候,为它们设计一个外观类,这样就可以实现统一的接口,这样即使有新增的子系统subSystem4,也可以在不修改客户端代码的情况下轻松完成。
public class Facade {
private subSystem1 subSystem1 = new subSystem1();
private subSystem2 subSystem2 = new subSystem2();
private subSystem3 subSystem3 = new subSystem3();
public void startSystem(){
subSystem1.start();
subSystem2.start();
subSystem3.start();
}
public void stopSystem(){
subSystem1.stop();
subSystem2.stop();
subSystem3.stop();
}
}
10.桥接模式(Bridge)
Circle类将DrwaApi与Shape类进行了桥接,代码:
interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}
class RedCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
class GreenCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}
class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
//客户端使用代码
Shape redCircle = new Circle(100,100, 10, new RedCircle());
Shape greenCircle = new Circle(100,100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();
11.组合模式(Composite)
组合模式是为了表示那些层次结构,同时部分和整体也可能是一样的结构,常见的如文件夹或者树。举例:
abstract class component{}
class File extends component{ String filename;}
class Folder extends component{
component[] files ; //既可以放文件File类,也可以放文件夹Folder类。Folder类下又有子文件或子文件夹。
String foldername ;
public Folder(component[] source){ files = source ;}
public void scan(){
for ( component f:files){
if ( f instanceof File){
System.out.println("File "+((File) f).filename);
}else if(f instanceof Folder){
Folder e = (Folder)f ;
System.out.println("Folder "+e.foldername);
e.scan();
}
}
}
}
12.享元模式(Flyweight)
使用共享对象的方法,用来尽可能减少内存使用量以及分享资讯。通常使用工厂类辅助,例子中使用一个HashMap类进行辅助判断,数据池中是否已经有了目标实例,如果有,则直接返回,不需要多次创建重复实例。
abstract class flywei{ }
public class Flyweight extends flywei{
Object obj ;
public Flyweight(Object obj){
this.obj = obj;
}
}
class FlyweightFactory{
private HashMap<Object,Flyweight> data;
public FlyweightFactory(){ data = new HashMap<>();}
public Flyweight getFlyweight(Object object){
if ( data.containsKey(object)){
return data.get(object);
}else {
Flyweight flyweight = new Flyweight(object);
data.put(object,flyweight);
return flyweight;
}
}
}