第十三讲 面向对象基础——Object类与内部类

● Java编程语言 专栏收录该内容
56 篇文章 1 订阅

Object类

Object类的概述

Object类是所有对象的直接或者间接父类,传说中的上帝。该类中定义的肯定是所有对象都具备的功能。

Object类中的equals方法

Object类中已经提供了对对象是否相同的比较方法,如果自定义类中也有比较相同的功能,没有必要重新定义。只要沿袭父类中的功能,建立自己特有比较内容即可,这就是覆盖。例,假如有一个Person类,其代码为:

class Person extends Object
{
    private int age;
    private String name;

    Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
}

现在我们的需求是定义一个方法,判断两个Person对象是否是同一个,判断的依据是根据姓名和年龄,如果姓名和年龄都相同,视为同一个人。我们可以这样分析,不用再自定义方法判断对象是否相同了,因为在Object父类中,已经定义了这样的方法,直接使用就可以了,但是判断的内容是根据Person类的特点定义的,那就需要保留父类的功能声明,定义子类功能的特有内容,使用覆盖。所以要在Person类中加入如下equals方法:

public boolean equals(Object obj) // Object obj = p2;--->Object obj = new Person();
{
    // 提高点效率。如果两个引用指向了同一个对象,就不用再转换并比较内容了,直接判断地址就哦了。
    if (this == obj)
        return true;

    // obj.age是错误的,因为Object中没有age属性,
    // 想要使用子类对象的特有属性或行为,必须对其进行向下转型,并且需要进行类型判断
    if (!(obj instanceof Person))
    {
        // return false; 
        throw new ClassCastException("类型错误");
    }
    Person p = (Person)obj;
    // 如果判断姓名字符串是否相同,不要用==,字符串本身是一个对象,所以要使用String类的equals方法
    return this.name.equals(p.name) && this.age == p.age;
}

Object类中的toString方法

有时候,我们还需要重写Object类的toString()方法,建立Person对象特有的字符串表现形式。查询API帮助文档,我们可以发现:Object类的toString方法返回的是一个字符串,该字符串由类名(对象是该类的一个实例)、at标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:getClass().getName() + '@' + Integer.toHexString(hashCode())。如果我们不在Person类中重写该方法,运行以下程序:

class ObjectDemo 
{
    public static void main(String[] args) 
    {
        Person p1 = new Person("lisi", 21);
        Person p2 = new Person("mazi", 25);

        System.out.println(p1); // Person@139a55,打印对象时,默认调用toString()方法
        System.out.println(p1.toString());
    }
}

则会输出Person@139a55,而且在打印对象时,默认调用了toString()方法。顺便说一下,如果要我们自己来弄,输出Person@139a55这样的东西,又该怎么做呢?这时,我们就要接触一点点反射的知识了,关于反射的知识以后会另开一篇专讲。A.class、B.class这些class文件都有名称,这些文件内都有构造函数、一般方法,Java中用Class来描述这些class文件,可通过getName()获取名称。所以以下代码会输出Person。

Person p1 = new Person("lisi", 21);
Class c = p1.getClass();
System.out.println(c.getName()); // Person

现在我们就可以自己来编写了,代码如下:

Person p1 = new Person("lisi", 21);
Class c = p1.getClass();
System.out.println(c.getName()+"@@"+Integer.toHexString(p1.hashCode())); 

这时就会输出Person@@139a55这样的东西了。
说完toString()方法,现在我们就要在Person类中重写该方法了,所以应在Person类中添加如下toString()方法:

/**
建立Person对象特有的字符串表现形式,只要覆盖toString方法即可
*/
public String toString()
{
    return "Person[name = " + this.name +", age = " + this.age + "]";
}

内部类

内部类的概述

将一个类定义在另一个类的里面,对里面的那个类就称为内部类(内置类,嵌套类)。例如,A类要直接访问B类中的成员时,可以将A类直接定义到B类中,作为B类的内部类存在。

内部类的访问规则

内部类可以直接访问外部类中的成员,包括私有。之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类中的引用,格式为外部类名.this。用一个例子来验证:

class Outer
{
    int num = 2;

    class Inner
    {
        int num = 3;

        void show()
        {
            int num = 4;
            System.out.println("show..." + Outer.this.num);
        }
    }

    public void method()
    {
        new Inner().show();
    }
}
class InnerClassDemo2 
{
    public static void main(String[] args) 
    {
        new Outer().method();
    }
}

而外部类要想访问内部类,那么只能创建内部类的对象来访问。

非静态,非私有的内部类访问方式

当内部类定义在外部类的成员位置上,而且非私有时,在外部其他类中,可以直接建立内部类对象,格式为:

外部类名.内部类名 变量名 = 外部类对象.内部类对象;

例子如下:

class Outer
{
    private int num = 4;
    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static
    {
        void show()
        {
            System.out.println(num);
        }
    }
}
class InnerClassDemo
{
    public static void main(String[] args) 
    {
        Outer.Inner in = new Outer().new Inner();
        in.show();
    }
}

而且在非静态内部类中只允许定义静态的常量,存于常量池中,不能定义其他静态成员。所以,以下代码编译是没有任何问题的:

class Outer
{
    private int num = 4;
    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static
    {
        static final int count = 5; // 在非静态内部类中只允许定义静态的常量,存于常量池中,不能定义其他静态成员
        void show()
        {
            System.out.println(num);
        }
    }
}
class InnerClassDemo
{
    public static void main(String[] args) 
    {
        Outer.Inner in = new Outer().new Inner();
        in.show();
    }
}

静态,非私有的内部类访问方式,访问非静态成员

当内部类在成员位置上时,就可以被成员修饰符修饰,比如,private将内部类在外部类中进行封装,static内部类就具备static的特性。当内部类被static修饰后,只能直接访问外部类中的static成员,出现了访问局限。那么在外部其他类中,如何直接访问静态内部类中的非静态成员呢?访问格式为new Outer.Inner().function();。例,

class Outer
{
    private static int num = 4;
    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static
    {
        void show()
        {
            System.out.println(num);
        }
    }

    static class Inner2 // 静态内部类,相当于一个外部类
    {
        void show2()
        {
            System.out.println("show2..." + num);
        }
    }

}
class InnerClassDemo
{
    public static void main(String[] args) 
    {
        Outer.Inner2 in = new Outer.Inner2();
        in.show2();
    }
}

静态,非私有的内部类访问方式,访问静态成员

在外部其他类中,如何直接访问静态内部类中的静态成员呢?访问格式为Outer.Inner.function();。例,

class Outer
{
    private static int num = 4;
    class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static
    {
        void show()
        {
            System.out.println(num);
        }
    }

    static class Inner2 // 静态内部类,相当于一个外部类
    {
        void show2()
        {
            System.out.println("show2..." + num);
        }

        static void show3()
        {
            System.out.println("show3..." + num);
        }
    }

}
class InnerClassDemo
{
    public static void main(String[] args) 
    {
        Outer.Inner2.show3();
    }
}

从上述程序代码可以看出,静态内部类里面甭管是静态方法还是非静态方法,只能访问外部类中的静态成员。
注意:当内部类中定义了静态成员,该内部类必须是static的。当外部类中的静态方法访问内部类时,内部类也必须是static的

class Outer {
    private static int x = 3;
    static class Inner { // 静态内部类
        static void function() { // 当内部类中定义了静态成员,该内部类必须是static的
            System.out.println("inner::::"+x); // 当内部类被static修饰后,只能直接访问外部类中的static成员
        }
    }
    static class Inner2 {
        void show() {
            System.out.println("inner2 show");
        }
    }
    public static void method() {
        new Inner2().show(); // 当外部类中的静态方法访问内部类时,内部类也必须是static的
    }
}

内部类定义在局部时

  1. 不可以被成员修饰符修饰,因为private、static不能修饰局部成员;
  2. 可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量,只能访问被final修饰的局部变量,主要原因是生命周期不同。注意:JDK1.8没这个区别了,但是被final修饰的变量是一个常量,只能被赋值一次,所以一经存在就不得更改
    为了能说明局部内部类只能访问被final修饰的局部变量,而且其主要原因是生命周期不同这一点,我们举例验证。

    class Outer
    {
        private int num = 4;
        Object obj;
        public void method()
        {
            /*final*/ int x = 5;
            class Inner extends Object // Inner本身继承Object
            {
                // 覆盖Object类中的toString()方法
                public String toString()
                {
                    System.out.println("x = " + x);
                    System.out.println("show..." + num);
                    return "Inner...abc";
                }
            }
            // 创建内部类的对象
            Inner in = new Inner();
            // 将内部类对象的地址赋值给obj
            obj = in;
        }
    
        public void function()
        {
            // 打印obj指向的对象的字符串表现形式
            System.out.println(obj.toString());
        }
    }
    
    
    class InnerClassDemo2 
    {
        public static void main(String[] args) 
        {
            new Outer().method();
        }
    }

    以上程序代码在JVM内存中的体现大概是这样的:
    这里写图片描述

匿名内部类

匿名内部类的概述

匿名内部类其实就是内部类的简写格式。定义匿名内部类有一个前提:内部类必须是继承一个类或者实现接口。匿名内部类定义的格式为:

new 父类或者接口() {定义子类的内容};

从上述格式可以看出,匿名内部类其实就是一个匿名子类对象,而且这个对象有点胖,可以理解为带内容的对象。还有一点需要注意的是匿名内部类中定义的方法最后不要超过3个。

abstract class AbsDemo {
    abstract void show();
}

class Outer {
    int x = 3;
    public void function() {
        AbsDemo d = new AbsDemo() {
            int num = 9;
            void show() {
                System.out.println("num==="+num);
            }
            void abc() {
                System.out.println("haha");
            }
        };
        d.show();
        // d.abc(); // 编译失败,因为父类中没有这个方法
    }
}

匿名内部类的练习

练习一,补全代码,通过匿名内部类。

interface Inter {
    void method();
}
class Test {
    // 补足代码。通过匿名内部类

}
class InnerClassTest {
    public static void main(String[] args) {
        Test.function().method();
    }
}

通过匿名内部类补全代码后:

interface Inter {
    void method();
}
class Test {
    // 补足代码。通过匿名内部类
    static Inter function() {
        return new Inter() {
            public void method() {
                System.out.println("Inter method");
            }
        };
    }
}
class InnerClassTest {
    public static void main(String[] args) {
        // Test.function():Test类中有一个静态的方法function
        // .method():function这个方法运算后的结果是一个对象,而且是一个Inter类型的对象,
        // 因为只有是Inter类型的对象,才可以调用method()
        Test.function().method();
    }
}

练习二,如果没有一个类继承或一个接口实现,还能使用匿名内部类吗?答案是可以的。

class InnerTest {

    public static void main(String[] args) {
        new Object() { // new Object() {}是Object类的子类对象
            public void function() {
                System.out.println("hello");
            }
        }.function();
    }
}

这是面试时可能遇到的一个小问题哟!

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:白松林 返回首页

打赏

李阿昀

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值