Java重点知识
形参实参
您可以在方法定义中使用参数(带参数的方法)。
实际参数(实际参数、argument):传递给函数/方法的参数,必须具有确定的值。形式参数(parameters):用于定义函数/方法并接收实际参数,不需要有显式值。
值传递引用传递
编程语言将实际参数传递给方法(或函数)有两种方式。
按值传递:此方法接收实际参数值的副本并创建一个副本。引用传递:该方法直接接收实参引用的对象在堆中的地址;不对形参进行复制。不影响实际参数。
许多编程语言(如C++和Pascal)提供了两种传递参数的方式,但Java只允许按值传递。
Java仍然向引用类型参数传递一个值,但这个值只是实际参数的地址。
也就是说,change方法的参数复制了arr(实参)的地址,所以这个方法和arr指向同一个数组对象。这也解释了为什么更改方法内的形式参数会影响实际参数。
交换两个引用类型的形式参数不会影响实际参数。
swap方法的参数person1和person2只是复制的实参xiaoZhang和xiaoLi的地址。因此,person1和person2的交换只是两个复制地址的交换,并不影响实际参数xiaoZhang和xiaoLi。
为什么 Java 不引入引用传递呢?
通过引用传递似乎非常好,允许您直接在方法内部更改实际参数的值。但为什么Java 不引入引用传递呢?
注:以下内容仅代表我个人观点,不代表Java相关人士的观点。
出于安全考虑,对方法内的值进行的操作对于调用者来说是透明的(方法被定义为接口,调用者不关心具体的实现)。你也可以想象一下,你拿着银行卡去取款,你提了100,扣了200。 Java 之父James Gosling 在早期设计阶段就注意到了C 和C++ 的许多缺点,想要设计一种新的语言——Java。他设计Java时遵循简单易用的原则,放弃了许多如果开发人员不小心就会产生问题的“特性”。语言本身的内容较少,对于开发人员来说也较少。学习。
总结
在Java 中将实际参数传递给方法(或函数)的方式是按值传递。
如果参数是基类型,则传递的是基类型文字值的副本,并创建一个副本。如果参数是引用类型,则传递的是实参引用的对象堆中地址值的副本,同时也会创建一个副本。
Java 反射机制
例如,反射也被用来实现注解,这是Java中的一个强大工具。
为什么使用Spring时@Component注解将一个类声明为Spring bean?为什么它通过@Value注解读取配置文件中的值?它到底是如何工作的?
所有这一切都是因为您可以基于反射分析类并获取类/属性/方法/方法参数的注释。获得注释后,您可以执行进一步的处理。
优点:使您的代码更加灵活,并为不同框架提供开箱即用的功能。
缺点:允许在运行时分析操作类,但这也增加了安全问题。例如,您可以忽略对泛型参数的安全检查(对泛型参数的安全检查是在编译时完成的)。另外,反射性能稍差一些,但实际上对框架影响不大。
获取 Class 对象的四种方式
如果要动态获取这些信息,就必须依赖Class对象。 Class 对象告诉正在运行的程序该类的方法、变量和其他信息。 Java提供了四种获取Class对象的方法:
1.如果知道具体的类,可以使用:
类alumbarClass=TargetObject.class;
然而,具体类别通常是未知的。以这种方式获取Class 对象本质上不会初始化它。
2.通过Class.forName()传递类的完整路径,得到:
类alunbarClass1=Class.forName(\’cn.javaguide.TargetObject\’);
3.通过instance.getClass()获取对象实例。
TargetObject o=new TargetObject();
类alumbarClass2=o.getClass();
4.通过类加载器xxxClassLoader.loadClass()传递类路径,得到:
ClassLoader.getSystemClassLoader().loadClass(\’cn.javaguide.TargetObject\’);
通过类加载器检索Class 对象不会对其进行初始化。这意味着静态代码块和静态对象只有在执行一系列步骤(包括初始化)后才会运行。
反射基操
/**
* 获取TargetObject类的Class对象并创建TargetObject类的实例
*/
类? targetClass=Class.forName(\’cn.javaguide.TargetObject\’);
TargetObject targetObject=(TargetObject) targetClass.newInstance();
/**
* 获取TargetObject类中定义的所有方法
*/
Method[] method=targetClass.getDeclaredMethods();
for(方法方法:方法){
System.out.println(method.getName());
}
/**
* 获取并调用指定方法
*/
方法publicMethod=targetClass.getDeclaredMethod(\’publicMethod\’,
字符串.class);
publicMethod.invoke(targetObject, \’JavaGuide\’);
/**
* 获取并修改指定参数
*/
字段field=targetClass.getDeclaredField(\’value\’);
//当我们更改类中的参数时取消安全检查
字段.setAccessible(true);
field.set(targetObject, \’JavaGuide\’);
/**
* 调用私有方法
*/
方法privateMethod=targetClass.getDeclaredMethod(\’privateMethod\’);
//取消调用私有方法的安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
代理模式
代理模式是一种比较容易理解的设计模式。简单来说,代理对象可以用来替代对真实对象的访问,提供额外的功能操作,在不改变原有目标对象的情况下扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能。例如,您可以在目标对象上执行方法之前和之后添加自定义操作。
静态代理
使用静态代理,您可以手动强化目标对象上的每个方法(*稍后显示代码*)。这是非常不灵活的(例如,当一个新方法添加到一个接口时,目标对象和代理都需要更改)并且需要对对象进行更改(*为每个目标类单独的代理需要编写一个类*)。 在日常开发中使用静态代理的实际应用场景很少。
上面,我们从实现和应用的角度讨论了静态代理。静态代理在编译期间将接口、实现类和代理类转换为实际的类文件。
静态代理实现步骤:
定义一个接口及其实现类。创建一个同样实现该接口的代理类,将目标对象注入到代理类中,并在代理类的对应方法上调用目标类的对应方法。在这种情况下,您可以通过代理类屏蔽对目标对象的访问,并在执行目标方法之前或之后执行任何必要的处理。
公共类SmsProxy 实现SmsService {;
私有最终SmsService smsService;
公共SmsProxy(SmsService smsService){
this.smsService=smsService;
}
@覆盖
公共字符串发送(字符串消息){
//调用该方法之前可以添加自己的操作
System.out.println(\’方法send()之前\’);
smsService.send(消息);
//调用方法后也可以添加自己的操作
System.out.println(\’方法send()之后\’);
返回空值。
}
公共类主要{
公共静态无效主(字符串[] args){
SmsService smsService=new SmsServiceImpl();
SmsProxy smsProxy=new SmsProxy(smsService);
smsProxy.send(\’java\’);
}
}
动态代理
与静态代理相比,动态代理更加灵活。不需要为每个目标类创建单独的代理类,也不需要实现任何接口。实现类可以直接被代理(CGLIB动态代理机制)。
从JVM 的角度来看,动态代理在运行时动态生成类字节码并将其加载到JVM 中。
说到动态代理,我们应该提到Spring AOP 和RPC 框架的实现都依赖于动态代理。
尽管动态代理在日常开发中相对较少使用,但它们几乎是框架中必需的技术。学习了动态代理之后,对于理解和学习不同框架的原理也很有帮助。
就Java而言,实现动态代理的方式有很多种,比如JDK动态代理、CGLIB动态代理等。
为什么在对浮点数float 或double 执行运算时会面临失去精度的风险?
这与计算机存储浮点数的机制有很大关系。我们知道计算机是二进制的,但是当计算机表示数字时,它们的宽度是有限的,当无限循环的小数存储在计算机中时,它只会被截断,并且小数的精度会丢失。这也解释了为什么浮点数不能用二进制精确表示。
例如,十进制数0.2无法准确转换为二进制十进制数。
//0.2转换为二进制的过程就是乘以2,直到没有小数为止。
//这个计算过程产生一个二进制结果,其中获得的整数部分按照从上到下的顺序排列。
0.2 * 2=0.4 – 0
0.4 * 2=0.8 – 0
0.8 * 2=1.6 – 1
0.6 * 2=1.2 – 1
0.2 * 2=0.4 – 0(会导致循环)
.
为了解决浮点运算中精度损失的问题,可以直接使用BigDecimal来定义浮点值并进行浮点运算。
Unsafe 介绍
何谓 SPI?
如上所述,语法糖的存在主要是为了开发者的方便。但实际上,Java 虚拟机并不支持这些语法糖。这些语法糖在编译阶段被简化为简单的基本语法结构。这个过程就是语法糖解码。
说到编译,我们都知道,在Java语言中,可以使用javac命令将后缀为.java的源文件编译为后缀为.class的字节码,可以在Java虚拟机上执行。如果您查看com.sun.tools.javac.main.JavaCompiler 的源代码,您会发现compile() 中的一个步骤是调用desugar()。该方法负责解码语法糖实现。
Java中最常用的语法糖包括泛型、变长参数、条件编译、自动拆箱、内部类等。本文主要分析这些语法糖背后的原理。让我们一步一步剥去糖衣,看看本质。
在我们开始之前,Java 的开关本身就支持基本类型。整数、字符等对于int类型,直接进行数值比较。如果是char类型,则比较其ASCII码。因此,编译器只能在switch 中使用整数,并且必须将任何类型的比较转换为整数。兼职工作等Short、char(ASCII 码是整数)和int。
Java 中有哪些常见的语法糖?
我们都知道很多语言都支持泛型,但是很多人不知道编译器对泛型的处理是不同的。编译器通常以两种方式处理泛型:代码专门化和代码共享。 C++和C#使用代码特定的处理机制,而Java使用代码共享机制。
代码共享方法为每个泛型类型创建唯一的字节码表示,并将泛型类型的实例映射到该唯一的字节码表示。将多个泛型类型实例映射到唯一的字节码表示是通过类型擦除来完成的。
因此对于Java 虚拟机来说,它不知道任何像MapString 或String 映射这样的语法。语法糖必须在编译阶段通过类型擦除来解码。
类型擦除的主要过程是: 1. 将所有泛型参数替换为其最左边界(顶部父类型)类型。 2. 删除所有类型参数。
泛型
变量参数是Java 1.5 中引入的一项功能。这允许该方法接受任意数量的值作为参数。
当你使用enum定义枚举时,编译器会自动帮助创建一个继承自Enum类的final类型类,因此枚举不能被继承。
可变长参数
内部类也称为嵌套类。内部类可以理解为外部类的常规成员。
内部类也是语法糖的原因是它们只是一个编译时概念。成功的编译将产生两个完全不同的.class 文件:外部.class 和.class 文件。外部$内部.类。因此,内部类的名称可以与外部类的名称相同。
内部类
在Java中,assert关键字是在JAVA SE 1.4中引入的。为了避免在旧版本的Java代码中使用assert关键字导致错误,Java在执行过程中默认不启用断言检查(目前所有Assertion语句都是启用的)。忽略!),如果要启用断言检查,则必须使用开关-enableassertions 或-ea 将其打开。
断言
对于(学生学生:学生){
if (stu.getId()==2)
学生.删除(stu);
}
抛出ConcurrentModificationException。
迭代器在单独的线程中运行并具有互斥锁。迭代器创建后,会建立一个指向原始对象的单链索引表,而即使原始对象的数量发生变化,这个索引表的内容也不会同步改变,所以索引指针可以向后移动。 \’t。遵循快速失败原则,迭代器立即抛出java.util.ConcurrentModificationException 异常,因为已知该对象将被迭代。
因此,Iterator 不允许您在迭代对象工作时对其进行修改。但是,您可以使用Iterator 自己的方法delete() 来删除对象。 Iterator.remove() 方法删除当前迭代对象,同时保持索引一致性。
#以上秋季招聘准备的Java必备知识(二)仅供参考。相关信息请参见官方公告。
原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/92477.html