前言
第一次写博客,先说下个人背景吧。本人是华中某985本25届应届生,专业是某工科,从今年初正式开始转码,跨选了一些软工专业的核心课,也不断通过看书看视频读文章等方式自学了一些计算机知识,最近在冲刺秋招,遂打算用写博客的方式来帮助自己复习巩固,完善知识体系。
第一篇博客先简单整理一下一些Java的基础知识点。
ps:本文内容均由书籍、网络等方式整理而来,会加入一些个人见解,如有侵权可以联系我删除。如有错误也欢迎批评指正。
Java和C++的区别
联系:
都是面向对象的编程语言,都支持继承、封装、多态。
区别:
- C++ 支持指针,而 Java 没有指针的概念;
- C++ 支持多继承,而 Java 不支持多重继承,但允许一个类实现多个接口;
- Java 是完全面向对象的语言,还取消了 C/C++ 中的结构和联合,使编译程序更加简洁;
- Java 自动进行无用内存回收操作,不再需要程序员进行手动删除(jvm的垃圾回收机制来保证),而 C++ 中必须由程序释放内存资源,这就增加了程序员的负担。
- Java 不支持操作符重载,操作符重载则被认为是 C++ 的突出特征;
- Java 允许预处理,但不支持预处理器功能,所以为了实现预处理,它提供了引入语句(import),但它与 C++ 预处理器的功能类似;
- Java 不支持缺省参数函数,而 C++ 支持;
- C 和 C++ 不支持字符串变量,在 C 和 C++ 程序中使用“Null”终止符代表字符串的结束。在 Java 中字符串是用类对象(String 和 StringBuffer)来实现的;
- goto 语句是 C 和 C++ 的“遗物”,Java 不提供 goto 语句,虽然 Java 指定 goto 作为关键字,但不支持它的使用,这使程序更简洁易读;
- Java 不支持 C++ 中的自动强制类型转换,如果需要,必须由程序显式进行强制类型转换。
面向对象的三大特征
封装
封装是将对象的属性和方法封装在一起,隐藏对象的内部实现细节,只暴露必要的接口。
- 通过访问修饰符(如private、protected、public)来控制对属性和方法的访问权限。
- 通过封装,类的内部数据可以被保护不受外部干扰和误用
继承
继承是创建新类(子类)的过程,子类继承父类的属性和方法,从而实现代码重用。
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展(extends关键字)。
- 子类重写父类的方法。
多态
多态允许不同类型的对象对同一消息做出响应。在Java中,多态可以通过继承、接口和重写方法来实现。
多态其实就是一种能力——同一个行为具有不同的表现形式或形态;
换句话说就是,执行一段代码,Java 在运行时能根据对象的不同产生不同的结果。
重载和重写的区别
首先,他们都是多态的表现。
重载
重载也叫做编译时多态,是一个类的多态性表现,指同一个类中多个方法具有相同的名字但参数列表不同。
重载发生在本类,方法名相同,参数列表不同,与返回值无关,只和方法名,参数的类型相关。
这里举个例子:
重写
重写也叫做运行时多态,是子类与父类的一种多态性表现,子类提供了父类方法的具体实现,指子类对父类方法的一种重新定义。
重写后的方法和原方法需要保持完全相同的返回值类型、方法名、参数个数以及参数类型,
这里也举个例子:
引用一下有人整理过的一个表格:
深拷贝、浅拷贝、引用拷贝的区别
浅拷贝 只是复制对象的符号引用,复制的对象和原对象共同指向同一个内存地址。
只有基本类型的字段会被复制,而引用类型的字段仍然指向原对象中的引用。
可以通过实现Cloneable接口和重写clone()方法来实现浅拷贝。
深拷贝 会从堆上完全复制整个对象,包括这个对象所包含的内部对象。
需要手动实现所有对象的复制,通常通过实现Cloneable接口,并在clone()方法中手动复制引用类型的字段。
引用拷贝 是将对象的引用复制给新的变量,两个变量指向同一个对象。
任何一个变量的改变都会影响另一个变量,因为它们引用的是同一个对象。
@javaguide整理的图,很生动形象
== 和 equals() 的区别
==:
对于基本数据类型来说,比较的是值。
对于引用数据类型来说,比较的是对象的内存地址。
equals():
不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。
(ps:equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法)
为什么重写 equals() 时必须重写 hashCode() ⽅法?
- hashCode() 的作⽤是获取哈希码;它实际上是返回⼀个 int 整数。这个哈希码的作⽤是确定该对象在哈希表中的索引位置。
- 如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals ⽅法都返回 true。
- 但是,两个对象有相同的 hashcode 值,它们也不⼀定是相等的。(因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值,这就是所谓的哈希碰撞)因此, equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖
hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode() ,
则该 class 的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据
String、StringBuffer、StringBuilder 的区别?
可变性
- String 是不可变的,不可修改。
- StringBuilder与StringBuffer 都继承自 AbstractStringBuilder 类,可变。
线程安全性
- String 中的对象是不可变的,也就可以理解为常量,线程安全。
- StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
- StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
性能
- String 性能差:每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
- StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
- 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
总结:
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
反射
对反射的理解
- 是一种在运行时动态获取类的信息并调用类的方法或访问类的字段的机制。
- 反射允许程序在运行时检查或修改自身的行为,而不需要在编译时知道特定类的信息。(这在某些情况下非常有用,如当需要在不知道类名称的情况下创建实例或调用方法。)
- 通过反射可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。
反射的主要用途
- 动态加载类: 在运行时加载类,而不需要在编译时指定类名。
- 创建实例: 动态地创建类的实例。
- 访问字段和方法: 即使是私有字段和方法也可以在运行时访问。
- 修改类的行为: 在运行时更改类的行为,例如修改方法实现。
- 正是因为反射才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
其中:
- 这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
- 注解 的实现也用到了反射
异常
对异常的理解
异常(Exception)是一种在程序运行期间出现的问题,可以是由错误的输入数据、硬件故障、代码逻辑错误等原因引起的。
通常有三种:
- Checked Exception(受检异常):编译时检查的异常,必须要处理,例如IOException、SQLException。(Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。)
- Unchecked Exception(未检异常):运行时异常,不强制处理,例如NullPointerException、ArrayIndexOutOfBoundsException。
- Error:严重错误,通常不需要捕获,例如OutOfMemoryError。程序是没办法处理的。
怎么处理异常
Java通过异常处理机制来捕获和处理这些问题,保证程序的健壮性和可靠性。异常处理的关键字有三个:try、catch和finally
- try:包含可能引发异常的代码。如果在try块中发生异常,异常会被抛出。
- catch:用于捕获try块中抛出的特定类型的异常。可以有多个catch块来处理不同类型的异常。
- finally:无论是否发生异常,finally块中的代码都会被执行,通常用于释放资源(例如关闭文件、网络连接等)
这里也举个例子:
接口和抽象类的区别
接口(Interface),在 Java 语言中是一个抽象类型,是抽象方法的集合,接口通常用 interface 来声明。一个类通过继承接口的方式,从而来继承并且实现接口中的抽象方法。
抽象类:
被abstract关键字修饰的类叫抽象类。被abstract关键字修饰的方法叫抽象方法
抽象方法的产生完全是为了迎合抽象类的存在:抽象方法只能写在抽象类中。
抽象类的字段只要像正常的继承关系那样使用就可以。
共同点:
- 都不能被实例化。
- 都可以包含抽象方法。
- 都可以有默认实现的方法。
区别:
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
- 一个类只能继承一个类,但是可以实现多个接口。
- 接口中的成员变量只能是 public 、static、 final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
Java的代理模式
在Java中,代理模式(Proxy Pattern)是一种设计模式,用于为其他对象提供代理,以控制对这个对象的访问。代理模式主要有两种实现方式:静态代理和动态代理
静态代理
- 静态代理是在编译时确定的代理类。
- 代理类实现与目标对象相同的接口,并在代理类的方法中调用目标对象的方法。
- 静态代理的缺点是需要为每个目标类手动编写代理类,增加了代码量。
动态代理
动态代理是在运行时生成代理类。
JDK 动态代理:
- JDK动态代理使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。
- 它只能代理实现了接口的类。
- 通过反射来接收被代理的类
CGLIB动态代理:
- CGLIB动态代理使用字节码生成技术,可以代理没有实现接口的类。
- CGLIB通过继承目标类来创建代理类(子类),因此不能代理final类和final方法。
注解
- 注解(Annotations)是看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
- 注解本质是一个继承了Annotation 的特殊接口:
注解这个概念在Spring框架中也很常见,以下就是Spring框架中常用的两个注解:
@AutoWired和@Resource的区别:
联系
- @Autowired和@Resource注解都是作为bean对象( Spring 框架中被实例化、管理和维护的对象)注入的时候使用的
- 两者都可以声明在字段和setter方法上
注意:如果声明在字段上,那么就不需要再写setter方法。但是本质上,该对象还是作为set方法的实参,通过执行set方法注入,只是省略了setter方法罢了
区别
- @Autowired注解是Spring提供的,而@Resource注解是J2EE本身提供的
- @Autowird注解默认通过byType方式注入,而@Resource注解默认通过byName方式注入
- @Autowired注解注入的对象需要在IOC容器中存在,否则需要加上属性required=false,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。
泛型
- Java 的泛型(Generics)是一种编程机制,可以在定义类、接口和方法时使用类型参数,从而使代码更加通用和可重用。
- 泛型允许类、接口和方法在被使用时指定具体的类型,而不是在定义时就固定下来。这提高了代码的类型安全性,并减少了类型转换的需要。
- 编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。
- 泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
@javaguide上举的一个泛型类的例子