1.接口和抽象类的区别
1.接口中变量都是final类型的不可变的,抽象类中可以有final也可以有非final
2.类可以实现多个接口,但是继承类只能继承一个(抽象非抽象都只可以继承一个类)
3.接口方法本质都是抽象方法,而抽象类可以有抽象,也可以有非抽象方法
4.接口中的成员函数都是public类型的,而抽象类中可以有public类型的也可以有其他类型的函数
5.接口和抽象类都是绝对抽象的,不可以被实例化
2.什么是值传递和引用传递
1.对象被值传递,实际传递的是对象的副本,在修改副本对象后不会影响原对象
2.引用传递,实际传送的是对象的引用,在修改引用对象的值或者属性后,会修改原对象的值或者属性
3.一般认为,java内的基础类型数据传递都是值传递. java中实例对象的传递是引用传递
private static String testKey = "Java class";
private static int intKey = 10;
private static TestBean mTestBean;
public static void main(String[] args) {
Main main = new Main();
mTestBean = new TestBean();
main.changeInt(intKey);//值传递
main.changeString(mTestBean);//引用传递
}
private void changeInt(int key) {
key = 100;
System.out.println("int key :" + intKey);
}
private void changeString(TestBean value) {
value.key = "change value";
System.out.println("change value :" + mTestBean.key);
}
}
//TestBean
public class TestBean {
public String key = "java and android";
}
输出结果:
int key :10
change value :change value
3.位运算
1.按 位 与 &
如果两个相应的二进制形式的对应的位数都为1,则结果为1,记为同1为1,否则为0。首先我们看一下对正数的运算
int a = 4;
int b = 5;
System.out.println("result:" + (4 & 5));
}
输出结果:result:4
原理是:把4转换成二进制数:00000100
把5转换成二进制数:00000101
在(4&5)同为1才为1 即得到二进制数:00000100 =4
2.按位或 |
有1为1,否则为0
3.异或 ^
相同为0,不同为1
二、移位运算
1.左 移 (<< )
右边空出的位用0填补高位左移溢出则舍弃该高位。计算机中常用补码表示数据
2.右移(>>)
左边空出的位用0或者1填补。正数用0填补,负数用1填补。注:不同的环境填补方式可能不同;低位右移溢出则舍弃该位。
3。无 符 号 右 移 (>>> )
无符号右移:正数与右移规则一样,负数的无符号右移,就是相应的补码移位所得,在高位补0即可
int a = 8 << 2;
int b = 8 >> 2;
//8:00001000 << 2 00100000 = 32;
//-8:10001000 最高位表示符号0是正数1为负数
System.out.println("result a=:" + a);
//8:00001000 >> 2 00000010 = 2;
System.out.println("result b=:" + b);
}
输出结果:
result a=:32
result b=:2
四、String、StringBuffer和StringBuilder的区别
1.String是不可变类,每次变化都会创建一个新的对象,老的对象会被系统回收,会频繁的创建销毁效率低
2.StringBuffer是线程安全的,很多方法都是由synchronized关键字修饰的,效率低
3.StringBuilder是线程不安全的,效率高,但是在要求线程安全的情况下必须使用StringBuffer。
4.StringBuffer与StringBuilder底层都是通过一个char的数组来存储数据
- StringBuffer和StringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象
五、Java中equals和==的区别
1.java中equals和==的区别 值类型是存储在内存中的堆栈(简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中
2.==操作比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同
3.equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同
=
4.==比较的是2个对象的地址,而equals比较的是2个对象的内容,显然,当equals为true时,==不一定为true。
六、HashMap的底层实现原理
一、原理解析
HashMap根据名称可知,其实现方法与Hash表有密切关系。在讨论哈希表之前,我们先大概了解下其他数据结构在新增,查找等基础操作执行性能。
数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)
线性链表:对于链表的新增,删除等操作(在找到指定操作位置后),仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历链表逐一进行比对,复杂度为O(n)
二叉树:对一棵相对平衡的有序二叉树,对其进行插入,查找,删除等操作,平均复杂度均为O(logn)。
哈希表:相比上述几种数据结构,在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),接下来我们就来看看哈希表是如何实现达到惊艳的常数阶O(1)的。
我们知道,数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组。
比如我们要新增或查找某个元素,我们通过把当前元素的关键字 通过某个函数映射到数组中的某个位置,通过数组下标一次定位就可完成操作。
location = hash(关键字)
其中,hash函数设计的优劣直接影响整体的性能。
哈希冲突
哈希算法存在一个缺点就是哈希冲突。例如,我们进行数据存储时,我们通过对关键字进行hash时得到的地址已经存储过数据了,这时就会出现哈希冲突。因此,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀。但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式
二、源码理解
1.关键变量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量,在隐式指定更高的值时使用
//由具有参数的构造函数之一
static final int MAXIMUM_CAPACITY = 1 << 30;
//构造函数中未指定时使用的负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//树型阀值这个名字是作者根据字面意思自己翻译的,大家看看就好了,
//对应参数为TREEIFY_THRESHOLD,之前提到过 HashMap 的结构为 Node 型数组,
//而 Node 的数据结构为链表,树型阀值就是当链表长度超过这个值时,
//将 Node 的数据结构修改为红黑树,以便优化查找时间,默认值为8
static final int TREEIFY_THRESHOLD = 8;
2.构造方法
//initialCapacity 长度 loadFactor 负载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
//指定长度,负载因子使用默认的负载因子
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//使用默认初始化长度和默认负载因子
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
3.关键函数put方法源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods put以及相关方法
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断table是否是空,是空的话初始化table
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//p = tab[i=(n-1) & hash] 这个是获取第一个位置的tab,如果是空的直接插入数据
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果第一个位置的有数据就把p赋值给e;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//判断p是否红黑树,如果是调用红黑树的putTreeVal方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果不是红黑树,循环遍历链表直到当前元素没有next时,创建新的Node
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果循环次数大于吧链表转换成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果找到了节点,说明关键字相同,进行覆盖操作,直接返回旧的关键字的值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果目前键值对个数已经超过阀值,重新构建
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
3.关键函数get方法源码
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//首先判断当前的table有没有创建过,而且第一个数据不能是空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//每次都首先第一个数据是不是要取得数据
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;//返回第一个Node
if ((e = first.next) != null) {//判断是否有其他数据、没有就返回null
if (first instanceof TreeNode)//判断当前选择的是链表还是红黑树
return ((TreeNode<K,V>)first).getTreeNode(hash, key);//红黑树查询
do {//递归查询链表
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
七、Java反射原理和使用
1.反射概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
反射就是把java类中的各种成分映射成一个个的Java对象
2.常用代码
被反射类
public class ReflexEntity {
private String name;
private int age;
private static boolean isWoman = true;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private ReflexEntity(String name) {
this.name = name;
}
public ReflexEntity(String name, int age) {
this.age = age;
this.name = name;
}
public ReflexEntity() {
}
}
反射方法
/**
* 获取对象
*/
private void ReflexNewInstance() {
try {
Class<?> reflex = Class.forName("ReflexEntity");
Object object = reflex.newInstance();
ReflexEntity entity = (ReflexEntity) object;
entity.setName("Reflex----------");
entity.setAge(999);
System.out.println(entity.getName());
System.out.println(entity.getAge());
} catch (Exception e) {
System.out.println(e.toString());
}
}
/**
* 获取私有构造方法
*/
private static void reflectPrivateConstructor() {
try {
Class<?> reflexClass = Class.forName("ReflexEntity");
Constructor<?> constructor = reflexClass.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object object = constructor.newInstance("Test----");
ReflexEntity reflexEntity = (ReflexEntity) object;
System.out.println(reflexEntity.getName());
} catch (Exception e) {
}
}
/**
* 反射私有属性
* 注意:反射时一定要有无参构造函数,或者反射选择有参构造函数
*/
public static void reflectPrivateField() {
try {
Class<?> entity = Class.forName("ReflexEntity");
Object object = entity.newInstance();
Field field = entity.getDeclaredField("isWoman");
field.setAccessible(true);
boolean isWoman = (Boolean) field.get(object);
System.out.println(isWoman);
} catch (Exception e) {
System.out.println("reflectPrivateField e:" + e.toString());
}
}
八、Java中常用的四种引用类型
强引用:像我们平时用new关键字创建出来的对象引用对象就是属于强引用,对于强引用类型的对象,虚拟机宁愿抛出OOM异常,也不会去回收这个对象,所以强引用可能会造成内存泄漏
软引用:软引用是通过SoftReference类实现的。如果一个对象只有软引用,一旦内存不足时,这个对象就会被自动回收,反之则不会。
弱引用:弱引用是通过WeakReference类实现的。如果一个对象只有弱引用,不管内存足不足够,这个对象一定会在垃圾回收过程时被回收。
虚引用:虚引用是通过PhantomReference类实现的。虚引用是所有引用类型里最弱的一个,几乎就是没有引用一样,随时都可能被回收。虚引用必须和引用队列联合使用,主要用来跟踪对象的垃圾回收过程。
九、java中的泛型概念和实现
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
1.常用的泛型
List list = new ArrayList();
list.add("12345");
list.add(12345);
for (int i = 0; i < list.size(); i++) {
String a = (String) list.get(i);
}
在没有泛型的情况下运行上面代码就会出现如下错误:
Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String
at Main.main(Main.java:14)
ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
List<String> list = new ArrayList<String>();
list.add("12345");
// list.add(12345);
2.泛型的特性
重点:泛型只在编译阶段有效。看下面的代码:
List<String> list = new ArrayList<String>();
List<Integer> integers = new ArrayList<Integer>();
Class<?> stringClass = list.getClass();
Class<?> integerClass = integers.getClass();
System.out.println((stringClass.equals(integerClass)));
编译器提示:Result of 'stringClass.equals(integerClass)' is always 'true'
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
3.泛型的使用
1.泛型类
代码如下:
**
* 泛型类
*
* @param <T>
*/
public class GenericUtils<T> {
}
2.泛型接口以及实现
/**
* 泛型接口
*
* @param <T>
*/
public interface GenericInterface<T> {
public T getValue();
}
/**
* 实现泛型接口
*/
public class GenericInterfaceClass implements GenericInterface<T> {
public T getValue() {
return null;
}
}
3.泛型方法
/**
* 泛型方法
* @param <V>
* @return
*/
public static <V> V getValue() {
V entity = (V) new ReflexEntity("woshinidie", 10);
return entity;
}
泛型方法调用:
ReflexEntity a = GenericUtils.getValue();
System.out.println(a.getName());
System.out.println(a.getAge());