每日一更新,选自各大网站真实面经,欢迎补充!
封装、继承、多态
编写一个类就是对数据和数据的操作封装,封装即时隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
例如:
子类继承父类的特征和行为。提供继承信息的类被称为父类(基类),得到继承信息的类被称为子类(派生类)。
注意:
格式例如:
class 父类 { }
class 子类 extends 父类 { }
super 和 this :
super.成员变量—父类 super.成员方法 —父类
this.成员变量—本类 this.成员方法 —本类
多态定义:同一个行为具有多个不同表现形式或形态能力。
多态实现方式:
方式一:重载与重写
重载:在一个类中,同一个方法名称,不同的参数类型及个数,可以完成不同功能。
重写:同一个方法名称,子类继承父类的方法,根据操作的子类不同,所完成的功能也不同。
方式二:抽象类和抽象方法
抽象方法:修饰符 abstract 返回值类型 方法名(参数列表){}
抽象类:abstract class 类名{}
特点:
1、抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
2、抽象类不可以是实例化
3、抽象类的子类,可以是抽象类,也可以是具体类。如果子类为具体类,需要重写抽象类中所有抽象方法
方式三:接口
定义:是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。
接口无法被实例化,但是可以被实现. 一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
1.==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所 指向的内存空间的值是不是相同
2.==是指对内存地址进行比较 , equals()是对字符串的内容进行比较
3.==指引用是否相同, equals()指的是值是否相同
代码如下(示例):
//创建一个引用
String A = new String("abcd");
//创建另一个引用,A和B内容一致
String B = new String("abcd");
//将abcd放入常量池
String C = "abcd";
//常量池中存在"abcd",所以不会再创建
String D = "abcd";
//true
System.out.println(A.equals(B));
//false
System.out.println(A == B);
//true
System.out.println(C.equals(D));
//true
System.out.println(C == D);
首先我们需要知道Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。 equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大降低效率。 于是,Java采用了哈希表的原理。
哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。
(1)如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
(2)如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了;
(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起。这样一来实际调用equals方法的次数就大大降低了。
所以hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。
废话少说,我用代码来演示一下:
public class HashTest {
private int i;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public int hashCode() {
return i % 10;
}
public final static void main(String[] args) {
HashTest a = new HashTest();
HashTest b = new HashTest();
a.setI(1);
b.setI(1);
Set<HashTest> set = new HashSet<HashTest>();
set.add(a);
set.add(b);
System.out.println(a.hashCode() == b.hashCode());
System.out.println(a.equals(b));
System.out.println(set);
}
}
输出结果为:
true
false
[HashTest@1, HashTest@1]
这时问题出现了,因为我们只是重写了HashCode方法,从上面的结果可以看出,虽然两个对象的HashCode相等,但是实际上两个对象并不是相等,因为我们没有重写equals方法,那么就会调用Object默认的equals方法,Object的equals方法调用的是 == 进行比较两个对象,显示这是两个不同的对象。
这里我们将生成的对象放到了HashSet中,而HashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象ab都被放到了HashSet中,这样HashSet就失去了他本身的意义了。
下面我们继续重写equals方法:
public class HashTest {
private int i;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public boolean equals(Object object) {
if (object == null) {
return false;
}
if (object == this) {
return true;
}
if (!(object instanceof HashTest)) {
return false;
}
HashTest other = (HashTest) object;
if (other.getI() == this.getI()) {
return true;
}
return false;
}
public int hashCode() {
return i % 10;
}
public final static void main(String[] args) {
HashTest a = new HashTest();
HashTest b = new HashTest();
a.setI(1);
b.setI(1);
Set<HashTest> set = new HashSet<HashTest>();
set.add(a);
set.add(b);
System.out.println(a.hashCode() == b.hashCode());
System.out.println(a.equals(b));
System.out.println(set);
}
}
输出结果:
true
true
[HashTest@1]
从结果我们可以看出,现在两个对象就完全相等了,HashSet中也只存放了一份对象。
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。
例如: String str = “a”; str = str + “b”;
这一步在内存看来并不是将原有的“a”变为“ab”,而是开辟一块新的内存并赋值“ab”。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。因为对这2个类进行修改都是在本身存在的那个对象上进行的。
StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
底层数据结构不同,
ArrayList底层是基于数组实现的,更适合进行随机查找
LinkedList底层是基于链表实现的,更适合删除和添加
ArrayList和LinkedList都实现了List接口,但是LinkedList额外实现了Deque
因此LinkedList还可以当作队列来进行使用。
注:
如果不熟悉数组和链表,建议查看一下相关博文。
简单来说就是数组因为是在内存中开辟了一段连续的内存空间,使得在查找时可以根据动态下标访问方便快捷,但是插入和删除的效率很低,无法进行动态扩展。
但是链表不是连续内存,而是通过指针指向将元素进行关联,所以插入和删除效率高,非连续内存,无固定大小,可以随时扩展,无法随机访问,查找效率低。
1.Hashtable是线程安全,HashMap是非线程安全。HashMap的性能会高于Hashtable,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步。
2.HashMap可以使用null作为key,不过建议还是尽量避免这样使用。HashMap以null作为key时,总是存储在table数组的第一个节点上。而Hashtable则不允许null作为key。
3.HashMap继承了AbstractMap,HashTable继承Dictionary抽象类,两者均实现Map接口。
4.HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。HashMap扩容时是当前容量翻倍即:capacity 2,Hashtable扩容时是容量翻倍+1即:capacity (2+1)。
5.HashMap和Hashtable的底层实现都是数组+链表结构实现
6.Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模
HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取模。
当然在真实的开发情况下:如果你不需要线程安全,那么使用HashMap,如果需要线程安全,那么使用ConcurrentHashMap。HashTable已经被淘汰了,不要在新的代码中再使用它。(HashTable官方注释原话)
首先,引用类型一般是针对于java中对象来说的,Java为引用类型专门定义了一个类叫做Reference。Reference是跟Java垃圾回收机制息息相关的类,通过探讨Reference的实现可以更加深入的理解Java的垃圾回收是怎么工作的。
Java中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。
Object obj = new Object();
我们new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。
强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。
软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。
先看下SoftReference的定义:
public class SoftReference<T> extends Reference<T>
SoftReference继承自Reference。它有两种构造函数:
public SoftReference(T referent)
public SoftReference(T referent, ReferenceQueue<? super T> q)
第一个参数很好理解,就是软引用的对象,第二个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。
我们举个SoftReference的例子:
public void softReference(){
Object obj = new Object();
SoftReference<Object> soft = new SoftReference<>(obj);
obj = null;
log.info("{}",soft.get());
System.gc();
log.info("{}",soft.get());
}
输出结果:
22:50:43.733 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
22:50:43.749 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
可以看到在内存充足的情况下,SoftReference引用的对象是不会被回收的。
weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。
同样的WeakReference也有两个构造函数:
public WeakReference(T referent);
public WeakReference(T referent, ReferenceQueue<? super T> q);
含义和SoftReference一致,这里就不再重复表述了。
我们看下弱引用的例子:
public void weakReference() throws InterruptedException {
Object obj = new Object();
WeakReference<Object> weak = new WeakReference<>(obj);
obj = null;
log.info("{}",weak.get());
System.gc();
log.info("{}",weak.get());
}
输出结果:
22:58:02.019 [main] INFO com.flydean.WeakReferenceUsage - java.lang.Object@71bc1ae4
22:58:02.047 [main] INFO com.flydean.WeakReferenceUsage - null
我们看到gc过后,弱引用的对象被回收掉了。
PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。
和SoftReference和WeakReference不同的是,PhantomReference只有一个构造函数,必须传ReferenceQueue:
public PhantomReference(T referent, ReferenceQueue<? super T> q)
看一个PhantomReference的例子:
public void usePhantomReference(){
ReferenceQueue<Object> rq = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantomReference = new PhantomReference<>(obj,rq);
obj = null;
log.info("{}",phantomReference.get());
System.gc();
Reference<Object> r = (Reference<Object>)rq.poll();
log.info("{}",r);
}
运行结果:
07:06:46.336 [main] INFO com.flydean.PhantomReferenceUsage - null
07:06:46.353 [main] INFO com.flydean.PhantomReferenceUsage - java.lang.ref.PhantomReference@136432db
我们看到get的值是null,而GC过后,poll是有值的。
因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直都是返回null。