Java中对象的理解

108 阅读9分钟

对象在内存中存在的形式

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类。

  1. 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) 通过静态工厂