Java和C++的区别
- 都是面向对象的,支持继承,封装,多态
- Java没有多继承,但是C++有
- Java没有指针,但是C++有
- Java没有手动内存管理(有JVM),但C++有
Java面向对象的三个特性
- 封装:将属性私有化,同时提供一些可以被外界访问的属性的方法
- 继承:使用已有的类的定义作为基础建立新类的技术,可以选择性增加功能,或属性
- 多态:引用变量指向的具体对象不在程序编译时绑定,而在运行时绑定
String,StringBuilder和StringBuffer的异同点
- String是用final修饰的字符数组来保存字符串,所以String是不可变的,其他两个都是继承自AbstractStringBuilder类,这个类中不是用final修饰的,,所以这两个对象是可变的
- String可以理解位常量,线程安全
- StringBuilder没有对方法加同步锁,所以是非线程安全的
- StringBuffer对方法加锁了,所以是线程安全的
- 总结:操作少量数据用String,单线程下操作大量数据用StringBuilder,多线程下操作大量数据用StringBuffer
基本数据类型和引用数据类型
- 基本数据类型
- 整数型:byte,short,int,long
- 字符型:char
- 浮点型:float,double
- 布尔型:boolean
- 引用数据类型
- 类
- 接口
- 数组
- String是引用类型,而不是基本类型,引用类型声明的变量是指该变量在内存中实际存储的是一个引用的地址,实体在堆中
int与Integer的区别
- Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象(故两个new出来的变量不相等,因为是两个对象,地址不同),而int则是直接存储数据值
- Integer默认值为null,int默认值是0
- Integer与int比较时,两者值相等时结果就为true 因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较) (了解问题5,拆箱与装箱)
自动拆箱和装箱就是基本类型数据和其引用类型的转换:
- 装箱:将基本类型转换成引用类型,如Integer i=10
- 拆箱:将引用类型转换成基本类型,:如Integer i=10;int j =i;
为什么要转换:因为基本类型转换成引用类型后,就可以new对象,从而调用包装类中封装好的方法进行基本类型之间的转换或者toString,还有就是如果集合中想存放基本类型,泛型的限定类型只能是对应的包装类型
接口和抽象类的区别
- 接口可以多继承,但是抽象类不可以多继承
- 接口中只有static和final变量,没有其他的变量,而抽象类不一定
- 接口方法默认时public,所有方法在接口中不能有实现,而在抽象类中可以有非抽象的方法
- 从设计层面上来说,抽象是对类的抽象,是一种模板设计,接口是对行为的抽象,是一种行为的规范
static关键字是什么意思?java中是否可以覆盖(override)一个private或者static方法?
static不可以用来修饰普通类,只能用来修饰内部类,静态内部类可以直接调用静态构造器(不使用对象)
用来修饰方法,static无this,在该方法内部不可以调用其他非static方法(但反过来可以),可以在没有创建任何对象的情况下,直接使用类名来调用static方法(常见的就是main)
用来修饰变量,静态变量随类加载一次,可以被多个对象共享
来修饰代码块(需要重点理解):随着类的加载而执行,且执行一次,执行优先级高于非静态的初始化块(构造函数)(这里涉及jvm知识,Cinit函数,需要再看),在类初始化的时候执行一次,执行完成后便 销毁, 它仅能初始化类变量,即static修饰的数据成员。( 用来优化性能)
- private修饰的方法不可以被覆盖,因为子类看不到
- static修饰的方法也不可以被覆盖,因为覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的
(补充)在一个静态方法内调用一个非静态成员为什么是非法的?
- 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态成员
- 静态的成员属于类,随着类的加载而加载到静态方法区内存(类被虚拟机载入时,会对static变量进行初始化),而类加载时,此时不一定有实例创建,没有实例就不能访问非静态成员
final关键字
- 修饰类时,表示这个类不可以被继承,类中所有方法会被隐式声明为final
- 修饰变量时,如果是基础类型变量,其数值一旦初始化就不可以再修改,如果时引用类型的变量,则在其初始化之后不能再让其指向另一个对象
- 使用final的原因有两个
- 把方法锁定,以防继承对该方法进行修改
- 效率,以前版本的java中final会内嵌调用
Override(重写)和Overload(重载)的区别
- 重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰可以不同(也就是参数列表不同)
- 重写:子类对父类允许访问的方法实现过程中重新进行编写,方法名,参数列表必须相同,返回值范围小于等于父类,抛出异常范围小于等于父类,访问修饰符范围大于等于父类,父类private的方法不可重写
==与equals()
==:判断两个对象的地址是不是相等,即,判断两个对象是不是同一个对象(基本类型比较的是值,引用类型比较的是内存地址)
equals():也是判断两个对象是否相等,但一般有两种情况:
- 类没有覆盖equals()方法,则通过equals(),进行比较时,就和==是一样的
- 类覆盖了equals()方法,则内容相同返回true
equals是判断两个变量或者实例指向同一个内存空间的值是不是相同
而==是判断两个变量或者实例是不是指向同一个内存空间
举个通俗的例子来说,==是判断两个人是不是住在同一个地址,而equals是判断同一个地址里住的人是不是同一个
说明:
- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值
- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用,如果没有就在常量池中重新创建一个String对象
HashTable,HashMap,ConcurrentHashMap
- HashTable(已经淘汰)
- 底层:数组+链表
- 线程安全,key,value都不能为null
- 初始size:11,若指定初始值,则就为该初始值
- 扩容:×2+1
- HashMap
- 底层:数组+链表(jdk1.8前) ,当链表超过阈值8,(先判断如果当前数组长度小于64,则先进行数组扩容,)链表转为红黑树(jdk1.8后)
- 线程不安全,key,value都可以是null
- 初始size:16,若指定初始值,则自动扩容为最接近的2的幂次方
- 扩容:×2,插入元素后才判断要不要扩容,当元素超过75%,触发扩容
- HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是了防止一些实现比较差的hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。 - 效率问题:单线程情况下_耗时:HashMap.put > HashTable.put;HashMap.get < HashTable.get
- ConcurrentHashMap
- 底层:分段的数组+链表
- 通过将整个map分成n个segment,可以提供相同的线程安全,效率提升n倍,默认是16倍
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容