文章目录
- 封装
- 什么是封装?
- 如何实现封装?
- 继承
- 什么是继承?
- 继承的语法
- 父类成员访问
- 子类访问父类的成员变量
- 子类访问父类的成员方法
- 认识super关键字
- 认识final关键字
- 子类的构造方法
- super VS this
- 在继承关系中各代码块的执行顺序是怎样的?
- Java支持的继承的方式
- 继承 VS 组合
- 多态
- 什么是多态?
- 什么是重写?
- 重写 VS 重载
- 向上转型和向下转型
- 向上转型
- 向下转型
封装
什么是封装?
什么是封装呢? 简单来说就是套壳屏蔽细节。
比如:对我们使用的电脑来说,在它运行过程中是有很多的部件相互配合共同执行的。但对于我们使用者而言只需要使用鼠标和键盘就可以操作电脑让它工作。 这其中就是套壳的思想,电脑把它的硬件资源全部套壳(封装起来),只留出几个接口供用户使用就可以和计算机交互。
封装的标准定义:将数据和操作数据的方法有机结合,隐藏对象的属性和实现细节,仅通过对外的公开接口来和对象进行交互。
如何实现封装?
Java中主要通过类和访问权限来实现封装。
类可以将数据和封装数据的方法结合在一起。
访问权限用来控制方法和字段能否直接在类外使用。
Java中提供了四种访问限定修饰符:来设置访问权限
注: default权限指:什么都不写时的默认权限。
继承
什么是继承?
Java中使用类来对现实世界中的实体进行描述,但是现实世界错综复杂,事物之间可能会存在一些关联、一些共性。比如:猫和狗,他们都是动物,因此他们就具有许多相似的属性,但又有自己独有的一些属性。比如:
具有这些相同属性的类还有很多,如果我们在这些类中都重新加上这些属性,代码就显的有写累赘。 因此就引入了继承。我们把所有类共同的一些属性写到一个类里,把每个类独有的部分再写到一个类中。
继承:专门用来进行共性抽取,实现代码复用。
共同的部分(类),叫作:父类、基类、超类;
独有的部分(类),叫作:子类、派生类;
继承的语法
借助extends关键字。
class Animal{
public String name;
public int age;
public double weight;
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
class Cat extends Animal{
public void miao(){
System.out.println("喵喵叫");
}
}
class Dog extends Animal{
public void bark(){
System.out.println("汪汪叫");
}
}
注:
- 子类会将父类中所有的成员变量、成员方法添加到子类中。
- 子类继承父类后必须新添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了。
父类成员访问
子类访问父类的成员变量
- 子类和父类中不存在同名的成员变量
class Animal{
public String name; //由于需要被子类对象使用,所有用public修饰
public int age;
}
class Dog extends Animal{
public String color;
public void method(){
name = "小黄"; // 直接访问父类的
age = 2; // 直接访问父类的
color = "黄色"; // 访问自己的
}
}
- 子类和父类中存在同名的成员变量
class Animal{
public String name;
public int age;
}
class Dog extends Animal{
public String name;
public String color;
public void method(){
name = "小黄"; // 优先访问自己的name
age = 2; // 直接访问父类的
color = "黄色"; // 访问自己的
}
}
注:
- 如果访问的成员变量子类中有,优先访问自己的成员变量;
- 如果访问的成员变量子类中无,则访问父类继承下来的;如果父类中也没,则编译报错;
- 如果访问的成员变量与父类中的成员变量同名,则优先访问自己的;
子类访问父类的成员方法
- 成员方法名字不同
class Animal{
public void eat(){
System.out.println("吃饭");
}
}
class Dog extends Animal{
public void bark(){
System.out.println("汪汪叫");
}
public void method(){
bark(); //访问子类自己的
eat(); //访问父类的
}
}
- 成员方法名字相同
class Animal{
public void eat(){
System.out.println("动物吃饭");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃饭");
}
public void method(){
eat(); //访问自己的
}
}
注:
- 通过子类对象访问 父类与子类中不同名的方法时,优先在子类中找,找到则访问;否则在父类中找,找到则访问,否则编译报错;
- 通过子类对象访问 父类与子类中同名的方法时,如果父类和子类的同名方法参数列表相同,则优先访问子类自己的;如果参数列表不同,则根据调用方法时传递的参数选择合适的方法访问,若没有则报错;
认识super关键字
如果子类中存在和父类相同的成员时,如何不访问子类的成员,而去访问父类的成员呢? 这就需要用到super关键字。
super:在子类方法中访问父类的成员;
class Animal{
public String name;
public void eat(){
System.out.println("动物吃饭");
}
}
class Dog extends Animal{
public String name;
public void eat(){
System.out.println("狗吃饭");
}
public void method(){
super.name = "动物名"; //访问父类的成员变量
super.eat(); //访问父类的成员方法
}
}
注:
- 只能在子类方法中使用;
- 只能在非静态方法中使用;
认识final关键字
final可以被用来修饰:变量、成员方法、类;
- final修饰变量或字段,表示常量—不能修改;
- final修饰类,表示此类不能被继承;
- finnal修饰方法,表示此方法不能被重写(多态中会使用到);
子类的构造方法
父子父子,先有父再有子;即:子类对象构造时,需要先调用父类的构造方法构造父类,然后执行子类的构造方法构造子类;
class Animal{
public Animal(){
System.out.println("执行父类构造方法");
}
}
class Dog extends Animal{
public Dog(){
System.out.println("执行子类构造方法");
}
}
//再main中创建子类时,结果为:
//执行父类构造方法
//执行子类构造方法
为什么还会调用到父类的构造方法呢?
子类对象中成员是由两部分组成的:父类继承下来的成员和子类新增的成员;父子父子,肯定先有父再有子。因此,需要先调用父类的构造方法,将从父类继承下来的成员构造完整;再调用子类自己的构造方法,将自己新增的成员构造完整。
注:
- 如果父类没有定义构造方法,在子类的构造方法中第一行会隐含的调用super(),即执行父类空的构造方法。
- 如果父类定义了无参的构造方法,在子类的构造方法中第一行会隐含的调用super(),即执行父类无参的构造方法。
- 如果父类定义了有参数的构造方法,此时编译器不会默认生成隐含的super()—因为它不知道参数是啥,需要用户显示的指定父类的构造方法。
- 在子类构造方法中,使用super(…)调用父类的构造方法时,super必须放在子类构造方法的第一行。
- super只能在子类构造方法中出现一次,且不能和this同时出现。
super VS this
相同点:
- 都是Java的关键字;
- 只能在类的非静态方法中使用,访问非静态的变量和非静态的方法
- 在构造方法中调用时,都必须在构造方法的第一行,且不能同时存在。
不同点:
- this是当前对象的引用,即引用当前对象的成员;super是从父类继承下来的成员的引用,即引用父类中的成员。
- this是非静态成员方法的一个隐藏参数,super不是一个隐藏参数;
- 在构造方法中,this用来调用本类构造方法,super用来构造父类构造方法;不能同时出现在一个构造方法里。
- 构造方法中一定存在super的调用,用户没有写编译器也会增加;但是this用户不写则没有。
在继承关系中各代码块的执行顺序是怎样的?
class Animal{
{
System.out.println("父类执行实例代码块");
}
static {
System.out.println("父类执行静态代码块");
}
public Animal(){
System.out.println("父类执行构造方法");
}
}
class Dog extends Animal{
{
System.out.println("子类执行实例代码块");
}
static {
System.out.println("子类执行静态代码块");
}
public Dog(){
System.out.println("子类执行构造方法");
}
}
public class test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
//结果:
父类执行静态代码块
子类执行静态代码块
父类执行实例代码块
父类执行构造方法
子类执行实例代码块
子类执行构造方法
注:
- 父类静态代码块优先于子类静态代码块执行,且都是最早执行;
- 父类实例代码块和父类构造方法再执行;
- 子类实例代码块和子类构造方法再执行;
- 实例代码块优先于构造方法执行;
- 第二次实例化子类对象时,父类和子类的静态代码块都不执行;
Java支持的继承的方式
继承 VS 组合
组合:和继承类似,也是一种表达类之间关系的方式。也能够达到代码复用的效果,它是将一个类的实例作为另一个类的字段。
//以汽车为例,说明继承和组成的不同
//轮胎类
class Tire{
}
//发动机类
class Engine{
}
//车内系统类
class VehicleSystem{
}
//组成
class Car{
private Tire tire;
private Engine engine;
private VehicleSystem vehicleSystem;
}
//继承 奔驰继承了汽车类
class Benz extends Car{
}
注:
- 继承表示对象之间是 is-a 的关系,即 猫是动物、狗是动物;
- 组合表示对象之间是 has-a的关系,即 汽车有轮子、发动机、车载系统;
- 继承借助extends关键字,会自动将父类成员加载到子类中;组合需要手动在某个类当中引入其他类的成员;
多态
什么是多态?
多态:即多种形态;具体点就是去完成某个行为时,不同的对象去完成时会产生不同的状态。
实现多态的条件:
- 必须在继承体系下;
- 子类必须对父类方法进行重写;
- 通过父类的引用调用重写方法;
class Animal{
public String name;
public int age;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃饭");
}
}
class Cat extends Animal{
public Cat(String name,int age){
super(name,age);
}
@Override
public void eat() {
System.out.println(name + "cat吃鱼");
}
}
class Dog extends Animal{
public Dog(String name, int age){
super(name,age);
}
@Override
public void eat() {
System.out.println(name + "dog吃骨头");
}
}
public class test {
//通过父类调用eat方法才能看出来重写的作用
//没有重写时,子类中没有eat方法,只能使用父类的eat方法,不管是哪个子类最后都会打印出 动物吃饭
//重写后,子类中有重写的eat方法,调用父类的eat方法时,会自动识别出哪个子类调用的eat方法,会打印出对应动物的吃饭
//
//子类有重写方法后,当然也可以不通过调用父类eat方法,而是直接调用自己重写后的eat方法打印出对应的动物的吃饭
//但这样就看不出重写的作用了
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("小猫",1);
Dog dog = new Dog("小狗",2);
eat(cat);
eat(dog);
}
}
什么是重写?
重写:也叫覆盖,是子类对父类非静态、非private修饰、非final修饰、非构造方法等的重新编写,返回值和形参都不能改变。即外壳不变,核心重写。
注:
- 子类在重写父类方法时,一般必须与父类方法原型一致:即修饰符、返回值类型、方法名、参数列表要完全一致。
- 特殊情况下,返回值类型可以不同,但是必须具有父子关系。
- 子类中重写方法的访问权限不能比父类方法中的访问权限更低。
- 父类中被static修饰的、private修饰的、final修饰的方法和构造方法,不能被重写。
- 重写后的方法要加上 @Override 注解。
重写 VS 重载
向上转型和向下转型
向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式: 父类类型 对象名 = new 子类类型()
// animal是父类类型但是可以引用一个子类对象 把小范围的对象放到大范围的对象里
Animal animal = new Dog("小狗",2);
使用场景:
- 直接赋值
- 方法传参
- 方法返回
注:
- 向上转型的优点:让代码实现更简单灵活。
- 向上转型的缺点:不能调用到子类特有的方法。
向下转型
向下转型:将子类对象向上转型后就无法调用子类特有的方法,但是有时候可能需要调用到子类特有的方法,此时就需要向下转型:将父类引用再还原为子类对象。
Cat cat = new Cat("小猫",1);
Dog dog = new Dog("小狗",2);
//向上转型
Animal animal = cat;
//向下转型: 按道理可以转为继承Animal的任意子类对象 但是有些不安全
//必须强制类型转换
Cat cat1 = (Cat) animal; //安全 因为向上转型时就是Cat转的
Dog dog1 = (Dog) animal; //不安全 会抛出异常 因为向上转型是Cat转的 但是现在却要把猫类成员赋值给狗类
Java中为了提高向下转型的安全性,引入了 instanceof :类型匹配 如果为true则可以安全转换。
Cat cat = new Cat("小猫",1);
Dog dog = new Dog("小狗",2);
//向上转型
Animal animal = cat;
//向下转型
if (animal instanceof Cat){
Cat cat1 = (Cat) animal;
}
if (animal instanceof Dog){
Dog dog1 = (Dog) animal;
}