访问修饰符
定义: Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。
①private:在同一类内可见。使用对象:变量、方法、内部类。不能修饰外部类。
②default :在同一包内可见,不使用任何修饰符。使用对象:接口或者注解里面的方法,switch。
//接口 interface Intr { default String value() { return ""; } } //注解 @interface Anno { String value() default ""; } //switch switch (param) { case 1: System.out.println(1); break; default: System.out.println("eeee"); }
③protected:对同包内的类和所有子类可见。使用对象:变量、方法,内部类。
④public:对所有类可见。使用对象:类、接口、变量、方法。
final、finally、finalize
final
主要用在三个地方:变量、类、方法。
- 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
- 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
- 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
fianlly
finally在异常处理时,提供finally块来执行任何清除操作。如果发生异常,匹配的catch块就会执行,然后进入finally块执行。
finalize
finalize是方法名,jObject里定义的,也就是说每个对象都有这一个方法。 垃圾回收器删除对象之前调用对象的该方法做清理工作。
Java特性
封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
关于继承如下 3 点请记住:
子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
多态
父类变量指向子类实例对象,在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
重载和重写
方法的重载和重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。
重载
发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分。
重写
发生在有继承关系的类中,方法名、参数列表必须相同。访问修饰符大于等于父类(public>protected>default>private);如果父类方法访问修饰符为private则子类中就不是重写。
构造方法不能被继承,因此不能被重写,但可以被重载。
深拷贝VS浅拷贝
浅拷贝
一个变量指向另一个变量。对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
public class Main implements Cloneable{ private String name; public static void main(String[] args) { //基本类型 int i=1; int i1=i; //引用类型 Main main=new Main(); Main main1=main; } }
深拷贝
对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
public class Main implements Cloneable{ private String name; public static void main(String[] args) throws CloneNotSupportedException { //基本类型· int i=1; int j=i; //引用类型 Main main=new Main(); Main main1= (Main) main.clone(); Main main2=new Main(main); } }
抽象类和接口
抽象类
抽象类是可以有私有方法或私有变量的,通过把类或者类中的方法声明为
abstract
来表示一个类是抽象类,被声明为抽象的方法不能包含方法体
。子类实现方法必须含有相同的或者更低的访问级别(public->protected->private)。抽象类的子类为父类中所有抽象方法
的具体实现,否则也是抽象类。接口
接口可以被看作是抽象类的变体,接口中所有的方法都是抽象的,可以通过接口来间接的实现'
多重继承'
。接口中的成员变量都是static final
类型,由于抽象类可以包含部分方法的实现,所以,在一些场合下抽象类比接口更有优势
。抽象类和接口相同点
- 都不能被实例化
- 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
抽象类和接口不同点
- 接口只有定义,不能有方法的实现,
java 1.8中可以定义default方法体
,而抽象类可以有定义与实现,方法可在抽象类中实现。- 实现接口的关键字为
implements
,继承抽象类的关键字为extends
。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。- 接口强调特定功能的实现,而抽象类强调所属关系。
- 接口成员变量默认为
public static final
,必须赋初值,不能被修改;其所有的成员方法都是public
、abstract
的。抽象类中成员变量默认default
,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract
修饰,不能被private
、static
、synchronized
和native
等修饰,必须以分号结尾,不带花括号。- 接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。功能需要累积时用抽象类,不需要累积时用接口。
异常体系
所有的异常都是由 Throwable 继承而来,但在下一层立即分解为两个分支:Error 和 Exception
Error
Error类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。(栈溢出等)
Exception
Exception又划分成了两个分支。
①RuntimeException :由程序错误导致的异常
- 错误的类型转换。
- 数组访问越界
- 访问 null 指针
②IOException:程序本身没有问题, 但由于像 I/O 错误这类问题导致的异常
- 试图在文件尾部后面读取数据。
- 试图打开一个不存在的文件。
- 试图根据给定的字符串查找 Class 对象, 而这个字符串表示的类并不存在
受查异常:IOException;
非受查异常:RuntimeException,error
反射
什么是反射
反射是框架设计的灵魂。JAVA反射机制是在运行状态中对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法
(要想解剖一个类,必须先要获取到该类的字节码文件对象(class)。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.)
获得Class主要有三种方法
- obj.getClass()
- obj.class:任何数据类型(包括基本的数据类型)都有一个“静态”的属性
- Class.forName(String classPath),通过类全路径名获取到类的信息,通过method.invoke执行方法
常用第三种。第一种对象都有了不需要使用反射;第二种需要导入类包,依赖太强,不导包就抛编译错误;一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。
获取类属性
①成员变量(Field)操作
public class Main { private String name; public static void main(String[] args) throws Exception { //获取类 Class aClass = Class.forName("com.agi.main.Main"); //类实例化,创建一个对象 Object obj = aClass.newInstance(); //获取所有成员变量 Field field = aClass.getDeclaredField("name"); //暴力反射,可以获取private的成员变量 field.setAccessible(true); field.set(obj, "AGi"); System.out.println(field.get(obj));//AGi } }
②构造器操作
public class Main { private String name; public int age; public Main() {} public Main(String name, int age) { this.name = name; this.age = age; } public static void main(String[] args) throws Exception { //获取类 Class aClass = Class.forName("com.agi.main.Main"); /* * 构造方法操作 * */ //有参构造器创建对象 Constructor constructor=aClass.getConstructor(String.class,int.class); Object obj=constructor.newInstance("AGi",24); //无参构造器创建对象 Constructor constructor1=aClass.getConstructor(); Object obj1=constructor1.newInstance(); //无参构造器可以简化,创建对象 Object obj2=aClass.newInstance(); } }
③成员方法操作
public class Main { private String name; public String getName() {return name;} public void setName(String name) {this.name = name;} public static void main(String[] args) throws Exception { //获取类 Class aClass = Class.forName("com.agi.main.Main"); //创建对象 Object obj = aClass.newInstance(); //获取指定setName方法 Method set = aClass.getMethod("setName", String.class); //执行setName方法 set.invoke(obj, "XXX"); //获取getName方法 Method get = aClass.getMethod("getName"); //执行getName方法并获取结果 Object o = get.invoke(obj); System.out.println(o); } }
反射有点
可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
反射缺点
让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
代理
代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
静态代理
应用层面上说,静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
静态代理实现步骤:
①定义一个接口及其实现类(目标类);
interface JTProxyInterface{ public String method(String msg); } class JTProxyImpl implements JTProxyInterface{ @Override public String method(String msg) { return msg; } }
②创建一个代理类同样实现这个接口
class JTProxyClass implements JTProxyInterface{ @Override public String method(String msg) { return msg; } }
③将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
class JTProxyClass implements JTProxyInterface{ private JTProxyInterface proxyInterface=new JTProxyImpl(); @Override public String method(String msg) { System.out.println(msg+" Before"); String result=proxyInterface.method(msg); System.out.println(msg+" After"); return result; } public static void main(String[] args) { JTProxyClass proxyClass=new JTProxyClass(); String msg= proxyClass.method("AGi"); System.out.println(msg); /* AGi Before AGi After AGi */ } }
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
静态代理总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。Spring AOP、RPC 框架的实现都依赖了动态代理。动态代理在我们日常开发中使用的相对较小,但是在框架中的几乎是必用的一门技术。学会了动态代理,对理解和学习各种框架的原理也非常有帮助。
JDK 动态代理
使用步骤:
①定义一个接口及其实现类;
interface JDKProxyInterface { public String method(String msg); } class JDKProxyImpl implements JDKProxyInterface { @Override public String method(String msg) { System.out.println(msg); return msg; } }
②自定义
InvocationHandler
并重写invoke
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;class JDKProxyClass implements InvocationHandler { private Object target; public JDKProxyClass(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; System.out.println("Before"); result = method.invoke(target, args); System.out.println("After"); return result; } }
③通过
Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
方法创建代理对象;class Test{ public static void main(String[] args) { //类加载器 ClassLoader classLoader = JDKProxyClass.class.getClassLoader(); //代理接口 Class[] interfaces = new Class[]{JDKProxyInterface.class}; //创建代理对象 JDKProxyImpl target = new JDKProxyImpl(); JDKProxyClass handler = new JDKProxyClass(target); JDKProxyInterface proxy = (JDKProxyInterface) Proxy.newProxyInstance( classLoader, interfaces, handler ); proxy.method("AGi"); /* Before AGi After */ } }
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
CGLIB 动态代理类
使用步骤
①定义一个类;
class CGLibProxyClass{ public String method(String msg){ System.out.println(msg); return msg; } }
②自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似;class CGLibProxyHandler implements MethodInterceptor{ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("Before"); Object obj=methodProxy.invokeSuper(o,objects); System.out.println("After"); return obj; } }
③通过
Enhancer
类的create()
创建代理类;class Test{ public static void main(String[] args) { Enhancer enhancer=new Enhancer(); //设置代理类 enhancer.setSuperclass(CGLibProxyClass.class); //设置回调函数 enhancer.setCallback(new CGLibProxyHandler()); //创建代理类 CGLibProxyClass proxy= (CGLibProxyClass) enhancer.create(); proxy.method("AGi"); } }
JDK 动态代理和 CGLIB 动态代理对比
JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
CGLIB基于继承来实现代理,代理对象实际上是目标对象的子类,它内部通过第三方类库ASM,加载目标对象类的class文件,修改字节码来生成子类,生成类的过程较低效,但生成类以后的执行很高效,可以通过将ASM生成的类进行缓存来解决生成类过程低效的问题;
特点:
- 代理对象,不需要实现接口
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
静态代理与动态代理的区别
灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
内部类
成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部
public class Outer { private String name; class Inner{ private String name; } }
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
public class Outer { public void method() { class Part implements Cloneable{ private String name; } } }
匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。
public class Outer { private String name; } class Anon{ private String val; }
静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
public class Outer { //静态内部类 static class Static{ private String name;} //内部类 class Inner{ } public static void main(String[] args) { //创建静态内部类 Static s=new Static(); //创建内部类 Inner o=new Outer().new Inner(); } } class Anon{ public static void main(String[] args) { //创建静态内部类 Outer.Static s=new Outer.Static(); //创建内部类 Outer.Inner o=new Outer().new Inner(); } }
为什么需要内部类⭐
- 每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
- 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
- 方便编写事件驱动程序
- 方便编写线程代码