一.多态
1.认识多态
多态是在继承/实现情况下的一种现象,表现为对象多态和行为多态
- 多态的前题
- 有继承/实现关系
- 有方法的重写
- 存在父类引用指向子类对象
- 多态的注意事项:多态是对象,行为的多态;变量则不涉及多态
- 代码执行:编译看左,运行看右
/** * 父类 */ public class People { //省略其他公共的属性和方法 //公共的方法跑步 public void run() { System.out.println("正常跑步"); } } //学生子类,继承people public class Student extends People{ @Override public void run() { System.out.println("学生跑步,蹦蹦跳跳"); } //独有的方法 public void study () { System.out.println("努力学习"); } } //讲师子类,继承people public class Teacher extends People { @Override public void run() { System.out.println("讲师跑步,颤颤巍巍"); } } //测试 public class Demo { public static void main(String[] args) { //之前的写法 //Student student = new Student(); //使用多态的形式创建Student对象 People p1 = new Student(); p1.run(); System.out.println(p1.toString()); //使用多态的形式创建Teacher对象 People p2 = new Teacher(); p2.run(); } }
2.多态的好处
- 在多态形势下,等号左右两边松耦合,更便于修改和维护
- 在多态下,定义方法时,可以使用父类类型作为形参,那么该方法可以接收该父类下所有子类的对象
public class Demo {
public static void main(String[] args) {
//1. 在多态形式下,等号左右两边松耦合,更便于修改和维护
Person person = new Teacher();
person.run();
person.run();
person.run();
person.run();
//2. 在多态下, 定义方法时, 可以使用父类类型作为形参, 那么该方法可以接收该父类下所有子类的对象
Person person1 = new Teacher();
Person person2 = new Student();
login(person1);
login(person2);
}
/**
* 登录方法,
* 验证用户名字和密码(省略)
* 跳转不同的主页
*/
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("学生跑的快");
}
@Override
public void jump() {
System.out.println("学生登录成功-好好学习");
}
}
class Teacher extends Person{
@Override
public void run() {
System.out.println("老师跑的慢");
}
@Override
public void jump() {
System.out.println("老师登录成功,努力备课");
}
}
3.多态的弊端
不能直接使用子类特有的功能(解决方案:强制类型转换)
- 多态中的转型
子-->父(小到大 自动转换):也称为向上转型,父类引用指向子类对象 Person p = new Student();
父-->子(大到小 强制转换):也称为向下转型,父类引用转为子类对象 Student s = (Student)p;
- 强转风险
强转是存在风险的,如果转为父类引用记录的真实子类对象,那么不会报错(否则会报ClassCastException)
如果想规避这个风险,可以在强转前,使用instanceof关键字,判断变量对应的类型
public class Demo {
// public static void main(String[] args) {
// //1、多态创建学生
// Person p1 = new Student();
// //2、调用方法
// p1.run();
// //3、调用学生对象中独有的功能
// //将 p1 转化成 子类 (父类转化子类:强转)
// Student stu = (Student) p1;
// stu.study();
// }
public static void main(String[] args) {
//1、多态创建学生
Person p1 = new Teacher();
System.out.println(p1);
//2、调用方法
p1.run();
//3、调用学生对象中独有的功能
//4、判断真实类型和强转的类型是否一致 变量 instanceof 类型
if (p1 instanceof Student) {
Student stu = (Student) p1; // ClassCastException:真实类型与强转类型不一致
stu.study();
} 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("老师在上课~~~~");
}
}
二.final
final关键字是最终的意思,可以修饰(类,方法,变量)
- 修饰类:该类被称为最终类,类不能再被继承
- 修饰方法:该方法被称为最终方法,方法不能再被重写
- 修饰变量:该变量只能被赋值一次,赋值完毕之后不能再被修改
成员变量:声明时赋完值,或者在构造方法结束之前完成赋值
局部变量:变量只能被赋值一次
- final修饰变量的注意事项
基本类型变量:变量记录的数据不能再被改变
引用数据变量:变量记录的地址不能再被改变,但是地址对应的堆内存中内容可以改变
public class Demo {
public static void main(String[] args) {
}
}
class Father {
public void run() {
System.out.println("爸爸在跑");
}
}
class Son extends Father {
String name = "123";
String school;
@Override
public void run() {
final int age = 10;
final int [] array = {1,2,3};
array[2] = 5;
int [] arr2 = {2,3,4};
//array = arr2;
System.out.println(age + "岁的儿子在跑");
}
}
三.抽象类
1.认识抽象类
在Java中有一个关键字叫:abstract,它就是抽象的意思,可以用它修饰类,成员方法
abstract修饰类,这个类就是抽象类
abstract修饰方法,这个方法就是抽象方法,抽象方法没有方法题体
- 抽象类的特点
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 类该有的成员(成员变量,方法,构造器)抽象类都可以有
- 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义为抽象类
public class Demo { public static void main(String[] args) { Person p1 = new Student(); p1.run(); } } //需求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.抽象类的使用场景
- 将所有子类中重复的代码,抽取到抽象的父类中,提高了代码的复用性(先编写子类,在编写抽象类)
- 我们不知道系统未来具体的业务时,可以先定义抽象类,将来让子类去继承实现,提高了代码的扩展性(先编抽象类,在编写子类)
/*
需求
某宠物游戏,需要管理猫、狗的数据。
猫的数据有:名字;行为是:喵喵喵的叫~
狗的数据有:名字;行为是:汪汪汪的叫~
*/
public class Demo {
public static void main(String[] args) {
//创建猫的对象
Animal an = new Cat();
an.setName("加菲猫");
an.cry();
}
}
//动物的父类,公共的属性和方法
abstract class Animal {
private String name;
public abstract void cry();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//定义猫的java类
class Cat extends Animal {
@Override
public void cry() {
System.out.println(super.getName() + "喵喵喵的叫~");
}
}
//定义枸的java类
class Dog extends Animal {
@Override
public void cry() {
System.out.println(super.getName() + "汪汪汪的叫~");
}
3.模板方法设计模式
一个功能的完成需要经过一系列步骤,这些步骤是固定的,但是中间某些步骤具体行为是待定的,在不同场景下行为不同
- 使用思路
- 定义一个抽象类,提供模板方法
- 模板方法中,需要让子类自己实现的地方,定义为抽象方法
- 子类只需要继承该抽象类,重写抽象方法即可完成
- 建议使用final关键字修饰模板方法,模板方法是给对象直接使用的,不能被子类重写,一旦子类重写了模板方法,模板方法就失效了
/**
* 抽象类模版
*/
public abstract class Person {
/**
* 1、定义模版(必须具有执行的步骤和顺序)
*/
public final void work() {
System.out.println("1、吃饭");
//具体的工作
doWork();
System.out.println("3、睡觉");
}
/**
* 2、定义具体的业务功能的抽象方法
*/
public abstract void doWork();
}
/**
* 子类
*/
public class Teacher extends Person{
@Override
public void doWork() {
System.out.println("讲课");
}
}
public class Student extends Person{
@Override
public void doWork() {
System.out.println("学习");
}
}
public class Demo {
public static void main(String[] args) {
Person p1 = new Teacher();
p1.work();
}
}
四.接口
1.接口概述
Java提供了一个关键字interface,用这个关键字可以定义一个特殊的结构:接口
- 定义格式
public interface 接口名{
成员变量(接口中的成员变量都是常量,默认是被public static final修饰的)
成员方法(接口中的成员方法都是抽象方法,默认是被public abstract修饰的)
注意:接口中不能有构造方法和代码块
}
- 注意事项
- 接口不能直接创建对象
- 接口是用来被类实现(implements)的,实现接口的类称为实现类
- 一个类可以实现多个接口,实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。修饰符class 实现implements 接口1,接口2,接口3,......{}
/**
* 定义接口
*/
public interface UserInterface {
//定义成员变量, public static final
String name = "zhangsan";
//定义成员方法, public abstract
void save(); //抽象方法,需要被实现
}
public interface HeadInterface {
void eat();
}
/**
* 1、定义一个实现类,实现UserInterface接口
* 2、实现接口中的抽象方法
*/
public class UserInterfaceImpl implements UserInterface {
@Override
public void save() {
System.out.println("保存");
}
}
public class Demo {
public static void main(String[] args) {
// 面相接口编程,创建对象,调用方法
// 接口 变量 = new 实现类() //是多态的第二种情况
UserInterface ui = new UserInterfaceImpl();
ui.save();
}
}
2.接口的好处
让程序可以面向接口编程,这样就可以灵活方便的切换各种业务实现(解耦合)
/**
* 接口的作用:解耦(松散耦合)
* 1、接口是用来定义规范
* 2、定义好,登录或者注册等功能的方法名,参数,返回值类型
*/
public interface UserService {
void login();
void register();
}
//这是一个用户操作类,可以完成用户的注册、登录功能
public class UserServiceImpl implements UserService {
// 注册
public void register() {
//省略手机号码登验证的步骤
System.out.println("我是通过手机号码注册的");
}
// 登录
public void login() {
//省略发短信和验证短信验证码的步骤
System.out.println("通过手机验证码登录");
}
}
//这是一个用户操作类,可以完成用户的注册、登录功能
public class UserServiceImpl2 implements UserService {
@Override
public void login() {
System.out.println("通过账号/密码登录");
}
@Override
public void register() {
System.out.println("我是通过账号/密码注册的");
}
}
public class UserServiceImpl3 implements UserService{
@Override
public void login() {
System.out.println("微信登录");
}
@Override
public void register() {
System.out.println("微信注册");
}
}
//使用这个类 模拟一个调用这角度的类
public class Demo {
public static void main(String[] args) {
//调用别人写好的代码完成登录和注册
//UserServiceImpl usi = new UserServiceImpl();
//usi.register();
//usi.login();
// UserServiceImpl2 usi1 = new UserServiceImpl2();
// usi1.zhuce();
// usi1.denglu();
//面相接口调用
UserService us = new UserServiceImpl3();
us.register();
us.login();
}
}
3.interface 新特性
/*
JDK8开始,接口中新增的三种方法
1、默认方法(jdk8开始支持):对接口中的方法提供默认实现
使用default修饰,有方法体,可以但是不强制要求实现类重写, 只能通过实现类的对象调用
2、静态方法(jdk8开始支持):方便调用
使用static修饰,有方法体,只能通过接口名调用
3、私有方法(jdk9开始支持):提高代码复用性
使用private修饰,服务于接口内部,用于抽取相同的功能代码
*/
public class Demo {
public static void main(String[] args) {
Animal a = new Dog();
a.eat();
a.defaultPrint();
Animal b = new Cat();
b.eat();
b.defaultPrint();
Animal.print();
}
}
interface Animal {
void eat();
//默认方法:有方法体,有默认的功能实现。实现类可以选择实现此方法也可以不实现
default void defaultPrint() {
System.out.println("defaultPrint");
privtePrint();
}
//静态方法
static void print() {
System.out.println("动物不知道在干嘛");
}
//私有方法
private void privtePrint() {
System.out.println("privtePrint");
}
}
class Dog implements Animal{
public void defaultPrint() {
System.out.println("dog实现了defaultPrint方法");
}
public void eat(){
System.out.println("动物苏醒了");
System.out.println("动物开始用膳");
}
}
class Cat implements Animal{
public void eat(){
System.out.println("动物苏醒了");
System.out.println("动物开始用膳");
}
}
4.interface注意事项
/*
类和接口的关系总结
1、类和类: 继承(extends)关系,对于类只支持单继承,不支持多继承,但是可以多层继承
2、接口和接口: 继承(extends)关系,对于接口,支持多继承
3、类和接口:实现(implements)关系,支持多实现,一个类同时实现多个接口
接口使用注感事项
1、一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。
2、一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。
3、一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
4、一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的
*/
public class Demo {
public static void main(String[] args) {
}
}
interface A {
void print();
default void defaultPrint() {
System.out.println("defaultPrint");
}
}
interface B {
void print();
default void defaultPrint() {
System.out.println("defaultPrint");
}
}
// 1、一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。
//interface C extends A ,B {
//
//}
//2、一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。
//class D implements A,B {
// @Override
// public void print() {
// System.out.println("xxxx");
// }
//}
// 3、一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
//class E implements A,B {
// @Override
// public void print() {
//
// }
//
// @Override
// public void defaultPrint() {
// A.super.defaultPrint();
// }
//}
class F {
public void show() {
System.out.println("展示");
}
}
interface G {
void show();
}
class H extends F implements G {
}