转载

Java面试题-->Java基础专题(二)

Java面试题-->Java基础专题(二)

导读

String、StringBuffer与StringBuilder、

HashCode、equals与==、

final关键字、

static关键字

1、String和StringBuffer、StringBuidler

1.1 可变性

String类中使用final关键字修饰的字符数组保存字符串 private final char value[] ,所以String对象是不可变的。

而StringBuidler与StringBuffer都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符串数组保存字符串 char value[] 但是由于没有用final关键字修饰,所以这两个类都是可变的。

1.2 线程安全性

  • String中的对象是不可变的,也就是可以理解为常量,线程安全。

  • AbstractStringBuilder 是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。

  • StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。

  • StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

1.3 性能

每次对String类型进行改变的时候,都会生成一个新的String对象,然后将地址引用指向新的String对象。

StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StingBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒着多线程不安全的风险。

1.4 对于三者的使用总结

  1. 操作少量的数据:String

  2. 单线程在字符串缓冲区下操作大量数据:StringBuidler

  3. 多线程在字符串缓冲区下操作大量数据:StringBuffer

2、自动装箱与拆箱

装箱 :将基本类型用它们对应的引用类型包装起来;

拆箱 :将包装类型转换为对应的基本数据类型;

3、HashCode、equals与==

3.1 HashCode()介绍

​ hashcode()的作用是获取哈希码,也成为散列码。它实际上返回的是一个int类型的整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashcode()定义在JDK的Object类中,这就意味着Java中任何类都包含有hashcode()函数。另外需要注意的是:Object的hashcode方法是本地方法,也就是用c或c++实现的,该方法通常用来将对象在内存中的内存地址转换为整数之后返回。

3.2 为什么需要hashCode

以"HashSet如何保证唯一"为例子来说明为什么要有hashcode:

当你把对象加入HashSet时,jvm会先去检索HashCode方法,如果返回值是相等的,进而调用equals方法比较内容,如果内容(各种字段值)相同,那么返回true,数据就不往里面添加,因为里面已经存有该实例,如果返回是false就说明,是不同的实例对象,就往里面存。

因此,保证每个类的HashCode的值的唯一性,有助于提高效率,这也就是下面第三条约定所谈及的内容,因为,一旦判断出来hashcode不同,就相当于不用比较equals里面的字段,直接存入即可。因此提高了效率。

3.3 hashCode()与equals()的相关规定

  1. 如果两个对象相等,则hashcode一定也是相同的。

  2. 两个对象相等,对两个对象分别调用equals方法都返回true。

  3. 两个对象有相同的hashcode值,它们也不一定是相等的。

  4. 因此,equals方法被重写过,则hashcode方法也必须被重写。

  5. hashcode()的默认行为是对堆内存上的对象产生独特值。如果没有重写hashcode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

3.4 为什么两个对象有相同的hashCode值,它们也不一定相等的?

hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的hashCode)。在HashSet中,在作对比的时候,同样的hashCode有多个对象,它会使用equals()来判断是否真的相同。也就是说hashCode只是用来缩小查找成本的。

3.5 ==与euqals

  • ==:它的作用是判断两个对象的地址是不是相等。即:判断两个对象是不是同一个对象。(基本数据类型"=="比较的是值,引用数据类型"=="比较的是内存地址)

  • **equals():**它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

    1. 类没有重写equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。

    2. 类重写了equals()方法。一般,我们都通过重写equals()方法来比较两个对象的内容是否相等;如果内容相等,则返回true(即,认为这两个对象相等)。

说明:

​ String中的equals方法是被重写过的,因为Object类的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。

​ 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重写创建一个String对象。

4、关于final关键字的一些总结

final关键字主要用在三个地方:变量、方法、类。

  1. 对于一个final变量:
    1. 如果是基本数据类型的变量,则其数组一旦初始化以后就不能更改;

    2. 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

  2. 当用final修饰一个类时,表面这个类不能被继承。final类中的所有成员方法都会被隐式的指定为final方法。

  3. 使用final修饰方法的原因:
    1. 把方法锁定,以防任何继承类修改它的含义

    2. 效率:在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。

5、接口和抽象类的区别

  1. 接口的方法默认是public,所有的方法在接口中不能有实现(Java8开始接口方法可以有默认实现),抽象类可以有非抽象的方法。

  2. 接口中的实例变量默认是final类型的,而抽象类中则不一定。

  3. 一个类可以实现多个接口,但最多只能实现一个抽象类。

  4. 一个类实现接口的话要实现接口中的所有方法,而抽象类不一定。

  5. 接口不能用new 实例化,但可以声明,但是必须引用一个实现该接口的对象;从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,必须重写,不然会报错。

6、static关键字

6.1 修饰成员变量和成员方法

  1. 被static修饰的成员变量属于静态成员变量,静态变量存放在Java内存区域的方法区中。

    调用格式: 类名.静态变量名

  2. 被static修饰的成员方法属于类,不属于这个类的某个对象,被类中所有的对象共享,并且建议通过类名调用。

    调用格式: 类名.静态方法名()

6.2 静态代码块

静态代码块定义在类中方法外,静态代码块在非静态代码块之间执行(静态代码块-->非静态代码块-->构造方法)。该类不管创建多少对象, 静态代码块只执行一次

静态代码块的格式是: static{语句体;}

一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问。

静态代码块定义在类中方法外,静态代码块在非静态代码块之前执行(静态代码块-->非静态代码块-->构造方法)。

该类不管创建多少对象,静态代码块只执行一次。

6.3 静态内部类(static修饰类的话,只能修饰内部类)

​ 静态内部类与非静态内部类之间存在一个巨大的区别:非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:

  1. 它的创建是不需要依赖外围类的创建。

  2. 它不能使用任何外围类的非static成员变量和方法。

6.4 静态导包(用来导入类中的静态资源,1.5之后的新特性)

​ 格式为: import static

​ 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

6.5 静态方法和非静态方法

​ 静态方法属于类本身,非静态方法属于从该类生成的每个对象。如果你的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。否则,它应该是非静态的。

总结:

  • 在外部调用静态方法时,可以使用“ 类名.方法名 ”的方式,也可以使用“ 对象名.方法名 ”的方式。而实例(非静态)方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。

  • 静态方法在访问本类的成员时, 只允许访问静态成员 (即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例(非静态)方法则没有这个限制。

6.6 静态代码块和非静态代码块

相同点:都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。

不同点:静态代码块在非静态代码块之前执行(静态代码块->非静态代码块->构造方法)。静态代码块只在第一次new 的时候执行一次,之后不再执行,而非静态代码块每new一次就执行一次。非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。

一般情况下,如果有些代码,比如一些项目最常用的变量或对象必须在项目启动的时就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法,两者的区别是:静态代码块是自动执行的而静态方法是被调用的时候才执行的。

6.7 非静态代码块和构造函数

​ 非静态代码块与构造函数的区别是:

​ 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。

​ 因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化的内容。

6.8 在一个静态方法内调用一个非静态成员为什么是非法的

​ 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。

最后的最后,欢迎关注我的个人博客社区: GyCode

原文  https://juejin.im/post/5e01e5656fb9a01636235731
正文到此结束
Loading...