八股整理之java基础篇
2026/5/15 18:53:48 网站建设 项目流程

animal.eat编译看左边,编译的时候会先看Animal里面有什么方法可以调用,bark没有在Animal中所以调用会直接报错。

运行看右边,animal.speak()运行的时候看右边类型,Animal和Dog都有这个方法,但是new的时候是Dog类型的,所以结果应该是汪汪汪

对象引用转换问题

向上转型是自动进行的,而且是安全的,如下:

class Animal {} class Dog extends Animal {} Dog dog = new Dog(); Animal animal= dog;//自动向上转型

但是向下转型需要手动进行,并且存在风险。如果父类对象实际上并不是目标子类的实例,在转型时就会抛出异常:

Animal animal = new Animal();//需要改成Animal animal = new dog(); Dog dog =(Dog)animal;// 运行时抛出classCastException

解決方式是需要使用instanceof检查:

if (animal instanceof Dog) { Dogdog = (Dog)animal;//只有确认animal是Dog的实例时才进行转型

为什么用bigDecimal 不用double?

使用BigDecimal可以确保精确的十进制数值计算,避免了使用double可能出现的舍入误差。需要注意的是,在创建BigDecimal对象时,应该使用字符串作为参数,而不是直接使用浮点数值,以避免浮点数精度丢失。

怎么把一个对象从一个jvm转移到另一个jvm?

使用序列化和反序列化:将对象序列化为字节流,并将其发送到另一个VM,然后在另一个VM中反序列化字节流恢复对象。这可以通过 Java 的 ObjectOutputStream 和 ObjectlnputStream 来实现。

使用消息传递机制:利用消息传递机制,比如使用消息队列(如RabbitMQ、Kafka)或者通过网络套接字进行通信,将对象从一个VM发送到另一个。这需要自定义协议来序列化对象并在另一个VM中反序列化。

使用远程方法调用(RPC):可以使用远程方法调用框架,如gRPC,来实现对象在不同JVM之间的传输。远程方法调用可以让你在分布式系统中调用远程MM上的对象的方法。

使用共享数据库或缓存:将对象存储在共享数据库(如MySQL、PostgreSQL)或共享缓存(如Redis)中,让不同的MM可以访问这些共享数据。这种方法适用于需要共享数据但不需要直接传输对象的场景。

序列化和反序列化让你自己实现你会怎么做?

Java 默认的序列化虽然实现方便,但却存在安全漏洞、不跨语言以及性能差等缺陷。

无法跨语言:Java序列化目前只适用基于Java语言实现的框架,其它语言大部分都没有使用Java的序列化框架,也没有实现Java序列化这套协议。因此,如果是两个基于不同语言编写的应用程序相互通信,则无法实现两个应用服务之间传输对象的序列化与反序列化。

容易被攻击:Java序列化是不安全的,我们知道对象是通过在ObjectlnputStream上调用readObject0方法进行反序列化的,这个方法其实是一个神奇的构造器,它可以将类路径上几乎所有实现了Serializable 接口的对象都实例化。这也就意味着,在反序列化字节流的过程中,该方法可以执行任意类型的代码,这是非常危险的。

序列化后的流太大:序列化后的二进制流大小能体现序列化的性能。序列化后的二进制数组越大,占用的存储空间就越多,存储硬件的成本就越高。如果我们是进行网络传输,则占用的带宽就更多,这时就会影响到系统的吞吐量。

我会考虑用主流序列化框架,比如FastJson、Protobuf来替代Java 序列化。

如果追求性能的话,Protobuf序列化框架会比较合适,Protobuf 的这种数据存储格式,不仅压缩存储数据的效果好,在编码和解码的性能方面也很高效。Protobuf 的编码和解码过程结合.proto文件格式,加上 Protocol Buffer独特的编码格式,只需要简单的数据运算以及位移等操作就可以完成编码与解码。可以说 Protobuf 的整体性能非常优秀。

有一个学生类,想按照分数排序再按照学号排序,应该怎么做

学生类实现Comparable接口,重写compareTo方法,,使用时直接用Collections.sort(Student);即可

public class Student implements Comparable<Student> { private int id; private int score; //构造方法和其他属性、方法省略 @override public int compareTo(Student other) { if (this.score != other.score) { return Integer.compare(other.score, this.score); } else { return Integer.compare(this.id,other.id);// 如果 } } } List<Student> students = new ArrayList<>();//添加学生对象到列表中 Collections.sort(students);

volatile和sychronized如何实现单例模式

public class SingleTon { // volatile 关键字修饰变量 防止指令重排序 private static volatile SingleTon instance = null; private SingleTon(){} public static SingleTon getInstance(){ if(instance == null){ //同步代码块只有在第一次获取对象的时候会执行到,第二次及以后访问时instance变量均非null故不会往下执行了 直接返回啦 synchronized(SingleTon.class){ if(instance == null){ instance = new SingleTon(); } } } return instance; } }

代理模式和适配器模式有什么区别?

目的不同:代理模式主要关注控制对对象的访问,而适配器模式则用于接口转换,使不兼容的类能够一起工作。

结构不同:代理模式一般包含抽象主题、真实主题和代理三个角色,适配器模式包含目标接口、适配器和被适配者三个角色。

应用场景不同:代理模式常用于添加额外功能或控制对对象的访问,适配器模式常用于让不兼容的接口协同工作

介绍一下策略模式和责任链模式,分别用在哪些场景?

策略模式定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。从而让算法可独立于使用它的客户而变化。

假设你正在开发一个电商网站的支付模块。用户可以选择支付宝、微信支付或者银行卡支付。每种支付方式的底层逻辑(比如调用的API、加密方式、参数构造)都完全不同,但对于调用支付功能的上层业务逻辑来说,它只需要调用一个统一的“支付”方法即可,无需关心具体是哪种支付。

这就是策略模式的用武之地。我们把每种支付方式(支付宝、微信)都封装成一个独立的“策略”类,它们都遵循一个共同的接口。上层业务代码通过这个共同的接口来调用支付功能,并可以在运行时根据用户的选择,动态地切换不同的策略。

所以,策略模式适用场景如下

  • 当一个系统需要动态地在几种算法中选择一种时。
  • 当一个对象有很多行为,不想使用大量的if-else或switch-case语句来选择时。
  • 当你希望算法的变化不会影响到使用它的客户端时。

责任链模式核心思想是为请求创建一个接收者对象的链。对请求的发送者和接收者进行解耦,让多个对象都有机会处理这个请求。请求沿着链传递,直到链上的一个对象处理它为止。

想象一下公司里的请假审批流程。一个员工要请假,他首先会把请假条交给自己的直属经理。如果请假天数很短(比如1天),经理自己就能批准。如果天数较长(比如5天),经理没有权限,就需要把请假条上报给部门总监。总监也有自己的审批权限上限,如果超过了,就需要继续上报给总经理。

在这个过程中,请假条(请求)就在一条由经理->总监->总经理(接收者链)上传递,直到某个角色(接收者)能够处理它。发送请假条的员工(客户端)并不知道最终是谁批准了他的假,他只需要把请求提交给链的第一个节点(经理)即可。

所以,责任链模式适用场景总结:

  • 当有多个对象可以处理同一个请求,但具体由哪个对象处理需要在运行时决定时。
  • 当你希望请求的发送者和接收者解耦,不直接通信时。
  • 当你需要动态地指定和修改处理一个请求的对象集合时。

Native方法解释一下

在Java中,native方法是一种特殊类型的方法,它允许Java代码调用外部的本地代码,即用C、C++或其他语言编写的代码。native关键字是Java语言中的一种声明,用于标记一个方法的实现将在外部定义。

在Java类中,native方法看起来与其他方法相似,只是其方法体由native关键字代替,没有实际的实现代码。例如:

public class NativeExample { public native void nativeMethod(); }

要实现native方法,你需要完成以下步骤:

1.生成JNI头文件:使用javah工具从你的Java类生成C/C++的头文件,这个头文件包含了所有native方法的原型。

2.编写本地代码:使用C/C++编写本地方法的实现,并确保方法签名与生成的头文件中的原型匹配。

3.编译本地代码:将C/C++代码编译成动态链接库(DLL,在Windows上),共享库(SO,在Linux上)

4.加载本地库:在Java程序中,使用System.loadLibraryO方法来加载你编译好的本地库,这样VM就能找到并调用native方法的实现了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询