一. JDK/JRE/JVM 三者之间的联系与区别
- JDK:开发者提供的开发工具箱,是给程序开发者用的。它包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。
- JRE:(Java Runtime Environment) JVM运行时所须的包依赖的环境都在JRE中。
- JVM:当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理、垃圾回收和安全机制等。这种独立于硬件和操作系统,正是Java程序可以一次编写多处执行的原因。
JDK>JRE>JVM
二. Java 面向对象编程的三大特性
2.1 封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性和方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有供外界访问的方法,那么这个类也就没有什么意义了。
2.2 继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者新的功能,也可以用父类的功能,但不能选择性的继承父类。通过使用继承我们能够非常方便地复用以前的代码。
关于继承如下三点需要记住:
- 子类拥有父类非private的属性和方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
2.3 多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用,在编程时并不确定,而是在程序运行期间才确定。即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中的同一方法)
三. 面向对象与面向过程
3.1、面向过程
- 优点:性能比面向对象强,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展。
3.2、面向对象
- 优点:易维护、易复用、易扩展。由于面向对象有封装、继承、多态的三大特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
- 缺点:性能比面向过程低。
四. Java 语言有哪些特点
- 简单易学;
- 面向对象(封装、继承、多态);
- 平台无关性(Java虚拟机实现平台无关性);
- 可靠性;
- 安全性;
- 支持多线程;
- 很方便的支持网络编程;
- 编译与解释并存;
五. Java和C++的区别
没学过C++,不代表面试官也没学过
- 都是面向对象的语言,都支持封装、继承和多态
- Java不提供指针来直接访问内存,程序内存更加安全
- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
- Java有自动内存管理机制,不需要程序员手动释放无用内存。
六. Java 基本数据类型
Java | byte | short | int | long | double | float | char | boolean |
---|---|---|---|---|---|---|---|---|
字节大小 | 1 | 2 | 4 | 8 | 8 | 4 | 2 | 1 |
占位大小 | 8 | 16 | 32 | 64 | 64 | 32 | 16 | 8 |
- 整数类型:byte、short、int、long
- 字符型:char
- 浮点类型:float、double
- 布尔型:boolean
注意:
整数型默认int型。小数型默认是double型。float和long类型必须加后缀。
比如:float f=100f。
七. 成员变量与局部变量的区别
从语法形式上看:
- 成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;
- 成员变量可以被public、private、static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰。但是,成员变量和局部变量都能被final所修饰。
从变量在内存中的存储方式来看:
- 如果成员变量是使用的 static修饰的,那么这个成员变量是属于类的,如果没有使用 static修饰,这个成员变量是属于实例(对象)的。而对象存在与堆内存中、局部变量则存在于栈内存中。
从变量在内存中的生存时间上看:
成员变量是对象的一部分。它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外:被final修饰的成员变量也必须显示的赋值),而局部变量则不会自动赋值。
八. 静态方法和实例方法有何不同
在外部调用静态方法时,可以使用“类名.方法名”的方式,也可以使用“对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
九. 构造方法
9.1、特性
- 名字与类名相同;
- 没有返回值。但是不能用void声明构造函数(方法);
- 生成类的对象时自动执行,无需调用。
9.2、构造方法是否可以被override
在继承中我们就知道父类的私有属性和构造方法不能被继承,所以Constructor也就不能被override(重写),但是可以overload(重装),所以你可以看到一个类中有多个构造函数的情况。
9.3、父子关系的构造方法的执行顺序
- 父类有无参构造方法,子类才可以写无参构造方法;父类有含参构造方法,子类才可以写含参构造方法。
- 构造方法不能被继承、重写。
- 当进行无参构造时,先调用父类无参构造方法,然后调用子类无参构造方法;当进行有参构造时,先调用父类有参构造,然后调用子类有参构造。
十. this和super关键字
- super关键字用于从子类访问父类的变量和方法,也包含构造方法。
- this关键字用于引用类的当前实例。此关键字是可选的。这意味着如果上面的实例在不使用此关键字的情况下表现相同。但是,使用此关键字可能会使代码更易读或易懂。this也可以调用当前类的构造方法。
- super调用父类中的其他构造方法时,调用时要放在构造方法的首行!this调用本类中的其他构造方法时,也要放在首行。
- this、super不能用在静态方法中。因为被static修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而this代表对本类对象的引用,指向本类对象;而super代表对父类对象的引用,指向父类对象;所以,this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西。
十一. 重载和重写的区别
- 重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
- 重写:发生在子父类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
十二. String和StringBuffer、StringBuidler
12.1 可变性
String类中使用final关键字修饰的字符数组保存字符串private final char value[],所以String对象是不可变的。
而StringBuidler与StringBuffer都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符串数组保存字符串 char value[] 但是由于没有用final关键字修饰,所以这两个类都是可变的。
12.2 线程安全性
- String中的对象是不可变的,也就是可以理解为常量,线程安全。
- AbstractStringBuilder 是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
- StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
- StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
12.3 性能
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将地址引用指向新的String对象。
StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StingBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒着多线程不安全的风险。
12.4 对于三者的使用总结
- 操作少量的数据:String
- 单线程在字符串缓冲区下操作大量数据:StringBuidler
- 多线程在字符串缓冲区下操作大量数据:StringBuffer
十三. 自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为对应的基本数据类型;
十四. HashCode、equals与==
14.1 HashCode()介绍
hashcode()的作用是获取哈希码,也成为散列码。它实际上返回的是一个int类型的整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashcode()定义在JDK的Object类中,这就意味着Java中任何类都包含有hashcode()函数。另外需要注意的是:Object的hashcode方法是本地方法,也就是用c或c++实现的,该方法通常用来将对象在内存中的内存地址转换为整数之后返回。
14.2 为什么需要hashCode
以"HashSet如何保证唯一"为例子来说明为什么要有hashcode:
当你把对象加入HashSet时,jvm会先去检索HashCode方法,如果返回值是相等的,进而调用equals方法比较内容,如果内容(各种字段值)相同,那么返回true,数据就不往里面添加,因为里面已经存有该实例,如果返回是false就说明,是不同的实例对象,就往里面存。
因此,保证每个类的HashCode的值的唯一性,有助于提高效率,这也就是下面第三条约定所谈及的内容,因为,一旦判断出来hashcode不同,就相当于不用比较equals里面的字段,直接存入即可。因此提高了效率。
14.3 hashCode()与equals()的相关规定
- 如果两个对象相等,则hashcode一定也是相同的。
- 两个对象相等,对两个对象分别调用equals方法都返回true。
- 两个对象有相同的hashcode值,它们也不一定是相等的。
- 因此,equals方法被重写过,则hashcode方法也必须被重写。
- hashcode()的默认行为是对堆内存上的对象产生独特值。如果没有重写hashcode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
14.4 为什么两个对象有相同的hashCode值,它们也不一定相等的?
hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的hashCode)。在HashSet中,在作对比的时候,同样的hashCode有多个对象,它会使用equals()来判断是否真的相同。也就是说hashCode只是用来缩小查找成本的。
14.5 ==与euqals
==:它的作用是判断两个对象的地址是不是相等。即:判断两个对象是不是同一个对象。(基本数据类型""比较的是值,引用数据类型""比较的是内存地址)
equals():它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
- 类没有重写equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 类重写了equals()方法。一般,我们都通过重写equals()方法来比较两个对象的内容是否相等;如果内容相等,则返回true(即,认为这两个对象相等)。
说明:
- String中的equals方法是被重写过的,因为Object类的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重写创建一个String对象。
十五. 关于final关键字的一些总结
final关键字主要用在三个地方:变量、方法、类。
对于一个final变量:
- 如果是基本数据类型的变量,则其数组一旦初始化以后就不能更改;
- 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
当用final修饰一个类时,表面这个类不能被继承。final类中的所有成员方法都会被隐式的指定为final方法。
使用final修饰方法的原因:
- 把方法锁定,以防任何继承类修改它的含义
- 效率:在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
十六. 接口和抽象类的区别
- 接口的方法默认是public,所有的方法在接口中不能有实现(Java8开始接口方法可以有默认实现),抽象类可以有非抽象的方法。
- 接口中的实例变量默认是final类型的,而抽象类中则不一定。
- 一个类可以实现多个接口,但最多只能实现一个抽象类。
- 一个类实现接口的话要实现接口中的所有方法,而抽象类不一定。
- 接口不能用new 实例化,但可以声明,但是必须引用一个实现该接口的对象;从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,必须重写,不然会报错。
十七. static关键字
17.1 修饰成员变量和成员方法
被static修饰的成员变量属于静态成员变量,静态变量存放在Java内存区域的方法区中。
调用格式:类名.静态变量名
被static修饰的成员方法属于类,不属于这个类的某个对象,被类中所有的对象共享,并且建议通过类名调用。
调用格式:类名.静态方法名()
17.2 静态代码块
静态代码块定义在类中方法外,静态代码块在非静态代码块之间执行(静态代码块-->非静态代码块-->构造方法)。该类不管创建多少对象,静态代码块只执行一次。
静态代码块的格式是:static{语句体;}
一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问。
静态代码块定义在类中方法外,静态代码块在非静态代码块之前执行(静态代码块-->非静态代码块-->构造方法)。
该类不管创建多少对象,静态代码块只执行一次。
17.3 静态内部类(static修饰类的话,只能修饰内部类)
静态内部类与非静态内部类之间存在一个巨大的区别:非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
- 它的创建是不需要依赖外围类的创建。
- 它不能使用任何外围类的非static成员变量和方法。
17.4 静态导包(用来导入类中的静态资源,1.5之后的新特性)
格式为:import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
17.5 静态方法和非静态方法
静态方法属于类本身,非静态方法属于从该类生成的每个对象。如果你的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。否则,它应该是非静态的。
总结:
- 在外部调用静态方法时,可以使用“类名.方法名”的方式,也可以使用“对象名.方法名”的方式。而实例(非静态)方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例(非静态)方法则没有这个限制。
17.6 静态代码块和非静态代码块
- 相同点:都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
- 不同点:静态代码块在非静态代码块之前执行(静态代码块->非静态代码块->构造方法)。静态代码块只在第一次new 的时候执行一次,之后不再执行,而非静态代码块每new一次就执行一次。非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
一般情况下,如果有些代码,比如一些项目最常用的变量或对象必须在项目启动的时就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法,两者的区别是:静态代码块是自动执行的而静态方法是被调用的时候才执行的。
17.7 非静态代码块和构造函数
非静态代码块与构造函数的区别是:
非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。
因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化的内容。
17.8 在一个静态方法内调用一个非静态成员为什么是非法的
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。