我们都知道,面向对象的三大特性:封装、继承、多态。具体说来,它们到低是啥意思呢,下面简单概括一下。
封装:高内聚、低耦合
- 将属性私有化,然后提供公共的getter和setter方法
- 将方法私有化,只对内使用,对外不暴露
继承:表达类与类之间的关系
多态:父类指针指向子类对象
访问修饰符
- private:当前本类内被访问,子类都不行
- public:不限制访问
- protected:本类、子类、同包可以访问。跨包非子类不允许访问
- 默认:本类,同包可以访问。其他不可以。
可以用以上修饰符来修饰类、类的内部结构(属性、方法)。
封装
类的封装
隐藏对象的信息,同时留出可控的访问接口,实现高内聚低耦合的效果。
public class Cat{
//私有属性
private String name;
//setter方法
public void setName(String name){
this.name = name;//再加上其他规则逻辑判断
}
//getter方法
public String getName(){
return this.name;
}
}
包封装
管理java文件,解决同名文件冲突问题
同一个包内文件名必须不同(类名),不同包内文件名可以相同(类名)
//声明包
package 包名
//导入包
import 包名
import com.hellomeng.study.*; //加载com.hellomeng.study包下的所有类(不包括子包)
import com.hellomeng.study.Java;//加载com.hellomeng.study包下的Java类
//还可以不在程序开头导入包,直接在代码中写全包名加类名的方式使用类
当然,利用IDE,你并不需要
package 包名
这样创建包。在你创建文件夹
时,这一切工具都替你自动完成了。打开IEDA,随便找一个java文件。在头部你都能看到上述信息。
代码块/初始化块
用{}
括起来的一段代码,一般用于初始化类或对象
public class Cat{
String name;
public void eat(){
System.out.println("吃")
}
{
//构造代码块,在对象被创建时调用,优先于构造方法执行
//如果是多个构造代码块,按照先后顺序执行
}
}
当代码块定义在方法中时,称之为普通代码块
public void eat(){
System.out.println("吃");
{
//普通代码块,按照方法内部执行顺序执行
}
}
用static修饰的代码块称之为静态代码块
static {
//随着 类的加载 而加载并自动执行(静态方法是随着类的加载而加载)
}
{
//非静态代码块随着 每个 对象的创建而加载并执行
}
下面说一下从类被加载,到创建对象,代码块、构造函数执行顺序
⚠️
静态代码块【类加载的时候执行一次】==》非静态/初始化代码块【对象创建时执行】==》构造函数 【创建对象时执行、父类先执行,子类后执行】
继承
表达的是一种类与类之间的关系。
使用已经存在的类作为基础创建新的类。
但是子类不能选择性的继承父类,一旦继承(包括直接父类和间接父类),就要继承所有的属性和方法(私有的 属性或方法,子类虽然继承了,但是不能访问)。
注意:子类无法直接访问父类中的私有属性或方法(可通过公共的getter或setter方法调用)
注意:父类的构造方法是不能被继承的 继承语法:
class 子类 extends 父类{
}
java中的继承只能是单继承,一个子类只能有一个父类。但是存在间接继承的关系,子类-》父类-》父类的父类-》。。。。
方法重写
在子类中。方法名,返回值类型,参数类型,参数个数,参数顺序都与父类完全一致,就构成了方法重写。
注意区分重写和重载。直白但不严谨的理解是:重载是同一类的两个“同名方法”。重写是覆盖了父类的“同名方法”。另外,方法重写是运行期特性,表现为多态。
-
属性也是可以”重写“的
-
子类重写父类时,访问修饰符可以修改,但是权限必须大于等于父类的访问权限(private<默认<protected<public)
-
子类重写父类方法时,返回值类型要和父类保持一致。但如果返回值是A类时,子类重写了这个方法的返回值,可以是A类或A类的子类。
-
子类不能重写父类中private权限的方法
-
子类重写的方法抛出的异常类型,要求不大于父类方法中异常的类型。
-
子类和父类同名同参(要重写)的方法,要么都是非static的(构成重写),要么都是static的(不构成重写)。否则会报错(父类static,子类非static,不允许)
子类对象实例化过程
要先把类加载进来,才能根据类实例化对象,这样就好理解了。
- 类的加载:
先加载父类的静态成员,再加载子类的静态成员 - 子类对象实例化的过程中
父类静态代码块->子类静态代码块->父类构造代码块->父类构造方法->子类构造代码块->子类构造方法
子类对象的实例化过程中,如果不显示指定,默认调用的是父类的无参构造。所以,无论用不用,都建议在创建一个类的时候,把无参构造显示声明出来。
子类构造中,如果用super显示调用父类构造的时候。如:super(参数)。这行代码要放在子类构造方法的第一行。
在构造方法中调用其他构造方法时。this(参数),super(参数),不能同时出现。
构造子类的时候,一定会直接或间接的调用父类的构造器,直到Object。但是,只会创建你想创建的这个子类的一个对象。虽然调用了父类的构造器,但并不会创建多个父类对象。
多态
- 何为多态?
可以理解为一个事物的多种形态。具体指允许不同类的对象对用一消息做出不同的响应 - 为什么多态?
可以让“接口”变得更加通用
public void test(Animal animal){
animal.eat()
}
假设 Animal 有两个子类 分别是 Gou 和 Mao。他们都重写了eat()方法。
此时,这个animal参数,你传的是狗,下面eat方法执行的就是狗的eat。你传mao,执行的就是mao的eat。如果没有多态性。你要进行方法重载,即分别写两个test方法。参数分别为猫,和狗。
-
怎么多态?
父类引用指向子类对象(重点)
⚠️:对象的多态性只适用于方法,不适用于属性。
(指向子类对象的父类指针调用一个子父类都有的属性,实际调用的是父类的。
向上转型
说的是父类指针指向子类对象
Animal * animal = new Animal();
Animal * cat = new Cat();
Animal * dog = new Dog();
aninal.eat() //动物吃东西
cat.eat() //猫吃鱼
dog.eat()//狗吃肉
父类指针 可以调用父类在子类中被重写的方法,或者父类派生下去的方法(子类继承的方法)。但无法调用子类特有的方法(因为对象(指针、引用)类型是父类)(如果非要调用子类特有的部分,只能强转子类)
编译看父类(类型),运行看子类(实例)。属性除外,都看父类
⚠️:由于父类和子类之间的同名静态方法不构成重写,所以,父类指针指向子类对象时,当调用静态方法时,实际调用的是父类中的
向下转型(强制转换)
父类指针指向子类对象,根据多态性,可以调用父类中的属性和方法。其中方法部分,实际运行时会执行子类的方法。但是,父类指针无法调用子类特有的对象和方法。除非把父类类型强制转换为子类类型。
也就是:
子类引用指向父类实例(必须强制转换)
Cat cat = (Cat)animal;
这种强制转换,一定要做好逻辑判断,否则会引起运行时错误。
做什么样的逻辑判断呢,就是判断 对象是否满足某个类的实例特征。比如。animal是否满足Cat的特性。满足才能强转。
instanceof
对象 instanceof 类 //对象是否满足某个类的实例特征
cat instanceof Cat;//true
cat instanceof Animal;//true
cat instanceof Car;//false
Object
是所有类的直接或间接父类(包括数组)
Java中,所有类,都可以使用Object中的成员或方法。
Object类中的常用方法
equals
判断两个对象的引用是否一致
在其他类中不一定是这个意思。比如在String中,就重写了equals方法,判断的时两个字符串的值是否相等
toString
打印出一个对象的字符串表现形式
一般在子类中也会对其重写
接口
不同的类型,不满足继承关系。可以抽象出某些功能放到接口中。或者可以把接口理解为一组规范,任何人都可以对接口进行实现,从而具备接口的功能,但必须满足规范。
一个类可以实现多个接口(遵守多个规范)
public interface 接口名{//接口名一般用I开头
//抽象方法
接口中声明的抽象方法,可以不写abstract关键字
public void 方法名();
}
使用接口时,在类的后面加implements
public class 类名 implements 接口名{
public void 方法名(){
//方法实现
}
}
当一个类去实现接口时,需要实现接口中的所有抽象方法
接口中也可以定义常量
在接口方法声明前加上default
,就成为默认方法,或者加上static
,成为静态方法。
实现这个接口的类可以选择不实现默认方法或静态方法
default public void 方法名(){
//默认方法体
}
区别:静态方法不能被实现类重写,只能接口自己用
调用接口中的默认方法:
A implements B{
B.supper.默认方法
}
一个类可以实现多个接口,中间用,
号分隔。
当多个接口中有同名的默认方法时,实现接口的类要调用默认方法的时候不知道调用哪一个。此时只能重写默认方法。注意:是默认方法哦,带default的那个。
如果接口中的默认方法和父类中的方法同名,子类调用的时候,调用的是父类中的方法。
对于多个接口、父类中 存在重名的常量时,子类调用。要么指定接口接口名.常量
。要么在子类中重写属于自己的常量。否则会报错。编译器不知道取哪个常量。
Java中接口也可以存在继承关系,并且接口可以多继承,用,
号分隔
public interface 接口名 extends 父接口,父接口{
}