多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。其中:
- 多个类可以称为子类,也叫派生类,
- 单独那一个类称为父类、超类或者基类。
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的 非私有 的属性和行为。
❗️注意:
- 子类其实是可以继承父类的私有成员的,只是不可以直接访问
- 对于父类的静态成员,子类可以直接访问,但是这并不是继承,而是本来就是共享的
通过 extends
关键字,可以声明一个子类继承另外一个父类,继承的格式如下:
class 父类名 {
...
}
class 子类名 extends 父类名 {
...
}
继承的作用:
💨 继承的应用场景:
is..a
的关系,不能盲目使用继承
is..a
的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类继承的设计规范:
1️⃣ 子类可以继承父类的属性和方法,但是子类不能继承父类的构造器(子类有自己的构造器,父类构造器用于初始化父类对象)
2️⃣ Java 中所有的类都是 Object 类的子类
3️⃣ Java 只支持单继承,不支持多继承,但 支持多层继承
4️⃣ 继承中成员变量的访问特点
在子类方法中访问一个变量(满足就近原则):
🙋举个栗子:
父类
public class Fu {
int num = 10;
}
子类
public class Zi extends Fu {
int num = 20;
public void method() {
int num = 30;
System.out.println(num); // 30,局部变量
System.out.println(this.num); // 20,本类的成员变量
System.out.println(super.num); // 10,父类的成员变量
}
}
测试类
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
}
}
❗️ 注意:
- 如果子父类当中,出现了重名的成员变量,通过就近原则,会优先使用子类的,如果一定要使用父类的,可以通过
super
关键字进行区分
- 局部变量: 直接写成员变量名
- 本类的成员变量:
this.成员变量名
- 父类的成员变量:
super.成员变量名
5️⃣ 继承中成员方法的访问特点
通过子类对象访问一个方法(满足就近原则)
🙋举个栗子:
父类
public class Fu {
public void show(){
System.out.println("父类的show方法");
}
}
子类
public class Zi extends Fu {
public void show(){
System.out.println("子类的show方法");
}
public void method(){
this.show();
super.show();
}
}
测试类
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.show();
// 子类的show方法
zi.method();
// 子类的show方法
// 父类的show方法
}
}
6️⃣ 继承中构造方法的访问特点
子类中所有的构造方法都会默认访问父类中的无参构造方法,再执行自己
super();
,这句代码被系统隐藏了,通过这句代码访问父类的无参构造方法如果我们编写的类,没有手动指定父类,系统也会自动继承 Object类(Java体系中最顶层父类)
🙋举个栗子:
父类 Person
public class Person extends Object {
private String name;
private int age;
public Person(){
// super();
System.out.println("我是父类的空参数构造方法");
}
public Person(String name, int age){
// super();
this.name = name;
this.age = age;
System.out.println("我是父类的带参数构造方法");
}
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;
}
}
子类 Student
public class Student extends Person {
// 子类自己特有的属性.
private int score;
public Student(){
// super();
System.out.println("我是子类的空参数构造方法..........");
}
public Student(int score){
// super();
this.score = score;
System.out.println("我是子类的带参数构造方法!!!");
}
/*public Student(String name,int age,int score){
super(name,age); // 初始化父类的变量
this.score = score; // 初始化自己本类的变量
System.out.println("我是子类的带参数构造方法!!!");
// super调用父类有参构造器的作用是什么?初始化继承自父类的数据
}*/
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
测试类
public class Test {
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(100);
// Student stu3 = new Student("微微",21,100);
// System.out.println(stu3.getName()+"\t"+stu3.getAge()+"\t"+stu3.getScore());
}
}
结果输出:
我是父类的空参数构造方法
我是子类的空参数构造方法..........
我是父类的空参数构造方法
我是子类的带参数构造方法!!!
内存图解
父类中成员变量
name
,age
都是私有的,为什么能够通过super(name,age);
继承到呢?
- 父类中私有内容子类的确不可以直接访问,但是并不意味着子类继承不到。
❓问:如果父类中没有空参构造方法,只有带参数的构造方法,会报错(因为子类默认是调用父类的无参构造器的),该如何解决?
super(...)
访问父类的带参构造方法public class Test2 {
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
int age;
// 空参数构造方法
/*public Fu(){
System.out.println("父类空参数构造方法");
}*/
// 带参数构造方法
public Fu(int age){
this.age = age;
}
}
class Zi extends Fu {
/* 父类中没有空参构造方法,而子类构造方法中默认会通过super();访问父类的无参构造方法,因此会报错
public Zi(){
}*/
public Zi(){
// 由于super和this不能共存,所以此时子类的空参构造中不存在super();语句
this(10); // 访问本类的带参构造方法
}
public Zi(int age){
super(age); // 该构造方法又通过该语句手动调用父类的带参构造
}
}
❗️ 注意:
- 一个Java文件中允许存在多个class,但是这多个class中只能有一个被public修饰且该class类名必须和文件名保持一致
this(…)
和super(…)
都必须放在构造方法的第一行有效语句,并且二者不能共存
在继承体系中,子类出现了和父类中一模一样(方法的名称一样,参数列表也一样)的方法声明,我们就称子类这个方法就是重写的方法
方法重写的应用场景:
🙋举个栗子:
父类
public class Fu {
public void method(){
System.out.println("说中文");
}
}
子类
public class Zi extends Fu{
/*
在idea中输入方法名再回车即可自动生成重写方法,
@Override用于检查当前的方法是否是一个正确的重写方法,
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
*/
@Override
public void method(){
super.method();
System.out.println("说外文");
}
}
测试类
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
// 说中文
// 说外文
}
}
❗️注意:
- 重写方法的名称、参数列表必须和被重写方法的名称、参数列表一致
- 父类中私有方法不能被重写(父类私有成员子类是不能直接访问的)
- 子类不可以重写父类的静态方法(如果重写,添加
@Override
验证会报错)
- 子类是可以继承父类的静态方法的,但是无法重写它们。这是因为静态方法是与类而不是与实例相关联的。当子类定义了与父类相同名称的静态方法时,并不会覆盖父类的静态方法,而是隐藏它。当我们通过子类的实例调用该方法时,编译器会根据实际类型来确定调用哪个静态方法。如果使用子类的实例调用静态方法,那么将调用子类自己定义的静态方法;如果使用父类的引用调用静态方法,那么将调用父类的静态方法。
- 子类重写父类的方法时,其 访问权限 必须 大于等于父类
包是用来分门别类的管理各种不同类的,类似于文件夹、建包有利于程序的管理和维护
建包的语法格式:
package 公司域名倒写.技术名称 // 包名建议全部英文小写,且具备意义
例如:
package com.baidu.javabean
关于导包:
import 包名.类名;
🙋举个栗子:
❗️注意:
- 建包语句必须在第一行,一般IDEA会自动帮忙创建
Java中有四种权限修饰符:
public | protected | (default) | private | |
---|---|---|---|---|
同一个类 | YES | YES | YES | YES |
同一个包 | YES | YES | YES | NO |
不同包子类 | YES | YES | NO | NO |
不同包非子类 | YES | NO | NO | NO |
❗️注意:
- (default)并不是关键字“default”,而是根本不写。
- 权限大小:
public
>protected
>(default)>private
- 编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private
,隐藏细节- 构造方法使用
public
,方便创建对象- 成员方法使用
public
,方便调用方法
final
关键字是最终的意思,可以修饰(方法、变量、类)
🙋举个栗子:
public static void main(String[] args) {
final int A = 10;
final int MAX = 10;
final int MAX_VALUE = 20;
final Student stu = new Student();
stu.setName("张三");
stu.setName("李四");
// stu = new Student(); stu 是引用数据类型的变量,让stu重新记录一份内存地址
class Student {
// 初始化时机一
final int a = 10;
private String name;
// 初始化时机二
/*final int a;
public Student(){
a =10;
}*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
常量是使用了 public static final
修饰的成员变量,必须有初始化值,而且执行的过程中其值不能改变
常量的作用和好处:
常量的执行原理:
❗️ 注意:
- 常量的命名规范:如果是一个单词,所有字母大写,如果是多个单词,所有字母大写,但是中间需要使用
_
分隔
枚举是Java中的一种特殊类型,其作用是为了做信息的标志与分类
定义枚举类的格式:
修饰符 enum 枚举名称{
第一行都是罗列枚举实例的名称
}
例如:
public enum Season{
SPRING,SUMMER,AUTUMN,WINTER;
}
通过javac Season.java
进行编译得到Season.class
,再对Season.class
进行反编译javap Season.class
❤️ 反编译后观察枚举的特征:
java.lang.Enum
😦 选择常量、枚举做信息标志和分类的区别
- 常量:虽然可以实现可读性,但是入参值不受约束,代码相对不够严谨
- 枚举:代码可读性好,入参约束严谨,代码优雅,是最好的信息分类技术,建议使用
在Java中abstract
是抽象的意思,可以修饰类、成员方法。
将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法。
abstract
修饰方法,这个类就是抽象方法,其格式为:
修饰符 abstract 返回值类型 方法名 (参数列表);
abstract
修饰类,这个类就是抽象类,其格式为:
abstract class 类名称 { }
❗️ 注意:
- 抽象方法只有方法声明,没有方法体
- 如果一个类中存在抽象方法,该类就必须声明为抽象类。
案例需求
实现步骤
Animal 类
public abstract class Animal { // 该类中有抽象方法故必须定义为抽象类
public void drink(){
System.out.println("喝水");
}
public abstract void eat(); // 定义的抽象方法
}
Dog 类
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
Cat 类
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
Test 类
public static void main(String[] args) {
Dog d = new Dog();
d.eat(); // 狗吃肉
d.drink(); // 喝水
Cat c = new Cat();
c.drink(); // 喝水
c.eat(); // 猫吃鱼
//Animal a = new Animal(); // 抽象类不能创建对象
//a.eat();
}
❗️注意:
- 类有的成员(成员变量、方法、构造器)抽象类都具备
- 抽象类不能实例化(创建对象)
- 抽象类的子类, 必须重写父类中所有的抽象方法,否则可以将自己也变成一个抽象类
- 抽象类中,可以是抽象方法也可以是非抽象方法,甚至可以一个抽象方法都不写;但是有抽象方法的类一定是抽象类
- 不能用
abstract
修饰变量、构造器、代码块
✨ 拓展:
final
和abstract
是什么关系?
- 互斥关系
- 修饰类:
abstract
定义的抽象类作为模版让子类继承,而final
定义的类是不能被继承的- 修饰方法:抽象方法定义通用功能让子类重写,而
final
定义的方法子类不能重写
设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
模板设计模式
把抽象类整体就可以看做成一个模板,模板中不能决定的东西定义成抽象方法
让使用模板的类(继承抽象类的类)去重写抽象方法实现需求
模板设计模式的优势
模板已经定义了通用结构,模板方法不能确定的部分定义成抽象方法,交给子类实现,使用者只需要关心自己需要实现的功能即可,同时提高了代码的复用性
模板设计模式的使用场景
当系统中出现同一个功能多处在开发,而该功能大部分代码是一样的,只有其中部分可能不同的时候
🙋举个栗子:
作文模板类
public abstract class CompositionTemplate {
// 模版方法
public final void write(){
System.out.println("<<我的爸爸>>");
// 抽象方法
body();
System.out.println("啊~ 这就是我的爸爸");
}
public abstract void body();
}
模版方法使用
final
修饰会更专业、更安全,防止子类重写
实现类
public class Tom extends CompositionTemplate {
@Override
public void body() {
System.out.println("那是一个秋天, 风儿那么缠绵,记忆中, " +
"那天爸爸骑车接我放学回家,我的脚卡在了自行车链当中, 爸爸蹬不动,他就站起来蹬...");
}
}
测试类
public class Test {
public static void main(String[] args) {
Tom t = new Tom();
t.write();
}
}
当一个类中的 所有方法都是抽象方法 的时候,我们就可以将其定义为 接口,接口也是一种引用数据类型(数组、类、接口),它比抽象类还要抽象。
接口,是方法的集合,如果说类的内部封装了成员变量、成员方法和构造方法,那么 接口的内部主要就是封装了方法,包含常量、抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。
接口的存在意义:
接口的定义与定义类方式相似,但是使用 interface
关键字,其格式如下:
public interface 接口名{}
它不能创建对象(实例化),但是可以被实现,使用implements
关键字 ,类似于被继承,格式如下:
public class 类名 implements 接口名{}
一个实现接口的类(可以看做是接口的子类)
🙋举个栗子:
接口类
public interface Inter {
public abstract void study();
// 由于接口体现规范思想,规范默认都是公开的,所以代码层面public abstract可以省略不写
// void study();
}
实现类
public class InterImpl implements Inter {
@Override
public void study() {
System.out.println("我是实现类中的study方法");
}
测试类
public class TestInterface {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.study(); // 我是实现类中的study方法
}
}
❗️注意:
- 接口,也是一种规范,规范一定是公开的。
- Java中不支持多继承,但是支持多实现
public class 类名 implements 接口1,接口2{}
- 定义接口时换成了关键字
interface
之后,编译生成的字节码文件仍然是:.java
—>.class
1️⃣ 成员变量(常量)
public
、static
(可以以通过接口名.
调用成员变量)、final
,这几个关键字可以省略不写2️⃣ 成员方法(抽象方法)
只能是抽象方法,系统会默认加入两个关键字public
、abstract
,这几个关键字可以省略不写
❗️注意:
- 对于“接口中的成员方法只能是抽象方法”只在JDK7以及之前的版本是正确的
1️⃣ 类与类
2️⃣ 类与接口
🙋举个栗子:
父类
public class Fu {
public void show(){
System.out.println("Fu...show");
}
}
接口类
public interface Inter {
public default void show(){
System.out.println("Inter....show");
}
}
实现类与测试类
public class TestInterface {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.show(); // Fu...show
}
}
class InterImpl extends Fu implements Inter {
}
3️⃣ 接口与接口
作用:规范合并,合并多个接口中的功能到同一个接口中,便于子类实现(只需要实现一个接口就可以了)
🙋举个栗子:
接口A类
public interface InterA {
public abstract void showA();
public default void method(){
System.out.println("InterA...method方法");
}
}
接口B类
public interface InterB {
public abstract void showB();
public default void method(){
System.out.println("InterB...method方法");
}
}
接口C类
// InterA,InterB出现了相同的方法声明method(),但是代码逻辑不一样,子类不知道该继承谁,自己重写该方法
public interface InterC extends InterA , InterB {
@Override
public default void method() {
System.out.println("InterC接口,解决代码逻辑冲突问题, 重写method方法");
}
}
实现类
// 实现类需要重写接口类中所有的抽象方法
public class InterImpl implements InterC {
@Override
public void showA() {
}
@Override
public void showB() {
}
}
测试类
public class TestInterface {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.method(); // InterC接口,解决代码逻辑冲突问题, 重写method方法
}
}
❓ 问题:当对接口丰富,加入多个新的抽象方法,那么接口的实现类也需要对新加入的方法进行重写,如何能在丰富接口功能的同时又不对实现类代码进行更改?
❤️ 解答:允许接口中定义带有方法体的方法
在JDK8版本之后,Java只对接口的成员方法进行了改进
default
修饰,这些方法就是默认方法(解决接口升级问题)static
,即静态方法1️⃣ 默认方法
类似于之前写的普通实例方法,只是在这里必须使用default
修饰,需要用接口的实现类的对象来调用。
接口中默认方法的定义格式:
public default 返回值类型 方法名(参数列表){}
❗️注意:
- 接口中的默认方法的权限修饰符都是
public
,就算不写,系统也会默认加上,但是default
关键字不能省略- 默认方法不是抽象方法,所以不强制被重写,但是可以被重写,实现类重写接口中的默认方法时,要去掉
default
关键字- 一个类如果实现了多个接口,多个接口存在同名的默认方法,不冲突,该类必须对该方法进行重写即可
2️⃣ 静态方法
接口中静态方法的定义格式:
public static 返回值类型 方法名(参数列表){}
❗️注意:
- 接口的静态方法只能通过本身的接口名调用,不能通过实现类名或者实现类对象名调用
- 所以如果一个类实现多个接口,多个接口中有同名的静态方法并不冲突
public
同样可以省略,但是static
不能省略
当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9 增加私有方法的必然性。
只能在本类(本接口)中使用。
接口中私有方法的定义格式:
// 格式1
private 返回值类型 方法名(参数列表){}
// 格式2
private static 返回值类型 方法名(参数列表){}
🙋举个栗子:
接口类
public interface Inter {
public default void start() {
System.out.println("start方法执行了...");
log();
}
public default void end() {
System.out.println("end方法执行了...");
log();
}
private void log(){
System.out.println("日志记录 ( 模拟 )");
}
private static void check(){
System.out.println("权限校验 ( 模拟 )");
}
public static void open() {
check();// 静态方法只能访问静态成员
System.out.println("open方法执行了");
}
public static void close(){
check();
System.out.println("close方法执行了");
}
}
实现类和测试类
public class TestInterface {
public static void main(String[] args) {
InterImpl ii = new InterImpl();
ii.start();
ii.end();
Inter.open();
Inter.close();
}
}
class InterImpl implements Inter {
}