Java基础入门篇
三、面向对象和JVM底层分析
3.4 面向对象的三大特征
面相对象的三大特征:封转、继承、多态。
(一)封装
“封装”(encapsulation),封装的理念是高内聚,低耦合。
- “高内聚”:封装细节,便于修改内部代码,提高代码可维护性
- “低耦合”:简化外部调用,便于使用者使用,便于扩展和协作
封装的优点:提高代码的安全性,提高代码的复用性
封装的实现——使用访问控制符,Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露。Java中的访问控制符:private、default、protected、public,访问权限:
- private(同一个类可用————类内友好)
- default(不定义默认就是default,同一个类、同一个包可用————包内友好)
- protected(同一个类、同一个包、子类可用————父子友好、包内友好)
- public(同一个类、同一个包、子类、所有类可用————全局友好)
若protected——若父类和子类“在同一个包中”,子类可以访问父类的protected成员,也可以访问父类new创建对象的protected成员。若protected——若父类和子类“不在同一个包中”,子类可以访问父类的protected成员,不可以访问父类new创建对象的protected成员。
package BasicJava.encapsulation.a;
/*
* 测试封装
*/
public class Person {
private int testPrivate;
int testDefault;
protected int testProtected;
public int testPublic;
public void test(){
System.out.println(this.testPrivate);
}
}
package BasicJava.encapsulation.a;
/*
* 测试封装
*/
// 同一个包内的不同类
public class Student {
public void study(){
Person p = new Person();
// System.out.println(p.testPrivate); // 无权访问
System.out.println(p.testDefault);
System.out.println(p.testProtected);
System.out.println(p.testPublic);
}
}
// 同一个包内具有继承关系的类
class Student2 extends Person{
public void study(){
Person p = new Person();
// System.out.println(p.testPrivate); // 无权访问
System.out.println(p.testDefault);
System.out.println(p.testProtected);
System.out.println(p.testPublic);
}
}
开发过程中封装的简单规则:
- 属性一般使用private,私有后提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的(注意:boolean变量的get方法是is开头的)。
- 方法:一些只用于本类的辅助方法可以用private修饰,希望其他类调用的方法可以用public修饰。
package BasicJava.encapsulation.b;
import BasicJava.encapsulation.a.Person;
/*
* 测试封装
*/
public class Child extends Person {
public void play(){
Person p = new Person();
// 此时由于是不同包的继承,所以不能使用父类对象的protected修饰的属性/方法
//System.out.println(p.testProtected);
System.out.println(super.testProtected); // 但是可以访问父类的protected成员
}
}
package BasicJava.encapsulation.b;
/*
* 测试封装的简单规则
*/
public class User {
private int id;
private String name;
private boolean man;
// private类型的属性通过get/set方法调用
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMan() {
return man;
}
public void setMan(boolean man) {
this.man = man;
}
}
package BasicJava.encapsulation.b;
/*
* 测试封装的简单规则
*/
public class TestUser {
public static void main(String[] args) {
User u = new User();
u.setId(100);
u.setName("小明");
u.setMan(true);
System.out.println(u.getId());
System.out.println(u.getName());
System.out.println(u.isMan());
}
}
(二)继承
当我们不显示指定extends时,默认继承自父类java.lang.Object类,继承的作用:
-
代码复用,更加容易实现类的扩展,
-
方便建模,子类是父类的扩展
instanceof
运算符是二元运算法,左边是对象,右边是类,用于判断当前对象是否是右边类或子类所创建的对象,是则返回true,反之返回false。
继承中父类又叫超类、基类,子类又叫派生类。Java中只有单继承,不像C++存在多继承,多继承会使得继承链过于复杂,系统难以维护引起混乱。Java中类没有多继承但是接口有多继承,子类继承父类,可以得到父类全部的属性和方法(除了父类的构造方法),但是不见得可以直接访问,例如父类中的私有属性/方法。
/*
* 测试继承
*/
public static class Person{
String name;
int height;
public void rest(){
System.out.println("休息一会!");
}
}
public static class Student extends Person{
String school;
public Student(String name, int height, String school){
this.name = name;
this.height = height;
this.school = school;
}
public void study(){
System.out.println("学习!");
}
}
public static void testExtends(){
Student s1 = new Student("小明", 175, "HBU");
s1.study();
s1.rest();
System.out.println(s1 instanceof Student);
System.out.println(s1 instanceof Person);
}
super可以看作是堆父类对象的直接引用,可以通过super来访问父类中被子类覆盖的方法/属性。使用super调用普通方法,语句没有位置限制,可以在子类中任意使用。在一个类中,若是构造方法第一行没有调用super(…)或者this(…),那么Java默认调用super(),含义是调用父类的无参构造方法。
/*
* 使用super调用父类方法
*/
public static class FatherClass{
int value;
void f(){
value = 100;
System.out.println("FatherClass's value is : " + value);
}
}
public static class ChildClass extends FatherClass{
int value;
int age;
void f(){
// 调用父类的普通方法
super.f();
value = 200;
System.out.println("ChildClass's value is : " + value);
System.out.println(super.value);
}
}
public static void testSuper(){
ChildClass c1 = new ChildClass();
c1.f();
}
}
方法重写override
与方法重载overload
毫无关系。重写是指子类重写父类的方法可以用自身的行为替换父类的行为,重写是实现“多态”的必要条件。方法重写需要符合以下三个要点:
-
“==”方法名、形参列表相同
-
“≤”返回值类型和声明异常类型,子类小于等于父类
-
“≥”访问权限,子类大于等于父类
除了继承,**“组合”**也可以实现代码的复用,组合的核心是将父类对象作为子类的属性。组合就是在子类中new一个父类对象,然后通过对象名+属性/方法的方式使用父类。和继承相比,组合更加灵活,继承只能有一个父类,但是组合可以new多个类,使用他们的属性。对于is-a的关系建议使用“继承”(Student is a Person),对于has-a的关系建议使用“组合”(Student has a bike)。
/*
* 测试方法重写override
*/
public static class Vehicle{
public void run(){
System.out.println("跑!");
}
public Vehicle getVehicle(){
System.out.println("给你一个交通工具!");
return null;
}
}
public static class Horse extends Vehicle{
@Override
public void run() {
System.out.println("这是一匹马跑起来了!");
}
@Override
public Horse getVehicle() {
return new Horse();
}
}
public static void testOverride(){
Horse h = new Horse();
h.run();
h.getVehicle();
}
/*
* 实现组合
*/
public static class Car{
String name;
int number;
Car(){
System.out.println("还没初始化");
}
Car(String name, int number){
this.name = name;
this.number = number;
}
public void run(){
System.out.println(this.name + this.number + "出发!");
}
}
public static class Bike{
// 组合就是在子类中new一个父类对象,然后通过对象名+属性/方法的方式使用父类
Car c = new Car();
int wheel;
public Bike(int wheel, String name, int number){
this.wheel = wheel;
this.c.name = name;
this.c.number = number;
}
public void letCarGo(){
System.out.println(this.c.name + this.c.number + "出发!");
}
public void letBikeGo(){
System.out.println("这是一个"+ this.wheel + "轮车,出发!");
}
}
public static void testCombine(){
Car c1 = new Car("奔驰", 1001);
c1.run();
Bike b1 = new Bike(3, "丰田", 1002);
b1.letCarGo();
b1.letBikeGo();
}
属性/方法查找顺序,例如,查找变量h -> 查找当前类中有没有属性h -> 依次上溯每个父类 -> 查找每个父类中是否有h,直到Object -> 如果没有找到,则出现编译错误。
构造方法调用顺序,构造方法第一句总是super(…)来调用父类对应的构造方法,流程是:现象上追溯到Object类 -> 然后再次依次向下执行类的初始化块和构造方法 -> 直到当前子类为止。
静态初始化块的执行顺序和构造方法调用顺序一致。
/*
* 感受构造函数初始化顺序
*/
class FatherClass2{
//定义静态初始化块
static{
System.out.println("静态初始化块:FatherClass2");
}
// 无参构造器
FatherClass2(){
System.out.println("创建FatherClass2");
}
}
class ChildClass2 extends FatherClass2{
//定义静态初始化块
static{
System.out.println("静态初始化块:ChildClass2");
}
ChildClass2(){
// 默认情况下是调用父类的无参构造器,等同于super();
System.out.println("创建ChildClass2");
}
}
(三)多态
“多态”(polymorphism):同一个方法调用,不同对象行为完全不同
多态的要点:
- 多态是方法的多态,与属性无关
- 多态的存在要有三个必要条件(继承、方法重写、父类引用指向子类对象)
- 父类引用指向子类对象后,用该父类引用调用子类重写的方法,就是多态
package BasicJava.polymorphism;
/*
* 测试多态
*/
public class Animal {
public void shout(){
System.out.println("动物的叫声");
}
}
class Dog extends Animal{
@Override
public void shout() {
System.out.println("汪汪汪!");
}
public void seeDoor(){
System.out.println("正在看门...");
}
}
class Cat extends Animal{
@Override
public void shout() {
System.out.println("喵喵喵!");
}
public void catchMouse(){
System.out.println("正在抓老鼠...");
}
}
对象的转型(casting):由父类引用指向子类对象的过程中,我们称之为“向上转型”,属于“自动类型转换”。向上转型后的父类引用变量只能调用它“编译类型”的方法,不能调用它“运行时类型”的方法,这时我们需要进行强制类型转换,我们称之为“向下转型”。
package BasicJava.polymorphism;
public class TestPoly {
public static void main(String[] args) {
animalShout(new Dog()); // 相当于Animal a = new Dog(); 属于“向上转型”
animalShout(new Cat()); // 相当于Animal a = new Cat(); 属于“向上转型”
// new Dog()是我们真正的类型,Animal animal是我们定义的类型,因此无法执行Dog()类中的seeDoor()方法
//编译类型 运行时类型
Animal animal = new Dog();
animal.shout(); // 可以执行
// animal.seeDoor(); //无法执行
// 通过“向下转型”就可以了,这里的d其实就是animal,只不过进行了强制类型转换
Dog d = (Dog)animal;
d.seeDoor();
// 我们可以添加一个判断来避免异常
if(animal instanceof Dog){
Dog d2 = (Dog)animal;
d2.seeDoor();
}
if(animal instanceof Cat){
Cat c2 = (Cat)animal;
c2.catchMouse();
}
// 测试用不同的类进行“向下转型”的强制类型转换
Cat c = (Cat)animal;
c.catchMouse(); // 发现并不报错,但是这实际上只是编译时不报错,但是运行的时候会报错的
// "ClassCastException: BasicJava.polymorphism.Dog cannot be cast to BasicJava.polymorphism.Cat"
}
static void animalShout(Animal a){
a.shout();
}
}