多态-final-抽象类-接口
多态
D:\code\黑马\code\oop-up-app02\src\com\polymorphism
多态的概念
-
多态是在继承或实现的情况下的一种现象,分为对象多态和行为多态
-
对象多态
-
行为多态:
- 编译看左边,运行看右边
- people p2 = new Student(); p2.say();
- 编译时只要左边people中有say方法就不会报错,但是运行时会在右边Student类中找重写后的say方法
//父类 public class people { public String name = "people"; public void say(){ System.out.println("I am a people"); } }
//子类1 public class Student extends people{ public String name = "Student"; @Override public void say(){ System.out.println("I am a Student"); } public void study(){ System.out.println("I am studying"); } }
//子类2 public class Teacher extends people{ public String name = "Teacher"; @Override public void say(){ System.out.println("I am a Teacher"); } }
public class Test { public static void main(String[] args) { //对象多态 people p1 = new people(); people p2 = new Student(); people p3 = new Teacher(); //不存在属性多态 System.out.println(p1.name); System.out.println(p2.name); System.out.println(p3.name); //行为多态 p1.say();//编译看左边,运行看右边 p2.say(); //p2.study(); //会报错,因为p2是people类型,people没有study方法,所以无法调用study方法 p3.say(); } } /* result: people people people I am a people I am a Student I am a Teacher */
-
多态的前提
- 有继承或实现关系
- 存在父类引用子类对象
- 存在方法重写
多态注意事项
- 多态是指对象和行为的多态,而不谈属性的多态。
- 调用属性时永远看左边,不看右边。即只能调用父类有的属性,且只能找到父类的属性,即使子类中有重名的属性,见上面对属性的调用
- 调用方法时永远是编译看左边,运行看右边
- 所能调用的方法只能是子类重写后的方法,子类特有的无法调用,见上面的study( )
多态的好处
-
可以解耦合,使代码扩展性更强
-
使用父类对象作为方法的形参,可以接收一切子类对象
//子类与父类的代码见上文代码 public class Test { public static void main(String[] args) { people p1 = new people(); people p2 = new Student(); people p3 = new Teacher(); sayHello(p1); sayHello(p2); sayHello(p3); } public static void sayHello(people p){//可以接受父类和所有子类对象 p.say(); } }
多态的问题
- 无法调用子类特有的方法,只能调用子类重写的方法,代码见上文多态的概念中的代码中的Test类
多态下的类型转换
用于解决多态无法调用子类特有的方法的问题
- 自动类型转换:父类 变量名 = new 子类()
- 强制类型转换:子类 变量名 = (子类)父类变量
注意事项
-
存在继承或实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错
-
运行时,如果发现对象的真实类型与强转后的类型不同,会报异常
public static void main(String[] args) { people p = new Teacher(); //Student s = (Student) p; // 会报ClassCastException,因为原来的p是Teacher类型,不能转换为Student类型 }
-
解决异常问题,在强转前使用instanceof关键字判断对象真实类型,再强转
public class Test2 { public static void main(String[] args) { people p = new Teacher(); hhh(p); } public static void hhh(people p){ p.say(); System.out.println(p instanceof Student); System.out.println(p instanceof Teacher); if (p instanceof Student) { Student s = (Student) p; s.study(); } else if (p instanceof Teacher) { Teacher t = (Teacher) p; t.teach(); } } } /* result: I am a Teacher false true I am teaching */
final
D:\code\黑马\code\oop-up-app02\src\com\finall
final关键字可以修饰类、方法和变量
-
修饰类:该类被称为最终类,特点是不能被继承了。
- 一般在工具类中使用
-
修饰方法:该方法被称为最终方法,特点是不能被重写了。
-
修饰变量:该变量只能被赋值一次。(必须在初始化时赋值)
-
final可以用来修饰局部变量和成员变量(实例成员变量和静态成员变量)
-
final修饰基本类型变量时,变量存储的数据不能被改变
-
final修饰引用类型的变量时,变量存储的地址不能变,但是地址指向的对象的内容可以改变
//final修饰局部变量的两种情况 public class Test { public static void main(String[] args) { final int a = 10; // a = 20; // 会报错,因为a是final类型,不能再次赋值 } public static void hhh(final int a){ // a = 20; // 会报错,因为a是final类型,不能再次赋值 } }
//final修饰成员变量 public class Test1 { static final int a = 10; final int b = 20; public static void main(String[] args) { // 会报错,因为静态成员变量a是final类型,不能再次赋值 // a = 20; // 会报错,因为实例成员变量b是final型,不能再次赋值 // b = 30; } }
//final修饰基本类型和引用类型的区别 public class Test2 { public static void main(String[] args) { final int a = 10; final int[] list = {1,2,3}; // a = 11;// 会报错,因为a是final修饰的基本数据类型的int变量,不能再次赋值 list[0] = 10;//不会报错,因为引用类型可以修改地址指向的内容的值 // list = null//会报错,因为final修饰的引用类型的变量所存储的地址不能再次赋值 } }
-
常量
-
概念
- 使用static final修饰的已经初始化的成员变量
-
作用
- 通常用于记录系统的配置信息
-
命名规范
- 使用大写英文单词,多个单词使用下划线连接起来
-
使用常量记录系统配置信息的优势
- 代码可读性更好
- 可维护性更好
-
执行原理
-
程序编译后,常量会被“宏替换”:出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的。也就是说不会影响性能,这些常量不会在运行时才找值,而是在编译后就将所有常量直接替换成需要的字面量
//.java文件 public class Test3 { public static final String MY_NAME = "LMH"; public static void main(String[] args) { System.out.println(MY_NAME); System.out.println(MY_NAME); System.out.println(MY_NAME); System.out.println(MY_NAME); } }
//编译后的.class文件 package com.finall; public class Test3 { public static final String MY_NAME = "LMH"; public Test3() { } public static void main(String[] args) { System.out.println("LMH");//对常量进行了自动赋值 System.out.println("LMH"); System.out.println("LMH"); System.out.println("LMH"); } }
-
抽象类abstract
D:\code\黑马\code\oop-up-app02\src\com\abstract_
-
抽象类
- abstract修饰的类
-
抽象方法
- abstract修饰的方法
- 抽象方法中不能有方法体,只能由方法签名
-
抽象类注意事项
-
抽象类中不一定有抽象方法,有抽象方法的类一定为抽象类
//将方法注释掉不会报错,因为抽象类中不一定有抽象方法 //如果去掉类中的abstract会报错,因为有抽象方法的类一定为抽象类 public abstract class Test { public abstract void test(); }
-
类该有的成员(成员变量,方法,构造器)抽象类都可以有,也就是说抽象类中既可以有抽象方法,也可以有实例方法,只要类中有一个抽象方法则该类就是抽象类
//如果去掉类中的abstract会报错,因为类中有一个抽象方法 public abstract class Test { public String name;//抽象类中可以有成员变量 public Test() {//抽象类中可以有构造方法 System.out.println("抽象类中可以有构造方法"); } public String getName() {//抽象类中可以有普通方法 return name; } public void setName(String name) {//抽象类中可以有普通方法 this.name = name; } public abstract void test();//抽象类中可以有抽象方法 }
-
一个类继承抽象类,必须重写该类所有的抽象方法,否则必须将当前类也定义为抽象类
//抽象类 public abstract class Test{ public abstract void a(); public abstract void b(); }
//重写了所有方法 public class test1 extends Test{ @Override public void a() { System.out.println("a"); } @Override public void b() { System.out.println("b"); } }
//由于只重写了一个方法会报错,除了重写所有方法会在类名前添加abstract public class test2 extends Test{ @Override public void a() { System.out.println("a"); } }
-
-
抽象类特点
- 抽象类不允许创建对象,仅作为一种特殊的父类,让子类继承并实现
-
抽象类的好处
- 父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重写实现,我们设计这样的抽象类是为了更好的支持多态。
-
案例
public abstract class Animal {
private String name;
public abstract void action();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class dog extends Animal{
@Override
public void action() {
System.out.println("狗汪汪汪的叫");
}
}
public class cat extends Animal{
@Override
public void action() {
System.out.println("猫喵喵喵的叫");
}
}
public class test {
public static void main(String[] args) {
Animal d = new dog();
d.setName("小狗");
d.action();
Animal c = new cat();
c.setName("小猫");
c.action();
}
}
/*
result:
狗汪汪汪的叫
猫喵喵喵的叫
*/
public abstract class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//模板方法,用final修饰,放入相同代码
public final void commonintroduce(){
System.out.println("我的名字是:"+name+" 年龄:"+age + selfintroduce());
}
//抽象方法
public abstract String selfintroduce();
}
public class Teacher extends Person{
private String Skill;
public Teacher() {
}
public Teacher(String name, int age ,String skill) {
super(name, age);
this.Skill = skill;
}
//实现抽象方法
@Override
public String selfintroduce() {
return " 我的技能是:"+Skill+"。";
}
}
public class Consult extends Person{
private int num;
public Consult() {
}
public Consult(String name, int age,int num) {
super(name, age);
this.num = num;
}
//实现抽象方法
@Override
public String selfintroduce() {
return " 我的咨询人数是:"+num+"人。";
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Teacher("张三", 20, "java");
Person p2 = new Consult("李四", 30, 100);
p1.commonintroduce();
p2.commonintroduce();
}
}
/*
result:
我的名字是:张三 年龄:20 我的技能是:java。
我的名字是:李四 年龄:30 我的咨询人数是:100人。
*/
接口
D:\code\黑马\code\oop-up-app02\src\com\interface_
初识接口
-
使用interface定义接口
-
接口不能创建对象
-
接口中只能由属性和抽象方法
- 属性默认为常量,默认使用public static final修饰
- 方法默认是抽象方法,默认使用public abstract修饰
-
接口需要被类使用implements实现,实现接口的类叫实现类
-
一个实现类可以实现多个接口,但是必须要重写所有接口的抽象方法,否则要将实现类定义为抽象类
//接口1 public interface A { // 接口中的属性都是常量,默认使用public static final修饰 String MY_NAME = "A"; // 接口中的方法都是抽象方法,默认使用public abstract修饰 void sayA(); }
//接口2 public interface B { String MY_NAME = "B"; void sayB(); }
//实现类 public class C implements A,B{ //必须同时实现所有的接口中的抽象方法 @Override public void sayA() { System.out.println("sayA"); } @Override public void sayB() { System.out.println("sayB"); } }
//测试类 public class Test { public static void main(String[] args) { // 接口不能被实例化 // A a = new A(); // B b = new B(); // 前两个为多态,后一个为普通对象 A a = new C(); B b = new C(); C c = new C(); // 接口中的属性都是常量,默认使用public static final修饰 System.out.println(A.MY_NAME); System.out.println(B.MY_NAME); // System.out.println(C.MY_NAME);//会报错,因为C中并没有常量MY_NAME // 接口中的方法都是抽象方法,默认使用public abstract修饰 a.sayA();//a中只有sayA()方法,没有sayB() b.sayB();//b中只有sayB()方法,没有sayA() c.sayA();//c中既有sayA()方法,也有sayB() } } /* result: A B sayA sayB sayA sayB */
接口的好处
-
弥补了类单继承的不足,一个类同时可以实现多个接口。
-
让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。
//抽象父类 abstract class people{ public String name; public int age; public people(String name, int age) { this.name = name; this.age = age; } public abstract void say(); }
//driver接口 interface Driver{ void drive(); }
//cooker接口 interface Cooker{ void cook(); }
//实现 class Student extends people implements Driver, Cooker{ public Student(String name, int age) { super(name, age); } @Override public void say() { System.out.println("我叫" + super.name + ",今年" + super.age + "岁,学生"); } @Override public void drive() { System.out.println("我叫" + super.name + ",今年" + super.age + "岁,司机"); } @Override public void cook() { System.out.println("我叫" + super.name + ",今年" + super.age + "岁,厨师"); } }
//测试 public class Test { public static void main(String[] args) { Student student = new Student("张三", 18); student.say(); student.drive(); student.cook(); //使用接口来接受对象,可以更有效的标识对象,当前对象就是个司机,无法调用学生和厨师的方法 Driver driver = new Student("李四", 20); driver.drive(); // driver.say();//会报错,因为是Driver类型,没有say方法 Cooker cooker = new Student("王五", 22); cooker.cook(); } } /* result: 我叫张三,今年18岁,学生 我叫张三,今年18岁,司机 我叫张三,今年18岁,厨师 我叫李四,今年20岁,司机 我叫王五,今年22岁,厨师 */
案例
//实体类
public class Student {
private String name;
private char sex;
private double score;
public Student() {
}
public Student(String name, char sex, double score) {
this.name = name;
this.sex = sex;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "name='" + name + '\'' +
", sex=" + sex +
", score=" + score;
}
}
//接口
public interface Studentoper {
void printInfo(ArrayList<Student> students);
void printAverageScore(ArrayList<Student> students);
}
//实现一,完成方案一
public class StudentoperImpl1 implements Studentoper{
@Override
public void printInfo(ArrayList<Student> students) {
for (Student student : students) {
System.out.println(student);
}
}
@Override
public void printAverageScore(ArrayList<Student> students) {
double sum = 0;
for (Student student : students) {
sum += student.getScore();
}
System.out.println("平均分为:" + sum / students.size());
}
}
//实现二,完成方案二
public class StudentoperImpl2 implements Studentoper{
@Override
public void printInfo(ArrayList<Student> students) {
int countMan = 0;
int countWoman = 0;
for (Student student : students) {
if (student.getSex() == '男')
countMan++;
else
countWoman++;
System.out.println(student);
}
System.out.println("男生人数:" + countMan);
System.out.println("女生人数:" + countWoman);
}
@Override
public void printAverageScore(ArrayList<Student> students) {
double max = students.get(0).getScore();
double min = students.get(0).getScore();
double sum = 0;
for (Student student : students) {
if (student.getScore() > max)
max = student.getScore();
if (student.getScore() < min)
min = student.getScore();
sum += student.getScore();
}
double sum1 = sum - max - min;
double avg = sum1/ (students.size() - 2);
System.out.println("平均分为:" + avg);
}
}
//操作类
public class ClassOper {
private final ArrayList<Student> students = new ArrayList<>();
//使用多态完成方案一
private final Studentoper studentoper1 = new StudentoperImpl1();
//使用多态完成方案二
private final Studentoper studentoper2 = new StudentoperImpl2();
//添加学生
public ClassOper() {
students.add(new Student("张三",'男' , 100));
students.add(new Student("李四", '女', 99));
students.add(new Student("王五",'男' , 98));
students.add(new Student("赵六", '女', 97));
students.add(new Student("田七",'女' , 96));
students.add(new Student("周八", '女', 95));
}
public void printInfo(boolean flag) {
System.out.println("---------------"+flag+"----------------");
if (flag)
studentoper1.printInfo(students);
else
studentoper2.printInfo(students);
}
public void printAverageScore(boolean flag) {
if (flag)
studentoper1.printAverageScore(students);
else
studentoper2.printAverageScore(students);
}
}
//测试类
public class Test {
public static void main(String[] args) {
ClassOper classOper = new ClassOper();
boolean flag = true;//如果更改为false,则使用StudentoperImpl2的实现
classOper.printInfo(flag);
classOper.printAverageScore(flag);
}
}
/*
result:
---------------true----------------
name='张三', sex=男, score=100.0
name='李四', sex=女, score=99.0
name='王五', sex=男, score=98.0
name='赵六', sex=女, score=97.0
name='田七', sex=女, score=96.0
name='周八', sex=女, score=95.0
平均分为:97.5、
---------------false----------------
name='张三', sex=男, score=100.0
name='李四', sex=女, score=99.0
name='王五', sex=男, score=98.0
name='赵六', sex=女, score=97.0
name='田七', sex=女, score=96.0
name='周八', sex=女, score=95.0
男生人数:2
女生人数:4
平均分为:97.5
*/
接口的其他细节
JDK8之后接口中新增的三种方法
- 默认方法,使用default修饰,可以被重写,但没必要
- 私有方法,使用private修饰,只能在接口内部调用
- 静态方法:使用static修饰
//接口
public interface A {
/*
* 接口中的默认方法(实例方法)
* 1. 必须使用default关键字修饰,默认被public修饰
* 2. 接口中的默认方法,可以通过接口的实现类对象,直接调用
* 3. 接口中的默认方法,也可以被接口的实现类进行覆盖重写
*/
default void method1(){
System.out.println("A接口的默认方法");
method2();
}
/*
* 接口中的私有方法(实例方法)
* 1. 必须使用private关键字修饰
* 2. 只能在接口中被调用
* 3. JDK9之后才有私有方法,可以被接口中的默认方法或者静态方法调用
*/
private void method2() {
System.out.println("A接口的私有方法");
}
/*
* 接口中的静态方法(类方法)
* 1. 必须使用static关键字修饰,默认被public修饰
* 2. 接口中的静态方法,可以通过接口名直接调用
*/
static void method3(){
System.out.println("A接口的静态方法");
}
}
//实现类
public class B implements A{
}
//测试类
public class Test {
public static void main(String[] args) {
A a = new B();
a.method1();
A.method3();
}
}
/*
result:
A接口的默认方法
A接口的私有方法
A接口的静态方法
*/
新增的三种方法的好处
- 增加了接口的能力,更便于项目的扩展和使用
- 在实际开发中,如果有100个类实现了某一个接口,那么当该接口需要新增一个功能时,对应的100个实现类都需要重写该新增的方法,那么工作量会很大,而现在只要使用新增的三种方法则只需要操作接口即可,实现类会自动继承新增的默认方法,也可通过接口直接调用新增的静态方法
接口的多继承
概念
- 一个接口可以继承多个接口
好处
-
便于实现类去实现接口
-
比如A接口继承了B和C接口,那么实现类只需要实现A接口,就可以实现A,B,C三个接口,此时实现类必须重写A,B,C三个接口中的全部方法
interface B{ void m1(); } interface C{ void m2(); } interface A extends B,C{ void m3(); } //只有同时实现A,B,C三个接口中的方法,才不会报错 public class Test implements A{ @Override public void m1() { } @Override public void m2() { } @Override public void m3() { } }
-
接口其他注意事项(了解)
-
一个接口继承多个接口,如果多个接口中存在方法签名不一致,则此时不支持多继承。
- 此时当前接口不知道应该继承哪个方法
-
一个接口继承多个接口,如果多个接口中存在方法签名一致,则此时只需要重写一次即可,支持多继承
//一个接口继承多个接口,如果多个接口中存在方法签名不一致,则此时不支持多继承。 interface D{ void test(); } interface E{ String test(); } //会报错,无法继承 interface F extends D,E{ }
//一个接口继承多个接口,如果多个接口中存在方法签名一致,则此时只需要重写一次即可,支持多继承 public class Test1 implements C{ @Override public void test() { System.out.println("test"); } } interface A{ void test(); } interface B{ void test(); } interface C extends A,B{ }
-
一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。
//一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。 public class Test2 implements A1,B1{ } //会报错 interface A1{ void m1(); } interface B1{ void m1(String s); }
-
一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。
//一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。 public class Test3 { public static void main(String[] args) { T3 t3 = new T3(); t3.m1();//没有重写的情况下,优先使用父类的方法 } } class T1 { public void m1() { System.out.println("T1.m1"); } } interface T2 { default void m1() { System.out.println("T2.m1"); } } class T3 extends T1 implements T2 { } /* result: T1.m1 */
-
一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
//一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。 public class Test4 { public static void main(String[] args) { C3 c3 = new C3(); c3.hi(); } } interface C1{ void hi(); } interface C2{ void hi(); } class C3 implements C1,C2{ @Override public void hi() { System.out.println("hi"); } }
}
//会报错
interface A1{
void m1();
}
interface B1{
void m1(String s);
}
- 一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。
```java
//一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。
public class Test3 {
public static void main(String[] args) {
T3 t3 = new T3();
t3.m1();//没有重写的情况下,优先使用父类的方法
}
}
class T1 {
public void m1() {
System.out.println("T1.m1");
}
}
interface T2 {
default void m1() {
System.out.println("T2.m1");
}
}
class T3 extends T1 implements T2 {
}
/*
result:
T1.m1
*/
-
一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
//一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。 public class Test4 { public static void main(String[] args) { C3 c3 = new C3(); c3.hi(); } } interface C1{ void hi(); } interface C2{ void hi(); } class C3 implements C1,C2{ @Override public void hi() { System.out.println("hi"); } }