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
总结:
- java编译形成字节码,平台无关性(扩展性好),c++一次编译形成机器码(效率高);
- c++是指针,java是对象的引用;
- c++多继承,java单继承,多实现;
- c++需要自己花费时间分配内存和垃圾回收,避免内存泄露,java由jvm实现自动内存管理(分配和回收)。**
ps:Java平台无关性体现在两个方面:
JVM: Java 编译器可生成与计算机体系结构无关的字节码指令,字节码文件不仅可以轻易地在任何机器上解释执行,还可以动态地转换成本地机器代码,转换是由 JVM 实现的,JVM 是平台相关的,屏蔽了不同操作系统的差异。
语言规范: 基本数据类型大小有明确规定,例如 int 永远为 32 位,而 C/C++ 中可能是 16 位、32 位,也可能是编译器开发商指定的其他大小。Java 中数值类型有固定字节数,二进制数据以固定格式存储和传输,字符串采用标准的 Unicode 格式存储。
JDK JRE JVM
jdk:开发者工具(针对开发人员),它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
jre:运行时环境(需要运行java程序的人员),它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
jvm:运行java字节码的虚拟机,JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在!!!
三者关系:
什么是字节码?采用字节码的好处是什么?
- 编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++、Delphi等。
- 解释型语言:程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次。程序执行效率比较低,依赖解释器,跨平台性好。如Python/JavaScript / Perl /Shell等。
java中的编译器和解释器:
- Java虚拟机是在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。
- 每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。
- Java对不同的操作系统有不同的JVM,Java编译器可以生成与平台无关的字节码指令,字节码文件不仅可以轻易地在任何机器上解释执行,还可以动态地转换成本地机器代码,转换是由 JVM 实现的,所以 Java实现了真正意义上的跨平台!
注意:字节码到机器码这一步,JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将运行频率高的字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
采用字节码好处:
- Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。
- 由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
ps:HotSpot 采⽤了惰性评估(Lazy Evaluation)的做法,根据⼆⼋定律,消耗⼤部分系统资源的只有那⼀⼩部分的代码(热点代码),⽽这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执⾏的情况收集信息并相应地做出⼀些优化,因此执⾏的次数越多,它的速度就越快。JDK 9 引⼊了⼀种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各⽅⾯的开销。JDK ⽀持分层编译和 AOT 协作使⽤。但是 ,AOT 编译器的编译质量是肯定⽐不上 JIT 编译器的。
创建对象的方式有哪些?
- 通过 new 关键字:这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象。比如 Object obj = new Object();
- 通过 Class 类的 newInstance() 方法:这种默认是调用类的无参构造方法创建对象。比如 Person p2 = (Person) Class.forName("com.ys.test.Person").newInstance();
- 通过 Constructor 类的 newInstance 方法:这和第二种方法类时,都是通过反射来创建对象。通过 java.lang.relect.Constructor 类的 newInstance() 方法指定某个构造器来创建对象。Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
- 利用 Clone 方法:Clone 是 Object 类中的一个方法,无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去。通过 对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B,clone 克隆,顾名思义就是创建一个一模一样的对象出来。Person p4 = (Person) p3.clone();
- 反序列化: 当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象。序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。
总结:前三种是通过构造函数创建对象,后两个不需要。
深拷贝or浅拷贝?
拷贝的引入:
- 引用拷贝:创建一个指向对象的引用变量的拷贝,即创建一个指向该对象的新的引用变量,teacher与otherteacher指向内存地址相同(只是引用不同),所以肯定指向一个对象Teacher("Taylor",26)。
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
注意:深拷贝和浅拷贝都是对象拷贝,都是针对一个已有的对象!对于基本数据类型(元类型),两种拷贝方式都是对值字段的复制(值传递),两者没有区别。
- 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。"里面的对象“会在原来的对象和它的副本之间共享。简言之,浅拷贝仅仅复制所考虑的对象,但不复制它所引用的对象(单层的拷贝),故原始对象及其副本引用的同一个对象。
- 深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。简言之,深拷贝把要复制的对象所引用的对象都复制了一遍(多层的拷贝),修改其中一个对象的任何内容,不会影响另一个对象的内容。
浅拷贝实现方式:
- 首先让定义的实体类实现Cloneable接口。然后重写clone方法,将clone方法的修饰符由protected改为public。这样就可以通过调用clone方法进行浅拷贝。
深拷贝实现方式:
- 首先是将引用的实体类也实现Cloneable接口(同时重写clone方法,也是修改修饰符为public)。然后同样是让定义的实体类实现Cloneable接口。然后重写clone方法,将clone方法的修饰符由protected改为public。但是方法体需要进行重写,将引用的对象属性调用它本身的clone方法进行赋值,然后将赋值后的对象返回即可。
什么是反射(reflection)机制?应用场景与优缺点。
反射是框架设计的灵魂。使用的前提条件:必须先得到代表字节码的Class类型对象
官方解释:反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法;这种动态获取的信息以及动态调用对象的属性方法的功能称为 Java 语言的反射机制。
总结:在运行时,构造任意类的对象,动态的获取类的信息并调用类的属性和方法。
如图是类的正常加载过程:反射的原理在于Class对象。熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象,反射的本质:得到Class对象反向获取信息及动态调用对象的属性或者方法。
既然Java反射可以访问和修改私有成员变量,那封装成private还有意义么?
从OOP封装的角度,可以理解为private只是一种约定(我们要去遵守),是一种设计规范。
反射调用私有方法和属性:
- getDeclaredMethods():调用私有方法
- getDeclaredFields():获取私有变量值
- getConstructor():得到有参的构造函数
注意:setAccessible(true);获取访问权限!
获取Class对象的方式(如何使用反射?):
- 通过Object类中的getClass方法:因为所有类都继承Object类。getClass方法:返回一个对象的运行时类,进而可以通过Class获取这个类中的相关属性和方法;
- 任何数据类型(包括基本数据类型)都有一个“静态”的class属性;
- 通过Class类的静态方法:Class.forName(String className)(常用,即通过全限定类名(绝对路径/真实路径)创建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);
}
}
反射使用总结:
- 通过 Class.forName("全限定类名"),获取调用类的Class对象;
- 反射获取类实例要通过 newInstance(),相当于 new 一个新对象;
- 反射获取方法要通过 getMethod(),获取到类方法之后使用 invoke() 对类方法进行调用(执行一个方法)。如果是类方法为私有方法的话,则需要通过 setAccessible(true) 来修改方法的访问限制,并且获取方法使用getDeclaredMethod()。
反射应用场景:
- 编程工具 IDEA 或 Eclipse 等,在写代码时会有代码(属性或方法名)提示,就是因为使用了反射;
- 很多知名框架都用到反射机制,通过配置加载不同类,在不修改源码的情况下,注入属性或者调用方法。
例如,Spring 可以通过配置来加载不同的类,调用不同的方法,代码如下所示:
例如,MyBatis 在 Mapper 使用外部类的 Sql 构建查询时,代码如下所示:
@SelectProvider(type = PersonSql.class, method = "getListSql")
List 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);
- Web服务器中利用反射调用了Sevlet的服务方法。
- 另外,像 Java 中的一大利器 注解 的实现也用到了反射。为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
优缺点:
- 优点:可以动态执行,节省资源在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。
- 缺点:对性能有影响,这类操作总是慢于直接执行java代码,要先生成Class对象。
动态代理(设计模式),常见的两种动态代理的实现?
写在前:静态代理:每个代理类只能为一个接口服务,这样会产生很多代理类。普通代理模式,代理类Proxy的Java代码在JVM运行时就已经确定了,也就是静态代理在编码编译阶段就确定了Proxy类的代码。而动态代理是指在JVM运行过程中,动态的创建一个类的代理类,并实例化代理对象。
动态代理:首先它是一个代理机制,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成,通过代理可以让调用者与实现者之间解耦。比如进行 RPC 调用,通过代理,可以提供更加友善的界面;还可以通过代理,做一个全局的拦截器。代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。解决的问题:直接访问一个对象带来的问题。
应用场景:
- 经典应用有 Spring AOP数据查询、例如,依赖注入 @Autowired 和事务注解 @Transactional 等,都是利用动态代理实现的;
- 封装一些rpc调用;
- 通过代理实现一个全局拦截器等。
动态代理与反射的关系:反射可以用来实现动态代理,但动态代理还有其他的实现方式,比如 ASM(一个短小精悍的字节码操作框架)、cglib 等。
jdk动态代理:
在java的类库中,java.util.reflect.Proxy类就是其用来实现动态代理的顶层类。可以通过Proxy类的静态方法Proxy.newProxyInstance()方法动态的创建一个类的代理类,并实例化。由它创建的代理类都是Proxy类的子类。
JDK动态代理实现步骤:
- 编写需要被代理的类和接口
- 编写代理类,需要实现 InvocationHandler 接口,重写 invoke() 方法;
- 使用Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)动态创建代理类对象,通过代理类对象调用业务方法。
注意: JDK Proxy 只能代理实现接口的类(即使是 extends 继承类也是不可以代理的)。
cglib实现动态代理:
- CGLIB是一个高性能的代码生成类库,被Spring广泛应用。其底层是通过ASM字节码框架生成类的字节码,达到动态创建类的目的。
ps:Spring AOP动态代理的实现方式有两种:cglib 和 JDK 原生动态代理。
cglib动态代理的实现步骤:
- 创建被代理的目标类。
- 创建一个方法拦截器类,并实现CGLIB的MethodInterceptor接口的intercept()方法。
- 通过Enhancer类增强工具,创建目标类的代理类。
- 利用代理类进行方法调用,就像调用真实的目标类方法一样。
要是用 cglib 实现要添加对 cglib 的依赖,如果是 maven 项目的话,直接添加以下代码:
cglib
cglib
3.2.12
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 原生动态代理:只能代理实现接口的类(即使是 extends 继承类也是不可以代理的),不需要添加任何依赖,可以平滑的支持 JDK 版本的升级;
- cglib 不需要实现接口,底层通过子类继承被代理对象的方式实现动态代理。可以直接代理普通类(但不能代理final修饰的类),需要添加依赖包,性能更高。
两者的区别:
- JDK 动态代理:基于 Java 反射机制实现,必须要实现了接口的业务类(extends 继承类不可代理)才能用这种办法生成代理对象。
- CGLib 动态代理:基于 ASM 机制实现,通过生成业务类的子类作为代理类(本质是子类继承被代理类的方法),所以代理的类不能是 final 修饰的。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 CGLib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 CGLib 框架的优势:
- 无需实现接口,达到代理类无侵入。故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
{
// 省略其他代码
前两个参数的声明:
- loader:为类加载器,也就是 target.getClass().getClassLoader()
- interfaces:接口代理类的接口实现列表
因此,要使用 JDK 原生的动态只能通过实现接口来完成。
什么是注解,什么是元注解?
注解是一种标记,使类或接口附加额外信息,帮助编译器和 JVM 完成一些特定功能,例如 @Override
标识一个方法是重写方法。
原理:注解的底层也是使用反射实现的。可以发现注解的本质就是接口,这个接口继承了jdk里面的Annotation接口。
元注解是自定义注解的注解,例如:
-
@Target
:约束作用位置,值是 ElementType 枚举常量,包括 METHOD 方法、VARIABLE 变量、TYPE 类/接口、PARAMETER 方法参数、CONSTRUCTORS 构造方法和 LOACL_VARIABLE 局部变量等。 -
@Rentention
:约束生命周期,值是 RetentionPolicy 枚举常量,包括 SOURCE 源码、CLASS 字节码和 RUNTIME 运行时。 -
@Documented
:表明这个注解应该被 javadoc 记录。
注解的作用:
- 生成文档,常用的有@param@return等。
- 替代配置文件的作用,尤其是在spring等一些框架中,使用注解可以大量的减少配置文件的数量。比如@Configuration标识这是一个配置类,@ComponentScan("spring.ioc.stu")配置包扫描路径 :
@Configuration
@ComponentScan("spring.ioc.stu")
public class SpringConfiguration {
xxx;
}
- 检查代码的格式,如@Override,标识某一个方法是否覆盖了它的父类的方法。
三大内置注解:
- @Deprecated 已过期,表示方法是不被建议使用的
- @Override 重写,标识覆盖它的父类的方法
- @SuppressWarnings 压制警告,抑制警告
巨人的肩膀:
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