目录
一. 面向对象
1. 面向对象和面向过程的区别?
2. 面向对象三大特性
3. Java语言是如何实现多态的?
4. 重载(Overload)和重写(Override)的区别是什么?
5. 重载的方法能否根据返回值类型进行区分?
6. 构造器(constructor)是否可被重写(override)?
7. 抽象类和接口的区别是什么?
8. 抽象类能使用 final 修饰吗?
9. java 创建对象有哪几种方式?
10. 什么是不可变对象?好处是什么?
11. 能否创建一个包含可变对象的不可变对象?
12. 值传递和引用传递的区别的什么?为什么说Java中只有值传递?
13. Object有哪些方法
二、对象相等判断
1. == 和 equals 区别是什么?
2. 介绍下hashCode()?
3. 为什么要有 hashCode?
4. hashCode(),equals()两种方法是什么关系?
5. 为什么重写 equals 方法必须重写 hashcode 方法 ?
三、String相关
1. String,StringBuffer, StringBuilder 的区别是什么?
2. String为什么要设计成不可变的?
3. 字符型常量和字符串常量的区别?
4. 什么是字符串常量池?
5. String str="aaa"与 String str=new String("aaa")一样吗?new String(“aaa”);创建了几个字符串对象?
6. String 是最基本的数据类型吗?
7. String有哪些特性?
8. 在使用 HashMap 的时候,用 String 做 key 有什么好处?
一. 面向对象
1. 面向对象和面向过程的区别?
面向过程:
-
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
-
缺点:没有面向对象易维护、易复用、易扩展。
面向对象:
-
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
-
缺点:性能比面向过程低。
2. 面向对象三大特性
-
封装。封装是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
-
继承。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
-
多态。它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
3. Java语言是如何实现多态的?
本质上多态分两种:
1、编译时多态(又称静态多态)
2、运行时多态(又称动态多态)
重载(overload)就是编译时多态的一个例子,编译时多态在编译时就已经确定,运行的时候调用的是确定的方法。
我们通常所说的多态指的都是运行时多态,也就是编译时不确定究竟调用哪个具体方法,一直延迟到运行时才能确定。这也是为什么有时候多态方法又被称为延迟方法的原因。
Java实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
-
继承:在多态中必须存在有继承关系的子类和父类。
-
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
-
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。 Animal a=new Dog();//向上转型,父类引用指向子类的对象
-
向下转型,经过查询网上的资料,个人觉得这句总结的比较清楚 ,“子类对象指向父类引用"。例:
Father f=new Son() //先向上转型
Son s=(Son) f;//再进行向下转型,进行强制转换,子类引用s,父类引用f
向下转型的条件:先向上转型,再向下转型。没有经过向上转型,会提示编译错误。
4. 重载(Overload)和重写(Override)的区别是什么?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
-
重写发生在子类与父类之间, 重写方法返回值和形参都不能改变,与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分。即外壳不变,核心重写!
-
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。
5. 重载的方法能否根据返回值类型进行区分?
不能根据返回值类型来区分重载的方法。因为调用时不指定类型信息,编译器不知道你要调用哪个函数。
float max(int a, int b);
int max(int a, int b);
当调用max(1,2);
时无法确定调用的是哪个,单从这一点上来说,仅返回值类型不同的重载是不应该允许的。
6. 构造器(constructor)是否可被重写(override)?
构造器不能被继承,因此不能被重写,但可以被重载。每一个类必须有自己的构造函数,负责构造自己这部分的构造。子类不会覆盖父类的构造函数,相反必须一开始调用父类的构造函数。
7. 抽象类和接口的区别是什么?
设计层面上的区别:
-
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
-
设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
语法层面上的区别:
-
抽象类使用abstract修饰 ,接口使用interface修饰。
-
抽象类可以有构造方法,接口中不能有构造方法。
-
抽象类中可以有静态方法,接口中不能有静态方法。
-
抽象类中可以有普通方法,接口中所有方法都必须是抽象的。
-
抽象类中可以有成员变量,接口中没有成员变量。(被final修饰变成了常量)
-
抽象类中抽象方法的访问类型可以是public,protected,但接口中抽象方法的访问类型只能是public,并且默认为public abstract(省略则自动默认补全)。
-
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
8. 抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
9. java 创建对象有哪几种方式?
java中提供了以下四种创建对象的方式:
-
new创建新对象
-
通过反射机制
-
采用clone机制
-
通过序列化机制
前两者都需要显式地调用构造方法。对于clone机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在java中序列化可以通过实现Externalizable或者Serializable来实现。
10. 什么是不可变对象?好处是什么?
不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象,如 String、Integer及其它包装类.不可变对象最大的好处是线程安全.
11. 能否创建一个包含可变对象的不可变对象?
当然可以,比如final Person[] persons = new Persion[]{}
. persons
是不可变对象的引用,但其数组中的Person实例却是可变的.这种情况下需要特别谨慎,不要共享可变对象的引用.这种情况下,如果数据需要变化时,就返回原对象的一个拷贝.
12. 值传递和引用传递的区别的什么?为什么说Java中只有值传递?
值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的是引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
基本类型作为参数被传递时肯定是值传递;引用类型作为参数被传递时也是值传递,只不过“值”为对应的引用。
13. Object有哪些方法
getClass() //返回此 Object 的运行类。
hashCode() //用于获取对象的哈希值。
equals(Object obj) //用于确认两个对象是否“相同”。
clone() //创建并返回此对象的一个副本。
toString() //返回该对象的字符串表示。
notify() //唤醒在此对象监视器上等待的单个线程。
notifyAll() //唤醒在此对象监视器上等待的所有线程。
wait() //用于让当前线程失去操作权限,当前线程进入等待序列
finalize() //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
二、对象相等判断
1. == 和 equals 区别是什么?
-
==
-
对于基本数类型,== 比较值
-
对于引用数据类型, == 比较内存地址
-
-
equals
-
没有重写 equals 时,使用 == 比较地址
-
可以重写为比较值
-
==
常用于相同的基本数据类型之间的比较,也可用于相同类型的对象之间的比较;
-
如果
==
比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等; -
如果
==
是比较的两个对象,那么比较的是两个对象的引用,也就是判断两个对象是否指向了同一块内存区域;
equals方法主要用于两个对象之间,检测一个对象是否等于另一个对象
看一看Object类中equals方法的源码:
public boolean equals(Object obj) {return (this == obj);
}
它的作用也是判断两个对象是否相等,般有两种使用情况:
-
情况1,类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
-
情况2,类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
java语言规范要求equals方法具有以下特性:
-
自反性。对于任意不为null的引用值x,x.equals(x)一定是true。
-
对称性。对于任意不为null的引用值x和y,当且仅当x.equals(y)是true时,y.equals(x)也是true。
-
传递性。对于任意不为null的引用值x、y和z,如果x.equals(y)是true,同时y.equals(z)是true,那么x.equals(z)一定是true。
-
一致性。对于任意不为null的引用值x和y,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)要么一致地返回true要么一致地返回false。
-
对于任意不为null的引用值x,x.equals(null)返回false。
2. 介绍下hashCode()?
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
3. 为什么要有 hashCode?
以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。
但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
4. hashCode(),equals()两种方法是什么关系?
要弄清楚这两种方法的关系,就需要对哈希表有一个基本的认识。其基本的结构如下:
对于hashcode方法,会返回一个哈希值,哈希值对数组的长度取余后会确定一个存储的下标位置,如图中用数组括起来的第一列。
不同的哈希值取余之后的结果可能是相同的,用equals方法判断是否为相同的对象,不同则在链表中插入。
则有hashCode()与equals()的相关规定:
-
如果两个对象相等,则hashcode一定也是相同的;
-
两个对象相等,对两个对象分别调用equals方法都返回true;
-
两个对象有相同的hashcode值,它们也不一定是相等的;
5. 为什么重写 equals 方法必须重写 hashcode 方法 ?
在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。
判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。
三、String相关
1. String,StringBuffer, StringBuilder 的区别是什么?
(1) 可变与不可变。String类中使用字符数组保存字符串,因为有“final”修饰符,所以string对象是不可变的。对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.
不可变是地址不能修改,指向的内容是可以改变的
String类利用了final修饰的char类型数组存储字符
private final char value[];
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,这两种对象都是可变的。
char[] value;
(2) 是否多线程安全。
String中的对象是不可变的,也就可以理解为常量,显然线程安全。
StringBuilder是非线程安全的。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
@Override
public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}
(3) 性能
每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
2. String为什么要设计成不可变的?
(1)便于实现字符串池(String pool)
在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。
String a = "Hello world!";
String b = "Hello world!";
如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!
(2)使多线程安全
在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。
(3)避免安全问题
在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
(4)加快字符串处理速度
由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。
总体来说,String不可变的原因要包括 设计考虑,效率优化,以及安全性这三大方面。
3. 字符型常量和字符串常量的区别?
(1)形式上: 字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符;
(2)含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算;字符串常量代表一个地址值(该字符串在内存中存放位置,相当于对象;
(3)占内存大小:字符常量只占2个字节;字符串常量占若干个字节(至少一个字符结束标志) (注意: char 在Java中占两个字节)。
4. 什么是字符串常量池?
java中常量池的概念主要有三个:全局字符串常量池
,class文件常量池
,运行时常量池
。我们现在所说的就是全局字符串常量池
,对这个想弄明白的同学可以看这篇Java中几种常量池的区分。
jvm为了提升性能和减少内存开销,避免字符的重复创建,其维护了一块特殊的内存空间,即字符串池,当需要使用字符串时,先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,如果不存在,初始化,并将该字符串放入字符串常量池中。
字符串常量池的位置也是随着jdk版本的不同而位置不同。在jdk6中,常量池的位置在永久代(方法区)中,此时常量池中存储的是对象。在jdk7中,常量池的位置在堆中,此时,常量池存储的就是引用了。在jdk8中,永久代(方法区)被元空间取代了。
5. String str="aaa"与 String str=new String("aaa")一样吗?new String(“aaa”);
创建了几个字符串对象?
-
使用
String a = “aaa” ;
,程序运行时会在常量池中查找”aaa”字符串,若没有,会将”aaa”字符串放进常量池,再将其地址赋给a;若有,将找到的”aaa”字符串的地址赋给a。 -
使用String b = new String("aaa");`,程序会在堆内存中开辟一片新空间存放新对象,同时会将”aaa”字符串放入常量池,相当于创建了两个对象,无论常量池中有没有”aaa”字符串,程序都会在堆内存中开辟一片新空间存放新对象。
具体分析,见以下代码:
@Test
public void test(){String s = new String("2");s.intern();String s2 = "2";System.out.println(s == s2);String s3 = new String("3") + new String("3");s3.intern();String s4 = "33";System.out.println(s3 == s4);
}
运行结果:
jdk6
false
falsejdk7
false
true
这段代码在jdk6中输出是false false
,但是在jdk7中输出的是false true
。
intern()函数:
intern函数的作用是将对应的符号常量进入特殊处理,在JDK1.6以前 和 JDK1.7以后有不同的处理;
在JDK1.6中,intern的处理是 先判断字符串常量是否在字符串常量池中,如果存在直接返回该常量,如果没有找到,则将该字符串常量加入到字符串常量区,也就是在字符串常量区建立该常量;
在JDK1.7中,intern的处理是 先判断字符串常量是否在字符串常量池中,如果存在直接返回该常量,如果没有找到,说明该字符串常量在堆中,则处理是把堆区该对象的引用加入到字符串常量池中,以后别人拿到的是该字符串常量的引用,实际存在堆中。
6. String 是最基本的数据类型吗?
不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
7. String有哪些特性?
-
不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性;
-
常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用;
-
final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。
8. 在使用 HashMap 的时候,用 String 做 key 有什么好处?
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。