目录
1 多态
1.1 多态的概述
什么是多态?
- 同一个对象,在不同时刻表现出来的不同形态
- 同类型的对象,执行同一个行为,会表现出不同的行为特征
举例:
- 我们可以说猫是猫:
猫 cat = new 猫();
- 我们也可以说猫是动物:
动物 animal = new 猫();
- 这里说明了猫在不同时刻表现出来了不同的形态,这就是多态
多态的前提
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
🙋举个栗子:
public class Test1Polymorphic {
public static void main(String[] args) {
// 当前事物, 是一只猫
Cat c = new Cat();
// 当前事物, 是一只动物
Animal a = new Cat();
a.eat();
}
}
class Animal {
public void eat(){
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
1.2 多态中成员访问特点
- 构造方法:同继承一样,子类会通过
super
访问父类的构造方法 - 成员变量:编译看左边(父类),执行看左边(父类)
- 如果父类当中没有写这个成员变量,编译会报错
- 成员方法:编译看左边(父类),执行看右边(子类)
- 如果父类当中没有写这个成员方法,编译会报错,有的话,可以保证编译通过,然后运行子类的方法逻辑
为什么成员方法和成员变量的访问不一样?
- 因为成员方法是有重写(子类重写父类的方法),而成员变量没有
public class Test2Polymorpic {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num); // 10
f.method(); // Zi.. method
}
}
class Fu {
int num = 10;
public void method(){
System.out.println("Fu.. method");
}
}
class Zi extends Fu {
int num = 20;
@Override
public void method(){
System.out.println("Zi.. method");
}
}
1.3 多态的利与弊
好处
- 在多态形式下,右边对象可以实现解耦合,便于程序的维护和扩展
- 定义方法时候,使用父类类型作为参数,该方法就可以接收这个父类的任意子类对象,在使用的时候,使用具体的子类型参与操作
弊端
- 不能使用子类的特有功能
🙋举个栗子:
public class Test3Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog()); // 狗吃肉
useAnimal(new Cat()); // 猫吃鱼
}
// 这个方法就可以接收Animal的一切子类对象
public static void useAnimal(Animal a){ // Animal a = new Dog();
// Animal a = new Cat();
a.eat();
//a.watchHome(); 编译会报错
// Dog dog = (Dog) a;
// dog.watchHome(); // ClassCastException 类型转换异常
// 调用useAnimal(new Cat());时,执行Animal a = new Cat();将原本是猫的a转成狗了
// 若执行上述注释的代码,运行结果为:狗吃肉\n看家\n猫吃鱼\n报ClassCastException 类型异常
}
}
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void watchHome(){ //这是子类Dog类特有的方法
System.out.println("看家");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
1.4 多态的转型
多态中的转型有两种:
- 向上转型,也叫自动类型转换
- 从子到父
- 父类引用指向子类对象
- 向下转型,也叫强制类型转换
- 从父到子
- 父类引用转为子类对象
- 格式:
子类 对象变量=(子类)父类类型变量;
- 作用:可以解决多态下的劣势,实现调用子类独有的功能
🙋举个栗子:
public class Test4Polymorpic {
public static void main(String[] args) {
// 1. 向上转型 : 父类引用指向子类对象
Fu f = new Zi();
f.show();
// 多态的弊端: 不能调用子类特有的成员
// f.method(); 编译看左边(父类)没有这个方法会报错
// 多态的方式,一般也只会调用共有的方法,如果一定要调用子类中特有的成员,有以下两种方法:
// A: 直接创建子类对象
// B: 向下转型
// 2. 向下转型 : 从父类类型, 转换回子类类型
Zi z = (Zi) f; // 相当于强制类型转换
z.method();
}
}
class Fu {
public void show(){
System.out.println("Fu..show...");
}
}
class Zi extends Fu {
@Override
public void show() {
System.out.println("Zi..show...");
}
public void method(){
System.out.println("我是子类特有的方法, method");
}
}
❗️注意:
- 如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现
ClassCastException
- 解决办法:关键字
instanceof
(建议强制类型转换之前,先判断变量指向对象的真实类型)
- 使用格式:
变量名 instanceof 类型
- 判断关键字左边的变量是否是右边的类型,返回
boolean
类型结果- 另外,有继承或实现关系的2个类型就可以进行强制类型转换,编译无问题,运行时,如果发现强制类型转换后的类型不是对象的真实类型则报错
接着1.3
中的例子,可以在 useAnimal
方法中加下以下代码进行判断
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
2 内部类
2.1 内部类概述
内部类:就是在一个类中定义一个类。举例:在A类的内部定义一个B类,B类就被称为内部类
格式:
修饰符 class 外部类名{
修饰符 class 内部类名{
}
}
内部类的使用场景与作用:
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
- 内部类通常可以方便的访问外部类的成员,包括私有成员
- 内部类提供了更好的封装性,内部类本身就可以用
private
、protected
等修饰,封装性可以做更多控制
按照内部类在类中定义的位置不同,可以分为如下两种形式
- 成员内部类
- 在类的成员位置
- 局部内部类
- 在类的局部位置
2.2 成员内部类
2.2.1 一般成员内部类
也叫实例内部类,没有static
修饰,属于外部类的对象所有。
在JDK16之前,成员内部类中不能定义静态成员,JDK16开始可以定义静态成员了。
内部类的访问特点:
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
创建内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
示例代码
public class Test1Inner {
public static void main(String[] args) {
// 外部类要访问内部类的成员,必须创建对象
Outer.Inner i = new Outer().new Inner();
System.out.println(i.num);
i.show();
}
}
class Outer {
private int a = 10;
private void aa(){
System.out.println("我是我外部类中的实例方法");
}
class Inner {
int num = 10;
public void show(){
System.out.println("Inner..show");
// 内部类, 访问外部类成员, 可以直接访问, 包括私有
System.out.println(a);
aa();
}
}
}
成员内部类,也属于成员,既然属于成员就可以被一些修饰符所修饰
private
私有成员内部类static
静态成员内部类
❗️注意:
- 成员内部类中可以直接访问外部类的静态成员(有
static
修饰)
- 外部类的静态成员只有一份,可以被共享
- 成员内部类中可以直接访问外部类的实例成员(没有
static
修饰)
- 因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类的实例成员
✨ 面试题拓展:观察如下代码,写出合适的代码对应其注释要求输出的结果
public 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
}
}
}
❗️注意:
- 在成员内部类中访问所在外部类的对象,格式:
外部类名.this
2.2.2 私有成员内部类
public class Test2Innerclass {
public static void main(String[] args) {
// Outer.Inner oi = new Outer().new Inner(); 内部类为私有,会报错
Outer o = new Outer();
o.method();
}
}
class Outer {
private class Inner { //内部类私有了
public void show(){
System.out.println("inner..show");
}
}
public void method(){
Inner i = new Inner();
i.show();
}
}
2.2.3 静态成员内部类
静态内部类:有static
修饰,属于外部类本身,它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已
要注意对于静态成员内部类,其创建对象的格式发生了变化:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
示例代码
public class Test3Innerclass {
public static void main(String[] args) {
Outer.Inner oi = new Outer.Inner();
oi.show();
Outer.Inner.method(); // 静态定义的内容可以直接通过类名调用
}
}
class Outer {
public static int a=100;
public String name;
static class Inner {
public void show(){
System.out.println("inner..show");
}
// 在静态成员内部类中再定义一个静态方法,
public static void method(){
System.out.println("inner..method");
// 静态内部类中可以直接访问外部类的静态成员
System.out.println(a);
// 外部类的实例成员必须用外部类对象访问
Outer o=new Outer();
System.out.println(o.name);
}
}
}
❗️注意:
- 静态内部类中可以直接访问外部类的静态成员(有
static
修饰)
- 外部类的静态成员只有一份,可以被共享
- 静态内部类中不可以直接访问外部类的实例成员(没有
static
修饰)
- 外部类的实例成员必须用外部类对象访问
2.3 局部内部类
局部内部类
- 概念
- 是定义在方法中的类
- 使用方法
- 所以外界是无法使用的,只能在方法内部创建对象并使用
- 访问特点
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
public class Test4Innerclass {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
class Outer {
int a = 10;
public void method(){
int b = 20;
class Inner {
public void show(){
System.out.println("show...");
System.out.println(a);//10
System.out.println(b);//20
}
}
Inner i = new Inner();
i.show();
}
}
2.4 匿名内部类(重点)
概念:匿名内部类本质上是一个特殊的局部内部类(一般定义在方法,代码块中)【匿名内部类的使用更加频繁,更为重要】
作用:方便创建子类对象,最终目的是为了简化代码的编写
前提:存在一个接口或者类
特点:
- 匿名内部类是一个没有名字的内部类
- 匿名内部类写出来就会产生一个匿名内部类对象
- 匿名内部类的对象类型相当于是当前 new 的那个类型的子类类型
格式:
new 类名或接口名(){
重写方法;
}
示例代码
public class Test5Innerclass {
/*
1. 创建实现类, 通过implements关键字去实现接口
2. 重写方法
3. 创建实现类对象
4. 调用重写后的方法
*/
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.show(); // InterImpl 重写的show方法
// 匿名内部类的理解: 将继承\实现, 方法重写, 创建对象, 放在了一步进行.
// 解释: 实现了Inter接口的, 一个实现类【对象】
new Inter() {
@Override
public void show() {
System.out.println("我是匿名内部类中的show方法");
}
}.show();
// 情况: 接口中存在多个方法,如何调用多个方法?
// "new Inter2(){……}"是接口的一个实现类对象,可以在前面用父接口的引用承接一下,就可以以多态的形式
Inter2 i = new Inter2() {
@Override
public void show1() {
System.out.println("show1...");
}
@Override
public void show2() {
System.out.println("show2...");
}
};
i.show1();
i.show2();
}
}
interface Inter {
void show();
}
interface Inter2 {
void show1();
void show2();
}
class InterImpl implements Inter {
@Override
public void show() {
System.out.println("InterImpl 重写的show方法");
}
}
匿名内部类的使用场景
- 当发现某个方法的形式参数是接口或抽象类时,我们就可以将匿名内部类作为实际参数传过去,来简化传统的代码
public class TestSwimming {
public static void main(String[] args) {
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
});
}
/**
* 使用接口的方法
*/
public static void goSwimming(Swimming swimming){
swimming.swim();
}
}
/*
游泳接口
*/
interface Swimming {
void swim();
}
3 lambda表达式
3.1 函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”。
面向对象思想
- 强调“必须通过对象的形式来做事情”
函数式思想
- 则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而Lambda表达式就是函数式思想的体现
3.2 lambda表达式概述
lambda
表达式是 JDK8 开始的一种新语法格式,其主要作用就是 简化匿名内部类 的代码
格式: (形式参数) -> {代码块}
(匿名内部类被重写方法的形式参数) —>{
被重写方法的方法体;
}
形式参数
:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可->
:由英文中画线和大于符号组成,固定写法,没有实际含义,仅代表指向动作代码块
:是我们具体要做的事情,也就是以前我们写的方法体内容
lambda 表达式的使用前提:
- 有一个 接口
- 接口中 有且仅有 一个 抽象方法
3.3 lambda表达式的使用
3.3.1 无参数无返回值
需求:
- 编写一个接口
ShowHandler
- 在该接口中存在一个抽象方法
show
,该方法是无参数无返回值- 在测试类
ShowHandlerDemo
中存在一个方法useShowHandler
- 方法的参数是
ShowHandler
类型的- 在方法内部调用了
ShowHandler
的show
方法
public class TestLambda {
public static void main(String[] args) {
useShowHandler(new ShowHandler() {
@Override
public void show() {
System.out.println("我是匿名内部类中的show方法");
}
});
// Lambda实现
useShowHandler( () -> System.out.println("我是Lambda中的show方法"));
}
public static void useShowHandler(ShowHandler showHandler){
showHandler.show();
}
}
interface ShowHandler {
void show();
}
3.3.2 带参数无返回值
需求:
- 首先存在一个接口
StringHandler
- 在该接口中存在一个抽象方法
printMessage
,该方法是有参数无返回值- 在测试类
StringHandlerDemo
中存在一个方法useStringHandler
- 方法的的参数是
StringHandler
类型的- 在方法内部调用了
StringHandler
的printMessage
方法
public class StringHandlerDemo {
public static void main(String[] args) {
useStringHandler(new StringHandler() {
@Override
public void printMessage(String msg) {
System.out.println("我是匿名内部类" + msg);
}
});
// Lambda实现
useStringHandler( (msg) -> {System.out.println("我是Lambda表达式" + msg);});
}
public static void useStringHandler(StringHandler stringHandler){
stringHandler.printMessage("hello world");
}
}
interface StringHandler {
void printMessage(String msg);
}
3.3.3 无参数有返回值
需求:
- 首先存在一个接口
RandomNumHandler
- 在该接口中存在一个抽象方法
getNumber
,该方法是无参数但是有返回值- 在测试类
RandomNumHandlerDemo
中存在一个方法useRandomNumHandler
- 方法的的参数是
RandomNumHandler
类型的- 在方法内部调用了
RandomNumHandler
的getNumber
方法
import java.util.Random;
public class RandomNumHandlerDemo {
public static void main(String[] args) {
useRandomNumHandler(new RandomNumHandler() {
@Override
public int getNumber() {
Random r = new Random();
int num = r.nextInt(10) + 1;
return num;
}
});
useRandomNumHandler( () -> {
Random r = new Random();
int num = r.nextInt(10) + 1;
return num;
// 注意: 如果lambda所操作的接口中的方法, 有返回值, 一定要通过return语句, 将结果返回
// 否则会出现编译错误
} );
}
public static void useRandomNumHandler(RandomNumHandler randomNumHandler){
int result = randomNumHandler.getNumber();
System.out.println(result);
}
}
interface RandomNumHandler {
int getNumber();
}
3.3.4 带参数带返回值
需求:
- 首先存在一个接口
Calculator
- 在该接口中存在一个抽象方法
calc
,该方法是有参数也有返回值- 在测试类
CalculatorDemo
中存在一个方法useCalculator
- 方法的的参数是
Calculator
类型的- 在方法内部调用了
Calculator
的calc
方法
public class CalculatorDemo {
public static void main(String[] args) {
useCalculator(new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
});
useCalculator( ( a, b) ->
a + b
);
}
public static void useCalculator(Calculator calculator){
int result = calculator.calc(10,20);
System.out.println(result);
}
}
interface Calculator {
int calc(int a, int b);
}
lambda表达式的省略规则:
- 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略,没有参数时不能省略
- 如果代码块的语句只有一条,可以省略大括号,同时要省略分号
- 如果代码块的语句只有一条,而且是
return
语句,可以省略大括号,同时必须要省略return
和分号
3.4 与匿名内部类的区别
- 所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda
表达式:只能是接口
- 使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用
Lambda
表达式,也可以使用匿名内部类 - 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用
Lambda
表达式
- 如果接口中有且仅有一个抽象方法,可以使用
- 实现原理不同
- 匿名内部类:编译之后,产生一个单独的
.class
字节码文件保存在硬盘当中 Lambda
表达式:编译之后,没有一个单独的.class
字节码文件。对应的字节码会在运行的时候动态生成,不会保留到本地的硬盘当中
- 匿名内部类:编译之后,产生一个单独的