封装
封装:将特定的域封装成特定的类,从而划分不同的类
优点:
便于管理
只通过特定的方法访问,防止对象域被破坏
可以改变内部实现,且不影响其他类
更改器可以执行错误检查
访问器
注意:不要编写返回引用可变对象的访问器
Class Employee{
private Date hiredate;
public Date getHireDate(){
return hireDate;
}
}
// 因为这样获取的对象能改变其属性值:
// exmployee.getHireDate().setTime(123456L);
// Date类有更改器(LocalDate没有),对象可变,这样就破坏了封装性
// 可以返回clone
public Date getHireDate(){
return (Date)hireDate.clone();
}
构造函数
构造函数没有返回值
所有对象都是在堆中构造,总是伴随new操作一起使用
方法参数
Java中方法都是按值传递参数,即形参为拷贝后的值
传递对象参数拷贝的是对象的引用,即可修改对象相应属性
方法不能让对象参数引用一个新的对象,即不能修改对象实参的引用(地址)(对象参数:无法修改内存地址指向,可以修改内存地址中的内容)
方法不能改变基础数据类型参数(可以使用Holder进行包装)
对象的析构和finalize方法
可以为任何类添加finalize方法,该方法将在垃圾回收器清除对象前调用(实际使用中不用依赖该方法)
有个叫
System.runFinalizersOnExit(true)
可以确保finalize方法被调用(但并不安全,不推荐使用,可以使用替代RunTime.addShutdownHook添加“关闭钩”)
静态方法
不能访问该类的非静态成员(属性、方法、this引用)
在静态方法中如何获取当前类名称?(
new Object(){}.getclass.getName()
)
重载(overload)与覆盖(override)
方法签名:方法名称+参数(类型)列表称为方法的签名。
同类中相同方法名称不同方法签名:重载
继承关系类中相同方法名称不同方法签名:覆盖 (多接口实现时的默认方法冲突问题)
在覆盖一个方法时,子类方法不能低于超类方法的可见性
理解方法调用
(1)编译器查看对象的声明类型和方法名称 => 获取所有可能被调用的候选方法 eg:f(String) f(int)
(2)编辑器查看调用方法时提供的参数类型 =>获得需要调用的方法名称和参数类型 eg:f(String)
(3)如果是private、static、final、方法或构造器,编译器可以准确地知道应该调用哪个方法,这种调用方式称为静态绑定(static binding)
当采用动态绑定调用方法时,编译器采用动态方式生成一条调用方法的指令 eg:f(String)
(4)当程序运行时,采用动态调用时,虚拟机一定调用与x引用对象的实际类型最合适的那个类的方法(如果该类定义了方法则直接调用它,否则在其超类中类推寻找)
类访问属性
default:只允许在同一个包下进行访问,其他不能(没在同一个包下的子类也不能,在同一包中的其他类也可直接访问)
protected:只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问
包导入
静态导入
import static java.lang.System.out;
类路径
JAR文件使用ZIP格式组织文件和子目录
Unix与Windows区别:Unix使用':',Windows使用';'分隔类路径中的不同项目,Unix禁止使用'*'
虚拟机和编译器定位文件不同
设置类路径:
(1)-classpath 选项java -classpath /home/classdir:.:/home/archives\archive.jar MyProg
(2)CLASSPATH 环境变量 (不建议)export CLASSPATH=/home/classdir:.:/home/archives\archive.jar
类设计技巧
一定要保证数据私有
一定要对数据初始化
不要在类中使用过多的基础数据类型(而是新封装一个类)
不是所有域都需要独立的访问器和更改器
将职责过多的类进行拆分
命名规范,体现职责
优先使用不可变类
super
只是一个指示编译器调用超类方法的特殊关键字,不能理解为引用
super(); //调用超类的构造函数,必须是子类构造器的第一条语句
super.getParm(); ////调用超类的方法
多态
多态:一个对象变量可以指示多个实际类型的现象称为polymorphism。
在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)
Java不支持多继承
可以将子类引用赋值给超类变量,不能把超类引用赋值给子类变量
动态绑定重要特性:无需对现存代码修改就可以对程序进行扩展
final
final:确保它们不会在子类中改变语义
如果不希望方法具有虚拟特征可以将其标记为final,
阻止继承:final类和方法,(final类所有方法都自动为final方法,但不包括域)
内联
如果一个方法没有被覆盖并且很短、被频繁调用,那么即时编译器就能够对它进行优化处理,这个过程称为内联,eg: e.getName() 将被替换成e.name
类型转换
只能在继承层次进行类型转换
在将超类转换为子类之前,应该使用instanceof进行检查
一般情况还是少用类型转换和instanceof运算符
抽象类
包含一个或多个抽象方法的类本身必须被声明为抽象类
除抽象方法外,抽象类还可以包含具体数据和方法(建议尽量将通用的域和方法(不管是否为抽象的)放在超类中不管是否为抽象类))
抽象类不能被实例化(可以定义指向子类的抽象类对象)
Object类:所有类的超类
equals方法
在Object类中该方法是判断两个对象是否具有相同引用;
在子类中定义equals方法时,首先调用超类的equals方法
@override
//boolean equals(Employee other){
boolean equals(Object other){
if(supper.equals(other)) return true; //具有相同引用
[Classname] otherThisObj = (Employee)other;
return Objects.equals(this.getId(), other.getId());
}
为防止两个null的比较可以用Objects.equals(Object,Object)方法
java语言规范要求equals方法具有下面特性:
自反性
对称性
如果子类能够拥有自己相等概念,则强制采用getClass进行检测;
如果超类决定相等概念,可以使用instanceof进行检测,并应该讲方法声明为final传递性
一致性
对任意非空引用x,x.equals(null) 应该返回false
if(this == other) return true;
if(other == null) return false;
if(getClass() != other.getClass) return false;
// or 所有子类拥有统一的语义:
// if (!(other instanceof [ClassName])) return false;
// 转换为当前类对象
[ClassName] otherThisObj = ([ClassName]) other;
return filed1 == otherThisObj.field1 && ...;
如果重新定义equals方法,那么必须重新定义hashCode方法,且两者的定义必须一致(如果equals返回true,那么两个对象的hashCode应返回相同的值)
以便将对象插入到散列表中
hashCode方法
String 与 StringBuffer中的hashCode方法:
String类中定义了hashCode方法,而StringBuffer中没有定义,其散列码是由Object默认hashCode方法导出的对象存储地址
Objects.haseCode方法,如果对象为null则返回0.
toString方法
数组的toString方法可以使用Arrays.toString进行修正。
包装器对象
对象包装器类为final类,不能定义子类
一旦构造了包装器对象,就不允许修改包装在其中的值。(不能修改方法参数的内容,如果想,可以使用org.omg.COBRA包中定义的持有者类型,包括IntHolder、BooleanHolder等)
public static void holder(IntHolder var) {
var.value++;
}
public static void main(String[] args) {
IntHolder var = new IntHolder(12);
holder(var);
System.out.println(var.value);
}
自动装箱和拆箱,编译器认可(非虚拟机)
自动装箱规范要求boolean、byte、char<=127,介于-128~127之间的short和int被包装到固定的对象中。
参数数量可变方法
public double max(double... values)
枚举类
在比较两个枚举类型的值时,永远不要使用equals方法,而是直接使用==(不同的类型其实对应着不同的实例);
在枚举类中添加的(私有)构造器和方法;
int ordinal(): 返回枚举在enum声明中的位置,从0开始计数。
int compareTo(E other) : -1-this之前 0:this=other >0: this之后
反射
Class类:运行时类型标识、存储类运行时的信息
Class类是一个泛型类(Class),一个Class对象实际表示的是一种类型,而这种类型未必一定是一个类(如int不是类,int.class是Class类型的对象)
鉴于历史原因,getName方法在应用于数组类型时会返回奇怪的名字:
int[].class.getName()
=> [I
Double[].class.getName()
=>[Ljava.lang.Double;
除非拥有访问权限,Java安全机制只允许查看任意对象有哪些域,而不允许读取它们的值(可以使用setAccessible方法修改,操作后记得改回原有的访问属性)
继承的设计与技巧
将公共操作和域放在超类
不要使用受保护的域
使用继承实现“is-a”关系
除非所有继承的方法都有意义,否则不要使用继承
在覆盖方法时,不要改变预期的行为
使用多态,而非类型信息
不要过度使用反射
接口
在Java中,接口不是类(不能new),而是对类的一组需求描述,类的实现要遵从接口描述的统一格式定义。
接口中的方法自动为public属性(接口中可以不加,但在实现类中实现时需要加上)
接口中的域将被自动设置为public static final
接口不能含义实例域,在SE8前也不能在接口中实现方法
在SE8中允许在接口中增加静态方法
接口中可以定义默认方法实现,默认方法可以调用其他任何方法(默认方法冲突:超类优先、(继承的两个接口方法冲突)子类必须覆盖接口的默认方法)
Comparable接口
任何实现Comparable接口的类都要包含int compareTo(Object other)
方法
SE5以改进为泛型类型
Cloneable接口
浅复制:相同引用(默认)
深复制:不同引用
实现Cloneable接口中的T clone(T source)
方法
接口与回调 - ActionListener接口
实现ActionListener接口中的void actionPerformed(ActionEvent event)
方法
Comparator接口
实现Comparator接口中的int compare(T first, T second)
方法
包含了很多方便的静态方法来创建比较器,这些方法可以用于lambda表达式或方法引用。
内部类
定义在另一个类中的类
内部类可以访问该类定义在作用域中的数据,包括私有数据
内部类可以对同一包中的其他类隐藏
想定义一个回调函数而不想编写大量代码时,使用匿名内部类比较方便
内部类的对象总有一个隐式引用- 即指向创建它的外部类对象
OuterClass.this.innerObject
内部类不能有static方法
局部内部类
定义在方法里的类
public void start(){
class HelloWorld implements ActionListener{
public void afterPerformed(ActionEvent event){
System.out.println("Hello world!");
}
}
ActionListener listener = new HelloWorld();
Timer t = new Timer(1000, listener);
t.start();
}
对外部完全隐藏
局部类的方法只能应用final属性的局部变量
public void start(final int beep){
class HelloWorld implements ActionListener{
public void afterPerformed(ActionEvent event){
System.out.println("Hello world!");
System.out.println(beep);
}
}
ActionListener listener = new HelloWorld();
Timer t = new Timer(1000, listener);
t.start();
}
匿名内部类
public void start(final int beep){
ActionListener listener = new ActionListener(){
public void afterPerformed(ActionEvent event){
System.out.println("Hello world!");
System.out.println(beep);
}
};
Timer t = new Timer(1000, listener);
t.start();
}
不能有构造器
实现接口时不能有任何构造参数
方法实现定义在{}内
静态方法中获取当前类类名:new Object(){}.getClass().getEnclosingClass()
静态内部类
内部类不需要引用外围类对象时,可以将内部类声明为static,以便取消产生的引用。
代理
有一个表示接口的Class对象,其确切的类型在编译期无法知道
要想构造一个实现该接口的类,就需要使用newInstance方法或反射找出这个实现类的构造器,
而且由于无法预先定义该实例对象(接口无法实例化),就需要在运行时定义一个新类。
为解决这问题,以前的程序是动态的生成代码文件,然后调用编译器编译后加载结果类文件(无疑很慢)
而代理机制可以更好的解决这一问题。
InvocationHandler接口 - 调用处理器接口
实现接口的 Object invoke(Object proxy, Method method, Object[] args)
代理对象
生成代理对象: Proxy类的newProxyInstance方法,三个参数:
一个类加载器
一个Class对象数组
一个调用处理器
如何定义处理器?
能够用结果代理对象做些什么?
=> 取决于打算使用代理机制解决什么问题
代理类是在程序运行时创建的,一旦被创建就变成常规类,虚拟机中额其他任何类没有区别
所有的代理类都扩展于Proxy类
所有的代理类都覆盖了Object类中toString、equals、hashCode方法(这些方法仅仅调用了处理器的invoke方法),其他方法如clone、getClass没有被重新定义
对于特定的类加载器和预设的一组接口来说,只能有一个代理类
代理类一定是public和final
Proxy.isProxyClass检测一个特定的对象是否表示一个代理类
没有定义代理类的名字,Sun虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名
使用代理机制实现登录拦截的例子:
//拦截器接口类:
public interface Interceptor {
/**
* 执行前
* @return
*/
boolean before(Object proxy, Object target, Method method, Object[] args);
/**
* 执行环绕
*/
void around(Object proxy, Object target, Method method, Object[] args);
/**
* 执行后
*/
void after(Object proxy, Object target, Method method, Object[] args);
}
//登录拦截实现类
public class LoginInterceptor implements Interceptor {
@Override
public boolean before(Object proxy, Object target, Method method, Object[] args) {
System.out.println(method.getName() + "\tbefore: do something");
// if ("doLogin".equals(method.getName())) {
// User user = (User) args[0];
// if (null == user) {
// return false;
// }
// if (null == user.getPassword()) {
// System.out.println("do login filter: password is null");
// return false;
// }
// if (null != user.getLoginName() &&
// user.getLoginName().equals("test")) {
// return false;
// }
// }
return true;
}
@Override
public void around(Object proxy, Object target, Method method, Object[] args) {
System.out.println(method.getName() + "\taround: do something");
}
@Override
public void after(Object proxy, Object target, Method method, Object[] args) {
System.out.println(method.getName() + "\tafter: do something");
}
}
//JDK动态代理-拦截-调用处理器类:
public class InterceptorJdkProxy implements InvocationHandler {
private Object target; // 代理目标
private Class interceptorClass; // 拦截器类
/**
* 构造器 - 指定拦截目标和拦截器
*
* @param target
* @param interceptorClass
*/
public InterceptorJdkProxy(Object target, Class interceptorClass) {
this.target = target;
this.interceptorClass = interceptorClass;
}
/**
* 生成代理对象
* @param target
* @param interceptorClass
* @return
*/
public static Object bind(Object target, Class interceptorClass) {
return Proxy.newProxyInstance(
// 一个类加载器 (用于生成代理类名字, )
target.getClass().getClassLoader(),
// 一个Class对象数组
target.getClass().getInterfaces(),
// new Class[]{},
// 一个调用处理器
new InterceptorJdkProxy(target, interceptorClass));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*拦截器未指定 直接执行 不作拦截*/
if (interceptorClass == null){
return method.invoke(target, args);
}
/* 反射生成拦截器对象*/
Interceptor interceptor = (Interceptor)interceptorClass.newInstance();
/*拦截器前置方法*/
if (interceptor.before(proxy, target,method,args)){
/*拦截器环绕方法*/
interceptor.around(proxy, target,method,args);
}
/*拦截器后置方法*/
interceptor.after(proxy, target,method,args);
return method.invoke(target, args);
}
}
扩展:JDK代理和Cglib代理
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor
{
// 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
public Object CreatProxyedObj(Class<?> clazz)
{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
{
// 这里增强
System.out.println("收钱");
return arg3.invokeSuper(arg0, arg2);
}
}
从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。
cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。
这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。
C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。
这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
{
System.out.println("收钱");
return arg3.invokeSuper(arg0, arg2);
}
因为如果我们通过反射 arg1.invoke(arg0, ...)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, ...)很明显会死循环。
所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用
fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。
对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。
评论区