# 面向对象进阶
# 面向对象的三特
- 封装性、继承性、多态性
# static 静态关键字
- static 表示静态,是一种java中的一种修饰符,可以修饰成员方法、成员变量
# 被static修饰的成员变量,称为静态变量,静态变量
# 特点
- 被该类所有对象共享
- 不属于对象,属于类
- 随着类的加载而加载,优先于对象的存在
注意
被static修饰的静态成员,属于该类公共成员,属于使用该类创建的对象共有的属性
# 调用方式
- 类名调用(推荐)
- 对象名调用 (静态成员随着类的加载而加载,因此不推荐对象名调用)
public class Static {
public static void main(String[] args) {
Student.schoolName = "尚硅谷";
Student s1 = new Student("张三", 18);
s1.show();//学校: 尚硅谷姓名:张三年龄:18
Student s2 = new Student("李四", 19);
s2.show();//学校: 尚硅谷姓名:李四年龄:19
//更改类中静态成员
Student.schoolName = "黑马大学";
s1.show();//学校: 黑马大学姓名:张三年龄:18
s2.show();//学校: 黑马大学姓名:李四年龄:19
}
}
# 被static修饰的成员方法,称为静态方法
# 特点
- 多用在测试类和工具类当中
- JavaBean当中很少会用
# 调用方式
- 类名调用(推荐)
- 对象名调用
# static关键字注意事项
# 工具类
帮我们做一些事情,但是不描述任何事物的类
# JavaBean类
用来描述一些事物的类。比如Student、Teacher、Dog、Cat等
# 测试类
用来检查其他类是否书写正确,带有main方法的类,是程序的主入口
# 工具类
不是用来描述一些事物的,而是帮我们做一些事情的类
# 工具类书写注意事项
- 私有化构造方法 (防止外部创建对象),创建工具类对象是无意义的
- 工具类中的成员变量,尽量设置为私有(static)的,外界直接使用类名调用成员方法
# 例 定义数组工具类
public class ArrayUtils {
//私有化构造方法
private ArrayUtils() {
}
//编写静态方法
//返回整数数组的内容
//只考虑整数数组
//只考虑一维数组
public static String printArr(int[] arr) {
if (arr.length == 0) return "[]";
StringJoiner sj = new StringJoiner(",", "[", "]");
for (int j : arr) sj.add(String.valueOf(j));
return sj.toString();
}
//用于返回平均分
//只考虑浮点型数组
//只考虑一维数组
public static double getAerage(double[] arr) {
double sum = 0;
for (double j : arr) sum += j;
return sum / arr.length;
}
}
# main方法
main方法,是程序的主入口public被JVM虚拟机调用,访问权限足够大static被JVM虚拟机调用,不用创建对象,直接类名访问- 因为
main方法是静态的,所以测试类中的其他方法也需要是静态的 void被JVM虚拟机调用,因此不需要给虚拟机返回值main一个通用名称。虽然不是关键字,但是被JVM识别String[] args以前用于键盘接收数据,现在没用
# 继承
- 继承是多态的前提,如果没有继承就没有多态
- 继承是面向对象编程中一个重要的概念,它允许我们定义一个类,继承另一个类,从而获得另一个类的属性和方法。
# 继承的好处
- 可以把多个子类中重复代码抽取到父类中,提高代码的复用性
- 子类可以在父类的基础上,增加其他功能,使子类更加强大
# 什么时候使用继承
当类与类之间,存在相同共性的内容,并满足子类是父类中的一种,就可以考虑继承,来优化代码
# 继承的特点
- Java只支持单继承,不支持多继承,但支持多层继承
- 单继承: 一个子类只能继承一个父类
# 子类能继承父类的哪些内容
# 构造方法 不能被继承
# 成员变量 可以被继承
- 所有成员变量都可以被继承
- 使用
private私有关键字修饰的成员变量,不能直接使用,需使用父类get、set方法进行访问
# 成员方法
- 如果该方法能够被添加到虚方法表当中,则该方法可以被继承
- 虚方法表:非
private修饰的成员方法、非static修饰的成员方法、非final修饰的成员方法
# 继承中成员的访问特点
- 如果父类和子类有同名的成员,变量以就近原则进行访问
this:表示当前对象,可以访问该类中的成员super:表示父类,可以访问父类中的成员
public class Fu() {
String name = "Fu";
}
public class Zi() extends Fu {
String name = "Zi";
public void show() {
String name = "Zi show";
System.out.println(name);//Zi show
System.out.println(this.name);//Zi
System.out.println(super.name);//Fu
}
}
# 继承体系练习
- 现有四种动物:布偶猫、中国狸花猫、哈士奇、泰迪
- 暂时不考虑属性,只考虑行为
请按照继承的思想特点进行继承思想体系设计
四种动物有以下行为
- 布偶猫:吃饭、喝水、抓老鼠
- 中国狸花猫:吃饭、喝水、抓老鼠
- 哈士奇:吃饭、喝水、看家、拆家
- 泰迪:吃饭、喝水、看家、蹭一蹭
# 方法重写
在继承中,当父类的方法不能满足子类现有的需求时,需要进行方法重写
# 书写格式
在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法
# @Override重写注解
@Override是放在重写的方法上,校验子类重写时语法是否正确- 加上注解后如果有红色波浪线,表示语法错误
- 建议重写方法都加
@Override注解,代码安全、优雅
public class hashDog extends dog {
//方法重写
@Override
public void doSomething2() {
System.out.println(this.name + "拆家");
}
}
# 重写方法的注意事项
- 重写方法的名称、形参列表必须与父类中的一致
- 子类重写父类方法时,访问权限子类必须大于等于父类
- 子类重写父类方法时,返回值类型子类必须小于等于父类
- 重写的方法尽量和父类保持一致
- 只有被添加到虚方法表中的方法 才能被重写
# 继承中构造方法的访问特点
- 子类不能继承父类的构造方法,但是可以通过
super关键字调用 - 子类构造方法的第一行,有一个默认的
super() - 默认先访问父类中无参构造方法,再执行自己
- 如果想要方法访问有参构造,必须手动书写
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Student extends Person {
public Student(String name, int age) {
//访问有参构造
super(name, age);
}
}
# 多态
# 什么是多态?
对象的多种形态
# 多态的前提
- 有继承/实现关系
- 有父类引用指向子类对象
- 有方法的重写
# 多态的好处
- 使用父类型作为参数,可以接受所有子类对象
- 体现多态的拓展性与便利
public class main {
public static void main(String[] args) {
Person p = new Person("张三", 18);
someOne(p);
Student s = new Student("李四", 19);
someOne(s);
Admin a = new Admin("王五", 20);
someOne(a);
}
//这个方法既能接收Student类,Admin类
//只能把类型写为这两个类型的父类
public static void someOne(Person p) {
p.show();
}
}
# 多态的弊端
- 多态性无法访问子类特有的成员
- 如需访问子类特有成员需使用类型转换(自动类型转换,强制类型转换)
- 自动类型转换
Person p = new Student(); - 强制类型转换
Student s = (Student) p;
# 多态调用特点
- 在成员变量中,使用多态方式创建的成员,只能访问父类成员
- 在成员方法中,使用多态方式创建的成员,不可以访问子类特有成员(父类没有子类有)
public class main {
public static void main(String[] args) {
//使用多态创建对象
Person1 p = new Admin1();
//使用多态调用成员变量
//编译看左边,运行看左边
//只能访问父类成员变量
System.out.println(p.name);//张三
//使用多态创建对象调用成员方法
//编译看左边,运行看右边
//在进行编译的时候先进行类型检查,检查不通过则报错
p.show();
//多态的弊端
//不能调用子类的特有功能(父类没有,子类有)
// p.hobby(); //报错
//解决方法 让父类型变为子类
//转换时类型不能瞎转 子类是什么类型就转为什么类型
//方法1 强制类型转换
// Admin1 a = (Admin1) p;
// a.hobby();
//方法2 类型判断转换
if (p instanceof Admin1 a1) {
a1.hobby();
} else if (p instanceof Teacher t1) {
t1.todo();
}
}
}
class Person1 {
String name = "张三";
public void show() {
System.out.println("我是" + name);
}
}
class Admin1 extends Person1 {
String name = "李四";
@Override
public void show() {
System.out.println("我是------" + name);
}
public void hobby() {
System.out.println("我是管理员,我的爱好是打篮球");
}
}
class Teacher extends Person1 {
String name = "王五";
@Override
public void show() {
System.out.println("我是]]']]]]" + name);
}
public void todo() {
System.out.println("我是老师,我正在教书");
}
}
# 强制类型转换能解决什么问题
- 可以转换为真正的子类类型,从而调用子类特有的功能
- 转换类型与真实对象类型不一致会报错
- 转换的时候用
instanceof判断类型是否一致
//测试类
public class text {
public static void main(String[] args) {
Person4 p = new Person4("张三", 18);
//年龄为18岁的张三养了一只旺财,它今年3岁了
//3岁的狗两只前腿死死的抱住骨头猛吃
//3岁的旺财看家
p.keepPet(new Dog("旺财", 3), "骨头");
// 年龄为18岁的张三养了一只招财猫,它今年2岁了
// 2岁的猫眯着眼睛吃鱼
// 2岁的招财猫抓老鼠
p.keepPet(new Cat("招财猫", 2), "鱼");
}
}
//动物父类
class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(String food) {
System.out.println(age + "岁的" + name + "吃" + food);
}
}
//子类狗
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat(String food) {
System.out.println(age + "岁的狗两只前腿死死的抱住" + food + "猛吃");
}
public void lookHome() {
System.out.println(age + "岁的" + name + "看家");
}
}
//子类猫
class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat(String food) {
System.out.println(age + "岁的猫眯着眼睛吃" + food);
}
public void catchMouse() {
System.out.println(age + "岁的" + name + "抓老鼠");
}
}
//饲养员
class Person4 {
String name;
int age;
public Person4(String name, int age) {
this.name = name;
this.age = age;
}
//喂养狗
// public void keepPet(Dog dog) {
// System.out.println("年龄为" + age + "岁的" + name + "养了一只" + dog.name + ",它今年" + dog.age + "岁了");
// dog.eat("骨头");
// }
//
// //喂养猫
// public void keepPet(Cat cat) {
// System.out.println("年龄为" + age + "岁的" + name + "养了一只" + cat.name + ",它今年" + cat.age + "岁了");
// cat.eat("鱼");
// }
public void keepPet(Animal animal, String food) {
System.out.println("年龄为" + age + "岁的" + name + "养了一只" + animal.name + ",它今年" + animal.age + "岁了");
//在此种情况下animal调用不了子类中特有的方法
animal.eat(food);
//使用类型判断强制转换类型
if (animal instanceof Dog dog) dog.lookHome();
else if (animal instanceof Cat cat) cat.catchMouse();
}
}
# 包
# 什么是包
包就是文件夹,用来管理各种不同功能的Java类,方便后期代码维护
# 包的命名规则
- 需要全部英文小写,见名知意
# 使用其他类时的导包规则
- 使用同一个包中的类时,不需要导包
- 使用Java.lang包中的类时,不需要导包
- 其他情况都需要导包
- 如果同时使用两个包中的同名类,需要用全类名(包名+类名)
import java.util.Scanner;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//全类名
main.util.Scanner sc1 = new main.util.Scanner(System.in);
}
}
# final关键字
final关键字可以用于变量、方法、类- 变量:叫做常量,只能赋值一次,不能修改
- 方法:表示该方法是最终方法,不能被重写
- 类:表示该类是最终类,不能被继承
注意
在使用
final关键字修饰变量时,需要遵守以下规则- 如果是基本数据类型,则该变量必须被初始化,并且不能被修改
- 如果是引用数据类型,则该变量存储的地址值不能发生改变,对象内部可以改变
:::
# 权限修饰符
权限修饰符:是用来控制一个成员能够被访问的范围
可以修饰成员变量、方法、构造方法、内部类
# 权限修饰符的分类
public>protected>default(空着不写)>private
public:公共权限,任何类都可以访问private:私有权限,只有自己可以访问
| 修饰符 | 同一个类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
|---|---|---|---|---|
| public | Y | Y | Y | Y |
| protected | Y | Y | Y | |
| default | Y | Y | ||
| private | Y |
# 代码块
# 代码块的分类
- 静态代码块:静态代码块在类加载的时候执行,只执行一次
- 局部代码: 局部代码块在方法执行时执行,每次执行方法都会执行一次
- 构造代码块:构造代码块在构造方法执行时执行,每次执行构造方法都会执行一次
# 构造代码块
抽取构造方法中的重复代码(但写法不够灵活)
# 静态代码块
数据初始化时,静态代码块只执行一次
public class Main {
static {
System.out.println("静态代码块执行");
}
public static void main(String[] args) {
}
}
# 抽象类和抽象方法
# 抽象类
- 抽象类不能被实例化,只能被继承
- 抽象类中可以定义抽象方法,也可以定义非抽象方法
- 具有抽象方法的类必须是抽象类
- 抽象类中可以有构造方法
- 抽象类的子类要么重写抽象类中的所有抽象方法要么是抽象类
# 抽象方法
- 抽象方法没有方法体,只有方法名
- 抽象方法必须定义在抽象类中
- 抽象方法必须被重写,重写时必须使用
@Override注解 - 抽象方法不能被私有化
public class text {
public static void main(String[] args) {
Frog f = new Frog("小青蛙", 1);
f.toDo();
Sheep s = new Sheep("小羊", 2);
s.toDo();
}
}
//具有抽象方法的类必须是抽象类
abstract class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void drink() {
System.out.print("喝水");
}
public abstract void toDo();
}
class Frog extends Animal {
public Frog(String name, int age) {
super(name, age);
}
@Override
public void toDo() {
super.drink();
System.out.println("吃虫子");
}
}
class Sheep extends Animal {
public Sheep(String name, int age) {
super(name, age);
}
@Override
public void toDo() {
super.drink();
System.out.println("吃草");
}
}
# 接口
接口是用来约束一个类的,接口当中的成员都是抽象的,接口当中的成员都是公共的
# 接口当中成员的特点
# 成员变量
- 只能是常量
- 默认修饰符:
public static final
# 构造方法
没有
# 成员方法
- 只能是抽象方法
- 默认修饰符:
public abstract - jdk7以前:接口中只能定义抽象方法
# 接口跟类之间的关系
# 类和类之间的关系
继承关系,只能单继承,不能多继承,但是可以多层继承
# 类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
# 接口和接口的关系
继承关系i可以单继承也可以多继承
public class textSport {
public static void main(String[] args) {
//创建篮球教练
BasketballCoach basketballCoach = new BasketballCoach("张三", 30, "篮球");
basketballCoach.teach();
//创建乒乓球教练
TennisCoach tennisCoach = new TennisCoach("李四", 40, "乒乓球");
tennisCoach.teach();
tennisCoach.speak();
//创建乒乓球运动员
TennisPlear tennisPlear = new TennisPlear("王五", 20, "乒乓球");
tennisPlear.study();
tennisPlear.speak();
//创建篮球运动员
BasketballPlear basketballPlear = new BasketballPlear("赵六", 25, "篮球");
basketballPlear.study();
}
}
//人员
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//教练
class Coach extends Person {
String teamName;
public Coach(String name, int age, String teamName) {
super(name, age);
this.teamName = teamName;
}
public void teach() {
System.out.println(name + "教" + teamName);
}
}
//运动员
class Plear extends Person {
String teamName;
public Plear(String name, int age, String teamName) {
super(name, age);
this.teamName = teamName;
}
public void study() {
System.out.println(name + "学" + teamName);
}
}
//篮球教练
class BasketballCoach extends Coach {
public BasketballCoach(String name, int age, String teamName) {
super(name, age, teamName);
}
}
//乒乓球教练
class TennisCoach extends Coach implements speakEn {
public TennisCoach(String name, int age, String teamName) {
super(name, age, teamName);
}
@Override
public void speak() {
System.out.println(name + "说英语");
}
}
//篮球运动员
class BasketballPlear extends Plear {
public BasketballPlear(String name, int age, String teamName) {
super(name, age, teamName);
}
}
//乒乓球运动员
class TennisPlear extends Plear implements speakEn {
public TennisPlear(String name, int age, String teamName) {
super(name, age, teamName);
}
@Override
public void speak() {
System.out.println(name + "说英语");
}
}
# Jdk8以后接口中新增的方法
# 允许在接口中定义默认方法,需要使用关键字default修饰
作用:解决接口升级问题
# 接口中默认方法的定义格式
- 格式:
public default 返回值类型 方法名(参数列表){方法体} - 举例:
public default void method(){System.out.println("123")
# 接口中默认方法的注意事项
- 默认方法不是抽象方法,所以不强制被重写,但是如果被重写,重写的时候去掉
default关键字 public可以省略,但不可以省略default- 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
public interface inter {
//接口中的默认方法,可以重写也可以不重写
public default void show() {
System.out.println("我是默认方法----");
}
}
# 允许在接口中定义静态方法,需要使用关键字static修饰
# 接口中静态方法的定义格式
- 格式:
public static 返回值类型 方法名(参数列表){方法体} - 举例:
public static void method(){System.out.println("静态方法");}注意
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
public可以省略,但不可以省略static
# jdk9以后接口中新增的私有方法
使用private修饰可以
# 接口中私有方法的定义格式
格式:
private 返回值类型 方法名(参数列表){方法体}举例:
private void method(){System.out.println("私有方法");}格式2:
private static 返回值类型 方法名(参数列表){方法体}举例2:
private static void method(){System.out.println("私有静态方法");}
注意
如果方法是静态方法,那么私有方法必须是静态的,静态私有方法是给静态方法服务的
//接口中的私有方法
public interface interA {
public default void method1() {
System.out.println("默认方法1");
//调用接口中的私有方法
method3();
}
public default void method2() {
System.out.println("默认方法2");
//调用接口中的私有方法
method3();
}
//提取接口中公共部分 组成一个私有方法
private void method3() {
System.out.println("这里有100行代码");
}
}
# 接口总结
- 接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了
- 当一个方法的参数是接口时,可以传递接口所有的实现类对象,这种方式称之为接口的多态
# 适配器模式
- 当一个接口中抽象方法过多,但是但是我只要使用其中一部分的时候,就可以使用适配器设计模式
# 书写步骤
- 编写中间类
XXXadapter,实现对应的接口 - 对接口中的抽象方法空实现
- 让真正的实现类继承中间类,并重写需要用的方法
- 为避免其他类创建适配器的对象,中间的适配器类用
abstract修饰
//适配器设计模式
public interface interB {
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
abstract class interBadapter implements interB {
@Override
public void method1() {
}
@Override
public void method2() {
}
@Override
public void method3() {
}
@Override
public void method4() {
}
@Override
public void method5() {
}
}
class classB extends interBadapter {
public void method1() {
System.out.println("重写方法");
}
}
# 内部类
# 类的五大成员
属性、方法、构造方法、代码块、内部类
# 什么是内部类
- 在一个类的里面再定义一个类,那么这个类就叫做内部类
- 内部类表示的是外部类的一部分
- 内部类单独出现没有意义
# 内部类的访问特点
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
class classB extends interBadapter {
public void classB() {
//外部类要想访问内部类必须创建对象
classB1 b1 = new classB1();
b1.show();
}
public void method1() {
System.out.println("重写方法");
}
//内部类
class classB1 {
String name;
int age;
public void show() {
System.out.println("内部类的定义");
}
}
}
# 内部类的分类
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类(重点掌握)
# 成员内部类
- 写在成员位置的,属于外部类成员
- 成员内部类可以被一些修饰符所修饰,比如
private、默认、protected、public、static等
修饰符权限
- 默认权限 只能在本包中使用
private私有 只能在成员内部使用protected受保护的 只能在本包和子类中使用public公共的 可以在任何地方使用static静态 只能在静态内部类中使用
public class main {
public static void main(String[] args) {
//方法一 直接创建成员内部类的对象
OutSide.InSide inside = new OutSide().new InSide();
//方法二 在外部类中使用函数导出成员内部类
Object inside1 = new OutSide().getInSide();
}
}
class OutSide {
String name;
class InSide {
String age;
}
//向外导出成员内部类的对象
public InSide getInSide() {
return new InSide();
}
}
# 成员内部类变量的获取
class OutSide {
number a = 10;
class InSide {
number a = 20;
public void show() {
number a = 30;
System.out.println(a);//30
//this表示内部类对象
System.out.println(this.a);//20
//OutSide.this表示成员内部类所创建的对象
System.out.println(OutSide.this.a);//10
}
}
}
# 静态内部类
- 写在成员位置的,属于外部类成员
- 被
static修饰,只能访问外部类的静态成员的内部类被称为静态内部类 - 如果想要访问非静态的需要创建对象
创建静态内部类对象的格式
- 外部类名.内部类名 对象名 = new 外部类名.内部类名(); 调用静态方法格式
- 外部类名.内部类名.静态方法名();
public class main {
public static void main(String[] args) {
//创建静态内部类对象
OutSide.InSide inSide = new OutSide.InSide();
//调用静态方法
OutSide.InSide.getInSide();
}
}
class OutSide {
String name;
static class InSide {
static String age;
//向外导出成员内部类的对象
static void getInSide() {
// System.out.println(age);//静态只能访问静态
System.out.println("内部类成员访问");
}
public void getInSide2() {
System.out.println(age);
}
}
}
# 局部内部类
- 将内部类定义在方法里面就叫局部内部类,类似于方法里面的局部变量
- 外界是无法直接使用,需要在方法内部直接创建对象并使用
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
public class main {
public static void main(String[] args) {
OutSide2 outSide2 = new OutSide2();
outSide2.showMed();
}
}
//局部内部类
class OutSide2 {
public void showMed() {
//写在方法中的内部类被称为局部内部类
class InSide {
int age = 10;
public void getInSide() {
System.out.println("局部内部类方法被调用了");
}
}
InSide inSide = new InSide();
System.out.println(inSide.age);
inSide.getInSide();
}
}
# 匿名内部类
- 隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置
- 匿名内部类格式:
new 类名或接口名(){重写方法}
# 格式细节
- 包含了继承或实现,方法重写,创建对象
- 整体就是一个类的子类对象或者接口的实现类对象
# 使用场景
当方法的参数是接口或者类时,以接口为例可以传递这个接口的实现类对象,如果实现类只要使用一次,就可以使用匿名内部类简化代码
public class main {
public static void main(String[] args) {
//继承匿名内部类
new OutSide2() {
//继承需实现父类中所有的实现方法
@Override
public void showMed1() {
System.out.println("showMed1");
}
};
//接口匿名内部类
new textInter() {
//实现接口中的方法
@Override
public void show() {
System.out.println("show");
}
};
//传递接口或实现类的对象简化代码
show(new textInter() {
@Override
public void show() {
System.out.println("传递接口或实现类的对象简化代码");
}
});//传递接口或实现类的对象简化代码
}
public static void show(textInter a) {
a.show();
}
}
abstract class OutSide2 {
public abstract void showMed1();
}