面向对象的特征
(1)抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
(2)继承:
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
(3)封装:
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
(4) 多态性:
多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
Comparable和Comparator接口
Comparable
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的。Comparable接口只包含compareTo()方法,这个方法可以给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。
实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
public class Person implements Comparable<Person> {
String name;
int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Person p) {
return this.age - p.getAge();
}
public static void main(String[] args) {
Person[] people = new Person[] { new Person("xujian", 20), new Person("xiewei", 10) };
System.out.println("排序前");
for (Person person : people) {
System.out.println(person.getName() + ":" + person.getAge());
}
Arrays.sort(people);
System.out.println("\n排序后");
for (Person person : people) {
System.out.println(person.getName() + ":" + person.getAge());
}
}
}
输出
排序前
xujian:20
xiewei:10
排序后
xiewei:10
xujian:20
Comparator
Comparator可以认为是一个外比较器
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
...
}
注意:
- 若一个类要实现Comparator接口:它一定要实现compare(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。
- int compare(T o1, T o2)是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。
public class Person {
String name;
int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class PersonCompartor implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
}
public class Test {
public static void main(String[] args) {
Person[] people = new Person[] { new Person("xujian", 20), new Person("xiewei", 10) };
System.out.println("排序前");
for (Person person : people) {
System.out.println(person.getName() + ":" + person.getAge());
}
Arrays.sort(people, new PersonCompartor());
System.out.println("\n排序后");
for (Person person : people) {
System.out.println(person.getName() + ":" + person.getAge());
}
}
}
输出:
排序前
xujian:20
xiewei:10
排序后
xiewei:10
xujian:20
extends 和super 泛型限定符
参考
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
- <? extends T>:是指 “上界通配符(Upper Bounds Wildcards)”
- <? super T>:是指 “下界通配符(Lower Bounds Wildcards)”
1 为什么要用通配符和边界?
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
class Plate<T>{
private T item;
public Plate(T t){item=t;}
public void set(T t){item=t;}
public T get(){return item;}
}
Plate<Fruit> p=new Plate<Apple>(new Apple());//会报错
Plate<Apple> p=new Plate<Apple>(new Apple());
容器里装的东西之间有继承关系,但容器之间是没有继承关系的。所以我们不可以把Plate的引用传递给Plate。
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());/不会报错
2 上界和下界的特点
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
//上界
List<? extends Fruit> flistTop = new ArrayList<Apple>();
flistTop.add(null);
//add Fruit对象会报错
//flist.add(new Fruit());
Fruit fruit1 = flistTop.get(0);
//下界
List<? super Apple> flistBottem = new ArrayList<Apple>();
flistBottem.add(new Apple());
flistBottem.add(new Jonathan());
//get Apple对象会报错
//Apple apple = flistBottem.get(0);
}
}
上界的list只能get,不能add(确切地说不能add出除null之外的对象,包括Object)
下界的list只能add,不能get
上界<? extend Fruit> :
表示所有继承Fruit的子类(包括Fruit),但是具体是哪个子类,无法确定,所以调用add的时候,要add什么类型,谁也不知道。但是get的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是Fruit,所以,我都可以用最大的父类Fruit接着,也就是把所有的子类向上转型为Fruit。
下界<? super Apple>:
表示Apple的所有父类,包括Fruit,一直可以追溯到老祖宗Object 。那么当我add的时候,我不能add Apple的父类,因为不能确定List里面存放的到底是哪个父类。但是我可以add Apple及其子类。因为不管我的子类是什么类型,它都可以向上转型为Apple及其所有的父类甚至转型为Object 。但是当我get的时候,Apple的父类这么多,我用什么接着呢,除了Object,其他的都接不住。
集合
黄色的代表接口,绿色的是抽象类,蓝色的具体类。下述实现Collection的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(), next(), remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。
部分底层原理可以参考:集合
List
特点:
- 可以允许重复的对象。
- 可以插入多个null元素。
- 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
- 常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
- Vector使用数组方式存储数据同时Vector中的方法添加了synchronized修饰,是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用
- 由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)
LinkedList和ArrayList:
- ArrayList是实现了基于动态数组的数据结构,LinkList是基于双向链表结构(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高)
- 对于随机访问get set方法ArrayList要优于LinkedList,因为LinkedList按序号索引数据需要进行前向或后向遍历
- 对于新增和删除操作add remove,LinkedList占优势,因为ArrayList要涉及数组元素移动等内存操作。
- 对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
- ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
- ArrayList并发add()可能出现数组下标越界异常
ArrayList和LinkedList的优缺点:
- ArrayList的优点在于可以顺序存储,随机存取,数据元素与位置相关联,因此查找效率高,索引遍历快,尾部插入与删除的速度与LinkedList的速相差无几。
- 缺点:线程不安全,插入与删除慢,需要通过移动元素来实现,因此效率低。
LinkedList的优点在于删除和添加数据所消耗的资源较少,且比ArrayList效率高。
缺点:线程不安全,查找消耗的资源大,效率低,不能随机访问。
Set
特点:
- 不允许重复对象(用对象的equals()方法来区分元素是否重复)
- 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
- 只允许一个 null 元素
- Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
Map
特点:
- Map不是collection的子接口或者实现类。Map是一个接口。
- Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
- TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
- Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
- Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
它主要有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.
Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。
HashMap
- 遍历时,取得数据的顺序是完全随机的
- 最多只允许一条记录的键为Null;允许多条记录的值为 Null
- 不支持线程的同步,如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap
- 使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。
注:Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。而快速失败则是迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量,集合在被遍历期间如果结构发生变化,就会改变modCount的值,每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。java.util包下面的所有的集合类都是快速失败的,在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出ConcurrentModificationException 异常. 但是可以通过Iterator接口中的remove()方法进行删除。而java.util.concurrent包下面的所有的类都是安全失败的,不会抛出这样的异常。
HashTable
- 不允许记录的键或者值为空
- 继承自Dictionary类
- 支持线程的同步
- 一般认为Hashtable是一个遗留的类
- HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的
LinkedHashMap
- HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.
- 当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
TreeMap
- 实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
总结
请解释为什么集合类没有实现Cloneable和Serializable接口?
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
实现Serializable序列化的作用:将对象的状态保存在存储媒体中以便可以在以后重写创建出完全相同的副本;按值将对象从一个应用程序域发向另一个应用程序域。
实现 Serializable接口的作用就是可以把对象存到字节流,然后可以恢复。所以你想如果你的对象没有序列化,怎么才能进行网络传输呢?要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化。如果你不需要分布式应用,那就没必要实现实现序列化。