参考:
面试题 / J2SE / J2SE JAVA 面试题
Java面试2018常考题目汇总(一)
多线程,并发及线程基础
数据类型转换的基本原则
垃圾回收(GC)
Java 集合框架
数组
字符串
GOF 设计模式
SOLID (单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)设计原则
抽象类与接口
Java 基础,如 equals 和 hashcode
泛型与枚举
Java IO 与 NIO
常用网络协议
Java 中的数据结构和算法
正则表达式
JVM 底层
Java 最佳实践
JDBC
Date, Time 与 Calendar
Java 处理 XML
JUnit
编程
在外部循环的前一行,加上标签
在break的时候使用该标签
即能达到结束多重嵌套循环的效果
public class HelloWorld {
public static void main(String[] args) {
//打印单数
outloop: //outloop这个标示是可以自定义的比如outloop1,ol2,out5
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
System.out.println(i+":"+j);
if(0==j%2)
break outloop; //如果是双数,结束外部循环
}
}
}
}
在Object类的equals()方法中有这么一段话
- Note that it is generally necessary to override the {@code hashCode}
翻译如下:
通常来讲,在重写这个方法的时候,也需要对hashCode方法进行重写,
以此来保证这两个方法的一致性——
当equals返回true的时候,这两个对象一定有相同的hashcode.
因为hashCode()方法和equals()方法都可以通过自定义类重写,是可以做到equals相同,但是hashCode不同的
hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。
根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。
HashMap使用Key对象的hashCode()和equals()方法去决定key-value对的索引。当我们试着从HashMap中获取值的时候,这些方法也会被用到。如果这些方法没有被正确地实现,在这种情况下,两个不同Key也许会产生相同的hashCode()和equals()输出,HashMap将会认为它们是相同的,然后覆盖它们,而非把它们存储到不同的地方。同样的,所有不允许存储重复数据的集合类都使用hashCode()和equals()去查找重复,所以正确实现它们非常重要。equals()和hashCode()的实现应该遵循以下规则:
如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。
该接口只定义了一个方法compareTo(O o)
引自jdk api:
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与 e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。注意,null 不是任何类的实例,即使 e.equals(null) 返回 false,e.compareTo(null) 也将抛出 NullPointerException。
compareTo(O o)比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
Comparable是排序接口。若一个类实现了Comparable接口,就意味着该类支持排序。实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
此外,实现此接口的对象可以用作有序映射中的键或有序集合中的集合,无需指定比较器。
public class Person implements Comparable<Person>
{
String name;
int age;
public Person(String name, int age)
{
super();
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
@Override
public int compareTo(Person p)
{
return this.age-p.getAge();
}
public static void main(String[] args)
{
Person[] people=new Person[]{new Person("xujian", 20),new Person("xiewei", 10)};
System.out.println("排序前");
for (Person person : people)
{
System.out.print(person.getName()+":"+person.getAge());
}
Arrays.sort(people);
System.out.println("\n排序后");
for (Person person : people)
{
System.out.print(person.getName()+":"+person.getAge());
}
}
}
结果
排序前
xujian:20xiewei:10
排序后
xiewei:10xujian:20
该接口定义了两个个方法:int compare(T o1, T o2)和equals
比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
引自jdk api:
强行对某个对象 collection 进行整体排序 的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
当且仅当对于一组元素 S 中的每个 e1 和 e2 而言,c.compare(e1, e2)==0 与 e1.equals(e2) 具有相等的布尔值时,Comparator c 强行对 S 进行的排序才叫做与 equals 一致 的排序。
Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。也就是说,我们可以通过实现Comparator来新建一个比较器,然后通过这个比较器对类进行排序。
注意:
1、若一个类要实现Comparator接口:它一定要实现compare(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。
2、int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。
现在假如上面的Person类没有实现Comparable接口,该如何比较大小呢?我们可以新建一个类,让其实现Comparator接口,从而构造一个“比较器"。
public class PersonCompartor implements Comparator<Person>
{
@Override
public int compare(Person o1, Person o2)
{
return o1.getAge()-o2.getAge();
}
}
public class Person
{
String name;
int age;
public Person(String name, int age)
{
super();
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public static void main(String[] args)
{
Person[] people=new Person[]{new Person("xujian", 20),new Person("xiewei", 10)};
System.out.println("排序前");
for (Person person : people)
{
System.out.print(person.getName()+":"+person.getAge());
}
Arrays.sort(people,new PersonCompartor());
System.out.println("\n排序后");
for (Person person : people)
{
System.out.print(person.getName()+":"+person.getAge());
}
}
}
Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。
Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。
用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。
false。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4 或float f = 3.4f
在java里面,没小数点的默认是int,有小数点的默认是 double;
3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F;。
0.30000000000000004
3*0.1 == 0.3 将会返回什么?true 还是 false?
false,因为有些浮点数不能完全精确的表示出来。 浮点数精度默认为6位
char是16位的,占两个字节
汉字通常使用GBK或者UNICODE编码,也是使用两个字节
所以可以存放汉字
Bit是最小的传输单位,byte是最小的存储单位,1byte=8bit,char 是一种基本数据类型,1char=2byte.
在java1.7之前,Java 虚拟机和字节代码这个层次上,只支持在 switch 语句中使用与整数类型兼容的类型,也就是byte,short,int,char以及Enum(枚举) 。但是不能作用在long和String上面
java1.7之后,switch可以作用在 String上。具体实现参看:
java7新特性之switch字符串比较原理
java基础(六) switch语句的深入解析
注:switch作用在String上从JDK1.7开始支持,实质是编译时将字符串替换为了其对应的hash值
参考:JAVASchool 反射
关于反射的相关定义和问题
完全不相关的。反射是一个机制,映射是一种关系。
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制。
映射是一种对应关系,在很多的情况下,表示一种存在的联系而已。
getField(String name) :返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
getFields() :返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
getDeclaredField(String name) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
getDeclaredFields() :返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段,包括公共、保护、默认(包)访问和私有字段,但不包括继承的字段。
getMethod(String name, Class>… parameterTypes) :它反映此 Class 对象所表示的类或接口的指定公共成员方法。
getMethods() :返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口**(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法**。数组类返回从 Object 类继承的所有(公共)member 方法。返回数组中的元素没有排序,也没有任何特定的顺序。如果此 Class 对象表示没有公共成员方法的类或接口,或者表示一个基本类型或 void,则此方法返回长度为 0 的数组
getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法。
getDeclaredMethod(String name,Class>… parameterTypes):返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
getSuperclass() :返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
newInstance() :创建此 Class 对象所表示的类的一个新实例。
getTypeParameters():按声明顺序返回 TypeVariable 对象的一个数组,这些对象表示用此 GenericDeclaration 对象所表示的常规声明来声明的类型变量。如果底层常规声明不声明类型变量,则返回长度为 0 的数组。
Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
public interface ParameterizedTypeextends Type
ParameterizedType 表示参数化类型,如 Collection。 这个接口貌似和泛型有关,但还不是很清楚,标记一下
测试代码
public static void main(String[] args) {
O cpaf = new O();
//getFields只能获取可访问公共字段
Field[] fs = O.class.getFields();
System.out.println(fs.length);//0
//所有字段,包括公共、保护、默认(包)访问和私有字段,但*不包括继承的字段
Field[] dfs = cpaf.getClass().getDeclaredFields();
System.out.println(dfs.length);//2
for (Field field : dfs) {
System.out.println(field);
}
//(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的*公共 member 方法
Method[] ms = cpaf.getClass().getMethods();
System.out.println(ms.length);//13,因为还有wait、equals等超类和接口的方法
for (Method method : ms) {
System.out.println(method);
}
//本类中的所有方法,包括私有的(private、protected、默认以及public)的方法
Method[] dms = cpaf.getClass().getDeclaredMethods();
System.out.println(dms.length);//4
for (Method method : dms) {
System.out.println(method);
}
System.out.println(O.class.getSuperclass());//class java.lang.Object
try {
O o1 = O.class.newInstance();
//下边的例子可以看出getType和getGenericType的差别
Field agef = o1.getClass().getDeclaredField("age");
Type t = agef.getGenericType();
System.out.println(t);//int
Class c = agef.getType();
System.out.println(c);//int
Field listf = o1.getClass().getDeclaredField("list");
Type listt = listf.getGenericType();
System.out.println(listt);//java.util.List
Class listc = listf.getType();
System.out.println(listc);//interface java.util.List
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
O的结构
package test;
import java.util.List;
class T extends Thread{
}
public class O {
private String name;
private int age;
private List<String> list;
private List<T> ts;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
下边是项目中用到的关于Type的代码:
private Object r(String jsonStr,MethodParameter methodParameter,
String paramName,WebDataBinder binder,Class clazz) throws Exception {
if (StringUtils.isEmpty(jsonStr)) {
return null;
}
JSONObject obj = JSONObject.fromObject(jsonStr);
////字符串,直接返回
if (String.class == clazz) {
return obj.get(paramName);
}
//List,转换成JSONArray
if (List.class == clazz) {
JSONArray array = (JSONArray) obj.get(paramName);
if (array.size() < 1) {
return null;
}
Type genericType = methodParameter.getGenericParameterType();
if (genericType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericType;
//得到泛型里的class类型对象
Class<?> genericClazz = (Class<?>) pt.getActualTypeArguments()[0];
List list = new ArrayList();
for (int i = 0, size = array.size(); i < size; i++) {
list.add(getEntity(genericClazz, array.getJSONObject(i), binder, methodParameter));
}
return list;
}
throw new Exception("暂时没有处理的类型");
}
//目前其他类型暂时按照POJO处理,如果还有,再扩展
JSONObject paramObj = (JSONObject) obj.get(paramName);
return getEntity(clazz, paramObj, binder, methodParameter);
}
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
Modifier
类对这些修饰符进行解码。其中,该修饰符是java.lang.reflect.Modifier的静态属性。
Modifier说明参见jdk 1.6 api
对应表如下:
PUBLIC: 1
PRIVATE: 2
PROTECTED: 4
STATIC: 8
FINAL: 16
SYNCHRONIZED: 32
VOLATILE: 64
TRANSIENT: 128
NATIVE: 256
INTERFACE: 512
ABSTRACT: 1024
STRICT: 2048
这个起源自网络上的一个示例
Field[] fields = bean.getClass().getDeclaredFields();
for(Field f : fields){
f.setAccessible(true);
if(f.getType() == java.util.List.class){
// 如果是List类型,得到其Generic的类型
Type genericType = f.getGenericType();
if(genericType == null) continue;
// 如果是泛型参数的类型
if(genericType instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType) genericType;
//得到泛型里的class类型对象
Class<?> genericClazz = (Class<?>)pt.getActualTypeArguments()[0];
}
}
}
class Back{
private void t(Object object,String methodStr,Object[] args){
try{
Class[] classes = new Class[args.length];
for (int i = 0 ;i<args.length;i++) {
Class c = args[i].getClass();
classes[i] = c == TsRequest.class ? HttpServletRequest.class : c;
}
//因为controller都被spring包装过了,所以使用jdk本身方法可能无效?
Class clazz = AopUtils.getTargetClass(object);
Method method = clazz.getDeclaredMethod(methodStr,classes);
List<Map<String, String>> list = (List<Map<String, String>>) method.invoke(object,args);
if (!CollectionUtils.isEmpty(list)) {
listBacklog.addAll(list);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Back back = new Back();
//因为每个角色都有菜单,所以这里暂时不做空指针处理。
for (SysMenu menu : sysMenuMapper.getMenuByRoleId(role.getRoleId())) {
if (StringUtils.isEmpty(menu.getUrl())) {
continue;
}
String url = menu.getUrl();
//这里以及下边使用Back方法时注意按照参数顺序写数组元素
Object[] args = new Object[]{request, menu};
// 注师基本信息变更审批
if (url.startsWith(Constans.CPAMS_BACKLOG_URL_11)) {
back.t(cpaAcctBchgController, "getCpaAcctBchgListForBacklog", args);
}
volatile
公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。
实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。
使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。
从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。
注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。
元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。
而且元素不能使用null作为默认值。
注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。
注解的语法比较简单。Java内置了一些注解
Java还提供了4中注解,专门负责新注解的创建。
表示该注解可以用于什么地方,可能的ElementType参数有:
表示需要在什么级别保存该注解信息。
可选的RetentionPolicy参数包括:
将注解包含在Javadoc中
允许子类继承父类中的注解
import java.lang.annotation.*;
/**
* 用来表示Api*Controller中参数是否需要特殊处理
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonObject {}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
泛型和枚举都是JDK1.5版本之后加入的新特性,泛型将程序代码的类型检查提前到了编译期间进行,枚举类型增强了程序代码的健壮性。
枚举是一个类类型,是JDK1.5的新特性。枚举的关键字是enum。Java中所有的枚举类都是java.lang.Enum的子类
注意:枚举类中可以包含成员有【字段(常量)、方法(构造方法、普通方法)】
// 枚举定义
public enum Color {
RED, GREEN, BLANK, YELLOW
}
// 枚举值的使用
static boolean isRed( Color color ){
if ( Color.RED.equals( color )) {
return true ;
}
return false ;
}
// 枚举使用
public static void main(String[] args) {
System.out.println( isRed( Color.BLANK ) ) ; //结果: false
System.out.println( isRed( Color.RED ) ) ; //结果: true
}
JDK1.5之后的switch语句支持Byte,short,int,char,enum类型,使用枚举,能让我们的代码可读性更强,JDK1.7之后开始支持String类型
要自定义方法,必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum实例
public enum Color {
//因为enum的构造方法是私有的,因此,这里相当于new出来多个enum常量
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法:注意**private**
private Color(String name, int index) {
this.name = name;
this.index = index;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return this.index+"_"+this.name;
}
// 普通方法
public static void main(String[] args) {
// 输出某一枚举的值
System.out.println(Color.RED.getName());
System.out.println(Color.RED.getIndex());
// 遍历所有的枚举,enum默认有一个values方法
for (Color color : Color.values()) {
System.out.println(color + " name: " + color.getName() + " index: " + color.getIndex());
}
}
}
红色
1
RED name: 红色 index: 1
GREEN name: 绿色 index: 2
BLANK name: 白色 index: 3
YELLO name: 黄色 index: 4
例如上例中覆盖toString()
所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类,例:
interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 接口方法
@Override
public String getInfo() {
return this.name;
}
@Override
public void print() {
System.out.println(this.index + ":" + this.name);
}
}
public interface Food {
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO
}
enum Dessert implements Food {
FRUIT, CAKE, GELATO
}
}
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的key是enum类型,而value则可以是任意类型。
泛型(Generic)的本质是类型参数化,也就是说所操作的数据类型被指定为一个参数。泛型将程序代码的类型检查提前到了编译期间(只是允许程序员在编译时检测到非法的类型而已,但是在运行期时,其中的泛型标志会变化为 Object 类型。)
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
public class GenericTest {
public static <E, T, K> E ret(T param1,K p2){
E p = (E)param1;
return p;
}
//类型参数声明部分(T和E代表的是两种不同的类型),在方法返回类型void之前
public static <T, E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
}
public static void main(String[] args) {
System.out.println(ret(1,""));
System.out.println(ret("str",""));
System.out.println(ret(new GenericTest(),""));
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println("整型数组元素为:");
printArray(intArray); // 传递一个整型数组
System.out.println("\n双精度型数组元素为:");
printArray(doubleArray); // 传递一个双精度型数组
System.out.println("\n字符型数组元素为:");
printArray(charArray); // 传递一个字符型数组
}
}
可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围&。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。
extends T>和 super T>的区别
public class GenericTest {
public static <T extends Integer, K extends Double> double max(T p1,K p2){
//T p = (T)p2;编译错误:不能cast K to T
return p2 > p1 ? p2 : p1;
}
public static void main(String[] args) {
System.out.println(max(5,3D));
}
}
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
public class GenericClass<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
GenericClass<Integer> integer = new GenericClass<Integer>();
GenericClass<String> string = new GenericClass<String>();
integer.add(new Integer(10));
string.add(new String("菜鸟教程"));
System.out.printf("整型值为 :%d\n\n", integer.get());
System.out.printf("字符串为 :%s\n", string.get());
}
}
如List、Set等接口
1、类型通配符一般是使用?代替具体的类型参数。例如 List> 在逻辑上是List,List 等所有List<具体类型实参>的父类。
2、类型通配符上限通过形如List extends Number>来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。
3、类型通配符下限通过形如 List super Number>来定义,表示类型只能接受Number及其三层父类类型,如Objec类型的实例。
import java.util.*;
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
// getUperNumber(name);//1错误,因为getUperNumber()方法中的参数已经限定了参数泛型上限为Number,所以泛型为String是不在这个范围之内,所以会报错
getUperNumber(age);// 2
getUperNumber(number);// 3
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
public static void getUperNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
}
可以包括多个类,但是只能出现一个public修饰的类,但是可以出现多个非public修饰的类,这些类编译后,为独立的class文件。
参考:Java内部类详解
匿名内部类 :匿名内部类则被编译为类名+$+数字.class
可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。
成员内部类是最普通的内部类,它的定义为位于另一个类的内部
如下,类P像是类Test的一个成员,Test称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
public class Test {
private int count = 0;
class P {
public void out() {
System.out.println(count);
}
}
}
不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
public class Test {
private int count = 0;
P p = new P();
public void outter() {
//外部类想要访问内部类成员,需要先对象化
System.out.println(p.pCount);
}
class P {
//内部类可以无条件访问外部类的成员
public int pCount = 1;
public void inner() {
System.out.println(count);
}
}
}
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
public class Test {
public static void main(String[] args) {
// 第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); // 必须通过Outter对象来创建,使用new Outter.Inner()报错,因为Inner很明显不是Outter的成员
// 第二种方式:这只是一种变种而已,让外部类持有内部类实例供外部访问
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {}
public Inner getInnerInstance() {
if (inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {}
}
}
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。
这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。
局部内部类是 定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
注意,局部内部类就像是方法里面的一个局部变量一样,是 不能有public、protected、private以及static修饰符的。
class People {
public People() {}
}
class Man {
public Man() {}
public People getWoman() {
class Woman extends People { // 局部内部类
int age = 0;
}
return new Woman();
}
}
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同。
private void setListener()
{
scan_bt.setOnClickListener(new Listener1());
history_bt.setOnClickListener(new Listener2());
}
class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {}
}
class Listener2 implements View.OnClickListener{
@Override
public void onClick(View v) {}
}
这种写法虽然能达到一样的效果,但是既冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。同样的,匿名内部类也是不能有访问修饰符和static修饰符的。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,文件名称为·外部类$内部类.class·
1 使用abstract
修饰的class
2 如果一个class
中包含abstract
方法,那么这个类必须为抽象类;
3 因为抽象类(可能)包含抽象方法,所以抽象类不能被实例化;
4 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
5 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
抽象类是子类通用特性的抽象,不能被实例化,只能用作超类。除了不能实例化之外,和普通的Java类没有区别。
接口是一种“契约模式”
1 接口中 可以含有变量和方法。
2 接口中的 变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误)
3 而 方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
Cloneable和Serializable一样都是 标记型接口,它们内部都没有方法和属性,implements Cloneable表示该对象能被克隆,能使用Object.clone()方法。如果没有implements Cloneable的类调用Object.clone()方法就会抛出CloneNotSupportedException。
克隆的分类
要让一个对象进行克隆,其实就是两个步骤:
继承是一个 "是不是(is a)"的关系,而 接口 实现则是 "有没有(has a)"的关系
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。
abstract的method是否可同时是static,是否可同时是synchronized?都不可以
参数 | 抽象类 | 接口 |
---|---|---|
默认的方法实现 | 它可以有默认的方法实现 接口完全是抽象的。 | 它根本不存在方法的实现 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与正常Java类的区别 | 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 | 接口是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符。 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它。 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
如果 子类需要一些默认的实现,那么必须使用抽象类
如果 需要实现多重继承,必须使用接口
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是 父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
重写是 子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
构造方法不能被重写。
当需要在子类中调用父类的被重写方法时,要使用super关键字。
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
在作为參数传递之前,我们能够使用Collections.unmodifiableCollection(Collection c)方法创建一个仅仅读集合,这将确保改变集合的不论什么操作都会抛出UnsupportedOperationException。
常用api
Map的迭代方法有两种
当我们需要一个同步的HashMap时,有两种选择:
Map m = Collections.synchronizedMap(new HashMap());
...
Set s = m.keySet(); // Needn't be in synchronized block
...
synchronized(m) { // **Synchronizing on m, not s!**
Iterator i = s.iterator(); // **Must be in synchronized block**
while (i.hasNext())
foo(i.next());
}
这两个选项之间的首选是使用ConcurrentHashMap,这是因为我们不需要锁定整个对象,以及通过ConcurrentHashMap分区地图来获得锁。
HashMap是最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。因为键对象不可以重复,所以HashMap最多只允许一条记录的键为Null,允许多条记录的值为Null,是非同步的
Hashtable与HashMap类似,是HashMap的线程安全版,它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,它继承自Dictionary类,不同的是它不允许记录的键或者值为null,同时效率较低。
线程安全,并且锁分离。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
LinkedHashMap 保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。不允许key值为空,非同步的;
一般现在不建议用HashTable,
①是HashTable是遗留类,内部实现很多没优化和冗余。
②即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。
两个map中的元素一样,但顺序不一样,导致hashCode()不一样。
同样做测试:
在HashMap中,同样的值的map,顺序不同,equals时,false;
而在treeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。
HashMap<String, String> map = new HashMap<>();
map.put(null, null);
List 和 Set 继承了Collection接口
但是Map和Collection之间没有继承关系,因为一个是键值对容器,一个是单值容器,无法兼容
总结: 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList性能比较好;当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。相比较于ArrayList它的写操作要慢一些,因为它需要实例的快照。
CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的"="将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合在多线程里使用,绝对不会发生ConcurrentModificationException ,因此CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。
以HashSet为例,判断重复的逻辑是:
String[] staffs = new String[]{"Tom", "Bob", "Jane"};
List staffsList = Arrays.asList(staffs);
需要注意的是, Arrays.asList() 返回一个受指定数组决定的固定大小的列表。所以不能做 add 、 remove 等操作,否则会报错。
List staffsList = Arrays.asList(staffs);
staffsList.add("Mary"); // UnsupportedOperationException
staffsList.remove(0); // UnsupportedOperationException
如果想再做增删操作呢?将数组中的元素一个一个添加到列表,这样列表的长度就不固定了,可以进行增删操作。
List staffsList = new ArrayList<String>();
for(String temp: staffs){
staffsList.add(temp);
}
staffsList.add("Mary"); // ok
staffsList.remove(0); // ok
String[] staffs = new String[]{"Tom", "Bob", "Jane"};
Set<String> staffsSet = new HashSet<>(Arrays.asList(staffs));
staffsSet.add("Mary"); // ok
staffsSet.remove("Tom"); // ok
String[] staffs = new String[]{"Tom", "Bob", "Jane"};
List staffsList = Arrays.asList(staffs);
Object[] result = staffsList.toArray();
String[] staffs = new String[]{"Tom", "Bob", "Jane"};
List staffsList = Arrays.asList(staffs);
Set result = new HashSet(staffsList);
String[] staffs = new String[]{"Tom", "Bob", "Jane"};
Set<String> staffsSet = new HashSet<>(Arrays.asList(staffs));
Object[] result = staffsSet.toArray();
String[] staffs = new String[]{"Tom", "Bob", "Jane"};
Set<String> staffsSet = new HashSet<>(Arrays.asList(staffs));
List<String> result = new ArrayList<>(staffsSet);
Iterator,所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:
Map<String,String> params = new HashMap<String,String>();
Set<Entry<String,String>> set = params.entrySet();
Iterator<Entry<String, String>> it = params.entrySet().iterator();
while(it.hasNext()){
Entry<String,String> entry = it.next();
}
List<String> list = new ArrayList<String>();
Iterator<String> it1 = list.iterator();
//ListIterator专门用来输出List的内容
ListIterator<String> it2 = list.listIterator();
while(it2.hasNext()){
// it2.previous()
// it2.hasPrevious();
// it2.next()
}
ListIterator针对List的迭代做了一些改造,因此,当操作List时,比起Iterator功能更多
注意:通过ListIterator添加的元素,在本次迭代过程中不会出现
String[] staffs = new String[] { "Tom", "Bob", "Jane" };
List staffsList = Arrays.asList(staffs);
List l = new ArrayList();
l.addAll(staffsList);
ListIterator<String> li = l.listIterator();
int i = 0;
while (li.hasNext()) {
Object o = li.next();
System.out.print(o + ",");//Tom,Bob,Jane,
li.add("" + i++);
}
System.out.println();
for (Object object : l) {
System.out.print(object + ",");//Tom,0,Bob,1,Jane,2,
}
①性能方面
Collection的remove方法必须首先找出要被删除的项,找到该项的位置采用的是单链表结构查询,单链表查询效率比较低,需要从集合中一个一个遍历才能找到该对象;
Iterator的remove方法结合next()方法使用,比如集合中每隔一项删除一项,Iterator的remove()效率更高
②容错方面
在使用Iterator遍历时,如果使用Collection的remove则会报异常,会出现ConcurrentModificationException,因为集合中对象的个数会改变而Iterator 内部对象的个数不会,不一致则会出现该异常
在使用Iterator遍历时,不会报错,因为iterator内部的对象个数和原来集合中对象的个数会保持一致
如果面试官问这个问题,那么他的意图一定是让你区分Iterator不同于Enumeration的两个方面:
不能对同一线程对象两次调用start()方法。
Java线程具有五中基本状态
future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态
ExecutorService e = Executors.newFixedThreadPool(3);
//submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 无阻塞
future.get() // return 返回值,阻塞直到该线程运行结束
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadUtil {
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadStyle1(),"第一方式");
t1.start();
Thread t2 = new Thread(new ThreadStyle2(),"第二方式");
t2.start();
Thread t3 = new Thread() {
@Override
public void run() {
System.out.println("实现多线程的第三种方式,其实是继承自Thread的变种!");
super.run();
}
};
t3.start();
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("实现多线程的第四种方式,其实是实现Runnable的变种!");
}
});
t4.start();
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
ThreadStyle3 t5 = new ThreadStyle3();
FutureTask<String> result = new FutureTask<String>(t5);
new Thread(result).start();
try {
//2.接收线程运算后的结果
String sum = result.get();
//FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的
System.out.println(sum);
System.out.println(result.isDone);//true
System.out.println("------------------------------------");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//继承自Thread,重写run
class ThreadStyle1 extends Thread{
@Override
public void run() {
System.out.println("实现多线程的第一种方法:继承Thread");
super.run();
}
}
//实现Runnable的run
class ThreadStyle2 implements Runnable{
@Override
public void run() {
System.out.println("实现多线程的第二种方法:实现接口Runnable");
}
}
//方式三:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
class ThreadStyle3 implements Callable<String>{
//返回类型Object就是Callable<>中指定的泛型
@Override
public String call() throws Exception {
return "实现多线程的第四种方法:实现接口Callable的call方法,和FutureTask搭配使用。比较Runnable,Callable可以有返回值";
}
}
Executors 类提供了用于此包中所提供的执行程序服务的 工厂方法。
线程池的创建入口 Executors,真正干活的是 ExecutorService
java.util.concurrent.Executors 工厂类可以创建四种类型的线程池,通过 Executors.newXXX 方法即可创建
可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。shutdown() 方法在终止前允许执行以前提交的任务,而 shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的 ExecutorService 以允许回收其资源。
创建固定大小的线程池。每次提交一个任务,就会启一个线程,直到线程池的线程数量达到线程池的上限。
创建一个可缓存的线程池。每次提交一个任务,委派给线程池空闲的线程处理, 如果木有空闲的线程, 则直接创建新线程,任务被执行完后,当前线程加入到线程池维护。其生命周期超过一定时间会被销毁回收。
创建只有一个线程的线程池。问题来了, 一个线程的线程池和普通创建一个线程一样么?当然不一样.线程销毁问题。
创建一个大小不受限的线程池。提供定时、周期地执行任务能力。
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
所有超级接口:
Executor, ExecutorService
一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
schedule 方法使用各种延迟创建任务,并返回一个可用于取消或检查执行的任务对象。
scheduleAtFixedRate(不管有没有完成,将执行时间算在延迟时间内) 和 scheduleWithFixedDelay(执行完成后一定时间) 方法创建并执行某些在取消前一直定期运行的任务。
方法摘要
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MyThreadPool {
public static void main(String[] args) {
// 固定大小线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
executorService.shutdown();
// 缓存线程池,不固定大小,当线程池中没有空闲线程时就创建新的线程
ExecutorService executorService2 = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService2.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
executorService2.shutdown();
// 单一线程池,和单线程的区别在于线程销毁于创建
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executorService3.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
executorService3.shutdown();
// 调度线程池,添加了定时任务调度等功能
// 正常调度
ExecutorService executorService4 = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 5; i++) {
executorService4.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
executorService4.shutdown();
// 周期性调度
// ScheduleAtFixedRate是基于固定时间间隔进行任务调度,
// ScheduleWithFixedDelay取决于每次任务执行的时间长短,是基于不固定时间间隔进行任务调度
ScheduledExecutorService executorService5 = Executors.newScheduledThreadPool(2);
long initialDelay = 1, delay = 1;
// 应用启动1S后,每隔1S执行一次
executorService5.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, initialDelay, delay, TimeUnit.SECONDS);
// 应用启动1S后,每隔2S执行一次
executorService5.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, initialDelay, delay, TimeUnit.SECONDS);
}
}
Jdk提供了四种拒绝策略。
FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。
通过FutrueTask.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。
synchronized关键字的作用域有二种:
synchronized static aStaticMethod{}
防止多个线程同时访问这个类中的synchronized static
方法。它可以对类的所有对象实例起作用。synchronized static aStaticMethod{}
防止多个线程同时访问这个类中的synchronized static
方法。它可以对类的所有对象实例起作用同步方法,这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法.同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。
下边是引用jdk api中的文字
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
例如,以下类生成对每个线程唯一的局部标识符。线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,在后续调用中不会更改。
import java.util.concurrent.atomic.AtomicInteger;
public class UniqueThreadIdGenerator {
private static final AtomicInteger uniqueId = new AtomicInteger(0);
private static final ThreadLocal < Integer > uniqueNum =
new ThreadLocal < Integer > () {
@Override protected Integer initialValue() {
return uniqueId.getAndIncrement();
}
};
public static int getCurrentThreadId() {
return uniqueId.get();
}
}
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
该类有以下四个方法
public class ThreadLocalTest {
ThreadLocal<Long> ids = new ThreadLocal<Long>();
ThreadLocal<String> names = new ThreadLocal<String>();
public void setInfo() {
ids.set(Thread.currentThread().getId());
names.set(Thread.currentThread().getName());
}
public Long getId() {
return ids.get();
}
public String getName() {
return names.get();
}
public static void main(String[] args) throws InterruptedException {
final ThreadLocalTest test = new ThreadLocalTest();
test.setInfo();
System.out.println(test.getId()+","+test.getName());
Thread t = new Thread(new Runnable() {
public void run() {
test.setInfo();
System.out.println(test.getId()+","+test.getName());
}
});
t.start();
t.join();
System.out.println(test.getId()+","+test.getName());
}
}
首先sleep和wait之间没有任何关系
sleep 是Thread类的方法,指的是当前线程暂停。
wait 是Object类的方法, 指的占用当前对象的线程临时释放对当前对象的占用,以使得其他线程有机会占用当前对象。 所以调用wait方法一定是在synchronized 中进行
wait和notify这两个方法是一对,wait方法阻塞当前线程,而notify是唤醒被wait方法阻塞的线程。
wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出IllegalMonitorStateException异常,所以这两个方法必须在同步块代码里面调用,经典的生产者和消费者模型就是使用这两个方法实现的。
1、java.util.Date
类 Date 表示特定的瞬间,精确到毫秒。从 JDK 1.1 开始,应该使用 Calendar 类实现日期和时间字段之间转换,使用 DateFormat 类来格式化和分析日期字符串。Date 中的把日期解释为年、月、日、小时、分钟和秒值的方法已废弃。
2、java.text.DateFormat(抽象类)
DateFormat 是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并分析日期或时间。日期/时间格式化子类(如 SimpleDateFormat)允许进行格式化(也就是日期 -> 文本)、分析(文本-> 日期)和标准化。将日期表示为 Date 对象,或者表示为从 GMT(格林尼治标准时间)1970 年,1 月 1 日 00:00:00 这一刻开始的毫秒数。
3、java.text.SimpleDateFormat(DateFormat的直接子类)
SimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。
SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。但是,仍然建议通过 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance 来新的创建日期-时间格式化程序。
4、java.util.Calendar(抽象类)
Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。
与其他语言环境敏感类一样,Calendar 提供了一个类方法 getInstance,以获得此类型的一个通用的对象。Calendar 的 getInstance 方法返回一个 Calendar 对象,其日历字段已由当前日期和时间初始化。
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class DateUtil {
private static void get() {
Calendar calendar = Calendar.getInstance();
//当前日期加150天
calendar.add(Calendar.DATE, 150);
//calendar.getTime获取Date
Date date = calendar.getTime();
//SimpleDateFormat是DateFormat的直接子类
DateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String dateStr = sdf.format(date);
System.out.println(dateStr);//2019年01月27日 13:16:40
try {
//字符串转日期
Date dateNow = sdf.parse("2018年8月31日 13:21:12");
Calendar calendar1 = Calendar.getInstance();
//calendar.setTime设置Date
calendar1.setTime(dateNow);
//after、before日期比较
System.out.println(calendar.after(calendar1));//true
System.out.println(calendar.before(calendar1));//false
//getTimeInMillis获取毫秒数
long f1 = calendar.getTimeInMillis(),f2 = calendar1.getTimeInMillis();
System.out.println((f1-f2)/1000/60/60/24);
} catch (ParseException e) {
e.printStackTrace();
}
Calendar calendar2 = Calendar.getInstance();
calendar2.set(Calendar.YEAR, 2012);
calendar2.set(Calendar.MONTH,0);//输入的数字超出范围,并不会报错,而是进行计算。如果没有设置DAY_OF_MONTH的话,则日期更离谱
calendar2.set(Calendar.DAY_OF_MONTH,2);
System.out.println(sdf.format(calendar2.getTime()));//2012年01月02日 13:37:54
//获取一个月有多少天
System.out.println(calendar2.getActualMaximum(Calendar.DATE));//31
}
public static void main(String[] args) {
get();
}
}
java.util
类 GregorianCalendar
java.lang.Object
java.util.Calendar
java.util.GregorianCalendar
isLeapYear
public boolean isLeapYear(int year)
确定给定的年份是否为闰年。如果给定的年份是闰年,则返回 true。要指定 BC 年份,必须给定 1 - 年份。例如,指定 -3 为 BC 4 年。
参数:
year - 给定的年份。
返回:
如果给定的年份为闰年,则返回 true;否则返回 false。
◇◇◇字节流——InputStream,OutputStream◇◇◇
“流”就像一根管道
字节流表示管道中只能通过“字节数据”
InputStream 字节输入流:从InputStream 派生出去可以创建“字节数据输入流对象”的所有类,如FileInputStream,BufferedInputStream(发动机),ByteArray+InputStream,Data+InputStream,Object+InputStream都继承了“读取字节read()”方法,都能读取磁盘文件
OutputStream字节输出流:从OutputStream派生出去可以创建“字节数据输出流对象”的所有类,如FileOutputStream,BufferedOutputStream(发动机),ByteArray+OutputStream,Data+OutputStream,Object+OutputStream都继承了“写入字节write()”方法,都能写入磁盘文件
◇◇◇字符流——Reader,Writer◇◇◇
“流”就像一根管道
字符流表示管道中只能通过“字符数据”
Reader 字符输入流:从Reader派生出去可以创建“字符数据输入流对象”的所有类,如FileReader,BufferedReader(发动机),CharArrayReader,String+Reader,InputStreamReader。都继承了“读取字符read()”方法,都能读取磁盘文件
Writer 字符输出流:从Writer派生出去可以创建“字符数据输出流对象”的所有类,如FileWriter,BufferedWriter(发动机),CharArrayWriter,StringWriter,OutputStreamWriter。都继承了“写入字符write()”方法,都能写入磁盘文件◎FileOutputStream 字节输出流写文件◎
字节数据:byte 定义的变量就是字节数据
FileOutputStream就是写“字节数据”到文件中的类(只能写入“字节数据”,如果不是“字节数据”就需要转换getBytes()方法转换成字节数据)
import java.io.FileOutputStream;
import java.io.IOException;
public class Maintest {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("G:\\a.txt");
byte word = 65; // byte定义的word变量是字节数据,可以直接写入
fos.write(word);
String str = "早上好啊"; // String定义的是字符数据,要用getBytes()转换成字节数据,再用byte定义变量接收
byte[] words = str.getBytes();
fos.write(words);
System.out.println("写入成功!");
fos.close();
} catch (IOException e) {
}
}
}
◎FileWriter 字符输出流写文件◎
FileWriter有write()方法,写入字符数据到输出流文件中但效率不高,一般只用于打开路径,而“用BufferedWriter来写入”,BufferedWriter bw=new BufferedWriter(fw)“缓冲高效写入字符输出流文件”的类
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Maintest {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("G:\\a.txt");
BufferedWriter bw = new BufferedWriter(fw);
String str = "写入的是字符,可以直接写入"; // FileWriter写入的是字符数据,就是不用getbytes()转换成字节数据了
bw.write(str);
System.out.println("写入成功");
bw.close();
} catch (IOException e) {
}
}
}
我的理解是:RandomAccessFile = File + OutputStream + InputStream + 指针位置操作
现有如下的一个需求,向已存在1G数据的txt文本里末尾追加一行文字,内容如下“Lucene是一款非常优秀的全文检索库”。可能大多数朋友会觉得这个需求很easy,说实话,确实easy,然后XXX君开始实现了,直接使用Java中的流读取了txt文本里原来所有的数据转成字符串后,然后拼接了“Lucene是一款非常优秀的全文检索库”,又写回文本里了,至此,大功告成。后来需求改了,向5G数据的txt文本里追加了,结果XXX君傻了,他内存只有4G,如果强制读取所有的数据并追加,会报内存溢出的异常。
其实上面的需求很简单,如果我们使用JAVA IO体系中的RandomAccessFile类来完成的话,可以实现零内存追加。
与普通的IO流相比,它最大的特别之处就是支持任意访问的方式,程序可以直接跳到任意地方来读写数据。
下面来看下RandomAccessFile类中比较重要的2个方法,其他的和普通IO类似
我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
/**
* 实现向指定位置 插入数据
*
* @param fileName
* 文件名
* @param points
* 指针位置
* @param insertContent
* 插入内容
**/
public static void insert(String fileName, long points, String insertContent) {
try {
File tmp = File.createTempFile("tmp", null);
tmp.deleteOnExit();// 在JVM退出时删除
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
// 创建一个临时文件夹来保存插入点后的数据
FileOutputStream tmpOut = new FileOutputStream(tmp);
FileInputStream tmpIn = new FileInputStream(tmp);
raf.seek(points);
/** 将插入点后的内容读入临时文件夹 **/
byte[] buff = new byte[1024];
// 用于保存临时读取的字节数
int hasRead = 0;
// 循环读取插入点后的内容
while ((hasRead = raf.read(buff)) > 0) {
// 将读取的数据写入临时文件中
tmpOut.write(buff, 0, hasRead);
}
// 插入需要指定添加的数据
raf.seek(points);// 返回原来的插入处
// 追加需要追加的内容
raf.write(insertContent.getBytes());
// 最后追加临时文件中的内容
while ((hasRead = tmpIn.read(buff)) > 0) {
raf.write(buff, 0, hasRead);
}
raf.close();
tmpOut.close();
tmpIn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void randomWrite(String path) {
try {
/** 以读写的方式建立一个RandomAccessFile对象 **/
RandomAccessFile raf = new RandomAccessFile(path, "rw");
// 将记录指针移动到文件最后
raf.seek(raf.length());
raf.write("我是追加的 \r\n".getBytes());
raf.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void t1() {
try {
/**
* model各个参数详解 r 代表以只读方式打开指定文件 rw 以读写方式打开指定文件 rws
* 读写方式打开,并对内容或元数据都同步写入底层存储设备 rwd 读写方式打开,对文件内容的更新同步更新至底层存储设备
**/
RandomAccessFile file = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\js.js", "r");
// 移动指针
file.seek(1000);
byte[] bs = new byte[1024];
// 用于保存实际读取的字节数
int hasRead = 0;
// 循环读取
while ((hasRead = file.read(bs)) > 0) {
// 打印读取的内容,并将字节转为字符串输入
System.out.println(new String(bs, 0, hasRead));
}
file.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
t1();
}
}
这两个流的作用在于:用IO流的方式来完成对字节数组的读写。
什么是内存虚拟文件或者内存映像文件?
将一块内存虚拟成一个硬盘上的文件,原来该写到硬盘文件上的内容会被写到这个内存中,原来改从一个硬盘文件上读取内容可以改为从内存中直接读取。(如果在程序运行过程中药产生过一些临时文件,就可以使用虚拟文件的方式来实现,不需要访问硬盘,而是直接访问内存)
ObjectInputStream能够让你从输入流中读取Java对象,而不需要每次读取一个字节。你可以把InputStream包装到ObjectInputStream中,然后就可以从中读取对象了
ObjectOutputStream能够让你把对象写入到输出流中,而不需要每次写入一个字节。你可以把OutputStream包装到ObjectOutputStream中,然后就可以把对象写入到该输出流中了
DataInputStream 是数据输入流。它继承于FilterInputStream。
DataInputStream 是用来装饰其它输入流,它 “允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。
这两个类分别是FilterInputStream和FilterOutputStream的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。
我们有必要知道 **不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。**带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!
同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReader和BufferedWriter两个类。
现在就可以回答在本文的开头提出的问题:
BufferedInputStream和BufferedOutputStream类就是实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。
close()方法的作用
1、关闭输入流,并且释放系统资源
2、BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。
那么如果我们想逐个关闭流,我们该怎么做?
答案是:先关闭外层流,再关闭内层流。一般情况下是:先打开的后关闭,后打开的先关闭;另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b
解题思路:望文知意,Reader是字符流,而buffer是缓冲的作用,缓冲区是基于内存的,起到读写高效的作用;所以BufferedReader是高效字符流
BufferedReader是字符流,也是一种包装流,用来增强reader流.主要用来读取数据的,最经典的方法是readline,可以一次读一行,是reader不具备的.
解题思路:因为明确说了是对字节流的读取,所以肯定是inputstream或者他的子类,又因为要大量读取,肯定要考虑到高效的问题,自然想到缓冲流。
用BufferedInputStream,原因:BufferedInputStream是InputStream的缓冲流,使用它可以防止每次读取数据时进行实际的写操作,代表着使用缓冲区。不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!并且也可以减少对磁盘的损伤。
序列化指的是 把一个Java对象,通过某种介质进行传输,比如Socket输入输出流,或者保存在一个文件里
实现java序列化的手段是让 该类实现接口 Serializable,这个接口是一个标识性接口,没有任何方法,仅仅用于表示该类可以序列化。
注意的是 被关键字static、transient修饰的变量不能被序列化。在被序列化后,transient修饰的变量会被设为初始值。如int型的是0、对象型的是null.
解题思路:首先,要知道装饰者模式和适配器模式的作用;其次,可以自己举个例子把它的作用生动形象地讲出来;最后,简要说一下要完成这样的功能需要什么样的条件。
装饰器模式:就是动态地给一个对象添加一些额外的职责(对于原有功能的扩展)。
比如,在io流中,FilterInputStream类就是装饰角色,它实现了InputStream类的所有接口,并持有InputStream的对象实例的引用,BufferedInputStream是具体的装饰器实现者,这个装饰器类的作用就是使得InputStream读取的数据保存在内存中,而提高读取的性能。
适配器模式:将一个类的接口转换成客户期望的另一个接口,让原本不兼容的接口可以合作无间。
1.适配器对象实现原有接口
2.适配器对象组合一个实现新接口的对象
3.对适配器原有接口方法的调用被委托给新接口的实例的特定方法(重写旧接口方法来调用新接口功能。)
比如,在io流中, InputStreamReader类继承了Reader接口,但要创建它必须在构造函数中传入一个InputStream的实例,InputStreamReader的作用也就是将InputStream适配到Reader。 InputStreamReader实现了Reader接口,并且持有了InputStream的引用。这里,适配器就是InputStreamReader类,而源角色就是InputStream代表的实例对象,目标接口就是Reader类。
适配器模式主要在于 将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的;
而装饰器模式不是要改变被装饰对象的接口,而是 保持原有的接口,但是增强原有对象的功能,或改变原有对象的方法而提高性能。
参考:Java NIO:NIO概述
NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题。实际上,旧的IO包也已经使用nio重新实现过,以便充分利用这种速度提高。
旧IO中有三个类被修改了,分别是FileInputStream、FileOutputStream和RandomAccessFile,用于产生FileChannel。而Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer。
NIO和IO最大的区别就是 IO是面向流的,NIO面向缓冲区的。IO的各种流是阻塞的,这意味着,当一个线程调用read和writer方法时,该线程将被阻塞,直到有一些数据被读取,或者数据完全被写入,该线程在此期间不能再干任何事。NIO是非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用数据,如果目前没有数据可用,就什么都不会获取,而不是保持线程阻塞,所以在变得数据可以获取之前,线程可以先继续做其他事情,非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写完,这个线程同时也可以去做其他事情,线程通常将非阻塞IO的空闲时间用于其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。IO是单线程的,而NIO是用选择器来模拟多线程的。
在FileInputStream中,有如下涉及FileChannel的源码:
......
private FileChannel channel;
......
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
fd.decrementAndGetUseCount();
channel.close();
}
int useCount = fd.decrementAndGetUseCount();
if ((useCount <= 0) || !isRunningFinalize()) {
close0();
}
}
......
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, false, true, append, this);
/*
* Increment fd's use count. Invoking the channel's close()
* method will result in decrementing the use count set for
* the channel.
*/
fd.incrementAndGetUseCount();
}
return channel;
}
}
在NIO中有几个比较关键的概念:Channel(通道),Buffer(缓冲区),Selector(选择器)。
以下是常用的几种通道:
下面给出通过FileChannel来向文件中写入数据的一个例子:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOTest {
public static void main(String[] args) throws IOException {
File file = new File("C:\\Users\\Administrator\\Desktop\\test.txt");
//获取通道
FileOutputStream os = new FileOutputStream(file);
FileChannel channel = os.getChannel();
// 初始化Buffer,设定Buffer每次可以存储数据量
// 创建的Buffer是1024byte的,如果实际数据本身就小于1024,那么limit就是实际数据大小
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("hello kitty".getBytes());
// buffer.flip();一定得有,如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0时候的字符。通过buffer.flip();这个语句,就能把buffer的当前位置更改为buffer缓冲区的第一个位置。
buffer.flip();
channel.write(buffer);
//关闭流和通道
channel.close();
os.close();
}
}
在调用channel的write方法之前必须调用buffer的flip方法,如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0时候的字符。通过buffer.flip();这个语句,就能把buffer的当前位置更改为buffer缓冲区的第一个位置。
Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。
Buffer类有几个重要的属性:
在NIO中,Buffer是一个顶层父类,它是一个抽象类,常用的Buffer的子类有:
如果是对于文件读写,上面几种Buffer都可能会用到。但是对于网络读写来说,用的最多的是ByteBuffer。
Buffer读写数据步骤:
Buffer的方法:
Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。
与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。
参考:java的动态代理机制详解
在Mybatis和spring的源码中都可以看到动态代理的使用(InvocationHandler接口的使用)
我觉得动态代理的精髓是:
不需要改动源码,也不需要继承,也不需要什么装饰模式、适配器模式,我就可以在目标方法执行之前、之后添加我要添加的动作。就像vue那样,在getter和setter时,执行额外的动作。
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
Spring主要有两大思想,一个是IoC(依赖注入),另一个就是AOP。AOP的原理就是java的动态代理机制
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
Object invoke(Object proxy, Method method, Object[] args) throws Throwable方法一共接受三个参数
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException
这个方法的作用就是得到一个动态的代理对象,其接收三个参数:
其中interfaces中包含的接口,就是Proxy.newProxyInstance方法返回值可以被强制类型转换的接口类型。
实现一个动态代理的步骤
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface ISubject{
void read(String content);
void write(String content);
}
class Subject implements ISubject{
@Override
public void read(String content) {
System.out.println("读取内容:"+content);
}
@Override
public void write(String content) {
System.out.println("写入内容:"+content);
}
}
class DynamicProxyHandler implements InvocationHandler{
private Object obj;
public DynamicProxyHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在代理方法执行之前,做一些事情");
method.invoke(obj, args);
System.out.println("在代理方法执行之后,做一些事情");
return null;
}
}
class Client{
public static void execute(){
ISubject subj = new Subject();
DynamicProxyHandler handler = new DynamicProxyHandler(subj);
//为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,
//我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。
//
//同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
ISubject subject = (ISubject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), subj.getClass().getInterfaces(), handler);
//虽然这里只是显性的调用方法,和直接new对象并执行方法的外观一样。但是执行后就发现,不但完成了read、write的动作,还添加了我们在invoke中添加的额外动作
//所以这里并不是执行ISubject的实现类方法,而是执行代理的方法,顺便执行了实现类方法而已。换句话说,完全可以不执行实现类方法
//这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行
subject.read("<<格林童话>>");
subject.write("<<天天日记>>");
}
}
public class ProxyTest {
public static void main(String[] args) {
Client.execute();
}
}
com.wanmei.meishu.ms.$Proxy0
在代理方法执行之前,做一些事情
读取内容:<<格林童话>>
在代理方法执行之后,做一些事情
在代理方法执行之前,做一些事情
写入内容:<<天天日记>>
在代理方法执行之后,做一些事情
1 Error和Exception都实现了Throwable接口
2 Error指的是JVM层面的错误,比如内存不足OutOfMemoryError
3 Exception 指的是代码逻辑的异常,比如下标越界OutOfIndexException
try里的return 和 finally里的return 都会执行,但是当前方法只会采纳finally中return的值
NullPointerException 空指针异常
ClassCastException 类型转换异常
IndexOutOfBoundsException 数组下标越界异常
ArithmeticException 算术异常,比如除数为零
ConcurrentModificationException 同步修改异常,遍历一个集合的时候,删除集合的元素,就会抛出该异常
NegativeArraySizeException 为数组分配的空间是负数异常
Class.forName的作用?为什么要用?
Class.forName常见的场景是在数据库驱动初始化的时候调用。
Class.forName本身的意义是加载类到JVM中。 一旦一个类被加载到JVM中,它的静态属性就会被初始化,在初始化的过程中就会执行相关代码,从而达到"加载驱动的效果"
Runtime代表这Java运行时环境,每个Java都有一个Runtime实例。该类会被自动创建,可以通过Runtime.getRuntime
获取Java运行时环境
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class RuntimeUtil {
static Runtime runtime = Runtime.getRuntime();
public static void main(String[] args) {
memory() ;
System.out.println("-------------");
processors();
}
private static void processors() {
//核心线程数
System.out.println(runtime.availableProcessors());
BufferedReader reader=null;
//执行命令
try {
Process process = runtime.exec("ls /home/lihh");
//获取执行命令的结果
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();// 等待子进程完成再往下执行。
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line + "\r\n");
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void memory() {
int chg = 1024*1024;
//jvm已经从操作系统拿到多少内存
System.out.println(runtime.totalMemory()/chg+"mb");
//已经从操作系统中拿到的内存中有多少空闲内存
System.out.println(runtime.freeMemory()/chg+"mb");
//jvm可以从操作系统拿到的最大内存,即配置的Xmx参数。如果没有配置,默认为64mb
System.out.println(runtime.maxMemory()/chg+"mb");
}
}
import java.util.ArrayList;
import java.util.Random;
public class Demo1 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
while(list.size() < 10){
Random random = new Random();
int nextInt = random.nextInt(20) + 1;
if(!list.contains(nextInt)) {
list.add(nextInt);
}
}
System.out.println(list);
}
}
JDBC的全称是Java DataBase Connection,也就是Java数据库连接,我们可以用它来操作关系型数据库。JDBC接口及相关类在java.sql包和javax.sql包里。
JDBC接口让Java程序和JDBC驱动实现了松耦合,使得切换不同的数据库变得更加简单。
加载JDBC驱动程序 -->建立数据库连接Connection --> 创建执行sql的语句Statement --> 处理执行结果 ResultSet --> 释放资源
JDBC API使用Java的反射机制来实现Java程序和JDBC驱动的松耦合。随便看一个简单的JDBC示例,你会发现所有操作都是通过JDBC接口完成的,而 驱动只有在通过Class.forName反射机制来加载的时候才会出现。
Driver接口由数据库厂商提供,作为java开发人员,只需要使用Driver接口就可以了。在编程中要连接数据库必须先安装特定厂商的数据库驱动程序,不同的数据库有不同的装载方法。
这个类管理数据库驱动程序的列表。确定内容是否符合从 java 应用程序使用的通信子协议正确的数据库驱动程序的连接请求。识别JDBC子协议的第一个取定程序将别用来建立数据库连接。
注:DataSource 接口是 JDBC 2.0 API 中的新增内容,它提供了连接到数据源的另一种方法。使用 DataSource 对象是连接到数据源的首选方法。
Connection 与特定数据库的连接(会话),在上下文中执行sql语句并返回结果。此接口有接触数据库的所有方法,连接对象便是通信上下文,即,与数据库中所有的通信都是通过此唯一的连接对象。
riverManager.getConnection(url,user,password) 方法来建立url 中定义的数据库来连接
如连接Mysql 数据库:Connection connection = DriverManager.getConnection(“jdbc:mysql://host:port/database”,“user”,“password”);
其他数据库类似
常用的方法:
用于执行静态sql语句并返回它所生成结果的对象
三种Statement 类:
常用的Statement方法:
Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。我们可以通过它的getResultSet方法来获取ResultSet,或者通过getUpdateCount()方法来获取更新的记录条数。
Statement的executeQuery(String query)接口用来执行select查询,并且返回ResultSet。即使查询不到记录返回的ResultSet也不会为null。我们通常使用executeQuery来执行查询语句,这样的话如果传进来的是insert或者update语句的话,它会抛出错误信息为 “executeQuery method can not be used for update”的java.util.SQLException。
Statement的executeUpdate(String query)方法用来执行insert或者update/delete(DML)语句,或者 什么也不返回DDL语句。返回值是int类型,如果是DML语句的话,它就是更新的条数,如果是DDL的话,就返回0。
只有当你不确定是什么语句的时候才应该使用execute()方法,否则应该使用executeQuery或者executeUpdate方法。
有的时候表会生成主键,这时候就可以用Statement的getGeneratedKeys()方法来获取这个自动生成的主键的值了。
它和Statement相比优点在于:
查询结果集,提供了检索不同类型字段的方法
常用的有:
ResultSet还提供了对结果集进行滚动的方法:
使用后依次关闭对象及连接:ResultSet -> Statement ->Connection
在查询数据库后会返回一个ResultSet,它就像是查询结果集的一张数据表。
ResultSet对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。如果调用了ResultSet的next()方法游标会下移一行,如果没有更多的数据了,next()方法会返回false。可以在for循环中用它来遍历数据集。
默认的ResultSet是不能更新的,游标也只能往下移。也就是说你只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的ResultSet,像下面这样。
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
当生成ResultSet的Statement对象要关闭或者重新执行或是获取下一个ResultSet的时候,ResultSet对象也会自动关闭。
可以通过ResultSet的getter方法,传入列名或者从1开始的序号来获取列数据。
CLOB意思是Character Large OBjects,字符大对象,它是由单字节字符组成的字符串数据,有自己专门的代码页。这种数据类型适用于存储超长的文本信息,那些可能会超出标准的VARCHAR数据类型长度限制(上限是32KB)的文本。
BLOB是Binary Larget OBject,它是二进制大对象,由二进制数据组成,没有专门的代码页。它能用于存储超过VARBINARY限制(32KB)的二进制数据。这种数据类型适合存储图片,声音,图形,或者其它业务程序特定的数据。
下面列举了其中的一些:
事务的基本概念
一组要么同时成功,要么同时失败的sql语句,是数据库操作的一个基本执行单元。
事务开始于:
事务的四大特点
事务的隔离级别
参考:设计模式
总体来说设计模式分为三大类:
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。
这类模式都是以创建对象为目的。包括:工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
下边是jdk中关于Calendar的一个单例模式
......
//Calendar的构造函数都是protected的
public static Calendar getInstance()
{
Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault());
cal.sharedZone = true;
return cal;
}
......
public Simple(){
private static Single s=new Single();
private Single(){}
public static Simple getSimple(){
return s;
}
}
class Single{
private static Single s = null;
public Single() {
if (s == null)
s = new Single();
return s;
}
}
懒汉模式在使用时,容易引起不同步问题,所以应该创建同步"锁"
class Single1 {
private static Single1 s = null;
public Single1() {}
//同步函数的demo
public static synchronized Single1 getInstance() {
if (s == null)
s = new Single1();
return s;
}
//同步代码快的demo加锁,安全高效
public static Single1 getInStanceBlock(){
if(s==null)
synchronized (Single1.class) {
if(s==null)
s = new Single1();
}
return s;
}
}
在Spring中,BeanFactory就是使用的工厂模式
工厂模式的最大好处是增加了创建对象时的封装层次。
如果你使用工厂来创建对象,之后你可以使用更高级和更高性能的实现来替换原始的产品实现或类,这不需要在调用层做任何修改。
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
缺点:
interface Shape{
void draw();
}
class Circle implements Shape{
@Override
public void draw() {
System.out.println("draw circle");
}
}
class Rectangle implements Shape{
@Override
public void draw() {
System.out.println("draw rectangle");
}
}
class ShapeFactory{
public static Shape getShape(String type) {
if(type.equals("Circle"))
return new Circle();
if(type.equals("Rectangle"))
return new Rectangle();
return null;
}
}
public class FactoryTest {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("Circle");
circle.draw();
Shape rectangle = ShapeFactory.getShape("Rectangle");
rectangle.draw();
}
}
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("draw circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("draw rectangle");
}
}
interface Color {
void fill();
}
class Red implements Color {
@Override
public void fill() {
System.out.println("red");
}
}
class Blue implements Color {
@Override
public void fill() {
System.out.println("blue");
}
}
abstract class AbstractFactory {
abstract Color getColor(String type);
abstract Shape getShape(String type);
}
class ColorFactory extends AbstractFactory {
public Color getColor(String type) {
if (type.equals("Red"))
return new Red();
if (type.equals("Blue"))
return new Blue();
return null;
}
@Override
Shape getShape(String type) {
return null;
}
}
class ShapeFactory extends AbstractFactory {
public Color getColor(String type) {
return null;
}
@Override
Shape getShape(String type) {
if (type.equals("Circle"))
return new Circle();
if (type.equals("Rectangle"))
return new Rectangle();
return null;
}
}
class FactoryProducer {
public static AbstractFactory getFactory(String choice) {
if (choice.equalsIgnoreCase("SHAPE")) {
return new ShapeFactory();
} else if (choice.equalsIgnoreCase("COLOR")) {
return new ColorFactory();
}
return null;
}
}
public class FactoryTest {
public static void main(String[] args) {
// 获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
// 获取形状为 Circle 的对象
Shape shape1 = shapeFactory.getShape("Circle");
// 调用 Circle 的 draw 方法
shape1.draw();
// 获取形状为 Rectangle 的对象
Shape shape2 = shapeFactory.getShape("Rectangle");
// 调用 Rectangle 的 draw 方法
shape2.draw();
// 获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
// 获取颜色为 Red 的对象
Color color1 = colorFactory.getColor("RED");
// 调用 Red 的 fill 方法
color1.fill();
// 获取颜色为 Blue 的对象
Color color3 = colorFactory.getColor("BLUE");
// 调用 Blue 的 fill 方法
color3.fill();
}
}
参考:java设计模式之建造者模式
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
建造者模式通常包括下面几个角色:
应用实例:
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。其实建造者模式就是抽象工厂模式的一种变体。
与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
建造者模式和工厂模式同样是创建一个产品,工厂模式就是一个方法,而建造者模式有多个方法,并且建造者模式是有顺序的执行方法。就是说建造者模式强调的是顺序,而工厂模式没有顺序一说
我的理解是:工厂模式的关注点是 怎么创建一个实体类,建造者模式则是 怎么创建一个符合要求的实体类
//创建一个建筑实体
class Building{
private String basic;//地基
private String wall;//墙体
private String roof;//房顶
public String getBasic() {
return basic;
}
public void setBasic(String basic) {
this.basic = basic;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoof() {
return roof;
}
public void setRoof(String roof) {
this.roof = roof;
}
}
//创建建造者接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建
interface IBuilder{
void buildBasic();
void buildWall();
void buildRoof();
Building createBuilding();
}
//创建建造者实现类。,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例
class Builder implements IBuilder{
Building building;
public Builder(){
building = new Building();
}
@Override
public void buildBasic() {
building.setBasic("地基");
}
@Override
public void buildWall() {
building.setWall("墙体");
}
@Override
public void buildRoof() {
building.setRoof("屋顶");
}
@Override
public Building createBuilding() {
return this.building;
}
}
//组合套餐的组合方式。调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建
class Director {
public Building createBuildingDirector(IBuilder builder){
builder.buildBasic();
builder.buildWall();
builder.buildRoof();
return builder.createBuilding();
}
}
public class BuilderTest {
public static void main(String[] args) {
Building b = (new Director()).createBuildingDirector(new Builder());
System.out.println(b.getBasic());
}
}
JDK中建造者模式的应用:
StringBuilder和StringBuffer的append()方法使用了建造者模式。
StringBuilder把构建者的角色交给了其的父类AbstractStringBuilder,而StringBuilder则充当着Director的角色
如下,StringBuilder中两个append方法
public StringBuilder append(String str) {
super.append(str);
return this;
}
// Appends the specified string builder to this sequence.
private StringBuilder append(StringBuilder sb) {
if (sb == null)
return append("null");
int len = sb.length();
int newcount = count + len;
if (newcount > value.length)
expandCapacity(newcount);
sb.getChars(0, len, value, count);
count = newcount;
return this;
}
这两个方法最终分别调用了父类的append(String)和getChars方法
......
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
......
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
......
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型对象创建新的对象
主要解决:在运行期建立和删除原型
何时使用:
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码:
1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone()。
2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点:
1、性能提高。
2、逃避构造函数的约束。
缺点:
使用场景:
原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
注意事项:
这种模式以增强已存在对象功能为目的。包括:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
装饰器模式和适配器模式的差别:
装饰器模式和适配器模式都是包装模式,用来扩展已存在类的功能。
装饰模式增加强了单个对象的能力。
Java IO 到处都使用了装饰模式,典型例子就是Buffered 系列类如BufferedReader和BufferedWriter,它们增强了Reader和Writer对象,以实现提升性能的 Buffer 层次的读取和写入。
允许向一个现有的对象中添加新的功能,同时又不改变结构。它作为现有类的包装类。可以代替继承
interface Sourcable {
void operation();
}
class Source implements Sourcable {
@Override
public void operation() {
System.out.println("原始类");
}
}
//第一个装饰器类
class SourceDescorator1 implements Sourcable {
private Sourcable sourcable;
public SourceDescorator1(Sourcable sourcable) {
this.sourcable = sourcable;
super();
}
@Override
public void operation() {
System.out.println("第一个装饰器 - f");
sourcable.operation();
System.out.println("第一个装饰器 - af");
}
}
//第二个装饰器类
class SourceDescorator2 implements Sourcable {
private Sourcable sourcable;
public SourceDescorator2(Sourcable sourcable) {
this.sourcable = sourcable;
super();
}
@Override
public void operation() {
System.out.println("第二个装饰器 - f");
sourcable.operation();
System.out.println("第二个装饰器 - af");
}
}
public class TestDecorator {
public static void main(String[] args) {
Sourcable source = new Source();
// 装饰类对象
Sourcable obj = new SourceDescorator2(new SourceDescorator1(source));
obj.operation();
}
}
第二个装饰器 - f
第一个装饰器 - f
原始类
第一个装饰器 - af
第二个装饰器 - af
适配器模式不适合在详细设计阶段使用它,它是一种补偿模式,专用来在系统后期扩展、修改时所用。
优点:
缺点:
//已经存在的类
class Voltage220 {
public void hasOut() {
System.out.println("输出220V电压!");
}
}
//标准接口
interface TargetVoltageI{
void out();
}
//实现了标准接口的普通类
class TargetVoltage implements TargetVoltageI{
@Override
public void out() {
System.out.println("输出5V电压");
}
}
//适配器类.继承已存在的类并实现标准接口
class Adapter extends Voltage220 implements TargetVoltageI{
@Override
public void out() {
//关键代码
super.hasOut();
}
}
public class AdapterModel {
public static void main(String[] args) {
TargetVoltageI common = new TargetVoltage();
common.out();
TargetVoltageI adapter = new Adapter();
adapter.out();
}
}
使用直接关联,或者称为委托的方式。让类直接持有已经存在的类的对象
//已经存在的类
class Voltage220 {
public void hasOut() {
System.out.println("输出220V电压!");
}
}
//标准接口
interface TargetVoltageI{
void out();
}
//实现了标准接口的普通类
class TargetVoltage implements TargetVoltageI{
private Voltage220 voltage220;
public TargetVoltage(Voltage220 voltage220) {
this.voltage220 = voltage220;
}
@Override
public void out() {
voltage220.hasOut();
}
}
参看10. 动态代理
代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司。
参考:代理模式
代理模式包含如下角色:
缺点
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
//抽象角色
interface ICoder{
void implDemand(String demandName);
}
//真实角色
class JavaCoder implements ICoder{
private String coderName;
public JavaCoder(String coderName) {
this.coderName = coderName;
}
@Override
public void implDemand(String demandName){
System.out.println(coderName+" implements demand :"+demandName);
}
}
//代理角色
class CoderProxy implements ICoder{
private ICoder coder;
public CoderProxy(ICoder coder) {
this.coder = coder;
}
@Override
public void implDemand(String demandName) {
//todo:这里可以添加代理自己的动作包裹被代理者的动作
coder.implDemand(demandName);
}
}
public class ProxyModel {
public static void main(String[] args) {
ICoder coder = new JavaCoder("Zhang");
ICoder proxy = new CoderProxy(coder);
proxy.implDemand("Add user manageMent");
}
}
动态代理也叫做:JDK代理,接口代理
AOP用的恰恰是动态代理。
代理类在程序运行时创建的代理方式被称为动态代理。也就是说,代理类并不需要在Java代码中定义,而是在运行时动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
与静态代理相比,抽象角色、真实角色都没有变化。变化的只有代理类。
总结一下,一个典型的动态代理可分为以下四个步骤:
在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,也叫动态代理类,这个类被要求实现InvocationHandler接口:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//1.抽象角色
interface ICoder{
void implDemand(String demandName);
}
//2.真实角色
class JavaCoder implements ICoder{
private String coderName;
public JavaCoder(String coderName) {
this.coderName = coderName;
}
@Override
public void implDemand(String demandName){
System.out.println(coderName+" implements demand :"+demandName);
}
}
//3.动态代理类被要求实现接口InvocationHandler
class CoderDynamicProxy implements InvocationHandler{
private ICoder coder;
public CoderDynamicProxy(ICoder coder) {
this.coder = coder;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(System.currentTimeMillis());
Object result = method.invoke(coder, args);
System.out.println(System.currentTimeMillis());
return result;
}
}
public class ProxyModel {
public static void main(String[] args) {
// 4.
//要代理的真实对象
ICoder coder = new JavaCoder("Zhang");
//创建中介类实例
InvocationHandler handler = new CoderDynamicProxy(coder);
//获取类加载器
ClassLoader cl = coder.getClass().getClassLoader();
//动态产生一个代理类
ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);
//通过代理类,执行doSomething方法;
proxy.implDemand("Modify user management");
}
}
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展
Cglib子类代理实现方法:
这些设计模式特别关注对象之间的通信。包括:行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。
责任链模式在Tomcat中的应用
举个很简单的例子,就是报销流程,项目经理<部门经理<总经理
其中项目经理报销额度不能大于500,则部门经理的报销额度是不大于1000,超过1000则需要总经理审核
abstract class ConsumeHandler {
private ConsumeHandler nextHandler;
public ConsumeHandler getNextHandler() {
return nextHandler;
}
public void setNextHandler(ConsumeHandler nextHandler) {
this.nextHandler = nextHandler;
}
/** user申请人 free报销费用 */
public abstract void doHandler(String user, double free);
public static void main(String[] args) {
ProjectHandler projectHandler = new ProjectHandler();
DeptHandler deptHandler = new DeptHandler();
GeneralHandler generalHandler = new GeneralHandler();
projectHandler.setNextHandler(deptHandler);
deptHandler.setNextHandler(generalHandler);
projectHandler.doHandler("lwx", 450);
projectHandler.doHandler("lwx", 600);
projectHandler.doHandler("zy", 600);
projectHandler.doHandler("zy", 1500);
projectHandler.doHandler("lwxzy", 1500);
}
}
// 项目经理
class ProjectHandler extends ConsumeHandler {
@Override
public void doHandler(String user, double free) {
if (free < 500) {
if (user.equals("lwx")) {
System.out.println("项目经理:给予报销:" + free);
} else {
System.out.println("项目经理:报销不通过");
}
} else {
if (getNextHandler() != null) {
getNextHandler().doHandler(user, free);
}
}
}
}
// 部门经理
class DeptHandler extends ConsumeHandler {
@Override
public void doHandler(String user, double free) {
if (free < 1000) {
if (user.equals("zy")) {
System.out.println("部门经理:给予报销:" + free);
} else {
System.out.println("部门经理:报销不通过");
}
} else {
if (getNextHandler() != null) {
getNextHandler().doHandler(user, free);
}
}
}
}
// 总经理
class GeneralHandler extends ConsumeHandler {
@Override
public void doHandler(String user, double free) {
if (free >= 1000) {
if (user.equals("lwxzy")) {
System.out.println("总经理:给予报销:" + free);
} else {
System.out.println("总经理:报销不通过");
}
} else {
if (getNextHandler() != null) {
getNextHandler().doHandler(user, free);
}
}
}
}
参考:JAVA设计模式之观察者模式
首先,弄明白两组概念:观察者(Observer)与被观察者(subject)、发布者(publicsher)与订阅者(subscriber)。这是相似的两组概念,讲的时候,要对应于各自所在的组,不要弄混了。
在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。(这和前端vue的思想相同)
其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。
package com.wanmei.meishu.ms;
import java.util.ArrayList;
import java.util.List;
//观察者接口
interface Observable{
//观察
void addSub(ISubject sub);
//取消观察
void removeSub(ISubject sub,String msg);
//读取消息
void watch(String msg);
//获取观察者名称
String getName();
}
//观察者实例
class Observer implements Observable {
private String name;
public Observer(String name) {
this.name = name;
}
@Override
public void addSub(ISubject sub){
sub.addObserver(this);
System.out.println("Observer:用户【"+this.name+"】 订阅了消息");
}
@Override
public void removeSub(ISubject sub,String msg){
sub.removeObserver(this);
System.out.println("Observer:用户【"+this.name+"】 取消了订阅消息," + (msg == null ? "" : ("并说:" + msg)));
}
@Override
public void watch(String msg) {
System.out.println("Observer:用户【"+this.name+"】读取到的订阅消息是:" + msg);
}
public String getName() {
return name;
}
}
//被观察者接口
interface ISubject{
//给观察者们发送消息
void sendMsg(String msg);
//添加一个观察者
void addObserver(Observable user);
//取消一个观察者
void removeObserver(Observable user);
}
//被观察者实现方式
class SubjectImpl implements ISubject{
//持有观察者队列
private List<Observable> observerList;
//添加一个观察者
public synchronized void addObserver(Observable user){
if(observerList == null){
observerList = new ArrayList<Observable>();
}
observerList.add(user);
String str = "";
for (Observable observable : observerList) {
str+= observable.getName()+"、";
}
System.out.println("ISubject:目前已有用户:" + str.substring(0, str.length()-1));
}
//取消一个观察者
public void removeObserver(Observable user){
observerList.remove(user);
if(!observerList.isEmpty()){
String str = "";
for (Observable observable : observerList) {
str+= observable.getName()+"、";
}
System.out.println("ISubject:目前剩余用户:" + str.substring(0, str.length()- 1));
}
}
@Override
public void sendMsg(String msg) {
if(!observerList.isEmpty()){
System.out.println("ISubject:发送消息:" + msg);
for (Observable observable : observerList) {
observable.watch(msg);
}
}
}
}
public class ObserverTest {
public static void main(String[] args) {
ISubject sub = new SubjectImpl();
//第一个观察者
Observable u1 = new Observer("吴文俊");
u1.addSub(sub);
Observable u2 = new Observer("吴华云");
u2.addSub(sub);
Observable u3 = new Observer("李爪哇");
u3.addSub(sub);
sub.sendMsg("PHP是世界上最好的语言!");
u3.removeSub(sub,"去死吧,PHP");
sub.sendMsg("PHP是世界上最好的语言!");
}
}
运行结果
ISubject:目前已有用户:吴文俊
Observer:用户【吴文俊】 订阅了消息
ISubject:目前已有用户:吴文俊、吴华云
Observer:用户【吴华云】 订阅了消息
ISubject:目前已有用户:吴文俊、吴华云、李爪哇
Observer:用户【李爪哇】 订阅了消息
ISubject:发送消息:PHP是世界上最好的语言!
Observer:用户【吴文俊】读取到的订阅消息是:PHP是世界上最好的语言!
Observer:用户【吴华云】读取到的订阅消息是:PHP是世界上最好的语言!
Observer:用户【李爪哇】读取到的订阅消息是:PHP是世界上最好的语言!
ISubject:目前剩余用户:吴文俊、吴华云
Observer:用户【李爪哇】 取消了订阅消息并说:去死吧,PHP
ISubject:发送消息:PHP是世界上最好的语言!
Observer:用户【吴文俊】读取到的订阅消息是:PHP是世界上最好的语言!
Observer:用户【吴华云】读取到的订阅消息是:PHP是世界上最好的语言!
开闭原则(Open Closed Principle)是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。
Spring+Spring MVC +Mybatis
使用Spring框架的好处是什么?
AOP使用场景:权限控制、异常处理、缓存、事务管理、日志记录、数据校验等等
AOP基本概念
由于Spring AOP只支持以Spring Bean的方法调用来作为连接点, 所以在这里切入点的定义包括:
参考:Spring AOP @Before @Around @After 等 advice 的执行顺序
执行顺序
多个Aspect作用于一个方法上,如何指定每个 aspect 的执行顺序呢?
方法有两种:
@Order(5)
@Component
@Aspect
public class Aspect1 {}
@Order(6)
@Component
@Aspect
public class Aspect2 {}
注意点
使用步骤
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>${aspectj.version}version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>${aspectj.version}version>
dependency>
<aop:aspectj-autoproxy proxy-target-class="true" />
import java.lang.annotation.*;
/**
* api拦截器,记录日志
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented//文档生成时,该注解将被包含在javadoc中,可去掉
public @interface ApiInf {
}
注意注解@Aspect,如果没有使用@Component,则需要在spring中注册该aspect的bean
<bean class="com.ufgov.util.rest.ApiLogAspect" />
参考:Spring AOP 中@Pointcut的用法
表达式类型
标准的Aspectj Aop的pointcut的表达式类型是很丰富的,但是Spring Aop只支持其中的9种,外加Spring Aop自己扩充的一种一共是10种类型的表达式,分别如下。
其实execution表示式的定义方式就是方法定义的全量方式
格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
括号中各个pattern分别表示:
其中后面跟着“?”的是可选项
例子
1)execution(* *(..))//表示匹配所有方法
2)execution(public * com.savage.service.UserService.*(..))//表示匹配com.savage.server.UserService中所有的公有方法
3)execution(* com.savage.server..*.*(..))//表示匹配com.savage.server包及其子包下的所有方法
**> 最靠近(…)的为方法名,靠近.(…))的为类名或者接口名,如上例的JoinPointObjP2.(…))
参考:Spring AOP 中pointcut expression表达式解析及配置
args()
@args()
//参数带有@MyMethodAnnotation标注的方法.
@args(com.elong.annotation.MyMethodAnnotation)
//参数为String类型(运行时决定)的方法.
args(String)
this()
target()
@target()
within()
@within()
//pointcutexp包里的任意类.
within(com.test.spring.aop.pointcutexp.*)
//pointcutexp包和所有子包里的任意类.
within(com.test.spring.aop.pointcutexp..*)
//实现了MyInterface接口的所有类,如果MyInterface不是接口,限定MyInterface单个类.
this(com.test.spring.aop.pointcutexp.MyInterface)
**> 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型.
@annotation()
//带有@MyTypeAnnotation标注的所有类的任意方法.
@within(com.elong.annotation.MyTypeAnnotation)
@target(com.elong.annotation.MyTypeAnnotation)
//带有@MyTypeAnnotation标注的任意方法.
@annotation(com.elong.annotation.MyTypeAnnotation)
**> @within和@target针对类的注解,@annotation是针对方法的注解
@Before("og()")
这种使用方式等同于以下方式,直接定义execution表达式使用
@Before("execution(* com.savage.aop.MessageSender.*(..))")
Pointcut定义时,还可以使用&&、||、! 这三个运算
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
private void logSender(){}
@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
private void logReceiver(){}
@Pointcut("logSender() || logReceiver()")
private void logMessage(){}
还可以将一些公用的Pointcut放到一个类中,以供整个应用程序使用,如下:
package com.savage.aop;
import org.aspectj.lang.annotation.*;
public class Pointcuts {
@Pointcut("execution(* *Message(..))")
public void logMessage(){}
@Pointcut("execution(* *Attachment(..))")
public void logAttachment(){}
@Pointcut("execution(* *Service.*(..))")
public void auth(){}
}
在使用上面定义Pointcut时,指定完整的类名加上Pointcut签名就可以了,如:
package com.savage.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class LogBeforeAdvice {
@Before("com.sagage.aop.Pointcuts.logMessage()")
public void before(JoinPoint joinPoint) {
System.out.println("Logging before " + joinPoint.getSignature().getName());
}
}
也可以使用xml配置
<aop:config>
<aop:pointcut id="log" expression="execution(* com.savage.simplespring.bean.MessageSender.*(..))"/>
<aop:aspect id="logging" ref="logBeforeAdvice">
<aop:before pointcut-ref="log" method="before"/>
<aop:after-returning pointcut-ref="log" method="afterReturning"/>
aop:aspect>
aop:config>
package com.ufgov.util.rest;
/**
* 记录api访问日志的切面
*/
@Aspect
public class ApiLogAspect{
private static Logger logger = Logger.getLogger(ApiLogAspect.class);
/**
* 定义切入点
*/
@Pointcut("@annotation(ApiInf)")//这里就是表达式
//Pointcut签名
public void controllerAspect() {}
@Around("controllerAspect()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// TODO something
return point.proceed(); // 不调用point.proceed()不会执行目标方法
}
/**
* 进入方法之前处理
*/
@Before("controllerAspect()")
public void doBefore() throws UnsupportedEncodingException{
......
}
/**
* 记录返回信息
*/
@AfterReturning(pointcut="controllerAspect()",returning="ret")
public void doAfterReturn(Object ret) {
//对返回数据进行格式化处理,用于入库
}
/**
* 记录发生的异常信息
* @param e
*/
@AfterThrowing(value="controllerAspect()",throwing="e")
public void doAfterThrow(Throwable e) {
e.printStackTrace();
......
}
}
@ApiInfo
....
运行原理:
DispatcherServlet是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工作有以下三项:
DispatcherServlet:前端控制器;(相当于一个转发器,中央处理器,调度)
ModelAndView:模型和视图的结合体;(Spring mvc的底层对象)
HandlerMapping: 处理器映射器;
<servlet>
<servlet-name>springservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:conf/spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
在Mybatis的使用中,我们只写了DAO接口和XML配置文件,怎么没写实现类?原理是什么?
mybatis通过JDK的动态代理方式,在启动加载配置文件时,根据配置mapper的xml去生成Dao的实现。
session.getMapper()使用了代理,当调用一次此方法,都会产生一个代理class的instance,看看这个代理class的实现.
注意在spring中的配置
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="classpath:conf/mybatis-config.xml"
p:mapperLocations="classpath:mapper/*.xml" />
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
p:basePackage="com.ufgov.mapper" p:sqlSessionFactoryBeanName="sqlSessionFactory" />
其中的两个class就是mybatis的入口,初始化mybatis的配置。然后在spring装配service时,需要实例化dao,就由MapperScannerConfigurer来接管。对于方法的执行则由org.apache.ibatis.binding包下的MapperProxy、MapperProxyFactory、MapperRegistry、MapperMethod来处理
如果我们需要在DAO层写一些代码的话,这种方式就无能为力了。此时,MyBatis-Spring提供给我们的SqlSessionDaoSupport类就派上了用场。
关于mybatis中的dao是怎么实例化的,可以参考:Mapper(DAO层)接口如何实例化