回顾:
String类的特点:
1 两种实例化方式的区别?
直接赋值 开辟一块空间,会自动进入到字符串常量池
构造方法 开辟两块空间 ,其中一块是垃圾空间,不会自动进入到字符串常量池 手动进入 intern()方法
2 == 与 equals()方法比较的区别?
== 比较的是内存地址, 比较内容 使用 equals()方法 继承自 Object类 String类 覆写的s
3 String 的字符串常量一旦声明则内容不可改变
4 字符串常量其实就是String类的匿名对象
今天主要讲解内容:
泛型: 了解泛型的概念
集合 : Collection List Set Map
扩展: 链表 单向
案例: 通过集合完成 3人斗地主
一: 泛型
想要理解泛型,首先咱们做一个案例,定义一个坐标类,这个类可以表示三种类型的坐标
1 整数坐标 x=10 y=20
2 小数坐标 x=10.1 y=20.1
3 字符串坐标 y=坐标y10 x=坐标x20
要求定义这么一个类,可以存放以上三种类型
Object是所有类的父类, 所有的类型都是Object类的子类,向上转型 所有的类型都可以自动向上转型为Object类
整数 基本数据类型 int ----> 自动装箱 包装类 自动向上转型 --> Object
小数 基本数据类型 double --自动装箱 包装类 自动向上转型--->Object
字符串 本身就是一个类 自动向上转型 Object
范例: 定义个 Point类
package org.point; public class Point { private Object x; //坐标x
private Object y; //坐标y
public Object getX() { return x; }
public void setX(Object x) { this.x = x; }
public Object getY() { return y; }
public void setY(Object y) { this.y = y; }
}
|
通过以上的定义,x y 的属性可以接收任意数据类型
范例:设置整数
public static void main(String[] args) { //设置一个整数 Point p=new Point();
p.setX(10); //设置坐标x
p.setY(20);//设置坐标y
int x=(Integer)p.getX(); //取得的时候需要向下转型 (强制类型转换)
int y=(Integer)p.getY();
System.out.println("坐标x"+x+" "+"坐标y"+y);
} |
以上的功能可以接收任意类型,主要原因是使用Object类,但是每次取出的时候都需要向下转型,这个时候就存在危险因素
以上程序的问题,在于成也 Object 败也Object类
思考: 有没有一种办法在赋值的时候就可以先约定好要赋值的类型呢?
支持以上做法的方式就叫做泛型
二 、泛型的使用
如果使用泛型的话,可以在类上定义一个类型,而这个类型是一个不确定的类型,当使用这个类的时候在确定这个类型规范
范例:修改之前的程序 使用泛型
package org.point; public class Point<T> { // T 只是一个标记 type 可以随便定义的 // T 有可能是 String double int 一切皆有可能 private T x; //坐标x
private T y; //坐标y public T getX() { return x; } public void setX(T x) { this.x = x; }
public T getY() { return y; }
public void setY(T y) { this.y = y; }
}
|
以上的类 定义了泛型,在使用的时候规定好要存放的类型,则可以避免向下转型造成可能出现的异常
public static void main(String[] args) {
//设置一个整数 Point<Integer> p=new Point(); // 只能存在int类型
p.setX(10); //设置坐标x
p.setY(20);//设置坐标y
int x=p.getX(); //取得的时候需要向下转型 (强制类型转换)
int y=p.getY();
System.out.println("坐标x"+x+" "+"坐标y"+y);
} |
集合
集合的概念在JDK1.2的时候就已经被引入到java中了,所谓集合指的就是一套动态对象数组,在实际开发中数组的概念的一定会使用的,但是数组的问题是一旦开辟空间则长度不可改变
其实就是对数据结构的一种封装,用户不用去编写,直接使用。 由于数据结构开发起来比较困难,还必须考虑性能问题
3.1 集合中需要掌握的核心接口
Collection List Set Map Iterator (了解) Enumeration Queue
3.2 List接口
List接口的定义:
public interface List<E> extends Collection<E> |
通过观察List接口的定义其继承的是Collection 接口
通过观察接口之间的关系,可以得出上图
观察Collection常用方法
1 public boolean add(E e); 增加元素到集合
2 public boolean addAll(Collection<? extends E> c); 存放一个集合
3 public boolean contains(Object o); 查找集合中的元素
4 public boolean isEmpty() ; 判断一个集合是否为空
5 public boolean remove(Object o) 删除一个集合中的元素
6 public int size(); 返回集合中的长度
观察List接口中的方法
List扩展Collection中的方法
1public E get(int index); 根据指定索引取得元素
2 public E set(int index, E element) ; 替换元素, index要替换元素下标 element要替换的元素
3 public ListIterator<E> listIterator() List自己的迭代器
List接口的特点: 可重复的, 有序的
使用List list本身是一个接口,如果想要使用一个接口则可以使用该接口的实现类完成
List下面的实现类:
需要掌握的实现类 : ArrayList LindkedList Vector
范例: 使用List 接口
public static void main(String[] args) { // 泛型的使用中 只能使用类 基本数据类型不行
List<Integer> array=new ArrayList<>();
array.add(4); //赋值
array.add(3);
array.add(3);
array.add(1);
for(int i=0;i<array.size();i++) {
System.out.println(array.get(i)); //通过get取值 } } |
通过使用ArrayList发现 其特点是 可重复的,并且有序的,顺序就存储时候的顺序
通过一个 add 增加元素到集合 通过get(index) 取出集合中的元素 下标的位置从0开始
观察其中的一些其他的操作方法:
1 判断集合是否为空 pulic boolean isEmpty();
2 取得集合中的长度 pulic int size();
3 删除集合中的元素 public boolean remove(Object obj);
public static void main(String[] args) { // 泛型的使用中 只能使用类 基本数据类型不行
List<Integer> array=new ArrayList<>();
System.out.println("&&&&&"+array.isEmpty());
array.add(4); //赋值
array.add(3);
array.add(3);
System.out.println(array.remove(1)); array.add(1); System.out.println(array.isEmpty());
System.out.println(array); } |
通过观察源码发现ArrayList 是一个对象数组, 每次增加的时候 会为数组扩容,数组长度是不能改变的,每次扩容数组内容拷贝的工作 ,ArrayList如果频繁增加内容,效率不高, 但是查询的时候由于底层使用的是数组,所以查询效率会高
面试题:
ArrayList保存自定义类:
首先观察使用系统自定义的类完成ArrayList类的添加
public static void main(String[] args) { // 泛型的使用中 只能使用类 基本数据类型不行
List<String> array=new ArrayList<>();
array.add("A");
array.add("B");
array.add("C");
System.out.println(array.contains("C")); for(String s:array) {
System.out.println(s); }
} |
以上使用的类 为系统自定义的String类 其类功能已经非常完善了,现在使用用户自定义的类完成ArrayList的添加
范例:实现自定义类
package org.list; public class Person { private String name;
private int age;
public Person(String name,int age) {
this.name=name;
this.age=age; } 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; } @Override //new Person("张三",20) public boolean equals(Object obj) { // 传进来要比较的内容 // TODO Auto-generated method stub
Person per=null; if(obj instanceof Person) {
per=(Person)obj; // Object 向下转型 为 Person类型 } // this 当前的对象 Person 和 传进来的Person比较 if(this==per) { //内存地址 一样肯定是同样对象
return true; }
if(this.age==per.age&&this.name.equals(per.name)) {
return true; }
return false; }
}
|
通过代码,发现自定义类的时候,必须覆写equals方法才能完成集合中 对象查找和删除,主要原因是在于进行对象删除或者查找的时候 集合中会判断传入的元素和集合本身的元素是否是内容相同的元素 只有相同才会删除或者查找
子类 Vetor
他是一个比较古老的类,在JDK1.1 就已经推出了,功能上来说与ArryList完全一样,但是区别是什么
1 Vector 属性线程安全 同步处理
2 ArrayList 非线程安全 异步处理
同步和异步处理 区别? 业务需求 ,如果要求对数据的安全性要高 同步 如果追求速度效率 异步
前提: 都是多个对象 访问同一个资源
同步: 每个线程对象在操作完成之后,下一个线程对象才可以对资源进行访问
异步:在同一时间 多个线程对象可以同时访问一个资源
LinkedList
这个子类是基于链表的实现 指针 -节点--》-》
以下为LinkedList类的定义
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable |
接口定义: 标准 能力 外对暴露方法
Cloneable 进行对象克隆的时候 必须实现该接口 才能有克隆的能力
Serializable : 要实现序列化的类 必须实现该接口才能有序列化的能力
特点: 插入 方便 任意节点之间都可以插入 ,但是查询并不方便 效率不高
LinkedList的常用方法
1 public void addFirst(E e) ;
2 public void addLast(E e);
范例: 使用LinkedList
public static void main(String[] args) { LinkedList<String> list=new LinkedList<>(); list.add("hello");
list.add("world");
list.add("java");
list.addFirst("hadoop");
list.addLast("hive"); for(String str:list) {
System.out.println(str); } } |
ArrayList 与 LinkedList的区别?
ArrayList 基于数组开发的,查询的效率高, 但是修改数据效率低 会移动数组中的数据
LinkedList 基于链表开发的 ,两端插入的的时候效率高,但是查询的时候效率低(两端插入)
ArrayList Vector LinkedList
Set 接口
特点: 最大的特点 就是集合中的元素 不可重复 其也是Collection接口的子接口 , Set本身也是一个接口,同样不能直接使用,需要实例化该接口的实现类 Set接口下面的实现类:
常用
1 TreeSet 有序 ,不可重复
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
|
常用方法 来自Collection
范例: 观察TreeSet子类的使用
public static void main(String[] args) { Set<String> all=new TreeSet<>();
all.add("B");
all.add("C");
all.add("A");
all.add("C"); //重复元素
for(String str:all) {
System.out.println(str); } } |
通过观察以上代码发现,TreeSet 存放的数据有以下两个特点:
1 有序 为什么有序?
TreeSet的排序是通过compreable 接口中的compareTo方法完成的 该方法返回 3个取值
分别为: 1 升序 - 1 降序 0 重复元素的判断
范例: 使用自定义类完成TreeSet
public static void main(String[] args) {
Set<Person> all=new TreeSet<>();
all.add(new Person("张三",32));
all.add(new Person("李四",20));
all.add(new Person("张三",32));
all.add(new Person("隔壁老王",89)); //重复元素
for(Person per:all) {
System.out.println(per); } } |
出错了以下的错误:
Exception in thread "main" java.lang.ClassCastException: org.list.Person cannot be cast to java.lang.Comparable at java.util.TreeMap.compare(Unknown Source) at java.util.TreeMap.put(Unknown Source) at java.util.TreeSet.add(Unknown Source) at org.set.TreeSetDemo.main(TreeSetDemo.java:15) |
说明在TreeSet中进行保存的时候,约定存储的类型必须实现一个接口,而这个接口就是comparable接口,如果没有这个接口则不能实现排序功能
范例:实现compareTo方法
@Override public int compareTo(Person o) { // TODO Auto-generated method stub if(this.age>o.age) { return 1; }else if(this.age<o.age) { return -1; } return 0; } |
2 不可重复
重复元素的区分 在TreeSet中是依靠compareTo方法是否返回0来判断的
2 HashSet 无序 不可重复
HashSet 子类使用最多一个子类, 无序 ,不可重复
观察HashSet定义
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable |
范例:使用HashSet
public static void main(String[] args) { Set<String> all=new HashSet<>(); all.add("hello"); all.add("world"); all.add("hadoop"); all.add("hive"); all.add("hbase"); all.add("hello"); all.add("hello"); for(String str:all) {
System.out.println(str); } } |
使用HashSet 判断重复元素依据 是 hashCode 和 equals()方法
hashCode是什么意思?
哈希表: 字典表 k v hashcode意思通过一些运算把复杂的对象,主键, 转换为数组的下标 arrayindex 进行存储
HashCode的设计原理主要三大特性:
1 确定的 对象.hashCode 该对象无论调用多少次这个hashCode方法得出的结果 一定是一样的
2 高效的 哈希算法一定不能是复杂的
3 均匀分布的
K hash V
A 2 zxy
B 0 pqr
C 3 ijk
D 2 uvm
通过了解hashCode的原理,以及为什么在HashSet 中元素是无序的, 以及为什么依靠HashCode 和equals()方法来判断重复元素
范例:使用自定义类完成HashSet的使用
public static void main(String[] args) {
Set<Person> all=new HashSet<>(); all.add(new Person("张三",20)); all.add(new Person("李四",30)); all.add(new Person("隔壁老王",89)); for(Person per:all) {
System.out.println(per); } } |
扩展: 实现单向链表
链表其实就是一种顺序存储的数据结构,一个节点上存在两个属性 数据 指向下一个节点的指针
对于链表的操作,其实就是一组操作标准:
1 增加元素
2 删除元素
3 判断链表是否为空
3 返回链表中的长度
既然以上的操作定义为标准,则可以抽象为接口 链表类直接实现该接口中的标准
实现链表:
1 定义链表的操作标准
package org.node; public interface List { /** * 获得链表中的长度 * @return */ public int size();
/** * 判断链表是否为空 * @return */ public boolean isEmpty();
/** * 插入元素 * @param index * @param obj * @throws Exception */ public void add(int index,Object obj)throws Exception;
/** * 删除元素 * @param index * @throws Exception */ public void remove(int index)throws Exception;
/** * 取得链表中的指定元素 * @param index * @return * @throws Exception */ public Object get(int index)throws Exception; }
|
2 定义节点类
package org.node; /** * 定义节点类 两个属性 存储的数据 指向下一个节点的指针 * @author wubo * */ public class Node {
Object element; //保存的数据
Node next; //指针
//构造方法 //头节点 public Node(Node nextval) {
this.next=nextval; }
//不是头节点 public Node(Object obj, Node nextval) {
this.element=obj;
this.next=nextval; } public Object getElement() { return element; } public void setElement(Object element) { this.element = element; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } }
|
3 定义链表类
package org.node; public class LinkList implements List{ Node head; //头指针
Node current; //当前节点
int size ; //记录节点元素的个数
//初始化一个空链表 public LinkList() { // TODO Auto-generated constructor stub //初始化空的头节点 this.head=current=new Node(null); this.size=0;
}
//定位方法 找到当前对象的前一个节点 public void index(int index)throws Exception{
//要对输入的index进行判断 if(index<-1||index>size-1) {
throw new Exception("参数错误");
} if(index==-1) { //如果传进来的是头节点直接return
return ; }
current=head.next;
int j=0; //循环变量
while(current!=null&&j<index) {
current=current.next; j++; } }
@Override public int size() { // TODO Auto-generated method stub return this.size; } @Override public boolean isEmpty() { // TODO Auto-generated method stub return this.size==0; } @Override public void add(int index, Object obj) throws Exception { // TODO Auto-generated method stub
//增加之前判断参数
if(index<0||index>size) {
throw new Exception("参数错误"); }
// 定位节点 index(index-1);
current.setNext(new Node(obj,current.next)); size++; } @Override public void remove(int index) throws Exception { // TODO Auto-generated method stub
if(isEmpty()) {
throw new Exception("空链表"); }
if(index<0||index>size) {
throw new Exception("参数错误"); }
index(index-1); //定位操作
current.setNext(current.next.next);
size--; } @Override public Object get(int index) throws Exception { // TODO Auto-generated method stub if(index<-1||index>size-1) {
throw new Exception("参数错误"); }
index(index);
return current.getElement(); } }
|
测试类:
package org.node; public class NodeTest {
public static void main(String[] args) throws Exception {
LinkList list=new LinkList();
for(int i=0;i<10;i++) {
list.add(i, "i"+i); }
for(int i=0;i<list.size;i++) {
System.out.println(list.get(i)); } } }
|
作业:
(简单)
1 创建一个集合元素 0 ~9 数字 获得 3 4 5 扩大10倍
输出原集合 和删除集合中的 7 8 9
package com.judy.demo;
import java.util.ArrayList;
public class HTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
int i;
for (i = 0; i < 10; i++) {
arrayList.add(i);
}
for (int arr : arrayList) {
System.out.print(arr + " ");
}
System.out.println();
for (i = 3; i < 6; i++) {
arrayList.set(i, arrayList.get(i) * 10);
}
for (i = arrayList.size() - 1; i > 6; i--) {
arrayList.remove(i);
}
for (int arr1 : arrayList) {
System.out.print(arr1+" ");
}
}
}
(中等)
2 单向链表 修改为 单向循环链表
如果能修改成功的同学 实现一个击鼓传花的游戏 10 3 剩下最后一个人游戏结束
me: 我只修改了LinkList 还不知道对不对, 加了一个成员变量最后节点,比较糊涂,待解。
package com.judy.demo;
public class LinkList implements List {
Node head; //头指针
Node current; //当前节点
int size ; //记录节点元素的个数
//我不清楚到底是在定义链表类的时候可以直接初始化最后一个节点指向head节点,
// 还是需要定义一个方法来判断是否是最后一个节点,然后再把节点指向头节点。求解。
Node lastNode;
public void setLastNode(Node lastNode) {
lastNode.next = head;
}
//初始化一个空链表
public LinkList() {
// TODO Auto-generated constructor stub
//初始化空的头节点
this.head=current=new Node(null);
this.size=0;
}
//定位方法 找到当前对象的前一个节点
public void index(int index)throws Exception{
//要对输入的index进行判断
if(index<-1||index>size-1) {
throw new Exception("参数错误");
}
if(index==-1) { //如果传进来的是头节点直接return
return ;
}
current=head.next;
int j=0; //循环变量
while(current!=null&&j<index) {
current=current.next;
j++;
}
}
@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return this.size==0;
}
@Override
public void add(int index, Object obj) throws Exception {
// TODO Auto-generated method stub
//增加之前判断参数
if(index<0||index>size) {
throw new Exception("参数错误");
}
// 定位节点
index(index-1);
current.setNext(new Node(obj,current.next));
size++;
}
@Override
public void remove(int index) throws Exception {
// TODO Auto-generated method stub
if(isEmpty()) {
throw new Exception("空链表");
}
if(index<0||index>size) {
throw new Exception("参数错误");
}
index(index-1); //定位操作
current.setNext(current.next.next);
size--;
}
@Override
public Object get(int index) throws Exception {
// TODO Auto-generated method stub
if(index<-1||index>size-1) {
throw new Exception("参数错误");
}
index(index);
return current.getElement();
}
}
//我不清楚到底是在定义链表类的时候可以直接初始化最后一个节点指向head节点,
// 还是需要定义一个方法来判断是否是最后一个节点,然后再把节点指向头节点。求解。
那个地方不知道该怎么写,概念比较模糊吧。
1.更改之后的实现循环链表:
package com.judy.demo;
public class LinkList implements List {
Node head; //头指针
Node current; //当前节点
int size; //记录节点元素的个数
//初始化一个空链表
public LinkList() {
// TODO Auto-generated constructor stub
//初始化空的头节点
this.head = current = new Node(null);
this.size = 0;
this.head.next = head; // 第一步 改这里,把头节点的指针指向头节点,形成环
}
//定位方法 找到当前对象的前一个节点
public void index(int index) throws Exception {
//要对输入的index进行判断
if (index < -1 || index > size - 1) {
throw new Exception("参数错误");
}
if (index == -1) { //如果传进来的是头节点直接return
return;
}
current = head.next;
int j = 0; //循环变量
while (current != head && j < index) { //第二步,尾指针条件不再是空,而是指向头节点
current = current.next;
j++;
}
}
@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return this.size == 0;
}
@Override
public void add(int index, Object obj) throws Exception {
// TODO Auto-generated method stub
//增加之前判断参数
if (index < 0 || index > size) {
throw new Exception("参数错误");
}
// 定位节点
index(index - 1);
current.setNext(new Node(obj, current.next));
size++;
}
@Override
public void remove(int index) throws Exception {
// TODO Auto-generated method stub
if (isEmpty()) {
throw new Exception("空链表");
}
if (index < 0 || index > size) {
throw new Exception("参数错误");
}
index(index - 1); //定位操作
current.setNext(current.next.next);
size--;
}
@Override
public Object get(int index) throws Exception {
// TODO Auto-generated method stub
if (index < -1 || index > size - 1) {
throw new Exception("参数错误");
}
index(index);
return current.getElement();
}
}
2.实现击鼓传花:
public class Game {
//实例化链表存储游戏的人数
LinkList list=new LinkList();
int num; //总人数
int key; // 数到几退出
// 初始化游戏的方法
public Game(int num,int key) {
this.num=num;
this.key=key;
}
public void play()throws Exception{
for(int i=0;i<num;i++) { //把游戏人数增加到链表中
list.add(i, i);
}
System.out.println("-----------游戏开始之前------------------");
for(int i=0;i<list.size;i++) {
System.out.print(list.get(i));
}
System.out.println("------------------游戏开始------------------------");
int jCount=num; //开始人数 等于总人数
int j=0; //累加器
Node node=list.head; //把一个头节点赋值给一个节点
while(jCount!=1) {
if(node.getElement()!=null&&Integer.parseInt(node.getElement().toString())!=-1) {
j++;
if(j%key==0) {
node.setElement(-1);
jCount--;
System.out.println();
for(int i=0;i<list.size;i++) {
System.out.print(list.get(i)+" ");
}
}
}
node=node.next;
}
System.out.println("---------游戏结束了------------------------");
for(int i=0;i<list.size;i++) {
System.out.print(list.get(i)+" ");
}
}
public static void main(String[] args) throws Exception {
Game g=new Game(10, 3);
g.play();
}
}
总结:
概念 List 特点
ArrayList Vector 区别? LinkedList (特点)
Set 特点
HashSet (特点) TreeSet(如何排序 以及判断重复的)