资讯专栏INFORMATION COLUMN

Java基础:Java核心技术提示的易忽略点 Ch6

Chaz / 2237人阅读

摘要:内部类就是这样一个情况,内部类的出现虽然在运行时会被拆分为独立的临时类,但是在代码层面加深了对代码的理解难度,所以很难说其优弊殊胜。

Core Java Volume 1 Key Points chap6 接口和抽象类的概念

接口和抽象类是Java继承链的基础,其区别也较为明显,在Java语言的设计中,允许接口的多实现,但不允许抽象类的多继承,这样做符合简洁明了的面向对象设计思路:也就是说类只可以简单地拥有唯一父模型(抽象类),但是可以拥有多种不同的特征(接口),这样的设计大大简化了Java的面向对象逻辑。

除此之外呢,它们还有这样的区别:
接口为了保证其描述特征的特性,只允许描述成员方法的特征(返回值、方法名、参数),不对成员方法做具体实现,而且在接口内部不允许使用私有属性和方法,究其根本,都是因为作为描述特征的接口不应该具有个性化的属性和方法;而抽象类像类一样,没有这样的限制,但是一般使用缺省的方法来统一定义模型的方法,所以方法体要么是空,要么是通用性较高的默认情况。

另外,在编码风格上,我们应该尽量给接口起名为形容词或副词,以贴近其是对类特征描述的本质,在JDK中这样的风格处处可见,比如大多数的接口会被以able结尾以说明实现这样的接口可以获得某些能力,比如实现Comparable接口可以获得被比较的能力,进而在对象数组中可以使用Arrays.sort方法来排序。而抽象类像类一样使用名词来定名,另外可以在尾部加上诸如helper,handler来说明其作用。

克隆

clone是Object这一通用父类的方法,这个方法是protected类型的,因此在用户编写的代码里是不能直接使用的。这个方法的目的为了实现对象的克隆,理论上讲,也就是复制一份完全相同的对象给我们使用。刚刚说了由于它是protected类型的,因此需要在我们使用的需要拷贝的对象的类里重写这个方法并把权限设为public的才行,不仅如此,为了类型检查的原因,我们还需要实现Cloneable这个marker接口(无方法接口)。这样做显然很繁琐,而且由于浅拷贝的问题,还很容易出错,因为很有可能拷贝出的新对象中某些子对象不是拷贝而仍然是引用。下面是个这样的例子:

class Email implements Cloneable{
    private String info;
    public Email(String info) {
        this.info = info;
    }
    
    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }
}
class People implements Cloneable {
    private String name;
    private Email mail;
    
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Email getMail() {
        return mail;
    }
    public void setMail(Email mail) {
        this.mail = mail;
    }
    
    @Override
    public String toString() {
        return "People [name=" + name + ", mail=" + mail.getInfo() + "]";
    }
    @Override
    public People clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        People people = (People) super.clone();
        return people;
    }
}
public class JustTest {
    public static void main(String[] args) throws InterruptedException {
        People people1 = new People();
        people1.setName("xiaoming");
        people1.setMail(new Email("xm@gmail.com"));
        People people2 = new People();
        try {
            people2 = (People) people1.clone();
            System.out.println(people1);
            System.out.println(people2);
            System.out.println("--------------------------------------");
            people1.getMail().setInfo("hi@gmail.com");
            System.out.println(people1);
            System.out.println(people2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

run:

People [name=xiaoming, mail=xm@gmail.com]
People [name=xiaoming, mail=xm@gmail.com]
--------------------------------------
People [name=xiaoming, mail=hi@gmail.com]
People [name=xiaoming, mail=hi@gmail.com]

这就是浅拷贝带来的问题,直接使用父类定义的clone就是会有这样的问题。

所以需要再重写的clone方法里对这样的问题进行修改:

@Override
    public People clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        People people = (People) super.clone();
        //people.setMail((Email) mail.clone());
        return people;
    }
内部类

在面向对象系统中,我们的类是对象的模板,接口表示类的特征,每个类拥有自己的成员变量和成员方法,对象拥有自己所属类的成员变量和成员方法,对象之间互相调用,通过方法来实现信息沟通并执行相应的功能。一切在运行时层面上其实就是这样,我们在代码层面可以保持和运行时层面相同的编码规则,这样做可以使得代码简洁有力,在编写和运行的过程中保持一致性。然而事实是任何一门语言都会在代码层面加入一些特性,注入语法特性,使得编码看起来更紧凑、编写更简单,但是破坏了这种代码层面和运行时层面的一致性。内部类就是这样一个情况,内部类的出现虽然在运行时会被拆分为独立的临时类,但是在代码层面加深了对代码的理解难度,所以很难说其优弊殊胜。

下面是个最简单的说明其用法的例子:

public class Outer {
    private String info="hello world";
    public class Inner{
        public void func(){
            System.out.println(info);
        }
    }
    public static void main(String[] args) {
        Outer outer = new Outer();
        Inner inner = outer.new Inner();
        inner.func();
    }
}

这个例子我们可以看出来,这里的内部类可以“看做”是Outer的一个成员,所以这样写就的内部类也叫作成员内部类,成员内部类可以直接使用外部类的成员和方法,而外部类则需要构造内部类的对象才能使用内部类。之所以说是“看做”,是因为在实际运行时内部类会被编译成一个临时类而脱离外部类,我们可以试试看:

$ javac Outer.java

编译后可以得到两个class文件:Outer.class和Outer$Inner.class,后者就是拆分好了的内部类class,所以在运行时可以使其和一般情况一样。
我们进一步分析其真身:

$ javap Outer
    Compiled from "Outer.java"
    public class Outer extends java.lang.Object{
        public Outer();
        public static void main(java.lang.String[]);
        static java.lang.String access$000(Outer);
    }

$ javap Outer$Inner
Compiled from "Outer.java"
public class Outer$Inner extends java.lang.Object{
    final Outer this$0;
    public Outer$Inner(Outer);
    public void func();
}

我们可以看到内部类的临时独立生成类的初始化方法中带有外部类类型的参数,这样就能够保证内部类可以完整访问外部类成员变量和成员方法。

那么我们为什么要使用这种看起来就不清不楚的内部类呢?我的理解是有些类实际上非常简单,多带带列出对整个系统意义不大,而且和某些类关系非常大,所以就直接把这个类放入和其关系大的类之中以形成内部类,这样在代码层面看起来更加简洁,但也不影响运行时的正确性。

除了上述的成员内部类外,还有局部内部类(内部类位于方法体内,这种内部类的作用域仅限于方法内部)、匿名内部类(不对内部类进行显式定义而直接在使用时顺带定义),但是无论是哪一种,其运行时都会被独立拆分并像一般情况那样运行。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65511.html

相关文章

  • Java基础Java核心技术提示的易忽略 Ch4

    摘要:所以,在读的核心技术的过程中,我记录下这些所谓的易忽略的问题,这些问题将会持续更新在我的这个的博客下,也算是激励自己重新挖掘这些基础问题的内涵。类路径只是让能够通过配置好的全路径名找到所需的外部类。 开篇Java是一门不那么简单也不那么复杂的语言,Java里面有很多问题和特性是容易被使用者忽视的,这些问题也许会难住新手,同时也许会是老手不小心跌入的无故之坑,只有精于对基础的提炼才能最大...

    ytwman 评论0 收藏0
  • Java基础Java核心技术提示的易忽略 Ch5

    摘要:而并不是父类对象的引用,而只是给编译器的一个提示性质的标志。或者自定义的提示在编译的时候使用当前子类的父类定义的构造器去初始化当前对象。所以,总结起来,的用法归为两种一是可以调用父类构造器,二是可以调用父类方法。 开篇Java是一门不那么简单也不那么复杂的语言,Java里面有很多问题和特性是容易被使用者忽视的,这些问题也许会难住新手,同时也许会是老手不小心跌入的无故之坑,只有精于对基础...

    weapon 评论0 收藏0
  • 从零开始学 Kotlin 之「2」数据类型

    摘要:前言大家好,这里是从零开始学之数据类型,本文首发于公众号,欢迎前往大家关注。输出布尔类型中的布尔类型用表示,它的值有和。若需要可空引用时,布尔类型的值会被装箱。此时程序会抛出异常最后从零开始学之数据类型到这里就结束了。 前言 大家好,这里是「从零开始学 Kotlin 之『2 』数据类型」,本文首发于公众号「Binguner」,欢迎前往大家关注。我会每周分享一些关于 Android 和...

    Awbeci 评论0 收藏0
  • Java 征途:行者的地图

    摘要:老实说,当时一进入世界的大门就晕了,各种规范概念和英文缩写词能把人整的晕晕乎乎。等新的英文缩写又出现了,一口老血还没来得及喷出,又重新振作开始新的学习征程。 showImg(http://upload-images.jianshu.io/upload_images/1131767-1c5d16e39435df10.jpg?imageMogr2/auto-orient/strip%7Ci...

    dkzwm 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<