多态——面向对象三大特征之三
- 同类型的对象,执行同一个行为,会表现出不同的行为特征
1 多态的常见形式
父类(或接口)类型new子类对象
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;
2 多态中成员访问特点(重点)
public abstract class Animal {
public String name = "父类——动物";
public abstract void eat();
}
class Dog extends Animal {
public String name = "子类——狗";
@Override
public void eat() { System.out.println("狗吃骨头");}
}
- 方法调用: 编译看左边,运行看右边
Animal animal = new Dog();
animal.eat(); // 运行结果:狗吃骨头
- 变量调用: 编译看左边,运行看左边(多态侧重“行为”多态)
Animal animal = new Dog();
System.out.println(animal.name); // 运行结果:父类——动物
3 多态的前提
- 有继承/实现关系
- 有父类引用指向子类对象
- 有方法重写
4 多态的特点
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护
Animal animal = new Dog();
animal.eat();
可直接将new Dog()修改为new Cat(),后续业务行为随对象而变,后续代码无需修改
Animal animal = new Cat();
animal.eat();
- 定义方法的时候,使用父类型作为参数,该方法就可以接收者父类的一切子类对象,体现出多态的扩展性与便利
public void go(Animal a) {
a.eat();
}
Animal a1 = new Dog();
go(a1);
Animal a2 = new Cat();
go(a2);
- 多态下不能使用子类的独有功能(子类有但父类没有的功能)
5 多态下引用数据类型的类型转换
5.1 自动类型转换(从子到父)
- 子类对象赋值给父类类型的变量指向
//基础的多态定义即自动转型
Animal a = new Dog();
5.2 强制类型转换(从父到子)
- 从父到子必须进行强制类型转换:
子类 对象变量 = (子类)父类类型的变量
- 作用: 可以解决多态下的劣势,实现调用子类独有的功能(子类有但父类没有的功能)
Animal a = new Dog();
a.eat();
Dog d = (Dog) a;
d.狗类独有方法();
- 注意: 如果转型后的类型和对象真实类型不是同一种类型时,编译不会报错,运行时会出现异常ClassCastException
Animal a = new Dog();
Cat c = (Cat) a; // 编译不会报错,运行时报异常
- 此时使用 instanceof 判断当前对象的真实类型,再进行强制转换
变量名 instanceof 真实类型
内部类、常用API
1 内部类
1.1 概述
- 内部类就是定义一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)
1.2 作用和使用场景
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
- 内部类通常可以方便访问外部类的成员,包括私有成员
- 内部类提供了更好的封装性,内部类本身就可以用pirvate、protected等修饰,封装性可以做更多控制
1.3 分类
- 静态内部类 [了解]
- 成员内部类 [了解]
- 局部内部类 [了解]
- 匿名内部类(重点)
1.3.1 静态内部类(了解)
- 有static修饰,属于外部类本身
- 静态内部类的特点和使用与普通类完全一样,类有的成分它都有,只是位置在别人里面
- 创建格式:
public class Outer {
// 静态成员内部类
public static class Inner{
}
}
- 创建对象格式:
外部类名.内部类名 对象名 = new 外部类名.内部类构造器;
Outer.Inner in = new Outer.Inner();
- 静态内部类的访问拓展
○静态内部类可以直接访问外部类的静态成员,因为外部类的静态成员只有一份可以被共享访问
○静态内部类不可以直接访问外部类的实例成员,因为外部类的实例成员必须用外部类对象访问
1.3.2 成员内部类(了解)
- 无static修饰,属于外部类的对象
- JDK16之后,成员内部类中可以定义静态成员
- 创建格式:
public class Outer {
// 成员内部类
public class Inner {
}
}
- 创建对象格式:
外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器();
Outer.Inner in = new Outer().new Inner();
- 成员内部类的访问拓展:
- 成员内部类中可以直接访问外部类的静态成员
- 成员内部类的实例方法中可以直接访问外部类的实例成员,因为必须先有外部类对象,才能有成员内部类对象
- 案例:
class People{
private int heartbeat = 150;
/**
成员内部类
*/
public class Heart{
private int heartbeat = 110;
public void show(){
int heartbeat = 78;
System.out.println(heartbeat); // 78
System.out.println(this.heartbeat); // 110
System.out.println(People.this.heartbeat); // 150
}
}
}
1.3.3 局部内部类(鸡肋语法,了解)
- 局部内部类放在方法、代码块、构造器等执行体中
- 局部内部类的类文件名为:外部类$N内部类.class(N为序号1,2,3…)
1.3.4 匿名内部类(重点)
- 本质上是一个没有名字的局部内部类,定义在方法中、代码块中
- 作用:方便创建子类对象,最终目的为了简化代码编写
- 创建格式:
//通式
new 类|抽象类名|接口名() {
@Override
重写方法
};
//举例
Employee a = new Employee() {
@Override
public void work() {}
};
- 特点:
○匿名内部类是一个没有名字的内部类
○匿名内部类写出来就会产生一个匿名内部类的对象
○匿名内部类的对象类型相当于是当前new的那个类的子类类型
○匿名内部类编译后会产生class文件,即外部类$N.class(N为序号1,2,3…)
- 常见使用形式:匿名内部类可以作为方法的实参进行传输
go(new Swimming()
{
@Override
public void swim() { // TODO }
});
- 给按钮绑定点击事件
// 为按钮绑定点击事件监听器
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 点击事件
}
});
//可以优化为一行:btn.addActionListener(e -> 点击事件);
- 总结:开发中不是我们主动去定义匿名内部类,而是别人需要我们写或者我们可以写的时候才会使用,匿名内部类的代码可以实现代码进一步的简化
2 Object、StringBuilder等常用API
2.1 Object
- Object类的方法是一切子类对象都可以直接使用的
- 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类
- 常用方法:
方法名 | 说明 |
public static boolean equals(Object a, Object b) | 比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较 |
public static boolean isNull(Object obj) | 判断变量是否为null ,为null返回true ,反之 |
2.1.1 toString方法
- 父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息!!
- 用sout输出对象信息时".toString()"可以省略不写
2.1.2 equals方法
- 直接比较两个对象的地址是否相同完全可以用“==”替代equals
- 默认是与另一个对象比较地址是否一样,让子类重写,以便比较2个子类对象的内容是否相同
- 父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则
2.2 Objects
- 官方在进行字符串比较时,没有用字符串对象的的equals方法,而是选择了Objects的equals方法来比较,使用Objects的equals方法在进行对象的比较会更安全
- 常用方法
2.3 String Builder
- StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器
- 作用:支持链式编程,提高字符串的操作效率,如拼接、修改等,性能较高
//链式编程
sb1.append("a").append("b").append("c").append("我爱你中国");
sb1.reverse().append("110");
2.3.1 常用方法
方法名称 | 说明 |
public StringBuilder append(任意类型) | 添加数据并返回StringBuilder对象本身 |
public StringBuilder reverse() | 将对象的内容反转 |
public int length() | 返回对象内容长度 |
public String toString() | 通过toString()就可以实现把StringBuilder转换为String |
2.3.2 StringBuilder高性能原理图:
2.3.3 String和StringBuilder总结比较
- String :内容是不可变的、拼接字符串性能差
StringBuilder:内容是可变的、拼接字符串性能好、代码优雅
- 定义字符串使用String
拼接、修改等操作字符串使用StringBuilder
2.4 Math
方法名 | 说明 |
public static int abs(int a) | 获取参数绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(float a) | 四舍五入 |
public static int max(int a,int b) | 获取两个int值中的较大值 |
public static double pow(double a,double b) | 返回a的b次幂的值 |
public static double random() | 返回值为double的随机值,范围[0.0,1.0) |
2.5 System
方法名 | 说明 |
public static void exit(int status) | 终止当前运行的 Java 虚拟机,非零表示异常终止 |
public static long currentTimeMillis() | 返回当前系统的时间毫秒值形式 |
public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) | 数组拷贝 |
2.6 BigDecima
- 用于解决浮点型运算精度失真的问题
public static BigDecimal valueOf(double val): 包装浮点数成为BigDecimal对象。
方法名 | 说明 |
public BigDecimal add(BigDecimal b) | 加法 |
public BigDecimal subtract(BigDecimal b) | 减法 |
public BigDecimal multiply(BigDecimal b) | 乘法 |
public BigDecimal divide(BigDecimal b) | 除法 |
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) | 除法 |
日期类、包装类、正则表达式、Arrays类、Lambda表达式
1 日期类
1.1 Date
- 创建日期类,获取时间毫秒值
- public Date();
- public long getTime();
- 时间毫秒值恢复成日期对象
- public Date(long time);
- public void setTime(long time);
1.2 SimpleDateFormat
- 完成Date日期对象,时间毫秒值的格式化操作
- public final String format(Date date)
- public final String format(Object time)
- 进行字符串时间的解析
- public Date parse(String source)
1.3 Calendar日历
- Calendar代表了系统此刻日期对应的日历对象
- Calendar是一个抽象类,不能直接创建对象,需调用方法创建对象:
public static Calendar getInstance()
- 常用方法
方法名 | 说明 |
public int get(int field) | 取日期中的某个字段信息。 |
public void set(int field,int value) | 修改日历的某个字段信息。 |
public void add(int field,int amount) | 为某个字段增加/减少指定的值 |
public final Date getTime() | 拿到此刻日期对象。 |
public long getTimeInMillis() | 拿到此刻时间毫秒值 |
- calendar是可变日期对象,一旦修改后其对象本身表示的时间将产生变化
1.4 JDK8新增的日期类
2 包装类
2.1 包装类的概念和作用
- 包装类是8种基本数据类型对应的引用类型,使用引用类型将基本数据类型对象化(一切皆对象),同时集合和泛型其实也只能支持包装类型,不支持基本数据类型。
基本数据类型 | 引用数据类型 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
2.2 自动装箱和自动拆箱
- 自动装箱:基本类型的数据和变量可以直接赋值给包装类型的变量
- 自动拆箱:包装类型的变量可以直接赋值给基本数据类型的变量
2.3 包装类特点
- 包装类的变量的默认值可以是null,容错率更高
- 可以把字符串类型的数值转换成真实的数据类型(很有用):
Integer.parseInt(“字符串类型的整数”)
Double.parseDouble(“字符串类型的小数”)
3 正则表达式
- 用一些规定的字符来制定规则,并用来校验数据格式的合法性
3.1 正则表达式匹配规则
- 在API文档Pattern类中查看
- 字符类(默认匹配一个字符)
[abc] 只能是a, b, 或c
[^abc] 除了a, b, c之外的任何字符
[a-zA-Z] a到z A到Z,包括(范围)
[a-d[m-p]] a到d,或m通过p:([a-dm-p]联合)
[a-z&&[def]] d, e, 或f(交集)
[a-z&&[^bc]] a到z,除了b和c:([ad-z]减法)
[a-z&&[^m-p]] a到z,除了m到p:([a-lq-z]减法)
- 预定义的字符类(默认匹配一个字符)
. 任何字符
\d 一个数字: [0-9]
\D 非数字: [^0-9]
\s 一个空白字符: [ \t\n\x0B\f\r]
\S 非空白字符: [^\s]
\w [a-zA-Z_0-9] 英文、数字、下划线
\W [^\w] 一个非单词字符
- 贪婪的量词(配合匹配多个字符)
X? X , 一次或根本不
X* X,零次或多次
X+ X , 一次或多次
X {n} X,正好n次
X {n, } X,至少n次
X {n,m} X,至少n但不超过m次
3.2 提供的匹配方法
public boolean matches (String regex): 判断是否匹配正则表达式,匹配返回true,不匹配返回false。
3.3 正则表达式的应用
- 表单验证
- 在字符串方法中的使用(了解):
- public String replaceAll(String regex,String newStr) 按照正则表达式匹配的内容进行替换
- public String[] split(String regex) 按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组
- 爬取信息
4 Arrays
- 数组操作工具类,专门用于操作数组元素
- 常用方法:
方法名 | 说明 |
public static String toString(类型[] a) | 返回数组的内容(字符串形式) |
public static void sort(类型[] a) | 对数组进行默认升序排序 |
public static void sort(类型[] a, Comparator<? super T> c) | 使用比较器对象自定义排序 |
public static int binarySearch(int[] a, int key) | 二分搜索数组中的数据,存在返回索引,不存在返回-1 |
- Arrays支持设置Comparator接口对应的比较器对象,来定制自定义比较规则(重要)
5 Lambda表达式(重要)
5.1 Lambda表达式的作用和格式
- JDK 8开始后的一种新语法形式,用于简化匿名内部类的代码写法
- 简化格式:
(匿名内部类被重写方法的形参列表) -> {
被重写方法的方法体代码。
}
注:-> 是语法形式,无实际含义
-
- Lambda表达式只能简化函数式接口的匿名内部类的写法形式
- 函数式接口:首先必须是接口、其次接口中有且仅有一个抽象方法的形式
@FunctionalInterface // 一旦加上这个注解必须是函数式接口,里面只能有一个抽象方法
5.2 Lambda表达式的省略规则
- 参数类型可以省略不写。
- 如果只有一个参数,参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
Arrays.sort(ages1, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1; // 降序
}
});//简化前
Arrays.sort(ages1, ( o1, o2 ) -> o2 - o1 );//简化后