对象在内存中存在的形式
Person person = new Person();
person.name = "顶真";
person.smoke();
1.Person person:先在方法区中找有没有已经加载Person类的信息,若没有,则加载Person类中的静态变量和代码到方法区中。生成一个Person类型的变量,存放在栈中。
2.new Person():new了一个Person的对象(即Person类的实例),存放在堆中,并在该堆中开辟一个空间,用于存放name变量。并在方法区里加载Person类的属性和方法。
3.=:将Person对象的地址赋给person变量。
4.person.name = "顶真":由于name是一个引用类型,会将常量池中 "顶真" 的地址赋给堆中name的空间。若为基本类型,则直接复赋值。若为其他的类,则会通过指针指向堆中对象的地址。
5.person.smoke():调用对象方法时,不会生成方法,而是会在方法区中通过指针调用该方法。
继承
1 子类只能访问非私有的属性和方法,私有的属性和方法不可以访问,可以让父类通过公共方法间接访问 修饰符:public所有包可用、protected子类和同一包可用、default同一包可用、private同一类可用
2 创建子类对象时,子类构造器中默认会先自动执行super(),即先调用父类无参构造器。若父类中有有参构造器,则需要在子类构造器中调用父类的有参构造器(不能用无参),且super只能放在第一行。
3.Java中是单继承,若想继承多个父类,可以间接继承。
4 若调用子类的信息(person.name),首先看子类是否有该属性且该属性是否可访问,若没有属性或不可访问,则向父类查找,直到Object类。
- A instanceof B,判断A是否是B的子类,或A的类型和B的类型是否相等
重写
1 子类重写父类的方法,子类的方法的返回值可以和父类不相同,但其返回值必须是父类返回值的子类
2 子类的方法不能缩小父类方法的访问权限(访问修饰符),但可以扩大
3 重载是在同类中方法名相同,返回值和参数不同;重写是父子类中方法名、参数必须相同,返回值必须为父类的返回值的子类。
多态
定义:多态是同一个行为具有多个不同表现形式或形态的能力。
Animal animal = new Dog();
animal.cry();
其中Dog是Animal的子类,animal的编译类型是Animal,运行类型是Dog。但animal不能调用运行类型的特有的成员。
编译阶段,编译器只找Animal中是否有该方法,运行阶段,Java优先调用的是Dog中的方法,而属性只看编译类型。
public class test {
public static void main(String[] args) {
Animal animal = new Dog();
System.out.println(animal.name);
animal.eat();
animal.sleep();
}
}
class Animal {
public String name = "animal";
public void eat() {
System.out.println("animal eat");
}
}
class Dog extends Animal {
public String name = "dog";
public void eat() {
System.out.println("dog eat");
}
public void sleep() {
System.out.println("dot sleep");
}
}
animal.sleep()会报错,因为编译时按照编译类型来寻找方法,但Animal类中没有该方法;animal.eat()会输出dog sleep,因为运行时会优先调用运行类型的方法。而animal.name属性应该是编译类型,即"animal"。
2.向上转型:父类引用指向子类对象,即将子类转换为父类
3.向下转型:子类引用指向父类对象,需要强制转换,而且要求父类对象引用类型必须是该子类类型
public static void main(String[] args) {
Animal animal = new Dog();
Dog dog = (Dog) animal;
Animal animal1 = new Animal();
Dog dog1 = (Dog) animal1;//不安全的向下转型,编译不报错,运行时会报错
}
抽象类、接口、匿名抽象类
抽象类
1.抽象方法必须存在于抽象类中,但抽象类中可以有非抽象方法、构造器、静态属性等
2.抽象方法没有方法体,即方法名后面的{},抽象方法不能使用private、final、static修饰符,因为抽象方法需要被子类重写
3.抽象类不能被实例化,若其他类继承了抽象类,则必须实现抽象类中的抽象方法或将自己也声明为抽象类
4.abstract只能修饰类和方法,不能修饰其他的属性
5.抽象类是一种模板、框架,通过抽象方法,可以由子类来决定一些特定的方法逻辑(即抽象方法),而其他的框架由抽象类自动实现
接口
1.接口中的方法默认为public和abstract,接口中也可以有静态方法、默认(default)方法、属性,其中属性默认为public static final的
2.接口内的方法不可以被实例化,只能被实现
3.一个类可以同时实现多个接口,一个接口可以继承多个接口,间接实现了类的多继承
4.接口是一种特殊的抽象类,它定义了一个规范、协议,让部分代码分离出去,有助于代码解耦、增加了代码的复用和统一了规范。
5.子类继承父类,则拥有父类的方法;子类实现接口,则拥有接口中方法的模板,但方法由子类定义,从而实现了功能的扩展。
public class Main {
public static void main(String[] args) {
Mysql mysql = new Mysql();
Oracle oracle = new Oracle();
DBOperation(mysql);
DBOperation(oracle);
}
public static void DBOperation(DB db) {
db.connect();
db.close();
}
}
interface DB {
public void connect();
public void close();
}
class Mysql implements DB {
@Override
public void connect() {
System.out.println("Mysql连接");
}
@Override
public void close() {
System.out.println("Mysql断开");
}
}
class Oracle implements DB {
@Override
public void connect() {
System.out.println("Orcale连接");
}
@Override
public void close() {
System.out.println("Orcale断开");
}
}
接口的多态
1.只要是实现了接口的实现类,都可以作为参数传入,体现了接口的多态
2.接口类型的变量可以指向实现类
DB interfaceDB = new Mysql();
3.接口数组中可以存入接口的实现类
4.接口具有传递性,即A接口继承B接口,则A接口变量可以指向B接口的实现类
形象理解继承和接口的区别
举个栗子:小猴子可以继承大猴子的方法,但无法继承鸟的方法(单继承);但小猴子可以实现飞翔方法(接口),从而让自己拥有飞翔的能力(实现),且还可以实现游泳的方法(多实现)。若一个方法需要一个会飞翔的动物作为参数,因为小猴子实现了飞翔的方法,所以小猴子可以作为该方法的参数。若另一个方法需要一个猴子作为参数,又因为小猴子继承了大猴子,所以小猴子也是猴子.
再举个栗子理解接口的作用:鸟和老鹰,通过实现飞这个接口,使他们获得了飞这个功能,但他们飞的形式不同。此时有个方法需要一个会飞的动物,虽然鸟和老鹰具体飞的实现不同,但他们仍然会飞,可以作为参数传入。
内部类
内部类:一个类的内部嵌套了另一个类(类的五大成员:属性、方法、构造器、代码块、内部类)
局部内部类:定义在局部位置(即方法中)上的有类名的内部类
1.局部内部类可以直接访问外部类的所有成员,包括私有成员
2.局部内部类是一个内部的局部变量,和方法中普通的变量一样,不能被访问修饰符修饰,但final可以(不能被继承),且只能在该方法中使用,其作用域是在方法体中
3.在局部内部类所在方法中可以直接new一个内部类对象,在同一个外部类但其他方法下不能new该对象
4.若内部类的变量和外部类的变量重名,遵守就近原则,若要访问外部类的变量,则使用 外部类.this.变量名(外部类.变量名 表示该变量为静态,而 外部类.this 表示调用局部内部类所在方法的对象)
匿名内部类:定义在局部位置上的没有类名的内部类
基于接口的匿名内部类
public class Main {
public static void main(String[] args) {
Animal animal = new Animal() {
@Override
public void run() {
System.out.println("run");
}
};
animal.run();
}
}
interface Animal {
public void run();
}
疑惑
接口Animal是不能被实例化的,但却被直接new出来并赋给了animal,因为此处用到了匿名内部类
原因
在Animal animal = new Animal()时,编译器会创建一个匿名内部类。并立即创建了Main$1的实例,再将地址赋给animal变量。可以等价于以下的代码
class Main$1 implements Animal {
@Override
public void run() {
System.out.println("run");
}
}
Animal animal = new Main$1();
1.匿名内部类有类名,其类名由编译器自动分配,类名为 外部类名$第几个内部类 ,本例中类名为Main$1,若有第二个匿名内部类,其类目为Main$2.
2.匿名内部类被创建一次实例后就不能再被使用,在作用域(即所在方法)结束后会被回收。若匿名内部类的对象被其他对象引用,则不会被回收,直至匿名内部类的对象的引用被释放后,匿名内部类才会被销毁
public class OuterClass {
public InnerClass createInnerClass() {
return new InnerClass() {
public void printX() {
System.out.println("innerClass");
}
};
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
InnerClass inner = outer.createInnerClass();
inner.printX();
}
}
该例子中,InnerClass为匿名内部类,其对象被inner变量所引用,因此它的生命周期延长,当inner变量被释放或不再引用其实例后,匿名内部类将会被回收。
基于普通类的匿名内部类
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
Dog innerDog = new Dog(){
};
}
}
class Dog {
public void Run() {
System.out.println("run");
}
}
new Dog()是创建了一个Dog类;而Dog innerDog = new Dog(){}则是创建了一个匿名内部类,可以等价为
class Main$1 extends Dog {}
Dog innerDog = new Main$1{};
成员内部类、静态内部类
成员内部类:定义在外部类的成员位置上(即方法外)的类
1.成员内部类可以添加任何访问修饰符
2.成员内部类的作用域为整个外部类,内部类中可以直接访问外部类的所有成员
3.外部其他类创建成员内部类:1)outerClass.new innerClass() 2)通过实例工厂
静态内部类:静态定义在外部类的成员位置上(即方法外)的类
1.同成员内部类
2.外部其他类访问静态成员内部类: 1) new outerClass.innerClass() 2) 通过实例工厂 3) 通过静态工厂