继承
覆盖方法
- 也就是重写
- 不改变预期的行为
- 如何访问到父类的数据
- 使用super
- 在构造方法中 如果调用super 需要将super写在有效代码的第一行;
package entity;
public class Person {
public String name;
public int age;
public void run(){
System.out.println("跑");
}
}
package entity;
public class Student extends Person{
public String className;
@Override
public void run(){
super.run();
System.out.println("学生跑");
}
}
package entity;
public class Main {
public static void main(String[] args) {
Student student = new Student();
student.run();
}
}
运行结果:
子类构造器
- 通过super来调用父类构造器
- 而且super必须是第一行有效代码
- 如果我们没有显示调用 那么会自动调用父类的无参构造器
- 如果父类里没有无参构造器, 但有其他的显示构造器 那么将会报错
- super 和this 的对比
多态
什么是多态?为什么使用多态?
用最简单的一句话就是:父类型的引用指向子类型的对象。用一句比较通俗的话:同一操作作用于不同的对象,可以产生不同的效果。这就是多态。
看Java核心技术这本书时,多态的内容讲解的很少,只是举个例子告诉我们怎样使用多态,而没有明确说明为什么使用多态的问题。
谈到面向对象的编程语言时不可避免的就会介绍继承,子类可以继承父类除private外的成员变量和方法,并且可以添加自己的成员变量与方法,以实现自己独特的功能。
但是如果在实际的开发中发现某个类的方法可以做出某些改进,但由于工程已经交付给用户使用,就不想增加不必要的麻烦!但以后再次创建该类的对象时就需要改进过来。
同时又不影响程序其他部分对该方法的调用。也就是说,如果再次创建该类的对象是不能做出改进的,应该由该类产生一个子类,并且子类将重写需要改进的方法,由于程序中其他地方需要调用该方法,所以对象的类型如果被创建成子类的类型的话将不能被有效调用,这就需要用到向上转换了即创建一个子类对象但是将其类型转换为父类的类型。这样做之后,程序的其他地方调用它时就能正确调用了,而且相应的方法也得到了改进。
由上可知:多态的存在有3个条件,1)要有继承 2)要有重写3)父类引用指向子类对象
- 子类可以赋值给父类类型
- 执行的会执行动态绑定,看执行那个类的方法
方法调用
- 参照核心卷
- 1 ) 编译器査看对象的声明类型和方法名。假设调用 x.f(param),且隐式参数 x声明为 C 类的对象。需要注意的是:有可能存在多个名字为 f, 但参数类型不一样的方法。例如,可 能存在方法 f(im) 和方法 String)。编译器将会一一列举所有 C 类中名为 f 的方法和其超类中 访问属性为 public 且名为 f 的方法(超类的私有方法不可访问) 。 至此, 编译器已获得所有可能被调用的候选方法。
- 2 ) 接下来,编译器将査看调用方法时提供的参数类型。如果在所有名为 f 的方法中存在 一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重栽解析(overloading resolution)。例如,对于调用 x.f(“ Hello” )来说, 编译器将会挑选 f(String),而不是 f(int)。由于允许类型转换(int 可以转换成double, Manager 可以转换成 Employee, 等等), 所以这 个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法, 或者发现经过类型转换后 有多个方法与之匹配, 就会报告一个错误。 至此, 编译器已获得需要调用的方法名字和参数类型。
- 3 ) 如果是 private 方法、 static 方法、final 方法(有关 final 修饰符的含义将在下一节讲 述)或者构造器, 那么编译器将可以准确地知道应该调用哪个方法, 我们将这种调用方式称 为静态绑定(static binding)。与此对应的是,调用的方法依赖于隐式参数的实际类型, 并且 在运行时实现动态绑定。在我们列举的示例中, 编译器采用动态绑定的方式生成一条调用 f (String) 的指令。
- 4 ) 当程序运行,并且采用动态绑定调用方法时, 虚拟机一定调用与 x 所引用对象的实 际类型最合适的那个类的方法。假设 x 的实际类型是 D,它是 C类的子类。如果 D类定义了 方法 f(String),就直接调用它;否则,将在 D类的超类中寻找 f(String), 以此类推。 每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个 方法表(method table), 其中列出了所有方法的签名和实际调用的方法。这样一来,在真正 调用方法的时候, 虚拟机仅查找这个表就行了。在前面的例子中, 虚拟机搜索 D 类的方法 表, 以便寻找与调用 f(Sting) 相K配的方法。这个方法既有可能是 D.f(String), 也有可能是 X.f(String), 这里的 X 是 D 的超类。这里需要提醒一点,如果调用 super.f(param), 编译器将 对隐式参数超类的方法表进行搜索。
强制类型转化
将一个子类的引用赋给一个超类(通俗的话:在公司里,每个经理都是员工,但不是每个员工都是经理,需要审核下他到底是不是)
Person person = new Student();
变量, 编译器是允许的。但将一个超类的引用赋给一个子类变量, 必须进行类型转换, 这样
才能够通过运行时的检査。
- 只能在继承层次内进行类型转换。
- 在将超类转换成子类之前,应该使用 instanceof进行检查。
if(person instanceof Student){
Student a = (Student)person;
}
抽象类
Object类
- equals 和==
- equals 对象调用 基本类型不能调用
- 在Object类中 equals 也是==
- 自定义的 equals 一般会用内容一致
- == 比较的地址的值
- 如果没有重写 = =引用类型比较地址
- 重写equals方法为啥要重写hashcode方法?
如果不重新定义hashcode,它的散列码是由Object类默认hashcode方法导出的对象存储地址
而要使x.equals(y)返回true,那么x.hashCode() 就必须与y.hashCode() 具有相同的值。如果是默认的hashcode返回的地址的散列码是不同的。
自动拆装箱
什么是拆箱
- 调用的什么方法
什么是装箱
- 调用的什么方法
为啥要有包装类
是有java编译器完成的
128陷阱
自动装箱规范要求:
boolean、byte、 char<=127, -128<= short,int <=127被包装到固定的对象中。
反射※
获取类信息的三种方式
- 类名.class
- 对象.getClass();
- Class.forName(“全限定名->包名+类名”);
能否获取方法的参数名?
可以获取到,一般来说,通过反射是很难获得参数名的,只能取到参数类型,因为在编译时,参数名有可能是会改变的,需要在编译时加入参数才不会改变。由于这一点,所以一般不会需要知道参数名,只需要得到参数类型即可。
我们为什么能/不能获得到方法的参数名?
如何通过类信息创建这个类的的对象?
newlnstance()
String s = "java.util .Random";
Object m = Class.forName(s) .newlnstance();
e.getClass0.newlnstance();
Student//一个学生类
Student zahngsan = Student.class.newInstance();
invoke ()
public static void fn() {
Employee e = new Employee("hssdst",50,new Date());
Employee e2 = new Employee("dfsfds",50,new Date());
try {
Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);
String n = (String) m1.invoke(e); //调用e的getName方法
System.out.println(n);//运行结果:hssdst
} catch (Exception e1) {
e1.printStackTrace();
}
}
由于invoke()方法的参数和返回值必须的是object类型。这就导致必须经过多次的类型转换才能得到想要的类型,这样会导致编译器错过检查代码的机会。可能到下工程的FT阶段才会发现错误。并且,利用反射获得方法指针的代码要比仅仅直接调用方法慢一些。
因此,在必要的时候使用Method,其他情况使用接口或者内部类比较好。
注:使用接口进行回调比使用Method对象的回调功能的代码执行速度更快,更易维护
小习题:补全程序,将zhangsan的name设置成张三
答案:
解析:由于Teacher类里 name是私有的并且没有构造方法
所以我们可以用反射机制来实现 ,通过getDeclaredField(“域的名称”)来获取域的Field对象,又因为是私有属性,所以用 setAssessible(boolean)来为反射对象设置可访问标志。flag 为 true 表明屏蔽 Java语言的访问检查,使得对象的 私有属性也可以被査询和设置。
反射的具体应用
- 一个jdbc驱动连接的载入
- servlet原理
(补充servlet知识)
内部类
内部类中声明的所有静态域都必须是 final。 为什么
答案:
一、课本解释
我们希望一个静态域只有一个实例 ,不过对于每个外部对象,会分别有一个单独的内部类实例。如果这个域不是fianl,
它可能是不唯一的。
二、我的理解
把一个类声明为内部类,通常是因为它和外围类是有一定联系的。现在我们把手机声明为外围类,手机屏幕声明为内部类
public class MobilePhone {
private String name;
public void create(Screen screen) {
...
// 通过给定的手机屏做了一个手机
...
}
// 内部类,只有内部类可以声明为私有
public class Screen {
//屏幕大小
public static final double size = 5.6;
}
}
现在张三和李四都创建了一个手机。现在假设张三嫌屏幕太小。于是想通MobilePhone.Screen.size = 10;修改下屏幕的大小。我们知道类的static属性是共享的,那么可想而知李四的屏幕也跟着“膨胀”了。这样会吓到别人滴。为了避免这种风险,对于“共享变量(static)”,一定要final。
代理※
静态代理模式
//cai 真正干苦力的类,(一会下边被代理的类)
public class Cai implements Singer {
@Override
public void singing() {
System.out.println("老蔡唱歌");
}
@Override
public void dance() {
System.out.println("老蔡跳舞");
}
@Override
public int playBasketBall(int a) {
System.out.println("老蔡打篮球");
return a;
}
}
//代理类 相当于一个类似中介的位置 上边有需求 他找人来干活(cai) 他也可以干一些扩展的事情,比如赚取差价
public class Bai implements Singer {
private Cai cai =new Cai();
@Override
public void singing() {
System.out.println("bai singing"); //这就是赚差价的地方
cai.singing();
}
@Override
public void dance() {
System.out.println("bai dance");//一样的赚
cai.dance();
}
@Override
public int playBasketBall(int a) {
return cai.playBasketBall(a);
}
}
public class Main {
public static void main(String[] args) {
//我来找(中介)bai 来解决我的需求了
Singer singer =new Bai();
singer.singing();
singer.dance();
singer.playBasketBall(123);
}
}
动态模式
- JDK提供的代理模式
JDK动态代理的优缺点:
优点:动态实现了不改变目标对象逻辑的扩展
缺点:目标必须实现接口,不然无法实现动态代理
public class DynamicTest {
public static void main(String[] args) {
Singer cai = new Cai();
//被代理的类的类加载器
//被代理的类的接口类信息数组
//怎么样被代理的 增强的功能规则
Object o = Proxy.newProxyInstance(Cai.class.getClassLoader(), Cai.class.getInterfaces(), new SingerProxy(cai));
if(o instanceof Singer){
Singer s= (Singer) o;
s.singing();
int i = s.playBasketBall(123);
System.out.println("返回值是"+i);
s.toString();
s.dance();
s.hashCode();
}
}
}
class SingerProxy implements InvocationHandler {
private Singer singer;
public SingerProxy(Singer singer) {
this.singer = singer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法名字");
if(args!=null&&args.length != 0){
for (Object arg : args) {
System.out.println("参数是:"+arg);
}
}
return method.invoke(singer,args);
}
}
这个包含有内部类,匿名内部类的写法 lambda表达式的写法
public class DynamicProxy {
public static void main(String[] args) {
Singer cai = new Cai();
class SingerHandle implements InvocationHandler {
private Singer singer;
public SingerHandle(Singer singer) {
this.singer = singer;
}
@Override //这个invoke方法是何时被调用的?
// 只要代理类的代理类的对象调用方法这个invoke就会被调用哦
//例如 下边的 o.singing();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("zhangsan");
return method.invoke(singer, args);
}
}
Singer o = (Singer) Proxy.newProxyInstance(Singer.class.getClassLoader(), Cai.class.getInterfaces(),
new SingerHandle(cai));
//o 是一个新创建的 cai的代理类-> T 的实例 这个代理类 T 一定实现了 Singer接口
o.singing();//这里执行时候也会调用上方类中的invoke方法
Singer i = (Singer) Proxy.newProxyInstance(Singer.class.getClassLoader(), Cai.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("匿名内部类");
return method.invoke(cai, args);
}
});
Singer i1 = (Singer) Proxy.newProxyInstance(Cai.class.getClassLoader(), Cai.class.getInterfaces(),
(proxy, method, params) -> {
System.out.println("lambda");
int invoke =(int) method.invoke(cai, params);
return invoke*2;
});
int i2 = i1.playBasketBall(10);
System.out.println(i2);
}
}
Cglib代理模式
- cglib的产生可以说是为了改进JDK动态代理必须实现接口的缺点,cglib是针对类的代理模式,
- 实现cglib必须实现MethodInterceptor接口
组成部分:
实现类:实现具体目标的逻辑
代理类:实现MethodInterceptor接口,扩展逻辑实现
Enhancer 设置代理类,并且生成代理对象,
优点:
实现了不使用接口就可以实现动态代理
缺点:
1. 实现类没有统一的限定格式,
2. 动态生成字节码虽然执行较快,但是生成速度很慢,
根据网上一些人的测试结果,cglib创建代理对象的速度要比JDK慢10 ~ 15倍。
区别和联系
总结 JDK动态代理和 CGLib 动态代理的特点:
JDK动态代理:
(1)代理类继承 Proxy 类, 并且实现委托类接口, 主要通过代理类调用 InvocationHandler 实现类的重写方法 invoke() 来实现动态代理.
(2)只能对接口进行代理. (只能对实现接口的委托类进行代理)
(3)底层使用反射机制进行方法掉调用.
适用场景
如果你的程序需要频繁、反复地创建代理对象,则JDK动态代理在性能上更占优。
CGLib动态代理:
(1)代理类继承了委托类, 在代理方法中, 会判断是否存在实现了 MethodInterceptor 接口的对象, 若存在则调用对象的 invoke() 方法, 对委托方法进行代理.
(2)不能对 final 类以及 final , private方法进行代理.
(3)底层将方法全部放入一个数组中, 通过索引直接进行方法调用.
代码实现:
public class Employee {
private String name;
private Integer age;
final public String fixName(String name){
this.name = name;
System.out.println("Employee fixName !");
return name;
}
public Integer fixAge(Integer age){
this.age = age;
System.out.println("Employee fixAge !");
return age;
}
}
public class EmployeeInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("EmployeeInterceptor : "+method.getName());
Object object = methodProxy.invokeSuper(o , objects);
return object;
}
}
public class TestCglibProxy {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Employee.class);
enhancer.setCallback(new EmployeeInterceptor());//设置回调
Employee proxy = (Employee) enhancer.create();
System.out.println(proxy.getClass().getName());
proxy.fixAge(23);
proxy.fixName(" ");
}
}
输出结果:
com.actchen.demo.cglibProxy.Employee$$EnhancerByCGLIB$$ea016430
EmployeeInterceptor : fixAge
Employee fixAge !
Employee fixName !
cglib与jdk不同的是,cglib摆脱了对Proxy的依赖,而避免了创建的代理类必须继承Proxy的限制。但cglib也依赖着一个类Enhancer,这个类也是为了生成代理类,但它不仅生成了代理类,还让代理类继承了被代理类,代理类会为委托方法生成两个方法,一个是重写的父类的方法,也就是被代理类的方法,另一个是代理类自身的同名方法,重写方法使用的是super调用了父类的方法,而代理类的方法则是对其的拓展。
cglib是通过Enhancer类来获取代理对象的。在cglib中需要获取代理对象分以下四步:
1)创建一个Enhancer对象;
2)调用setSuperclass()方法 将被代理的类设置为Enhancer类的父类;
3)调用setCallback()方法 设置Enhancer的回调对象为MethodInterceptor的实现类;
4)调用create()方法来创建代理对象;
适用场景
不需要频繁创建代理对象的应用,
如Spring中默认的单例bean,只需要在容器启动时生成一次代理对象。
主要应用场景:
Spring的AOP机制就是采用动态代理的机制来实现切面编程。
还有哪些,大佬可以评论区告诉我下,我也在找,谢谢啦。