封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。
class Person{
String name; // 属性
int age;
// 方法
public Person(String name, int age){
this.name = name;
this.age = age;
}
}
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
class Student extends Person{
double grade; // 在已经的name,age中, 在学生类中添加了成绩或者班级
// 方法
public Student(String name, int age, double grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}
三要素:加黑的地方!
首先我觉得即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。强调在编译的时候,不知道该引用指向的是哪个对象的实例,包括调用哪个实例的方法,只有运行的时候,动态知道。
举个例子:
任何事物的多个姿态,多个形态。比如,你说一个猫在吃东西,同样的,你也能说一个动物在吃东西。
public class Test {
public static void main(String[] args){
Animal animal = new Cat();
animal.eat() // 猫也会吃饭
// 你看到了一只猫,同样它也是动物
// 比如有很多其他种类继承了动物哈,
// 当编译期间的animal引用变量,到底指的哪个实例对象,(重要)(主语是引用变量)
// 或者该引用调用的eat方法,到底是哪个实例对象的eat,编译期间恐怕不知道哦(主语是引用变量)
// 只有运行期间,哦哦, 原来是猫的eat方法哇...
}
}
-
Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型。调用方法时通过传递的参数类型来决定具体使用哪个方法,这就是多态性。比如,java lang的包很多工具类如String工具类,那么就有很多相同的名字,但是参数类型、数量和返回值等等不一样。
-
Java的方法重写,是父类与子类之间的多态性,子类可继承父类中的方法,但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。重写的参数列表和返回类型均不可修改。这也是多态性。比如JUC的AQS框架,凡事继承了AQS的那几个类,其中几个重要的方法,都被重写了,很多这样的情况。
首先要说:首先当程序运行需要某个类时,类加载器会将相应的class文件载入到JVM中,并在方法区建立该类的类型信息(包括方法代码,类变量、成员变量以及方法表。(标黑的这个玩意)
面试官:方法表有啥?
我:方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
接着回答:方法表是实现动态调用的核心。为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向记录该类方法的方法表,方法表中的每一个项都是对应方法的指针。
到这里:就要分情况讨论了,一个是方法调用,一个是接口
class Person {
// 重写object的toString
public String toString(){
return "I'm a person.";
}
public void eat(){}
public void speak(){}
}
class Boy extends Person{
// 重写object的toString
public String toString(){
return "I'm a boy";
}
// 继承Person的speak
public void speak(){}
// 自己实现的自定义方法
public void fight(){}
}
class Girl extends Person{
// 重写object的toString
public String toString(){
return "I'm a girl";
}
// 继承Person的speak
public void speak(){}
// 自己实现的自定义方法
public void sing(){}
}
这张图的指向:你可以根据颜色对应上,注意方法表条目指向的具体的方法地址。其次注意蓝色部分其继承自于 Person 的方法 eat() 和 speak() 分别指向 Person 的方法实现和本身的实现。如果子类改写了父类的方法,那么子类和父类的那些同名的方法共享一个方法表项。因此,所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值。Person 或 Object中的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。
调用过程:
- 在常量池里找到方法调用的符号引用(肯定先看到Person定义引用类型)
- 查看Person的方法表,得到speak方法在该方法表的偏移量(假设为15),这样就得到该方法的直接引用。
- 根据this(invoker this字节码)指针得到具体的对象(即 girl 所指向的位于堆中的对象)。
- 根据对象得到该对象对应的方法表,根据偏移量15查看有无重写(override)该方法,如果重写,则可以直接调用(Girl的方法表的speak项指向自身的方法而非父类);如果没有重写,则需要拿到按照继承关系从下往上的基类(这里是Person类)的方法表,同样按照这个偏移量15查看有无该方法。
一个类可以实现多个接口,那么就像多继承一样,这样的话,在方法表中的索引就会不一样,所以Java 对于接口方法的调用是采用搜索方法表的方式。
有这样的两个类:Person和Student
Person:
class Person{
// 静态代码块
static {
System.out.println("Person 静态方法");
}
// 代码块
{
System.out.println("Person 代码块");
}
// 构造方法
Person(){
System.out.println("Person 构造方法");
}
}
Student:
class Student extends Person{
// 静态代码块
static {
System.out.println("Student 静态方法");
}
// 代码块
{
System.out.println("Student 代码块");
}
// 构造方法
Student(){
System.out.println("Student 构造方法");
}
}
测试
public class Main {
public static void main(String[] args) {
Student student = new Student();
}
}
// result:
Person 静态方法
Student 静态方法
Person 代码块
Person 构造方法
Student 代码块
Student 构造方法
解释:还不是因为类加载器的双亲委派哈, 先走类方法,也就是说static属于类,所以先调用Person static -> Student static,接着走对象的初始化,那么对象初始化了,还是先走父类的初始化了,所以Perosn {} -> Person(){},最后走子类的初始化Student {} -> Student(){}
- 方法是否能实现:所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。
- 变量:接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。
- 实现:一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过implement关键字扩展多个接口。
- 修饰符:接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 5
public class Main {
public static void main(String[] args) {
String str = "m";
new Thread(){
@Override
public void run() {
System.out.println(str);
}
}.start();
}
}
// 在1.8版本之前是编译不通过的,1.8能编译能过,但是还是需要final保证局部变量的数据一致性
反编译
public class Hello$1 extends Thread {
private String val$str;
Hello$1(String paramString) {
this.val$str = paramString;
}
public void run() {
System.out.println(this.val$str);
}
}
解释:局部变量是被当成了参数传递给匿名对象的构造器(那就相当于拷贝一份,那就是浅拷贝了),这样的话,如果不管是基本类型还是引用类型,一旦这个局部变量是消失(局部变量随着方法的出栈而消失),还是数据被改变,那么此时匿名构造类是不知道的,毕竟是你浅拷贝了一份,那么如果加上final,这个数据或者引用永远都不会改变,保证数据一致性。注意:在JDK8中如果我们在匿名内部类中需要访问局部变量,那么这个局部变量不需要用final修饰符修饰。
-
public:可以被所有其他类所访问。
-
private:只能被自己访问和修改。
-
protected:自身,子类及同一个包中类可以访问。
-
default(默认):同一包中的类可以访问