Spring MVC

Java基础知识总结(下)

2021-05-13  本文已影响0人  _code_x

Java语言的特点,与c++的区别

(1)Java源码会先经过编译器编译成字节码(class文件),然后由JVM中内置的解释器解释成机器码。而C++经过一次编译就形成机器码C++比Java执行效率快,但是Java可以利用JVM跨平台(一次编译,到处运行!)
(2)Java是纯面向对象的语言,所有代码都必须在类定义。而C++中还有面向过程的东西,比如全局变量和全局函数。
(3)C++中有指针,Java中不提供指针直接访问内存,内存更加安全,但是有引用。
(4)C++支持多继承,Java类都是单继承。但是继承都有传递性,同时Java中的接口是多继承,接口可以多实现。
(5)Java 中内存的分配和回收由Java虚拟机实现(自动内存管理机制),会自动清理引用数为0的对象。而在 C++ 编程时,则需要花精力考虑如何避免内存泄漏。
(6)C++运算符可以重载,但是Java中不可以。同时C++中支持强制自动转型,Java中不行,会出现ClassCastException(类型不匹配)。

ps:在 C 语⾔中,字符串或字符数组最后都会有⼀个额外的字符‘\0’来表示结束。但是,Java 语⾔中没有结束符这⼀概念。 这是⼀个值得深度思考的问题,具体原因推荐看这篇⽂章:https://blog.csdn.net/sszgg2006/article/details/49148189

总结:

ps:Java平台无关性体现在两个方面:

JVM: Java 编译器可生成与计算机体系结构无关的字节码指令,字节码文件不仅可以轻易地在任何机器上解释执行,还可以动态地转换成本地机器代码,转换是由 JVM 实现的,JVM 是平台相关的,屏蔽了不同操作系统的差异。

语言规范: 基本数据类型大小有明确规定,例如 int 永远为 32 位,而 C/C++ 中可能是 16 位、32 位,也可能是编译器开发商指定的其他大小。Java 中数值类型有固定字节数,二进制数据以固定格式存储和传输,字符串采用标准的 Unicode 格式存储。

JDK JRE JVM

三者关系

什么是字节码?采用字节码的好处是什么?

java中的编译器和解释器:

注意:字节码到机器码这一步,JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将运行频率高的字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。

采用字节码好处

ps:HotSpot 采⽤了惰性评估(Lazy Evaluation)的做法,根据⼆⼋定律,消耗⼤部分系统资源的只有那⼀⼩部分的代码(热点代码),⽽这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执⾏的情况收集信息并相应地做出⼀些优化,因此执⾏的次数越多,它的速度就越快。JDK 9 引⼊了⼀种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各⽅⾯的开销。JDK ⽀持分层编译和 AOT 协作使⽤。但是 ,AOT 编译器的编译质量是肯定⽐不上 JIT 编译器的。

创建对象的方式有哪些?

总结:前三种是通过构造函数创建对象,后两个不需要。

深拷贝or浅拷贝?

拷贝的引入

Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);
// -----------结果-----------------
blog.Teacher@355da254
blog.Teacher@355da254
Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);
// -----------结果-----------------
blog.Teacher@355da254
blog.Teacher@4dc63996

注意:深拷贝和浅拷贝都是对象拷贝,都是针对一个已有的对象!对于基本数据类型(元类型),两种拷贝方式都是对值字段的复制(值传递),两者没有区别。

浅拷贝实现方式:

深拷贝实现方式:

什么是反射(reflection)机制?应用场景与优缺点。

反射是框架设计的灵魂。使用的前提条件:必须先得到代表字节码的Class类型对象

官方解释:反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法;这种动态获取的信息以及动态调用对象的属性方法的功能称为 Java 语言的反射机制。

总结:在运行时,构造任意类的对象,动态的获取类的信息并调用类的属性和方法

如图是类的正常加载过程:反射的原理在于Class对象。熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象,反射的本质:得到Class对象反向获取信息及动态调用对象的属性或者方法。

既然Java反射可以访问和修改私有成员变量,那封装成private还有意义么?

从OOP封装的角度,可以理解为private只是一种约定(我们要去遵守),是一种设计规范。

反射调用私有方法和属性:

注意:setAccessible(true);获取访问权限!

获取Class对象的方式(如何使用反射?):

package reflection;
public class Reflection {
    public static void main(String[] args) {
        //第一种方式获取Class对象  
        Student stu1 = new Student();   //通过new方式(构造器床架对象)产生一个Student对象,一个Class对象。
        Class stuClass = stu1.getClass();   //Object类中的getClass()方法,获取Class对象
        System.out.println(stuClass.getName());
        
        //第二种方式获取Class对象
        Class stuClass2 = Student.class;
        System.out.println(stuClass == stuClass2); //判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
        
        //第三种方式获取Class对象
        try {
            Class stuClass3 = Class.forName("reflection.Student");  //注意此字符串必须是真实路径,包名.类名
            System.out.println(stuClass3 == stuClass2);  //判断三种方式是否获取的是同一个Class对象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意:在运行期间,一个类,只有一个Class对象产生。

三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,可以通过传入一个字符串(全限定类名),也可写在配置文件中等多种方法。

使用反射调用类中的方法,分为三种情况:

package com.interview.chapter4;
class MyReflect {
    // 静态方法
    public static void staticMd() {
        System.out.println("Static Method");
    }
    // 公共方法
    public void publicMd() {
        System.out.println("Public Method");
    }
    // 私有方法
    private void privateMd() {
        System.out.println("Private Method");
    }
    
    public static void main(String[] args) {
        
        // 反射调用静态方法
        Class myClass = Class.forName("com.interview.chapter4.MyReflect");
        Method method = myClass.getMethod("staticMd");
        method.invoke(myClass);
        
        // 反射调用公共方法
        Class myClass = Class.forName("com.interview.chapter4.MyReflect");
        // 创建实例对象(相当于 new )
        Object instance = myClass.newInstance();
        Method method2 = myClass.getMethod("publicMd");
        method2.invoke(instance);
        
        // 反射调用私有方法
        Class myClass = Class.forName("com.interview.chapter4.MyReflect");
        // 创建实例对象(相当于 new )
        Object object = myClass.newInstance();
        Method method3 = myClass.getDeclaredMethod("privateMd");
        method3.setAccessible(true);
        method3.invoke(object);
    }
}

反射使用总结:

反射应用场景

例如,Spring 可以通过配置来加载不同的类,调用不同的方法,代码如下所示:

<bean id="person" class="com.spring.beans.Person" init-method="initPerson">
    
</bean>

例如,MyBatis 在 Mapper 使用外部类的 Sql 构建查询时,代码如下所示:

@SelectProvider(type = PersonSql.class, method = "getListSql")
List<Person> getList();
class PersonSql {
    public String getListSql() {
        String sql = new SQL() {{
            SELECT("*");
            FROM("person");
        }}.toString();
        return sql;
    }
}
String url = "jdbc:mysql://127.0.0.1:3306/mydb";
String username = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);

优缺点

动态代理(设计模式),常见的两种动态代理的实现?

写在前:静态代理:每个代理类只能为一个接口服务,这样会产生很多代理类。普通代理模式,代理类Proxy的Java代码在JVM运行时就已经确定了,也就是静态代理在编码编译阶段就确定了Proxy类的代码。而动态代理是指在JVM运行过程中,动态的创建一个类的代理类,并实例化代理对象。

动态代理:首先它是一个代理机制,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成,通过代理可以让调用者与实现者之间解耦。比如进行 RPC 调用,通过代理,可以提供更加友善的界面;还可以通过代理,做一个全局的拦截器。代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。解决的问题:直接访问一个对象带来的问题。

应用场景:

动态代理与反射的关系:反射可以用来实现动态代理,但动态代理还有其他的实现方式,比如 ASM(一个短小精悍的字节码操作框架)、cglib 等。

jdk动态代理:

在java的类库中,java.util.reflect.Proxy类就是其用来实现动态代理的顶层类。可以通过Proxy类的静态方法Proxy.newProxyInstance()方法动态的创建一个类的代理类,并实例化。由它创建的代理类都是Proxy类的子类。

JDK动态代理实现步骤:

注意: JDK Proxy 只能代理实现接口的类(即使是 extends 继承类也是不可以代理的)

cglib实现动态代理:

ps:Spring AOP动态代理的实现方式有两种:cglib 和 JDK 原生动态代理。

cglib动态代理的实现步骤:

要是用 cglib 实现要添加对 cglib 的依赖,如果是 maven 项目的话,直接添加以下代码:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.12</version>
</dependency>

cglib 的具体实现,请参考以下代码:

class Panda {
    public void eat() {
        System.out.println("The panda is eating");
    }
}
class CglibProxy implements MethodInterceptor {
    private Object target; // 代理对象
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 设置父类为实例类
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用前");
        Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
        System.out.println("调用后");
        return result;
    }
}
public static void main(String[] args) {
    // cglib 动态代理调用
    CglibProxy proxy = new CglibProxy();
    Panda panda = (Panda)proxy.getInstance(new Panda());
    panda.eat();
}

由以上代码可以知道,cglib 的调用通过实现 MethodInterceptor 接口的 intercept 方法,调用 invokeSuper 进行动态代理的。它可以直接对普通类(可以有子类的普通类,但不能代理最终类)进行动态代理,并不需要像 JDK 代理那样,需要通过接口来完成, Spring 的动态代理也是通过 cglib 实现的。

注意:cglib 底层是通过子类继承被代理对象的方式实现动态代理的(即,动态的生成被代理类的子类),因此代理类不能是最终类(final),否则就会报错 java.lang.IllegalArgumentException: Cannot subclass final class xxx。

JDK原生态动态代理和CGlib区别

两者的区别:

JDK Proxy 的优势:

基于类似 CGLib 框架的优势:

ps:为什么 JDK 原生的动态代理必须要通过接口来完成?

这是由于 JDK 原生设计的原因,动态代理的实现方法 newProxyInstance() 的源码如下:

/**
 * ......
 * @param   loader the class loader to define the proxy class
 * @param   interfaces the list of interfaces for the proxy class to implement
 * ......
 */ 
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
// 省略其他代码

前两个参数的声明:

因此,要使用 JDK 原生的动态只能通过实现接口来完成。

什么是注解,什么是元注解?

注解是一种标记,使类或接口附加额外信息,帮助编译器和 JVM 完成一些特定功能,例如 @Override 标识一个方法是重写方法。

原理:注解的底层也是使用反射实现的。可以发现注解的本质就是接口,这个接口继承了jdk里面的Annotation接口。

元注解是自定义注解的注解,例如:

注解的作用:

@Configuration
@ComponentScan("spring.ioc.stu")
public class SpringConfiguration {
     xxx;
}

三大内置注解:

巨人的肩膀:

https://www.cnblogs.com/ysocean/p/8482979.html
https://www.jianshu.com/p/35d69cf24f1f
https://baiyexing.blog.csdn.net/article/details/71788741
https://blog.csdn.net/sinat_38259539/article/details/71799078
https://blog.csdn.net/huanglei305/article/details/101012177'kk

上一篇 下一篇

猜你喜欢

热点阅读