1.说一说hashCode()和equals()的关系
hashCode()用于获取哈希码,equals()用于比较两个对象是否相等,他们应遵循如下规定:
1.如果两个对象相等,则它们必须拥有相等的哈希码
2.如果两个对象有相等的哈希码,但是他们未必相等
2.为什么要重写hashCode()和equals()
Object类的equal方法默认是用==来进行比较的,也就是两个对象是同一个对象的时候,才能返回相等的结果。但是实际业务中,我们通常的需求是:若两个不同的对象他们的内容是相等的,就认为是相等的,所以通常需要重写
由于hashCode()与equals()具有联动关系,所以equals()重写的时候,一般都要重写hashCode()重写,让两个方法满足约定关系
3.==和equals()的比较
==:
1.既可以判断基本类型,又可以判断引用类型
2.若判断基本类型,判断数值是否相等
3.若判断引用类型,判断的是地址值是否相等,是否是同一个对象
equals:
只能判断引用类型,默认判断地址值是否相等,子类往往重写该方法判断内容是否相等
4.String类有哪些方法
比较常用的有:
1.char charAt(int index) : 返回指定索引处的字符
2.String subString(int beginIndex , int endIndex) : 从此字符串中截取出一部分子串
3.String[] split(String regex) : 以指定的规则把这个字符串分割成数组
4.String trim() :删除字符串前导和后置的空格
5.int indexof(String str) : 返回子串在此字符串首次出现的索引
6.int lastIndexOf(String str) : 返回子串在此字符串最后出现的索引
7.boolean startsWith(String prefix) : 判断此字符串是否以指定的前缀开头
8.boolean endsWith(String suffix) : 判断此字符串是否以指定的后缀结尾
9.String toUpperCase() : 所有字符大写
10.String toLowerCase() : 所有字符小写
11.String replaceFirst(String regex , String replacement) : 用指定字符串替换第一个匹配的子串
12.String replaceAll(String regex , String replacement) : 用指定字符串替换所有匹配的子串
5.String可以被继承吗
String类被final修饰,是不能被继承的
String类中,
在java 9 之前:private final char[] value 用来存放字符串内容,但是value的地址值不能修改
在java 9 之后 : private final byte[] value
6.String和StringBuffer有什么区别
String类是一个不可变类,String保存的是字符串常量,里面的值不可以修改,每次String类更新实际变的是地址
StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer创建后,通过一系列API等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换成一个String对象
7.说一声StirngBuffer和StringBuilder有什么区别
StringBuffer和StringBuilder都代表可变的字符串对象,它们有共同的父类AbstractAStringBuilder,并且两个类的构造方法和成员方法基本相同。不同的是,StringBuffer是线程安全的,而StringBuilder是非线程安全的,StringBuffer是非线程安全的,所以StirngBuilder性能略高
8.使用字符串时,new和“”推荐使用哪种方式
首先看"hello"和new String(“hello”)的区别:
1.当java直接直接使用"hello"的字符串直接量时,JVM会使用常量池来管理这个字符串,最终引用指向的是常量池的空间
2.当使用 new String(“hello”)时,JVM会先使用常量池来管理"hello"这个直接量,再调用String类的构造器来创建一个新的String对象,也就是在堆中开辟了一个空间,该引用指向这个空间,然后这个空间维护了一个value属性,这个属性指向的是常量池的"hello"的空间,所以new的方式引用最终指向的是堆空间地址
显然,new的方式会多创建一个对象,会占用更多内存,所以一般建议直接使用直接量的方式来创建字符串
9.对字符串拼接的理解
1.+运算符 :如果拼接的都是字符串常量,则适合使用+运算符实现拼接
1.如果拼接的都是字符串常量,则在编译时编译器都会把其优化成一个完整的字符串,和自己直接写的一个完整的字符串是一样的,所以效率高
2.如果拼接的字符串中包含变量,则在编译时编译器采用StringBuilder对其进行优化,则自动创建StringBuilder实例并调用append()方法,将这些字符串拼接在一起,所以效率也高。但是如果这个拼接是在循环中进行的,那么每次循环编译器都创建一个StringBuidler实例,再去拼接,会导致效率低
2.StringBuilder :如果拼接的字符串中包含变量,并且不要求线程安全,则适合使用StringBuidler
3.StringBuffer :如果拼接的字符串中包含变量,并且要求线程安全,则适合使用StringBuffer
在采用这两个拼接字符串时:
1.StringBuilder和StringBuffer都有字符串缓冲区,缓冲区的容量在创建对象时确定,默认是16.当拼接的字符串超过缓冲区的容量时,会触发缓冲区的扩容机制,也就是缓冲区加倍
2.缓冲区频繁的扩容会降低拼接的性能,所以如果能提前预估最终字符串的长度,则建议在创建可变字符串对象时放弃使用默认的容量,可以指定缓冲区的容量为预估字符串的长度
4.String类的concat方法,如果只是对两个字符串进行拼接,并且包含变量,则适合使用concat方法
1.concat的拼接逻辑是,先创建一个足以容纳待拼接的两个字符串的字节数组,然后先后将两个字符串拼到这个数组里,最后把这个数组转换成字符串
2.在拼接大量字符串时,concat的方法低于StringBuidler。但是只拼接2个字符串时候,concat效率更好一点
10.两个字符串相加的底层是如何实现的
如果拼接的是两个字符串直接量,则在编译时编译器会把其直接优化成一个完整的字符串
如果拼接的字符串中包含变量,编译时编译器采用StringBuilder对其进行优化,则自动创建其实例调用append()方法
11.String a = "abc"说一说这个过程
先从常量池查看是否有"abc"的数据空间,若有则直接指向,若无则重新创建,然后指向,a最终指向的是常量池的空间地址
12.new String(“abc”)是去了哪里,仅仅是在堆吗
首先JVM在常量池来管理字符串直接量"abc",然后创建在堆中开辟一个空间,也就是一个对象,引用指向这个对象,在这个对象空间中有vlaue指向常量池中的"abc"
13.接口和抽象类的区别
设计目的来看:
接口体现的是一种规范,抽象类体现的是一种模板设计
从使用方式来看:
1.接口只能包含抽象方法、静态方法、默认方法和(私有方法),不能为普通方法提供具体实现,抽象类可以包含普通方法
2.接口只能定义静态常量,也就是说 int a = 1 => public static final int a = 1,不能定义普通成员变量,抽象类可以定义静态常量也可以定义普通成员变量
3.接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器不是用于创建对象的(抽象类不能实例化),而是让其子类嗲用这些构造器来完成初始化操作
4.接口不能包含初始化块,抽象类完全可以包含初始化块
5.一个类最多只能有一个直接父类,包括抽象类,但是一个类可以直接实现多个接口来弥补java单继承的不足
共同特征:
1.接口和抽象类都不能被实例化,他们都位于继承树的顶端,用于被其他类继承和实现
2.接口和抽象类都可以包含抽象方法,继承和实现的子类必须实现这些抽象方法
细节:
抽象类:
1.抽象类不能被实例化
2.抽象类不一定有抽象方法,但是有抽象方法就一定是抽象类
3.abstract只能修饰类和方法,不能修饰其他的
4.抽象类可以有任意的成员
5.抽象类的抽象方法不能具体实现(不能带{})
6.另一个类继承抽象类必须实现抽象方法,除非自己也声明为抽象类
7.抽象方法不能使用private final static等修饰,和重写是违背的
接口:
1.接口不能被实例化
2.接口中所有方法都是public的,接口中的抽象方法可以不适用abstract修饰,也就是说 void a() => public abstarct void a()
3.一个普通类实现接口,就必须把该接口所有方法都实现
4.抽象类实现接口,可以不用实现接口的方法
5.一个类可以实现多个接口
6.接口中的属性是final的
7.接口中的属性访问形式: 接口名.属性
8.接口的修饰符只能是public 和 默认
14.对面向接口编程的理解
接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以很好降低各模块之间的耦合,充分提高系统的可扩展性和可维护性。基于这种原则很多软件架构设计都倡导“面向接口”编程而不是面向实现类编程