java学习第三周总结
- 2020 年 3 月 18 日
- 筆記
1. 多态
多态在java中是一个比较难入门理解的内容,有点不着边际。所以我们先用接口来初步解释一下多态的基础通俗含义,以后再来慢慢补充。
我们在之前的学习中知道了接口的含义,也基本了解了接口的使用方法,接下来我们就用接口来演示一下多态。
1.1 接口生活化演示
从生活中映射USB接口
interface USB 规定USB设备必须完成的方法 void connect();
鼠标类 implements USB 鼠标是一个USB设备,必须完成connect方法
键盘类 implements USB 键盘是一个USB设备,必须完成connect方法
USB接口在电脑上,我们需要使用USB接口
/* 从生活中映射USB接口 interface USB 规定USB设备必须完成的方法 void connect(); 鼠标类 implements USB 鼠标是一个USB设备,必须完成connect方法 键盘类 implements USB 键盘是一个USB设备,必须完成connect方法 */ /** * USB接口 * @author Anonymous * */ interface USB { /** * 要求所有的USB设备,必须完成的方法,告知USB接口连接之后完成的 * 功能是什么 */ void connect(); } /** * 鼠标类,遵从USB接口,实现connect方法 * * @author Anonymous */ class Mouse implements USB { @Override public void connect() { System.out.println("鼠标连接USB接口,控制光标"); } } /** * Logi类,继承Mouse鼠标类 * 1. 鼠标设备 * 2. Logi间接遵从USB接口,是一个USB设备 * @author Anonymous * */ class Logi extends Mouse { @Override public void connect() { System.out.println("Logi Master 2S"); } } /** * 键盘类,遵从USB接口,实现connect方法 * * @author Anonymous */ class Keyboard implements USB { @Override public void connect() { System.out.println("键盘连接USB接口,输入设备"); } } /** * IKBC继承Keyboard类 * 1. 键盘设备 * 2. 间接遵从USB接口,也是一个USB设备 * @author Anonymous * */ class IKBC extends Keyboard { @Override public void connect() { System.out.println("IKBC C87 静音红轴"); } } /** * PC电脑类,使用USB接口,这里需要通过USB接口连接一个USB设备 * * @author Anonymous */ class PC { /** * 电脑类连接USB接口连接方法,这里需要的是一个USB设备 * * @param usb USB接口对应的设备 */ public void usbConnect(USB usb) { // usb设备执行connect方法 usb.connect(); } } public class Demo1 { public static void main(String[] args) { // 电脑中有一个方法是使用USB接口连接USB设备 PC pc = new PC(); // 鼠标和键盘都是USB设备 Mouse mouse = new Mouse(); Keyboard keyboard = new Keyboard(); // 电脑需要通过USB接口连接USB设备,mouse鼠标就是USB设备 pc.usbConnect(mouse); // keyboard键盘也是USB设备 pc.usbConnect(keyboard); // 传入一个Logi类对象 pc.usbConnect(new Logi()); // 传入一个IKBC类对象 pc.usbConnect(new IKBC()); } }
1.2 继承生活化演示
动物园: 所有的动物都可以看做是一个Animal类 狗类 Dog
熊猫类 Panda
老虎类 Tiger
方法: 喂食动物的方法 获取动物的方法
/* 动物园: 所有的动物都可以看做是一个Animal类 狗类 Dog 熊猫类 Panda 老虎类 Tiger 方法: 喂食动物的方法 获取动物的方法 */ /** * 动物类 * @author Anonymous * */ class Animal { } /** * 狗类,为Animal动物的子类 * @author Anonymous * */ class Dog extends Animal { } /** * 熊猫类,为Animal动物的子类 * @author Anonymous * */ class Panda extends Animal { } /** * 老虎类,为Animal动物的子类 * @author Anonymous * */ class Tiger extends Animal { } public class Demo2 { public static void main(String[] args) { Animal animal = new Animal(); Dog dog = new Dog(); Tiger tiger = new Tiger(); Panda panda = new Panda(); // 这里传入一个Animal类对象 feed(animal); // 这里可以传入Dog类对象,因为Dog类对象是Animal类的子类对象 feed(dog); // 这里传入的对象是Tiger类对象,Tiger类是Animal的子类 feed(tiger); // 同上 feed(panda); // 数据类型强转, Dog dog2 = (Dog) getAnimal(); System.out.println(dog2.getClass()); } /** * 该方法的是喂食【动物】的方法 * * @param animal 需要的参数是Animal类对象 */ public static void feed(Animal animal) { // 获取当前对象的完整包名.类名 System.out.println(animal.getClass() + "来吃饭了!!!"); } /** * 返回【动物】类对象的方法 * * @return Animal类对象 */ public static Animal getAnimal() { // return new Animal(); return new Dog(); // return new Tiger(); // return new Panda(); } }
通过上边两个代码的实现,不难看出主要是接口的引用指向引用借口的类对象,还有父类的引用指向子类的对象。 这个就叫做多态!!!
作用: 1. 拓宽方法的参数范围 例如: 方法参数为Animal类型 Animal类对象 可以传入Animal类型本身,或者子类对象都可以 方法参数为USB接口类型 只要是直接或者间接遵从USB接口的类对象可以作为方法的参数传入 2. 拓宽方法的返回值范围。与参数同理。 3. 简化代码开发,提高开发效率,整合数据类型。
2. 异常
这个我已经在之前的博客中总结过了,可以去看一下我的“异常基础”。 异常基础
3. 泛型
可能我比较特殊,也可能是我学习深度,广度都不太够。在我看来,泛型的学习比多态还要更难一些…
3.1 什么是泛型,为什么引入泛型
在我们学习泛型之前,所有编写的功能代码是要考虑之后的复用!!!
当前代码有且只支持一种类型,如果需要更换数据类型,按照目前的技术,只能重新完成对应数据类型的功能代码。有可能出现各式各样的类型,工作是重复而且无意义的。
这里不单单是对于功能模块的要求封装。同时需要对数据类型进行统一的约束!!!根据用户的要求对应数据类型。
Java的泛型就是来解决对应的问题
格式: <自定义泛型无意义大写英文字母占位符> 一般使用<T> <E> <K> <V>
泛型可以在方法中使用 泛型可以在类中使用 泛型可以在接口中使用
3.2 泛型在方法中使用
使用静态方法举例说明泛型在方法中的使用格式
格式: public static 返回值类型[自定义泛型] 方法名(必须存在一个对应泛型的参数) { 方法体内可以使用自定义泛型 }
使用注意事项: 1. 自定义泛型声明在返回值之前,已保证方法的参数和返回值都可以使用对应的泛型 2. 方法声明的泛型,在方法的参数中必须有一个参数是对应声明的自定义泛型。当前参数是用于约束方法内所有使用到泛型的位置对应的具体数据类型是什么。 3. 代码演示如下
public class Demo1 { public static void main(String[] args) { /* * test方法使用了自定义泛型,传入的实际参数是什么类型 * 泛型T对应的具体数据类型是什么类型 */ // 首先定义几个变量 Integer test = test(1234234); String test2 = test("武汉加油"); Demo1 test3 = test(new Demo1()); // 定义字符串类型数组 String[] arr1 = { "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" }; // 调用方法 printArray(arr1); System.out.println("-----------------------"); // 定义Integer类型数组,Integer是int的包装类,可以理解为int大哥 Integer[] arr2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; printArray(arr2); System.out.println("-----------------------"); // 定义Demo1类型数组 Demo1[] arr3 = {new Demo1(), new Demo1(), new Demo1(), new Demo1(), new Demo1()}; printArray(arr3); } /* * 声明一个带有自定义泛型的方法 */ /** * 带有自定义泛型声明的方法 * * @param <T> 自定义泛型无意义占位符 * @param t 指定的泛型数据类型,由用户约束 * @return 对应的T类型, 具体数据类型由用户约束 */ public static <T> T test(T t) { return t; } /* * 展示任意类型数组中数据的方法 */ /** * 展示任意类型数组的方式 * * @param <T> 自定义泛型无意义占位符 * @param arr 用户指定类型的数组,同时约束自定义泛型对应的具体数据类型 */ public static <T> void printArray(T[] arr) { for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } }
3.3 泛型可以在类中使用
这部分有一些比较难理解的点
格式: class 类名<自定义泛型无意义大写字母占位符> { 在类内的成员变量和成员方法都可以使用自定义泛型 建议: 成员变量不建议使用自定义泛型 }
使用注意事项: 1. 类声明自定义泛型,需要通过创建对象的方式来约束 TypeA typeA = new TypeA(); Eclipse标准写法 TypeA typeA = new TypeA<>(); IDEA写法 2. 类声明泛型约束之后,在类内的所有成员方法中使用的泛型都是类约束的泛型具体数据类型 3. **如果没有约束类声明泛型,所有使用到泛型的位置都是Object,【不推荐】**Object是所有类的超类,可以理解为盘古爸爸 4. 类声明的自定义泛型不能用于类内的静态方法【没有对象】 类泛型约束靠的是类对象,他是个快乐的单身狗,怎么约束他?不理解静态的,可以看一下我的另一篇博客static关键字
下边我们通过代码来验证一下:
/* * 定义一个带有自定义泛型的类 */ class TypeA<T> { /** * 使用和类名一致的自定义泛型,需要通过【类对象】来约束 * * @param t 对应泛型具体数据类型的参数 * @return 对应泛型具体数据类型的返回值 */ public T test1(T t) { return t; } /** * 使用和类名一致的自定义泛型,需要通过【类对象】来约束 * * @param t1 对应泛型具体数据类型的参数 * @param t2 对应泛型具体数据类型的参数 */ public void test2(T t1, T t2) { System.out.println(t1); System.out.println(t2); } /* * 为什么类名自定义的泛型不能用于当前类内的静态成员方法 * 静态成员方法是在类文件的加载阶段准备完毕,已经可以明确的保证该方法的是 * 可以执行的。 * 如果使用类声明的自定义泛型,对应泛型的具体数据类型需要在创建类对象之后 * 才可以明确。 * 当前静态方法在类文件加载阶段无法明确泛型数据类型是哪一个,也就无法保证 * 执行,【没有对象】 * * 如果方法想要使用自定义泛型,自娱自乐,自己定义自己用!!! * 要求自定义的泛型占位符和类声明泛型占位符不一致,避免没有必要的歧义,提高阅读性 */ public static <E> E staticMethod(E e) { return e; } } public class Demo2 { public static void main(String[] args) { /* * 创建带有自定义泛型类的对象 * TypeA<String> typeA = new TypeA<String>(); Eclipse标准写法 * TypeA<String> typeA = new TypeA<>(); IDEA写法 */ // 明确告知编译器,这里泛型对应的具体数据类型是String类型 // 类内所有使用到泛型占位符的位置都是的T类型 TypeA<String> typeA = new TypeA<String>(); String test1 = typeA.test1("北京烤鸭"); typeA.test2("卤煮火烧", "豆汁"); // 明确告知编译器这里泛型对应的具体数据类型是Demo2类型 TypeA<Demo2> typeA2 = new TypeA<Demo2>(); Demo2 test12 = typeA2.test1(new Demo2()); typeA2.test2(new Demo2(),new Demo2()); /* * 没有告知编译器泛型对应的具体数据类型,那么在类内的所有非静态 * 成员方法方法中使用到的泛型, 都是Object类型,虽然可以支持任意 * 类型传入参数,但是缺少了数据类型一致化的约束。 * * 不推荐!!! */ TypeA typeA3 = new TypeA(); } }
3.4 泛型可以在接口中使用
接口 interface 接口名 { 成员变量缺省属性: public static final 定义时必须初始化,并且初始化之后无法修改 成员方法缺省属性: public abstract 方法没有方法体 有方法体如何使用: default修饰默认方法,非强制实现方法 }
泛型在接口中使用格式 interface 接口名<自定义泛型无意义占位符> { 问题: 接口的泛型是否可以用于成员变量中? T t 定义时必须初始化,但是当前泛型的数据类型是不明确的,无法进行初 始化过程。和final有关。 接口中的泛型有且只能用于成员方法! }
使用注意事项:
- 接口声明的泛型,有且只能用于类内的成员方法。 问题: default修饰的方法是否可以使用接口声明的自定义泛型? 可以,因为有方法体,有对象。
- 遵从带有自定义泛型的接口的类,一种可以遵从过程中明确告知泛型具体类型,一种是在创建类对象是明确泛型具体数据类型。
- class TypeA<T> implements A<String> 正确 接口的泛型明确接口中的方法,使用泛型对应的具体数据类型 类声明的泛型用于自定义约束自己类内的方法
- class TypeA<E> implements A<T> 错误 接口中的泛型没有明确数据类型,也无法通过类创建对象的过程中明确 泛型对应的具体数据类型,无法编译
代码实现:
interface A<T> { void test(T t); } /* * 接口带有泛型,遵从接口的实现类有两种方案可以implements当前接口 * 1. 自由 * 2. 规矩 */ /** * 自由方式,类名之后和接口同名自定义泛型,泛型对应的具体数据类型是 * 需要在创建当前类对象是进行约束使用。 * * @author Anonymous * * @param <T> 遵从接口和接口一致的泛型 */ class TypeB<T> implements A<T> { @Override public void test(T t) { System.out.println("自由模式"); } } /** * 规矩模式,遵从接口时,接口使用泛型对应的具体数据类型已经明确 * 在类内使用接口中缺省属性为public abstract方法时,泛型已确认 * * @author Anonymous */ class TypeC<T> implements A<String> { @Override public void test(String t) { System.out.println("规矩模式"); } } public class Demo3 { public static void main(String[] args) { /* * 自由模式 */ TypeB<Integer> typeB = new TypeB<Integer>(); typeB.test(10); TypeB<Character> typeB2 = new TypeB<Character>(); typeB2.test('A'); TypeC typeC = new TypeC(); typeC.test("比较无聊,但是很规矩"); } }
4. 集合
4.1 为什么使用集合
开发中会使用大量相同数据类型的情况。如果使用数组来解决问题 1. 数组能够使用的方法非常少,功能方法需要程序员自己完成。 2. 数据类型单一化,不支持多种情况。 3. 数组容量不可以更改。
集合为解决问题而生: 1. 方法多种多样,基本功能完善 2. 数据类型支持多样化,但是又不失数据类型一致要求 3. 容量可以变,并且不用开发者操心
4.2 集合架构
Java中集合的【总接口】Collection。Java中所有和集合有关的内容,都是Collection接口的子接口或者实现类
interface Collection –| interface List List接口,有序可重复 —-| class ArrayList 【重点】可变长数组结构 原码实现,了解其中的特征,性能… —-| class LinkedList 【重点】双向链表结构 —-| class Vector 【远古时代】JDK1.0 线程安全的ArrayList,如果不考虑线程安全问 题,建议使用ArrayList –| interface Set Set接口,无序不可重复 —-| HashSet 底层存储数据的方式是采用哈希表方式 —-| TreeSet 底层存储数据的方式一个平衡二叉树方式
以上这些东西我们之后会一一讲解,现在我们先来了解一下collection接口的常用方法
4.3 Collection<E>接口下的常用方法
增: boolean add(E e); 存入元素到当前集合对象中,这里要求的数据类型是E类型,也就是泛型对于 的具体数据类型 boolean addAll(Collection<? extends E> c); class Dog extends Animal class Cat extends Animal class Tiger extends Animal ==> ? extends E 泛型的上限 要求存入的集合c中,存储的元素要么是E类型,要么是E类的子类 删: void clear(); 清空整个集合 boolean remove(Object obj); 删除集合中的指定元素 boolean removeAll(Collection<?> c); 删除两个集合的交集 boolean retainAll(Collection<?> c); 保留两个集合的交集 查: int size(); 返回集合中有效元素个数 boolean isEmpty(); 判断当前集合是否为空 boolean contains(Object obj); 判断指定元素在当前集合中是否存在 boolean containsAll(Collection<?> c); 判断集合c是不是当前集合的子集合
以下是代码的实现: 注意导包,之前的部分代码也需要导包 就是这个: import java.util.ArrayList; import java.util.Collection;
public class Demo1 { public static void main(String[] args) { /* * 因为Collection<E>是一个接口,接口没有自己的类对象 * 这里使用Collection接口的实现类来完成演示过程 ArrayList<E> */ Collection<String> c = new ArrayList<String>(); c.add("82年的拉菲"); c.add("82年的雪碧"); c.add("82年的可乐"); c.add("82年的老雪"); System.out.println(c); Collection<String> c1 = new ArrayList<String>(); c1.add("百威"); c1.add("福佳白"); c1.add("精酿啤酒"); c1.add("修道院啤酒"); c.addAll(c1); System.out.println(c); c.remove("82年的雪碧"); System.out.println(c); //c.removeAll(c1); //System.out.println(c); //c.retainAll(c1); //System.out.println(c); System.out.println("size:" + c.size()); System.out.println(c.isEmpty()); System.out.println(c.contains("百威")); System.out.println(c.contains("哈尔滨")); System.out.println(c.containsAll(c1)); c1.add("野格"); System.out.println(c.containsAll(c1)); } }
很抱歉在这里断掉,集合还有很多没总结,我会尽快补上第四周学习总结,或者单独开一章集合。