进阶-day02
一、多态
1.1多态的体现
1.对象的编译类型和运行类型可以不一致
Animal animal = new Dog();
/* animal 是对象引用(对象名)
animal 的编译类型是Animal,运行类型是Dog(真正的对象)
*/
2.编译类型在定义对象时就确定了,不能改变
animal = new Cat();
3.运行类型是可以改变的
4.编译类型看定义是等号(=)的左边,运行类型看等号(=)的右边
1.2多态的介绍
多态是在继承、实现情况下的一种现象,表现为:对象多态、行为多态。
多态是面向对象编程中的一个重要概念,它指的是同一类型的对象,在不同的情况下表现出不同的行为。简单来说,多态允许一个变量可以引用多种不同类型的对象,并根据实际类型调用相应对象的方法。
比如:Teacher和Student都是People的子类,代码可以写成下面的样子
1.3多态的好处
在多态形式下,右边的代码是解耦合的,更便于扩展和维护。(方便实现方案的切换)
- 怎么理解这句话呢?比如刚开始p1指向Student对象,run方法执行的就是Student对象的业务
定义方法时,使用父类类型作为形参,可以接收一切子类对象,扩展行更强,更便利。
public class Test2 {
public static void main(String[] args) {
// 目标:掌握使用多态的好处
Teacher t = new Teacher();
go(t);
Student s = new Student();
go(s);
}
//参数People p既可以接收Student对象,也能接收Teacher对象。
public static void go(People p){
System.out.println("开始------------------------");
p.run();
System.out.println("结束------------------------");
}
}
1.4快速入门案例
父类-Animal
public class Animal {
private String name;
private String color;
public Animal() {
}
public Animal(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void eat(){
System.out.println("在吃饭");
}
public void work(){
System.out.println("在工作");
}
}
子类-Cat
public class Cat extends Animal {
public Dog() {
}
public Dog(String name, String color) {
super(name, color);
}
@Override
public void eat() {
System.out.println(super.getName()+"吃猫粮");
}
@Override
public void work() {
System.out.println(super.getName()+"抓老鼠");
}
}
子类-Dog
public class Dog extends Animal {
public Dog() {
}
public Dog(String name, String color) {
super(name, color);
}
@Override
public void eat() {
System.out.println(super.getName()+"吃骨头");
}
@Override
public void work() {
System.out.println(super.getName()+"看家");
}
}
测试类-Test
public class Test {
public static void main(String[] args) {
Animal a1 = new Dog("小黄","黄色");
a1.eat();//输出小黄吃骨头
a1.work();//输出小黄看家
System.out.println("——————————————————————————");
Animal a2 = new Cat("小白","白色");
a2.eat();//输出小白吃猫粮
a2.work(); //输出小白抓老鼠
}
}
多态的描述
-
想要实现多态,必须有类的继承或者接口实现的前提
-
实现多态没有方法的重写,多态没有意义
-
实现多态,需要父类引用指向子类空间
-
多态的前提下,不能直接访问子类特有方法
1.5向上转型
1.本质:父类的引用指向子类的对象
2.语法:父类类型 引用名 = new 子类类型
3.特点:
(1)编译类型看左边,运行类型看右边;
(2)可以调用父类中的所有成员(需要遵守访问权限);
(3)不能调用子类的特有成员;
(4)最终运行效果看子类的具体实现
- 示例代码
//测试类
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
cat.run();//跑
cat.eat();//猫吃鱼
//cat.catchMouse-》报错-》不能调用子类的特有成员
}
}
//Animal类
public class Animal {
String name ="动物";
int age = 10;
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
}
//cat类
public class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
问题1:animal 可以调用哪些方法或属性?
(1)可以调用父类方法的所有成员,但遵守访问权限
Animal animal = new Cat();
System.out.println(animal.age);
System.out.println(animal.name);
animal.eat();
animal.run();
(2)不能调用子类的特有方法
// animal.catchMouse(); ->报错
/*原因:
因为编译阶段,能调用哪些成员是由编译类型决定的,父类中没有catchMouse()这个方法,所以不能调用
*/
问题2:最终运行效果看子类的具体实现
animal.eat();//猫吃鱼
//即调用方法时,从运行类型开始查找。
1.6向下转型
问题:通过向上转型案例代码,我们希望可以调用cat的特有方法。
1.语法:
子类类型 引用名 = (子类类型)父类引用
2.要求父类引用必须指向的是当前目标类型的对象
(意思:假如你的父类对象要强转为子类类型,那么这个父类引用必须原先是指向子类类型)
3.只能强转父类的引用,不能强转父类的对象
4.当强转后就可以调用子类类型中的所有成员
向下转型
Animal animal = new cat();
Cat cat = (Cat)animal;
//错误:Cat cat = (Cat)Animal->只能强转父类的引用,不能强转父类的对象
1.7 instanceof
虽然多态形式下有一些好处,但是也有一些弊端。在多态形式下,不能调用子类特有的方法,比如在Teacher类中多了一个teach方法,在Student类中多了一个study方法,这两个方法在多态形式下是不能直接调用的。
多态形式下不能直接调用子类特有方法,但是转型后是可以调用的(向下转型)。
但如果类型转换错了,就会出现类型转换异常ClassCastException,比如把Teacher类型转换成了Student类型.
这时就需要instanceof!!!
instanceof
是 Java 中的一个运算符,用于检查一个对象是否是指定类或其子类的实例。它的语法如下:
其中 object
是要检查的对象,Class
是要进行检查的类。
当使用 instanceof
运算符时,会进行以下判断:
- 如果
object
是null
,则返回false
。 - 如果
object
是指定类的一个实例,则返回true
。 - 如果
object
不是指定类的实例,则继续判断是否是其子类的实例,直到找到匹配的类或遍历完全部的继承层次结构。如果找不到匹配的类,则返回false
。
通过使用 instanceof
运算符,可以在进行类型转换之前,先对对象的类型进行检查,从而避免出现 ClassCastException 异常。
示例:
输出结果:
在上面的示例中,animal
对象是 Dog
类的一个实例。使用 instanceof
运算符,首先判断 animal
是否是 Animal
类的实例,结果为 true
。然后继续判断 animal
是否是 Dog
类的实例,同样也为 true
。因此,两个条件都满足,所以两个输出语句都会执行。
需要注意的是,instanceof
运算符只能用于引用类型之间的比较,不能用于基本数据类型(如 int
、double
等)。
- 代码案例
//测试类
public class Test {
public static void main(String[] args) {
Person p1 = new Student();
print(p1);
Person p2 = new Teacher();
print(p2);
}
public static void print(Person p){
p.eat();
if (p instanceof Student){
Student s = (Student) p;
s.work();
}
if (p instanceof Teacher){
Teacher t = (Teacher) p;
t.Work();
}
}
}
/*输出结果:
学生吃饭
键盘敲烂,月薪过万
老师吃白米饭
老师教授Java
*/
//父类
public class Person {
public void eat(){
System.out.println("吃饭");
}
}
//子类-Student
public class Student extends Person{
@Override
public void eat() {
System.out.println("学生吃饭");
}
public void work(){
System.out.println("键盘敲烂,月薪过万");
}
}
//子类-Teacher
public class Teacher extends Person {
@Override
public void eat() {
System.out.println("老师吃白米饭");
}
public void Work(){
System.out.println("老师教授Java");
}
}
1.8多态的属性
多态的属性没有重写之说!访问属性的值看编译类型,不能以方法的调用机制来看待,属性没有动态绑定机制,哪里声明,哪里使用!
//测试类
public class Test {
public static void main(String[] args) {
Base b = new Sub();
System.out.println(b.count);//输出10-》调用的值看编译类型
}
}
//父类
class Base{
int count =10;//属性
}
//子类
class Sub extends Base{
int count =20;
}
1.9多态数组和多态参数
- 多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
java
(1)应用实例:现有一个继承结构如下
要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象,
统一放在数组中,并调用每个对象 say 方法.(2) 应用实例升级:如何调用子类特有的方法,比如
Teacher 有一个 teach , Student 有一个 study
//定义一个 Person类
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//返回名字和年龄
public String say() {
return name + "\t" + age;
}
}
"--------------------------------------------------------------"
//定义子类-学生类-Student-继承Person类
public class Student extends Person {
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
//重写父类 say
@Override
public String say() {
return "学生 " + super.say() + " score=" + score;
}
//特有的方法
public void study() {
System.out.println("学生 " + getName() + " 正在学 java...");
}
}
"--------------------------------------------------------------"
//定义子类-老师类-Teacher-继承Person类
public class Teacher extends Person {
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//写重写父类的 say 方法
@Override
public String say() {
return "老师 " + super.say() + " salary=" + salary;
}
//特有方法
public void teach() {
System.out.println("老师 " + getName() + " 正在讲 java 课程...");
}
}
"---------------------------------------------------------------"
//主方法
public class PloyArray {
public static void main(String[] args) {
//应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
// 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
//循环遍历多态数组,调用 say
for (int i = 0; i < persons.length; i++) {
//提示: person[i] 编译类型是 Person ,运行类型是是根据实际情况由 JVM 来判断
System.out.println(persons[i].say());//动态绑定机制
//使用 类型判断 + 向下转型. if(persons[i] instanceof Student)
//判断 person[i] 的运行类型是不是 Student
if(persons[i] instanceof Student){
//小伙伴也可以使用一条语句 ((Student)persons[i]).study();
Student student = (Student)persons[i];//向下转型
student.study();
} else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
/*输出
名字: jack 年龄: 20
学生 名字: mary 年龄: 18
学生 mary 正在学 java...
学生 名字: smith 年龄: 16
学生 smith 正在学 java...
老师 名字: scott 年龄: 30
老师 scott 正在讲 java 课程...
老师 名字: king 年龄: 50
老师 king 正在讲 java 课程....
*/
- 多态参数
方法定义的形参类型为父类类型实参类型允许为子类类型
应用实例:
定义员工类 Employee,包含姓名和月工资[private],以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法
测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法 [e.getAnnual0]
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
//定义父类-员工类Employee
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
//得到年工资的方法
public double getAnnual() {
return 12 * salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
//定义子类-经理类-继承Employee
public class Manager extends Employee{
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public void manage() {
System.out.println("经理 " + getName() + " is managing");
}
//重写获取年薪方法
@Override
public double getAnnual() {
return super.getAnnual() + bonus;
}
}
//定义子类-普通员工-继承Employee
public class Worker extends Employee {
public Worker(String name, double salary) {
super(name, salary);
}
public void work() {
System.out.println("普通员工 " + getName() + " is working");
}
@Override
public double getAnnual() {
//因为普通员工没有其它收入,则直接调用父类方法
return super.getAnnual();
}
}
//主方法
public class PloyParameter {
public static void main(String[] args) {
Worker tom = new Worker("tom", 2500);
Manager milan = new Manager("milan", 5000, 200000);
PloyParameter ployParameter = new PloyParameter();
ployParameter.showEmpAnnual(tom);
ployParameter.showEmpAnnual(milan);
ployParameter.testWork(tom);
ployParameter.testWork(milan);
}
//showEmpAnnual(Employee e)
//实现获取任何员工对象的年工资,并在 main 方法中调用该方法[e.getAnnual()]
public void showEmpAnnual(Employee e) {
System.out.println(e.getAnnual());//动态绑定机制. }
//添加一个方法,testWork,如果是普通员工,则调用 work 方法,如果是经理,则调用 manage 方法
public void testWork(Employee e) {
if(e instanceof Worker) {
((Worker) e).work();//有向下转型操作
} else if(e instanceof Manager) {
((Manager) e).manage();//有向下转型操作
} else {
System.out.println("不做处理...");
}
}
}
1.0Java的动态绑定机制(重点)
//父类-A
class A{
private int i =10;
public int sum(){
return get1()+10;//20+10=30
}
public int sum1(){
return i+10;//10+10=20
}
public int get1(){
return i;
}
}
//子类-B
class B extends A{
private int i = 20;
/* public int sum(){
return get1()+10;
}*/
/* public int sum1(){
return i+10;
}*/
public int get1(){
return i;
}
}
//测试类
public class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum()); //输出30
System.out.println(a.sum1()); //输出20
}
}
Java的动态绑定机制
当调用对象方法的时候,该方法会和该对象的内存/运行类型绑定
解析:
(1)若把B类的sum方法注销,则a.sum时,在B类中未找到sum方法,触发动态继承的调用方法机制,往上一级寻找,找到A类的sum()然后进行调用。但A类sum方法中有return get1()方法,此时触发了动态绑定机制,运行类型是B,所以return get1()+10;是调用B类的get1()方法。但属性没有绑定机制,所以返回的是B类的属性值(哪里声明,哪里使用),所以是20;return get1()+10=>20+10=30
(2)若是把B类的sum1也注销后,a.sum1时,调用方法时,该方法会和该对象的内存地址/运行类型绑定,运行类型是B,但在B中找不到sum1()方法,然后触发了继承的调用方法机制,往上一级寻找,但属性没有绑定机制,所以返回的是A类的属性-10,然后return 返回20。
二、final关键字
final中文意思:最后的、最终的
final可以修饰类、属性、方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用到final
(1)当不希望类被继承时,可以使用final修饰;
(2)当不希望父类的某个方法被子类覆盖/重写时,可以使用final;
(3)当不希望类的某个属性的值被修改,可以用final修饰;
(4)当不希望某个局部变量被修改,可以使用final修饰
2.1final细节
- 细节1
final和static修饰的属性又叫常量,一般用字母全大写,单词之间用下划线分割(XX_XX)
static final int TAX_RETE = 10;
- 细节2
final修饰的属性在定义时,必须赋初值,并且以后不能修改,赋值可以在如下位置之一
(1)定义时
pulic final double TAX_RATE = 0.8;
(2)在构造器中
public MyClass(int value) { myVariable = value; }
(3)代码块中
{ myVariable = 20; }
-
细节3
如果final修饰的属性是静态(static)的,则初始化位置只能是
(1)定义时
(2)静态代码块中
ps:不能再构造器中赋值
– 无论是在声明时初始化还是在静态代码块中初始化,静态 final 属性在类加载时就会被赋值,并且之后无法再修改。
–需要注意的是,由于静态 final 属性在类加载时被初始化,它们的值对于所有的对象实例都是相同的。而构造器是用于实例化对象的,无法在构造器中对静态 final 属性进行赋值。
-
细节4
final修饰的类是不能被继承的,但可以被实例化对象(创建对象)
- 细节5
如果类不是final,但含有final方法,则该方法可以被继承
- 细节6
final不能修饰构造器
- 细节7
final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化
好处
final 和 static 搭配使用可以为属性和方法带来一些好处:
- 不可变性:使用 final 修饰的静态属性具有不可变性,即在初始化之后无法修改其值。这可以保证属性的值在整个程序运行过程中保持不变,从而避免了意外的修改导致的错误。
- 共享数据:静态属性是属于类的,而不是属于实例的。通过将属性设置为 final 和 static,可以确保该属性在所有实例之间共享相同的值。这在多个对象之间共享数据时非常有用。
- 提高效率:使用 final 和 static 可以提高代码的执行效率。由于 final 属性在编译时被优化,所以读取 final 静态属性的速度比读取普通属性要快一些。
- 代码简洁性:final 和 static 的组合可以确保属性或方法只需要定义一次,并且在全局范围内可见。这样可以简化代码并提高代码的可读性和可维护性。
不会导致类加载
final 和 static 搭配使用不会导致类加载的原因如下:
- final 修饰符:final 关键字用于表示一个类、方法或变量是最终的,不能被子类或其他方式修改。在类加载的过程中,final 修饰的成员变量(静态或实例)可以在类的初始化阶段直接赋值或通过静态代码块进行初始化。这意味着 final 成员变量的值在类加载时已经确定并且不可改变,不需要类加载过程额外的操作。
- static 修饰符:static 关键字用于表示一个成员属于类本身而不是实例。静态成员(包括静态方法和静态变量)在类加载时被初始化,并且只有一份实例,在整个程序运行期间都存在。由于静态成员与类本身紧密相关,所以不需要每次创建对象时重新加载,而是在类加载时就完成了初始化,因此不会导致额外的类加载。
-
扩展
final 修饰的成员变量在构造器中初始化扩展
final 修饰的成员变量必须要在定义时或构造函数中进行初始化,如果一个类有一个有参构造器和无参构造器时(或其他多个构造器),必须在无参构造器处也为final修饰的成员变量进行初始化。
因为假如无参构造器处没有为final修饰的成员变量进行初始化。在进行创建类时调用的是无参构造器的话,fianl就没有被初始化,就会报错,所以如果有多个构造器,那么需要确保每个构造器都有为final进行初始化的操作。
原因是 final 修饰的成员变量被赋予了"最终"状态,一旦赋值后就不能再被修改。为了确保 final 变量的值不能被修改,Java 要求 final 成员变量在对象创建时必须经过赋值。
为了确保 final 变量的赋值不会被遗漏或错误地处理,Java 要求 final 变量在定义时或构造函数中完成初始化。这样可以保证 final 变量的值始终是确定的,并且在对象创建后就不能再改变。
final修饰的静态变量为什么不能再构造器中赋值
final修饰的静态变量不能在构造器中赋值的原因是,静态变量属于类级别的成员,而构造器是用来初始化实例级别的成员的。
静态变量在类加载时就会被初始化,并且只会被初始化一次。它们的赋值操作与具体的对象实例无关,而是针对整个类的。因此,构造器只能用于实例级别的初始化工作,无法修改或重新赋值静态变量。
三、抽象类(abstract)
3.1引出abstract
public class Animal {
private String name;
private int age;
public oops() {
}
public oops(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("这是一个动物,但目前不确定他吃什么");
}
}
/*引出:父类的不确定性
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。
*/
3.2解决之道
当父类一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。
public abstract class Animal{
String name;
int age;
public abstract void cry();
}
/*解释
(1)考虑该方法设计为抽象abstract方法
(2)所谓抽象方法就是没有实现的方法
(3)所谓没有实现就是没有方法体
(4)当一个类中存在抽象方法时,需要将该类声明为abstract类
(5)一般来说,抽象类会被继承,让其子类来实现抽象方法
*/
3.3抽象类的介绍
1.用abstract关键字来修饰一个类时,这个类就是抽象类。
访问修饰符 abstract class 类名{
}
2.用abstract关键字来修饰一个方法时,这个方法就是抽象方法,并且没有方法体
访问修饰符 abstract 返回类型 方法名(参数列表);
3.抽象类的价值更多作用在于设计,设计者设计好后,让子类继承并实现抽象类。
4.抽象类,是在框架和设计模式使用较多。
3.4细节
- 细节1
抽象类不能被实例化(创建对象),仅作为一种特俗的父类,让子类继承并实现
- 细节2
抽象类不一定包含abstract方法,也就是说抽象类可以没有抽象方法;
- 细节3
一旦包含了abstract方法,则这个类必须声明为abstract
- 细节4
abstract只能修饰类和方法,不能修饰其他
- 细节5
抽象类可以有任意成员(抽象类本质还是类)
比如非抽象方法、构造器、静态属性等等
- 细节6
抽象方法不能有主体,即不能实现
- 细节7
如果一个类继承了抽象方法,则它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象方法
- 细节8
抽象类和抽象方法不能使用private、final和static,因为这些关键字都是和重写违背的。
3.5细节8的扩展
- private
修饰抽象类和抽象方法的的错误
抽象类用于被其他类继承和扩展,如果被私有化,其他类无法继承,就没办法实现其设计的目的。
抽象方法设计的初衷是被让子类去实现和扩展,如果进行了私有化,子类就会无法进行访问和重写。
- fianl
抽象类不能用
final
修饰的原因是final
关键字会阻止类的继承抽象方法不能使用
final
修饰的原因是final
关键字用于表示一个方法或类是最终的,不可被子类修改或重写
- static
抽象类不能使用
static
修饰的原因是static
关键字用于表示类或方法属于类本身,而不是类的实例。它可以被直接调用,无需创建对象。然而,抽象类是为了提供一个共享的基类,用于被子类继承和实现。它的目的是为了扩展和完善其功能,并且必须通过实例化对象来使用。因此,抽象类不能声明为
static
,因为它与抽象类的设计初衷相悖
3.6设计模式
设计模式是解决某一类问题的最优方案。
那模板方法设计模式解决什么问题呢?模板方法模式主要解决方法中存在重复代码的问题
比如A类和B类都有sing()方法,sing()方法的开头和结尾都是一样的,只是中间一段内容不一样。此时A类和B类的sing()方法中就存在一些相同的代码。
要求:
假如今天进行一场晚会,每个人都需要上场表演,但都有固定的流程
“唱一首自己喜欢的歌曲”
“演唱的歌曲内容”
“演唱结束,有请下一位”
//设置模板
//定义一个抽象类
public abstract class C {
//定义一个方法为模板方法,把相同代码放进去(普通方法)
public void sing(){
System.out.println("唱一首自己喜欢的歌曲");
//在模板方法中调用抽象方法
doing();
System.out.println("演唱结束,有请下一位");
}
//定义一个抽象方法,具体实现交给子类
public abstract void doing();
}
//继承抽象类C
public class A extends C{
//重写抽象方法(必须)
@Override
public void doing() {
System.out.println("我是一只小小小小鸟,想要飞就能飞的高~~~");
}
}
//继承抽象类C
public class B extends C {
//重写抽象方法(必须)
@Override
public void doing() {
System.out.println("我们一起学猫叫,喵喵喵喵喵喵喵~~");
}
}
//在测试类中进行创建对象和调用方法
public class Test {
public static void main(String[] args) {
C c1 = new A();
//调用模板方法
c1.sing();
System.out.println("——————————————————————————");
C c2 = new B();
//调用模板方法
c2.sing();
}
}
/*输出:
唱一首自己喜欢的歌曲
我是一只小小小小鸟,想要飞就能飞的高~~~
演唱结束,有请下一位
——————————————————————————
唱一首自己喜欢的歌曲
我们一起学猫叫,喵喵喵喵喵喵喵~~
演唱结束,有请下一位
*/
四、接口
4.1入门
//定义接口
public interface UsbInterface {
//规定接口的相关方法
void start();//等价于 public abstract void start();
void stop();//等价于 public abstract void stop();
}
//定义相机类-Camera
public class Camera implements UsbInterface{//实现接口usbInterface
//需要全部实现接口的方法
@Override
public void start() {
System.out.println("相机开始工作");
}
@Override
public void stop() {
System.out.println("相机停止工作");
}
}
//定义手机类-Phone
public class Phone implements UsbInterface{
//需要全部实现接口的方法
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机停止工作");
}
}
//定义测试类
public class Test {
public static void main(String[] args) {
Camera camera =new Camera();
camera.start();//相机开始工作
camera.stop();//相机停止工作
System.out.println("——————————————————————————");
Phone phone = new Phone();
phone.start();//手机开始工作
phone.stop();//手机停止工作
}
}
/*输出:
相机开始工作
相机停止工作
——————————————————————————
手机开始工作
手机停止工作
*/
4.2接口的介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来
- 接口是用来被类实现(implements)的,我们称之为实现类。
- 一个类是可以实现多个接口的(接口可以理解成干爹),类实现接口必须重写所有接口的全部抽象方法,否则这个类也必须是抽象类
4.3接口的定义
public interface 接口名{
成员变量(常量)//这里的常量指用final和static搭配使用的变量
成员方法(抽象方法)
}
4.5接口的实现
class 类名 implements 接口{
必须实现接口的抽象方法
}
4.5细节
-
细节1
接口不能实例化,接口是用来被实现的,实现接口的类称为实现类
-
细节2
接口的所有方法是public 接口中抽象方法可以不用写上abstract
void start();//等价于 public abstract void start(); void stop();//等价于 public abstract void stop();
- 细节3
一个普通类实现接口,就必须将该接口的所有方法都实现
- 细节4
抽象类实现接口,可以不用实现接口的方法
- 细节5
一个类可以同时实现多个接口
interface IE{} interface IE{} class pig implement ID,IE{}
- 细节6
接口中的属性只能是final的,而且是public static final
int i = 10; 实际上是 :public static final int i =1;(必须初始化)
- 细节7
接口中属性访问的形式:接口名.属性名
- 细节8
接口不能继承其他类,但是可以继承多个别的接口
interface ID{} interface IE{} interface Ip extends ID,IE{}
4.6接口和抽象类的比较
- 比较
接口(interface)和抽象类(abstract class)是面向对象编程中两个重要的概念,它们有一些共同点,但也存在一些区别。下面是接口和抽象类的比较:
1.定义方式:
接口:使用关键字 interface 定义,可以包含常量和抽象方法,但不能包含实例变量和具体方法的实现。
抽象类:使用关键字 abstract class 定义,可以包含实例变量、实例方法和抽象方法。抽象类允许定义具体方法的实现。
---------------------------------------------------------------
2.继承关系:
接口:可以通过关键字 implements 实现多个接口,一个类可以实现多个接口。
抽象类:使用关键字 extends 继承抽象类,一个类只能继承一个抽象类(单继承)。
---------------------------------------------------------------
3.构造器:
接口:不能有构造器,因为接口无法实例化。
抽象类:可以有构造器,并且在子类实例化时会调用其父类的构造器。
---------------------------------------------------------------
4.成员变量:
接口:只能包含常量(public static final),默认修饰符是 public。
抽象类:可以包含实例变量,可以使用各种修饰符来定义。
---------------------------------------------------------------
5.方法实现:
接口:所有方法默认都是抽象的,没有方法体。从 Java 8 开始,接口可以定义默认方法(default method)和静态方法(static method)。
抽象类:可以包含抽象方法和具体方法的实现。抽象方法没有方法体,子类必须提供具体的实现。
---------------------------------------------------------------
6.功能限制:
接口:用于定义一组相关的操作,强调对行为的抽象。一个类可以实现多个接口,实现了接口的类需要提供接口中定义的所有方法。
抽象类:用于表示一个抽象的概念或模板,可以包含具体的实现和一些子类的共有逻辑。一个类只能继承一个抽象类,但可以通过组合和接口的方式实现多重继承的效果。
- 怎么运用
1.设计目的:接口用于定义一组相关的操作,强调对行为的抽象;而抽象类用于表示一个抽象的概念或模板,可以包含具体的实现和一些子类的共有逻辑。
2.继承关系:接口通过实现多个接口实现多继承的效果,适用于需要在类之间建立契约、行为一致性的场景;抽象类通过单继承,适用于表示一种"is-a"的关系,具有更强的代码复用性。
3.具体实现:接口只能定义方法的签名,没有方法的实现;抽象类可以包含抽象方法和具体方法的实现。如果需要提供通用的抽象模板,可以使用抽象类,以减少代码的重复。
4.扩展性:接口的更改对现有实现无影响,因为实现类必须提供接口中的所有方法;抽象类的更改可能会影响其子类,因为子类需要实现或覆盖抽象类中的方法。
--------------------------------------------------------------
基于以上考虑,可以有以下几种情况:
使用接口:
需要定义一组相关的操作或行为,强调对行为的抽象。
需要实现多个接口,实现类需要满足多个接口的契约。
需要定义公共的行为规范,以便多个类共享相同的契约。
--------------------------------------------------------------
使用抽象类:
需要表示一种"is-a"的关系,子类与父类具有共同的属性和行为。
需要提供通用的抽象模板,包含一些子类的共有逻辑。
需要在抽象类中定义具体方法的实现,减少子类的代码重复性。
4.7接口和继承
- 代码演示
//定义接口1
public interface Fishable {
void swing();
}
//定义接口2
public interface Birdable {
void fly();
}
//父类-猴子
public class Monkey {
private String name;
public Monkey() {
}
public Monkey(String name) {
this.name = name;
}
public void Climbing(){
System.out.println(name+"会爬树。。。。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//定义子类继承Monkey类和实现接口1实现接口2
public class LittleMonkey extends Monkey implements Fishable,Birdable{
public LittleMonkey(String name) {
super(name);
}
@Override
public void swing() {
System.out.println(getName()+"通过学习可以跟鱼儿一样游泳。。。。");
}
@Override
public void fly() {
System.out.println(getName()+"通过学习可以跟鸟儿一样飞翔。。。。");
}
}
//定义测试类
public class Test {
public static void main(String[] args) {
LittleMonkey wk = new LittleMonkey("悟空");
wk.Climbing();
wk.swing();
wk.fly();
}
}
/*输出:
悟空会爬树。。。。
悟空通过学习可以跟鱼儿一样游泳。。。。
悟空通过学习可以跟鸟儿一样飞翔。。。。
*/
总结:
1.当子类继承父类时,就自动拥有父类的功能;
2.如果子类需要扩展,可以通过实现接口的方式扩展
3.可以理解:实现接口是对Java单继承机制的一种补充
接口和继承解决的问题不同
1.继承的价值主要在于解决代码的复用性和可维护性;
2.接口的价值主要在于设计,设计好各种规范(方法),让类去实现这些方法
3.接口在一定程度上实现代码解耦(接口规范化+动态绑定机制)
4.8接口多态
接口多态(Interface Polymorphism)是面向对象编程中的一个重要概念。它允许使用接口类型作为参数类型、变量类型或返回类型,并通过实现该接口的不同类的实例来实现不同行为。
在接口多态中,可以使用接口类型的引用变量来引用实现了该接口的不同类的对象。这样做的好处是可以在不改变代码结构的情况下,灵活地替换具体的实现类,实现代码的可扩展性和可维护性。
下面以一个简单的示例来说明接口多态的概念:
在上述示例中,Animal 是一个接口,它定义了一个 makeSound()
方法。Dog 和 Cat 类都实现了 Animal 接口,并分别重写了 makeSound()
方法。
在 main
方法中,我们使用 Animal 接口的引用变量来引用不同的实现类的对象。通过调用 makeSound()
方法,可以根据具体对象的类型执行相应的行为。
这样,在不改变 main
方法中的代码的情况下,可以轻松地增加或替换其他实现了 Animal 接口的类,实现了代码的可扩展性和灵活性。
总结起来,接口多态允许使用接口类型的引用来引用不同类的对象,并通过调用接口方法来实现不同的行为,从而提高代码的灵活性和可扩展性。
-
案例
面向对象思想设计一个电脑对象,可以使用鼠标和键盘。
鼠标、键盘要求实现USB接口,实现接入、拔出功能,并具备独有功能 。
鼠标有单击功能( click ),键盘有输入功能(input)定义接口
public interface USB {
void connect();//连接方法
void exit();
}
定义键盘类-实现接口
public class KeyBoard implements USB{
@Override
public void connect() {
System.out.println("连接成功");
}
@Override
public void exit() {
System.out.println("退出连接");
}
public void input(){
System.out.println("键盘敲烂,月薪过万");
}
}
定义鼠标类—实现接口
public class Mouse implements USB {
@Override
public void connect() {
System.out.println("连接成功");
}
@Override
public void exit() {
System.out.println("退出连接");
}
public void click(){
System.out.println("疯狂点击");
}
}
定义电脑类
public class Computer {
public void use(USB usb) {
usb.connect(); // 连接设备
if (usb instanceof KeyBoard) {
KeyBoard keyboard = (KeyBoard) usb;
keyboard.input(); // 调用键盘的输入方法
}
if (usb instanceof Mouse) {
Mouse mouse = (Mouse) usb;
mouse.click(); // 调用鼠标的单击方法
}
usb.exit(); // 退出设备
}
}
测试类
public class Test {
public static void main(String[] args) {
/* 面向对象思想设计一个电脑对象,可以使用鼠标和键盘。
鼠标、键盘要求实现USB接口,实现接入、拔出功能,并具备独有功能 。
鼠标有单击功能( click ),键盘有输入功能(input)*/
Mouse mouse = new Mouse();
KeyBoard keyboard = new KeyBoard();
Computer computer = new Computer();
computer.use(mouse);
System.out.println("——————————————————————————");
computer.use(keyboard);
}
}
4.9JDK8的新增特性
随着JDK版本的升级,在JDK8版本以后接口中能够定义的成员也做了一些更新,从JDK8开始,接口中新增的三种方法形式。
我们看一下这三种方法分别有什么特点?
public interface A {
/**
* 1、默认方法:必须使用default修饰,默认会被public修饰
* 实例方法:对象的方法,必须使用实现类的对象来访问。
*/
default void test1(){
System.out.println("===默认方法==");
test2();
}
/**
* 2、私有方法:必须使用private修饰。(JDK 9开始才支持的)
* 实例方法:对象的方法。
*/
private void test2(){
System.out.println("===私有方法==");
}
/**
* 3、静态方法:必须使用static修饰,默认会被public修饰
*/
static void test3(){
System.out.println("==静态方法==");
}
void test4();
void test5();
default void test6(){
}
}
接下来我们写一个B类,实现A接口。B类作为A接口的实现类,只需要重写抽象方法就尅了,对于默认方法不需要子类重写。代码如下:
public class B implements A{
@Override
public void test4() {
}
@Override
public void test5() {
}
}
最后,写一个测试类,观察接口中的三种方法,是如何调用的
public class Test {
public static void main(String[] args) {
// 目标:掌握接口新增的三种方法形式
B b = new B();
b.test1(); //默认方法使用对象调用
// b.test2(); //A接口中的私有方法,B类调用不了
A.test3(); //静态方法,使用接口名调用
}
}
课外知识:解藕
什么是解耦
在Java中,解耦(decoupling)是指将一个系统或组件的各个部分之间的依赖关系减少到最小,以提高代码的灵活性、可维护性和可扩展性。以下是一些常见的 Java 解耦技术:
接口和实现类:使用接口定义公共的行为规范,将具体实现封装在实现类中。通过面向接口编程,可以将依赖关系转移到接口上,而不是具体的实现类。
解耦案例
假设我们有一个汽车制造的场景,有多个不同类型的汽车(比如轿车、卡车)需要制造,每个汽车都有一个共同的行为——驾驶。
- 首先,定义一个驾驶接口(Driving),其中包含一个驾驶方法(drive):
2.然后,分别创建轿车和卡车的实现类,实现驾驶接口:
3.最后,我们可以通过接口进行解耦,实现对不同类型汽车的统一操作:
在这个例子中,通过定义驾驶接口(Driving),我们将驾驶行为进行了抽象。轿车和卡车作为实现类,实现了驾驶接口,并提供了各自的具体驾驶逻辑。在 CarFactory 类中,我们通过接口进行对象的创建,并调用统一的驾驶方法,从而实现了对不同类型汽车的解耦操作。
这种设计方式使得我们可以在不修改 CarFactory 类的情况下,轻松地添加新的汽车类型,并且通过面向接口编程,实现了松耦合的代码结构。
抽象类的课外扩展
抽像类为什么不能被实例化
抽象类是一种不能被实例化的类,这是因为抽象类本身是不完整的,它存在着未实现的抽象方法。抽象方法是在抽象类中声明但没有具体实现的方法,要求子类来实现。
由于抽象类中存在未实现的抽象方法,因此无法确定如何创建一个完整的抽象类对象。抽象类的目的是作为其他类的基类,它提供了一组共同的属性和方法的定义,但需要子类根据各自的特定需求来实现这些抽象方法。因此,抽象类只能被继承,而不能被实例化。
当我们希望使用抽象类中定义的功能时,我们需要创建一个具体的子类来继承该抽象类,并实现其中的抽象方法。这样,我们就可以通过子类的实例来使用抽象类的功能。
总结起来,抽象类不能被实例化的原因是因为它存在未实现的抽象方法,需要子类来实现这些方法,并且抽象类的主要目的是作为其他类的基类。
抽象类不能被私有化是由于抽象类的设计初衷和语言语法上的规定所决定的
抽象类不能被私有化是由于抽象类的设计初衷和语言语法上的规定所决定的。
-抽象类是一种特殊的类,它被设计为作为其他类的基类或父类,并提供一个模板或蓝图来定义共享的行为和属性。抽象类本身不能直接实例化,而是作为其他类的继承基础,通过子类来实现其抽象方法。
-私有化(private)是访问修饰符之一,用于限制成员的可访问范围。私有化的成员只能在当前类内部访问,无法被其他类使用或继承。
----------------------------------------------------------
-将抽象类私有化会导致以下问题:
-抽象类用于被其他类继承和扩展,如果将其私有化,那么其他类将无法继承该抽象类,从而无法实现其设计的目的。
-抽象类中的抽象方法是为了被子类重写和提供具体实现的,如果将抽象类私有化,子类将无法访问和重写抽象方法,导致无法实现预期的行为多态性。
根据面向对象设计原则,抽象类应该具有可见性,以便其他类可以继承它并提供具体实现。因此,根据语法规范,抽象类不能被声明为私有的。
--------------------------------------------------------
-将抽象方法私有化会导致以下问题:
-抽象方法被设计为由具体的子类来重写和提供实现,如果将其私有化,子类将无法访问和重写该方法,违反了抽象方法的设计初衷。
-抽象方法在父类中定义了行为规范,通过子类来实现具体的功能。如果将抽象方法私有化,其他类将无法通过继承和实现来使用该方法,限制了代码的扩展性和灵活性。
-----------------------------------------------------------
-根据面向对象设计原则,抽象方法应该具有可见性,以便子类能够重写和提供具体实现。因此,根据语法规定,抽象方法不能被声明为私有的。
-如果希望限制抽象类和方法的访问范围,可以使用其他访问修饰符,例如 protected 或包级私有化(默认访问修饰符)。这样可以在一定程度上控制抽象类的可见性,并确保其能够被继承和实现。
抽象类不能用final修饰的原因是final关键字会阻止类的继承,而抽象类的主要目的就是为了被继承和子类化。当我们将一个类声明为抽象类时,它就成为了一个可以被其他类继承的基类。
使用final关键字修饰抽象类会产生矛盾的效果。如果抽象类是final的,那么它就无法被继承,这样就无法实现其设计的目的。抽象类的目标是为了让其他类通过继承和实现来扩展和完善其功能,而final关键字会限制这种扩展性。
另外,需要注意的是,抽象类中的方法可以使用final关键字进行修饰。当一个方法在抽象类中被声明为final时,表示该方法不能再被子类重写或覆盖。但是抽象类本身不能被修饰为final。
-----------------------------------------------------------------
抽象方法不能使用final修饰的原因是final关键字用于表示一个方法或类是最终的,不可被子类修改或重写。而抽象方法的存在就是为了要求其子类必须实现该方法,以便完善抽象类的功能。
如果我们将抽象方法声明为final,那么子类就无法对其进行重写或覆盖,这与抽象方法的设计初衷相悖。抽象方法的目的是为了让子类实现自己的具体实现细节,以满足不同子类的需求。
此外,抽象类中的抽象方法本身就具有一种隐含的abstract关键字,表示该方法是抽象的,无需再显式声明。因此,在抽象类中使用final关键字修饰抽象方法是不合法的,也是没有意义的。
私有化是由于抽象类的设计初衷和语言语法上的规定所决定的**
抽象类不能被私有化是由于抽象类的设计初衷和语言语法上的规定所决定的。
-抽象类是一种特殊的类,它被设计为作为其他类的基类或父类,并提供一个模板或蓝图来定义共享的行为和属性。抽象类本身不能直接实例化,而是作为其他类的继承基础,通过子类来实现其抽象方法。
-私有化(private)是访问修饰符之一,用于限制成员的可访问范围。私有化的成员只能在当前类内部访问,无法被其他类使用或继承。
----------------------------------------------------------
-将抽象类私有化会导致以下问题:
-抽象类用于被其他类继承和扩展,如果将其私有化,那么其他类将无法继承该抽象类,从而无法实现其设计的目的。
-抽象类中的抽象方法是为了被子类重写和提供具体实现的,如果将抽象类私有化,子类将无法访问和重写抽象方法,导致无法实现预期的行为多态性。
根据面向对象设计原则,抽象类应该具有可见性,以便其他类可以继承它并提供具体实现。因此,根据语法规范,抽象类不能被声明为私有的。
--------------------------------------------------------
-将抽象方法私有化会导致以下问题:
-抽象方法被设计为由具体的子类来重写和提供实现,如果将其私有化,子类将无法访问和重写该方法,违反了抽象方法的设计初衷。
-抽象方法在父类中定义了行为规范,通过子类来实现具体的功能。如果将抽象方法私有化,其他类将无法通过继承和实现来使用该方法,限制了代码的扩展性和灵活性。
-----------------------------------------------------------
-根据面向对象设计原则,抽象方法应该具有可见性,以便子类能够重写和提供具体实现。因此,根据语法规定,抽象方法不能被声明为私有的。
-如果希望限制抽象类和方法的访问范围,可以使用其他访问修饰符,例如 protected 或包级私有化(默认访问修饰符)。这样可以在一定程度上控制抽象类的可见性,并确保其能够被继承和实现。
抽象类不能用final修饰的原因是final关键字会阻止类的继承,而抽象类的主要目的就是为了被继承和子类化。当我们将一个类声明为抽象类时,它就成为了一个可以被其他类继承的基类。
使用final关键字修饰抽象类会产生矛盾的效果。如果抽象类是final的,那么它就无法被继承,这样就无法实现其设计的目的。抽象类的目标是为了让其他类通过继承和实现来扩展和完善其功能,而final关键字会限制这种扩展性。
另外,需要注意的是,抽象类中的方法可以使用final关键字进行修饰。当一个方法在抽象类中被声明为final时,表示该方法不能再被子类重写或覆盖。但是抽象类本身不能被修饰为final。
-----------------------------------------------------------------
抽象方法不能使用final修饰的原因是final关键字用于表示一个方法或类是最终的,不可被子类修改或重写。而抽象方法的存在就是为了要求其子类必须实现该方法,以便完善抽象类的功能。
如果我们将抽象方法声明为final,那么子类就无法对其进行重写或覆盖,这与抽象方法的设计初衷相悖。抽象方法的目的是为了让子类实现自己的具体实现细节,以满足不同子类的需求。
此外,抽象类中的抽象方法本身就具有一种隐含的abstract关键字,表示该方法是抽象的,无需再显式声明。因此,在抽象类中使用final关键字修饰抽象方法是不合法的,也是没有意义的。