-
Overload – Overwrite
overload(重载)发生在同一个类中,方法名相同,数的类型、个数、顺序不同,方法的返回值和修饰符可以不同。在java中,overload就是在一个类中定义多个同名方法的能力。编译器能够区分这些方法,因为它们的方法标识。这个属于也被称为方法重载,主要用于增加程序的可读性;让他看起来更容易理解
重载要记住的一点是,不能有多个具有相同名称、编号、和参数类型的方法,因为这种声明不能让编译器理解它们的不同之处。另外,不能声明两个方法具有相同的签名,即使它们具有唯一的返回类型。这是因为编译器在区分方法时不考虑返回类型。java中的重载创建了代码中的一致性,这有助于消除可能导致语法错误的不一致性。重载也是使代码更易于阅读的一种方便方法
overwrite(重写)如果在子类定义的一个方法,其名称、返回值类型及参数签名正好与父类中的某个方法的名称,返回值类型以及参数名相匹配,那么可以说,子类的方法重写了父类的方法
重写方法必须满足以下约束:
- 子类的方法的名称、参数签名和返回值类型必须与父类方法名称、参数签名及返回值类型一致
- 子类在重写父类方法时,重写方法不能缩小父类方法的访问权限
- 子类方法不能抛出比父类方法更多的异常;子类方法抛出的异常必须和父类方法抛出的异常相同,或者子类方法抛出的异常是父类方法抛出的异常的子类(多态机制冲突)
- 方法重写只存在于子类和父类之间,在同一个类中,方法只能重载不能重写
- 父类的静态方法不能被子类重写为非静态方法
- 子类可以定义与父类的静态方法同名的静态方法
- 父类的非静态方法不能被子类重写为静态方法
- 父类中的私有方法不能被子类重写
- 父类的抽象方法可以背子类通过两种方式重写,一是实现父类的抽象方法,二是重新声明父类的抽象方法
- 父类的非抽象方法可以被重写为抽象方法
-
final
表示最终修饰的变量:
- 修饰基本类型变量,一经过初始化后就不能够对其进行修改
- 修饰引用类型变量,不能够指向另一个引用
- 修饰类或方法,不能被继承或重写
-
反射(reflect)*
反射可以说是框架设计的重中之重
什么是反射:在java中,反射机制即在运行状态中,对于任意一个类,可以获取这个类的所有属性和方法;对于任意一个对象,都能够调用他的任意一个方法和属性。这种动态获取的信息,以及动态调用对象的方法的功能称为反射机制。
当然,想获取信息的前提条件是获取到该类的.class文件(即字节码文件对象)。每一个class类中的方法的获取,都需要获取所对应的.class文件对应的class类型的对象(好绕-_-)。
总结:反射机制即在运行时动态获取类的完整信息,增加了程序的灵活性。
-
jdk动态代理
(AOP):Aspect Oriented Programming 即面向切面编程,其核心就是采用了动态代理机制。
JDK静态代理是通过直接编码创建的,而JDK动态代理是利用反射机制在运行时根据指定参数创建代理类的
看到这里我连忙扒了一下动态代理的概念(都忘完了):
java中的代理:java.lang.reflect.proxy 官方文档是这么解释的:Proxy提供了创建动态代理类和实例的静态方法,它也是这些方法创建的所有动态代理类的超类。
动态代理类是一个类,它实现了在运行时创建类时指定的一系列接口,他的表现如下。代理接口就是这样一个有代理类实现的接口。代理实例是代理类的实例。每个代理实例都有一个关联的调用处理程序对象,该对象实现接口InvocationHandler。通过代理接口之一对代理实例的方法调用将被分派到实例调用处理程序的调用方法,传递代理实例、标识被调用方法的java.lang.reflect.Method对象,以及包含参数的object类型数组。调用处理程序适当地处理编码的方法调用,它返回的结果将作为代理实例上的方法调用的结果返回。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
我们需要用newProxyInstance方法来返回一个代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InnovationHandler h用来指明产生的这个代理对象要做什么事情。
动态代理如何应用?
在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的invoke方法。开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。
自己定义的JDKProxy类实现的InvocationHandler接口的invoke方法。可以看到上面生成的代理类就是通过调用invoke这个方法来进行目标方法的调用。
在动态代理中InvocationHandler是核心,每个代理实例都具有一个关联的调用处理程序(InvocationHandler)。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序(InvocationHandler)的 invoke 方法。所以对代理方法的调用都是通InvocationHadler的invoke来实现中,而invoke方法根据传入的代理对象,方法和参数来决定调用代理的哪个方法。invoke方法定义如下:
public Object invoke(Object proxy, Method method, Object[] args)
编写生成代理对象的类
首先定义接口:
//定义对象的action
public interface Pet {
String aoWu(String dog);
String miaoWu(String cat);
}
接下来定义目标对象类(即对象所拥有的method)
//定义目标对象类
public class Person implements Pet{
public String aoWu(String dog){
System.out.println("person xue:"+dog+"aoWu~");
return "I'm dog";
}
public String miaoWu(String cat){
System.out.println("person xue:"+cat+"miaoWu~");
return "I'm cat";
}
}
最后生成代理对象的代理类(用匿名内部类的方式)
// 生成代理对象的代理类
public class PersonProxy {
//设计一个类变量,代理类要代理的目标对象
private Pet p = new Person();
//返回一个代理对象的方法:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
public Pet getProxy(){
return (Pet) Proxy.newProxyInstance(PersonProxy.class.getClassLoader(), p.getClass().getInterfaces(),
new InvocationHandler() {
//InvocationHandler接口只定义了一个invoke方法,因此对于这样的接口
//直接使用一个匿名内部类来实现接口,new InvocationHandler(){}就是针对该接口的匿名实现类
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果调用的是aowu方法
if(method.getName().equals("aoWu")){
System.out.println("我是p先生的代理,想要让他狗叫先经过我同意");
return method.invoke(p,args);//代理调用p 即目标对象的aowu方法
}
if(method.getName().equals("miaoWu")){
System.out.println("我是p先生的代理,想要让他猫叫先经过我同意");
return method.invoke(p,args);//代理调用p 即咪表对象的miaowu方法
}
return null;
}
});
}
public static void main(String[] args) {
PersonProxy proxy = new PersonProxy();
//获得代理对象
Pet pet = proxy.getProxy();
//调用aowu method
String param = pet.aoWu("wang");
System.out.println(param);
//调用miaowu method
String param2 = pet.miaoWu("miao");
System.out.println(param2);
}
}
执行结果如下:
反思1:结合上一条复习的reflect,jdk动态代理在哪里用到了反射?
-
在调用Proxy.newProxyInstance方法时用反射获取接口传入方法
-
在生成代理类时,通过反射获取构造方法生成代理类
-
override invoke方法中调用被代理方法时,通过反射进行调用
反思2:为什么调用代理类的方法要去override invoke方法?
jdk生成的代理类中,是继承了proxy类实现了我们定义的被代理接口,而这个代理类在实现我们定义的接口方法时,是通过反射调用了InvocationHandlerImpl的invoke方法,然后在这个invoke方法当中,我们实现了增强的逻辑以及被代理方法的真正调用。
-
IO编程
java中如果按流的方向划分的话:有输入流(inputStream)和输出流(outputStream)
如果按照实现功能划分:则有节点流(如FileReader)和处理流(如BufferedReader)
按照处理数据的单位划分: 字节流和字符流 字节流继承于InputStream和OutputStream,字符流继承于InputStreamReader和OutputStreamWriter
字节流转为字符流:
字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。
字节流和字符流的区别?
字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。字节流主要是操作 byte 类型数据,以 byte 数组为准,主要操作类就是 OutputStream、InputStream字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。所以字符流是由 Java 虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。在程序中一个字符等于两个字节,java 提供了 Reader、Writer 两个专门操作字符流的类。
为什么用NIO,传统IO有什么缺陷?
一个使用传统阻塞I/O的系统,如果还是使用传统的一个请求对应一个线程这种模式,一旦有高并发的大量请求,就会有如下问题:
1、线程不够用, 就算使用了线程池复用线程也无济于事;
2、阻塞I/O模式下,会有大量的线程被阻塞,一直在等待数据,这个时候的线程被挂起,只能干等,CPU利用率很低,换句话说,系统的吞吐量差;
3、如果网络I/O堵塞或者有网络抖动或者网络故障等,线程的阻塞时间可能很长。整个系统也变的不可靠
最直接的体现在于服务器与客户端进行通讯时,每加入一台客户端需要一个IO线程阻塞等待对方数据传送,会导致服务器不断开启线程,但这些线程大部分时间都是阻塞在那里,浪费资源,并且支持不了大并发
NIO和IO的区别
原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的(具体解释见下一点)。
原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区
Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。
怎么理解NIO是面向块的、非阻塞的
NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
NIO是怎么实现的?
1.buffer 缓存数组
缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:
-
写数据到缓冲区;
-
调用buffer.flip()方法;
-
从缓冲区中读取数据;
-
调用buffer.clear()或buffer.compat()方法;
当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
-
-
-
a.容量(capacity)
b.界限(limit)
c.位置(position)