面向对象特点:
面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。 面向对象的语言中,包含了三大基本特征,即封装、继承和多态
面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的 方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性,提高了代码的复用性。
使用 private 关键字来修饰成员变量。
private 数据类型 变量名;
(2)对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法
/*问题描述:定义Person的年龄时,无法阻止不合理的数值被设置进来。
解决方案:用private关键字将需要保护的成员变量进行修饰。
一旦使用了private进行修饰,那么本类当中仍然可以随意访问。
但是!超出了本类范围之外就不能再直接访问了。
间接访问private成员变量,就是定义一对儿Getter/Setter方法
*/
public class Person {
String name; // 姓名
private int age; // 年龄
public void show() {
System.out.println("我叫:" + name + ",年龄:" + age);
}
// 这个成员方法,专门用于向age设置数据
public void setAge(int num) {
if (num < 100 && num >= 9) { // 如果是合理情况
age = num;
} else {
System.out.println("数据不合理!");// 此时age取默认值0
}
}
// 这个成员方法,专门用于获取age的数据
public int getAge() {
return age;
}
}
public class DemoPerson {
public static void main(String[] args) {
Person person = new Person();
person.show();
person.name = "赵丽颖";
// person.age = -20; // 直接访问private内容,错误写法!
person.setAge(20);
person.show();
}
}
注意:
对于基本类型当中的boolean值,Getter方法一定要写成isXxx的形式,而setXxx规则不变。
当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。
如果需要访问本类当中的成员变量,需要使用格式:
this.成员变量名
public class Person {
String name; // 我自己的名字
// 参数name是对方的名字
// 成员变量name是自己的名字
public void sayHello(String name) {
System.out.println(name + ",你好。我是" + this.name);
System.out.println(this);// 地址值
}
}
public class DemoPerson {
public static void main(String[] args) {
Person person = new Person();
// 设置我自己的名字
person.name = "王健林";
person.sayHello("王思聪");
// 可以得出,this和person的地址值一致
System.out.println(person); // 地址值
}
}
注意:
this一定在方法内部,this所在方法被哪个对象调用,this就代表哪个对象
构造方法是专门用来创建对象的方法,当我们通过关键字new来创建对象时,其实就是在调用构造方法。
public 类名称(参数类型 参数名称) {
方法体
}
注意:
void都不写return一个具体的返回值Java Beanprivate关键字修饰Getter/Setter方法public class Student {
// 成员变量
private String name;
private int age;
// 无参数的构造方法
public Student() {
System.out.println("无参构造方法执行啦!");
}
// 全参数的构造方法
public Student(String name, int age) {
System.out.println("全参构造方法执行啦!");
this.name = name;
this.age = age;
}
// Getter Setter
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
public class DemoStudent {
public static void main(String[] args) {
Student stu1 = new Student(); // 无参构造
System.out.println("============");
Student stu2 = new Student("赵丽颖", 20); // 全参构造
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
// 如果需要改变对象当中的成员变量数据内容,仍然还需要使用setXxx方法
stu2.setAge(21); // 改变年龄
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
}
}
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
其中,多个类可以称为子类,也叫派生类,单独那一个类称为父类、超类或者基类。
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
在父子类的继承关系当中,如果子类父类中出现不重名的成员变量或成员方法,这时的访问是没有影响的。
(1)成员变量重名
如果父类与子类的成员变量重名,则创建子类对象时,访问有两种方式:
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super关键字:
super.父类成员变量名
注意:
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
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();
}
}
(2)成员方法重名
在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就优先用谁,如果没有则向上找。
注意:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。
(3)构造方法
public class Fu {
public Fu() {
System.out.println("父类无参构造");
}
public Fu(int num) {
System.out.println("父类有参构造");
}
}
public class Zi extends Fu {
public Zi() {
super(); // 在调用父类无参构造方法
// super(20); // 在调用父类重载的构造方法
System.out.println("子类构造方法!");
}
public void method() {
// super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
}
}
public static void main(String[] args) {
Zi zi = new Zi();
}
继承关系中,父子类构造方法的访问特点:
super()调用,所以一定是先调用的父类构造,后执行的子类构造。子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。
总结:继承的三个特点
super关键字的用法有三种:
- 在子类的成员方法中,访问父类的成员变量。
- 在子类的成员方法中,访问父类的成员方法。
- 在子类的构造方法中,访问父类的构造方法。
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
- 在本类的成员方法中,访问本类的成员变量。
- 在本类的成员方法中,访问本类的另一个成员方法。
- 在本类的构造方法中,访问本类的另一个构造方法。 在第三种用法当中要注意: A. this(…)调用也必须是构造方法的第一个语句,唯一一个。
B. super和this两种构造调用,不能同时使用。
(1)抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
修饰符 abstract 返回值类型 方法名 (参数列表);
public abstract void run();
(2)抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。
abstract class 类名字 {
}
public abstract class Animal {
public abstract void run();
}
(3)抽象的使用
public abstract class Animal {
// 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
public abstract void eat();
// 这是普通的成员方法
public void normalMethod() {
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
// Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象
Cat cat = new Cat();
cat.eat();
}
}
注意:
super(),需要访问父类构造方法。重写(Override):在继承关系当中,方法的名称一样,参数列表也【一样】。覆盖、覆写。
重载(Overload):方法的名称一样,参数列表【不一样】。
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。
注意:
必须保证父子类之间方法的名称相同,参数列表也相同。
@Override:写在方法前面,用来检测是不是有效的、正确的覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
子类方法的返回值必须【小于等于】父类方法的返回值范围。
小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。
子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > (default) > private
备注:(default)不是关键字default,而是什么都不写,留空。
群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:
请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。
public class User {
private String name; // 姓名
private int money; // 余额,也就是当前用户拥有的钱数
public User() {
}
public User(String name, int money) {
this.name = name;
this.money = money;
}
// 展示一下当前用户有多少钱
public void show() {
System.out.println("我叫:" + name + ",我有多少钱:" + money);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
// 群主的类
public class Manager extends User {
public Manager() {
}
public Manager(String name, int money) {
super(name, money);
}
public ArrayList<Integer> send(int totalMoney, int count) {
// 首先需要一个集合,用来存储若干个红包的金额
ArrayList<Integer> redList = new ArrayList<>();
// 首先看一下群主自己有多少钱
int leftMoney = super.getMoney(); // 群主当前余额
if (totalMoney > leftMoney) {
System.out.println("余额不足");
return redList; // 返回空集合
}
// 扣钱,其实就是重新设置余额
super.setMoney(leftMoney - totalMoney);
// 发红包需要平均拆分成为count份
int avg = totalMoney / count;
int mod = totalMoney % count; // 余数,也就是甩下的零头
// 除不开的零头,包在最后一个红包当中
// 下面把红包一个一个放到集合当中
for (int i = 0; i < count - 1; i++) {
redList.add(avg);
}
// 最后一个红包
int last = avg + mod;
redList.add(last);
return redList;
}
}
// 普通成员
public class Member extends User {
public Member() {
}
public Member(String name, int money) {
super(name, money);
}
public void receive(ArrayList<Integer> list) {
// 从多个红包当中随便抽取一个,给我自己。
// 随机获取一个集合当中的索引编号
int index = new Random().nextInt(list.size());
// 根据索引,从集合当中删除,并且得到被删除的红包,给我自己
int delta = list.remove(index);
// 当前成员自己本来有多少钱:
int money = super.getMoney();
// 加法,并且重新设置回去
super.setMoney(money + delta);
}
}
import java.util.ArrayList;
public class MainRedPacket {
public static void main(String[] args) {
Manager manager = new Manager("群主", 100);
Member one = new Member("成员A", 0);
Member two = new Member("成员B", 0);
Member three = new Member("成员C", 0);
manager.show(); // 100
one.show(); // 0
two.show(); // 0
three.show(); // 0
System.out.println("===============");
// 群主总共发20块钱,分成3个红包
ArrayList<Integer> redList = manager.send(20, 3);
// 三个普通成员收红包
one.receive(redList);
two.receive(redList);
three.receive(redList);
manager.show(); // 100-20=80
// 6、6、8,随机分给三个人
one.show();
two.show();
three.show();
}
}
(1)接口的概述
接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、成员方法和构造方法,那么 接口的内部主要就是封装了方法,包含常量、抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。
接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
注意:
引用数据类型:数组,类,接口。
(2)接口的定义格式
public interface 接口名称 {
// 接口内容
}
注意:
换成了关键字interface之后,编译生成的字节码文件仍然是:.java —> .class
(3)接口的使用
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类 似继承,格式相仿,只是关键字不同,实现使用 implements关键字
① 抽象方法的使用
接口使用步骤:
接口不能直接使用,必须有一个“实现类”来“实现”该接口。
格式:
public class 实现类名称 implements 接口名称 {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【可选】
}
接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
实现:去掉abstract关键字,加上方法体大括号。
不能直接new接口对象使用,所以接口没有构造方法,应该创建实现类的对象,进行使用。
注意:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
在任何版本的Java中,接口都能定义抽象方法
[public] [abstract] 返回值类型 方法名称(参数列表);
public interface MyInterface {
// 这是一个抽象方法
public abstract void methodAbs1();
// 这也是抽象方法
abstract void methodAbs2();
// 这也是抽象方法
public void methodAbs3();
// 这也是抽象方法
void methodAbs4();
}
注意:
public abstract,这两个关键字修饰符,可以选择性地省略② 默认方法的使用
从Java 8开始,接口里允许定义默认方法。
[public] default 返回值类型 方法名称(参数列表) {
方法体
}
注意:
public interface MyInterfaceDefault {
// 抽象方法
public abstract void methodAbs();
// 新添加了一个抽象方法
// public abstract void methodAbs2(); 子类若是没有重写该方法会报错
// 新添加的方法,改成默认方法,子类若是没有重写该方法不会报错
public default void methodDefault() {
System.out.println("这是新添加的默认方法");
}
public class MyInterfaceDefaultA implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,AAA");
}
}
public class MyInterfaceDefaultB implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,BBB");
}
@Override
public void methodDefault() {
System.out.println("实现类B覆盖重写了接口的默认方法");
}
}
public class DemoInterface {
public static void main(String[] args) {
// 创建了实现类对象
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
a.methodAbs(); // 调用抽象方法,实际运行的是右侧实现类。
// 调用默认方法,如果实现类当中没有,会向上找接口
a.methodDefault(); // 这是新添加的默认方法
System.out.println("==========");
MyInterfaceDefaultB b = new MyInterfaceDefaultB();
b.methodAbs();
b.methodDefault(); // 实现类B覆盖重写了接口的默认方法
}
}
③ 静态方法的使用
从Java 8开始,接口当中允许定义静态方法,但接口中不能有静态代码块 static {}
[public] static 返回值类型 方法名称(参数列表) {
方法体
}
注意:
abstract或者default换成static即可,带上方法体接口名称.静态方法名(参数);public interface MyInterfaceStatic {
public static void methodStatic() {
System.out.println("这是接口的静态方法!");
}
}
public class MyInterfaceStaticImpl implements MyInterfaceStatic {
}
public class Demo03Interface {
public static void main(String[] args) {
// 创建了实现类对象
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
// 错误写法!
// impl.methodStatic();
// 直接通过接口名称调用静态方法
MyInterfaceStatic.methodStatic();
}
}
④ 私有方法的使用
我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
但是这个共有方法不应该让实现类使用,应该是私有化的。private 的方法只有接口自己才可以调用,不能被实现类或被人调用
从Java 9开始,接口当中允许定义私有方法。
private 返回值类型 方法名称(参数列表) {
方法体
}
private static 返回值类型 方法名称(参数列表) {
方法体
}
⑤ 常量的使用
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的【常量】。
[public] [static] [final] 数据类型 常量名称 = 数据值;
注意:
public static final ,注意:不写也照样是这样。final 关键字进行修饰,说明不可改变接口名称.常量名称(4)接口的多实现
一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
// 覆盖重写所有抽象方法
}
注意:
(5)接口的多继承
类与类之间是单继承的。直接父类只有一个。
类与接口之间是多实现的。一个类可以实现多个接口。
接口与接口之间是多继承的。
public interface MyInterfaceA {
public abstract void methodA();
public abstract void methodCommon();
public default void methodDefault() {
System.out.println("AAA");
}
}
public interface MyInterfaceB {
public abstract void methodB();
public abstract void methodCommon();
public default void methodDefault() {
System.out.println("BBB");
}
}
/*
这个子接口当中有几个方法?答:4个。
methodA 来源于接口A
methodB 来源于接口B
methodCommon 同时来源于接口A和B
method 来源于我自己
*/
public interface MyInterface extends MyInterfaceA, MyInterfaceB {
public abstract void method();
@Override
public default void methodDefault() {
}
}
public class MyInterfaceImpl implements MyInterface {
@Override
public void method() {
}
@Override
public void methodA() {
}
@Override
public void methodB() {
}
@Override
public void methodCommon() {
}
}
注意:
(1)多态的概述
多态是继封装、继承之后,面向对象的第三大特性。
extends继承或者implements 实现,是多态性的前提。
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
多态: 是指同一行为,具有多个不同表现形式。
(2)多态的格式与使用
代码当中体现多态性,其实就是一句话:父类引用指向子类对象。
父类名称 对象名 = new 子类名称();
// 或者:
接口名称 对象名 = new 实现类名称();
public class Fu {
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
public class Zi extends Fu {
@Override
public void method() {
System.out.println("子类方法");
}
}
public class Demo {
public static void main(String[] args) {
// 使用多态的写法
// 左侧父类的引用,指向了右侧子类的对象
Fu obj = new Zi();
obj.method(); // 子类方法
obj.methodFu(); // 父类特有方法
}
}
注意:
左父右子就是多态,把子类当做父类来使用(一只猫被当作动物来看待),因为子类就是一个父类。
(3)多态中成员变量的使用
访问成员变量的两种方式:
public class Fu /*extends Object*/ {
int num = 10;
public void showNum() {
System.out.println(num);
}
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
public class Zi extends Fu {
int num = 20;
int age = 16;
@Override
public void showNum() {
System.out.println(num);
}
@Override
public void method() {
System.out.println("子类方法");
}
public void methodZi() {
System.out.println("子类特有方法");
}
}
public class Demo{
public static void main(String[] args) {
// 使用多态的写法,父类引用指向子类对象
Fu obj = new Zi();
System.out.println(obj.num); // 父:10
// System.out.println(obj.age); // 错误写法!
System.out.println("=============");
// 子类没有覆盖重写,就是父:10
// 子类如果覆盖重写,就是子:20
obj.showNum();
}
}
注意:
成员变量不可以覆盖重写,成员方法可以
(4)多态中成员方法的使用
在多态的代码当中,成员方法的访问规则是:看new的是谁,就优先用谁,没有则向上找。
口诀:编译看左边,运行看右边。
对比一下:
成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边。
public class Fu {
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
public class Zi extends Fu {
@Override
public void method() {
System.out.println("子类方法");
}
public void methodZi() {
System.out.println("子类特有方法");
}
}
public class Demo0{
public static void main(String[] args) {
Fu obj = new Zi(); // 多态
obj.method(); // 父子都有,优先用子
obj.methodFu(); // 子类没有,父类有,向上找到父类
// 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
// obj.methodZi(); // 错误写法!
}
}
(5)多态的好处
多态的转型分为向上转型与向下转型两种

(1)向上转型
多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的
父类类型 变量名 = new 子类类型();
// 如:Animal a = new Cat();
(2)向下转型
父类类型向子类类型向下转换的过程,这个过程是强制的
子类类型 变量名 = (子类类型) 父类变量名;
// 如:Cat c =(Cat) a;
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
// 子类特有方法
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
/*
向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端:
对象一旦向上转型为父类,那么就无法调用子类原本【特有】的内容。
解决方案:用对象的向下转型【还原】。
*/
public class Demo{
public static void main(String[] args) {
// 对象的向上转型,就是:父类引用指向之类对象。
Animal animal = new Cat(); // 本来创建的时候是一只猫
animal.eat(); // 猫吃鱼
// animal.catchMouse(); // 错误写法!
// 向下转型,进行“还原”动作
Cat cat = (Cat) animal;
cat.catchMouse(); // 猫抓老鼠
// 下面是错误的向下转型
// 本来new的时候是一只猫,现在非要当做狗
// 错误写法!编译不会报错,但是运行会出现异常:
// java.lang.ClassCastException,类转换异常
Dog dog = (Dog) animal;
}
}
(3)类型判断
如何才能知道一个父类引用的对象,本来是什么子类?
对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例
public class Demo02Instanceof {
public static void main(String[] args) {
Animal animal = new Cat(); // 本来是一只猫
animal.eat(); // 猫吃鱼
// 如果希望掉用子类特有方法,需要向下转型
// 判断一下animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
}