1、泛型
1.1、泛型介绍
泛型技术:JDK1.5时期出现的。它的主要作用:它代表的是某种数据类型(复杂、引用类型)。需要在使用的时候进行确定,并且可以进行类型的限定。
书写格式:<引用数据类型> 尖括号中不能书写基本类型。
1.2、泛型体现
泛型大部分都应用在集合上。
/*
* 泛型技术
*/
public class GenericDemo {
public static void main(String[] args) {
/*
* 创建集合对象
* 在HashSet类的定义的时候,在类名的后面拥有<E> (public class HashSet<E>)
* <E>需要我们在使用HashSet的时候,由使用者自己来明确(确定)当前E代表的具体的类型
*/
HashSet<String> set = new HashSet <String>();
/*
* 在定义集合HashSet的时候,我们通过泛型技术限定HashSet中只能存放String类型的数据
* 下面代码在调用的时候,尝试给HashSet中保存了非String类型的数据
* 这时编译的时候编译就会检测到当前存储的类型和泛型限定的类型不一致,那么程序就无法编译通过
*/
set.add("aaa");
set.add("bbbb");
//set.add(123);
set.add("cccc");
set.add("dddddd");
// 遍历
for( Iterator<String> it = set.iterator(); it.hasNext() ; ){
String s = it.next();
// 拿String进行其他操作。
//....
}
// JDK7中添加的泛型的菱形技术
HashMap <String , Integer> map = new HashMap< String , Integer >();
map.put("张三", 23);
map.put("李四", 33);
map.put("王五", 43);
map.put("赵六", 53);
map.put("田七", 63);
Set<Entry<String, Integer>> entrySet = map.entrySet();
for( Iterator<Entry<String, Integer>> it = entrySet.iterator() ; it.hasNext() ; ){
Entry<String, Integer> entry = it.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+"...."+value);
}
}
}
1.3、自定义泛型
/*
* 演示自己定义泛型
*
* 泛型分为:
* 1、定义泛型
* 在定义类的时候,在类上使用<>定义泛型。
* 2、使用泛型
* 我们写代码的时候,用到了别的类,别的类上有泛型,那么我们使用的时候
* 就需要给出泛型代表的具体的数据类型。
*
* 泛型的定义位置:
* 1、定义在类上
* 2、定义在单独的某个方法上(静态或非静态)
* 3、定义在接口上。
*
*/
/*
* 定义在类上的泛型格式:
* 修饰符 class 类名<泛型名称>{
*
* }
* 泛型的名称:合法的标识符。但是一般这个名称中的字母会全部大写。
*/
1.3.1、泛型类
在定义类的时候,在类上定义泛型。
/*
* 定义泛型类
* class Demo<ABC> 这里在Demo后面使用<> 定义泛型,<>中的具体标识符,一般只要符合标识符的定义规则即可
* 但是一般情况下,都是根据当前这个泛型大体含义,给出其对应的那个单词
* 当其他程序中使用这个Demo类的时候,就要去明确当前在Demo上定义的泛型的具体类型
*/
class Demo< Q > {
/*
* 在类上定义的泛型,在类中是可以直接引用的。
*/
private Q x ;
public void show( Q x ){
}
}
public class GenericDemo2 {
public static void main(String[] args) {
/*
* 在使用Demo的时候,需要明确当前泛型的具体类型。
*/
Demo<Date> d = new Demo<Date>();
d.show( new Date() );
}
}
注意:类上定义的泛型,只有在创建这个类的对象的时候才能确定具体的类型。
1.3.2、泛型方法
在定义方法的时候,在方法上定义泛型。
泛型方法的定义格式:
修饰符 <E> 返回值类型 方法名( 参数列表 ){
}
注意:静态方法不能使用类上的泛型。如果静态方法需要泛型,只能在静态方法上定义自己的泛型。
1.3.3、泛型接口
在定义接口的时候,在接口上定义泛型。
/*
* 泛型接口
*/
interface Inter<W>{
public void show(W w);
}
/*
* 接口上定义的泛型,需要在定义接口的实现类的时候明确泛型的类型
*/
class InterImpl implements Inter<StringBuilder>{
@Override
public void show(StringBuilder w) {
System.out.println(w);
}
}
/*
* 泛型传递 : 实现类,在实现接口的时候,这时并不能确定泛型的具体类型,可以继续在实现类上定义泛型
* 最后需要谁使用实现类,谁明确泛型的类型。
*/
class InterImpl2<W> implements Inter<W>{
@Override
public void show(W w) {
}
}
1.4、泛型总结
- 类上的泛型:需要在使用这个类的时候明确类上的泛型的具体类型。同时在类中可以直接应用类上定义的泛型。
- 方法上的泛型:在被调用的时候,根据传递的具体数据明确泛型表示的类型。
- 静态方法不能使用类上的泛型,如果要使用,需要在当前静态方法上定义泛型。
- 接口上的泛型可以在书写实现类的时候明确,或者将泛型继续往下传递。
1.5、泛型的限定
/*
* 泛型的限定
*/
class Fu{
}
class Zi extends Fu{
}
class SubZi extends Fu{
}
public class GenericDemo4 {
public static void main(String[] args) {
ArrayList<Zi> list = new ArrayList<Zi>();
list.add( new Zi() );
list.add( new Zi() );
list.add( new Zi() );
list.add( new Zi() );
print(list);
HashSet<SubZi> set = new HashSet<SubZi>();
set.add( new SubZi() );
set.add( new SubZi() );
set.add( new SubZi() );
set.add( new SubZi() );
print(set);
LinkedList<String> ll = new LinkedList<String>();
ll.add("aaaa");
//print(ll);
}
/*
* 下面的这个打印的方法,打印的集合中存储的对象是Fu或Fu的子类都可以
*/
public static void print( Collection< ? extends Fu > coll ){
for (Iterator<? extends Fu> it = coll.iterator(); it.hasNext();) {
System.out.println(it.next());
}
}
}
泛型的限定:
上限限定:? extends E 这里?表示的类型可以是E或是E的子类类型
下限限定:? super E 这里?表示的是E或者E的父类类型。
泛型的擦除技术:它其实是编译时期的技术,当代码编译结束之后,在生产的class文件中是没有泛型的。
2、JDK5的特性
2.1、foreach循环
如果我们使用for循环仅仅只是为了遍历容器(数组、集合),那么可以使用foreach结构简化for循环的代码。
普通的for循环
for( 数据类型 变量名 = 值 ; 条件 ; 变量++ ){
}
foreach语法格式:
for( 容器中的数据类型 变量名 : 容器名 ){
循环体
}
变量名(变量) : 它中存的是从容器中取出来的数据。
//foreach结构
public static void demo() {
// 使用for遍历数组
int[] arr = {11,22,33,44,55,66};
for( int i = 0 ; i < arr.length ; i++ ){
System.out.println(arr[i]);
}
System.out.println("============");
// 使用foreach遍历
for( int x : arr ){
System.out.println( x );
}
System.out.println("============");
// 使用foreach遍历集合(集合一遍使用迭代器Iterator遍历)
ArrayList<String> list = new ArrayList<String>();
list.add("aaa");
list.add("aaa");
list.add("ddd");
list.add("eeee");
for( Iterator<String> it = list.iterator() ; it.hasNext() ; ){
System.out.println( it.next() );
}
System.out.println("===================");
// 使用foreach遍历
for( String s : list ){
System.out.println(s);
}
System.out.println("===================");
HashMap<String , String> map = new HashMap<String , String>();
map.put("aaa1", "bbbb1");
map.put("aaa2", "bbbb2");
map.put("aaa3", "bbbb3");
// keySet遍历
Set<String> set = map.keySet();
for (Iterator<String> it = set.iterator(); it.hasNext();) {
String key = it.next();
String value = map.get(key);
System.out.println(key+"..."+value);
}
System.out.println("===================");
// 使用foreach遍历HashMap
for( String k : /*set*/ map.keySet() ){
String value = map.get(k);
System.out.println(k+"..."+value);
}
}
foreach:它只能用来遍历容器。
2.2、可变参数
可变参数:使用在定义函数的函数列表中。
格式:
修饰符 返回值类型 函数名 ( 接收的数据类型 ... 变量名 ){
函数体;
}
/*
* 可变参数
*/
public class Demo {
public static void main(String[] args) {
int[] arr = {1,2,4};
int sum = getSum(arr);
System.out.println(sum);
// 其实针对可变数组,传值的时候,可以直接在调用语句中写数据
sum = getSum(1,2,3,4,5,6,7,8,9);
System.out.println(sum);
sum = getSum(1,2,3);
System.out.println(sum);
sum = getSum( );
System.out.println(sum);
}
/*
* 定义函数,计算机若干int类型数据的和值
*/
public static int getSum( int ... a ){
/*
* 可变参数上的变量名,其实就是一个数组名。
* 在函数中可以将可变参数看作一个数组
*/
int sum = 0;
for ( int i = 0 ; i < a.length ; i++) {
sum += a[i];
}
return sum;
}
}
如果函数的参数列表中除了可变参数之外,还有其他的参数,这时可变参数必须放在最后一个。
2.3、静态导入
/*
* 静态导入:
* 如果在类中使用到其他类中的静态方法或者成员变量,可以在定义类之前
* 使用import static 的方式,将这些静态的方法和变量提前导入。
*/
import static java.lang.System.out;
import static java.lang.System.currentTimeMillis;
public class Demo2 {
public static void main(String[] args) {
// 打印
out.println("你好");
// 获取系统时间
long t = System.currentTimeMillis();
long time = currentTimeMillis();
}
}
3、基本类型包装类
3.1、介绍包装类
包装类:它主要是对8种基本类型各自进行包装的。
Java本身是面向对象的语言:
在Java尽可能的将所有的事物(生活中、概念上、虚幻)将其封装成Java中的类,然后通过new关键字创建对象,调用其中的方法。
但是在Java中8种基本类型,不属于上面这样的描述。
byte 、short 、int 、long 、 float、double 、char 、boolean。它们就是简单描述数据类型,根本不提供任何的行为或属性供我们使用。
int a ; 就是开辟一个int类型的空间,名称为a。我们仅仅只能通过名称a,给其存储数据,或者获取其中的数据。而没有办法通过a像使用引用变量一样调用方法等内容。
于是Java将针对每个基本类型提供一个包装类:
byte--------》Byte
short-------》Short
int-----------》Integer
long--------》Long
float-------》Float
double-----》Double
char--------》Character
boolean----》Boolean
3.2、演示包装类使用
包装类都在java.lang包下:
/*
* 演示基本类型包装类的使用
*/
public class Demo {
public static void main(String[] args) {
// 将基本类型转成包装类型
int a = 123;
// 转成Integer类型
Integer i = new Integer( a );
Integer i2 = new Integer( "234" );
// toBinaryString :将十进制值转成二进制
System.out.println( Integer.toBinaryString(63) );
// toHexString : 转成十六进制
System.out.println( Integer.toHexString(63) );
// toOctalString : 转成八进制
System.out.println( Integer.toOctalString(63) );
System.out.println( Integer.MAX_VALUE );
System.out.println( Integer.MIN_VALUE );
// 判断是否是数字字符
char ch = 'W';
// 自己手动写判断
if( ch >='0' && ch <='9' ){
System.out.println("是数字字符");
}
// 调用包装类中的方法完成判断
if( Character.isDigit(ch) ){
System.out.println("是数字字符");
}
}
}
3.3、基本类型、包装类、字符串之间转换
3.3.1、基本类型和包装类之间的转换
基本类型到包装类型:
- 通过包装类的构造方法完成
- 使用包装类中的静态的valueOf方法
// 基本类型到包装类型
public static void demo() {
// 基本类型
byte b = 122;
// 包装类型:使用的是构造方法
Byte b2 = new Byte( b );
// 使用valueOf方法
Byte b3 = Byte.valueOf(b);
}
包装类型到基本类型:
- xxxValue方法:xxx对应的基本类型
// 包装类到基本类型
public static void demo2() {
// 包装类型
Double d = new Double(31.4);
// 到基本类型
double value = d.doubleValue();
System.out.println(value);
}
3.3.2、基本类型、包装类和字符串之间转换
基本类型转成字符串:
- 使用+号连接。
- 使用的String类中的静态的valueOf方法
- 使用基本类型对应的包装类中的静态的toString方法
// 基本类型到字符串
public static void demo() {
int a = 123;
// + 号连接
// 字符串类型
String s = a + "" ;
// 使用String类中的valueOf方法
String s2 = String.valueOf(123);
// 使用包装类中的toString方法
String s3 = Integer.toString(a);
}
包装类到字符串:
- + 号连接
- 使用的包装类中的非静态的toString方法
// 包装类型到字符串
public static void demo2() {
// 包装类型
Float f = new Float(3.34);
// + 号连接
String s = f + "";
// 调用toString方法
String s2 = f.toString();
}
字符串到基本类型和包装类型:
- 调用包装类中的静态的parseXxxx方法(字符串到基本类型)
- 字符串到包装类型(字符串到包装类)
- 调用包装类中的valueOf(字符串到包装类)
// 字符串到基本类型、包装类型
public static void demo3() {
// 字符串
String s = "3C";
// 基本类型
int x = Integer.parseInt(s, 16);
System.out.println(x);
// 包装类型
Integer i = new Integer( s );
}
3.4、自动装箱和拆箱技术
自动装箱和拆箱技术:它是JDK5中添加的内容。它们的主要目的是为简化基本类型和自己对应的包装类型之间的转换操作。
/*
* 自动拆箱:将包装类型转成基本类型
*/
public static void demo2() {
// 包装类型
Float f = new Float(3.33);
// 基本类型
float ff = f.floatValue();
/*
* 自动拆箱 : 将包装类中包裹的那个数据取出来赋值给这个基本类型
*/
float ff2 = f;
}
/*
* 自动装箱 : 基本类型到包装类型
*/
public static void demo() {
// 基本类型到包装类型:
Integer i = new Integer(123);
Integer i2 = Integer.valueOf(123);
/*
* 自动装箱代替上面的代码:
* 下面的代码内部:会先将3.14这个double类型的基本数据,转成Double对象,
* 然后将对象的内存地址放在d引用变量中。
*/
Double d = 3.14;
}
3.5、面试题
// 下面的代码都有哪些自动的转换
int x = 123; // 基本类型
Integer y = 456; // 自动装箱
/*
* 1、先将包装类型y拆箱为int类型
* 2、int类型的y中的值和x进行求和运算
* 3、将和值在自动装箱成Integer类型
*/
y = y + x;
// 面试题
public static void demo2() {
Integer i = 127;
Integer i2 = 127;
System.out.println( i == i2);
/*
* 在Integer类的内部将-128到127之前的256个数字已经封装成Integer对象。
* 如果我们在程序中使用自动装箱或者使用Integer类中的valueOf方法将基本类型转成Integer类型
* 如果转的数据在-128和127之间,这时不会再次重新创建Integer对象,而是去使用Integer内部
* 缓存的数组中找到当前这个基本类型值对应的那个对象,然后将这个对象的地址赋值给当前的这个引用变量
*
* Integer i = 127; 从缓存的数组中找到127对应的Integer对象
* Integer i2 = 127; 从缓存数组中找到的127对应的Integer对象
* 所以i和i2放的地址应该是相同。
*
* Integer i3 = 128 由于128超过缓存数组缓存的数据的范围,因此每次只要使用不在
* 缓存数组中的数据,不管是自动装箱还是调用valueOf方法都会每次创建新的Integer对象
*/
Integer i3 = 128;
Integer i4 = 128;
System.out.println( i3 == i4);
}
4、集合练习
4.1、开发流程
4.2、联系人系统
需求:系统提供可以对联系人的信息进行CRUD操作。
分析:
- 联系人有姓名、电话、住址、性别、邮箱等信息
- 提供对联系人的增、删、改、查的四种操作的功能
- 删除的:需要指定根据什么删除。假设电话不能重复,那么就可以根据电话将当前这个联系人删除。
- 修改:也要根据电话进行修改。
- 查询:分为两种情况:根据电话查询;或者是全部查询
程序需要一个入口。启动程序。只能使用控制台给出提示,然后让用户选择性的操作。
4.3、程序入口
/*
* 它是程序的入口
*/
public class Start {
// 定义成员变量 ,其中主要提供的用于键盘录入的对象
private static final Scanner sc = new Scanner( System.in );
// 定义成员变量。充当存放所有联系人的容器
private static final Map<String , Contact> map = new HashMap<String , Contact>();
public static void main(String[] args) {
System.out.println("===================欢迎使用联系人管理平台=====================");
// 调用核心业务
core();
System.out.println("===================谢谢使用联系人管理平台=====================");
}
// 负责给出用户提示信息,同时根据提示信息进行CURD的调用
public static void core() {
while(true){
System.out.println("请选择操作方式:");
System.out.println("1:添加联系人");
System.out.println("2:删除联系人");
System.out.println("3:修改联系人");
System.out.println("4:查询所有联系人");
System.out.println("5:查询单个联系人");
System.out.println("6:删除所有联系人");
System.out.println("0:退出");
// 获取用户输入的一行内容
String op = sc.nextLine();
if( op.equals("1") ){
// 调用添加联系人的功能
addContact();
}else if( op.equals("2") ){
// 调用删除联系人的功能
delete();
}else if( op.equals("3") ){
// 调用修改联系人的功能
update();
}else if( op.equals("4") ){
// 调用查询所有联系人的功能
findAll();
}else if( op.equals("5") ){
// 调用查询单个联系人的功能
findByTel();
}else if( op.equals("6") ){
// 调用删除所有联系人的功能
deleteAll();
}else if( op.equals("0") ){
// 调用退出功能
return ;
}else{
System.out.println("输入的操作类型错误,请重新输入");
}
}
}
// 修改联系人
public static void update() {
System.out.println("请输入需要修改的联系人的电话:");
String tel = sc.nextLine();
if( map.containsKey(tel) ){
//System.out.println("当前联系人的姓名是: 是否修改姓名Y/N");
System.out.println("请输入姓名:");
String name = sc.nextLine();
System.out.println("请输入住址:");
String addr = sc.nextLine();
System.out.println("请输入性别:");
String sex = sc.nextLine();
System.out.println("请输入邮箱:");
String email = sc.nextLine();
Contact con = new Contact();
con.setAddr(addr);
con.setEmail(email);
con.setName(name);
con.setSex(sex);
con.setTel(tel);
map.put(tel, con);
}else{
System.out.println("没有这个联系人!!!请确认电话是否正确!!!");
}
}
// 查询单个联系人
public static void findByTel() {
System.out.println("请输入需要查询的联系人的电话:");
String tel = sc.nextLine();
if( map.containsKey(tel) ){
System.out.println(map.get(tel));
}else{
System.out.println("没有这个联系人!!!请确认电话是否正确!!!");
}
}
// 删除所有联系人
public static void deleteAll() {
map.clear();
System.out.println("删除全部联系人成功!!!永远找不回来啦!!!!");
}
// 删除联系人
public static void delete() {
System.out.println("请输入需要删除的联系人的电话:");
String tel = sc.nextLine();
if( map.containsKey(tel) ){
map.remove(tel);
System.out.println("删除成功!!!");
}else{
System.out.println("没有这个联系人,删除失败!!!");
}
}
// 查询所有联系人
public static void findAll() {
if( map.isEmpty() ){
System.out.println("没有联系人,请先去添加");
}else{
System.out.println("=================================");
// 取出所有联系人
Collection<Contact> coll = map.values();
for (Iterator<Contact> it = coll.iterator(); it.hasNext();) {
System.out.println(it.next());
System.out.println("=================================");
}
}
}
// 负责添加联系人,包含提示用户输入联系人的信息
public static void addContact() {
System.out.println("请输入姓名:");
String name = sc.nextLine();
System.out.println("请输入电话:");
String tel = sc.nextLine();
System.out.println("请输入住址:");
String addr = sc.nextLine();
System.out.println("请输入性别:");
String sex = sc.nextLine();
System.out.println("请输入邮箱:");
String email = sc.nextLine();
// 需要一个能够封装上面这些数据的对象
Contact con = new Contact();
con.setName(name);
con.setEmail(email);
con.setAddr(addr);
con.setSex(sex);
con.setTel(tel);
// 需要一个容器,将所有的联系人保存起来。
map.put(tel, con);
System.out.println("添加成功");
}
}
public class Contact {
private String addr;
private String email;
private String name;
private String sex;
private String tel;
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
}
5、接口
5.1、总结学习的集合
ArrayList:
它底层用的数组,它有下标,同时提供围绕下标对集合中的元素进行曾删改查判断等操作。增删的速度慢,查询的速度快。它可以存放重复元素。
HashSet:
它底层是哈希表。给HashSet中保存的任何元素都需要复写Object类中的hashCode和equals方法。不能存放重复元素,不保证存取顺序。
HashMap:
它底层也使用的哈希表。它保存的key-value这样的一组具有一定对应关系的数据。key不能重复。每个key都有一个value和其一一对应。
遍历集合:
ArrayList和HashSet都可以使用Iterator进行遍历。
HashMap必须先转成单列集合,然后再使用Iterator遍历。
keySet:将所有的key保存的Set集合中。
entrySet:将key和value重新封装成一个新的Entry对象,存储在Set集合中。
5.2、接口引入
类:封装某一个群体(描述事物)。类和类之间有继承关系。
描述狗:class Dog{ }
描述猫:class Cat{ }
描述猪:class Pig{ }
上面描述了三个不同的动物,那么他们肯定会对应三个类。在三个类中可定会书写各自具备的一些行为。
假设狗经过后天的训练具备了一些不属于狗这个事物原有的行为,例如:导盲、搜救、缉毒等这些行为。这些行为也需要使用方法进行描述。如果那个小动物经过训练具备这些的行为,那么就应该在这些小动物的类中添加这些行为。
假设猫经过后天的训练也具备了导盲、搜救、缉毒等行为了。那么就业需要在猫这个类中书写这些行为。
狗和猫中出现了同名的行为,一般在开发中,如果多个类中有相同的行为(方法),我们就会将其抽取之后描述到顶层类中。可以达到尽量公用某个行为,而不需要单独描述。能不能将狗和猫经过训练所具备的行为抽取描述到Animal类中呢?
这些行为是不能给Animal类中书写的,因为这些行为根本不属于Animal自身具备的行为,这是可以将这些本来不属于这个事物体系中的额外的附加行为抽取到Java中的接口中去描述。
Java中采用接口来封装一些行为,一般都不属于整个体系中的固有内容。当某个个体具备接口中的行为的时候,单独的让这个个体和接口产生关系。
5.3、接口的定义
类的定义格式:
修饰符 class 类名{
成员变量(静态、非静态)
构造方法
成员方法(静态、非静态)
}
接口的定义格式:
修饰符 interface 接口名{
成员变量
成员方法
}
接口的修饰:
public 、默认的
接口名:
它和类名的命名规则相同,多个单词,每个单词的首字母大写。
成员变量:
接口中的成员变量具有固定的修饰符,不能更改:public static final
成员方法:
接口中的一般方法也有固定的修饰符,不能更改:public abstract
在JDK8之后,接口中的方法的修饰有改变:
public default 修饰。这个方法必须有方法体。将这个方法称为默认方法。
5.4、演示代码定义接口
/*
* 简单演示接口
*/
public interface InterDemo {
/*
* 接口中的变量都有固定的修饰符
*
*/
public static final int a = 123;
/*
* 定义接口中的变量的时候,如果没有写修饰符,那么
* 在编译生成的class文件中会自动添加,
* 因此我们在写接口中的变量的时候,可以省略前面的哪些修饰符
*/
int b = 456;
/*
* 定义方法
*/
public abstract void run();
/*
* 由于接口中的一般的方法,它们也有固定的修饰符,
* 因此定义的时候也可以省略
*/
void show();
/*
* 在JDK8中,接口里面的方法可以使用default关键字修饰
* 只要是接口中被default关键字修饰的方法,这个方法可以书写方法体
*/
public default void method(){
}
}
5.5、接口的使用
描述事物(类)的时候,事物和事物之间的关系使用继承体现。而不属于事物体系固有的行为,抽取到接口中描述。那个事物具备这个特殊的行为,需要这个事物和这个接口产生关系。
类和类之间:继承(extends)。
类和接口之间:实现(implements)。
/*
* 演示接口的基本使用
*/
/*
* 定义接口:描述的是不属于动物体系固有的行为
*/
interface GuideAble{
public abstract void guide();
}
/*
* 描述动物:定义的所有动物这个体系共有的行为
*/
abstract class Animal{
public abstract void eat();
}
/*
* 描述狗
* class Dog extends Animal : Dog是Animal中的一种
* Dog并没有脱离Animal这个事物体系。但是Dog implements GuideAble
* 那么Dog它就具备了除了Animal这个体系固有的一些行为之外的其他行为
*/
class Dog extends Animal implements GuideAble{
@Override
public void eat() {
System.out.println("啃骨头");
}
@Override
public void guide() {
// TODO Auto-generated method stub
}
}
// 描述猫
class Cat extends Animal{
@Override
public void eat() {
System.out.println("吃鱼");
}
}
// 测试类
public class InterDemo {
public static void main(String[] args) {
Cat c = new Cat();
c.eat();
Dog d = new Dog();
d.guide();
d.eat();
}
}
5.6、接口的细节
Java支持单继承,不支持多继承(如果多个父类中有同名的方法,但是方法体不同,这是子类调用父类方法会发生不确定性)。
在Java接口和接口之间支持多继承:
interface A{ }
interface B{ }
interface C extends B , A{ }
注意:接口中的方法一般默认都是抽象的方法,即使多个接口之间有相同的方法,最终这些方法都需要某个实现接口的类将方法体给具体实现了。
注意:多个接口中如果有同名的成员变量,这时如果通过实现类去使用这些成员变量,也会发生不确定性。
/*
* 接口和接口之间的问题
*/
interface A{
public static final int TEMP = 123;
public static final int TEMP1 = 123;
public void show();
}
interface B{
public static final int TEMP = 456;
public static final int TEMP2 = 456;
public void show();
}
interface C extends A , B{
public static final int TEMP = 789;
public static final int TEMP3 = 789;
public void show();
}
class Demo implements C , A , B{
@Override
public void show() {
}
}
public class InterDemo2 {
public static void main(String[] args) {
Demo d = new Demo();
// 多个接口中有同名的方法,最终调用的肯定是实现类中的那个复写的方法
d.show();
/*
* 多个接口中有同名的变量,调用不能通过实现类的名称去调用,因为不确定到底使用的是哪个接口中的变量
* 多个接口中的变量名,不同名,可以通过接口名或实现类的名称去调用。
* 如果要使用接口中的变量,只能通过当前某个接口自己的接口名去使用
*/
// 调用不同命的接口中的变量
int x = Demo.TEMP1;
// 调用接口中同名的变量
int y = Demo.TEMP; // 编译报错,因为使用实现类调用接口中同名的变量
int z1 = A.TEMP;
int z2 = B.TEMP;
int z3 = C.TEMP;
}
}
总结:
类和类之间:继承(extends),单继承。
类和接口之间:实现(implements),多实现。
接口和接口之间:继承(extends),多继承。
5.7、适配器设计模式
适配器设计模式:它主要的作用是解决接口和真正实现类之间的一个过渡问题。
假设:一个接口中有很多的抽象方法,现在需要使用这个接口中的部分方法,这时会先定义一个抽象类,让这个抽象类去实现这个接口,并将接口中的所有方法全部给空实现(方法体中没有任何的代码)。真正需要使用接口的类不要再去实现接口,而是改为去继承当前这个抽象类。然后复写自己需要使用的那几个方法即可。
/*
* 适配器演示
*/
interface Inter{
public void show1();
public void show2();
public void show3();
public void show4();
public void show5();
}
// 一般一个接口中的方法超过2个,大部分情况下都会给这个接口提供一个适配器类
abstract class InterAdpter implements Inter{
@Override
public void show1() {}
@Override
public void show2() {}
@Override
public void show3() {}
@Override
public void show4() {}
@Override
public void show5() {}
}
// 在开发中,经常会在某个实现接口的类后面书写Impl
class InterImpl extends InterAdpter{
public void show2(){
// 是需要使用的方法
}
public void show3(){
// 是需要使用的方法
}
}
public class InterDemo3 {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.show2();
ii.show3();
}
}
6、介绍集合中的接口
6.1、List接口
6.1.1、List接口介绍
ArrayList集合,它可以存储重复元素,有下标。增删慢、查询快。而ArrayList它就是List接口下的一个实现类。
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。难免有人希望通过在用户尝试插入重复元素时抛出运行时异常的方法来禁止重复的列表,但我们希望这种用法越少越好。
阅读List接口的API,告诉我们List接口它是在定义所有有下标,可以放重复元素的集合的共性的行为。
6.1.2、LinkedList集合
ArrayList集合是List接口的一个实现类。但是List接口下面还有其他的实现类。
LinkedList集合:它也有下标,也可以存放重复元素。LinkedList集合,它主要可以通过头和尾的方式对集合中的元素进行CRUD操作。
LinkedList集合:它虽然有下标,可以保存重复元素,但是真正使用LinkedList集合的时候,基本都是在使用它特有的这些方法,来模拟另外两种数据存储的方式。如果程序仅仅是存储对象,只通过下标操作,建议优先使用ArrayList。
队列结构:存储数据的时候,最先存入到容器中的数据最先被取出。先进先出、后进后出。
例如:火车过山洞。
堆栈结构:存储的数据,最先存入到容器中的数据是最后被取出的。先进后出、后进先出。
例如:方法运行时的栈内存、手枪弹夹。
// 模拟数据结构
public static void demo2() {
// 创建集合容器对象
LinkedList list = new LinkedList();
// 一直是给集合的头部添加
list.addFirst("aaa");
list.addFirst("bbb");
list.addFirst("ccc");
list.addFirst("ddd");
// 模拟队列结构
list.removeLast();
// 模拟堆栈结构
list.removeFirst();
}
// 简单演示
public static void demo() {
// 创建集合容器对象
LinkedList list = new LinkedList();
list.addFirst("aaa");
list.addFirst("bbb");
list.addFirst("ccc");
for( Iterator it = list.iterator() ; it.hasNext() ; ){
System.out.println(it.next());
}
}
如果需要使用LinkedList集合模拟上面的两种结构:
队列:addFirst、removeLast ; addLast 、removeFirst
堆栈:addFirst、removeFirst ; addLast 、removeLast
6.1.3、ListIterator接口
由于List接口规定:它们描述的集合都有下标,可以重复。因此针对List接口下面的所有实现类(集合)提供了除去Iterator之外的另外一个迭代器ListIterator。
ListIterator:它是针对List接口而设计的一个特有的迭代器,可以对List接口下的任何集合进行正向或逆向的遍历。遍历的过程中可以通过ListIterator迭代器中的方法对集合进行CRUD操作。
/*
* 演示List接口下的特有的迭代器
*/
public class ListIteratorDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("eee");
System.out.println("使用 Iterator 遍历");
// 普通的Iterator遍历
for( Iterator<String> it = list.iterator() ; it.hasNext(); ){
System.out.println(it.next());
}
System.out.println("使用ListIterator正向遍历");
/*
* ListIterator<String> lit = list.listIterator()
* 通过集合获取到ListIterator迭代器,这是迭代器默认的光标也在集合的第一个元素的前面
*/
for(ListIterator<String> lit = list.listIterator() ; lit.hasNext() ; ){
System.out.println(lit.next());
}
System.out.println("使用ListIterator逆向遍历");
/*
* ListIterator<String> lit = list.listIterator( list.size() )
* 由于上面在获取ListIterator的时候,给参数中传递了具体的数据,而这个数据恰好是集合中最有一个元素的后面的位置
* 这样就导致直接获取到的ListIterator迭代器的光标位于集合最后一个元素的后面,再调用hasNext方法,肯定是没有任何元素
*/
for( ListIterator<String> lit = list.listIterator( list.size() ) ; lit.hasPrevious() ; ){
System.out.println(lit.previous());
}
}
}
6.1.4、Vector集合
List接口,以及其下的ArrayList、LinkedList等集合它们都是在JDK1.2版本的时候才出现的集合。而在JDK1.2版本之前,也有集合概念。只不过所有的单列数据都使用Vector集合存储。所有的有对应关系的数据都使用Hashtable存储。
Vector:它是JDK1.0时期的集合。在JDK1.2的时候Vector称为List接口的一个实现类。
Vector它在JDK1.2之后被ArrayList代替。因此我们只要看到程序中有Vector的地方,都可以将Vector当做ArrayLis使用。
/*
* 演示Vector集合
*/
public class VectorDemo {
public static void main(String[] args) {
// 创建集合对象
Vector<String> v = new Vector<String>();
v.addElement("aaaa");
v.addElement("aaaa");
v.addElement("bbbb");
v.addElement("dddd");
// 遍历
for( Iterator<String> it = v.iterator(); it.hasNext() ; ){
System.out.println(it.next());
}
/*
* Iterator 、 ListIterator 这些迭代器都是JDK1.2版本之后才有的迭代器。
* 在JDK1.2之前, Vector集合肯定也需要遍历。
*
* 在JDK1.2之前的时候,使用的迭代器叫:Enumeration。
*/
for( Enumeration<String> en = v.elements(); en.hasMoreElements() ; ){
System.out.println( en.nextElement() );
}
}
}
6.1.5、Enumeration接口
Enumeration接口它的功能和Iterator功能重复,并且API中告诉我们优先使用Iterator。
6.2、Set接口
6.2.1、Set接口介绍
HashSet集合:它里面不能存放重复元素,不保证元素的存储顺序。HashSet集合,它恰好是Set接口的实现类。
一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2)
的元素对 e1
和 e2
,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
Set接口规定:其下面的集合中不能保存重复元素。
Set接口下面的常用集合类:
HashSet:不重复,不保证存取顺序,存放的元素需要复写Object类中的hashCode和equals方法。
TreeSet:不重复,它可以对其中的元素进行排序。
LinkedHashSet:不重复,可以保证存取顺序。存放的元素需要复写Object类中的hashCode和equals方法。
6.2.2、TreeSet集合
TreeSet:可以对其中的元素进行排序,在创建TreeSet对象的时候,如果什么都没有传递,那么就会选择默认的方式排序(升序、按照自然(字典)顺序)。或者在创建TreeSet的时候给其传递Compartor的对象,这时TreeSet就会根据传递的Compartor中定义的方式排序。
/*
* 演示 创建TreeSet集合,使用默认顺序
*/
public class TreeSetDemo {
public static void main(String[] args) {
// 创建集合对象
TreeSet<String> set = new TreeSet<String>();
// 添加元素
set.add("zzzzzzz");
set.add("ABefhjdhfjkdhC");
set.add("abc");
set.add("AAAA");
set.add("AAAA");
set.add("aaaa");
set.add("aaaa");
set.add("123");
// 遍历:Set下面的集合只能使用Iterator遍历
for( Iterator<String> it = set.iterator() ; it.hasNext() ; ){
System.out.println( it.next() );
}
}
}
6.2.3、TreeSet中存放自定义对象
/*
* 自定义的类,最终演示给TreeSet中保存
*/
public class Person {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
/*
* 演示给TreeSet中保存自定义对象
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Person> set = new TreeSet<Person>();
// 添加元素
set.add( new Person( "zhaosi", 13) );
set.add( new Person( "tianqi", 28) );
set.add( new Person( "wangermazi", 28) );
set.add( new Person( "wangliu", 33) );
set.add( new Person( "zhaosi", 13) );
// 遍历
for( Iterator<Person> it = set.iterator() ; it.hasNext() ; ){
System.out.println( it.next() );
}
}
}
将自定义类Person对象存储到TreeSet集合中,发现代码运行出现了问题: Person无法被转成Comparable类型。
报错的原因:TreeSet集合会对存放在其中的元素进行排序。排序的本质就是对两个元素(对象)进行大小的比较, 最终确定出彼此大小之后,才能放到集合中。而TreeSet集合本身其实使用的是存放在其中对象的比较功能在对对象进行大小的比较。也就是要求存放到TreeSet集合中的对象必须提供比较大小的功能,TreeSet集合才能完成排序。如果给TreeSet集合中保存的对象比具备比较功能,那么程序运行就会发生上面的问题。
6.2.4、Comparable接口
Java中要求如果某个类需要具备比较大小的功能,那么这个类必须是实现Comparable接口。只要类实现了Comparable接口,就意味着这个类中一定会有比较大小的行为。
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
/*
* 自定义的类,最终演示给TreeSet中保存
*
*/
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
/*
* Person类实现了Comparable接口,那就表示Person类中具备了比较大小的行为
* 而Comparable接口中专门负责比大小的方法:compareTo
* compareTo方法返回结果:
* 正整数:调用compareTo方法的那个对象(this)比传递进来的这个对象大
* 零:调用compareTo方法的那个对象(this)和传递进来的这个对象相等
* 负整数:调用compareTo方法的那个对象(this)比传递进来的这个对象小
*/
public int compareTo(Person o) {
/*
* 在compareTo中,需要书写具体根据Person的name还是age进行比较
*/
if( this.name.equals(o.name) && this.age == o.age ){
return 0;
}
/*
* 如果程序能够执行到这里,说明年龄相等,姓名不同
*
* 现在需要比较的是姓名,而姓名是String类型的。
* 其实比较姓名,就是在比较两个字符串的大小。那么就应该调用字符串类中提供的比较大小的行为
*/
if( this.name.compareTo(o.name) > 0 ){
return 1;
}
if(this.name.compareTo(o.name) < 0 ){
return -1;
}
// 先比较年龄
if( this.age > o.age ){
return 1;
}
else{
return -1;
}
}
}
6.2.5、Comparator接口
如果某个类已经具备比较的功能,但是比较的方式和需求中想要的结果不一致。这时不要直接去改类中的比较方式。我们可以使用Comparator完成自己想要的比较方式。
Comparator:把实现这个接口的类称为比较器。Comparator的主要功能是对任何两个对象进行大小的比较。被比较的类和Comparator可以没有任何的关系。
/*
* 演示 Comparator接口的使用
*/
// 定义类,实现Comparator接口
public class MyComparator implements Comparator<Person>{
/*
* 类实现了Comparator接口,需要在类中书写compare方法,
* 在方法中书写具体的比较的方式:
*
*/
public int compare(Person o1, Person o2) {
int temp = -(o1.getAge() - o2.getAge()) ;
return temp == 0 ? o2.getName().compareTo(o1.getName()) : temp ;
}
}
/*
* 演示比较器的使用
*/
public class TreeSetDemo3 {
public static void main(String[] args) {
// 创建自己的比较器
MyComparator com = new MyComparator();
// 创建TreeSet对象
TreeSet<Person> set = new TreeSet<Person>( com );
// 添加元素
set.add(new Person("zhaosi", 28));
set.add(new Person("zhaosi", 28));
set.add(new Person("tianqi", 28));
set.add(new Person("wangermazi", 28));
set.add(new Person("wangliu", 33));
set.add(new Person("zhaosi", 13));
// 遍历
for (Iterator<Person> it = set.iterator(); it.hasNext();) {
System.out.println(it.next());
}
}
}
6.2.6、Comparable和Comparator
相同点:Comparable和Comparator它们都可以对对象进行比较。返回的结果:负整数、零、正整数。
不同点:
- Comparable:必须是让对象对应的类实现这个接口(Person类实现Comparable接口),让这个类自身具备了比较的功能。
- Comparator:它不需要和被比较的类之间有任何的关系,只要在实现Comparator接口的那个类中的compare方法中书写具体需要比较的两个对象的代码。
- Comparable中的比较方法compareTo,Comparator中的比较方法compare。
6.3、Collection接口
List接口:定义的是所有有下标,可以存放重复元素的集合共性方法。
Set接口:定义的是所有不能存放重复元素的集合共性方法。
不管是List接口还是Set接口,这两个接口中还有共性的方法,将其抽取到了Collection接口中。
6.4、Map接口
6.4.1、Map接口介绍
HashMap集合:它是双列集合,主要存放有一定对应关系的数据。数据以key-value的方式存在,key不能重复。HashMap集合是Map接口的一个直接实现类。
6.4.2、TreeMap集合
Map接口下面有两个比较重要的集合:
HashMap:它底层使用的哈希表。哈希表主要作用在Map的key上。因此要求:如果使用自定义对象作为HashMap的key值,自定义对象必须复写hashCode和equals方法。
TreeMap:它底层使用的二叉树。
/*
* 演示TreeMap集合
*/
public class TreeMapDemo {
public static void main(String[] args) {
// 创建集合对象
TreeMap<String , String> map = new TreeMap<String , String>();
map.put("AAA", "aaaa");
map.put("ABC", "abc");
map.put("CBA", "cba");
map.put("CBA", "cba");
map.put("BCA", "bca");
map.put("BCA", "bca");
/*
* keySet遍历
*/
Set<String> keySet = map.keySet();
for( Iterator<String> it = keySet.iterator() ; it.hasNext() ; ){
String key = it.next();
String value = map.get(key);
System.out.println(key+"..."+value);
}
/*
* entrySet遍历
*/
Set<Entry<String,String>> entrySet = map.entrySet();
for( Iterator<Entry<String,String>> it = entrySet.iterator() ; it.hasNext() ; ){
Entry<String , String> entry = it.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"==="+value);
}
}
}
6.4.3、Hashtable集合
Hashtable:它是JDK1.0时期存在的一个双列集合。和Vector同时存在。在JDK1.2之后被Map接口收编。也就是从JDK1.2之后Hashtable被HashMap代替。
/*
* 演示古老的双列集合Hashtable
*/
public class HashtableDemo {
public static void main(String[] args) {
// 创建集合对象
Hashtable<String , String> table = new Hashtable<String , String>();
table.put("abc", "bbb");
table.put("ABC", "qwer");
table.put("BCD", "qaz");
// 遍历 : 获取到的是所有的key组成的一个迭代器
Enumeration<String> keys = table.keys();
while( keys.hasMoreElements() ){
String key = keys.nextElement();
String value = table.get(key);
System.out.println(key + ":" + value);
}
// 拿到的是所有的value组成的迭代器
Enumeration<String> elements = table.elements();
while( elements.hasMoreElements() ){
System.out.println( elements.nextElement() );
}
}
}