类的封装继承和多态
** **封装、继承和多态是面向对象编程的三大编程。
封装,把对象的属性和方法结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口。
继承,从已知的一个类中派生出一个新的类,叫子类。子类实现了父类所有非私有化的属性和方法,并根据实际需求扩展出新的行为。
多态,多个不同的对象对同一消息作出响应,同一消息根据不同的对象而采用各种不同的方法。
类的封装
** **封装的目的是为了保障变量的安全性,使得使用者在使用的时候不必要在意实现的细节,只需要通过外部接口来访问类的成员,如果不进行封装,那么就可以通过创建类实列来创建实例,可能会修改影响到其他代码,所以说在编写类时就会将成员变量私有化,在外部通过Getter和Setter来查看和设置即可;
** ⭐️将Cat类中的成员变量变为**private
,这样在外部就访问不到了
package com.company;
import sun.awt.windows.WPrinterJob;
public class Cat {
private String name;
private String sex;
private int age;
private static String info;
public Cat(String name,String sex,int age){
this.name = name;
this.sex = sex;
this.age = age;
}
}
这样在外部就不能直接通过类对象来获取和修改,但是可以使用get和set来获取和修改
package com.company;
import sun.awt.windows.WPrinterJob;
public class Cat {
private String name;
private String sex;
private int age;
private static String info;
public Cat(String name,String sex,int age){
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getInfo() {
return info;
}
public static void setInfo(String info) {
Cat.info = info;
}
}
⭐️也可以通过修改set函数,对外界修改进行一些额外的操作,如下,使得外界调用修改 name
时不能修改包含有"猪"这个字
public void setName(String name) {
if(name.contains("猪")) return;
this.name = name;
}
可以看到修改失败
⭐️还可以限制构造方法,将构造方法改为私有,只能通过内部的方式来构造对象
public class Cat {
private String name;
private String sex;
private int age;
private static String info;
private Cat() {
}
public static Cat create(){
return new Cat();
}
public String getName() {
return name;
}
类的继承
** **有时候定义很多类,它们都会有相同的属性,我们可以将这些相同的属性共同组成一个父类,子类可以继承自父类但是不能访问父类的私有成员变量
** **类的继承就相当于你和你老爸,你的很多是不是都是继承你的老爸的,性格啊、外貌啊、等等,当然有些东西是你爸爸自己独有的你继承不了,比如说你爸爸的年龄你肯定继承不了吧
** **还有一种就是人和我们自己,每个人的相同特征是不是都是有一个名字,有年龄,还有性别,这是人类共有的,但是你也会有属于你自己的东西,比如说你的职业,你的性格等等,就可以看作是两个类,一个类叫做人,一个类叫做学生,学生是继承人的特性的,那么学生也会有自己的成员变量,比如在哪里读书,成绩怎么样等等
这是一个父类
public class Person{
String name;
String sex;
int age;
}
⭐️可以通过extends关键字来继承父类,创建了一个学生类继承了 Person
public class Student extends Person {
}
⭐️类可以不断继续向下衍生,但是每个子类只能继承一个类,如果不想这个类被继承需要将类标记为 final
package com.company;
public final class Person{
String name;
String sex;
int age;
}
前面我们说过,子类无法访问到父类的私有变量,但是是已经继承了但是无法访问
⭐️如果父类有一个全参构建方法,需要参数才可以创建对象,那么在子类中是无法直接继承的
public class Person{
String name;
String sex;
int age;
public Person(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
}
** ⭐️因为子类在构建的时候,不仅仅要初始化子类的属性,还需要初始化父类的属性,在默认的情况下子类调用了父类的构建方法,但是如果在全参的情况下,就需要手动指定使用**super
,super
就表示父类 this
表示当前类
public class Student extends Person {
public Student(String name, String sex, int age) {
super(name, sex, age);
}
}
** **我们在使用子类时,是可以当作父类去使用的,在父类定义一个 hello
方法
package com.company;
public class Person{
String name;
String sex;
int age;
public void hello() {
System.out.println("我是父类的Hello!");
}
}
这里我用父类的类型,去应引用了一个子类的类型,但是这个子类的类型是访问到父类的东西的
⭐️但是这里不是直接变成父类了,仅仅是当作父类使用而已,可以使用强制类型转换来转换成子类(但是我觉得并没有任何意义)
public class Main {
public static void main(java.lang.String[] args) {
// write your code here
Person person = new Student();
person.hello();
Student student = (Student) person;
student.hello();
}
}
⭐️如果我们需要判断这个变量所引用的对象到底是什么类可以使用 instanceof
来判断
public class Main {
public static void main(java.lang.String[] args) {
// write your code here
Person person = new Student();
if (person instanceof Person) {
out.println("是Person类");
}
if (person instanceof Student) {
out.println("是Student类");
}
}
}
⭐️如果子类和父类存在于同名的成员变量,那么就可以使用到 super
来访问
package com.company;
public class Person{
public String name = "白白白";
String sex;
int age;
public void hello() {
System.out.println("我是父类的Hello!");
}
}
⭐️子类在子类中使用 super
来调用父类的名字
package com.company.zy01;
import com.company.Person;
public class Student extends Person {
String name="黑黑黑";
public void outputString() {
System.out.println("我的父类叫"+super.name);
System.out.println("我叫"+this.name);
}
}
输出
顶层Object类
⭐️所有的类默认都是默认继承了 Object
类,可以看到提示冗余
如下图所示,加粗的都是类本身自带的方法,而不加粗的都是继承自 Object
类
可以来看看这个类里面有哪些内容
public class Object {
private static native void registerNatives(); //标记为native的方法是本地方法,底层是由C++实现的
static {
registerNatives(); //这个类在初始化时会对类中其他本地方法进行注册,本地方法不是我们SE中需要学习的内容,我们会在JVM篇视频教程中进行介绍
}
//获取当前的类型Class对象,这个我们会在最后一章的反射中进行讲解,目前暂时不会用到
public final native Class<?> getClass();
//获取对象的哈希值,我们会在第五章集合类中使用到,目前各位小伙伴就暂时理解为会返回对象存放的内存地址
public native int hashCode();
//判断当前对象和给定对象是否相等,默认实现是直接用等号判断,也就是直接判断是否为同一个对象
public boolean equals(Object obj) {
return (this == obj);
}
//克隆当前对象,可以将复制一个完全一样的对象出来,包括对象的各个属性
protected native Object clone() throws CloneNotSupportedException;
//将当前对象转换为String的形式,默认情况下格式为 完整类名@十六进制哈希值
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//唤醒一个等待当前对象锁的线程,有关锁的内容,我们会在第六章多线程部分中讲解,目前暂时不会用到
public final native void notify();
//唤醒所有等待当前对象锁的线程,同上
public final native void notifyAll();
//使得持有当前对象锁的线程进入等待状态,同上
public final native void wait(long timeout) throws InterruptedException;
//同上
public final void wait(long timeout, int nanos) throws InterruptedException {
...
}
//同上
public final void wait() throws InterruptedException {
...
}
//当对象被判定为已经不再使用的“垃圾”时,在回收之前,会由JVM来调用一次此方法进行资源释放之类的操作,这同样不是SE中需要学习的内容,这个方法我们会在JVM篇视频教程中详细介绍,目前暂时不会用到
protected void finalize() throws Throwable { }
}
方法的重写
** **方法的重写是为了将之前原本的方法实现,来重写方法,使得达到自己想要的效果,上面我们提到过,任何类都会继承Object类,那我们可以使用重写的方法来修改一下这个父类的方法,接下来我们使用代码来重写一下 equals
方法,在 Object
中提到了 equals
方法是检测两个对象是否相等
** **但是如果是以下情况就不能,他们两名字一样,性别一样,年龄也一样,我们可以重写 equals
方法是他判断成员变量是否相等,如果相等就表示他们相等
重写方法,在 Person
类中
public class Person{
...
@Override //重写方法可以添加 @Override 注解,有关注解我们会在最后一章进行介绍,这个注解默认情况下可以省略
public boolean equals(Object obj) { //重写方法要求与父类的定义完全一致
if(obj == null) return false; //如果传入的对象为null,那肯定不相等
if(obj instanceof Person) { //只有是当前类型的对象,才能进行比较,要是都不是这个类型还比什么
Person person = (Person) obj; //先转换为当前类型,接着我们对三个属性挨个进行比较
return this.name.equals(person.name) && //字符串内容的比较,不能使用==,必须使用equals方法
this.age == person.age && //基本类型的比较跟之前一样,直接==
this.sex.equals(person.sex);
}
return false;
}
}
再次使用就是返回 true
了
⭐️在类中,有时候为了方便我们查看各个成员变量对应的值,就可以重写 toString
方法,这个是可以通过 ide
快捷生成的
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
⚠️ 静态类是不能被重写但是可以被继承
** **基于这种方法,在一个父类中定义了一个行为(方法),不同的子类会表现出不同的方法,比如在人这个类中定义了一个收入,学生和工人的收入应该是会不一样的,所以说: 对于一个类的行为,不同的子类可以出现不同的行为
这其实就是多态特性的一种体现
public class Person {
....
public void income(){ //定义了收入行为
}
}
在两个类中重写 income
方法
@Override
public void income() {
System.out.println("我是学生,我在学习我没有收入");
}
@Override
public void income() {
System.out.println("我是工人,我每个月有4000的工资");
}
在 Main
函数调用
⭐️那么如果不希望重写这个方法,就可以假如 final
关键字,表示这个方法已经是最终形态了;
public final void income(){
}
** ⭐️还有一种方法就是将父类的方法直接设置为private,那么子类也同样无法访问,也不能重写了,但是子类可以不加注解,直接创建一个新的同名方法**
public class Person {
....
private void income(){
}
}
将 Override
注解去除
⭐️如果需要在子类中调用父类的方法,还是可以使用 super
字段
@Override
public void income() {
super.income();
System.out.println( "我也是学生,我在学习我没有收入");
}
}
⭐️子类的重写权限可以别父类大,但是不能比父类低
protected void income(){ //父类中的方法是protected权限
System.out.println("我是人类");
}
修改子类中的方法,可以看到如果我们将子类变成默认权限,就会提示权限报错
抽象类
⭐️在Java中,抽象是指无法直接实例化的类和没有具体实现的方法。
- 抽象类(Abstract Class):抽象类是一种特殊的类,它不能被直接实例化。它的目的是为了被其他类继承,并提供一组公共的接口或部分实现供子类使用或扩展。抽象类通常包含抽象方法和具体方法:
- 抽象方法:在抽象类中定义但没有具体实现的方法,它们只有声明而没有方法体。这些方法必须在任何直接或间接继承该抽象类的子类中被重写(实现)。
- 具体方法:抽象类也可以包含已经实现了的具体方法,这些方法可以在子类中直接使用,或者根据需要进行重写。
- 抽象方法(Abstract Method):抽象方法是一种没有实现的方法,只有方法签名,即方法名、参数列表和返回类型。它们必须在继承了抽象类的任何非抽象子类中被实现。
⭐️之前我们说过多态就是一个父类里面有一个行为方法,但是在继承它的不同子类的方法就会有不同的结果,这个行为方法是一点会被重写在子类中实现的,这个行为方法完全就是可以在子类中进行实现,父类不需要提供实现的,就可以使用抽象类,使用 abstract
定义为抽象类
public abstract class Person { //将Person类定义为抽象类
}
⭐️ 抽象类和普通类的区别在于,抽象类可以拥有抽象方法,前面说过抽象方法不需要在父类中去实现,而是要在子类中去实现,所以说抽象类中的抽象方法是不能有主体的;
public abstract class Person { //将Person类定义为抽象类
public abstract void income(); //定义抽象类
}
⭐️ 子类如果继承的是一个抽象类,则必须实现抽象类中的抽象方法
public class Worker extends Person {
@Override
public void income() { //实现抽象方法
System.out.println("我也是是工人,我每个月有4000的工资");
}
}
⭐️ 抽象类不同于普通类可以直接创建类对象,因为抽象类可能会存在某些方法没有实现(比如抽象方法需要在子类中实现),所以说无法直接通过 new
来创建对象,所以说要使用抽象类就需要使用子类来创建
⭐️ 抽象类一般是做继承使用,子类也可以是一个抽象类,如果子类也是一个抽象类,那么它可以不去实现父类的抽象方法,可以看到下图并没有报错
⭐️ 抽象类中不是只能由抽象方法,也可以有正常方法
public abstract class Student extends Person {
public void hello(){
System.out.println("Hello 我是Student类的正常方法");
}
}
⭐️ 抽象方法不能为 private
,抽象方法一定是需要子类实现的,如果子类都实现不了,那就没有什么意义了
接口
** **接口和类不一样,它是一些方法的集合,接口包含了一类方法的定义,类可以实现这个接口,表示类支持接口代表的功能(类似于一个插件,只能作为一个附属功能加在主体上,同时具体实现还需要由主体来实现)
** **比如对于人的不同的职业,比如老师和学生,他们都具有学习的能力,就可以将学习作为一个结构来进行使用,只要是这个接口的类就都回有学习的能力
public interface Study {
public void study(); //接口内的方法也不可以创建本体,具体和抽象方法类似,但是可以使用默认方法,在下面会说到
}
之后就可以让类来调用这个接口了(需要在创建一个 Teacher
类),使用关键字 implement
来继承类,⭐️如果接口类有方法的话,接口和继承抽象类拥一样必须需要实现否则会报错,如果不想实现,可以将类设置为抽象类,或者
public class Teacher implements Study{
}
public class Teacher implements Study{
@Override
public void study() {
System.out.println("我是老师,我会在教书的过程中边教边学");
}
}
public class Student extends Person implements Study{
@Override
public void study(){
System.out.println("我是学生,我在读书的过程中学习");
}
}
⭐️接口不同于继承,接口可以连接多个
public interface Run { //在创建一个Run接口
public void run();
}
学生类在继承一个 Run
接口
public class Student extends Person implements Study,Run{
@Override
public void study(){
System.out.println("我是学生,我在读书的过程中学习");
}
public void run(){
System.out.println("我会跑步");
}
}
⭐️ 学到这里我觉得这个和继承差不多,但是要比继承更加强大,如果是普通继承的话,就不是必须要实现父类中的行为,而抽象类和接口则必须实现,但是接口可以连接多个,而继承则只能继承一个
⭐️ 接口和抽象类一样不可以直接创建对象,但是可以将接口的实现类的对象(就是连接了接口的类),以接口的形式去使用
⭐️ 接口也支持向连接了它的类对象强转,如下代码所示,但是我觉得非常多余,当然只需要知道就好
public class Main {
public static void main(String[] args) {
// write your code here
Study stu = new Student();
if(stu instanceof Student){
Student s1 = (Student) stu;
stu.study();
}
}
}
⭐️ 从 Java8
开始,接口就可以存在方法的默认实现,默认方法就不需要强制要求实现
public interface Run {
public void run();
default void test1(){
System.out.println("我是默认实现方法1");
}
default void test2(){
System.out.println("我是默认实现方法2");
}
}
public class Main {
public static void main(String[] args) {
// write your code here
Student stu = new Student();
Teacher tea = new Teacher(); //这里需要将Teacher类连接Run接口
stu.test1();
tea.test2();
}
}
⭐️ 接口不允许存在成员变量和成员方法,但是可以存在静态变量和静态方法
public interface Study {
String course_1 = "物理"; //我这里没有写static,默认就是static
public void study();
public static void sta(){
System.out.println("我是接口的静态方法");
}
}
可以看到默认就是 public static final
和普通的类中的一样,可以直接通过接口名来调用
public class Main {
public static void main(String[] args) {
// write your code here
Study.sta();
System.out.println("Study.course_1");
}
}
⭐️ 接口是可以继承于接口的,但是接口的继承可以继承多个,接口继承接口相当于两个接口的继承
public interface Run extends Study{
...
}
⭐️ 在上面提到 object
类提供了一个克隆方法,它是需要接口才可以使用的,需要创建 Cloneable
接口
public interface Cloneable { //不需要定义任何方法
}
由类连接接口
public class Student extends Person implements Study,Run,Cloneable{ //连接接口
}
还需要将 clone
方法的权限范围修改一下,这就需要使用到重写
package com.company.zy01;
public class Student extends Person implements Study,Run,Cloneable{
@Override
public Object clone() throws CloneNotSupportedException { //提升clone方法的访问权限
return super.clone(); //因为底层是C++实现,我们直接调用父类的实现就可以了
}
}
然后使用,报错了
枚举类
一个学生肯定会有,它的一个状态的,比如睡觉,吃饭,学习这几个状态,那么这个状态就可以被定义为一个枚举类,主要是枚举类它可以提示你支持什么;
public class Student extends Person implements Study,Run,Cloneable{
private String status ;
}
然后创建这个成员变量的枚举类
package com.company.zy01;
public enum Status {
STUDY,SLEEP,EAT
}
这样就可以知道我们这个成员变量支持哪些参数了
枚举类型使用起来就非常方便了,其实枚举类型的本质就是一个普通的类,但是它继承自 Enum
类,我们定义的每一个状态其实就是一个 public static final
的Status类型成员变量:
//这里使用javap命令对class文件进行反编译得到 Compiled from "Status.java"
public final class com.test.Status extends java.lang.Enum<com.test.Status> {
public static final com.test.Status RUNNING;
public static final com.test.Status STUDY;
public static final com.test.Status SLEEP;
public static com.test.Status[] values();
public static com.test.Status valueOf(java.lang.String);
static {};
}
⭐️ 也可以给枚举的普通类
public enum Status {
RUNNING("睡觉"), STUDY("学习"), SLEEP("睡觉"); //无参构造方法被覆盖,创建枚举需要添加参数(本质就是调用的构造方法)
private final String name; //枚举的成员变量
Status(String name){ //覆盖原有构造方法(默认private,只能内部使用!)
this.name = name;
}
public String getName() { //获取封装的成员变量
return name;
}
}
这样,枚举就可以按照我们想要的中文名称打印了:
public static void main(String[] args) {
Student student = new Student("小明", 18, "男");
student.setStatus(Status.RUNNING);
System.out.println(student.getStatus().getName());
}