目录
- Java的8种基本数据类型
- String,为什么不可变,这样做的好处
- String为何会产生很多对象?
- StringBuffer、StringBuilder 和 String 的区别
- 为什么重写equals方法后必须重写hashCode方法
- final、finally、finalize
- Java中的序列化和反序列化
- Comparable和Comparator的区别
- 讲讲Java的三大特性(封装、继承、多态)
- 重载和重写
- Object类有哪些方法?
- Java四种修饰符的访问权限
- 装箱和拆箱
- int 和 Integer的区别?
- == 和 equals 的区别
- 抽象类和接口的区别
- static、private
- Comparable和Comparator的区别
- 成员变量和局部变量的区别?静态变量和实例变量的区别?静态方法和实例方法的区别?
- JDK、JRE、JVM 的区别?
- 面向过程和面向对象的区别?
- try{ return "a" } finally { return "b" } 输出什么? 答案:"b"
- Java 与 C++ 的区别
- public static void main(String[] args){…}的意义?为什么要有?
- for 和 foreach 效率比较?
- 数组的优缺点
- Java中创建对象的方式
Java的8种基本数据类型
- char 能存放汉字:char c = ‘中’
- 基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上。
- 引用数据类型在被创建时,首先要在栈上给其引用分配一块内存,而对象的具体信息都存储在堆内存上,然后由栈上面的引用指向堆中对象的地址。
- int型数的范围是:-231 ~ 231-1,即 -2147483648 ~ 2147483647
- String 不属于基本数据类型,它是一个引用类型。
String,为什么不可变,这样做的好处
- String 本质是一个用 final 修饰的 char 类型数组,所以它不能被继承,也不能被改变。将 String 设置成不可变的,有三个好处:
- ①String的不可变性支持字符串常量池,在大量使用字符串时,可以节省内存空间,提高效率。
- ②String的不可变性支持线程安全,不可变对象不能被写,所以线程安全。
- ③作为Map的key,提高了访问效率。因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,比如HashMap中的键往往都使用字符串。
String为何会产生很多对象?
- String类的value域是被final修饰的,String对象在创建完成后value值就已经无法再修改了,所以每次对String进行操作产生的字符串都是新的String对象。
StringBuffer、StringBuilder 和 String 的区别
- 在执行速度上,StringBuilder > StringBuffer > String,因为 String 为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可变的,但后两者的对象是变量,是可变的。所以,Java中对 String 对象进行的操作实际上是一个不断创建新的对象并将旧的对象回收的一个过程,所以执行速度很慢。而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,没有频繁的创建和回收动作,所以速度要比String快。
- 线程安全: StringBuilder是线程不安全的,而StringBuffer是线程安全的。StringBuffer 通过 synchronized 来保证线程安全。所以如果进行多线程操作,使用StringBuffer。单线程的话使用 StringBuilder。String是不可变类,所以也是线程安全的。
- 使用场景:String:适用于少量的字符串操作的情况;StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况;StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
为什么重写equals方法后必须重写hashCode方法
- 重写 equals 方法一般都要重写 hashCode 方法。Java规定:如果两个对象的 equals 方法比较结果为 true ,那它们的 hashCode 也必须相等。之所以这样规定是因为,如果两个对象的 hashCode 相同,那在 hashMap 中,它们会定位到同一个桶的位置,但无法得知它们两个在这个桶中是否重复,所以需要再通过重写的 equals 方法来比较它们的值是否相等。如果两个对象的 equals 比较的值相等,但没有重写 hashCode 方法来保证它们的 hashCode 相同,就会导致它们定位到不同的桶的位置,而它们的值又是相等的,就会出现错误。
final、finally、finalize
- final 如果修饰类,表示类不能被继承;修饰方法,表示方法不能被重写。所以 final 不能和 abstract 同时使用,因为 abstract 修饰的类只能被继承,abstract 修饰的方法必须被子类重写。final 如果修饰变量,表示变量的引用不可变,也就是它只能指向初始化时的对象,而不关心这个对象内容的变化,所以变量必须初始化。
- finally 用在异常处理的 try/catch 语句中,在它们后面制定一些动作,比如用来释放资源。
- finalize 是 Object 类的方法,在垃圾收集器将对象从内存中清除之前,可以调用 finalize 方法做必要的清理工作。
Java中的序列化和反序列化
- 序列化:把对象转换为可保存和传输的字节序列。
- 反序列化:把字节序列恢复为对象。
- 一个对象只有实现了 Serializable 序列化接口,它的对象才能被序列化。这个接口里其实什么内容都没有,它起的是一个标识作用,告知JVM可以对这个类做序列化操作。
- 我们可以对实现了序列化接口的类定义一个 serialVersionUID 变量,在序列化时,系统会将这个 UID 变量写到序列化的文件中,在进行反序列化时,系统会先检查文件中的 UID 是否跟当前文件的 UID 一致,如果一致就反序列化成功,否则就表示当前类发生了变化,那么反序列化时就会报错。
- 这个 UID 最好自己定义,因为系统默认生成的 UID 对类的信息比较敏感,不同的Java编译器来实现可能会出现差异,导致反序列化失败。
Comparable和Comparator的区别
- Comparable 接口是 java.lang 包下的,而 Comparator 接口是 java.util 包下的。
- 实现了 Comparable 接口的对象可以直接成为一个可比较的对象,需要重写 compareTo 方法来定义比较规则,跟类绑定,扩展性不好;Comparator 是一个外部比较器,它可以用来比较任意类型的对象,采用了策略模式,可以根据需求设计多个比较器。
- 策略模式:一个类的行为或算法可以在运行时更改。
- 优先队列 PriorityQueue 内部是基于 Comparator 实现的。
讲讲Java的三大特性(封装、继承、多态)
- 封装:将对象中不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。
- 继承:当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法了,只需要通过extend语句继承父类即可,这样,子类就会自动拥有父类定义的属性和方法,达到代码复用的效果。Java 是单继承,一个类只能继承一个父类。
- 多态:指同一个行为具有多种不同的表现形式,有三种方式来实现多态:子类继承父类,子类重写父类的方法,父类型的引用指向子类型的对象。主要的应用有:方法的重载和重写。好处是使代码具有可替换性、可扩充性、接口性、灵活性、简化性、降低耦合。
重载和重写
- 重载:重载是一个类中多态性的一种表现。同一个类中的方法,方法名相同,参数列表不同。
- 重写:重写是父类与子类之间多态性的一种表现。子类继承父类,重写父类的方法,要求方法名、参数列表相同、返回类型都相同。子类的 访问权限 >= 父类,抛出异常 <= 父类。
Object类有哪些方法?
Object类是所有类的父类,任何类都默认继承Object。Object类实现了以下几种方法:
- hashCode 方法和 equals 方法。重写 equals 方法一般都要重写 hashCode 方法。Java规定:如果两个对象的 equals 方法比较结果为 true 的话,它们的 hashCode 方法也必须相同。之所以这样规定是因为,如果两个对象的 hashCode 相同,那在 hashMap 中,它们会定位到同一个桶的位置,但无法得知它们两个在这个桶中是否重复,所以需要再通过重写的 equals 方法来比较它们的值是否相等。如果两个对象的 equals 比较的值相等,但没有重写 hashCode 方法来保证它们的 hashCode 相同,就会导致它们定位到不同的桶的位置,而它们的值又是相等的,就会出现错误。
- clone 方法。
- toString 方法。
- wait 、notify 和 notifyAll 方法。
- getClass 方法。用于获得运行时类型。
Java四种修饰符的访问权限
装箱和拆箱
- 定义:装箱是自动将基本类型转换为包装器类型;拆箱是自动将包装器类型转换为基本类型。
- 自动装箱的发生场景:将基本类型赋值给包装器类型;
- 自动拆箱的发生场景:将包装器类型赋值给基本类型;还有包装器类型进行数学运算时;包装器类型与基本类型之间进行 ==、!= 比较操作时。
int 和 Integer的区别?
- int 是java的一种基本数据类型,Integer是int的包装类,是引用数据类型(new 生成的两个Integer永不相等)。
- int变量直接存储数据值,Integer必须实例化后才能使用,存储的是对象的地址。
- int的默认值是0,Integer的默认值是null。
- 对于Integer,如果Integer a = 127,Integer b = 127 ,那么 ab 输出为true;如果Integer a = 128,Integer b = 128 ,那么 ab 输出为false;(常量池)。而 int 直接存的数据值,所以都会相等。
- 参考
== 和 equals 的区别
- == 比较的是两个对象的内存地址是否相同,也就是是否指向同一个对象。而 equals 比较的是值是否相等。
- 其实 Object 默认提供的 equals 方法比较的是地址,但很多类重写了 equals 方法,比如 String、Integer ,它们把引用比较改成了值比较,所以大多数情况下 equals 比较的是值了。
抽象类和接口的区别
- 普通方法去掉 { } ,加上 abstract 和分号 即是抽象方法。 public abstract void test();
- 有抽象方法的类只能是抽象类,抽象类可以没有抽象方法。
- abstract 修饰的类只能被继承,修饰的方法必须被子类重写。final 修饰的类不能被继承,修饰的方法不能被重写。所以 final 和 abstract 不能同时使用。
- 抽象类和接口都不能被实例化。
- 接口中不能实现方法,但抽象类可以。
- 类只能继承一个抽象类,但是可以实现多个接口。
- 接口中的成员变量只能是 public static final 修饰的静态常量,接口中的方法只能用 public 和 abstract 来修饰。
- 抽象是对类的抽象,是一种模板模式的设计思想。
- 抽象类和它的子类之间应该是一般和特殊的关系,而接口仅仅是它的子类应该实现的一组规则。
static、private
-
static 修饰的成员变量和成员方法在类加载的时候被加载到方法区。非 static 修饰的成员变量要实例化后才被加载在堆内存中。
-
引用 static 修饰的成员变量和成员方法时,通过类名. 的方式调用,引用非 static 修饰的成员变量和成员方法时通过对象名. 或者 this. 的方式调用。
-
静态方法形式上可以被重写,即子类中可以重写父类中静态的方法,但是在通过父类引用调用子类的这个已经重写的静态方法时,执行的还是父类的静态方法,子类的静态方法被隐藏了。
-
无法重写被private修饰的方法,因为被private修饰的父类方法在子类中是不可见的。
Comparable和Comparator的区别
- Comparable 接口是 java.lang 包下的,它内部有一个 compareTo 方法用来排序,而 Comparator 接口是 java.util 包下的,它内部有一个 compare 方法用来排序。
- 实现了 Comparable 接口的对象可以直接成为一个可比较的对象,需要重写 compareTo 方法来定义比较规则,跟类绑定,扩展性不好;Comparator 是一个外部比较器,它可以用来比较任意类型的对象,采用了策略模式,可以根据需求设计多个比较器。
- 策略模式:一个类的行为或算法可以在运行时更改。
- 优先队列 PriorityQueue 内部是基于 Comparator 实现的。
成员变量和局部变量的区别?静态变量和实例变量的区别?静态方法和实例方法的区别?
(成员变量 = 实例变量 + 静态变量)
成员变量和局部变量的区别?
- 成员变量定义在类中,方法外;局部变量定义在方法中。
- 成员变量在堆内存中(成员变量属于对象,对象进堆内存);局部变量在栈内存中(局部变量属于方法,方法进栈内存)。
- 成员变量随着对象的创建而存在,随着对象的消失而消失;局部变量随着方法的调用而存在,随着方法的调用完毕而消失。
- 成员变量有系统默认的初始化值;局部变量没有默认值,必须定义、赋值,才能使用。
- java 运行局部变量和成员变量同名,如果成员变量和方法里的局部变量同名,局部变量会覆盖成员变量。如果需要在这个方法里引用被覆盖的成员变量,则可使用 this.(对于实例变量) 或类名.(对于类变量)的方式来访问成员变量。
静态变量和实例变量的区别?
- 静态变量前要加 static 关键字;实例变量不用加。
- 静态变量直接用类名调用即可;实例变量只能用对象调用。
- 静态变量存储在方法区的静态存储区,是所有对象共享的;实例变量存储在 Java 堆中,是每个对象私有的。(Java的方法区有个静态存储区,专门存放静态变量和静态块)
静态方法和实例方法的区别?
- 静态方法带有 static 关键字;实例方法没有 static 关键字。
- 静态方法直接用类名调用即可;实例方法只能用对象调用。
- 静态方法只能访问静态成员,不能访问实例成员;而实例方法可以访问静态成员和实例成员。
- 实例方法可以直接调用静态方法;静态方法不能直接调用实例方法,因为静态方法加载时类还没有实例化。
- 静态方法是一直存放在内存中,因此调用速度快,但是却占用内存。实例方法使用完成后会由回收机制自动进行回收,下次再使用必须再实例化。
JDK、JRE、JVM 的区别?
- JRE 是Java程序的运行环境,面向Java程序的使用者,而不是开发者。
- JDK包含JRE,同时还包含了编译 Java 源码的编译器 Javac,以及很多 Java 程序调试和分析的工具。
- JVM 指java虚拟机。是JRE的一部分。它是一个虚构出来的计算机,有自己的硬件架构,比如方法区、堆栈、寄存器等。
- 如果需要运行Java程序,只需安装 JRE 就可以了,如果需要编写 Java 程序,则需要装 JDK。
面向过程和面向对象的区别?
- 面向过程就是先分析出解决问题需要的步骤,然后用函数把这些步骤一个个实现,使用的时候依次调用,面向过程的核心是过程。
- 面向对象就是把构成问题的事物分解成一个个对象,建立对象不是为了实现一个步骤,而是为了描述某个事物在解决问题中的行为,面向对象的核心是对象。
try{ return “a” } finally { return “b” } 输出什么? 答案:“b”
- 如果 try 和 finally 中都有 return 时,忽略掉 try 中的 return,只使用 finally 中的 return。
- 如果只有 try 中有 return 时,输出结果为2,但这里并不是 finally 中的代码块没有被执行,它真正的执行顺序是,先执行 return ++x 中的 ++x 操作,得到 x 为 2,将这个结果存到局部变量表中;接着执行 finally 中的 ++x 操作,得到 x 为 3;最后执行 return 操作,但这里返回的并不是此时 x 为 3 这个值,而是返回局部变量表中 x 的值,所以结果为 2。
Java 与 C++ 的区别
- Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 既支持面向对象也支持面向过程。
- Java 通过JVM虚拟机实现跨平台的特性,但是 C++ 依赖于特定的平台。
- Java 没有指针,它的引用可以理解为指针,而 C++ 具有和 C 一样的指针。
- Java 支持自动垃圾回收,而 C++ 需要手动回收。
- Java 不支持多继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多继承。
public static void main(String[] args){…}的意义?为什么要有?
- public 修饰符:Java类由JVM调用,为了让JVM能自由调用这个main方法,所以使用public修饰符把这个方法暴露出来
- static 修饰符:JVM调用这个主方法时,不会先创建该主类的对象,而是直接通过该类来调用主方法,因此用 static 修饰主方法
- void 返回值:因为主方法被JVM调用,如果有返回值就直接给JVM了,因此 main 方法没有返回值。
for 和 foreach 效率比较?
- ArrayList采用数组的形式保存对象,这种方式将对象放在连续的内存块中,所以插入和删除时比较麻烦,查询比较方便。
- LinkedList将对象放在独立的空间中,而且每个空间中还保存下一个空间的索引,也就是数据结构中的链表结构,插入和删除比较方便,但是查找很麻烦,要从第一个开始遍历。
- 循环数组结构的数据时,建议使用for循环,因为for循环采用下标访问,对数组结构的数据来说,采用下标访问效率更高。
- 循环链表结构的数据时,一定不要使用for循环,因为for循环要获取第i个元素必须从头开始遍历,而foreach是通过iterator实现的遍历,只需要遍历一次,所以效率比for循环的高。
数组的优缺点
- 优点:索引查询速度快,能存储大量数据 。
- 缺点:
- 数组大小必须定义时给出,一经确定不能改变。
- 数组中所有元素类型必须相同
- 增加、删除元素效率慢
- 数组的空间必须连续,这就造成数组在内存中分配空间时必须找到一块连续的内存空间。所以数组不可能定义得太大,因为内存中不可能有那么多大的连续的内存空间,而解决这个问题的方法就是使用链表。
Java中创建对象的方式
- clone:Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone(),调用这个方法后会返回一个新对象,而不是一个引用。
- new 出一个对象的方式来创建对象。
- 利用java.lang.Class类的newInstance方法来创建对象,它可以根据Class对象的实例,建立该Class所表示的类的对象实例,比如 a.getClass().newInstance()
- 序列化和反序列化:对对象进行序列化来保存对象的状态,比如可以用 fileOutputStream 来把数据写出到磁盘文件,要注意,要将某个类的对象进行序列化时,该类必须实现了 serializable 接口,该接口是一个标志,用来告诉jvm该类的对象可以被序列化。通过序列化保存好对象的状态后,就可以通过反序列化得到之前保存的对象了。