面向过程和面向对象的编程思想
面向过程:问题分解成多个函数,依次进行调用
分别创建:开始—黑走—棋盘—判断—白走—棋盘—判断—循环的函数。
面向对象:问题抽象成多个对象,对象之间彼此调用
分别创建黑白对象,棋盘对象,规则对象等。重复使用。
优劣对比:
面向过程:占用资源低,速度快
面向对象:占用资源高,速度慢
面向对象的三大基本特征
封装:把客观事物封装成抽象的类,并且将类中的数据和方法进行隐藏。
继承:对现有的类进行扩展。有实现继承和接口继承两种方式。
多态:一个类实例的相同方法在运行期有不同的表现形式。父类引用子类对象。
多态的必要条件
1.类继承或者接口实现;2.子类重写父类方法;3.父类引用子类对象。
我认为,多态应该是一种运行期特性,Java 中的重写是多态的体现。不过
也有人提出重载是一种静态多态的想法。我更加倾向于重载不是多态。
重载和重写
重载:同一个类中,相同名称的方法有不同的表现形式。(编译器){方法名相同}
重写:子类复写父类方法。(运行期)
方法的重写要遵循“两同两小一大”
“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
继承与实现
继承:类具有相同功能,复用
实现:类处理相同目标,标准
继承与组合
继承:is-a。子父类
组合:has-a。在类中new一个对象。
对于两个类 A 和 B, 只有当两者之间确实存在 is-a 关系的时候,类 B 才应该继承类 A。
构造函数与默认构造函数
作用:初始化对象。
无返回类型。所属类名相同
类变量,成员变量,局部变量=》方法区,堆内存,栈内存
变量和方法作用域
protected:同在一个包中的其他类可见,其他包下 的类不可访问,除非是他的子类。
default:同一个包的内可见,其他包内的 类不能访问,即便是它的子类
面向对象的五大基本原则(警铃)
单一职责:一类一功能
开放封闭:扩展开放,修改封闭
里氏替换:子类能替代父类
依赖倒置原则:依赖于抽象接口,不要依赖于具体实现
接口隔离:使用多个小的专门接口,不要使用大的总接口
平台无关性
一次编译,到处执行
编译成二进制文件:源代码-词法分析-符号流-语法分析-语法树-语义分析-中间代码(class文件)-机器代码-机器语言
过程:java文件-javac-class文件-jvm-二进制文件
Class 文件可以在任何平台创建,也可以被任何平台的 Java 虚拟机装载并执行,所以才有了 Java 的平台无关性。
java虚拟机(jvm)=》平台有关性
字节码:class文件
java语言规范:保证基本数据类型在所有平台的一致性
jvm支持的语言:Groovy、Scala、Kotlin
实参与形参
方法定义的是形参,调用方法传入的是实参。
值传递与引用传递
引用传递:将实际参数的地址直接传递函数内部,修改影响实参。
值传递:将实际参数复制传递函数内部,修改不影响实参
java的值传递=》共享对象传递
先获取到实际参数的地址,然后将其复制,并把该地址的拷贝传递给被调函数的形式参数
在被调函数中改变了形式参数的值,调用者是可以看到这种变化的
传共享对象调用是传值调用的特例
基本数据类型
字符,布尔,数值
计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,
使用 BigDecimal 或者 Long(单位为分)来表示金额
java的关键字
transient
被 transient 修饰的成员变量,在序列化的时候其值会被忽略
instanceof
测试它左边的对象是否是它右边的类的实例
volatile
轻量级的synchronized。变量修饰符,用来修饰可能被多线程同时访问的变量。
private volatile Singleton singleton;
原理:
1.可见性原理:处理器和内存之间存在多级缓存来提升处理器速度;多级缓存导致数据不一致问题。对volatile变量进行写操作的时候,jvm会向处理器发送lock前缀指令,立刻将缓存变量写入主内存。缓存存在一致性协议,发现自己数据与主内存不一致,从主内存更新数据。
2.有序性原理:处理器优化导致指令重排,代码乱序执行。禁止指令重排优化
3.原子性原理:当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去 CPU 使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。volatile 是不能保证原子性的
synchronized
并发控制的关键字,同步代码块和同步方法。只能被单个线程访问。
同步方法:
jvm采用ACC_SYNCHRONIZED标记符来实现同步。
当某个线程要访问某个方法的时候,会检查是否有 ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。(如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放)
同步代码块:
jvm采用monitorenter,monitorexit两个指令来实现同步。可以把执行monitorenter 指令理解为加锁,执行 monitorexit 理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0,当一个线程获得锁(执行 monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计 数器再次自增。当同一个线程释放锁(执行 monitorexit 指令)的时候,计数器再自减。当计数器为 0 的时候。锁将被释放,其他线程便可以获得锁。
无论是 ACC_SYNCHRONIZED 还是 monitorenter、monitorexit 都是基于Monitor 实现的,在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitor 实现。
原理:
1.原子性原理:通过 monitorenter 和 monitorexit 指令,可以保证被 synchronized 修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。线程 1 在执行 monitorenter 指令的时候,会对 Monitor 进行加锁,加锁后其他线程无法获得锁,除非线程 1 主动解锁。即使在执行过程中,由于某种原因,比如 CPU 时间片用 完,线程 1 放弃了 CPU,但是,他并没有进行解锁。而由于 synchronized 的锁是可重入 的,下一个时间片还是只能被他自己获取到,还是会继续执行代码。直到所有代码执行完。这就保证了原子性。
2.可见性原理:对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。
3.有序性原理:synchronized 是无法禁止指令重排和处理器优化的。单线程支持as-if-serial 语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化,都必须遵守as-if-serial 语义。
final
final 变量被定义之后,是无法进行修改的。
final方法声明之后,则不能覆盖它。
final类声明之后,则不能继承它。
static
用来修饰成员变量和成员方法,也可以形成静 static代码块
String
字符串的不可变性
String s = “abcd”; String s2 = s;
s 中保存了 string 对象的引用。s2 保存了相同的引用值。代表同一个对象
字符串连接 s = s.concat(“ef”);
s 中保存的是一个重新创建出来的 string 对象的引用。
string 对象在内存(堆)中被创建出来,他就无法被修改。String 类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
substring的原理及区别
substring(int beginIndex, int endIndex)方法截取字符串并返回其[beginIndex,endIndex-1]范围内的内容。
JDK 6 中的 substring :
在 jdk 6 中,String 类包含三个成员变量: char value[], int offset,int count。他们分别用来存储真正的字符数组,数组的第一个位置索引以及字符串中包含的字符个数。
当调用 substring 方法的时候,会创建一个新的 string 对象,但是这个 string 的值仍然指向堆中的同一个字符数组。这两个对象中只有 count 和 offset 的值是不同的。会造成资源的浪费,因为指向的还是旧的长字符。
JDK 7 中的 substring :
其使用 new String 创建了一个新字符串,避免对老字符串的引用。从而解决了内存泄露问题。
replace、replaceAll 和 replaceFirst
Java 中常用的替换字符的方法
1、 replaceAll() 替换符合正则的所有文字
2、replaceFirst() 替换第一个符合正则的数据
//文字替换(全部)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World ");
//替换第一个符合正则的数据
System.out.println(matcher.replaceAll("Java"));
System.out.println(matcher.replaceFirst("Java"));
String对“+”的重载
1、String s = “a” + “b”,编译器会进行常量折叠
2、对于能够进行优化的(String s = “a” + 变量 等)用 StringBuilder 的 append()方法替代,最后调用 toString() 方法 (底层就是一个 new String())
字符串拼接
+:String 转成了 StringBuilder 后,使用其 append 方法进行处理的。
constant:首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再
把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的 String 对象 并返回。
StringBuilder;StringBuffer:内部的append方法会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。
StringUtils.join:通过 StringBuilder来实现的
所有的所谓字符串拼接,都是重新生成了一个新的字符串
效率:StringBuilder<StringBuffer<concat<+<StringUtils.join
String.valueOf和Integer.toString
String.valueOf(i)也是调用 Integer.toString(i) 来实现的
switch原理
支持类型:int,byte,short,char,String
1.switch 对 int 的判断是直接比较整数的值。
2.对 char 类型进行比较的时候,实际上比较的是ascii 码,编译器会把 char 型变量转换成对应的 int 型变量
3.switch 是通过 equals()和 hashCode()方法来实现的
字符串池
当代码中出现双引号形式(字面量)创建字符串对象时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。
JDK1.7之前,运行时常量池(字符串常量池也在里边)是存放在方法区,此时方法区的实现是永久带
JDK1.8,永久带更名为元空间(方法区的新的实现),但字符串常量池池还在堆中,运行时常量池在元空间(方法区)。
Class常量池
是 Class 文件中的资源仓库,其中保存了各种编译器常量。
三种常量池
分别是字符串常量池、Class 常量池和运行时常量池。
Class 是用来保存常量的一个媒介场所,并且是一个中间场所。在 JVM 真的运行时,需要把常量池中的常量加载到内存中
javap -v xxx.class生成的文档中Constant pool就是class常量池。用于存放编译器生成的各种字面量(Literal)和符号引用。
字面量就是指由字母、数字等构成的字符串或者数值。
符号引用主要包括了以下三类常量:
* 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符
运行时常量池
每一个运行时常量池都在 Java 虚拟机的方法区中分配,在加载类和接口到虚拟机后,就创建对应的运行时常量池。
运行时常量池中的内容包含:Class 常量池中的常量、字符串常量池中的内容。
Class 常量池只是一个媒介场所。在 JVM 真的运行时,需要把常量池中的常量加载到内存中,进入到运行时常量池
String的长度限制
编译期:字符串有长度限制,在编译期,要求字符串常量池中的常量不能超过 65535,并且在 javac 执行过程中控制了最大值为 65534
运行期:构造方法的参数类型是int,最大范围2^31-1
自动拆装箱
Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。为了让基本类型也具有对象的特征,就出现了包装类型。
自动装箱都是通过包装类的 valueOf()方法来实现的.自动拆箱都是通过包装类对象的 xxxValue()来实现的。
自动拆装箱的场景
1.将基本数据类型放入集合类
2.包装类型和基本类型的大小比较
3.包装类型的运算
4.三目运算符的使用
5.函数参数与返回值
自动拆装箱与缓存
在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。适用于整数值区间-128 至 +127。 只适用于自动装箱。使用构造函数创建对象不适用。
布尔返回值的形式
布尔基本类型自动生成的 getter 和 setter 方法,名称都是 isXXX()和 setXXX()形式的。
布尔包装类型自动生成的 getter 和 setter 方法,名称都是 getXXX()和 setXXX()形式的。
序列化带来的影响:
fastjson 和 jackson通过反射遍历出该类中的所有 getter 方法,得到 getHollis 和 isSuccess,然后根据JavaBeans 规则,他会认为这是两个属性 hollis 和 success 的值。
Gson通过反射遍历该类中的所有属性,并把其值序列化成 json
默认值带来的影响
对象的默认值是 null,boolean 基本数据类型的默认值是 false。
异常类型
受检异常(编译时异常),非受检异常(运行时异常)
异常相关关键字:
try⽤来指定⼀块预防所有异常的程序;
catch⼦句紧跟在 try 块后⾯, ⽤来指定你想要捕获的异常的类型;
finally 为确保⼀段代码不管发⽣什么异常状况都要被执⾏;
throw 语句⽤来明确地抛出⼀个异常;
throws⽤来声明⼀个⽅法可能抛出的各种异常;
Stream相关用法
特点:1.不是数据结构,无存储。2.函数式编程
中间操作:
Stream 的中间操作可以用来处理 Stream,中间操作的输入和输出都是 Stream,中间操作可以是过滤、转换、排序
filter:过滤元素
list.stream().filter(item->{if(){retrun true;} return false;}).forEach();
map:对元素进行映射
integers.stream().map(item->{ return item*2; }).forEach(System.out::println);
limit/skip: limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素
sorted :用于对流进行排序
distinct: 用来去重
最终操作:
Stream 的最终操作可以将 Stream 转成其他形式,如计算出流中元素的个数、将流装换成集合、以及元素的遍历
forEach:遍历
count:统计流中的元素个数
collect:规约操作
Arrays.asList获得的只是一个Arrays的内部类,不能对其进行增删操作,用ArrayList的构造器可以将其变成真正的ArrayList.
fail-fast和fail-safe
fail-fast:一旦发生异常,直接停止并上报
当多个线程对部分集合进行结构上的改变(新增,删除)的操作时,可能产生fail-fast机制,抛出CME异常。
产生场景:增强 for 循环遍历增删元素
在增强 for 循环中,集合遍历是通过 iterator 进行的,但是元素的add/remove 却是直接使用的集合类自己的方法。这就导致 iterator 在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示用户,可能发生了并发修改
为了避免触发 fail-fast 机制,导致异常,我们可以使用 Java 中提供的一些采用了fail-safe 机制的集合类。
集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
1.直接使用 Iterator 进行操作增删;
2.使用 Java 8 中提供的 filter 过滤;
3.直接使用 fail-safe 的集合类
Copy-On-Write
是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy 出去形成一个新的内容然后再改,这是一种延时懒惰策略。
IO流
读文件的步骤:
// 读取文件
File file = new File("D:\\miao.txt");
if (file.exists()){
try {
// 创建文件流
FileInputStream fileInputStream = new FileInputStream(file);
// 创建字符流
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
// 创建缓存流
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
// 读取内容
StringBuffer sb = new StringBuffer();
String text = null;
while((text = bufferedReader.readLine()) != null){
sb.append(text);
}
System.out.println( sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
写文件:
// 创建文件输出流
FileOutputStream fileOutputStream = null;
// 判断文件是否存在
File file = new File(txtPath);
try {
if(file.exists()){
//判断文件是否存在,如果不存在就新建一个txt
file.createNewFile();
}
fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(content.getBytes());
fileOutputStream.flush();
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
同步、异步,阻塞,非阻塞
同步、异步,是描述被调用方的。
阻塞,非阻塞,是描述调用方的。
同步不一定阻塞,异步也不一定非阻塞。没有必然关系。
Linux 5 种 IO 模型
阻塞式 IO 模型:用户线程调用io方法,线程一直等待io的返回结果
非阻塞式 IO 模型:用户线程不断轮询调用io方法,线程立刻io的返回准备状态。
io复用模型:只有一个线程去不断轮询socket状态,才会真正调用io方法。
信号驱动io模型:用户线程注册信号函数,接受返回信号,调用io方法。
异步io模型:用户线程调用io方法后,立刻做其他事,将数据拷贝到用户线程。
java的三种io实现方式
BIO:同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。
NIO:支持阻塞与非阻塞模式。叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
AIO:在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧 开之后,水壶会自动通知我水烧开
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。
NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发 局限于应用中,编程比较复杂,JDK1.4 开始支持。
AIO 方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。
NIO 实现文件的读取和写入
// 读取文件
File file = new File("D:\\miao.txt");
FileInputStream fin = null;
if (file.exists()){
try {
fin = new FileInputStream(file);
FileChannel channel = fin.getChannel();
int capacity = 100;// 字节
ByteBuffer bf = ByteBuffer.allocate(capacity);
int length = -1;
while ((length = channel.read(bf)) != -1) {
bf.clear();
byte[] bytes = bf.array();
System.out.write(bytes, 0, length);
}
channel.close();
} catch (Exception e) {
e.printStackTrace();
}finally { if (fin != null) { try {fin.close(); } catch (IOException e) { e.printStackTrace(); } } }
}
// 写文件
File file = new File("D:\\miao.txt");
FileOutputStream fos = null;
if (file.exists()){
try {
fos = new FileOutputStream(file);
FileChannel channel = fos.getChannel();
ByteBuffer src = Charset.forName("utf8").encode("你好你好你好你好你 好");
int length = 0;
while ((length = channel.write(src)) != 0) {
System.out.println("写入长度:" + length);
}
} catch (Exception e) {
e.printStackTrace();
}finally {if (fos != null) { try {fos.close(); } catch (IOException e) { e.printStackTrace(); } } }}
}
反射
在运行时来进行自我检查并且对内部的成员进行操作。给 定类的名字,就可以通过反射机制来获得类的所有信息。
反射版工厂模式
class Factory{
public static fruit getInstance(String ClassName){
fruit f=null;
try{f=(fruit)Class.forName(ClassName).newInstance();
}catch (Exception e) {
e.printStackTrace();
}return f;
}
}
获取Class方式
Class.forName(“完整类名带包名”)
对象.getClass()
任何类型.class
反射Constructor【反射/反编译一个类的构造方法】
public String getName() 返回构造方法名
public int getModifiers() 获取构造方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?>[] getParameterTypes() 返回构造方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public T newInstance(Object … initargs) 创建对象【参数为创建对象的数据】
反射Filed【反射/反编译一个类的属性】
获取属性
public Field[] getFields() 返回类中public修饰的属性
public Field[] getDeclaredFields() 返回类中所有的属性
public Field getDeclaredField(String name) 根据属性名name获取指定的属性
修改属性
public void setAccessible(boolean flag) 默认false,设置为true为打破封装
public String getName() 返回属性名
public int getModifiers() 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?> getType() 以Class类型,返回属性类型【一般配合Class类的getSimpleName()方法使用】
public void set(Object obj, Object value) 设置属性值
public Object get(Object obj) 读取属性值
ExcelData annotation = field.getAnnotation(ExcelData.class); 获取属性的注解
反射Method【反射/反编译一个类的方法】
获取方法
public String getName() 返回方法名
public int getModifiers() 获取方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?> getReturnType() 以Class类型,返回方法类型【一般配合Class类的getSimpleName()方法使用】
public Class<?>[] getParameterTypes() 返回方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public Object invoke(Object obj, Object… args) 调用方法
枚举
使用 enmu 来定义一个枚举类型的时候,编译器会自动帮我们创建一个 final类型的类继承 Enum 类,所以枚举类型不能被继承
public enum Season{
SPRING,SUMMER,AUTUMN,WINTER;
}
泛型
泛型最大的好处是可以提高代码的复用性。
表示类型的上界,格式为:<? extends T>,即类型必须为 T 类型或者 T 子类 表示类型的下界,格式为:<? super T>,即类型必须为 T 类型或者 T 的父类
动态代理的实现方式
1、JDK 动态代理
java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理类的能力。
使用动态代理实现功能:不改变 Test 类的情况下,在方法 target 之前打印一句话,之后打印一句话。
public class UserServiceImpl implements UserService {
@Override public void add() {
System.out.println("--------------------add---------------------- ");
}
}
代码实现:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
super(); this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
PerformanceMonior.begin(target.getClass().getName()+"."+method.getName());
Object result = method.invoke(target, args);
PerformanceMonior.end();
return result;
}
public Object getProxy(){
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
}
}
public static void main(String[] args) {
UserService service = new UserServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(service);
UserService proxy = (UserService) handler.getProxy();
proxy.add();
}
2、Cglib 动态代理
Cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
JDK 的动态代理有一个限制,就是使用动态 代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用 CGLIB实现。
public class CglibProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现 MethodInterceptor 接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置代理");
return result;
}
}
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
//通过生成子类的方式创建代理类
UserServiceImpl proxyImp = (UserServiceImpl)proxy.getProxy(UserServiceImp l.class); proxyImp.add();
}
序列化和反序列化
Serializable 和 Externalizable
Externalizable 继承了 Serial izable, 该接⼜中定义了两个抽象⽅法: writeExternal()与 readExternal()。
当使⽤Externalizable 接口来进⾏序列化与反序列化的时候需要开发⼈员重写 writeExternal()与 readExternal()⽅法。如果没有在这两个⽅法中定义序列化实现细节, 那么序列化之后, 对象内容为空。实现 Externalizable 接⼜的类必须要提供⼀个 public 的⽆参的构造器。 所以, 实现 Externalizable, 并实现 writeExternal()和 readExternal()⽅法可以指定序列化哪些属性。
//Write Obj to File
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(user);
}catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(oos);
}
//Read Obj from File
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(file));
User newUser = (User) ois.readObject();
System.out.println(newUser);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ois);
try {
FileUtils.forceDelete(file);
} catch (IOException e) {
e.printStackTrace();
} } }
注解
元注解有四个:@Target(表示该注解可以用于什么地方)、@Retention(表示再什 么级别保存该注解信息)、@Documented(将此注解包含再 javadoc 中)、@Inherited(允许子类继承父类中的注解)。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableAuth { String name() default "猿天地"; }
时间处理
线程不安全:SimpleDateFormat作为成员变量
//Date 转 String
Date data = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dataStr = sdf.format(data);
System.out.println(dataStr);
// String 转 Data
System.out.println(sdf.parse(dataStr));
线程安全:1.将 SimpleDateFormat 声明成局部变量;2.对成员变量的SimpleDateFormat 进行加锁;3.使用 ThreadLocal;4.使用 DateTimeFormatter
//解析日期
String dateStr= "2016 年 10 月 25 日";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日"); LocalDate date= LocalDate.parse(dateStr, formatter);
//日期转换为字符串
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy 年 MM 月 dd 日 hh:mm a"); String nowStr = now .format(format);
System.out.println(nowStr);
Java 8 中的时间处理
获取当前时间:
LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
创建指定日期的时间:
LocalDate date = LocalDate.of(2018, 01, 01);
检查闰年:
LocalDate nowDate = LocalDate.now();
//判断闰年
boolean leapYear = nowDate.isLeapYear();
计算两个⽇期之间的天数和⽉数 :
Period period = Period.between(LocalDate.of(2018, 1, 5),LocalDate.of(2018, 2, 5));
编码方式
ASCII:使⽤7 位⼆进制数( 剩下的 1 位⼆进制为 0) 来表⽰所有的⼤写和⼩写字母,
Unicode:一个标准,定义了一个字符集以及一系列的编码规则,Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码规则。
UTF-8 就是 Unicode 的一个使用方式
GBK支持中文字符的编码方式,国内标准