一、封装
1. 封装介绍
封装指的是属性私有化,即隐藏具体属性和实现细节,仅对外开放接口,控制程序中属性的访问级别;使用类设计对象时,将需要处理的数据以及处理这些数据的方法,设计到对象中。
封装目的:
a. 更好的维护数据。
b. 使用者无需关心内部实现,只要知道如何使用即可。
封装的设计规范:合理隐藏,合理暴露。
2. 权限修饰符
- private:同一个类中
public class Student {
private int age;
private void show(){
System.out.println(age);//在同一个类中才可以访问属性age
}
}
测试:
public class StudentTest {
public static void main(String[] args) {
Student stu = new Student();
stu.age; //报错,访问不到成员变量age
stu.show(); //报错,访问不到成员方法show()
}
}
- (defalut):同一个类中,同一个包中
- protected:同一个类中,同一个包中,不同包的子类(与继承相关)
- public:任意位置访问
权限修饰符可以修饰成员变量和成员方法,不同的权限修饰符修饰的权限访问大小是不一样的。
3. 私有成员变量的设置和获取
私有成员变量是为了保证数据的安全性,但是在主方法中却不能通过 对象名. 的方式来访问私有成员变量,所以这边针对私有成员变量,提供对应的 setXxx 和 getXxx 方法。
例如:
public class Student {
//私有成员变量
private int age;
//设置年龄的方法
public void setAge(int age){
//可以判别获取的年龄是否是合理数据
if (age > 0 & age < 150){
this.age = age;//给成员变量age赋值
}
else {
System.out.println("年龄有误!");
}
}
//返回年龄的方法
public int getAge(){
return age;
}
}
测试:
public class StudentTest {
public static void main(String[] args) {
Student stu = new Student();
stu.setAge(18);
int age = stu.getAge();
System.out.println(age);
}
}
运行结果为:
18
4. 标准 JavaBean
JavaBean 就是实体类,即封装数据的类。
这个类中的成员变量都要私有,并且要对外提供相应的 getXxx 和 setXxx 方法,此外该类中也需要提供无参、带参构造方法。
实体类的应用场景:实体类只负责数据存取,而对数据的处理交给其它类来完成,以实现数据和数据业务处理相分离。
例如:
public class Student {
//私有成员变量
private String name;
private int age;
//空参构造方法
public Student() {
}
//带参构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
}
二、继承
1. 继承介绍
让类与类之间产生关系(子父类关系),子类可以直接使用父类中非私有的成员。
继承的格式:
- 格式:public class 子类名 extends 父类名 { }
- 范例:public class Zi extends Fu { }
- Fu:是父类,也被称为基类、超类
- Zi:是子类,也被称为派生类
什么时候使用继承:
- 当类与类之间,存在相同(共性)的内容,并且产生了 is a 的关系,就可以考虑使用继承,来优化代码。
- 例如:
2. 继承中成员的访问特点
成员变量
子父类中,如果出现了重名的成员变量,使用的时候根据就近原则,优先使用子类的成员变量。
父类:
public class Fu {
int num = 10;
}
子类:
public class Zi extends Fu {
int num = 20;
public void method(){
System.out.println(num);
}
}
测试类:
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.method();//运行结果为20
}
}
但是如果一定要使用父类中的重名成员变量,可以使用 super 关键字:
public class Zi extends Fu {
int num = 20;
public void method(){
System.out.println(super.num);//此时测试类中再次调用该方法时运行的结果为10
}
}
成员方法
子类继承了父类的父类之后,子类仍然可以继续定义自己的方法。
子父类中,如果出现了方法声明一模一样的方法(方法名,参数,返回值),在创建子类对象,调用方法的时候,会优先使用子类的方法逻辑,这虽然是就近原则的现象,但其实是子类的方法对父类的方法进行了重写操作:
- 方法重写的前提:必须要存在继承的关系
- 方法重写:子类中,出现了同父类方法声明一模一样的方法(方法名,参数,返回值)
public class Test {
public static void main(String[] args) {
Son s = new Son();
s.method();//运行结果为 Son...method
}
}
class Father {
public void method(){
System.out.println("Father...method");
}
}
class Son extends Father {
public void method(){
System.out.println("Son...method");//方法重写
}
}
- 方法重写的使用场景:子类需要父类的方法,且想对父类的方法逻辑进行修改或者增强,就可以对父类的方法进行重写
public class Test {
public static void main(String[] args) {
Son s = new Son();
s.method();
/*
运行结果为:Father...method
Son...method
*/
}
}
class Father {
public void method(){
System.out.println("Father...method");
}
}
class Son extends Father {
public void method(){
super.method();//仍需要父类的方法
System.out.println("Son...method");//对父类的方法逻辑进行增强
}
}
- 方法重写的注意事项:
父类中私有方法不能被重写
子类重写父类方法时,访问权限必须大于等于父类
权限修饰符访问权限:
注意与方法重载区分:
方法重载的要求:
- 方法名称必须相同
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)
- 方法的返回类型可以相同也可以不相同
- 仅仅返回类型不同不足以成为方法的重载
构造方法
- 子类不能继承父类的构造方法,子类需要手动编写构造方法。
- 子类在初始化之前,需要先完成父类的初始化,其实在除了 Object 类以外的所有的构造方法中,都默认隐藏了一句代码: super(); 通过这句代码可以访问父类的空参构造方法。
- Java当中所有的类,都直接或者间接的继承了 Object 类。
下面通过内存图来理解:
首先主方法进入栈内存,接着创建对象 stu,通过 new 在堆内存中开辟了一块空间,该空间内部存放了 Student 类的成员变量 score,以及从 Person 类继承的成员变量 name 和 age,虽然 name 和 age 是 Person 类的私有成员变量,但是 Student 类可以继承,只是无法直接访问。
然后调用 Student 类的构造方法进行传值,Student 类的构造方法进入栈内存,其中 super(name,age)访问父类的带参构造方法,通过父类进行初始化,所以 Person 类的构造方法进入栈内存,以此来给成员变量 name 和 age 赋值。
之后 Person 类的构造方法弹栈消失,继续给 Student 类的成员变量 score 赋值,随后 Student 类的构造方法弹栈消失,至此堆内存中的所有变量都已经赋值完毕。
最后把堆内存中 Student 类开辟空间的地址交给栈内存中的对象 stu,这样通过 stu 就可以找到 Student 类的各个成员变量。
3. super 关键字
代表父类存储空间的标识
应用范围
只能用于子类的构造函数和实例方法中,不能用于子类的静态方法中。
why:因为 super 指代的是一个父类的对象,它需要在运行时被创建,而静态方法是类方法,它是类的一部分。当类被加载时,方法已经存在,但是这时候父类对象还没有被初始化。
应用场景
a. 在子类中调用父类的成员变量或成员方法: 如果子父类有相同的成员变量或者成员方法,那么想调用父类的成员变量或者成员方法,就必须使用 super 关键字。
b. 在子类中指代父类的构造方法: 在 Java 中,子类是父类的派生类,子类的实例化依赖于父类的实例化。所以它的任何一个构造函数都必须要初始化父类,Java 就是使用 super 关键字来调用父类的构造方法来完成这个操作。
例如:
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
public Fu() {
System.out.println("Fu...构造方法");
}
}
class Zi extends Fu {
public Zi() {
/*
隐藏代码 调用了父类的无参构造方法
super(); 必须要在子类构造方法的第一行
*/
System.out.println("Zi...构造方法");
}
}
运行结果为:
Fu...构造方法
Zi...构造方法
在子类的构造方法中,如果没有显式调用 super 来初始化父类的话,那么 Java 会隐式的调用 super();注意 Java 只会隐式的调用无参构造函数,如果父类没有无参构造函数,那么子类中就必须显示的调用 super 关键字来调用已有的有参构造函数来初始化父类。
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
public Fu(String name) {
System.out.println("Fu...有参构造方法");
}
}
class Zi extends Fu {
public Zi() {
super("name");//显式调用已有的有参构造函数来初始化父类
System.out.println("Zi...无参构造方法");
}
}
运行结果为:
Fu...有参构造方法
Zi...无参构造方法
super 调用父类成员的省略规则
super . 父类成员变量 | super . 父类成员方法 ( ) - - - > 被调用的变量和方法在子类中不存在,super.可以直接省略
this 和 super
super 注意点:
- super 调用父类的构造方法,必须在构造方法的第一个
- super 必须只能出现在子类的方法或者构造方法中
- super 和 this 不能同时调用构造方法 - - - > 两者不能共存
Vs this:
代表的对象不同:
- this:代表本类对象的引用
- super:代表父类对象的引用
前提不同:
- this:没有继承关系也可以使用
- super:只有在继承关系下才可以使用
构造方法不同:
- this( ):本类的构造方法
- super( ):父类的构造方法
三、抽象类和抽象方法
抽象类是一种特殊的父类,内部可以编写抽象方法。
1. 抽象类和抽象方法的介绍
- 抽象方法:将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法。
- 抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类。
2. 抽象类和抽象方法的定义格式
- 抽象方法的定义格式:public abstract 返回值类型 方法名(参数列表);
- 抽象类的定义格式:public abstract class 类名{ }
例如:定义一个员工类,员工类中存在一个 work 方法,由于不同的员工有着不同的工作,所以 work 方法可以定义为抽象方法,员工类也可以定义为抽象类。
public abstract class Employee {
public abstract void work();
}
程序员类:
public class Coder extends Employee {
//重写抽象方法
public void work() {
System.out.println("打代码");
}
}
项目经理类:
class Manger extends Employee {
//重写抽象方法
public void work() {
System.out.println("项目管理");
}
}
3. 注意事项
- 抽象类不能实例化
- 抽象类存在构造方法,这样子类通过 super 可以访问
- 抽象类中可以存在普通方法,可以让子类继承到从而继续使用
- 抽象类的子类:要么重写抽象类中的所有抽象方法;要么是抽象类
4. abstract 关键字的冲突
- final:被 abstract 修饰的方法,强制要求子类重写,被 final 修饰的方法子类不能重写
- private:被 abstract 修饰的方法,强制要求子类重写,被 private 修饰的方法子类不能重写
- static:被 static 修饰的方法可以类名调用,类名调用抽象方法没有意义
四、接口
1. 接口的介绍
体现的思想是对规则的声明,Java 中的接口更多体现的是对行为的抽象。
如果一个类没有成员变量,没有普通方法,只有抽象方法,一般将该类设计为接口。
- 接口用关键字 interface 来定义
具体的定义格式为:interface 接口名 { } - 接口不能实例化,接口和类之间是实现关系,通过 implements 关键字来表示
具体的定义格式为:public class 类名 implements 接口名 { } - 实现类(接口的子类)需要重写接口中的所有抽象方法
例如定义一个接口:
interface Inter {
public abstract void show();
public abstract void method();
}
实现类:
public class InterImpl implements Inter {//重写所有的抽象方法
@Override
public void show() {
System.out.println("show...");
}
@Override
public void method() {
System.out.println("method...");
}
}
测试类:
public class InterfaceDemo01 {
public static void main(String[] args) {
InterImpl i = new InterImpl();
i.show();
i.method();
}
}
运行结果为:
show...
method...
2. 接口中的成员特点
- 成员变量:只能是常量,默认修饰符为 public static final
- 构造方法:没有
- 成员方法:只能是抽象方法,默认修饰符为 public abstract
3. 类和接口之间的关系
- 类和类的关系:继承关系,只能单继承,但是可以多层继承
- 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
- 接口和接口的关系:继承关系,可以单继承,也可以多继承
4. 抽象类和接口的对比
- 成员变量:
抽象类:可以定义变量,也可以定义常量
接口:只能定义常量 - 成员方法:
抽象类:可以是定义具体方法,也可以定义抽象方法
接口:只能定义抽象方法 - 构造方法:
抽象类:有
接口:没有
五、多态
1. 多态介绍
同一个行为具有多个不同表现形式或形态的能力
多态前提:
- 有继承或者实现关系
- 有方法重写
- 有父类引用指向子类对象
2. 对象多态
方法的形参可以定义为父类类型,那么该方法就可以接收到该父类的任意子类对象
通过例子来理解,首先创建一个动物 Animal 类,内部包含一个抽象方法 eat 方法:
public abstract class Animal {
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("猫吃鱼");
}
}
测试类:
public class PolymorphismDemo01 {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());//对象多态
}
public static void useAnimal(Animal a) {
/*
相当于Animal a = new Dog()
等号左边是父类引用
等号右边是子类对象 --->满足多态的前提
*/
}
}
3. 行为多态
同一个行为具有多个不同表现形式或形态的能力
仍以对象多态的例子来说明,修改测试类:
public class PolymorphismDemo01 {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());
}
public static void useAnimal(Animal a) {
a.eat();//同一个行为具有多个不同表现形式或形态的能力
}
}
运行结果为:
狗吃肉
猫吃鱼
4. 多态的成员访问特点
- 成员变量:编译看左边(父类),运行看左边(父类)
- 成员方法:编译看左边(父类),运行看右边(子类)
例如:
public class PolymorphismDemo02 {
public static void main(String[] args) {
Fu f = new Zi();//父类的引用指向子类的对象
/*
多态的成员访问成员变量:
编译时看父类是否存在变量num,如果不存在则报错
运行时输出的是父类中num的值
*/
System.out.println(f.num);
/*
多态的成员访问成员方法:
编译时看父类是否存在方法show,如果不存在则报错
运行时执行子类的show的方法逻辑
*/
f.show();
}
}
class Fu {
int num = 10;
public void show() {
System.out.println("Fu...show");
}
}
class Zi extends Fu {
int num = 20;
public void show() {
System.out.println("Zi...show");
}
}
运行结果为:
10
Zi...show
需要注意,多态创建对象,调用静态成员的情况:
因为静态的成员推荐类名进行调用,所以在生成字节码文件后,会自动将对象名调用,改成类名调用,所以谁访问的静态成员,就执行谁的代码逻辑。
6. 多态的好处和弊端
- 多态的好处:提高了程序的扩展性
- 多态的弊端:不能使用子类的特有成员(因为编译时需要看父类是否具有该成员)
7. 多态中的转型
多态中的转型分为向上转型和向下转型两种
-
向上转型:多态本身就是向上转型的过程
- 使用格式:父类类型 变量名 = new 子类类型(); - - - > Fu f = new Zi();
-
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用各类型
- 使用格式:子类类型 变量名 =(子类类型) 父类类型的变量; - - - > Zi z = (Zi) f;
- 适用场景:当要使用子类特有功能时
多态中的转型问题:如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现 ClassCastException
解决方法就是使用 instanceof 关键字
8. instanceof 关键字
- 使用格式:对象名 instanceof 类型
- 判断一个对象是否是一个类的实例(即判断关键字左边的对象,是否是右边的类型)
- 注意: 返回类型为布尔类型
例如:
public class Application {
public static void main(String[] args) {
// Object > String
// Object > Person > Teacher
// Object > Person > Student
Object object = new Student();
System.out.println(object instanceof Student);//true
System.out.println(object instanceof Person);//true
System.out.println(object instanceof Object);//true
System.out.println(object instanceof Teacher);//false
System.out.println(object instanceof String);//false
System.out.println("==================================");
Person person = new Student();
System.out.println(person instanceof Student);//true
System.out.println(person instanceof Person);//true
System.out.println(person instanceof Object);//true
System.out.println(person instanceof Teacher);//false
// System.out.println(person instanceof String); 编译报错!
System.out.println("==================================");
Student student = new Student();
System.out.println(student instanceof Student);//true
System.out.println(student instanceof Person);//true
System.out.println(student instanceof Object);//true
// System.out.println(student instanceof Teacher); 编译报错!
// System.out.println(student instanceof String); 编译报错!
}
}
class Person{}
class Student extends Person{}
class Teacher extends Person{}
运行结果为:
true
true
true
false
false
==================================
true
true
true
false
==================================
true
true
true