1.访问修饰符
- public:对外公开
- protected:对子类和同一个包中的类公开
- 默认:对同一个包中的类公开
- private:只有类本身可以访问,不对外公开
访问级别 同类 同包 子类 不同包 public √ √ √ √ protected √ √ √ × 默认 √ √ × × private √ × × × tips:
修饰符用于修饰类中的属性、方法以及类本身,但是只有默认和public才能修饰类
作用在类中的属性和方法的访问修饰符的前提是创建对象实例,比如对于A类中的公开属性name,不是说同包下的B类直接可以使用name,还是要创建A类的实例后,能通过实例调用不会报错
2.封装
- 概念:把抽象出来的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法]才能对数据进行操作
3.继承
概念:当多个类存在相同的属性和方法时,可以从这些类抽象出父类,在父类中定义这些相同的属性和方法,子类只需要使用extends来声明继承父类即可
子类继承了所有属性和方法(并不意味着都能直接使用),但是不能直接访问父类的私有属性和方法,需要通过公共的方法访问
子类必须调用父类的构造器,完成父类的初始化:
- 当创建子类对象时,默认情况下都会去调用父类的无参构造器(调用无参构造器在默认情况下可以不明确写出)
- 如果父类没有无参构造器,必须在子类的构造器中使用super去指定使用父类的哪个构造器(一定要完成对父类的初始化)
- super(…)在使用时,一定要放在构造器首行,且只能在构造器中使用
Object是所有类的基类(父类)
父类构造器的调用不仅限于直接父类,将一直追溯到Object类
子类只能继承一个父类**(java中的单继承机制)**,要实现A继承B和C,可以让A继承B,B继承C
tips:
- 当类中没有构造器时,会默认创建一个无参构造器;当类中只有有参构造器时,默认的无参构造器就不存在了,需要自己创建
- super(…)和this(…)都需要放在构造器首行,所以这两个方法不能存在一个构造器中
- super关键字可以在除构造器外的方法中使用
4.继承的内存布局
假设代码如下:
class GrandPa { String name = "爷爷"; int age = 100; } class Father extends GrandPa { String name = "爸爸"; private int age = 40; } class Child extends Father { String name = "儿子"; int age = 40; }
- 查找关系:
- 看子类是否有需要查找的属性
- 如果子类有并且可以访问,则返回信息
- 如果子类没有该属性,看父类是否有该属性且可以直接访问,满足就返回
- 父类没有就按照上面规则继续找上级
- 即使Father类中age属性为private,但是在内存中依旧是存在的,只是无法直接被访问
- 此时访问
c.age
(假设Child类没有age属性)会报错,因为当父类有该属性但是无法访问时是不会继续往上级寻找的
5.方法重写/覆盖
- 子类方法的参数列表(类型、个数,顺序)和方法名称要和父类的完全一致
- 子类方法的返回类型要和父类的返回类型一致或者是父类返回类型的子类,比如父类返回类型为Object,子类方法返回类型是String
- 子类方法的访问权限不能小于父类方法的访问权限
名称 发生范围 方法名 形参列表 返回类型 修饰符 重载(overload) 本类 必须一样 类型、个数或顺序至少有一个不同 无要求 无要求 重写(override) 父子类 必须一样 相同(参数名称可以不同) 子类方法的返回类型要和父类的返回类型一致或者是父类返回类型的子类 子类方法的访问权限不能小于父类方法的访问权限
6.多态
假设代码要实现人给不同动物投喂不同的食物,代码如下:
// --------不使用多态----------- public void feed(Cat cat, Fish fish){ System.out.println("....."); } public void feed(Dog dog, Bone bone){ System.out.println("....."); } // ----------使用多态----------- public void feed(Animal a, Food f){ System.out.println("....."); }
这样可以实现要求,但是相同的方法会很多,不利于管理和维护。此时使用多态就可以提高代码的复用性:
方法的多态:
- 重载:根据参数的不同去调用相同名称的方法,这就是方法存在多种状态的表现
- 重写:当子类child对象和父类father对象都去调用相同的方法,执行结果会根据在类中定义不同而不同,这也是方法存在多态的表现
对象的多态:
- 一个对象的编译类型和运行类型可以不一致:
Animal animal = new Dog()
中编译类型是Animal,但是运行类型是Dog- 编译类型在定义对象时就已经确定,无法改变
- 运行类型可以改变(多种状态的表现)
多态注意事项-向上转型:
- 前提:两个对象存在继承关系
- 本质:父类的引用指向了子类的对象(向上转型)
- 特点:
- 可以调用父类的所有成员(遵循访问权限,尽管编译类型是父类,但是依旧无法访问private的成员属性和方法)
- 不能调用子类中特有的成员(因为在编译阶段能否调用方法由编译器决定)
- 最终运行效果看子类的实现,即调用方法时从子类开始查找方法
多态注意事项-向下转型:
- 语法:子类类型 引用名 = (子类类型) 父类引用
GrandPa g = new Child(); Child c = (Child) g; // 此时编译类型和运行类型都是Child Father f = (Father)g; // 不会报错 Other o = (Other)g; // 报错,父类引用g指向的是Child类,Child类和Other类毫无关系,不符合特点2
- 特点:
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 向下转型后就可以调用子类类型中所有的成员
tips:
- 对于
Animal animal = new Dog()
,如果父类Animal没有eat方法,但是Dog类中有该方法,调用animal.eat()
会报错;如果父类Animal有eat方法,但是Dog类中没有该方法,调用animal.eat()
不会报错- 属性没有重写,属性的值看编译类型(如果是直接打印属性的值,该值是看编译类型;但是如果属性在方法中使用,看的还是该属性是哪个类中的属性)
instanceof
操作符用于判断对象的运行类型是否为xx类型或者xx类型的子类Base b = new Sub(); Sub a = (Sub) b; System.out.println(b.name); // 打印"psj" System.out.println(a.name); // 打印"psw" // ----------类的定义------------ class Base { String name = "psj"; } class Sub extends Base { String name = "psw"; }
- 如果子类强转为父类后,编译类型是父类,但是运行类型还是子类(接口的实现类强转为接口也是如此):
Son s = new Son(); Father f = (Father) s; System.out.println(f.getClass()); // Son类 f.test(); // 运行子类的方法 f.name // 报错 // 类的实现 class Father { void test() { System.out.println("father"); } } class Son extends Father { public String name; @Override void test() { System.out.println("son"); } }
- 当父类引用指向子类对象,只会调用父类的静态方法,此行为并不具有多态性:
public class test extends Father{ public static void test(){ System.out.println("Son"); } public static void main(String[] args) { Father test = new test(); test.test(); // 调用的还是Father类的test方法 } } class Father{ public static void test(){ System.out.println("Father"); } }
7.动态绑定机制*
假设代码如下:
Base b = new Sub(); System.out.println(b.sum()); // 打印30 System.out.println(b.sum_1()); // 打印20 // -----------类的定义---------------- class Base { public int i = 10; public int sum() { return getI() + 10; } public int sum_1(){ return i + 10; } public int getI() { return i + 10; } } class Sub extends Base { public int i = 20; public int getI(){ return i; } }
上述代码中,由于子类Sub没有sum函数,会调用父类Base的sum函数,会调用getI函数,但是两个类都存在getI函数,该调用哪个?
- 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定
所以执行
b.sum()
时,调用的是子类Sub的getI函数,该函数中的i为Sub类中的20。如果执行sum_1函数输出什么?
- 当调用对象属性时,没有动态绑定机制,哪里声明就哪里使用
所以执行
b.sum_1()
时,需要使用的属性i是当前类Base中的i=10
8.object类详解
- equals和==的对比(面试题):
- ==判断基本类型时判断值是否相等;判断引用类型时判断地址是否相等
- equals是方法,只能判断引用类型。默认判断地址是否相等,子类往往会重写该方法,用于判断内容是否相等(如String,Integer)
- hashCode方法:
- 提高具有哈希结构的容器的效率
- 两个引用如果指向的是同一个对象,则哈希值是一样的
- 哈希值是根据地址转换为的整数,但不能完全将哈希值等价于地址
- toString方法:
- 默认返回全类名(包名+类名) + @ + 哈希值的十六进制
- 直接输出一个对象时,该方法会被默认调用
- finalize方法:
- 当对象被回收时,系统自动调用该对象的该方法。子类可以重写该方法做一些释放资源等操作
- 当某个对象没有任何引用时,jvm就认为该对象是垃圾对象,会使用垃圾回收机制销毁该对象,销毁前会先调用该方法
- 垃圾回收机制的调用是由系统决定,也可以通过System.gc()主动触发垃圾回收机制
tips:
- 当某个对象没有任何引用时,垃圾回收机制不会立刻销毁对象,而是由GC算法去决定什么时候回收