1.多态
1.1 么是多态?
多态是在继承或者实现情况下的一种现象,表现为:对象多态、行为多态。
1.2 多态的具体代码体现
1.3 多态的前提
- 有继承/实现关系
- 有方法的重写
- 存在父类引用指向子类对象
1.4 多态的注意事项
多态是对象、行为的多态,Java中的属性(成员变量)不谈多态。
/*
多态
是在继承/实现情况下的一种现象, 表现为对象多态和行为多态
对象多态:子类对象可以多态创建一个Person类,它的对象可以是他子类中的任意一个Student(),Teacher()
ps:父类 对象名 = new 对象类;new什么对象名就是什么
行为多态:指的是子类重写了父类的方法,执行类方法时,执行的时重写的类方法
ps:父类中没有定义的方法,在子类存在,但是多态不能使用子类的自有的方法
多态的前提
1. 有继承/实现关系
2. 有方法的重写
3. 存在父类引用指向子类对象
多态的注意事项
多态是对象、行为的多态; 变量则不涉及多态
代码执行:
编译看左,运行看右
*/
public class Demo {
public static void main(String[] args) {
//使用多态的形式创建Student对象
People p1 = new Student();
p1.run();//后空翻跑步
// p1.study();虽然子类定义了study方法,但是在多态形式下,不能调用子类独功能;
p1.drink();//喝口水:只是继承来的方法,行为多态存在父类引用指向子类对象
//使用多态的形式创建Teacher对象
People p2 = new Teacher();
p2.run();//颤颤巍巍跑步
}
}
//定义父类People, 内含跑步run方法
class People {
public void run() {
System.out.println("正常人跑步");
}
public void drink() {
System.out.println("喝口水");
}
}
//定义子类Student, 内含跑步run方法
class Student extends People {
public void study() {
System.out.println("我正在学习");
}
@Override
public void run() {
System.out.println("后空翻跑步");;
}
}
//定义父类Teacher, 内含跑步run方法
class Teacher extends People {
@Override
public void run() {
System.out.println("颤颤巍巍跑步");
}
}
1.5 多态的好处与弊端
- 在多态形式下,等号左右两边松耦合,更便于修改和维护
- 定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利
/*
多态的好处
1. 在多态形式下,等号左右两边松耦合,更便于修改和维护
2. 在多态下, 定义方法时, 可以使用父类类型作为形参, 那么该方法可以接收该父类下所有子类的对象
*/
public class Demo {
public static void main(String[] args) {
//好处1: 在多态形式下,等号左右两边松耦合,更便于修改和维护
//创建一个老师对象,调用5次跑步的方法=======有一天需求改了========>创建一个学生对象,调用5次跑步的方法
Teacher teacher = new Teacher();
teacher.run();
teacher.run();
teacher.run();
teacher.run();
teacher.run();
//又得改成Student()
Student student = new Student();
student.run();
student.run();
student.run();
student.run();
student.run();
//用多态的话就避免了这种情况
Person person = new Student();//需要什么对象改成什么对象
person.run();
person.run();
person.run();
person.run();
person.run();
//好处2: 在多态下, 定义方法时, 可以使用父类类型作为形参, 那么该方法可以接收该父类下所有子类的对象
Person p1 = new Student();
Person p2 = new Teacher();
login(p1);//登陆成功,请开始学习
login(p2);//登陆成功,请开始备课
}
//好处2: 在多态下, 定义方法时, 可以使用父类类型作为形参, 那么该方法可以接收该父类下所有子类的对象
//创建一个run方法,接收一个老师对象,然后调用对象的run方法=======有一天需求改了========>接收一个学生对象,然后调用对象的run方法
public static void run(Teacher teacher){
teacher.run();
}
public static void run(Student student) { student.run(); }//这样写也很麻烦,需要new一个新对象,然后再用对象调用
public static void login(Person person) {
//验证用户账号和密码(省略)
//跳转到不同的页面
person.jump();
}
}
class Person{
public void run(){}
public void jump() {
System.out.println("登陆成功");
}
}
class Student extends Person{
@Override
public void run() {
System.out.println("学生跑的快");
}
public void jump() {
System.out.println("登陆成功,请开始学习");
}
}
class Teacher extends Person{
@Override
public void run() {
System.out.println("老师跑的慢");
}
public void jump() {
System.out.println("登陆成功,请开始备课");
}
}
/*
多态的弊端
不能直接使用子类特有的功能
解决方案: 强制类型转换
多态中的转型
子-->父 (小到大 自动转换): 也称为向上转型, 父类引用指向子类对象 Person p = new Student();
父-->子 (大到小 强制转换): 也称为向下转型, 父类引用转为子类对象 Student s = (Student)p;
强转风险
强转是存在风险的, 如果转为父类引用记录的真实子类对象,那么不会报错(否则会报ClassCastException)
如果想规避这个风险,可以在强转前,使用instanceof关键字, 判断变量对应的类型
*/
public class Demo {
public static void main(String[] args) {
//传入一个老师对象
run(new Teacher());
}
public static void run(Person person){
//调用老师的run()方法
person.run();
//为了防止坏B使坏,我们可以先判断一下传来的类是不是Teacher
if (person instanceof Teacher){
//需求: 想再调用一下老师的teach()方法
Teacher teacher = (Teacher) person;
teacher.teach();
} else {
System.out.println("哈哈,被我识破了吧");
}
}
}
class Person{
public void run(){}
}
class Student extends Person {
@Override
public void run() {
System.out.println("学生跑的快");
}
public void study(){
System.out.println("学生在学习~~~~");
}
}
class Teacher extends Person {
@Override
public void run() {
System.out.println("老师跑的慢");
}
public void teach(){
System.out.println("老师在上课~~~~");
}
}
1.6 总结
1.7 final 关键字
1、final修饰的类、方法、变量各有什么特点?
修饰类:该类被称为最终类, 类不能再被继承
修饰方法:该方法被称为最终方法, 方法不能被重写
修饰变量:该变量只能被赋值一次, 赋值完毕之后不能再修改
2、final修饰基本类型和引用类型的变量有啥区别?
final修饰基本类型的变量,变量存储的数据不能被改变
final修饰引用类型的变量,变量存储的地址不能被改变
但地址所指向对象中的内容是可以被改变的。
/*
final关键字是最终的意思,可以修饰(类、方法、变量)
修饰类:该类被称为最终类, 类不能再被继承
修饰方法:该方法被称为最终方法, 方法不能被重写
修饰变量:该变量只能被赋值一次, 赋值完毕之后不能再修改
成员变量: 声明时赋完值,或者在构造方法结束之前完成赋值
局部变量: 变量只能被赋值一次
final修饰变量的注意事项
基本类型变量: 变量记录的数据不能再被改变
引用类型变量: 变量记录的地址不能再被改变, 但是地址对应的堆内存中的内容可以改变
*/
public class Demo {
public static void main(String[] args) {
final int age;
age = 20;//局部变量: 变量只能被赋值一次
// age = 18;//Variable 'age' might already have been assigned to
}
}
class Father {
public void run() {
System.out.println("爸爸在跑");
}
}
final class Son extends Father {
String name;
final String school = "12";
// school = "12";//Variable 'school' might not have been initialized 声明时赋完值,或者在构造方法结束之前完成赋值
@Override
public void run() {//修饰方法,就不能被重写
int age = 10;
System.out.println(age + "岁的儿子在跑");
}
}
//class uncle extends Son{//Cannot inherit from final 'd_final.Son'修饰类就不能被继承
//
//}
2.抽象类
2.1 什么是抽象类
- 在Java中有一个关键字叫:abstract,它就是抽象的意思,可以用它修饰类、成员方法。
- abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法。
2.2 抽象类的特点
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
- 类该有的成员(成员变量、方法、构造器)抽象类都可以有。
- 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
/*
抽象类和抽象方法
在Java中有一个关键字叫:abstract,它就是抽象的意思,可以用它修饰类、成员方法。
abstract修饰类,这个类就是抽象类
abstract修饰方法,这个方法就是抽象方法,抽象方法没有方法体
抽象类的特点
1. 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
2. 类该有的成员(成员变量、方法、构造器)抽象类都可以有。
3. 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
4. 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
*/
public class Demo {
public static void main(String[] args) {
Person person = new Student();
person.run();
// new Person();//'Person' is abstract; cannot be instantiated 抽象类是无法创建对象的
}
}
//需求1: 将Person类声明为抽象类
abstract class Person {
//成员变量
private String name;
//成员方法
public void eat(){
System.out.println("吃饭");
}
//需求2: 将run方法修改为抽象方法
public abstract void run();
}
class Student extends Person{
@Override
public void run() {
System.out.println("后空翻跑步");
}
}
class Teacher extends Person{
@Override
public void run() {
System.out.println("拉爆博尔特");
}
}
2.3 使用抽象类的场景和好处
/*
抽象类的应用场景和好处
1、将所有子类中重复的代码,抽取到抽象的父类中,提高了代码的复用性(先编写子类,再编写抽象类)
2、我们不知道系统未来具体的业务时,可以先定义抽象类,将来让子类去继承实现,提高了代码的扩展性 (先编抽象类,再编写子类)
需求
某宠物游戏,需要管理猫、狗的数据。
猫的数据有:名字;行为是:喵喵喵的叫~
狗的数据有:名字;行为是:汪汪汪的叫~
*/
public class Demo {
public static void main(String[] args) {
Animal an1 = new Cat();
Animal an2 = new Dog("雪豹");
an1.setName("小馋猫");
an1.cry();
an2.cry();
}
}
//动物
abstract class Animal {
private String name;
public Animal() {
}
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void cry();
}
//猫
class Cat extends Animal {
public Cat() {
super();
}
public Cat(String name) {
super(name);
}
@Override
public void cry() {
System.out.println(super.getName() + ",喵喵叫");
}
}
//狗
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void cry() {
System.out.println(super.getName() + ",你不要给我汪汪叫");
}
}
2.4 模板方法设计模式
一个功能的完成需要经过一系列步骤,这些步骤是固定的,但是中间某些步骤具体行为是待定的,在不同的场景中行为不同
值得注意的是:如果使用模板方法之后,建议用 final 关键字进行修饰,这样可以有效避免子类重学模板方法,致使模板方法失效。
/*
设计模式
对于某类问题,前人总结出类的解决问题的套路
模板方法设计模式
一个功能的完成需要经过一系列步骤,这些步骤是固定的,但是中间某些步骤具体行为是待定的,在不同的场景中行为不同
使用思路
1、定义一个抽象类(Person作为父类),提供模板方法
2、模板方法中,需要让子类自己实现的地方,定义为抽象方法
3、子类(Teacher Student)只需要继承该抽象类,重写抽象方法即可完成些完成的功能
多学一招:
建议使用final关健字修饰模板方法
模板方法是给对象直接使用的,不能被子类重写
一旦子类重写了模板方法,模板方法就失效了
*/
public class Demo {
public static void main(String[] args) {
Person person = new Teacher();
person.work();
}
}
abstract class Person {
public final void work() {//为了避免子类重写方法,影响代码执行,如果不加会执行Student类中的重写方法work
System.out.println("吃饭");
doWork();
System.out.println("睡觉");
}
public abstract void doWork();
}
class Student extends Person {
// @Override
// public void work() {
// System.out.println("捣蛋鬼别捣蛋");
// }
@Override
public void doWork() {
System.out.println("学习");
}
}
class Teacher extends Person {
@Override
public void doWork() {
System.out.println("教课");
}
}
3.接口
3.1 认识接口
/*
接口
Java提供了一个关键字interface,用这个关键字我们可以定义出一个特殊的结构:接口
定义格式
public interface 接口名 {
成员变量(接口中的成员变量都是常量, 默认是被public static final修饰的)
成员方法(接口中的成员方法都是抽象方法, 默认是被public abstract修饰的)
注意: 接口中不能有构造方法和代码块
}
注意事项
1. 接口不能直接创建对象
2. 接口是用来被类实现(implements)的,实现接口的类称为实现类。
3. 一个类可以实现多个接口,实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。
修饰符 class 实现类 implements 接口1, 接口2, 接口3 , ... {}
*/
public class Demo {
public static void main(String[] args) {
//面向接口编程,创建对象,调用方法 接口 变量 = new 实现类()
UserInterface ui = new UserInterfaceImpl();
ui.save();
}
}
//接口
public interface UserInterface {
//定义成员变量,默认会有 public static final
String name = "顶真";
//定义成员方法,默认会有 public abstract
void save();//保存用户顶真
}
//实现类
public class UserInterfaceImpl implements UserInterface{
@Override
public void save() {
System.out.println("保存用户" + UserInterface.name );
}
}
接口其实和多态差不多,只是多态是类的多态,接口则是创建一个接口,在JDK 8 之前接口中只能定义一些成员变量和成员方法(抽象的,需要被重写),但在JDK 8 之后接口新增了三个特性,使得接口内可以定义其他内容。
3.2 接口的好处
接口的好处:一句话带过就是解耦合(让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现)。可以看看代码,帮助与理解什么是解耦合。
/*
接口特点、好处
让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。(解耦合)
*/
//使用这个类 模拟一个调用这角度的类
public class Demo {
public static void main(String[] args) {
UserService ui = new UserServiceImpl1();
//这种还不是最大程度的解耦合只是帮助理解,可以根据登录的方式不同直接改上边使用的实现类
ui.register();
ui.login();
}
}
//接口
public interface UserService {
void register();
void login();
}
//这是一个用户操作类,可以完成用户的注册、登录功能
class UserServiceImpl1 implements UserService{
// 注册
@Override
public void register() {
System.out.println("手机号/验证码登录");
}
// 登录
public void login() {
System.out.println("登陆成功");
}
}
public class UserSeviceImpl2 implements UserService{
@Override
public void register() {
System.out.println("账号/密码登陆");
}
@Override
public void login() {
System.out.println("登录成功");
}
}
3.3 接口的其他细节
方法签名冲突是指:在一个类/接口继承的两个类/接口中,两个类/接口有相同的方法(方法名、形参、返回值都一样),这样会导致方法冲突。