Day16
1.抽象类及抽象方法的使用
*我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类*
*抽象方法* : 使用abstract关键字修饰,抽象方法只包含一个方法名,而没有方法体。
*抽象类* :如果一个类包含抽象方法,那么该类必须是抽象类。*注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。*
需求:编写人类为父类,编写两个子类(中国人、日本人)
抽象方法:没有代码块,使用abstract修饰的方法,交给非抽象的子类去实现
注意: 抽象方法必须在抽象类中
继承抽象类的子类****必须重写父类所有的抽象方法****。否则,该子类也必须声明为抽象类。
java
//抽象类:使用abstract修饰
public abstract class Person {//父类
private String name;
private char sex;
private int age;
//无参构造,有参构造,get、set方法,省略
//抽象方法:没有代码块,使用abstract修饰的方法,交给非抽象的子类去实现
//注意:抽象方法必须在抽象类中
public abstract void eat();
public void sleep(){
System.out.println(this.name + "睡觉觉");
}
}
java
public abstract class Chinese extends Person{
private String id;
public Chinese() {
}
public Chinese(String name, char sex, int age, String id) {
super(name, sex, age);
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public abstract void hobbies();
}
java
public class SiChuan extends Chinese{
public SiChuan() {
}
public SiChuan(String name,char sex,int age,String id){
super(name, sex, age, id);
}
@Override
public void hobbies() {
System.out.println(super.getName() + "喜欢打麻将、炸金花");
}
@Override
public void eat() {
System.out.println(super.getName() + "吃火锅、串串香");
}
}
java
public class Japanese extends Person{
private String yearNum;
public Japanese() {
}
public Japanese(String name, char sex, int age, String yearNum) {
super(name, sex, age);
this.yearNum = yearNum;
}
public String getYearNum() {
return yearNum;
}
public void setYearNum(String yearNum) {
this.yearNum = yearNum;
}
@Override
public void eat() {
System.out.println(super.getName() + "吃马赛克");
}
}
java
//测试类
public class Test01 {
public static void main(String[] args) {
Japanese j = new Japanese("波多野结衣", '女', 18, "令和");
j.eat();
j.sleep();
System.out.println("-----------------------");
SiChuan sc = new SiChuan("小彭", '男', 21, "1234567890");
sc.eat();
sc.hobbies();
sc.sleep();
System.out.println("-----------------------");
GuangDong gd = new GuangDong("李嘉诚", '男', 97, "0987654321");
gd.eat();
gd.hobbies();
gd.sleep();
}
}
1.1抽象类的特征
抽象类的特征总结起来可以说是 有得有失
1.*有得:抽象类得到了拥有抽象方法的能力。*
2.*有失:抽象类失去了创建对象的能力。*****
其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。
1.2抽象类的细节
1.抽象类****不能创建对象****,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
2.抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
3.抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
4.抽象类的子类,必须重写抽象父类中****所有的****抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
5.抽象类存在的意义是为了被子类继承。 理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。
6.抽象方法只有实现 理解:方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做****实现方法****。
2.抽象类及抽象方法的深入
面试题
1.抽象类不能有构造方法?
抽象类可以有构造方法
2.抽象类中只能有抽象方法?抽象类中可以有属性、构造方法、成员方法、静态方法、抽象方法
3.抽象类中不可以没有抽象方法?可以,但是这样毫无意义
(因为某些原因将方法设计成抽象的,抽象方法必须在抽象类中,这是你才会使用到抽象类)
4.什么原因将方法设计成抽象的?这个方法应该在该类中,但是该方法不太好实现,就将方法设计成抽象的,交给非抽象的子类去实现
5.如果父类是抽象类,则子类必须实现父类的抽象方法?不一定,如果子类是抽象类,可以不实现父类的抽象方法
6.可以使用new关键字来创建抽象类对象?不可以,创建的是匿名子类的对象
java
public class Test02 {
public static void main(String[] args) {
Person p = new Person("弗罗兹·甘地",'男',23) {//底层是创建匿名类内类,不是抽象类
@Override
public void eat() {
System.out.println(super.getName() + "吃咖喱");
}
};
p.eat();
}
}
匿名类内类内存理解图1:
匿名类内类创建过程:
1.创建一个没有名字的匿名类(子类),继承Person类(父类),重写eat方法
2.创建匿名子类的对象
3.将匿名子类对象的内存地址赋值给父类的引用(多态)
3.接口的使用
理解:
1.接口是一个特殊的抽象类
2.JDK1.8之前,接口中只能有抽象方法及静态常量
3.JDK1.8开始,接口中允许使用抽象方法、静态常量、静态方法、默认方法
注意:1.接口中的抽象方法默认添加public abstract(经验:一般把abstract去掉)
2.接口中的属性默认添加public static final (j静态常量)
3.接口中的默认方法默认添加public
应用场景:接口相当于是制定规则(标准),再让实现类去实现
需求:设计学生管理系统项目的接口(列大纲)分析:
学生管理系统管理一个一个的学生对象
管理 - 数据的操作:增、删、改、查
java
//实体类
public class Student {
private String name;
private char sex;
private int age;
private String classId;
private String id;
//无参构造,有参构造,get、set方法,省略
@Override
public String toString() {
return "Student [name=" + name + ", sex=" + sex + ", age=" + age + ", classId=" + classId + ", id=" + id + "]";
}
}
java
//学生管理系统的接口 (大纲)
public interface IStudentManagerSystem {
//静态常量
//默认使用public static final修饰
int NAME = 1;
int SEX = 2;
int AGE = 3;
int CLASS_ID = 4;
int ID = 5;
//抽象方法 (大纲)
//默认使用public abstract修饰
public void add(Student stu);//添加
public void delete(String classId,String id);//删除
public void update(String classId,String id,int type,Object val);//更新
public Student getStu(String classId,String id);//查询
//静态方法
public static void method01(){
System.out.println("IStudentManagerSystem接口中的静态方法");
}
//默认方法
//默认使用public修饰
default void method02(){
System.out.println("IStudentManagerSystem接口中的默认方法");
}
}
java
//学生管理系统的实现类
public class StudentManagerSystemImpl implements IStudentManagerSystem{
@Override
public void add(Student stu) {
}
@Override
public void delete(String classId, String id) {
}
@Override
public void update(String classId, String id, int type, Object val) {
}
@Override
public Student getStu(String classId, String id) {
return null;
}
}
java
//测试类
public class Test01 {
public static void main(String[] args) {
StudentManagerSystemImpl sms = new StudentManagerSystemImpl();
//调用实现类实现的方法
sms.add(new Student());
//调用默认方法
sms.method02();
//调用静态方法
IStudentManagerSystem.method01();
}
}
3.1定义格式
接口的声明:interface
接口名称:首字母大写,满足"驼峰模式"
java
interface 接口名称{
// 抽象方法
}
注意:接口可以多继承,类只能单继承。
3.2接口的实现
类与接口的关系为实现关系,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。
实现接口的格式
java
//接口的实现:
class 类名 implements 接口1,接口2,接口3...{
}
3.2.1 类实现接口的要求和意义
1.必须重写实现的全部接口中所有抽象方法。
2.如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
3.*意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。*
4.接口的深入
面试题
1.一个类可以实现多个接口?
可以
2.一个接口可以实现多个接口? 不可以
但是一个接口可以继承多个接口
3.接口里面的方法不一定都是抽象的? JDK1.8之前,接口中只能有抽象方法和静态常量
JDK1.8开始,接口中除了有抽象方法和静态常量以外,还可以有静态方法和默认方法
4.接口解决了类的单继承问题? 是的,因为一个类可以实现多个接口
5.一个类是否可以继承一个类并同时实现多个接口? 可以
6.接口可以new对象? 不可以,创建的是匿名实现类的对象
类 - 接口的关系:类 - 类:单继承(一个类只能继承另一个类,不能继承多个类)
类 - 接口:多实现(一个类可以实现多个接口)
接口 - 接口:多继承(一个接口可以继承多个接口)
java
public interface I1 {
public void i1Method();
}
java
public interface I2 {
public void i2Method();
}
java
public interface I3 extends I4,I5{
public void i3Method();
}
java
public interface I4 {
public void i4Method();
}
java
public interface I5 {
public void i5Method();
}
public class B {
}
java
public class A extends B implements I1,I2,I3{//A 继承 B 实现I1,I2,I3接口
@Override
public void i2Method() {
}
@Override
public void i1Method() {
}
@Override
public void i4Method() {
}
@Override
public void i5Method() {
}
@Override
public void i3Method() {
}
}
5.多态
5.1类的多态
理解:子类对象指向父类引用;父类引用中存储的是子类对象在堆中开辟的内存地址
需求:使用代码描述出老师骑着自行车上班
分析:老师类、自行车类
需求升级:自行车 -> 小汽车
步骤: 1.创建Car类,编写open、close
2.改动原来的Teacher,编写start、stop
设计原则:前人给我们总结的经验,告诉我们不能做什么,如果做了会出现严重后果设计模式:前人给我们总结的经验,告诉我们怎么做,我们就跟着他一步一步做就能实现功能
开闭原则 - OCPO - Open - 在需求升级时,对于创建类是欢迎的(因为创建类对于原来代码的影响几乎为0)
C - Close - 在需求升级时,对于改动原有类是拒绝的(因为原有类之间的关系是趋于稳定状态,如果改动原有类,很有可能打破这种平衡,导致bug的出现)
P - Principle - 原则
需求升级:自行车 -> 小汽车 -> 飞机步骤:创建Plane类,继承Vehicles,重写open、close
java
//交通工具
public abstract class Vehicles {
private int count;//座位数
private String color;//颜色
public Vehicles() {
}
public Vehicles(int count, String color) {
this.count = count;
this.color = color;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public abstract void open();
public abstract void close();
}
java
public class Bick extends Vehicles{
public Bick() {
}
public Bick(int count,String color){
super(count, color);
}
public void open(){
System.out.println("自行车:握好扶手,踩下脚踏板");
}
public void close(){
System.out.println("自行车:捏手刹");
}
}
java
public class Car extends Vehicles{
public Car() {
}
public Car(int count,String color){
super(count, color);
}
public void open(){
System.out.println("小汽车:一键启动,挂D档,踩下油门");
}
public void close(){
System.out.println("小汽车:踩刹车,挂P档,熄火");
}
}
java
public class Plane extends Vehicles{
@Override
public void open() {
System.out.println("飞机:踩油门");
}
@Override
public void close() {
System.out.println("飞机:达到P城,跳伞");
}
}
java
public class Teacher {
public void start(Vehicles v){
v.open();
}
public void stop(Vehicles v){
v.close();
}
}
java
public class Test01 {
public static void main(String[] args) {
Teacher t = new Teacher();
//类的多态:子类对象指向父类引用
//理解:父类引用中存储的是子类对象在堆中开辟的内存地址
Vehicles v = new Plane();
t.start(v);
System.out.println("欣赏沿途的风景");
t.stop(v);
}
}
5.2接口的多态
理解:实现类对象指向接口的引用;接口的引用存储的是实现类对象在堆中开辟的地址
需求:使用代码描述出电脑连接外部设备
java
public interface IUSB {
public void use();
}
java
public class Mouse implements IUSB{
@Override
public void use() {
System.out.println("鼠标:左点点、右点点");
}
}
java
public class KeyBoard implements IUSB{
@Override
public void use() {
System.out.println("键盘:输入数据");
}
}
java
//电脑类
public class Computer {
//连接
public void connection(IUSB usb){
usb.use();
}
}
java
public class Test01 {
public static void main(String[] args) {
Computer computer = new Computer();
//接口的多态:实现类对象指向接口的引用
//理解:接口的引用存储的是实现类对象在堆中开辟的地址
IUSB usb = new KeyBoard();
computer.connection(usb);
}
}
5.3多态的形式
*多态是出现在继承或者实现关系中的*。
多态体现的格式:父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();
*多态的前提*:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。
5.4多态的前提
*多态*: 是指同一行为,具有多个不同表现形式。
*前提【重点】*1.有继承或者实现关系
2.方法的重写【意义体现:不重写,无意义】
3.父类引用指向子类对象【格式体现】
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
*多态的前提*:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。
6.对象转型
6.1向上转型
自动转型 - 向上转型:子类类型 转 父类类型 (转 --> =)
父类类型 变量名 = new 子类类型();
注意:1.向上转型就是多态!!!
2.向上转型后,可以调用父类属性
3.向上转型后,可以调用父类方法
4.向上转型后,不可以调用子类独有的属性,方法
5.向上转型后,可以调用子类重写父类的方法
java
public class Father {
String fatherAttr = "父类属性";
public void fatherMethod(){
System.out.println("父类方法");
}
public void fun(){
System.out.println("父类方法");
}
}
java
public class Son extends Father{
String sonAttr = "子类属性";
public void sonMethod(){
System.out.println("子类方法");
}
@Override
public void fun() {
System.out.println("子类重写父类的方法");
}
}
java
public class Test01 {
public static void main(String[] args) {
//向上转型
Father father = new Son();
System.out.println(father.fatherAttr);
father.fatherMethod();
father.fun();
}
}
6.2向下转型
强制转型 - 向下转型:父类类型 转 子类类型
子类类型 变量名 = (子类类型) 父类变量名;
注意: 1.向下转型是不安全的 -- ClassCastException类型转换异常,出现ClassCastException,一定要看错误信息
2.父类对象不能赋值给子类引用 -- Dog dog = (Dog) new Animal();
3.向下转型之前必须先向上转型
4.向下转型之前,使用instanceof判断类型
java
public class Test01 {
public static void main(String[] args) {
//向上转型
Animal an = new Cat();
//向下转型
if(an instanceof Dog){//判断引用an中指向的对象是否是Dog类型
Dog dog = (Dog) an;
dog.shout();
}else if(an instanceof Cat){//判断引用an中指向的对象是否是Cat类型
Cat cat = (Cat) an;
cat.eat();
}
}
}
6.3对象转型的应用场景
java
public class MyString {
private char[] value;
public MyString(String original) {
//"abc"
//['a','b','c']
value = original.toCharArray();//将字符串转换为字符数组
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj instanceof MyString){
MyString my = (MyString) obj;
char[] v1 = this.value;
char[] v2 = my.value;
//比较字符长度
if(v1.length != v2.length){
return false;
}
for (int i = 0; i < v1.length; i++) {
//比较字符的Unicode码是否相同
if(v1[i] != v2[i]){
return false;
}
}
return true;
}
return false;
}
@Override
public String toString() {
return String.valueOf(value);//将字符数组转为字符串
}
}
java
public class Test01 {
public static void main(String[] args) {
String str1 = new String("abc");
System.out.println(str1.equals(new Student()));//false
System.out.println("-------------------------------");
MyString m1 = new MyString("abc");
System.out.println(m1.equals(new Student()));//false
}
}
7.内部类
理解:一个类中再声明另外一个类
将一个类A定义在另一个类B里面,里面的那个类A就称为****内部类*,B则称为*外部类****。
分类:
成员内部类:类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
- 静态内部类:类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
- 接口内部类
- 局部内部类:类定义在方法内
- 匿名内部类:没有名字的内部类,可以在方法中,也可以在类中方法外
应用场景:B类的对象只在A类中使用,并且B类对象使用到了A类所有的属性,就可以将B类作为A类的成员内部类
B类的对象只在A类中使用,并且B类对象使用到了A类静态的属性,就可以将B类作为A类的静态内部类
抽象类的子类只创建一次对象,就没必要创建子类,直接使用匿名内部类(new 抽象类)
接口的实现类只创建一次对象,就没必要创建实现类,直接使用匿名内部类(new 接口)
java
public interface I1 {
//接口内部类
class Inner{
}
}
java
//外部类
public class Outter {
//成员内部类
class Inner01{
}
//静态内部类
static class Inner02{
}
public void method(){
//局部内部类
class Inner03{
}
}
}
7.1成员内部类
需求:创建成员内部类的对象,操作对象的方法
成员内部类对象的创建格式: 内部类 变量 = new 外部类().new 内部类();
小结: 1.创建成员内部类对象之前,必须创建外部类对象
2.成员内部类可以调用外部类所有的属性
3.在成员内部类中调用指定的外部类属性:外部类.this.属性
java
//外部类
public class Outter {
private String str1 = "外部类属性1";
String str2 = "外部类属性2";
protected String str3 = "外部类属性3";
public String str4 = "外部类属性4";
final String str5 = "外部类属性5";
static String str6 = "外部类属性6";
static final String str7 = "外部类属性7";
//成员内部类
class Inner{
String str1 = "内部类属性";
public void innerMethod(){
System.out.println("成员内部类的方法");
System.out.println(str1);//this.str1
System.out.println(Outter.this.str1);
System.out.println(str2);//Outter.this.str2
System.out.println(str3);//Outter.this.str3
System.out.println(str4);//Outter.this.str4
System.out.println(str5);//Outter.this.str5
System.out.println(str6);//Outter.str6
System.out.println(str7);//Outter.str7
}
}
}
java
public class Test01 {
public static void main(String[] args) {
//创建成员内部类的对象
Inner inner = new Outter().new Inner();
//调用方法
inner.innerMethod();
}
}
7.3静态内部类
需求:创建静态内部类的对象,操作对象的方法
静态内部类对象的创建格式: 外部类.内部类 变量 = new 外部类.内部类构造器;
小结: 1.创建静态内部类对象,不用创建外部类对象
2.静态内部类只能调用外部类静态的属性
java
//外部类
public class Outter {
static String str1 = "外部类属性1";
static final String str2 = "外部类属性2";
//静态内部类
static class Inner{
public void innerMethod(){
System.out.println("静态内部类的方法");
System.out.println(str1);//Outter.str1
System.out.println(str2);//Outter.str2
}
}
}
java
public class Test01 {
public static void main(String[] args) {
//创建静态内部类的对象
Inner inner = new Outter.Inner();
//调用方法
inner.innerMethod();
}
}
7.4接口内部类
需求:创建静态内部类的对象,操作对象的方法
小结: 接口内部类的使用方式和静态内部类一致
java
//外部接口
public interface Outter {
//接口内部类
//默认使用public static修饰
class Inner{
public void innerMethod(){
System.out.println("接口内部类的方法");
}
}
}
java
public class Test01 {
public static void main(String[] args) {
//创建静态内部类的对象
Inner inner = new Outter.Inner();
//调用方法
inner.innerMethod();
}
}
7.5局部内部类
需求:调用局部内部类的方法
小结: 1.局部内部类不能使用访问修饰符
2.局部内部类的作用域就在外部类方法中
java
//外部类
public class Outter {
public void method(){
//局部内部类
class Inner{
public void innerMethod(){
System.out.println("局部内部类的方法");
}
}
//创建局部内部类对象
Inner inner = new Inner();
//调用方法
inner.innerMethod();
}
}
java
public class Test01 {
public static void main(String[] args) {
Outter outter = new Outter();
outter.method();
}
}
7.5.1局部内部类的面试题
面试题:局部内部类使用到外部类的局部变量时,为什么局部变量会变为常量
答:局部变量变成常量,是让该变量的生命周期变长,是的方法以外还能找的到该数据,
如果该变量时局部变量,方法执行完毕就直接被回收,在方法就不能使用该数据
java
//外部类
public class Outter {
public Object method(){
int num = 100;
//局部内部类
class Inner{
@Override
public String toString(){
return "局部内部类的方法 -- " + num;
}
}
Object obj = new Inner();
return obj;
}
}
java
public class Test01 {
public static void main(String[] args) {
Outter out = new Outter();
Object obj = out.method();
System.out.println(obj.toString());//调用子类重写的toString
}
}
局部内部类理解图:
常量和局部变量的生命周期?
常量:存放在常量池中,项目销毁时,常量才会被回收
局部变量:调用方法,方法在栈中开辟空间,用于存放局部变量,方法执行完毕,该空间会立刻回收
意味着方法结束,局部变量也结束了
7.6匿名内部类
匿名内部类前提:
匿名内部类必须****继承一个父类*或者*实现一个父接口****
匿名内部类格式
java
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
创建匿名内部类的对象
1.底层创建一个匿名类(Test01$1.class),继承A类,重写method方法
2.创建匿名子类的对象
3.赋值给父类的引用(类的多态)
java
public abstract class A {
public abstract void method();
}
java
public class Test01 {
public static void main(String[] args) {
//创建匿名内部类的对象
//1.底层创建一个匿名类(Test01$1.class),继承A类,重写method方法
//2.创建匿名子类的对象
//3.赋值给父类的引用(类的多态)
A a = new A() {
@Override
public void method() {
}
};
a.method();
}
}
7.6.1匿名内部类(接口)
创建匿名内部类的对象
1.底层创建一个匿名类(Test01$1.class),实现I1接口,重写method方法
2.创建匿名实现类的对象
3.赋值给接口的引用(接口的多态)
java
public interface I1 {
public void method();
}
java
public class Test01 {
public static void main(String[] args) {
//创建匿名内部类的对象
//1.底层创建一个匿名类(Test01$1.class),实现I1接口,重写method方法
//2.创建匿名实现类的对象
//3.赋值给接口的引用(接口的多态)
I1 i1 = new I1() {
@Override
public void method() {
}
};
i1.method();
}
}
总结
1.抽象类及抽象方法
2.接口
3.思考题:抽象类与接口的区别
4.多态 -- 重要!!!
5.对象转型(向上转型、向下转型)
6.内部类(成员内部类,静态内部类,接口内部类,局部内部类,匿名内部类)1.注重使用
2.注重局部内部类的面试题
3.注重匿名内部类的内存图